diff options
| author | Kyren223 <contact@kyren.codes> | 2026-10-07 03:14:47 +0300 |
|---|---|---|
| committer | Kyren223 <contact@kyren.codes> | 2026-10-07 03:14:47 +0300 |
| commit | 6e70fe447ec9504757fbdc152b5b3697c9af8def (patch) | |
| tree | 4da46c4f479e87e17ac5f786baafff4e285b37c1 | |
| parent | ba1be9573ef20d6424ae987257a1c727c47d4b53 (diff) | |
Initial iteration
| -rw-r--r-- | public/main.css | 6 | ||||
| -rw-r--r-- | public/style.css | 162 | ||||
| -rw-r--r-- | public/styles/fonts/JetBrainsMono.css | 108 | ||||
| -rw-r--r-- | public/styles/fonts/Monocraft.css | 108 | ||||
| -rw-r--r-- | public/styles/markdown/asides.css | 49 | ||||
| -rw-r--r-- | public/styles/markdown/markdown.css | 251 | ||||
| -rw-r--r-- | public/styles/markdown/props.css | 189 | ||||
| -rw-r--r-- | public/styles/markdown/reset.css | 48 | ||||
| -rw-r--r-- | public/styles/markdown/shiki.css | 13 | ||||
| -rw-r--r-- | public/styles/markdown/util.css | 61 | ||||
| -rw-r--r-- | public/styles/terminal.css | 47 | ||||
| -rw-r--r-- | public/theme.js | 12 | ||||
| -rw-r--r-- | website.go | 285 | ||||
| -rw-r--r-- | website.templ | 147 | ||||
| -rw-r--r-- | website_templ.go | 356 |
15 files changed, 1658 insertions, 184 deletions
diff --git a/public/main.css b/public/main.css index 8457bc5..f93965c 100644 --- a/public/main.css +++ b/public/main.css @@ -1,5 +1,7 @@ -@import "tailwindcss"; +@layer theme, base, components, utilities; +@import "tailwindcss/theme.css" layer(theme); +@import "tailwindcss/preflight.css" layer(base); +@import "tailwindcss/utilities.css" layer(utilities); @source "../**/*.go"; @source "../**/*.templ"; -@source ".."; diff --git a/public/style.css b/public/style.css index 5369786..d1e775f 100644 --- a/public/style.css +++ b/public/style.css @@ -7,11 +7,11 @@ sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; - --color-fuchsia-600: oklch(59.1% 0.293 322.896); --color-gray-700: oklch(37.3% 0.034 259.733); --spacing: 0.25rem; --text-3xl: 1.875rem; --text-3xl--line-height: calc(2.25 / 1.875); + --font-weight-normal: 400; --font-weight-bold: 700; --default-font-family: var(--font-sans); --default-mono-font-family: var(--font-mono); @@ -238,6 +238,15 @@ .mb-0 { margin-bottom: 0px; } + .mb-6 { + margin-bottom: calc(var(--spacing) * 6); + } + .mb-6\.25 { + margin-bottom: calc(var(--spacing) * 6.25); + } + .mb-\[1\.5625rem\] { + margin-bottom: 1.5625rem; + } .mb-\[1\.5625vw\] { margin-bottom: 1.5625vw; } @@ -274,6 +283,9 @@ .h-\[9\.5vw\] { height: 9.5vw; } + .h-\[40rem\] { + height: 40rem; + } .h-\[62\%\] { height: 62%; } @@ -298,15 +310,42 @@ .w-0 { width: 0px; } + .w-300 { + width: calc(var(--spacing) * 300); + } .w-\[30\%\] { width: 30%; } + .w-\[40rem\] { + width: 40rem; + } .w-\[67\%\] { width: 67%; } .w-\[70\%\] { width: 70%; } + .w-\[75\.0rem\] { + width: 75.0rem; + } + .w-\[75\.001rem\] { + width: 75.001rem; + } + .w-\[75\.1rem\] { + width: 75.1rem; + } + .w-\[75\.12rem\] { + width: 75.12rem; + } + .w-\[75\.13rem\] { + width: 75.13rem; + } + .w-\[75\.123rem\] { + width: 75.123rem; + } + .w-\[75rem\] { + width: 75rem; + } .w-\[95vw\] { width: 95vw; } @@ -325,6 +364,9 @@ .flex-1 { flex: 1; } + .border-collapse { + border-collapse: collapse; + } .transform { transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,); } @@ -352,12 +394,21 @@ .justify-between { justify-content: space-between; } + .gap-10 { + gap: calc(var(--spacing) * 10); + } .gap-16 { gap: calc(var(--spacing) * 16); } + .gap-20 { + gap: calc(var(--spacing) * 20); + } .gap-\[1\.25vw\] { gap: 1.25vw; } + .gap-\[1ch\] { + gap: 1ch; + } .gap-x-\[5vw\] { column-gap: 5vw; } @@ -374,6 +425,9 @@ .overflow-auto { overflow: auto; } + .overflow-hidden { + overflow: hidden; + } .overflow-x-hidden { overflow-x: hidden; } @@ -384,18 +438,59 @@ border-style: var(--tw-border-style); border-width: 1px; } + .border-0 { + border-style: var(--tw-border-style); + border-width: 0px; + } + .border-1 { + border-style: var(--tw-border-style); + border-width: 1px; + } + .border-2 { + border-style: var(--tw-border-style); + border-width: 2px; + } + .border-3 { + border-style: var(--tw-border-style); + border-width: 3px; + } + .border-10 { + border-style: var(--tw-border-style); + border-width: 10px; + } .border-\[0\.1vw\] { border-style: var(--tw-border-style); border-width: 0.1vw; } + .border-\[0\.2rem\] { + border-style: var(--tw-border-style); + border-width: 0.2rem; + } + .border-\[0\.25rem\] { + border-style: var(--tw-border-style); + border-width: 0.25rem; + } .border-\[0\.25vh\] { border-style: var(--tw-border-style); border-width: 0.25vh; } + .border-\[0\.125rem\] { + border-style: var(--tw-border-style); + border-width: 0.125rem; + } .border-solid { --tw-border-style: solid; border-style: solid; } + .border-\(--alt\) { + border-color: var(--alt); + } + .border-\(--text\) { + border-color: var(--text); + } + .border-\[color\:var\(--alt\)\] { + border-color: var(--alt); + } .border-\[var\(--alt\)\] { border-color: var(--alt); } @@ -414,12 +509,42 @@ .p-0 { padding: 0px; } + .p-2 { + padding: calc(var(--spacing) * 2); + } + .p-3 { + padding: calc(var(--spacing) * 3); + } + .p-4 { + padding: calc(var(--spacing) * 4); + } + .p-8 { + padding: calc(var(--spacing) * 8); + } + .p-10 { + padding: calc(var(--spacing) * 10); + } .p-\[0\.8em\] { padding: 0.8em; } + .p-\[2\.0rem\] { + padding: 2.0rem; + } + .p-\[2\.2rem\] { + padding: 2.2rem; + } .p-\[2\.2svh\] { padding: 2.2svh; } + .p-\[2\.5rem\] { + padding: 2.5rem; + } + .p-\[2\.rem\] { + padding: 2.rem; + } + .p-\[2rem\] { + padding: 2rem; + } .p-\[3vw\] { padding: 3vw; } @@ -456,6 +581,9 @@ .font-\[\'JetBrains_Mono\'\] { font-family: 'JetBrains Mono'; } + .font-mono { + font-family: var(--font-mono); + } .text-3xl { font-size: var(--text-3xl); line-height: var(--tw-leading, var(--text-3xl--line-height)); @@ -463,6 +591,12 @@ .text-\[0\.96vw\] { font-size: 0.96vw; } + .text-\[1\.125rem\] { + font-size: 1.125rem; + } + .text-\[1\.375rem\] { + font-size: 1.375rem; + } .text-\[2\.5vw\] { font-size: 2.5vw; } @@ -488,9 +622,19 @@ --tw-font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold); } + .font-normal { + --tw-font-weight: var(--font-weight-normal); + font-weight: var(--font-weight-normal); + } + .text-\(--primary\) { + color: var(--primary); + } .text-\[var\(--accent\)\] { color: var(--accent); } + .text-\[var\(--primary\)\] { + color: var(--primary); + } .text-\[var\(--secondary\)\] { color: var(--secondary); } @@ -500,6 +644,10 @@ .opacity-0 { opacity: 0%; } + .outline { + outline-style: var(--tw-outline-style); + outline-width: 1px; + } @media (hover: hover) { .group-hover\:text-\[var\(--alt\)\]:is(:where(.group):hover *) { color: var(--alt); @@ -510,6 +658,12 @@ .group-hover\:underline:is(:where(.group):hover *) { text-decoration-line: underline; } + .hover\:text-\(--accent\):hover { + color: var(--accent); + } + .hover\:text-\[var\(--accent\)\]:hover { + color: var(--accent); + } .hover\:text-\[var\(--alt\)\]:hover { color: var(--alt); } @@ -716,6 +870,11 @@ syntax: "*"; inherits: false; } +@property --tw-outline-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} @layer properties { @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { *, ::before, ::after, ::backdrop { @@ -727,6 +886,7 @@ --tw-space-x-reverse: 0; --tw-border-style: solid; --tw-font-weight: initial; + --tw-outline-style: solid; } } } diff --git a/public/styles/fonts/JetBrainsMono.css b/public/styles/fonts/JetBrainsMono.css new file mode 100644 index 0000000..807fe1f --- /dev/null +++ b/public/styles/fonts/JetBrainsMono.css @@ -0,0 +1,108 @@ +@font-face { + font-family: "Jetbrains Mono"; + src: url("/fonts/JetBrainsMono-Thin.woff2") format("woff2"); + font-weight: 100; + font-style: normal; +} +@font-face { + font-family: "Jetbrains Mono"; + src: url("/fonts/JetBrainsMono-ThinItalic.woff2") format("woff2"); + font-weight: 100; + font-style: italic; +} +@font-face { + font-family: "Jetbrains Mono"; + src: url("/fonts/JetBrainsMono-ExtraLight.woff2") format("woff2"); + font-weight: 200; + font-style: normal; +} +@font-face { + font-family: "Jetbrains Mono"; + src: url("/fonts/JetBrainsMono-ExtraLightItalic.woff2") format("woff2"); + font-weight: 200; + font-style: italic; +} +@font-face { + font-family: "Jetbrains Mono"; + src: url("/fonts/JetBrainsMono-Light.woff2") format("woff2"); + font-weight: 300; + font-style: normal; +} +@font-face { + font-family: "Jetbrains Mono"; + src: url("/fonts/JetBrainsMono-LightItalic.woff2") format("woff2"); + font-weight: 300; + font-style: italic; +} +@font-face { + font-family: "Jetbrains Mono"; + src: url("/fonts/JetBrainsMono-Regular.woff2") format("woff2"); + font-weight: 400; + font-style: normal; +} +@font-face { + font-family: "Jetbrains Mono"; + src: url("/fonts/JetBrainsMono-Italic.woff2") format("woff2"); + font-weight: 400; + font-style: italic; +} +@font-face { + font-family: "Jetbrains Mono"; + src: url("/fonts/JetBrainsMono-Medium.woff2") format("woff2"); + font-weight: 500; + font-style: normal; +} +@font-face { + font-family: "Jetbrains Mono"; + src: url("/fonts/JetBrainsMono-MediumItalic.woff2") format("woff2"); + font-weight: 500; + font-style: italic; +} +@font-face { + font-family: "Jetbrains Mono"; + src: url("/fonts/JetBrainsMono-SemiBold.woff2") format("woff2"); + font-weight: 600; + font-style: normal; +} +@font-face { + font-family: "Jetbrains Mono"; + src: url("/fonts/JetBrainsMono-SemiBoldItalic.woff2") format("woff2"); + font-weight: 600; + font-style: italic; +} +@font-face { + font-family: "Jetbrains Mono"; + src: url("/fonts/JetBrainsMono-Bold.woff2") format("woff2"); + font-weight: 700; + font-style: normal; +} +@font-face { + font-family: "Jetbrains Mono"; + src: url("/fonts/JetBrainsMono-BoldItalic.woff2") format("woff2"); + font-weight: 700; + font-style: italic; +} +@font-face { + font-family: "Jetbrains Mono"; + src: url("/fonts/JetBrainsMono-ExtraBold.woff2") format("woff2"); + font-weight: 800; + font-style: normal; +} +@font-face { + font-family: "Jetbrains Mono"; + src: url("/fonts/JetBrainsMono-ExtraBoldItalic.woff2") format("woff2"); + font-weight: 800; + font-style: italic; +} +@font-face { + font-family: "Jetbrains Mono"; + src: url("/fonts/JetBrainsMono-ExtraBold.woff2") format("woff2"); + font-weight: 900; + font-style: normal; +} +@font-face { + font-family: "Jetbrains Mono"; + src: url("/fonts/JetBrainsMono-ExtraBoldItalic.woff2") format("woff2"); + font-weight: 900; + font-style: italic; +} diff --git a/public/styles/fonts/Monocraft.css b/public/styles/fonts/Monocraft.css new file mode 100644 index 0000000..34fd24a --- /dev/null +++ b/public/styles/fonts/Monocraft.css @@ -0,0 +1,108 @@ +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft-ExtraLight.ttf") format("truetype"); + font-weight: 100; + font-style: normal; +} +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft-ExtraLight-Italic.ttf") format("truetype"); + font-weight: 100; + font-style: italic; +} +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft-ExtraLight.ttf") format("truetype"); + font-weight: 200; + font-style: normal; +} +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft-ExtraLight-Italic.ttf") format("truetype"); + font-weight: 200; + font-style: italic; +} +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft-Light.ttf") format("truetype"); + font-weight: 300; + font-style: normal; +} +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft-Light-Italic.ttf") format("truetype"); + font-weight: 300; + font-style: italic; +} +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft.ttf") format("truetype"); + font-weight: 400; + font-style: normal; +} +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft-Italic.ttf") format("truetype"); + font-weight: 400; + font-style: italic; +} +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft.ttf") format("truetype"); + font-weight: 500; + font-style: normal; +} +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft-Italic.ttf") format("truetype"); + font-weight: 500; + font-style: italic; +} +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft-SemiBold.ttf") format("truetype"); + font-weight: 600; + font-style: normal; +} +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft-SemiBold-Italic.ttf") format("truetype"); + font-weight: 600; + font-style: italic; +} +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft-Bold.ttf") format("truetype"); + font-weight: 700; + font-style: normal; +} +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft-Bold-Italic.ttf") format("truetype"); + font-weight: 700; + font-style: italic; +} +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft-Black.ttf") format("truetype"); + font-weight: 800; + font-style: normal; +} +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft-Black-Italic.ttf") format("truetype"); + font-weight: 800; + font-style: italic; +} +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft-Black.ttf") format("truetype"); + font-weight: 900; + font-style: normal; +} +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft-Black-Italic.ttf") format("truetype"); + font-weight: 900; + font-style: italic; +} diff --git a/public/styles/markdown/asides.css b/public/styles/markdown/asides.css new file mode 100644 index 0000000..86aff8d --- /dev/null +++ b/public/styles/markdown/asides.css @@ -0,0 +1,49 @@ +.starlight-aside { + padding: 1rem; + border-inline-start: 0.25rem solid var(--sl-color-asides-border); + color: var(--sl-color-white); +} +.starlight-aside--note { + --sl-color-asides-text-accent: var(--sl-color-blue-high); + --sl-color-asides-border: var(--sl-color-blue); + background-color: var(--sl-color-blue-low); +} +.starlight-aside--tip { + --sl-color-asides-text-accent: var(--sl-color-purple-high); + --sl-color-asides-border: var(--sl-color-purple); + background-color: var(--sl-color-purple-low); +} +.starlight-aside--caution { + --sl-color-asides-text-accent: var(--sl-color-orange-high); + --sl-color-asides-border: var(--sl-color-orange); + background-color: var(--sl-color-orange-low); +} +.starlight-aside--danger { + --sl-color-asides-text-accent: var(--sl-color-red-high); + --sl-color-asides-border: var(--sl-color-red); + background-color: var(--sl-color-red-low); +} + +.starlight-aside__title { + display: flex; + gap: 0.5rem; + align-items: center; + font-size: var(--sl-text-h5); + font-weight: 600; + line-height: var(--sl-line-height-headings); + color: var(--sl-color-asides-text-accent); +} + +.starlight-aside__icon { + font-size: 1.333em; + width: 1em; + height: 1em; +} + +.starlight-aside__title + .starlight-aside__content { + margin-top: 0.5rem; +} + +.starlight-aside__content a { + color: var(--sl-color-asides-text-accent); +} diff --git a/public/styles/markdown/markdown.css b/public/styles/markdown/markdown.css new file mode 100644 index 0000000..eccbf7d --- /dev/null +++ b/public/styles/markdown/markdown.css @@ -0,0 +1,251 @@ +.sl-markdown-content + :not(.expressive-code *) + + :not(a, strong, em, del, span, input, code, br) + + :not(a, strong, em, del, span, input, code, br, :where(.not-content *)) { + margin-top: 1rem; +} + +/* Headings after non-headings have more spacing. */ +.sl-markdown-content + :not(h1, h2, h3, h4, h5, h6) + + :is(h1, h2, h3, h4, h5, h6):not(:where(.not-content *)) { + margin-top: 1.5em; +} + +.sl-markdown-content li + li:not(:where(.not-content *)), +.sl-markdown-content dt + dt:not(:where(.not-content *)), +.sl-markdown-content dt + dd:not(:where(.not-content *)), +.sl-markdown-content dd + dd:not(:where(.not-content *)) { + margin-top: 0.25rem; +} + +.sl-markdown-content li:not(:where(.not-content *)) { + overflow-wrap: anywhere; +} + +.sl-markdown-content + li + > :last-child:not(li, ul, ol):not( + a, + strong, + em, + del, + span, + input, + :where(.not-content *) + ) { + margin-bottom: 1.25rem; +} + +.sl-markdown-content dt:not(:where(.not-content *)) { + font-weight: 700; +} +.sl-markdown-content dd:not(:where(.not-content *)) { + padding-inline-start: 1rem; +} + +.sl-markdown-content :is(h1, h2, h3, h4, h5, h6):not(:where(.not-content *)) { + color: var(--sl-color-white); + line-height: var(--sl-line-height-headings); + font-weight: 600; + font-size: 10000%; +} + +.sl-markdown-content + :is(img, picture, video, canvas, svg, iframe):not(:where(.not-content *)) { + display: block; + max-width: 100%; + height: auto; +} + +.sl-markdown-content h1:not(:where(.not-content *)) { + font-size: var(--sl-text-h1); +} +.sl-markdown-content h2:not(:where(.not-content *)) { + font-size: var(--sl-text-h2); +} +.sl-markdown-content h3:not(:where(.not-content *)) { + font-size: var(--sl-text-h3); +} +.sl-markdown-content h4:not(:where(.not-content *)) { + font-size: var(--sl-text-h4); +} +.sl-markdown-content h5:not(:where(.not-content *)) { + font-size: var(--sl-text-h5); +} +.sl-markdown-content h6:not(:where(.not-content *)) { + font-size: var(--sl-text-h6); +} + +.sl-markdown-content a:not(:where(.not-content *)) { + color: var(--sl-color-text-accent); +} +.sl-markdown-content a:hover:not(:where(.not-content *)) { + color: var(--sl-color-white); +} + +.sl-markdown-content code:not(:where(.not-content *)) { + background-color: var(--sl-color-bg-inline-code); + margin-block: -0.125rem; + padding: 0.125rem 0.375rem; + font-size: var(--sl-text-code-sm); +} +.sl-markdown-content :is(h1, h2, h3, h4, h5, h6) code { + font-size: inherit; +} + +.sl-markdown-content pre:not(:where(.not-content *)) { + border: 1px solid var(--sl-color-gray-5); + padding: 0.75rem 1rem; + font-size: var(--sl-text-code); + tab-size: 2; +} + +.sl-markdown-content pre code:not(:where(.not-content *)) { + all: unset; + font-family: var(--__sl-font-mono); +} + +.sl-markdown-content blockquote:not(:where(.not-content *)) { + border-inline-start: 1px solid var(--sl-color-gray-5); + padding-inline-start: 1rem; +} + +/* Table styling */ +.sl-markdown-content table:not(:where(.not-content *)) { + display: block; + overflow: auto; + border-spacing: 0; +} +.sl-markdown-content :is(th, td):not(:where(.not-content *)) { + border-bottom: 1px solid var(--sl-color-gray-5); + padding: 0.5rem 1rem; + /* Align text to the top of the row in multiline tables. */ + vertical-align: baseline; +} +.sl-markdown-content + :is(th:first-child, td:first-child):not(:where(.not-content *)) { + padding-inline-start: 0; +} +.sl-markdown-content + :is(th:last-child, td:last-child):not(:where(.not-content *)) { + padding-inline-end: 0; +} +.sl-markdown-content th:not(:where(.not-content *)) { + color: var(--sl-color-white); + font-weight: 600; +} +/* Align headings to the start of the line unless set by the `align` attribute. */ +.sl-markdown-content th:not([align]):not(:where(.not-content *)) { + text-align: start; +} +/* <table>s, <hr>s, and <blockquote>s inside asides */ +.sl-markdown-content + .starlight-aside + :is(th, td, hr, blockquote):not(:where(.not-content *)) { + border-color: var(--sl-color-gray-4); +} +@supports ( + border-color: + color-mix(in srgb, var(--sl-color-asides-text-accent) 30%, transparent) +) { + .sl-markdown-content + .starlight-aside + :is(th, td, hr, blockquote):not(:where(.not-content *)) { + border-color: color-mix( + in srgb, + var(--sl-color-asides-text-accent) 30%, + transparent + ); + } +} + +/* <code> inside asides */ +@supports ( + border-color: + color-mix(in srgb, var(--sl-color-asides-text-accent) 12%, transparent) +) { + .sl-markdown-content .starlight-aside code:not(:where(.not-content *)) { + background-color: color-mix( + in srgb, + var(--sl-color-asides-text-accent) 12%, + transparent + ); + } +} + +.sl-markdown-content hr:not(:where(.not-content *)) { + border: 0; + border-bottom: 1px solid var(--sl-color-hairline); +} + +/* <details> and <summary> styles */ +.sl-markdown-content details:not(:where(.not-content *)) { + --sl-details-border-color: var(--sl-color-gray-5); + --sl-details-border-color--hover: var(--sl-color-text-accent); + + border-inline-start: 2px solid var(--sl-details-border-color); + padding-inline-start: 1rem; +} +.sl-markdown-content details:not([open]):hover:not(:where(.not-content *)), +.sl-markdown-content details:has(> summary:hover):not(:where(.not-content *)) { + border-color: var(--sl-details-border-color--hover); +} +.sl-markdown-content summary:not(:where(.not-content *)) { + color: var(--sl-color-white); + cursor: pointer; + display: block; /* Needed to hide the default marker in some browsers. */ + font-weight: 600; + /* Expand the outline so that the marker cannot distort it. */ + margin-inline-start: -0.5rem; + padding-inline-start: 0.5rem; +} +.sl-markdown-content details[open] > summary:not(:where(.not-content *)) { + margin-bottom: 1rem; +} + +/* <summary> marker styles */ +.sl-markdown-content summary:not(:where(.not-content *))::marker, +.sl-markdown-content + summary:not(:where(.not-content *))::-webkit-details-marker { + display: none; +} +.sl-markdown-content summary:not(:where(.not-content *))::before { + --sl-details-marker-size: 1.25rem; + + background-color: currentColor; + content: ""; + display: inline-block; + height: var(--sl-details-marker-size); + width: var(--sl-details-marker-size); + margin-inline: calc((var(--sl-details-marker-size) / 4) * -1) 0.25rem; + vertical-align: middle; + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M14.8 11.3 10.6 7a1 1 0 1 0-1.4 1.5l3.5 3.5-3.5 3.5a1 1 0 0 0 0 1.4 1 1 0 0 0 .7.3 1 1 0 0 0 .7-.3l4.2-4.2a1 1 0 0 0 0-1.4Z'/%3E%3C/svg%3E%0A"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M14.8 11.3 10.6 7a1 1 0 1 0-1.4 1.5l3.5 3.5-3.5 3.5a1 1 0 0 0 0 1.4 1 1 0 0 0 .7.3 1 1 0 0 0 .7-.3l4.2-4.2a1 1 0 0 0 0-1.4Z'/%3E%3C/svg%3E%0A"); + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; +} +@media (prefers-reduced-motion: no-preference) { + .sl-markdown-content summary:not(:where(.not-content *))::before { + transition: transform 0.2s ease-in-out; + } +} +.sl-markdown-content + details[open] + > summary:not(:where(.not-content *))::before { + transform: rotateZ(90deg); +} +[dir="rtl"] .sl-markdown-content summary:not(:where(.not-content *))::before, +.sl-markdown-content [dir="rtl"] summary:not(:where(.not-content *))::before { + transform: rotateZ(180deg); +} +/* <summary> with only a paragraph automatically added when using MDX */ +.sl-markdown-content summary:not(:where(.not-content *)) p:only-child { + display: inline; +} + +/* <details> styles inside asides */ +.sl-markdown-content .starlight-aside details:not(:where(.not-content *)) { + --sl-details-border-color: var(--sl-color-asides-border); + --sl-details-border-color--hover: var(--sl-color-asides-text-accent); +} diff --git a/public/styles/markdown/props.css b/public/styles/markdown/props.css new file mode 100644 index 0000000..58a09f6 --- /dev/null +++ b/public/styles/markdown/props.css @@ -0,0 +1,189 @@ +:root, +::backdrop { + /* Colors (dark mode) */ + --sl-color-white: hsl(0, 0%, 100%); /* “white” */ + --sl-color-gray-1: hsl(224, 20%, 94%); + --sl-color-gray-2: hsl(224, 6%, 77%); + --sl-color-gray-3: hsl(224, 6%, 56%); + --sl-color-gray-4: hsl(224, 7%, 36%); + --sl-color-gray-5: hsl(224, 10%, 23%); + --sl-color-gray-6: hsl(224, 14%, 16%); + --sl-color-black: hsl(224, 10%, 10%); + + --sl-hue-orange: 41; + --sl-color-orange-low: hsl(var(--sl-hue-orange), 39%, 22%); + --sl-color-orange: hsl(var(--sl-hue-orange), 82%, 63%); + --sl-color-orange-high: hsl(var(--sl-hue-orange), 82%, 87%); + --sl-hue-green: 101; + --sl-color-green-low: hsl(var(--sl-hue-green), 39%, 22%); + --sl-color-green: hsl(var(--sl-hue-green), 82%, 63%); + --sl-color-green-high: hsl(var(--sl-hue-green), 82%, 80%); + --sl-hue-blue: 234; + --sl-color-blue-low: hsl(var(--sl-hue-blue), 54%, 20%); + --sl-color-blue: hsl(var(--sl-hue-blue), 100%, 60%); + --sl-color-blue-high: hsl(var(--sl-hue-blue), 100%, 87%); + --sl-hue-purple: 281; + --sl-color-purple-low: hsl(var(--sl-hue-purple), 39%, 22%); + --sl-color-purple: hsl(var(--sl-hue-purple), 82%, 63%); + --sl-color-purple-high: hsl(var(--sl-hue-purple), 82%, 89%); + --sl-hue-red: 339; + --sl-color-red-low: hsl(var(--sl-hue-red), 39%, 22%); + --sl-color-red: hsl(var(--sl-hue-red), 82%, 63%); + --sl-color-red-high: hsl(var(--sl-hue-red), 82%, 87%); + + --sl-color-accent-low: hsl(224, 54%, 20%); + --sl-color-accent: hsl(224, 100%, 60%); + --sl-color-accent-high: hsl(224, 100%, 85%); + + --sl-color-text: var(--sl-color-gray-2); + --sl-color-text-accent: var(--sl-color-accent-high); + --sl-color-text-invert: var(--sl-color-accent-low); + --sl-color-bg: var(--sl-color-black); + --sl-color-bg-nav: var(--sl-color-gray-6); + --sl-color-bg-sidebar: var(--sl-color-gray-6); + --sl-color-bg-inline-code: var(--sl-color-gray-5); + --sl-color-bg-accent: var(--sl-color-accent-high); + --sl-color-hairline-light: var(--sl-color-gray-5); + --sl-color-hairline: var(--sl-color-gray-6); + --sl-color-hairline-shade: var(--sl-color-black); + + --sl-color-backdrop-overlay: hsla(223, 13%, 10%, 0.66); + + /* Shadows (dark mode) */ + --sl-shadow-sm: 0px 1px 1px hsla(0, 0%, 0%, 0.12), + 0px 2px 1px hsla(0, 0%, 0%, 0.24); + --sl-shadow-md: 0px 8px 4px hsla(0, 0%, 0%, 0.08), + 0px 5px 2px hsla(0, 0%, 0%, 0.08), 0px 3px 2px hsla(0, 0%, 0%, 0.12), + 0px 1px 1px hsla(0, 0%, 0%, 0.15); + --sl-shadow-lg: 0px 25px 7px hsla(0, 0%, 0%, 0.03), + 0px 16px 6px hsla(0, 0%, 0%, 0.1), 0px 9px 5px hsla(223, 13%, 10%, 0.33), + 0px 4px 4px hsla(0, 0%, 0%, 0.75), 0px 4px 2px hsla(0, 0%, 0%, 0.25); + + /* Text size and line height */ + --sl-text-2xs: 0.75rem; /* 12px */ + --sl-text-xs: 0.8125rem; /* 13px */ + --sl-text-sm: 0.875rem; /* 14px */ + --sl-text-base: 1rem; /* 16px */ + --sl-text-lg: 1.125rem; /* 18px */ + --sl-text-xl: 1.25rem; /* 20px */ + --sl-text-2xl: 1.5rem; /* 24px */ + --sl-text-3xl: 1.8125rem; /* 29px */ + --sl-text-4xl: 2.1875rem; /* 35px */ + --sl-text-5xl: 2.625rem; /* 42px */ + --sl-text-6xl: 4rem; /* 64px */ + + --sl-text-body: var(--sl-text-base); + --sl-text-body-sm: var(--sl-text-xs); + --sl-text-code: var(--sl-text-sm); + --sl-text-code-sm: var(--sl-text-xs); + --sl-text-h1: var(--sl-text-4xl); + --sl-text-h2: var(--sl-text-3xl); + --sl-text-h3: var(--sl-text-2xl); + --sl-text-h4: var(--sl-text-xl); + --sl-text-h5: var(--sl-text-lg); + + --sl-line-height: 1.75; + --sl-line-height-headings: 1.2; + + --sl-font-system: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, + "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --sl-font-system-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; + --__sl-font: var(--sl-font, var(--sl-font-system)), var(--sl-font-system); + --__sl-font-mono: var(--sl-font-mono, var(--sl-font-system-mono)), + var(--sl-font-system-mono); + + /** Key layout values */ + --sl-nav-height: 3.5rem; + --sl-nav-pad-x: 1rem; + --sl-nav-pad-y: 0.75rem; + --sl-mobile-toc-height: 3rem; + --sl-sidebar-width: 18.75rem; + --sl-sidebar-pad-x: 1rem; + --sl-content-width: 45rem; + --sl-content-pad-x: 1rem; + --sl-menu-button-size: 2rem; + --sl-nav-gap: var(--sl-content-pad-x); + /* Offset required to show outline inside an element instead of round the outside */ + --sl-outline-offset-inside: -0.1875rem; + + /* Global z-index values */ + --sl-z-index-toc: 4; + --sl-z-index-menu: 5; + --sl-z-index-navbar: 10; + --sl-z-index-skiplink: 20; +} + +:root[data-theme="light"], +[data-theme="light"] ::backdrop { + /* Colours (light mode) */ + --sl-color-white: hsl(224, 10%, 10%); + --sl-color-gray-1: hsl(224, 14%, 16%); + --sl-color-gray-2: hsl(224, 10%, 23%); + --sl-color-gray-3: hsl(224, 7%, 36%); + --sl-color-gray-4: hsl(224, 6%, 56%); + --sl-color-gray-5: hsl(224, 6%, 77%); + --sl-color-gray-6: hsl(224, 20%, 94%); + --sl-color-gray-7: hsl(224, 19%, 97%); + --sl-color-black: hsl(0, 0%, 100%); + + --sl-color-orange-high: hsl(var(--sl-hue-orange), 80%, 25%); + --sl-color-orange: hsl(var(--sl-hue-orange), 90%, 60%); + --sl-color-orange-low: hsl(var(--sl-hue-orange), 90%, 88%); + --sl-color-green-high: hsl(var(--sl-hue-green), 80%, 22%); + --sl-color-green: hsl(var(--sl-hue-green), 90%, 46%); + --sl-color-green-low: hsl(var(--sl-hue-green), 85%, 90%); + --sl-color-blue-high: hsl(var(--sl-hue-blue), 80%, 30%); + --sl-color-blue: hsl(var(--sl-hue-blue), 90%, 60%); + --sl-color-blue-low: hsl(var(--sl-hue-blue), 88%, 90%); + --sl-color-purple-high: hsl(var(--sl-hue-purple), 90%, 30%); + --sl-color-purple: hsl(var(--sl-hue-purple), 90%, 60%); + --sl-color-purple-low: hsl(var(--sl-hue-purple), 80%, 90%); + --sl-color-red-high: hsl(var(--sl-hue-red), 80%, 30%); + --sl-color-red: hsl(var(--sl-hue-red), 90%, 60%); + --sl-color-red-low: hsl(var(--sl-hue-red), 80%, 90%); + + --sl-color-accent-high: hsl(234, 80%, 30%); + --sl-color-accent: hsl(234, 90%, 60%); + --sl-color-accent-low: hsl(234, 88%, 90%); + + --sl-color-text-accent: var(--sl-color-accent); + --sl-color-text-invert: var(--sl-color-black); + --sl-color-bg-nav: var(--sl-color-gray-7); + --sl-color-bg-sidebar: var(--sl-color-bg); + --sl-color-bg-inline-code: var(--sl-color-gray-6); + --sl-color-bg-accent: var(--sl-color-accent); + --sl-color-hairline-light: var(--sl-color-gray-6); + --sl-color-hairline-shade: var(--sl-color-gray-6); + + --sl-color-backdrop-overlay: hsla(225, 9%, 36%, 0.66); + + /* Shadows (light mode) */ + --sl-shadow-sm: 0px 1px 1px hsla(0, 0%, 0%, 0.06), + 0px 2px 1px hsla(0, 0%, 0%, 0.06); + --sl-shadow-md: 0px 8px 4px hsla(0, 0%, 0%, 0.03), + 0px 5px 2px hsla(0, 0%, 0%, 0.03), 0px 3px 2px hsla(0, 0%, 0%, 0.06), + 0px 1px 1px hsla(0, 0%, 0%, 0.06); + --sl-shadow-lg: 0px 25px 7px rgba(0, 0, 0, 0.01), + 0px 16px 6px hsla(0, 0%, 0%, 0.03), 0px 9px 5px hsla(223, 13%, 10%, 0.08), + 0px 4px 4px hsla(0, 0%, 0%, 0.16), 0px 4px 2px hsla(0, 0%, 0%, 0.04); +} + +@media (min-width: 50em) { + :root { + --sl-nav-height: 4rem; + --sl-nav-pad-x: 1.5rem; + --sl-text-h1: var(--sl-text-5xl); + --sl-text-h2: var(--sl-text-4xl); + --sl-text-h3: var(--sl-text-3xl); + --sl-text-h4: var(--sl-text-2xl); + } +} + +@media (min-width: 72rem) { + :root { + --sl-content-pad-x: 1.5rem; + --sl-mobile-toc-height: 0rem; + } +} diff --git a/public/styles/markdown/reset.css b/public/styles/markdown/reset.css new file mode 100644 index 0000000..42be0c5 --- /dev/null +++ b/public/styles/markdown/reset.css @@ -0,0 +1,48 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +* { + margin: 0; +} + +html { + color-scheme: dark; + accent-color: var(--sl-color-accent); +} + +html[data-theme="light"] { + color-scheme: light; +} + +body { + font-family: var(--__sl-font); + line-height: var(--sl-line-height); + -webkit-font-smoothing: antialiased; + color: var(--sl-color-text); + background-color: var(--sl-color-bg); +} + +input, +button, +textarea, +select { + font: inherit; +} + +p, +h1, +h2, +h3, +h4, +h5, +h6, +code { + overflow-wrap: anywhere; +} + +code { + font-family: var(--__sl-font-mono); +} diff --git a/public/styles/markdown/shiki.css b/public/styles/markdown/shiki.css new file mode 100644 index 0000000..704eea8 --- /dev/null +++ b/public/styles/markdown/shiki.css @@ -0,0 +1,13 @@ +:root { + --astro-code-color-text: var(--sl-color-white); + --astro-code-color-background: var(--sl-color-gray-6); + --astro-code-token-constant: var(--sl-color-blue-high); + --astro-code-token-string: var(--sl-color-green-high); + --astro-code-token-comment: var(--sl-color-gray-2); + --astro-code-token-keyword: var(--sl-color-purple-high); + --astro-code-token-parameter: var(--sl-color-red-high); + --astro-code-token-function: var(--sl-color-red-high); + --astro-code-token-string-expression: var(--sl-color-green-high); + --astro-code-token-punctuation: var(--sl-color-gray-2); + --astro-code-token-link: var(--sl-color-blue-high); +} diff --git a/public/styles/markdown/util.css b/public/styles/markdown/util.css new file mode 100644 index 0000000..63ede50 --- /dev/null +++ b/public/styles/markdown/util.css @@ -0,0 +1,61 @@ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +.sl-hidden { + display: none; +} +.sl-flex { + display: flex; +} +.sl-block { + display: block; +} +@media (min-width: 50rem) { + .md\:sl-hidden { + display: none; + } + .md\:sl-flex { + display: flex; + } + .md\:sl-block { + display: block; + } +} +@media (min-width: 72rem) { + .lg\:sl-hidden { + display: none; + } + .lg\:sl-flex { + display: flex; + } + .lg\:sl-block { + display: block; + } +} +[data-theme="light"] .light\:sl-hidden { + display: none; +} +[data-theme="dark"] .dark\:sl-hidden { + display: none; +} + +/* +Flip an element around the y-axis when in an RTL context. +Primarily useful for things where we can’t rely on writing direction like icons. + +<Icon name="right-arrow" class="rtl:flip" /> + +In a LTR context: → In a RTL context: ← +*/ +[dir="rtl"] .rtl\:flip:not(:where([dir="rtl"] [dir="ltr"] *)) { + transform: matrix(-1, 0, 0, 1, 0, 0); +} diff --git a/public/styles/terminal.css b/public/styles/terminal.css new file mode 100644 index 0000000..f44bcd0 --- /dev/null +++ b/public/styles/terminal.css @@ -0,0 +1,47 @@ +@import url("@styles/fonts/JetBrainsMono.css"); + +@font-face { + font-family: "Monocraft"; + src: url("/fonts/Monocraft.ttf") format("truetype"); + font-display: swap; +} + +:root { + --primary: #54d7a9; + --secondary: #9b7eca; + --accent: #7ed4fb; + --extra: #8fcc75; + --text: #ffffff; + --alt: #999999; + --background: #000000; + --font: "Jetbrains Mono", monospace; +} + +html { + background-color: var(--background); + color: var(--text); + font-family: var(--font); + user-select: none; + + font-size: 1vw; +} + +body { + min-height: 100vh; + display: grid; + place-items: center; +} + +@layer base { + a { + font-weight: 300; + text-decoration: none; + color: var(--secondary); + transition-duration: 0.2s; + transition-property: color, text-decoration; + } + a:hover { + text-decoration: underline; + color: var(--accent); + } +} diff --git a/public/theme.js b/public/theme.js new file mode 100644 index 0000000..90bd1de --- /dev/null +++ b/public/theme.js @@ -0,0 +1,12 @@ +async function applyStyle(path, name) { + const res = await fetch(`${path}/${encodeURIComponent(name)}`); + if (!res.ok) return false; + for (const [key, value] of Object.entries(await res.json())) { + document.documentElement.style.setProperty(`--${key}`, value); + } + return true; +} + +window.setStyle = (path, name) => applyStyle(path, name); +window.setTheme = (name) => applyStyle("/theme", name); +window.setFontPack = (name) => applyStyle("/font-pack", name); @@ -1,12 +1,271 @@ package main import ( + "encoding/json" "fmt" "net/http" + "strings" + "sync" + + "github.com/a-h/templ" ) +func Assert(ok bool) { + if !ok { + panic("assertion failure") + } +} + +type Theme struct { + primary string + secondary string + accent string + extra string + text string + alt string + background string +} + +type FontPack struct { + Monospace string + Normal string +} + +type Style struct { + theme Theme + fontPack FontPack +} + +var ( + mu sync.RWMutex + themes = map[string]Theme{} + fontPacks = map[string]FontPack{} +) + +func SetFontPack(name string, fontPack FontPack) { + mu.Lock() + defer mu.Unlock() + fontPacks[name] = fontPack +} + +func GetFontPack(name string) (FontPack, bool) { + mu.RLock() + defer mu.RUnlock() + fontPack, ok := fontPacks[name] + return fontPack, ok +} + +func SetTheme(name string, theme Theme) { + mu.Lock() + defer mu.Unlock() + themes[name] = theme +} + +func GetTheme(name string) (Theme, bool) { + mu.RLock() + defer mu.RUnlock() + theme, ok := themes[name] + return theme, ok +} + +func currentStyle(r *http.Request) Style { + theme, ok := GetTheme("default") + Assert(ok) + fontPack, ok := GetFontPack("default") + Assert(ok) + style := Style{ + theme: theme, + fontPack: fontPack, + } + + if cookie, err := r.Cookie("theme"); err == nil { + if theme, ok := GetTheme(cookie.Value); ok { + style.theme = theme + } + } + + if cookie, err := r.Cookie("font-pack"); err == nil { + if fontPack, ok := GetFontPack(cookie.Value); ok { + style.fontPack = fontPack + } + } + + return style +} + +func (theme Theme) Vars() map[string]string { + return map[string]string{ + "primary": theme.primary, + "secondary": theme.secondary, + "accent": theme.accent, + "extra": theme.extra, + "text": theme.text, + "alt": theme.alt, + "background": theme.background, + } +} + +func (fontPack FontPack) Vars() map[string]string { + return map[string]string{ + "font-mono": fontPack.Monospace, + "font-normal": fontPack.Normal, + } +} + +func (style Style) Vars() map[string]string { + vars := style.theme.Vars() + for k, v := range style.fontPack.Vars() { + vars[k] = v + } + return vars +} + +func styleToCss(style Style) templ.SafeCSS { + var b strings.Builder + for k, v := range style.Vars() { + fmt.Fprintf(&b, "--%s:%s;", k, v) + } + return templ.SafeCSS(b.String()) +} + +type Project struct { + Name string + Type string + Desc []string + Cmd string + Link string +} + +var projects = []Project{ + { + Name: "CPU vs AI", + Type: "Game (made with Zig)", + Desc: []string{ + "A tower defense game where an AI chip on your motherboard", + "generates bugs! Place CPUs to destroy the bugs!", + "Don't let them corrupt your RAM!", + }, + }, + { + Name: "Eko", + Type: "Social Media Platform", + Desc: []string{ + "A terminal-native alternative", + "to Discord for Terminal", + "nerds!", + }, + }, + { + Name: "Trident", + Type: "IntelliJ Plugin", + Desc: []string{ + "A blazingly fast way to", + "Navigate to your mosta used", + "files. Minimize keystrokes", + "and maximize productivity!", + }, + }, + { + Name: "Dotfiles", + Type: "Configuration", + Desc: []string{ + "My Linux configuration,", + "includes Neovim, tmux and", + "other programs.", + }, + }, +} + +func styleHandler(cookie string, lookup func(string) (map[string]string, bool)) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + name := r.PathValue("name") + vars, ok := lookup(name) + if !ok { + http.NotFound(w, r) + return + } + http.SetCookie(w, &http.Cookie{ + Name: cookie, Value: name, Path: "/", + MaxAge: 31536000, SameSite: http.SameSiteLaxMode, + }) + w.Header().Set("Cache-Control", "no-store") + json.NewEncoder(w).Encode(vars) + } +} + func main() { - fmt.Println("foo") + carbonightCat := Theme{ + primary: "#94e2d5", + secondary: "#b4befe", + accent: "#74c7ec", + extra: "#a6e3a1", + text: "#cdd6f4", + alt: "#585b70", + background: "#1e1e2e", + } + darkness := Theme{ + primary: "#000000", + secondary: "#000000", + accent: "#000000", + extra: "#000000", + text: "#000000", + alt: "#000000", + background: "#000000", + } + dark := Theme{ + primary: "#54d7a9", + secondary: "#9b7eca", + accent: "#7ed4fb", + extra: "#8fcc75", + text: "#ffffff", + alt: "#999999", + background: "#000000", + } + light := Theme{ + primary: "#009399", + secondary: "#440b8e", + accent: "#177282", + extra: "#40a02b", + text: "#000000", + alt: "#4d4d4d", + background: "#ffffff", + } + banana := Theme{ + primary: "#ffdd44", + secondary: "#9b7eca", + accent: "#7ed4fb", + extra: "#000000", + text: "#ffffff", + alt: "#999999", + background: "#000000", + } + + jetbrains := FontPack{ + Monospace: "JetBrains Mono", + Normal: `system-ui, sans-serif`, + } + + minecraft := FontPack{ + Monospace: `"Monocraft", "JetBrains Mono", monospace`, + Normal: `"Monocraft", system-ui, sans-serif`, + } + + SetTheme("carbonight-cat", carbonightCat) + SetTheme("banana", banana) + SetTheme("black", dark) + SetTheme("dark", darkness) + + SetTheme("light", light) + SetTheme("white", light) + SetTheme("sun", light) + SetTheme("sunlight", light) + SetTheme("flashbang", light) + + SetTheme("default", carbonightCat) + + SetFontPack("jetbrains", jetbrains) + SetFontPack("minecraft", minecraft) + SetFontPack("default", jetbrains) mux := http.NewServeMux() @@ -14,22 +273,26 @@ func main() { mux.Handle("GET /public/", http.StripPrefix("/public/", fileServer)) mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) { - HomePage().Render(r.Context(), w) + HomePage(currentStyle(r)).Render(r.Context(), w) }) - mux.HandleFunc("GET /about", func(w http.ResponseWriter, r *http.Request) { - AboutPage().Render(r.Context(), w) + mux.HandleFunc("GET /skills", func(w http.ResponseWriter, r *http.Request) { + SkillsPage(currentStyle(r)).Render(r.Context(), w) }) - mux.HandleFunc("GET /items", func(w http.ResponseWriter, r *http.Request) { - data := []Item{ - {Name: "Foo", Active: true}, - {Name: "Bar", Active: true}, - {Name: "Baz", Active: false}, - } - ItemsPage(data).Render(r.Context(), w) + mux.HandleFunc("GET /projects", func(w http.ResponseWriter, r *http.Request) { + ProjectsPage(currentStyle(r), projects).Render(r.Context(), w) }) + mux.HandleFunc("GET /theme/{name}", styleHandler("theme", func(n string) (map[string]string, bool) { + theme, ok := GetTheme(n) + return theme.Vars(), ok + })) + mux.HandleFunc("GET /font-pack/{name}", styleHandler("font-pack", func(n string) (map[string]string, bool) { + fontPack, ok := GetFontPack(n) + return fontPack.Vars(), ok + })) + fmt.Println("Server running at http://localhost:8080") if err := http.ListenAndServe(":8080", mux); err != nil { panic(err) diff --git a/website.templ b/website.templ index a3f137a..face9f9 100644 --- a/website.templ +++ b/website.templ @@ -1,81 +1,88 @@ package main -import "fmt" +templ HeadComponent(title string, description string) { + <head> + <meta charset="UTF-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1.0"/> + <meta name="color-scheme" content="dark"/> + <link rel="icon" type="image/svg+xml" href="/favicon.svg"/> + <meta name="darkreader-lock"/> + <meta name="robots" content="index, follow, noarchive"/> + <meta name="keywords" content="Kyren223, Krypton, Compiler, Minecraft, Developer, Language, Langdev, Gamedev, Game, Software, Software Engineer, Game developer"/> + <meta name="author" content="Kyren223"/> + <meta name="description" content={ description }/> + <title>{ title }</title> + <meta property="og:title" content={ title }/> + <meta property="og:description" content={ description }/> + <meta property="og:type" content="website"/> + <meta property="og:url" content="https://kyren.codes/"/> + <meta property="og:image" content="https://kyren.codes/image.png"/> + <meta property="og:image:alt" content="a cyan sphere with a dark background"/> + <meta property="og:site_name" content="Kyren's Website"/> + <script src="/public/theme.js" defer></script> + <link rel="stylesheet" href="/public/style.css"/> + { children... } + </head> +} + +templ NavLink(href string, label string) { + <a href={ templ.SafeURL(href) } class="text-(--primary) hover:text-(--accent)">{ label }</a> +} -// 1. Shared Base Layout Component -templ PageLayout(title string) { - <!DOCTYPE html> - <html lang="en"> - <head> - <meta charset="UTF-8"/> - <title>{ title }</title> - <style> - body { font-family: monospace; background: #0f172a; color: #f8fafc; padding: 2rem; line-height: 1.5; } - nav a { color: #38bdf8; margin-right: 1rem; text-decoration: none; } - nav a:hover { text-decoration: underline; } - .card { background: #1e293b; padding: 1rem; border-radius: 8px; margin-top: 1rem; border: 1px solid #334155; } - .badge-active { color: #4ade80; } - .badge-inactive { color: #f87171; } - </style> - <link rel="stylesheet" href="/public/style.css"/> - </head> - <body> - <nav> - <a href="/">/home</a> - <a href="/about">/about</a> - <a href="/items">/items</a> - </nav> - <hr style="border-color: #334155; margin: 1rem 0;"/> - { children... } - </body> - </html> +templ NavComponent(path string) { + <nav class="flex justify-between gap-10 p-4 mb-[1.5625rem] border-2 border-(--alt) text-[1.375rem]"> + <span>{ path }</span> + <span class="flex gap-[1ch]"> + @NavLink("/", "home") + for _, dir := range []string{"skills", "projects", "blogs"} { + @NavLink("/"+dir, dir) + } + @NavLink("/shell", "shell") + </span> + </nav> } -// 2. Page 1: Home -templ HomePage() { - @PageLayout("Home") { - <h1>Home</h1> - <p>Go server running with Templ templates and Air hot-reloading.</p> - } +templ TerminalLayout(style Style, path string) { + <!DOCTYPE html> + <html lang="en" style={ styleToCss(style) }> + @HeadComponent(path, "My personal website") { + <link rel="stylesheet" href="/public/styles/terminal.css"/> + } + <body> + <main class="w-[75rem] h-[40rem] p-8 border-[0.2rem] border-(--text) overflow-hidden text-[1.125rem]"> + @NavComponent(path) + { children... } + </main> + </body> + </html> } -// 3. Page 2: About -templ AboutPage() { - @PageLayout("About") { - <h1 class="text-3xl font-bold underline">About</h1> - <p>Single-binary web app using Go's standard library net/http.</p> - } +templ HomePage(style Style) { + @TerminalLayout(style, "/home/kyren/") { + <div id="content" class="w-0 h-0 invisible sm:w-full sm:h-full sm:visible"> + <h1>Home</h1> + <p>Go server running with Templ templates and Air hot-reloading.</p> + </div> + } } -// Data struct for dynamic rendering -type Item struct { - Name string - Active bool +templ SkillsPage(style Style) { + @TerminalLayout(style, "/home/kyren/skills.md") { + <h1 class="text-3xl font-bold">About</h1> + <p>Single-binary web app using Go's standard library net/http.</p> + } } -// 4. Page 3: Dynamic Data (Conditionals + Loops) -templ ItemsPage(items []Item) { - @PageLayout("Items") { - <h1>Items</h1> - <div class="card"> - if len(items) == 0 { - <p>No items in database.</p> - } else { - <ul> - for _, item := range items { - <li> - <span>{ item.Name }</span> - if item.Active { - <span class="badge-active"> [ACTIVE]</span> - } else { - <span class="badge-inactive"> [INACTIVE]</span> - } - </li> - } - </ul> - } - </div> - <p style="color: #94a3b8; font-size: 0.8rem; margin-top: 1rem;"> - { fmt.Sprintf("Total items processed: %d", len(items)) } - </p> - } +templ ProjectsPage(style Style, projects []Project) { + @TerminalLayout(style, "/home/kyren/projects") { + <h1>Items</h1> + <div class="card"> + <ul> + <li> + <span>foo</span> + <span class="badge-active">[ACTIVE]</span> + </li> + </ul> + </div> + } +} diff --git a/website_templ.go b/website_templ.go index 9acf03c..5782d94 100644 --- a/website_templ.go +++ b/website_templ.go @@ -8,10 +8,7 @@ package main import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" -import "fmt" - -// 1. Shared Base Layout Component -func PageLayout(title string) templ.Component { +func HeadComponent(title string, description string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -32,20 +29,59 @@ func PageLayout(title string) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><title>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"color-scheme\" content=\"dark\"><link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\"><meta name=\"darkreader-lock\"><meta name=\"robots\" content=\"index, follow, noarchive\"><meta name=\"keywords\" content=\"Kyren223, Krypton, Compiler, Minecraft, Developer, Language, Langdev, Gamedev, Game, Software, Software Engineer, Game developer\"><meta name=\"author\" content=\"Kyren223\"><meta name=\"description\" content=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var2 string - templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title) + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.ResolveAttributeValue(description) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 13, Col: 48} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"><title>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 14, Col: 16} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</title><meta property=\"og:title\" content=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 15, Col: 43} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 11, Col: 22} + return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\"><meta property=\"og:description\" content=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><style>\n body { font-family: monospace; background: #0f172a; color: #f8fafc; padding: 2rem; line-height: 1.5; }\n nav a { color: #38bdf8; margin-right: 1rem; text-decoration: none; }\n nav a:hover { text-decoration: underline; }\n .card { background: #1e293b; padding: 1rem; border-radius: 8px; margin-top: 1rem; border: 1px solid #334155; }\n .badge-active { color: #4ade80; }\n .badge-inactive { color: #f87171; }\n </style><link rel=\"stylesheet\" href=\"/public/style.css\"></head><body><nav><a href=\"/\">/home</a> <a href=\"/about\">/about</a> <a href=\"/items\">/items</a></nav><hr style=\"border-color: #334155; margin: 1rem 0;\">") + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(description) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 16, Col: 55} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"><meta property=\"og:type\" content=\"website\"><meta property=\"og:url\" content=\"https://kyren.codes/\"><meta property=\"og:image\" content=\"https://kyren.codes/image.png\"><meta property=\"og:image:alt\" content=\"a cyan sphere with a dark background\"><meta property=\"og:site_name\" content=\"Kyren's Website\"><script src=\"/public/theme.js\" defer></script><link rel=\"stylesheet\" href=\"/public/style.css\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -53,7 +89,7 @@ func PageLayout(title string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</body></html>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</head>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -61,8 +97,7 @@ func PageLayout(title string) templ.Component { }) } -// 2. Page 1: Home -func HomePage() templ.Component { +func NavLink(href string, label string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -78,12 +113,144 @@ func HomePage() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var3 := templ.GetChildren(ctx) - if templ_7745c5c3_Var3 == nil { - templ_7745c5c3_Var3 = templ.NopComponent + templ_7745c5c3_Var6 := templ.GetChildren(ctx) + if templ_7745c5c3_Var6 == nil { + templ_7745c5c3_Var6 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var4 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<a href=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 templ.SafeURL + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(href)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 29, Col: 30} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" class=\"text-(--primary) hover:text-(--accent)\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(label) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 29, Col: 87} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</a>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func NavComponent(path string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var9 := templ.GetChildren(ctx) + if templ_7745c5c3_Var9 == nil { + templ_7745c5c3_Var9 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<nav class=\"flex justify-between gap-10 p-4 mb-[1.5625rem] border-2 border-(--alt) text-[1.375rem]\"><span>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(path) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 34, Col: 14} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</span> <span class=\"flex gap-[1ch]\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = NavLink("/", "home").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, dir := range []string{"skills", "projects", "blogs"} { + templ_7745c5c3_Err = NavLink("/"+dir, dir).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = NavLink("/shell", "shell").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</span></nav>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func TerminalLayout(style Style, path string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var11 := templ.GetChildren(ctx) + if templ_7745c5c3_Var11 == nil { + templ_7745c5c3_Var11 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<!doctype html><html lang=\"en\" style=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(styleToCss(style)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 47, Col: 42} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var13 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -95,13 +262,29 @@ func HomePage() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<h1>Home</h1><p>Go server running with Templ templates and Air hot-reloading.</p>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<link rel=\"stylesheet\" href=\"/public/styles/terminal.css\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = PageLayout("Home").Render(templ.WithChildren(ctx, templ_7745c5c3_Var4), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = HeadComponent(path, "My personal website").Render(templ.WithChildren(ctx, templ_7745c5c3_Var13), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<body><main class=\"w-[75rem] h-[40rem] p-8 border-[0.2rem] border-(--text) overflow-hidden text-[1.125rem]\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = NavComponent(path).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ_7745c5c3_Var11.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</main></body></html>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -109,8 +292,7 @@ func HomePage() templ.Component { }) } -// 3. Page 2: About -func AboutPage() templ.Component { +func HomePage(style Style) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -126,12 +308,12 @@ func AboutPage() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var5 := templ.GetChildren(ctx) - if templ_7745c5c3_Var5 == nil { - templ_7745c5c3_Var5 = templ.NopComponent + templ_7745c5c3_Var14 := templ.GetChildren(ctx) + if templ_7745c5c3_Var14 == nil { + templ_7745c5c3_Var14 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var6 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var15 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -143,13 +325,13 @@ func AboutPage() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<h1 class=\"text-3xl font-bold underline\">About</h1><p>Single-binary web app using Go's standard library net/http.</p>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<div id=\"content\" class=\"w-0 h-0 invisible sm:w-full sm:h-full sm:visible\"><h1>Home</h1><p>Go server running with Templ templates and Air hot-reloading.</p></div>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = PageLayout("About").Render(templ.WithChildren(ctx, templ_7745c5c3_Var6), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = TerminalLayout(style, "/home/kyren/").Render(templ.WithChildren(ctx, templ_7745c5c3_Var15), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -157,14 +339,7 @@ func AboutPage() templ.Component { }) } -// Data struct for dynamic rendering -type Item struct { - Name string - Active bool -} - -// 4. Page 3: Dynamic Data (Conditionals + Loops) -func ItemsPage(items []Item) templ.Component { +func SkillsPage(style Style) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -180,12 +355,12 @@ func ItemsPage(items []Item) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var7 := templ.GetChildren(ctx) - if templ_7745c5c3_Var7 == nil { - templ_7745c5c3_Var7 = templ.NopComponent + templ_7745c5c3_Var16 := templ.GetChildren(ctx) + if templ_7745c5c3_Var16 == nil { + templ_7745c5c3_Var16 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var8 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var17 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -197,79 +372,60 @@ func ItemsPage(items []Item) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<h1>Items</h1><div class=\"card\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<h1 class=\"text-3xl font-bold\">About</h1><p>Single-binary web app using Go's standard library net/http.</p>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if len(items) == 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<p>No items in database.</p>") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<ul>") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err + return nil + }) + templ_7745c5c3_Err = TerminalLayout(style, "/home/kyren/skills.md").Render(templ.WithChildren(ctx, templ_7745c5c3_Var17), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func ProjectsPage(style Style, projects []Project) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr } - for _, item := range items { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<li><span>") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var9 string - templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 67, Col: 45} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</span> ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if item.Active { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<span class=\"badge-active\">[ACTIVE]</span>") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<span class=\"badge-inactive\">[INACTIVE]</span>") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</li>") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var18 := templ.GetChildren(ctx) + if templ_7745c5c3_Var18 == nil { + templ_7745c5c3_Var18 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var19 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</ul>") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div><p style=\"color: #94a3b8; font-size: 0.8rem; margin-top: 1rem;\">") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var10 string - templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("Total items processed: %d", len(items))) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 79, Col: 66} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err + }() } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</p>") + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<h1>Items</h1><div class=\"card\"><ul><li><span>foo</span> <span class=\"badge-active\">[ACTIVE]</span></li></ul></div>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = PageLayout("Items").Render(templ.WithChildren(ctx, templ_7745c5c3_Var8), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = TerminalLayout(style, "/home/kyren/projects").Render(templ.WithChildren(ctx, templ_7745c5c3_Var19), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } |
