This commit is contained in:
koehr 2023-12-06 16:37:53 +01:00 committed by Norman Köhring
commit da75595706
14 changed files with 2033 additions and 0 deletions

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
*.log
.cache
.DS_Store
src/.temp
node_modules
dist
.env
.env.*
.vitepress/cache
.vitepress/dist

56
.vitepress/config.mts Normal file
View file

@ -0,0 +1,56 @@
import { defineConfig } from 'vitepress'
import { defineConfigWithTheme } from 'vitepress'
import type { ThemeConfig } from './theme/Config'
export default defineConfigWithTheme<ThemeConfig>({
title: "k0r.386",
description: "Norman Köhrings Homepage",
themeConfig: {
commands: [{
command: 'about',
aliases: ['info'],
help: 'Who is Norman Köhring?',
message: 'Norman Köhring is a programmer, hacker and open source enthusiast based in Berlin. He is the Principal Frontend Engineer at Code Gaia, where he is a proud part of revolutionizing carbon emission reporting.',
uris: [{
label: 'Berlin', uri: 'https://www.openstreetmap.org/#map=12/52.4595/13.5335'
}, {
label: 'CodeGaia', uri: 'https://codegaia.io/'
}, {
label: 'Hacker?', uri: 'https://en.wikipedia.org/wiki/Hacker'
}]
}, {
command: 'contact',
aliases: ['email'],
help: 'How to contact Norman Köhring?',
message: [
'# other servers',
'email - n@koehr.in OR norman.koehring@mailbox.org',
'mastodon - mstdn.io/@koehr',
'twitter - twitter.com/koehr_in',
'github - github.com/nkoehring',
'instagram - instagram.com/coffee_n_code',
'500px - 500px.com/koehr',
'# my server',
'sourcecode - git.k0r.in/ (forgejo)',
'fediverse - m.k0r.in/@n (misskey)',
].join('\n'),
uris: [{
label: 'email', uri: 'mailto:n@koehr.in'
}, {
label: 'mastodon', uri: 'https://mstdn.io/@koehr'
}, {
label: 'twitter', uri: 'https://twitter.com/koehr_in'
}, {
label: 'github', uri: 'https://github.com/nkoehring'
}, {
label: 'instagram', uri: 'https://instagram.com/coffee_n_code'
}, {
label: '500px', uri: 'https://500px.com/koehr'
}, {
label: 'sourcecode', uri: 'https://git.k0r.in/'
}, {
label: 'fediverse', uri: 'https://m.k0r.in/@n'
}]
}],
}
})

View file

@ -0,0 +1,16 @@
export type Uri = {
label: string
uri: string
}
export type SimpleCommand = {
command: string,
aliases?: string[],
help?: string,
message: string,
uris: Uri[],
}
export interface ThemeConfig {
commands: SimpleCommand[]
}

View file

@ -0,0 +1,67 @@
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
import { useData } from 'vitepress'
import useTerminal from './useTerminal'
import titleArt from './titles'
// https://vitepress.dev/reference/runtime-api#usedata
const { site, frontmatter } = useData()
const enhancedReadability = ref(false)
const title = computed(() => {
const titleKey = frontmatter.value.title
const title = titleArt[titleKey] || titleArt['welcome']
return title.join('\n')
})
const content = computed(() => frontmatter.value.content ?? [])
const commands = computed(() => site.value.themeConfig.commands)
const prompt = '\n$> '
const lines = ref(title.value + '\n\n' + content.value.join('\n') + '\n' + prompt)
const textArea = ref<HTMLTextAreaElement | null>(null)
const footer = ref([])
onMounted(() => {
if (textArea.value === null) {
console.error('textarea is missing')
return
}
const { addText, clear, footerLinks } = useTerminal(textArea.value, commands.value)
clear()
addText('Hello World!')
watch(footerLinks, () => {
footer.value = footerLinks.value
}, { immediate: true })
})
</script>
<template>
<div id="screen" :class="{ 'enhanced-readability': enhancedReadability }">
<div id="wrap">
<div id="interlace" />
<div id="scanline" />
<div id="inner">
<textarea ref="textArea"
spellcheck="false"
autocorrect="false"
autocapitalize="false"
autocomplete="false"
autofocus
></textarea>
<footer>
<a v-for="({ uri, label}) in footer"
:href="uri"
target="_blank"
rel="noopener"
>
{{ label }}
</a>
</footer>
</div>
</div>
</div>
</template>

15
.vitepress/theme/index.ts Normal file
View file

@ -0,0 +1,15 @@
// https://vitepress.dev/guide/custom-theme
import Layout from './Layout.vue'
import type { Theme } from 'vitepress'
import '@fontsource/vt323'
import './reset.css'
import './style.css'
export default {
Layout,
enhanceApp({ app, router, siteData }) {
// ...
}
} satisfies Theme

114
.vitepress/theme/reset.css Normal file
View file

@ -0,0 +1,114 @@
/***
The new CSS reset - version 1.11.2 (last updated 15.11.2023)
GitHub page: https://github.com/elad2412/the-new-css-reset
***/
/*
Remove all the styles of the "User-Agent-Stylesheet", except for the 'display' property
- The "symbol *" part is to solve Firefox SVG sprite bug
- The "html" element is excluded, otherwise a bug in Chrome breaks the CSS hyphens property (https://github.com/elad2412/the-new-css-reset/issues/36)
*/
*:where(:not(html, iframe, canvas, img, svg, video, audio):not(svg *, symbol *)) {
all: unset;
display: revert;
}
/* Preferred box-sizing value */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* Fix mobile Safari increase font-size on landscape mode */
html {
-moz-text-size-adjust: none;
-webkit-text-size-adjust: none;
text-size-adjust: none;
}
/* Reapply the pointer cursor for anchor tags */
a,
button {
cursor: revert;
}
/* Remove list styles (bullets/numbers) */
ol,
ul,
menu,
summary {
list-style: none;
}
/* For images to not be able to exceed their container */
img {
max-inline-size: 100%;
max-block-size: 100%;
}
/* removes spacing between cells in tables */
table {
border-collapse: collapse;
}
/* Safari - solving issue when using user-select:none on the <body> text input doesn't working */
input,
textarea {
-webkit-user-select: auto;
}
/* revert the 'white-space' property for textarea elements on Safari */
textarea {
white-space: revert;
}
/* minimum style to allow to style meter element */
meter {
-webkit-appearance: revert;
appearance: revert;
}
/* preformatted text - use only for this feature */
:where(pre) {
all: revert;
box-sizing: border-box;
}
/* reset default text opacity of input placeholder */
::placeholder {
color: unset;
}
/* fix the feature of 'hidden' attribute.
display:revert; revert to element instead of attribute */
:where([hidden]) {
display: none;
}
/* revert for bug in Chromium browsers
- fix for the content editable attribute will work properly.
- webkit-user-select: auto; added for Safari in case of using user-select:none on wrapper element*/
:where([contenteditable]:not([contenteditable="false"])) {
-moz-user-modify: read-write;
-webkit-user-modify: read-write;
overflow-wrap: break-word;
-webkit-line-break: after-white-space;
-webkit-user-select: auto;
}
/* apply back the draggable feature - exist only in Chromium and Safari */
:where([draggable="true"]) {
-webkit-user-drag: element;
}
/* Revert Modal native behavior */
:where(dialog:modal) {
all: revert;
box-sizing: border-box;
}
/* Remove details summary webkit styles */
::-webkit-details-marker {
display: none;
}

162
.vitepress/theme/style.css Normal file
View file

@ -0,0 +1,162 @@
:root {
--black: #000;
--white: #FFF;
--red: #800;
--cyan: #AFE;
--violet: #C4C;
--green: #0C5;
--blue: #00A;
--yellow: #EE7;
--orange: #D85;
--brown: #640;
--light-red: #F77;
--light-green: #AF6;
--light-blue: #08F;
--grey-1: #333;
--grey-2: #777;
--grey-3: #BBB;
--crt-frame: #BFBCAD;
}
@keyframes scanline {
0% {
top: 0;
}
30% {
top: 100%;
}
100% {
top: 100%;
}
}
@media (prefers-color-scheme: light) {
body {
background: var(--cyan);
color: var(--blue);
}
}
@media (max-width: 1280px) {
#app {
width: 90vw;
height: 90vh;
}
}
body {
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-family: VT323, monospace;
font-size: 24px;
background: var(--black);
}
#app {
position: relative;
z-index: 10;
background: var(--crt-frame);
width: 1280px;
height: 900px;
max-width: 1280px;
max-height: 1024px;
box-shadow: inset 0.25em 0.25em 2px rgba(255, 255, 255, 0.4), inset -0.25em -0.25em 2px rgba(0, 0, 0, 0.4);
user-select: none;
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
perspective: 1000;
color: var(--cyan);
text-shadow: 0 0 2px yellow;
}
#screen.enhanced-readability {
text-shadow: none;
}
#screen {
width: calc(100% - 2.4em);
height: calc(100% - 2.4em);
overflow: hidden;
margin: 1.2em;
z-index: 20;
box-shadow: 0 0 1px 3px #0A0A0AB2;
background: var(--blue);
}
#app,
#screen {
border-radius: 1em;
}
#wrap {
position: relative;
height: 100%;
padding: 1.5em;
background: radial-gradient(ellipse at center, #FFF2 0%, #0003 100%);
}
#interlace {
position: absolute;
right: 0;
top: 0;
bottom: 0;
left: 0;
background: linear-gradient(#888 50%, #000 0);
background-repeat: repeat-y;
background-size: 100% 4px;
opacity: .1;
z-index: 21;
pointer-events: none;
}
#scanline {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1em;
background: linear-gradient(180deg, transparent 0, #EEE 50%, navy 0, transparent);
opacity: .1;
animation: scanline 6s linear infinite;
pointer-events: none;
}
#inner {
height: 100%;
background: #0003;
border-radius: .5em;
overflow-y: auto;
}
#inner::selection {
color: var(--blue);
background: var(--cyan);
}
#inner>textarea {
width: 100%;
height: calc(100% - 1.5em);
padding: 0 1em;
scroll-behavior: smooth;
}
#inner>footer {
display: flex;
flex-flow: row nowrap;
height: 1.5em;
line-height: 1.5em;
overflow-x: auto;
}
#inner>footer>a {
padding: 0 1em;
margin-right: 1em;
background: var(--cyan);
color: var(--blue);
}

View file

@ -0,0 +1,22 @@
export default {
welcome: [
" ________ __ ",
"| | | |.-----.| |.----.-----.--------.-----.",
"| | | || -__|| || __| _ | | -__|",
"|________||_____||__||____|_____|__|__|__|_____|",
],
aboutMe: [
" _______ __ __ _______ ",
"| _ | |--.-----.--.--.| |_ | | |.-----.",
"| | _ | _ | | || _| | || -__|",
"|___|___|_____|_____|_____||____| |__|_|__||_____|",
],
resume: [
" ______ ",
"| __ \.-----.-----.--.--.--------.-----.",
"| <| -__|__ --| | | | -__|",
"|___|__||_____|_____|_____|__|__|__|_____|",
],
}

View file

@ -0,0 +1,142 @@
import { ref } from 'vue'
import type { SimpleCommand, Uri } from './Config'
export default function useTerminal(inputEl: HTMLTextAreaElement, commands: SimpleCommand[]) {
const prompt = '\n$> '
const footerLinks = ref([])
function moveCursorToEnd() {
const pos = inputEl.value.length
// allow text selection
if (inputEl.selectionStart !== inputEl.selectionEnd) {
console.debug('allowing text selection', inputEl.selectionStart, inputEl.selectionEnd)
return
}
inputEl.setSelectionRange(pos, pos)
}
function setFocus() {
inputEl.focus()
}
function addText(text: string) {
const line = text + prompt
inputEl.value = inputEl.value + line
inputEl.scrollTop = inputEl.scrollTopMax
}
function addLine(line: string) {
addText('\n'+line)
}
function clear() {
inputEl.value = ''
addText('')
}
type SYS_OUT = 'NOT_FOUND' | 'USAGE' | 'INFO'
const SHELL = 'k0rSH'
const INFO = 'k0rSH v0.1: the k0r SHell, fiddled together by k0r -- https://k0r.in'
const PAD = 16
const USAGE = [
...commands.map(cmd => {
const command = `${(cmd.command+':').padEnd(PAD)}`
const help = `${cmd.help ?? 'no helptext provided'}`
const aliases = cmd.aliases ? ` (aliases: ${cmd.aliases?.join(', ')})` : ''
return `${command}${help}${aliases}`
}),
`${'help:'.padEnd(PAD)}This help text. (aliases: usage)`,
`${'version:'.padEnd(PAD)}Print version information.`,
`${'clear:'.padEnd(PAD)}Clear the screen.`,
].join('\n')
function systemOutput(output: SYS_OUT, arg = '') {
switch (output) {
case 'NOT_FOUND':
console.debug('command not found')
addLine(`${SHELL}: ${arg}: command not found...`)
break
case 'USAGE':
console.log('help is underway')
addLine(`${SHELL} - available commands:\n\n${USAGE}`)
break
case 'INFO':
console.log('explaining myself')
addLine(`${SHELL}: ${INFO}`)
break
}
}
function cursorAtPrompt() {
return inputEl.value.endsWith(prompt)
}
function setFooter(uris: Uri[]) {
footerLinks.value = uris
}
function getCurrentCommand() {
const value = inputEl.value
const start = value.lastIndexOf(prompt) + prompt.length
const end = value.length
return value.slice(start, end).trim()
}
function execUserCommand(cmd: string) {
const userCommand = commands.find(c => {
const commandMatch = c.command === cmd
const aliasesMatch = c.aliases.includes(cmd)
return commandMatch || aliasesMatch
})
if (!userCommand) return systemOutput('NOT_FOUND', cmd)
addLine(userCommand.message)
setFooter(userCommand.uris)
}
function handleCurrentCommand() {
const cmd = getCurrentCommand()
if (!cmd) {
addText('')
return
}
switch (cmd) {
case 'help':
case 'usage':
systemOutput('USAGE')
break
case 'version':
systemOutput('INFO')
break
case 'clear':
clear()
break
default:
execUserCommand(cmd)
}
}
function handleInput(ev) {
switch (ev.key) {
case 'Enter':
handleCurrentCommand()
ev.preventDefault()
break
case 'Backspace':
if (cursorAtPrompt()) ev.preventDefault()
break
}
}
inputEl.addEventListener('focus', () => moveCursorToEnd())
inputEl.addEventListener('blur', () => inputEl.focus())
inputEl.addEventListener('click', () => moveCursorToEnd())
inputEl.addEventListener('keydown', handleInput)
return { addText, addLine, setFocus, clear, footerLinks }
}

49
api-examples.md Normal file
View file

@ -0,0 +1,49 @@
---
outline: deep
---
# Runtime API Examples
This page demonstrates usage of some of the runtime APIs provided by VitePress.
The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files:
```md
<script setup>
import { useData } from 'vitepress'
const { theme, page, frontmatter } = useData()
</script>
## Results
### Theme Data
<pre>{{ theme }}</pre>
### Page Data
<pre>{{ page }}</pre>
### Page Frontmatter
<pre>{{ frontmatter }}</pre>
```
<script setup>
import { useData } from 'vitepress'
const { site, theme, page, frontmatter } = useData()
</script>
## Results
### Theme Data
<pre>{{ theme }}</pre>
### Page Data
<pre>{{ page }}</pre>
### Page Frontmatter
<pre>{{ frontmatter }}</pre>
## More
Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata).

13
index.md Normal file
View file

@ -0,0 +1,13 @@
---
home: true
title: 'welcome'
content: [
'This is the homepage of Norman Köhring,',
'a programmer, OpenSource enthusiast and hacker based in Berlin, Germany.',
'',
'I call myself a code artist because programming can and should be seen as a creative process. Therefore code is art. I love to craft pieces of art with code that are beautiful and elegant in their simplicity, readability and architecture.',
'',
'Type "help" to see a list of available commands.',
'It is also possible to use the links in the footer.'
]
---

85
markdown-examples.md Normal file
View file

@ -0,0 +1,85 @@
# Markdown Extension Examples
This page demonstrates some of the built-in markdown extensions provided by VitePress.
## Syntax Highlighting
VitePress provides Syntax Highlighting powered by [Shikiji](https://github.com/antfu/shikiji), with additional features like line-highlighting:
**Input**
````md
```js{4}
export default {
data () {
return {
msg: 'Highlighted!'
}
}
}
```
````
**Output**
```js{4}
export default {
data () {
return {
msg: 'Highlighted!'
}
}
}
```
## Custom Containers
**Input**
```md
::: info
This is an info box.
:::
::: tip
This is a tip.
:::
::: warning
This is a warning.
:::
::: danger
This is a dangerous warning.
:::
::: details
This is a details block.
:::
```
**Output**
::: info
This is an info box.
:::
::: tip
This is a tip.
:::
::: warning
This is a warning.
:::
::: danger
This is a dangerous warning.
:::
::: details
This is a details block.
:::
## More
Check out the documentation for the [full list of markdown extensions](https://vitepress.dev/guide/markdown).

14
package.json Normal file
View file

@ -0,0 +1,14 @@
{
"scripts": {
"docs:dev": "vitepress dev",
"docs:build": "vitepress build",
"docs:preview": "vitepress preview"
},
"devDependencies": {
"vitepress": "1.0.0-rc.31",
"vue": "^3.3.10"
},
"dependencies": {
"@fontsource/vt323": "^5.0.8"
}
}

1268
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff