Reskin GregKilmer.com in the Patiun brand

- Palette: Patiun Teal, Midnight, night sky, Starlight, flat Alchemist Gold;
  violet/indigo only as nebula light. Starlight content sections, night-sky
  nav and heroes (.on-sky scope re-points accent/gold to on-dark values).
- Type: Cinzel for headings, names and card titles; Montserrat for body, UI
  and eyebrows (font-eyebrow); JetBrains Mono kept for code-like blocks.
- Header lockup: Patiun glyph + Cinzel "GREG KILMER" + "Web Development ·
  a Patiun LLC company"; glyph also marks the footer and store nav.
- Teal filled buttons with Midnight text; gold eyebrows; small links use a
  darker teal so they pass WCAG AA on Starlight.
- Favicons, app icons and web manifest from the GregKilmer.com brand package.
- docs/THEMING.md updated for the new palette and fonts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
This commit is contained in:
Claude
2026-10-11 01:54:04 +00:00
parent 5edf0c6061
commit 01431468e4
71 changed files with 423 additions and 309 deletions
+23 -10
View File
@@ -6,16 +6,27 @@ How color, light/dark mode, section tone, and animation work on this site.
## Palette ## Palette
From the spec, exposed as CSS variables in `src/styles/themes.css` and mapped to The site uses the Patiun LLC brand palette (shared by Patiun and Greg Kilmer),
Tailwind utilities in `src/styles/global.css`: exposed as CSS variables in `src/styles/global.css` under `@theme`:
| Token | Value | Meaning | | Token | Value | Meaning |
|---|---|---| |---|---|---|
| `--color-slate` | `#1E293B` | Professional confidence (primary) | | `--color-teal` | `#14A39A` | Patiun Teal, primary: buttons, fills, the glyph |
| `--color-amber` | `#F59E0B` | Warm accent (the making/creative side) | | `--color-teal-bright` | `#2BC2B7` | Teal text and links on dark grounds |
| `--color-teal` | `#699a94` | Soft teal (secondary, favorite) | | `--color-teal-deep` | `#0F8C84` | Teal marks on light grounds |
| `--color-ink` | `#0F172A` | Near-black text on light | | `--color-slate-teal` | `#2D4A58` | Dark end of the Tide gradient |
| `--color-cream` | `#FFFBEB` | Cream-white text on dark | | `--color-gold` | `#C9A24A` | Alchemist Gold: stars, rules, seal lines (never a large fill) |
| `--color-gold-bright` | `#D8B35A` | Gold text on dark grounds |
| `--color-gold-deep` | `#8A6516` | Gold text under 24px on Starlight |
| `--color-violet` / `--color-indigo` | `#6A2FE0` / `#3B1F8F` | Nebula light only, never text |
| `--color-midnight` | `#131C2B` | Panels, tiles, browser theme color |
| `--color-sky` | `#0E1522` | Night-sky ground |
| `--color-starlight` | `#F4F2ED` | Text on dark; paper ground |
The semantic Tailwind tokens (`--color-accent`, `--color-surface`, ...) resolve
at `:root`, so content sections render with the Starlight values in
`themes.css`. Night-sky bands (the nav and page heroes) carry the `.on-sky`
class, which re-points accent and gold to their on-dark values.
Brand colors are fixed. **Semantic** tokens (`--surface`, `--text`, `--accent`, Brand colors are fixed. **Semantic** tokens (`--surface`, `--text`, `--accent`,
`--secondary`, `--border`, ...) resolve differently per theme + section, and are `--secondary`, `--border`, ...) resolve differently per theme + section, and are
@@ -118,9 +129,11 @@ reduce)')` before drawing, so even JS-driven motion is skipped.
Self-hosted variable fonts via Fontsource (woff2, no external requests): Self-hosted variable fonts via Fontsource (woff2, no external requests):
- **Headings / UI:** Plus Jakarta Sans Variable (geometric sans) - **Headings, names, card titles:** Cinzel Variable (`font-display`, used by
- **Body:** Inter Variable `h1`-`h4` automatically). Open tracking, never for paragraphs.
- **Mono / kickers / quotes:** JetBrains Mono Variable - **Body and UI:** Montserrat Variable (`font-sans`, `font-body`).
- **Eyebrows / kickers:** Montserrat uppercase with wide tracking (`font-eyebrow`).
- **Code-like blocks only:** JetBrains Mono Variable (`font-mono`).
Font sizes use `clamp()` for fluid scaling (see the `h1`-`h4` rules in Font sizes use `clamp()` for fluid scaling (see the `h1`-`h4` rules in
`global.css`). `global.css`).
+8 -8
View File
@@ -12,9 +12,9 @@
"@astrojs/mdx": "^7.0.2", "@astrojs/mdx": "^7.0.2",
"@astrojs/node": "^11.0.2", "@astrojs/node": "^11.0.2",
"@astrojs/react": "^6.0.2", "@astrojs/react": "^6.0.2",
"@fontsource-variable/inter": "^5.2.8", "@fontsource-variable/cinzel": "^5.3.0",
"@fontsource-variable/jetbrains-mono": "^5.2.8", "@fontsource-variable/jetbrains-mono": "^5.2.8",
"@fontsource-variable/plus-jakarta-sans": "^5.2.8", "@fontsource-variable/montserrat": "^5.3.0",
"@iconify-json/mdi": "^1.2.3", "@iconify-json/mdi": "^1.2.3",
"@tailwindcss/vite": "^4.3.2", "@tailwindcss/vite": "^4.3.2",
"@types/nodemailer": "^8.0.1", "@types/nodemailer": "^8.0.1",
@@ -1389,10 +1389,10 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@fontsource-variable/inter": { "node_modules/@fontsource-variable/cinzel": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.3.0.tgz", "resolved": "https://registry.npmjs.org/@fontsource-variable/cinzel/-/cinzel-5.3.0.tgz",
"integrity": "sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==", "integrity": "sha512-eFcU0TwaAP5QESF2pV8VoOPJJXJ6sSGm/6mXMwqTdMBPAJMaLhfa6oISHuOtFeddsoHP9RYm4Io0j1j3mNEz2Q==",
"license": "OFL-1.1", "license": "OFL-1.1",
"funding": { "funding": {
"url": "https://github.com/sponsors/ayuhito" "url": "https://github.com/sponsors/ayuhito"
@@ -1407,10 +1407,10 @@
"url": "https://github.com/sponsors/ayuhito" "url": "https://github.com/sponsors/ayuhito"
} }
}, },
"node_modules/@fontsource-variable/plus-jakarta-sans": { "node_modules/@fontsource-variable/montserrat": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/@fontsource-variable/plus-jakarta-sans/-/plus-jakarta-sans-5.3.0.tgz", "resolved": "https://registry.npmjs.org/@fontsource-variable/montserrat/-/montserrat-5.3.0.tgz",
"integrity": "sha512-/l/4r0yyWK9JzAlmA0LiYgGgmJe/Gswt4jTJEzr5QhJfwMbvJZDmYWyW0M4X7yCK69BajMVlV/Lx8g7WDc1+sw==", "integrity": "sha512-7PaZoxaxrWLAyrhO46v65An9LhUhfkTExWLhfbywYZCnZEgg/W1rEHnlNmZKjNZ3nJTVYyicqxlJt10z/26yTA==",
"license": "OFL-1.1", "license": "OFL-1.1",
"funding": { "funding": {
"url": "https://github.com/sponsors/ayuhito" "url": "https://github.com/sponsors/ayuhito"
+2 -2
View File
@@ -19,9 +19,9 @@
"@astrojs/mdx": "^7.0.2", "@astrojs/mdx": "^7.0.2",
"@astrojs/node": "^11.0.2", "@astrojs/node": "^11.0.2",
"@astrojs/react": "^6.0.2", "@astrojs/react": "^6.0.2",
"@fontsource-variable/inter": "^5.2.8", "@fontsource-variable/cinzel": "^5.3.0",
"@fontsource-variable/jetbrains-mono": "^5.2.8", "@fontsource-variable/jetbrains-mono": "^5.2.8",
"@fontsource-variable/plus-jakarta-sans": "^5.2.8", "@fontsource-variable/montserrat": "^5.3.0",
"@iconify-json/mdi": "^1.2.3", "@iconify-json/mdi": "^1.2.3",
"@tailwindcss/vite": "^4.3.2", "@tailwindcss/vite": "^4.3.2",
"@types/nodemailer": "^8.0.1", "@types/nodemailer": "^8.0.1",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

+1 -15
View File
@@ -1,15 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><linearGradient id="fgk" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#131C2B"/><stop offset="1" stop-color="#1F3B49"/></linearGradient></defs><rect width="64" height="64" rx="14" fill="url(#fgk)"/><circle cx="32" cy="32" r="26" fill="none" stroke="#14A39A" stroke-width="2.4" /><g fill="#F4F2ED" aria-label="GK"><path d="M754.5337524414062 280V115Q730.9266357421875 85 683.427001953125 55.0Q635.9273681640625 25 569.7135009765625 5.5Q503.4996337890625 -14 422.6427001953125 -14Q309.3572998046875 -13.60711669921875 224.41104125976562 30.67864990234375Q139.46478271484375 74.96441650390625 92.1434326171875 156.76797485351562Q44.82208251953125 238.571533203125 44.82208251953125 351.2142333984375Q44.82208251953125 462.6427001953125 91.53631591796875 544.0355834960938Q138.25054931640625 625.428466796875 224.714599609375 669.7142333984375Q311.17864990234375 714 428.7138671875 714Q483.7138671875 714 535.9099426269531 706.5177917480469Q588.1060180664062 699.0355834960938 632.1056518554688 686.8747253417969Q676.1052856445312 674.7138671875 705.1052856445312 659.3565673828125L706.8917846679688 488.000732421875H697.8917846679688Q679.2490844726562 556.3217163085938 643.2312927246094 596.4288330078125Q607.2135009765625 636.5359497070312 559.7668762207031 654.1968078613281Q512.3202514648438 671.857666015625 459.0697021484375 671.857666015625Q377.4981689453125 671.857666015625 321.39068603515625 632.2861328125Q265.283203125 592.714599609375 236.568603515625 519.8395080566406Q207.85400390625 446.96441650390625 207.85400390625 345.14306640625Q207.85400390625 248.7857666015625 234.44369506835938 177.428466796875Q261.03338623046875 106.0711669921875 311.9803771972656 67.30319213867188Q362.9273681640625 28.53521728515625 435.999267578125 28.142333984375Q473.32098388671875 28.142333984375 507.4462585449219 39.553009033203125Q541.571533203125 50.96368408203125 567.5541076660156 73.19570922851562Q593.5366821289062 95.427734375 604.5374145507812 127.28466796875L605.5374145507812 280Q605.5374145507812 325 554.5374145507812 325H538.5374145507812V335H819.5337524414062V325H804.5337524414062Q752.5337524414062 325 754.5337524414062 280Z" transform="translate(12.09,41.50) scale(0.02500,-0.02500)"/><path d="M412.851806640625 430.53375244140625 674.99560546875 72.24908447265625Q697.8169555664062 41.9281005859375 722.0489807128906 26.46405029296875Q746.281005859375 11 777.281005859375 10V0H694.8184204101562Q684.2113037109375 0 658.7294616699219 1.196441650390625Q633.2476196289062 2.39288330078125 601.1230773925781 9.410675048828125Q568.99853515625 16.428466796875 536.5704345703125 33.696075439453125Q504.142333984375 50.96368408203125 479.85693359375 83.10601806640625Q475 89.7843017578125 460.2320251464844 109.87362670898438Q445.46405029296875 129.96295166015625 425.24945068359375 157.80245971679688Q405.03485107421875 185.6419677734375 383.3380432128906 215.55300903320312Q361.6412353515625 245.46405029296875 343.04561360677087 271.22924296061194Q324.4499918619792 296.9944356282552 312.61641438802087 313.3904673258463Q300.7828369140625 329.7864990234375 300.7828369140625 329.7864990234375ZM663.6034545898438 693 362.7821044921875 335.857666015625 300.7828369140625 329.7864990234375 575.7828369140625 693ZM733.8176879882812 700V690.8438842773437H729.8176879882812Q696.8176879882812 690.8438842773437 668.3176879882812 677.6183837890625Q639.8176879882812 664.3928833007812 619.8176879882812 642L538.6041870117188 644Q550.9970703125 661 543.1041870117188 676.0Q535.2113037109375 691 514.8065592447916 691H504.60418701171875V700H729.8176879882812ZM268.7828369140625 700V0H118.7864990234375V700ZM121.7864990234375 73V0H41.7864990234375V10Q43.000732421875 10 48.590057373046875 10.0Q54.17938232421875 10 54.7864990234375 10Q80.7864990234375 10 99.2864990234375 28.5Q117.7864990234375 47 118.7864990234375 73ZM121.7864990234375 627H118.7864990234375Q117.7864990234375 653 99.2864990234375 671.5Q80.7864990234375 690 54.7864990234375 690Q54.17938232421875 690 48.590057373046875 690.0Q43.000732421875 690 41.7864990234375 690V700H121.7864990234375ZM265.7828369140625 73H268.7828369140625Q269.7828369140625 47 288.2828369140625 28.5Q306.7828369140625 10 332.7828369140625 10Q333.9970703125 10 339.43665489783655 10.0Q344.8762394831731 10 345.7828369140625 10V0H265.7828369140625ZM265.7828369140625 627V700H345.7828369140625V690Q344.7828369140625 690 339.38995361328125 690.0Q333.9970703125 690 332.7828369140625 690Q306.7828369140625 690 288.2828369140625 671.5Q269.7828369140625 653 268.7828369140625 627Z" transform="translate(32.99,41.50) scale(0.02500,-0.02500)"/></g><circle cx="32" cy="6" r="4" fill="#131C2B"/><polygon points="32.00,0.80 33.10,4.90 37.20,6.00 33.10,7.10 32.00,11.20 30.90,7.10 26.80,6.00 30.90,4.90" fill="#C9A24A"/></svg>
<!-- Greg Kilmer mark: a stylized "G" over a warm amber field with slate. -->
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#1e293b"/>
<stop offset="1" stop-color="#0f172a"/>
</linearGradient>
</defs>
<rect width="64" height="64" rx="14" fill="url(#bg)"/>
<!-- Amber accent arc -->
<circle cx="32" cy="32" r="20" fill="none" stroke="#f59e0b" stroke-width="4" stroke-dasharray="94 200" stroke-linecap="round" transform="rotate(-30 32 32)" opacity="0.85"/>
<!-- G glyph -->
<path d="M40 26a12 12 0 1 0 0 12" fill="none" stroke="#fffbeb" stroke-width="5" stroke-linecap="round"/>
<line x1="32" y1="32" x2="42" y2="32" stroke="#fffbeb" stroke-width="5" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 846 B

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

+33 -1
View File
@@ -1 +1,33 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} {
"name": "Greg Kilmer Web Development",
"short_name": "Greg Kilmer",
"description": "Web development by Greg Kilmer, a Patiun LLC company.",
"icons": [
{
"src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "/icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#131C2B",
"background_color": "#131C2B",
"display": "standalone",
"start_url": "/"
}
+40
View File
@@ -0,0 +1,40 @@
---
/**
* PatiunGlyph.astro
* ---------------------------------------------------------------------------
* The Patiun LLC main mark: the alchemical symbol for platinum (the moon of
* silver joined to the sun of gold). Used small as the maker's mark in the
* Greg Kilmer lockup. Geometry is copied unchanged from the brand file
* patiun-symbol-A-exact-teal.svg; only the color is themable.
*
* Brand rules: Patiun Teal by default (gold, black or white are the only
* other allowed colors), no rings around it at small sizes, minimum 16px, and
* text sits beside it, never over it.
*
* Usage: <PatiunGlyph class="h-6 w-auto" />
*/
interface Props {
class?: string;
style?: string;
/** Accessible name. Omit (default) when the mark sits next to visible text. */
title?: string;
}
const { class: className, style = "color: var(--color-teal);", title } = Astro.props;
---
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="20 33 152 127"
class={className}
style={style}
role={title ? "img" : undefined}
aria-hidden={title ? undefined : "true"}
aria-label={title}
focusable="false"
>
<g fill="none" stroke="currentColor" stroke-width="7.2" stroke-linejoin="round">
<path d="M31,46.75 A54.3,54.3 0 1 1 31,146.25 A53.4,53.4 0 0 0 31,46.75 Z" />
<circle cx="134" cy="96.5" r="26.9" />
</g>
<circle cx="134" cy="96.5" r="10.3" fill="currentColor" />
</svg>
+2 -2
View File
@@ -96,13 +96,13 @@ const updated = updatedAt ? new Date(updatedAt).toLocaleDateString('en-US', {
<div class="flex flex-col gap-2 p-4"> <div class="flex flex-col gap-2 p-4">
<h3 <h3
class="font-sans text-base leading-tight underline-offset-4 group-hover:underline" class="font-display text-base leading-tight underline-offset-4 group-hover:underline"
style="color: var(--color-text);" style="color: var(--color-text);"
> >
{name} {name}
</h3> </h3>
<div <div
class="flex flex-wrap items-center gap-2 font-mono text-[0.65rem] uppercase tracking-[0.16em]" class="flex flex-wrap items-center gap-2 font-eyebrow text-[0.65rem] uppercase tracking-[0.16em]"
style="color: var(--color-text-muted);" style="color: var(--color-text-muted);"
> >
{formatLabel && <span>{formatLabel}</span>} {formatLabel && <span>{formatLabel}</span>}
+2 -2
View File
@@ -67,7 +67,7 @@ const alt = artAlt ?? title;
</span> </span>
{year && ( {year && (
<span <span
class="font-mono text-[0.65rem] uppercase tracking-[0.18em]" class="font-eyebrow text-[0.65rem] uppercase tracking-[0.18em]"
style="color: var(--color-text-muted);" style="color: var(--color-text-muted);"
> >
{year} {year}
@@ -75,7 +75,7 @@ const alt = artAlt ?? title;
)} )}
</div> </div>
<h3 class="font-sans text-lg" style="color: var(--color-text);">{title}</h3> <h3 class="font-display text-lg" style="color: var(--color-text);">{title}</h3>
<p class="grow text-sm leading-relaxed" style="color: var(--color-text-muted);"> <p class="grow text-sm leading-relaxed" style="color: var(--color-text-muted);">
{blurb} {blurb}
+2 -2
View File
@@ -41,7 +41,7 @@ const { icon, title, status, currently = false, class: className = '' } = Astro.
<Icon name={icon} class="h-6 w-6" /> <Icon name={icon} class="h-6 w-6" />
</span> </span>
{currently && ( {currently && (
<span class="inline-flex items-center gap-1.5 font-mono text-[0.65rem] uppercase tracking-[0.18em]" style="color: var(--color-accent);"> <span class="inline-flex items-center gap-1.5 font-eyebrow text-[0.65rem] uppercase tracking-[0.18em]" style="color: var(--color-accent);">
<span class="h-2 w-2 rounded-full animate-pulse-glow" style="background: var(--color-accent);"></span> <span class="h-2 w-2 rounded-full animate-pulse-glow" style="background: var(--color-accent);"></span>
Active Active
</span> </span>
@@ -50,7 +50,7 @@ const { icon, title, status, currently = false, class: className = '' } = Astro.
<span class="pill">{status}</span> <span class="pill">{status}</span>
)} )}
</div> </div>
<h3 class="font-sans text-lg" style="color: var(--color-text);">{title}</h3> <h3 class="font-display text-lg" style="color: var(--color-text);">{title}</h3>
<div class="grow text-sm leading-relaxed" style="color: var(--color-text-muted);"> <div class="grow text-sm leading-relaxed" style="color: var(--color-text-muted);">
<slot /> <slot />
</div> </div>
+2 -2
View File
@@ -81,7 +81,7 @@ const detail = [year ? String(year) : null, count].filter(Boolean).join(' \u00b7
</div> </div>
<div class="flex flex-col gap-2 p-4"> <div class="flex flex-col gap-2 p-4">
<div class="flex items-start justify-between gap-2"> <div class="flex items-start justify-between gap-2">
<h3 class="font-sans text-lg leading-tight" style="color: var(--color-text);"> <h3 class="font-display text-lg leading-tight" style="color: var(--color-text);">
{title} {title}
</h3> </h3>
{status && <span class="pill shrink-0">{status}</span>} {status && <span class="pill shrink-0">{status}</span>}
@@ -89,7 +89,7 @@ const detail = [year ? String(year) : null, count].filter(Boolean).join(' \u00b7
{ {
detail && ( detail && (
<span <span
class="font-mono text-[0.65rem] uppercase tracking-[0.18em]" class="font-eyebrow text-[0.65rem] uppercase tracking-[0.18em]"
style="color: var(--color-text-muted);" style="color: var(--color-text-muted);"
> >
{detail} {detail}
+21 -21
View File
@@ -85,17 +85,17 @@ export default function AnimatedBackground({
// Resolve star colors from CSS variables so the field matches the theme. // Resolve star colors from CSS variables so the field matches the theme.
const gold = const gold =
getComputedStyle(document.documentElement).getPropertyValue('--color-amber').trim() || getComputedStyle(document.documentElement).getPropertyValue('--color-gold').trim() ||
'#e8b54a'; '#c9a24a';
const violet = // Nebula Violet is background light only, so violet stars use a lifted
getComputedStyle(document.documentElement).getPropertyValue('--color-violet').trim() || // tint of it; teal stars use the on-dark teal so they read at dot size.
'#8b7bd8'; const violet = '#8a63ee';
const teal = const teal =
getComputedStyle(document.documentElement).getPropertyValue('--color-teal').trim() || getComputedStyle(document.documentElement).getPropertyValue('--color-teal-bright').trim() ||
'#6fb6ad'; '#2bc2b7';
const white = const white =
getComputedStyle(document.documentElement).getPropertyValue('--color-cream').trim() || getComputedStyle(document.documentElement).getPropertyValue('--color-starlight').trim() ||
'#f4f1e8'; '#f4f2ed';
const hueColor = (h: Star['hue']) => (h === 'gold' ? gold : h === 'violet' ? violet : h === 'teal' ? teal : white); const hueColor = (h: Star['hue']) => (h === 'gold' ? gold : h === 'violet' ? violet : h === 'teal' ? teal : white);
@@ -214,7 +214,7 @@ export default function AnimatedBackground({
const proximity = 1 - Math.sqrt(d2) / LINK_DIST; const proximity = 1 - Math.sqrt(d2) / LINK_DIST;
const pulse = 0.5 + 0.5 * Math.sin(now / 1400 + (i + j) * 0.7); const pulse = 0.5 + 0.5 * Math.sin(now / 1400 + (i + j) * 0.7);
const alpha = proximity * 0.22 * (0.55 + 0.45 * pulse); const alpha = proximity * 0.22 * (0.55 + 0.45 * pulse);
ctx.strokeStyle = `rgba(232, 181, 74, ${alpha.toFixed(3)})`; ctx.strokeStyle = `rgba(201, 162, 74, ${alpha.toFixed(3)})`;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(A.x, A.y); ctx.moveTo(A.x, A.y);
ctx.lineTo(B.x, B.y); ctx.lineTo(B.x, B.y);
@@ -270,8 +270,8 @@ export default function AnimatedBackground({
const grad = ctx.createLinearGradient(sh.x, sh.y, tailX, tailY); const grad = ctx.createLinearGradient(sh.x, sh.y, tailX, tailY);
const headA = Math.max(0, Math.min(1, sh.life)); const headA = Math.max(0, Math.min(1, sh.life));
grad.addColorStop(0, `rgba(244, 241, 232, ${headA.toFixed(3)})`); grad.addColorStop(0, `rgba(244, 241, 232, ${headA.toFixed(3)})`);
grad.addColorStop(0.4, `rgba(232, 181, 74, ${(headA * 0.6).toFixed(3)})`); grad.addColorStop(0.4, `rgba(201, 162, 74, ${(headA * 0.6).toFixed(3)})`);
grad.addColorStop(1, 'rgba(232, 181, 74, 0)'); grad.addColorStop(1, 'rgba(201, 162, 74, 0)');
ctx.strokeStyle = grad; ctx.strokeStyle = grad;
ctx.lineWidth = 1.6; ctx.lineWidth = 1.6;
ctx.beginPath(); ctx.beginPath();
@@ -332,7 +332,7 @@ export default function AnimatedBackground({
className="absolute inset-0" className="absolute inset-0"
style={{ style={{
background: background:
'linear-gradient(180deg, #04061a 0%, #070b24 32%, #0a1230 58%, #0e1a3e 82%, #12224a 100%)', 'linear-gradient(180deg, #080c14 0%, #0b111c 32%, #0e1522 58%, #131c2b 82%, #1a2a3a 100%)',
}} }}
/> />
{/* Milky Way galaxy band — a soft diagonal luminous streak with a denser {/* Milky Way galaxy band — a soft diagonal luminous streak with a denser
@@ -341,10 +341,10 @@ export default function AnimatedBackground({
className="animate-gradient-shift absolute inset-0 opacity-90" className="animate-gradient-shift absolute inset-0 opacity-90"
style={{ style={{
backgroundImage: ` backgroundImage: `
radial-gradient(38% 70% at 68% 38%, rgba(111, 182, 173, 0.22), transparent 62%), radial-gradient(38% 70% at 68% 38%, rgba(20, 163, 154, 0.22), transparent 62%),
radial-gradient(30% 55% at 72% 44%, rgba(139, 123, 216, 0.26), transparent 60%), radial-gradient(30% 55% at 72% 44%, rgba(106, 47, 224, 0.26), transparent 60%),
radial-gradient(22% 40% at 76% 50%, rgba(232, 181, 74, 0.12), transparent 60%), radial-gradient(22% 40% at 76% 50%, rgba(201, 162, 74, 0.12), transparent 60%),
linear-gradient(115deg, transparent 30%, rgba(236, 233, 255, 0.06) 45%, rgba(236, 233, 255, 0.09) 52%, rgba(236, 233, 255, 0.05) 60%, transparent 72%) linear-gradient(115deg, transparent 30%, rgba(236, 233, 226, 0.06) 45%, rgba(236, 233, 226, 0.09) 52%, rgba(236, 233, 226, 0.05) 60%, transparent 72%)
`, `,
backgroundSize: '200% 200%', backgroundSize: '200% 200%',
}} }}
@@ -354,14 +354,14 @@ export default function AnimatedBackground({
className="absolute inset-0" className="absolute inset-0"
style={{ style={{
backgroundImage: ` backgroundImage: `
radial-gradient(45% 55% at 12% 12%, rgba(139, 123, 216, 0.18), transparent 60%), radial-gradient(45% 55% at 12% 12%, rgba(106, 47, 224, 0.18), transparent 60%),
radial-gradient(40% 50% at 88% 88%, rgba(111, 182, 173, 0.16), transparent 62%) radial-gradient(40% 50% at 88% 88%, rgba(20, 163, 154, 0.16), transparent 62%)
`, `,
}} }}
/> />
{/* Faint astrolabe + sacred-geometry ornaments for esoteric texture. */} {/* Faint astrolabe + sacred-geometry ornaments for esoteric texture. */}
<svg <svg
className="absolute -left-40 -top-40 h-[34rem] w-[34rem] animate-spin-slow text-(--color-amber) opacity-[0.11]" className="absolute -left-40 -top-40 h-[34rem] w-[34rem] animate-spin-slow text-(--color-gold) opacity-[0.11]"
viewBox="0 0 100 100" viewBox="0 0 100 100"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
@@ -414,7 +414,7 @@ export default function AnimatedBackground({
className="absolute inset-0" className="absolute inset-0"
style={{ style={{
background: background:
'radial-gradient(120% 80% at 50% 30%, transparent 40%, rgba(4, 6, 26, 0.7) 100%)', 'radial-gradient(120% 80% at 50% 30%, transparent 40%, rgba(8, 12, 20, 0.7) 100%)',
}} }}
/> />
{/* Canvas star field (skipped under reduced motion). */} {/* Canvas star field (skipped under reduced motion). */}
+5 -5
View File
@@ -3,11 +3,11 @@
* Footer.astro * Footer.astro
* --------------------------------------------------------------------------- * ---------------------------------------------------------------------------
* Minimal footer on a deep night-sky surface: a faint static starfield accent, * Minimal footer on a deep night-sky surface: a faint static starfield accent,
* a StarBurst mark beside the name, social links (mdi icons), copyright, and * the Patiun glyph as a maker's mark beside the name, social links (mdi icons), copyright, and
* the spec microcopy "Built with intention. Powered by caffeine." No emojis. * the spec microcopy "Built with intention. Powered by caffeine." No emojis.
*/ */
import Icon from '@components/Icon.astro'; import Icon from '@components/Icon.astro';
import StarBurst from '@components/mystic/StarBurst.astro'; import PatiunGlyph from '@components/brand/PatiunGlyph.astro';
import StarField from '@components/mystic/StarField.astro'; import StarField from '@components/mystic/StarField.astro';
import { socials, footerMicrocopy, siteIdentity } from '@lib/site'; import { socials, footerMicrocopy, siteIdentity } from '@lib/site';
const year = new Date().getFullYear(); const year = new Date().getFullYear();
@@ -26,12 +26,12 @@ const socialList = Object.values(socials);
/> />
<div class="container-prose relative z-10 flex flex-col items-center gap-6 py-10 text-center sm:flex-row sm:justify-between sm:text-left"> <div class="container-prose relative z-10 flex flex-col items-center gap-6 py-10 text-center sm:flex-row sm:justify-between sm:text-left">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<StarBurst class="h-5 w-5 shrink-0" style="color: var(--color-accent);" /> <PatiunGlyph class="h-6 w-auto shrink-0" />
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<p class="font-sans text-sm font-semibold" style="color: var(--color-text);"> <p class="font-display text-sm font-bold uppercase tracking-[0.14em]" style="color: var(--color-text);">
{siteIdentity.name} {siteIdentity.name}
</p> </p>
<p class="font-mono text-xs italic" style="color: var(--color-text-subtle);"> <p class="font-eyebrow text-xs italic" style="color: var(--color-text-subtle);">
{footerMicrocopy} {footerMicrocopy}
</p> </p>
</div> </div>
+7 -7
View File
@@ -6,7 +6,7 @@
* Milky Way field: a back-to-hub breadcrumb, a small icon mark, the title, and * Milky Way field: a back-to-hub breadcrumb, a small icon mark, the title, and
* the italic tagline. * the italic tagline.
* *
* Tuned for the dark starfield: text uses bright cream tones (not the dim * Tuned for the dark starfield: text uses bright Starlight tones (not the dim
* --color-text-subtle/--color-text which vanish on the deep sky). The title is * --color-text-subtle/--color-text which vanish on the deep sky). The title is
* h2-sized (the page is a sub-page, not Home) so it doesn't dominate. An * h2-sized (the page is a sub-page, not Home) so it doesn't dominate. An
* optional banner image sits above the heading. * optional banner image sits above the heading.
@@ -39,7 +39,7 @@ const {
} = Astro.props; } = Astro.props;
--- ---
<section class="relative overflow-hidden pb-8 pt-28 sm:pt-32"> <section class="on-sky relative overflow-hidden pb-8 pt-28 sm:pt-32">
<!-- Full animated Milky Way field + stars (matches the Home/Interests hero). <!-- Full animated Milky Way field + stars (matches the Home/Interests hero).
Carries the per-page morph name (one .vt-sky per page snapshot). --> Carries the per-page morph name (one .vt-sky per page snapshot). -->
<AnimatedBackground client:idle className="vt-sky" /> <AnimatedBackground client:idle className="vt-sky" />
@@ -54,8 +54,8 @@ const {
<Reveal> <Reveal>
<a <a
href="/interests" href="/interests"
class="inline-flex items-center gap-1.5 font-mono text-xs uppercase tracking-[0.18em] transition-colors" class="inline-flex items-center gap-1.5 font-eyebrow text-xs uppercase tracking-[0.18em] transition-colors"
style="color: var(--color-cream); opacity: 0.8;" style="color: var(--color-starlight); opacity: 0.8;"
> >
<Icon name="arrow-left" class="h-4 w-4" /> <Icon name="arrow-left" class="h-4 w-4" />
{eyebrow} {eyebrow}
@@ -74,8 +74,8 @@ const {
<Reveal delay={100}> <Reveal delay={100}>
<h2 <h2
class="font-sans font-bold tracking-tight" class="font-display font-bold"
style="color: var(--color-cream); font-size: clamp(1.75rem, 2vw + 1rem, 2.5rem); line-height: 1.15;" style="color: var(--color-starlight); font-size: clamp(1.75rem, 2vw + 1rem, 2.5rem); line-height: 1.15;"
> >
{title} {title}
</h2> </h2>
@@ -84,7 +84,7 @@ const {
<Reveal delay={140}> <Reveal delay={140}>
<p <p
class="max-w-xl border-l-2 pl-4 font-sans text-base italic sm:text-lg" class="max-w-xl border-l-2 pl-4 font-sans text-base italic sm:text-lg"
style="border-color: var(--color-accent); color: var(--color-cream); opacity: 0.92;" style="border-color: var(--color-accent); color: var(--color-starlight); opacity: 0.92;"
> >
{tagline} {tagline}
</p> </p>
+1 -1
View File
@@ -40,7 +40,7 @@ const { label, hint, class: className = '' } = Astro.props;
<Icon name="pencil" class="h-4 w-4" /> <Icon name="pencil" class="h-4 w-4" />
</span> </span>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<span class="font-mono text-[0.65rem] font-bold uppercase tracking-[0.18em]" style="color: var(--color-accent);"> <span class="font-eyebrow text-[0.65rem] font-bold uppercase tracking-[0.18em]" style="color: var(--color-accent);">
TODO &middot; {label} TODO &middot; {label}
</span> </span>
{hint && <p class="text-sm" style="color: var(--color-text-muted);">{hint}</p>} {hint && <p class="text-sm" style="color: var(--color-text-muted);">{hint}</p>}
+1 -1
View File
@@ -23,7 +23,7 @@ const { items } = Astro.props;
--- ---
<section class="mt-16"> <section class="mt-16">
<h3 class="mb-5 font-mono text-xs uppercase tracking-[0.22em]" style="color: var(--color-accent);"> <h3 class="mb-5 font-eyebrow text-xs uppercase tracking-[0.22em]" style="color: var(--color-accent);">
You might also like You might also like
</h3> </h3>
<div class="grid gap-4 sm:grid-cols-2"> <div class="grid gap-4 sm:grid-cols-2">
+4 -4
View File
@@ -39,8 +39,8 @@ const {
} = Astro.props; } = Astro.props;
const isHero = tone === "hero"; const isHero = tone === "hero";
const titleColor = isHero ? "var(--color-cream)" : undefined; const titleColor = isHero ? "var(--color-starlight)" : undefined;
const subtitleColor = isHero ? "var(--color-cream)" : "var(--color-text-muted)"; const subtitleColor = isHero ? "var(--color-starlight)" : "var(--color-text-muted)";
--- ---
<div <div
@@ -53,11 +53,11 @@ const subtitleColor = isHero ? "var(--color-cream)" : "var(--color-text-muted)";
{ {
kicker && ( kicker && (
<span <span
class="inline-flex items-center gap-2 font-mono text-xs uppercase tracking-[0.22em]" class="inline-flex items-center gap-2 font-eyebrow text-xs uppercase tracking-[0.22em]"
style={ style={
isHero isHero
? "color: var(--color-accent); text-shadow: 0 0 14px color-mix(in srgb, var(--color-accent) 45%, transparent);" ? "color: var(--color-accent); text-shadow: 0 0 14px color-mix(in srgb, var(--color-accent) 45%, transparent);"
: "color: var(--color-accent);" : "color: var(--color-secondary);"
} }
> >
{icon && <Icon name={icon} class="h-4 w-4" />} {icon && <Icon name={icon} class="h-4 w-4" />}
+2 -2
View File
@@ -39,7 +39,7 @@ const {
/> />
<span class="flex flex-col gap-1"> <span class="flex flex-col gap-1">
<span <span
class="font-mono text-xs uppercase tracking-[0.18em]" class="font-eyebrow text-xs uppercase tracking-[0.18em]"
style="color: var(--color-text-subtle); text-align:right;" style="color: var(--color-text-subtle); text-align:right;"
> >
The professional wing The professional wing
@@ -59,7 +59,7 @@ const {
> >
<span class="flex flex-col gap-1"> <span class="flex flex-col gap-1">
<span <span
class="font-mono text-xs uppercase tracking-[0.18em]" class="font-eyebrow text-xs uppercase tracking-[0.18em]"
style="color: var(--color-text-subtle);" style="color: var(--color-text-subtle);"
> >
The personal wing The personal wing
+4 -4
View File
@@ -29,13 +29,13 @@ const { groups, pillIcon = 'play-circle' } = Astro.props;
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<div class="flex items-baseline gap-2.5"> <div class="flex items-baseline gap-2.5">
<h3 <h3
class="font-sans text-lg" class="font-display text-lg"
style="color: var(--color-text);" style="color: var(--color-text);"
> >
{group.category} {group.category}
</h3> </h3>
<span <span
class="font-mono text-[0.65rem] uppercase tracking-[0.18em]" class="font-eyebrow text-[0.65rem] uppercase tracking-[0.18em]"
style="color: var(--color-text-muted);" style="color: var(--color-text-muted);"
> >
{group.items.length} {group.items.length}
@@ -51,7 +51,7 @@ const { groups, pillIcon = 'play-circle' } = Astro.props;
<span>{item.title}</span> <span>{item.title}</span>
{item.status && ( {item.status && (
<span <span
class="font-mono text-[0.6rem] uppercase tracking-[0.14em]" class="font-eyebrow text-[0.6rem] uppercase tracking-[0.14em]"
style="color: var(--color-accent);" style="color: var(--color-accent);"
> >
{item.status} {item.status}
@@ -59,7 +59,7 @@ const { groups, pillIcon = 'play-circle' } = Astro.props;
)} )}
{item.year && ( {item.year && (
<span <span
class="font-mono text-[0.6rem] tracking-[0.04em]" class="font-eyebrow text-[0.6rem] tracking-[0.04em]"
style="color: var(--color-text-muted);" style="color: var(--color-text-muted);"
> >
{item.year} {item.year}
@@ -11,7 +11,7 @@
* --color-violet at the call site). * --color-violet at the call site).
* *
* Usage: * Usage:
* <AlchemicalDivider class="my-4 h-8 w-full max-w-md mx-auto" style="color: var(--color-amber)" /> * <AlchemicalDivider class="my-4 h-8 w-full max-w-md mx-auto" style="color: var(--color-gold)" />
*/ */
interface Props { interface Props {
class?: string; class?: string;
+1 -1
View File
@@ -11,7 +11,7 @@
* opacity + color at the call site so it reads as a faint engraving. * opacity + color at the call site so it reads as a faint engraving.
* *
* Usage: * Usage:
* <Astrolabe class="pointer-events-none absolute -left-32 -top-32 h-[28rem] w-[28rem] animate-spin-slow" style="color: var(--color-amber); opacity:.12" /> * <Astrolabe class="pointer-events-none absolute -left-32 -top-32 h-[28rem] w-[28rem] animate-spin-slow" style="color: var(--color-gold); opacity:.12" />
*/ */
interface Props { interface Props {
class?: string; class?: string;
+1 -1
View File
@@ -80,7 +80,7 @@ const objectFit = fit === "contain" ? "object-contain" : "object-cover";
</svg> </svg>
</div> </div>
<span <span
class="absolute bottom-3 left-1/2 -translate-x-1/2 font-mono text-[0.6rem] uppercase tracking-[0.2em]" class="absolute bottom-3 left-1/2 -translate-x-1/2 font-eyebrow text-[0.6rem] uppercase tracking-[0.2em]"
style="color: var(--color-text-subtle); opacity:.8;" style="color: var(--color-text-subtle); opacity:.8;"
> >
{alt} {alt}
+7 -7
View File
@@ -56,9 +56,9 @@ export default function CircuitField({
let traces: Trace[] = []; let traces: Trace[] = [];
let nodes: Node[] = []; let nodes: Node[] = [];
const gold = '#e8b54a'; const gold = '#c9a24a';
const violet = '#8b7bd8'; const violet = '#8a63ee';
const teal = '#6fb6ad'; const teal = '#2bc2b7';
function build() { function build() {
if (!canvas || !ctx) return; if (!canvas || !ctx) return;
@@ -116,7 +116,7 @@ export default function CircuitField({
for (let i = 0; i < traces.length; i++) { for (let i = 0; i < traces.length; i++) {
const t = traces[i]; const t = traces[i];
// Base trace — clearly visible violet conductor line. // Base trace — clearly visible violet conductor line.
ctx.strokeStyle = 'rgba(168, 158, 220, 0.55)'; ctx.strokeStyle = 'rgba(43, 194, 183, 0.55)';
ctx.lineWidth = 1.4; ctx.lineWidth = 1.4;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(t.x1, t.y1); ctx.moveTo(t.x1, t.y1);
@@ -140,7 +140,7 @@ export default function CircuitField({
ctx.stroke(); ctx.stroke();
// Bright pulse head dot. // Bright pulse head dot.
ctx.globalAlpha = 1; ctx.globalAlpha = 1;
ctx.fillStyle = '#fff4d6'; ctx.fillStyle = '#fbf3dc';
ctx.beginPath(); ctx.beginPath();
ctx.arc(px, py, 1.8, 0, Math.PI * 2); ctx.arc(px, py, 1.8, 0, Math.PI * 2);
ctx.fill(); ctx.fill();
@@ -172,7 +172,7 @@ export default function CircuitField({
if (!ctx) return; if (!ctx) return;
ctx.clearRect(0, 0, width, height); ctx.clearRect(0, 0, width, height);
for (const t of traces) { for (const t of traces) {
ctx.strokeStyle = 'rgba(168, 158, 220, 0.5)'; ctx.strokeStyle = 'rgba(43, 194, 183, 0.5)';
ctx.lineWidth = 1.4; ctx.lineWidth = 1.4;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(t.x1, t.y1); ctx.moveTo(t.x1, t.y1);
@@ -243,7 +243,7 @@ export default function CircuitField({
className="absolute inset-0" className="absolute inset-0"
style={{ style={{
background: background:
'linear-gradient(180deg, #04061a 0%, #070b24 32%, #0a1230 58%, #0e1a3e 82%, #12224a 100%)', 'linear-gradient(180deg, #080c14 0%, #0b111c 32%, #0e1522 58%, #131c2b 82%, #1a2a3a 100%)',
}} }}
/> />
{/* Circuit traces + nodes + signal pulses. */} {/* Circuit traces + nodes + signal pulses. */}
+5 -5
View File
@@ -67,10 +67,10 @@ export default function ConstellationField({
} }
let stars: Star[] = []; let stars: Star[] = [];
const gold = '#e8b54a'; const gold = '#c9a24a';
const violet = '#8b7bd8'; const violet = '#8a63ee';
const teal = '#6fb6ad'; const teal = '#2bc2b7';
const white = '#f4f1e8'; const white = '#f4f2ed';
const accent = tint === 'violet' ? violet : tint === 'teal' ? teal : gold; const accent = tint === 'violet' ? violet : tint === 'teal' ? teal : gold;
// One full rotation every ~10 minutes — barely perceptible, "planet turning". // One full rotation every ~10 minutes — barely perceptible, "planet turning".
@@ -168,7 +168,7 @@ export default function ConstellationField({
const proximity = 1 - Math.sqrt(d2) / LINK_DIST; const proximity = 1 - Math.sqrt(d2) / LINK_DIST;
const pulse = 0.5 + 0.5 * Math.sin(now / 1600 + (i + j) * 0.7); const pulse = 0.5 + 0.5 * Math.sin(now / 1600 + (i + j) * 0.7);
const alpha = proximity * 0.24 * (0.55 + 0.45 * pulse); const alpha = proximity * 0.24 * (0.55 + 0.45 * pulse);
ctx.strokeStyle = `rgba(232, 181, 74, ${alpha.toFixed(3)})`; ctx.strokeStyle = `rgba(201, 162, 74, ${alpha.toFixed(3)})`;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(A.x, A.y); ctx.moveTo(A.x, A.y);
ctx.lineTo(B.x, B.y); ctx.lineTo(B.x, B.y);
+1 -1
View File
@@ -11,7 +11,7 @@
* Apply opacity + sizing + color at the call site. * Apply opacity + sizing + color at the call site.
* *
* Usage: * Usage:
* <SacredGeometry class="pointer-events-none absolute -right-24 -top-24 h-96 w-96" style="color: var(--color-amber); opacity:.08" /> * <SacredGeometry class="pointer-events-none absolute -right-24 -top-24 h-96 w-96" style="color: var(--color-gold); opacity:.08" />
*/ */
interface Props { interface Props {
class?: string; class?: string;
+1 -1
View File
@@ -11,7 +11,7 @@
* - `outline`: stroke-only, lighter * - `outline`: stroke-only, lighter
* *
* Usage: * Usage:
* <StarBurst class="h-4 w-4" style="color: var(--color-amber)" /> * <StarBurst class="h-4 w-4" style="color: var(--color-gold)" />
* <StarBurst variant="outline" class="h-6 w-6" /> * <StarBurst variant="outline" class="h-6 w-6" />
*/ */
interface Props { interface Props {
+1 -1
View File
@@ -10,7 +10,7 @@
* cheap. Pointer-events disabled; inherits `currentColor`. * cheap. Pointer-events disabled; inherits `currentColor`.
* *
* Usage: * Usage:
* <StarField class="absolute inset-0 h-full w-full" style="color: var(--color-amber); opacity:.5" /> * <StarField class="absolute inset-0 h-full w-full" style="color: var(--color-gold); opacity:.5" />
*/ */
interface Props { interface Props {
class?: string; class?: string;
+18 -17
View File
@@ -12,7 +12,7 @@
*/ */
import Icon from "@components/Icon.astro"; import Icon from "@components/Icon.astro";
import MobileMenu from "./MobileMenu.tsx"; import MobileMenu from "./MobileMenu.tsx";
import StarBurst from "@components/mystic/StarBurst.astro"; import PatiunGlyph from "@components/brand/PatiunGlyph.astro";
import { navGroups, siteIdentity } from "@lib/site"; import { navGroups, siteIdentity } from "@lib/site";
const path = Astro.url.pathname; const path = Astro.url.pathname;
@@ -33,7 +33,7 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
id="site-nav" id="site-nav"
data-nav data-nav
class:list={[ class:list={[
"fixed inset-x-0 top-0 z-50 transition-[background,box-shadow,border-color] duration-300", "on-sky fixed inset-x-0 top-0 z-50 transition-[background,box-shadow,border-color] duration-300",
heroSkyClass, heroSkyClass,
]} ]}
> >
@@ -45,28 +45,29 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
class="container-prose flex h-16 items-center justify-between gap-4" class="container-prose flex h-16 items-center justify-between gap-4"
aria-label="Primary" aria-label="Primary"
> >
<!-- Logo / identity --> <!-- Logo / identity: the approved Greg Kilmer lockup. Cinzel wordmark,
the "a Patiun LLC company" line in Montserrat, and the Patiun glyph
as a small maker's mark beside it (never overlapping). -->
<a <a
href="/" href="/"
class="group flex items-center gap-2 leading-tight" class="group flex items-center gap-2.5 leading-tight"
aria-label={`${siteIdentity.name} home`} aria-label={`${siteIdentity.name} home`}
> >
<StarBurst <PatiunGlyph
class="h-4 w-4 shrink-0 transition-transform duration-700 group-hover:rotate-90" class="h-5 w-auto shrink-0 transition-transform duration-700 group-hover:-rotate-12"
style="color: var(--color-accent);"
/> />
<span class="flex flex-col"> <span class="flex flex-col">
<span <span
class="font-sans text-base font-bold tracking-tight" class="font-display text-base font-bold uppercase tracking-[0.14em]"
style="color: var(--accent);" style="color: var(--color-starlight);"
> >
{siteIdentity.name} {siteIdentity.name}
</span> </span>
<span <span
class="hidden font-mono text-[0.65rem] uppercase tracking-[0.18em] sm:block" class="hidden font-eyebrow text-[0.62rem] font-medium tracking-[0.08em] sm:block"
style="color: var(--color-text-subtle);" style="color: var(--color-text-subtle);"
> >
{siteIdentity.tagline} {siteIdentity.lockupLine}
</span> </span>
</span> </span>
</a> </a>
@@ -93,7 +94,7 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
style={ style={
active active
? "color: var(--color-accent);" ? "color: var(--color-accent);"
: "color: var(--color-cream); opacity: 0.85;" : "color: var(--color-starlight); opacity: 0.85;"
} }
onmouseover="this.style.opacity='1'" onmouseover="this.style.opacity='1'"
onmouseout="this.style.opacity='0.85'" onmouseout="this.style.opacity='0.85'"
@@ -127,8 +128,8 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
[data-nav-inner] { [data-nav-inner] {
background: linear-gradient( background: linear-gradient(
180deg, 180deg,
rgba(8, 10, 32, 0.92) 0%, rgba(14, 21, 34, 0.92) 0%,
rgba(12, 14, 40, 0.86) 100% rgba(19, 28, 43, 0.86) 100%
); );
} }
/* Scrolled state: stronger blur + hairline border + shadow. */ /* Scrolled state: stronger blur + hairline border + shadow. */
@@ -140,7 +141,7 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
var(--color-accent) 30%, var(--color-accent) 30%,
var(--color-border) var(--color-border)
); );
box-shadow: 0 8px 30px -20px rgba(11, 16, 38, 0.7); box-shadow: 0 8px 30px -20px rgba(8, 12, 20, 0.7);
} }
/* A subtle violet/gold nebula glow behind the nav, fixed to the top. */ /* A subtle violet/gold nebula glow behind the nav, fixed to the top. */
[data-nav]::before { [data-nav]::before {
@@ -152,12 +153,12 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
background: background:
radial-gradient( radial-gradient(
60% 120% at 85% -20%, 60% 120% at 85% -20%,
rgba(139, 123, 216, 0.22), rgba(106, 47, 224, 0.22),
transparent 70% transparent 70%
), ),
radial-gradient( radial-gradient(
50% 120% at 10% -10%, 50% 120% at 10% -10%,
rgba(232, 181, 74, 0.1), rgba(201, 162, 74, 0.1),
transparent 70% transparent 70%
); );
} }
@@ -58,14 +58,14 @@ const entries: CertEntry[] = [
{c.status && <span class="pill">{c.status}</span>} {c.status && <span class="pill">{c.status}</span>}
</div> </div>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<h3 class="font-sans text-base" style="color: var(--color-text);"> <h3 class="font-display text-base" style="color: var(--color-text);">
{c.name} {c.name}
</h3> </h3>
<p class="text-sm font-medium" style="color: var(--color-accent);"> <p class="text-sm font-medium" style="color: var(--color-accent);">
{c.issuer} {c.issuer}
</p> </p>
<span <span
class="font-mono text-xs uppercase tracking-[0.18em]" class="font-eyebrow text-xs uppercase tracking-[0.18em]"
style="color: var(--color-text-subtle);" style="color: var(--color-text-subtle);"
> >
{c.year} {c.year}
+1 -1
View File
@@ -223,7 +223,7 @@ export default function ContactForm({ turnstileSiteKey }: Props) {
<path d="M20 6L9 17l-5-5" /> <path d="M20 6L9 17l-5-5" />
</svg> </svg>
</span> </span>
<h3 className="font-sans text-lg font-semibold" style={{ color: 'var(--color-text)' }}> <h3 className="font-display text-lg font-semibold" style={{ color: 'var(--color-text)' }}>
Message sent. Message sent.
</h3> </h3>
<p className="text-sm" style={{ color: 'var(--color-text-muted)' }}> <p className="text-sm" style={{ color: 'var(--color-text-muted)' }}>
@@ -45,7 +45,7 @@ const entries: EduEntry[] = [
<Icon name={e.icon} class="h-5 w-5" /> <Icon name={e.icon} class="h-5 w-5" />
</span> </span>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<h3 class="font-sans text-lg" style="color: var(--color-text);"> <h3 class="font-display text-lg" style="color: var(--color-text);">
{e.credential} {e.credential}
</h3> </h3>
<p <p
@@ -55,7 +55,7 @@ const entries: EduEntry[] = [
{e.institution} {e.institution}
</p> </p>
<span <span
class="font-mono text-xs uppercase tracking-[0.18em]" class="font-eyebrow text-xs uppercase tracking-[0.18em]"
style="color: var(--color-text-subtle);" style="color: var(--color-text-subtle);"
> >
{e.period} {e.period}
@@ -33,7 +33,7 @@ const links = [
<div class="flex items-start justify-between gap-3"> <div class="flex items-start justify-between gap-3">
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<h3 style="color: var(--color-text);">{d.title}</h3> <h3 style="color: var(--color-text);">{d.title}</h3>
<p class="font-mono text-xs uppercase tracking-[0.18em]" style="color: var(--color-text-subtle);"> <p class="font-eyebrow text-xs uppercase tracking-[0.18em]" style="color: var(--color-text-subtle);">
{d.role} {d.role}
</p> </p>
</div> </div>
+8 -8
View File
@@ -8,7 +8,7 @@
* *
* Three bugs previously fixed here: * Three bugs previously fixed here:
* 1. Contrast: the rail used surface-relative tokens (--color-surface-raised / * 1. Contrast: the rail used surface-relative tokens (--color-surface-raised /
* --color-cream) which collapsed to the same tone on light surfaces (the * --color-starlight) which collapsed to the same tone on light surfaces (the
* interest-surface cream made icons/text invisible). The rail now uses * interest-surface cream made icons/text invisible). The rail now uses
* FIXED dark colors regardless of the page's surface tone. * FIXED dark colors regardless of the page's surface tone.
* 2. Progress line clipping: the line sat at the icon column's horizontal * 2. Progress line clipping: the line sat at the icon column's horizontal
@@ -41,7 +41,7 @@ const sections = [
for the progress line, then the icon+label list. */} for the progress line, then the icon+label list. */}
<div <div
class="relative flex gap-2 rounded-2xl border p-3 shadow-2xl" class="relative flex gap-2 rounded-2xl border p-3 shadow-2xl"
style="border-color: rgba(232, 181, 74, 0.22); background: linear-gradient(180deg, rgba(12, 14, 40, 0.97), rgba(8, 10, 32, 0.97)); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); box-shadow: 0 18px 40px -24px rgba(11, 16, 38, 0.7);" style="border-color: rgba(201, 162, 74, 0.22); background: linear-gradient(180deg, rgba(19, 28, 43, 0.97), rgba(14, 21, 34, 0.97)); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); box-shadow: 0 18px 40px -24px rgba(8, 12, 20, 0.7);"
> >
{/* --- Left gutter: the progress track + fill. Sits LEFT of the icons. --- */} {/* --- Left gutter: the progress track + fill. Sits LEFT of the icons. --- */}
<div class="relative my-1 w-2 shrink-0"> <div class="relative my-1 w-2 shrink-0">
@@ -69,11 +69,11 @@ const sections = [
href={`#${s.id}`} href={`#${s.id}`}
data-section-link={s.id} data-section-link={s.id}
class="group relative flex items-center gap-2.5 rounded-lg px-2.5 py-2 font-sans text-sm font-semibold transition-all" class="group relative flex items-center gap-2.5 rounded-lg px-2.5 py-2 font-sans text-sm font-semibold transition-all"
style="color: #e8e6f5;" style="color: #ece9e2;"
> >
<span <span
class="relative z-10 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-lg transition-colors" class="relative z-10 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-lg transition-colors"
style="background: rgba(232, 181, 74, 0.14); color: #f4cd72;" style="background: rgba(201, 162, 74, 0.14); color: #d8b35a;"
data-section-dot={s.id} data-section-dot={s.id}
> >
<Icon name={s.icon} class="h-[1.15rem] w-[1.15rem]" /> <Icon name={s.icon} class="h-[1.15rem] w-[1.15rem]" />
@@ -92,19 +92,19 @@ const sections = [
} }
.pro-section-nav a[data-section-link]:hover { .pro-section-nav a[data-section-link]:hover {
opacity: 1; opacity: 1;
background: rgba(232, 181, 74, 0.08); background: rgba(201, 162, 74, 0.08);
} }
/* Active link: bright accent text + accent wash. */ /* Active link: bright accent text + accent wash. */
.pro-section-nav a[data-active="true"] { .pro-section-nav a[data-active="true"] {
opacity: 1 !important; opacity: 1 !important;
color: var(--color-accent) !important; color: var(--color-accent) !important;
background: rgba(232, 181, 74, 0.14); background: rgba(201, 162, 74, 0.14);
} }
/* Active dot: filled accent, dark icon, glow ring. */ /* Active dot: filled accent, dark icon, glow ring. */
.pro-section-nav a[data-active="true"] [data-section-dot] { .pro-section-nav a[data-active="true"] [data-section-dot] {
background: var(--color-accent) !important; background: var(--color-accent) !important;
color: #0b1026 !important; color: #131c2b !important;
box-shadow: 0 0 12px rgba(232, 181, 74, 0.65); box-shadow: 0 0 12px rgba(201, 162, 74, 0.65);
} }
</style> </style>
+3 -3
View File
@@ -72,7 +72,7 @@ const techStack = [
<div class="flex flex-col gap-8"> <div class="flex flex-col gap-8">
<!-- Core Competencies --> <!-- Core Competencies -->
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<h3 class="font-sans" style="color: var(--color-text);"> <h3 class="font-display" style="color: var(--color-text);">
Core Competencies Core Competencies
</h3> </h3>
<ul class="grid gap-3 sm:grid-cols-2"> <ul class="grid gap-3 sm:grid-cols-2">
@@ -112,7 +112,7 @@ const techStack = [
<!-- Technical Stack --> <!-- Technical Stack -->
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<h3 class="font-sans" style="color: var(--color-text);">Technical Stack</h3> <h3 class="font-display" style="color: var(--color-text);">Technical Stack</h3>
<div class="grid gap-4 sm:grid-cols-2"> <div class="grid gap-4 sm:grid-cols-2">
{ {
techStack.map((item) => ( techStack.map((item) => (
@@ -151,7 +151,7 @@ const techStack = [
<!-- Emerging & Exploring --> <!-- Emerging & Exploring -->
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<h3 class="font-sans" style="color: var(--color-text);"> <h3 class="font-display" style="color: var(--color-text);">
Emerging &amp; Exploring Emerging &amp; Exploring
</h3> </h3>
<ul class="grid gap-3 sm:grid-cols-3"> <ul class="grid gap-3 sm:grid-cols-3">
+1 -1
View File
@@ -50,7 +50,7 @@ const rendered = await Promise.all(
</span> </span>
{entry.data.company} {entry.data.company}
</h3> </h3>
<span class="font-mono text-xs uppercase tracking-[0.18em]" style="color: var(--color-text-subtle);"> <span class="font-eyebrow text-xs uppercase tracking-[0.18em]" style="color: var(--color-text-subtle);">
{entry.data.period} {entry.data.period}
</span> </span>
</div> </div>
+8 -8
View File
@@ -150,11 +150,11 @@ export default function Carousel({ slides, label = 'Product examples' }: Props)
{/* Caption gradient + label, always visible for the active slide. */} {/* Caption gradient + label, always visible for the active slide. */}
<div <div
className="pointer-events-none absolute inset-x-0 bottom-0 flex items-end p-4" className="pointer-events-none absolute inset-x-0 bottom-0 flex items-end p-4"
style={{ background: 'linear-gradient(to top, rgba(7,10,28,0.85), transparent)' }} style={{ background: 'linear-gradient(to top, rgba(14,21,34,0.85), transparent)' }}
> >
<span <span
className="font-sans text-sm font-semibold sm:text-base" className="font-sans text-sm font-semibold sm:text-base"
style={{ color: 'var(--color-cream)' }} style={{ color: 'var(--color-starlight)' }}
> >
{s.label} {s.label}
</span> </span>
@@ -172,9 +172,9 @@ export default function Carousel({ slides, label = 'Product examples' }: Props)
aria-label="Previous slide" aria-label="Previous slide"
className="absolute left-2 top-1/2 inline-flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full border backdrop-blur-sm transition-colors" className="absolute left-2 top-1/2 inline-flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full border backdrop-blur-sm transition-colors"
style={{ style={{
color: 'var(--color-cream)', color: 'var(--color-starlight)',
borderColor: 'color-mix(in srgb, var(--color-accent) 40%, transparent)', borderColor: 'color-mix(in srgb, var(--color-accent) 40%, transparent)',
background: 'rgba(7,10,28,0.5)', background: 'rgba(14,21,34,0.5)',
}} }}
> >
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
@@ -187,9 +187,9 @@ export default function Carousel({ slides, label = 'Product examples' }: Props)
aria-label="Next slide" aria-label="Next slide"
className="absolute right-2 top-1/2 inline-flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full border backdrop-blur-sm transition-colors" className="absolute right-2 top-1/2 inline-flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full border backdrop-blur-sm transition-colors"
style={{ style={{
color: 'var(--color-cream)', color: 'var(--color-starlight)',
borderColor: 'color-mix(in srgb, var(--color-accent) 40%, transparent)', borderColor: 'color-mix(in srgb, var(--color-accent) 40%, transparent)',
background: 'rgba(7,10,28,0.5)', background: 'rgba(14,21,34,0.5)',
}} }}
> >
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
@@ -201,8 +201,8 @@ export default function Carousel({ slides, label = 'Product examples' }: Props)
{/* Slide counter (top-right). */} {/* Slide counter (top-right). */}
<span <span
className="pointer-events-none absolute right-3 top-3 rounded-full px-2.5 py-1 font-mono text-xs" className="pointer-events-none absolute right-3 top-3 rounded-full px-2.5 py-1 font-eyebrow text-xs"
style={{ color: 'var(--color-cream)', background: 'rgba(7,10,28,0.6)' }} style={{ color: 'var(--color-starlight)', background: 'rgba(14,21,34,0.6)' }}
> >
{index + 1} / {count} {index + 1} / {count}
</span> </span>
+1 -1
View File
@@ -27,7 +27,7 @@ const inStockCount = colors.filter((c) => c.inStock).length;
<span class="text-sm" style="color: var(--color-text-muted);"> <span class="text-sm" style="color: var(--color-text-muted);">
Choose from the available filament colors. Choose from the available filament colors.
</span> </span>
<span class="font-mono text-xs" style="color: var(--color-text-subtle);"> <span class="font-eyebrow text-xs" style="color: var(--color-text-subtle);">
{inStockCount} of {colors.length} in stock {inStockCount} of {colors.length} in stock
</span> </span>
</div> </div>
+1 -1
View File
@@ -210,7 +210,7 @@ export default function OrderForm({ turnstileSiteKey, colors = [] }: Props) {
<path d="M20 6L9 17l-5-5" /> <path d="M20 6L9 17l-5-5" />
</svg> </svg>
</span> </span>
<h3 className="font-sans text-lg font-semibold" style={{ color: 'var(--color-text)' }}> <h3 className="font-display text-lg font-semibold" style={{ color: 'var(--color-text)' }}>
Order request sent. Order request sent.
</h3> </h3>
<p className="text-sm" style={{ color: 'var(--color-text-muted)' }}> <p className="text-sm" style={{ color: 'var(--color-text-muted)' }}>
+1 -1
View File
@@ -60,7 +60,7 @@ const { icon, caption, tiers, priceHeader = 'Price', subtitle } = Astro.props;
<span class="ml-2 text-xs" style="color: var(--color-text-subtle);">({tier.note})</span> <span class="ml-2 text-xs" style="color: var(--color-text-subtle);">({tier.note})</span>
)} )}
</td> </td>
<td class="px-4 py-3 text-right font-mono font-semibold" style="color: var(--color-accent);"> <td class="px-4 py-3 text-right font-semibold" style="color: var(--color-accent);">
{tier.price} {tier.price}
</td> </td>
</tr> </tr>
+2 -2
View File
@@ -37,8 +37,8 @@ const cover = product.images[0];
/> />
{product.images.length > 1 && ( {product.images.length > 1 && (
<span <span
class="pointer-events-none absolute right-2 top-2 inline-flex items-center gap-1 rounded-full px-2 py-1 font-mono text-xs" class="pointer-events-none absolute right-2 top-2 inline-flex items-center gap-1 rounded-full px-2 py-1 font-eyebrow text-xs"
style="color: var(--color-cream); background: rgba(7,10,28,0.6);" style="color: var(--color-starlight); background: rgba(14,21,34,0.6);"
> >
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<rect x="3" y="5" width="18" height="14" rx="2" /> <rect x="3" y="5" width="18" height="14" rx="2" />
+2 -2
View File
@@ -24,7 +24,7 @@ const { items, heading } = Astro.props;
function toneColor(icon: Requirement['icon']): string { function toneColor(icon: Requirement['icon']): string {
switch (icon) { switch (icon) {
case 'alert': case 'alert':
return '#e8b54a'; // amber — caution return '#d8b35a'; // gold — caution
case 'close-circle': case 'close-circle':
return '#e5655a'; // red — hard no return '#e5655a'; // red — hard no
case 'check-decagram': case 'check-decagram':
@@ -36,7 +36,7 @@ function toneColor(icon: Requirement['icon']): string {
--- ---
{heading && ( {heading && (
<h3 class="font-sans text-xl font-bold" style="color: var(--color-text);">{heading}</h3> <h3 class="font-display text-xl font-bold" style="color: var(--color-text);">{heading}</h3>
)} )}
<ul class="flex flex-col gap-3"> <ul class="flex flex-col gap-3">
+1 -1
View File
@@ -45,7 +45,7 @@ const pct = total > 0 ? Math.max(0, Math.min(100, (open / total) * 100)) : 0;
<div class="flex items-center justify-between text-sm" style="color: var(--color-text-muted);"> <div class="flex items-center justify-between text-sm" style="color: var(--color-text-muted);">
<span>{open} of {total} slots open</span> <span>{open} of {total} slots open</span>
<span class="font-mono text-xs" style="color: var(--color-text-subtle);">{closed ? '0%' : `${Math.round(pct)}%`}</span> <span class="font-eyebrow text-xs" style="color: var(--color-text-subtle);">{closed ? '0%' : `${Math.round(pct)}%`}</span>
</div> </div>
<div <div
+13 -14
View File
@@ -8,13 +8,13 @@
* two sites not be clearly linked yet. * two sites not be clearly linked yet.
* *
* Visually it mirrors NavBar.astro's treatment — the dark nebula band, the * Visually it mirrors NavBar.astro's treatment — the dark nebula band, the
* scroll blur, and the StarBurst mark — so it reads as the same house while * scroll blur, and the Patiun glyph mark — so it reads as the same house while
* being unmistakably a store (a "Store" wordmark + the store.gregkilmer.com * being unmistakably a store (a "Store" wordmark + the store.gregkilmer.com
* domain label). * domain label).
*/ */
import Icon from '@components/Icon.astro'; import Icon from '@components/Icon.astro';
import MobileMenu from '@components/nav/MobileMenu.tsx'; import MobileMenu from '@components/nav/MobileMenu.tsx';
import StarBurst from '@components/mystic/StarBurst.astro'; import PatiunGlyph from '@components/brand/PatiunGlyph.astro';
interface NavEntry { interface NavEntry {
label: string; label: string;
@@ -40,22 +40,21 @@ const groups = [{ items: storeNav }];
<header <header
id="store-nav" id="store-nav"
data-nav data-nav
class="fixed inset-x-0 top-0 z-50 transition-[background,box-shadow,border-color] duration-300" class="on-sky fixed inset-x-0 top-0 z-50 transition-[background,box-shadow,border-color] duration-300"
> >
<div class="border-b border-transparent transition-colors duration-300" data-nav-inner> <div class="border-b border-transparent transition-colors duration-300" data-nav-inner>
<nav class="container-prose flex h-16 items-center justify-between gap-4" aria-label="Store"> <nav class="container-prose flex h-16 items-center justify-between gap-4" aria-label="Store">
<!-- Wordmark: "Greg Kilmer · Store" + the store domain label --> <!-- Wordmark: "Greg Kilmer · Store" + the store domain label -->
<a href="/store" class="group flex items-center gap-2 leading-tight" aria-label="Greg Kilmer Store — home"> <a href="/store" class="group flex items-center gap-2 leading-tight" aria-label="Greg Kilmer Store — home">
<StarBurst <PatiunGlyph
class="h-4 w-4 shrink-0 transition-transform duration-700 group-hover:rotate-90" class="h-5 w-auto shrink-0 transition-transform duration-700 group-hover:-rotate-12"
style="color: var(--color-accent);"
/> />
<span class="flex flex-col"> <span class="flex flex-col">
<span class="font-sans text-base font-bold tracking-tight" style="color: var(--accent);"> <span class="font-display text-base font-bold uppercase tracking-[0.12em]" style="color: var(--color-starlight);">
Greg Kilmer · Store Greg Kilmer · Store
</span> </span>
<span <span
class="hidden font-mono text-[0.65rem] uppercase tracking-[0.18em] sm:block" class="hidden font-eyebrow text-[0.65rem] uppercase tracking-[0.18em] sm:block"
style="color: var(--color-text-subtle);" style="color: var(--color-text-subtle);"
> >
store.gregkilmer.com store.gregkilmer.com
@@ -71,7 +70,7 @@ const groups = [{ items: storeNav }];
<a <a
href={item.href} href={item.href}
class="inline-flex items-center gap-1.5 rounded-md px-3 py-2 text-sm font-medium transition-colors" class="inline-flex items-center gap-1.5 rounded-md px-3 py-2 text-sm font-medium transition-colors"
style={active ? 'color: var(--color-accent);' : 'color: var(--color-cream); opacity: 0.85;'} style={active ? 'color: var(--color-accent);' : 'color: var(--color-starlight); opacity: 0.85;'}
onmouseover="this.style.opacity='1'" onmouseover="this.style.opacity='1'"
onmouseout="this.style.opacity='0.85'" onmouseout="this.style.opacity='0.85'"
aria-current={active ? 'page' : undefined} aria-current={active ? 'page' : undefined}
@@ -100,8 +99,8 @@ const groups = [{ items: storeNav }];
[data-nav-inner] { [data-nav-inner] {
background: linear-gradient( background: linear-gradient(
180deg, 180deg,
rgb(8, 10, 32) 0%, rgb(14, 21, 34) 0%,
rgb(12, 14, 40) 100% rgb(19, 28, 43) 100%
); );
border-bottom: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent); border-bottom: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
} }
@@ -113,7 +112,7 @@ const groups = [{ items: storeNav }];
var(--color-accent) 30%, var(--color-accent) 30%,
var(--color-border) var(--color-border)
); );
box-shadow: 0 8px 30px -20px rgba(11, 16, 38, 0.7); box-shadow: 0 8px 30px -20px rgba(8, 12, 20, 0.7);
} }
[data-nav]::before { [data-nav]::before {
content: ''; content: '';
@@ -122,8 +121,8 @@ const groups = [{ items: storeNav }];
z-index: -1; z-index: -1;
pointer-events: none; pointer-events: none;
background: background:
radial-gradient(60% 120% at 85% -20%, rgba(139, 123, 216, 0.22), transparent 70%), radial-gradient(60% 120% at 85% -20%, rgba(106, 47, 224, 0.22), transparent 70%),
radial-gradient(50% 120% at 10% -10%, rgba(232, 181, 74, 0.1), transparent 70%); radial-gradient(50% 120% at 10% -10%, rgba(201, 162, 74, 0.1), transparent 70%);
} }
</style> </style>
+1 -1
View File
@@ -173,7 +173,7 @@ export default function BoardGameShelf({
<figcaption <figcaption
className="absolute inset-x-0 bottom-0 flex flex-col gap-0.5 p-4" className="absolute inset-x-0 bottom-0 flex flex-col gap-0.5 p-4"
style={{ color: 'var(--color-cream)' }} style={{ color: 'var(--color-starlight)' }}
> >
<span className="font-sans text-lg leading-tight drop-shadow"> <span className="font-sans text-lg leading-tight drop-shadow">
{activeGame?.name ?? shelfCaption} {activeGame?.name ?? shelfCaption}
+12 -5
View File
@@ -15,8 +15,10 @@
import "@/styles/global.css"; import "@/styles/global.css";
// Self-hosted fonts (woff2, variable). Avoids external requests for perf + privacy. // Self-hosted fonts (woff2, variable). Avoids external requests for perf + privacy.
import "@fontsource-variable/inter"; // Patiun brand type: Cinzel (headings, names) + Montserrat (body, UI).
import "@fontsource-variable/plus-jakarta-sans"; // JetBrains Mono stays for the rare code-like block.
import "@fontsource-variable/cinzel";
import "@fontsource-variable/montserrat";
import "@fontsource-variable/jetbrains-mono"; import "@fontsource-variable/jetbrains-mono";
import { ClientRouter } from "astro:transitions"; import { ClientRouter } from "astro:transitions";
@@ -104,7 +106,7 @@ const ogImageUrl = ogImage
<meta name="description" content={description} /> <meta name="description" content={description} />
<link rel="canonical" href={canonical} /> <link rel="canonical" href={canonical} />
{noindex && <meta name="robots" content="noindex, nofollow" />} {noindex && <meta name="robots" content="noindex, nofollow" />}
<meta name="theme-color" content="#0b1026" /> <meta name="theme-color" content="#131c2b" />
<!-- Open Graph / social cards --> <!-- Open Graph / social cards -->
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
@@ -115,7 +117,12 @@ const ogImageUrl = ogImage
{ogImageUrl && <meta property="og:image" content={ogImageUrl} />} {ogImageUrl && <meta property="og:image" content={ogImageUrl} />}
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" /> <!-- Favicons and app icons: the Greg Kilmer package from the Patiun brand
files (GK monogram on Midnight). -->
<link rel="icon" href="/favicon.ico" sizes="48x48" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<!-- Theme boot: blocking inline script prevents flash of the wrong theme. <!-- Theme boot: blocking inline script prevents flash of the wrong theme.
Dark mode is currently forced (light mode deferred), so this just pins Dark mode is currently forced (light mode deferred), so this just pins
@@ -149,7 +156,7 @@ const ogImageUrl = ogImage
<body data-surface={surface} data-page={isHome ? "home" : "other"}> <body data-surface={surface} data-page={isHome ? "home" : "other"}>
<a <a
href="#main" href="#main"
class="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-[100] focus:rounded-lg focus:bg-(--color-amber) focus:px-4 focus:py-2 focus:text-(--color-ink)" class="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-[100] focus:rounded-lg focus:bg-(--color-teal) focus:px-4 focus:py-2 focus:text-(--color-midnight)"
> >
Skip to content Skip to content
</a> </a>
+4 -2
View File
@@ -28,8 +28,10 @@ export const siteIdentity = {
name: 'Greg Kilmer', name: 'Greg Kilmer',
/** Spec tagline; appears beside the logo. */ /** Spec tagline; appears beside the logo. */
tagline: 'Developer. Maker. Storyteller.', tagline: 'Developer. Maker. Storyteller.',
/** Spec domain. */ /** Brand lockup line shown under the name in the header (Patiun brand). */
domain: 'gregkilmer.com', lockupLine: 'Web Development · a Patiun LLC company',
/** Domain, written capitalized per the Patiun brand. */
domain: 'GregKilmer.com',
} as const; } as const;
/** /**
+2 -2
View File
@@ -38,7 +38,7 @@ const quickFacts = [
description="The human story behind the resume: Greg Kilmer on curiosity, tabletop RPGs, resin dice, and a Philadelphia apartment with too many board games." description="The human story behind the resume: Greg Kilmer on curiosity, tabletop RPGs, resin dice, and a Philadelphia apartment with too many board games."
> >
<!-- Hero — full animated Milky Way field, matching the Interests hero. --> <!-- Hero — full animated Milky Way field, matching the Interests hero. -->
<section class="relative overflow-hidden pb-8 pt-28 sm:pt-32"> <section class="on-sky relative overflow-hidden pb-8 pt-28 sm:pt-32">
<AnimatedBackground client:idle className="vt-sky" /> <AnimatedBackground client:idle className="vt-sky" />
<div class="container-prose relative z-10 flex flex-col gap-8 py-8"> <div class="container-prose relative z-10 flex flex-col gap-8 py-8">
<Reveal> <Reveal>
@@ -124,7 +124,7 @@ const quickFacts = [
<Reveal delay={120}> <Reveal delay={120}>
<aside class="card flex flex-col gap-4 lg:sticky lg:top-24"> <aside class="card flex flex-col gap-4 lg:sticky lg:top-24">
<h3 <h3
class="font-mono text-xs uppercase tracking-[0.22em]" class="font-eyebrow text-xs uppercase tracking-[0.22em]"
style="color: var(--color-accent);" style="color: var(--color-accent);"
> >
Quick facts Quick facts
+4 -4
View File
@@ -89,7 +89,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
<Reveal> <Reveal>
<section class="flex flex-col gap-4"> <section class="flex flex-col gap-4">
<h2 <h2
class="font-mono text-xs uppercase tracking-[0.22em]" class="font-eyebrow text-xs uppercase tracking-[0.22em]"
style="color: var(--color-accent);" style="color: var(--color-accent);"
> >
Professional Professional
@@ -117,7 +117,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
<span class="text-xs" style="color: var(--color-text-subtle);"> <span class="text-xs" style="color: var(--color-text-subtle);">
<ObfuscatedEmail <ObfuscatedEmail
address={emailAddress} address={emailAddress}
class="font-mono uppercase tracking-[0.1em]" class="font-eyebrow uppercase tracking-[0.1em]"
/> />
</span> </span>
</span> </span>
@@ -164,7 +164,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
<Reveal delay={80}> <Reveal delay={80}>
<section class="flex flex-col gap-4"> <section class="flex flex-col gap-4">
<h2 <h2
class="font-mono text-xs uppercase tracking-[0.22em]" class="font-eyebrow text-xs uppercase tracking-[0.22em]"
style="color: var(--color-secondary);" style="color: var(--color-secondary);"
> >
Personal / Social Personal / Social
@@ -222,7 +222,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
<Reveal delay={100}> <Reveal delay={100}>
<section class="card flex flex-col gap-5"> <section class="card flex flex-col gap-5">
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<h2 class="font-sans" style="color: var(--color-text);"> <h2 class="font-display" style="color: var(--color-text);">
Send a message Send a message
</h2> </h2>
<p class="text-sm" style="color: var(--color-text-muted);"> <p class="text-sm" style="color: var(--color-text-muted);">
+5 -5
View File
@@ -52,7 +52,7 @@ const cards = [
<BaseLayout> <BaseLayout>
<!-- ===================== HERO ===================== --> <!-- ===================== HERO ===================== -->
<section <section
class="vt-hero-sky relative flex min-h-[88vh] items-center overflow-hidden pt-16" class="on-sky vt-hero-sky relative flex min-h-[88vh] items-center overflow-hidden pt-16"
> >
<AnimatedBackground client:idle className="vt-sky" /> <AnimatedBackground client:idle className="vt-sky" />
@@ -62,8 +62,8 @@ const cards = [
<div class="flex w-full flex-col gap-10 lg:flex-row lg:items-center"> <div class="flex w-full flex-col gap-10 lg:flex-row lg:items-center">
<Reveal as="div" class="max-w-2xl grow"> <Reveal as="div" class="max-w-2xl grow">
<span <span
class="mb-5 inline-flex items-center gap-2 rounded-full border px-3 py-1 font-mono text-xs uppercase tracking-[0.18em]" class="mb-5 inline-flex items-center gap-2 rounded-full border px-3 py-1 font-eyebrow text-xs uppercase tracking-[0.18em]"
style="border-color: var(--color-border); color: var(--accent-contrast); background: color-mix(in srgb, var(--color-surface-raised) 75%, transparent);" style="border-color: color-mix(in srgb, var(--color-gold) 40%, transparent); color: var(--color-gold-bright); background: color-mix(in srgb, var(--color-midnight) 75%, transparent);"
> >
<span <span
class="h-2 w-2 rounded-full animate-pulse-glow" class="h-2 w-2 rounded-full animate-pulse-glow"
@@ -171,7 +171,7 @@ const cards = [
> >
<Icon name={card.icon} class="h-6 w-6" /> <Icon name={card.icon} class="h-6 w-6" />
</span> </span>
<h3 class="font-sans" style="color: var(--color-text);"> <h3 class="font-display" style="color: var(--color-text);">
{card.title} {card.title}
</h3> </h3>
<p <p
@@ -221,7 +221,7 @@ const cards = [
</span> </span>
<span class="flex flex-col gap-1 px-5 py-4 sm:px-0 sm:py-0"> <span class="flex flex-col gap-1 px-5 py-4 sm:px-0 sm:py-0">
<span <span
class="font-mono text-xs uppercase tracking-[0.18em]" class="font-eyebrow text-xs uppercase tracking-[0.18em]"
style="color: var(--color-text-subtle);" style="color: var(--color-text-subtle);"
> >
Reading &amp; Media Reading &amp; Media
+1 -1
View File
@@ -188,7 +188,7 @@ const related = [
<Icon name={g.icon} class="h-5 w-5" /> <Icon name={g.icon} class="h-5 w-5" />
</span> </span>
<h3 <h3
class="font-sans text-base" class="font-display text-base"
style="color: var(--color-text);" style="color: var(--color-text);"
> >
{g.name} {g.name}
+3 -3
View File
@@ -4,7 +4,7 @@
* --------------------------------------------------------------------------- * ---------------------------------------------------------------------------
* The "fun wing" landing page. A warm grid of cards linking into each interest * The "fun wing" landing page. A warm grid of cards linking into each interest
* sub-page, using the spec's taglines. Sets surface="interest" so the whole * sub-page, using the spec's taglines. Sets surface="interest" so the whole
* page reads warm (cream/amber) regardless of the global light/dark toggle. * page reads as the craft-side night sky regardless of the global light/dark toggle.
*/ */
import BaseLayout from "@layouts/BaseLayout.astro"; import BaseLayout from "@layouts/BaseLayout.astro";
import Reveal from "@components/motion/Reveal.astro"; import Reveal from "@components/motion/Reveal.astro";
@@ -114,7 +114,7 @@ const interests = [
description="The fun wing: tabletop RPGs, gaming, reading, resin dice and 3D printing, and Pokémon. Where Greg's passions live as stories and collections." description="The fun wing: tabletop RPGs, gaming, reading, resin dice and 3D printing, and Pokémon. Where Greg's passions live as stories and collections."
> >
<!-- Hero --> <!-- Hero -->
<section class="relative overflow-hidden pb-8 pt-28 sm:pt-32"> <section class="on-sky relative overflow-hidden pb-8 pt-28 sm:pt-32">
{ {
/* Full animated Milky Way field + stars, matching the Home hero treatment. */ /* Full animated Milky Way field + stars, matching the Home hero treatment. */
} }
@@ -176,7 +176,7 @@ const interests = [
/> />
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<h3 class="font-sans" style="color: var(--color-text);"> <h3 class="font-display" style="color: var(--color-text);">
{item.title} {item.title}
</h3> </h3>
<p <p
+1 -1
View File
@@ -58,7 +58,7 @@ const related = [
<Icon name="robot" class="h-10 w-10" /> <Icon name="robot" class="h-10 w-10" />
</span> </span>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<span class="font-mono text-xs uppercase tracking-[0.22em]" style="color: var(--color-accent);">Favorite Pokémon</span> <span class="font-eyebrow text-xs uppercase tracking-[0.22em]" style="color: var(--color-accent);">Favorite Pokémon</span>
<h2 style="color: var(--color-text);">Metagross</h2> <h2 style="color: var(--color-text);">Metagross</h2>
<p class="text-sm" style="color: var(--color-text-muted);">Steel / Psychic. The iron leg Pokémon.</p> <p class="text-sm" style="color: var(--color-text-muted);">Steel / Psychic. The iron leg Pokémon.</p>
</div> </div>
+8 -8
View File
@@ -49,7 +49,7 @@ const projects = (await getCollection("projects")).sort(
puts its center on the boundary: half in the hero, half over the next puts its center on the boundary: half in the hero, half over the next
section, in front of both (z-50). */ section, in front of both (z-50). */
} }
<section class="relative overflow-visible pt-28 pb-28 sm:pt-32 sm:pb-36"> <section class="on-sky relative overflow-visible pt-28 pb-28 sm:pt-32 sm:pb-36">
{ {
/* Background layer — clipped to the hero so the circuit canvas/astrolabe /* Background layer — clipped to the hero so the circuit canvas/astrolabe
don't bleed into the content below. */ don't bleed into the content below. */
@@ -155,8 +155,8 @@ const projects = (await getCollection("projects")).sort(
class="aspect-[21/9] w-full object-cover" class="aspect-[21/9] w-full object-cover"
/> />
<figcaption <figcaption
class="absolute bottom-0 left-0 m-3 rounded-md px-3 py-1 font-mono text-[0.65rem] uppercase tracking-[0.18em] backdrop-blur-sm" class="absolute bottom-0 left-0 m-3 rounded-md px-3 py-1 font-eyebrow text-[0.65rem] uppercase tracking-[0.18em] backdrop-blur-sm"
style="background: color-mix(in srgb, var(--color-ink) 60%, transparent); color: var(--color-cream);" style="background: color-mix(in srgb, var(--color-midnight) 60%, transparent); color: var(--color-starlight);"
> >
Where the day-job work happens Where the day-job work happens
</figcaption> </figcaption>
@@ -233,7 +233,7 @@ const projects = (await getCollection("projects")).sort(
<Reveal delay={60}> <Reveal delay={60}>
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<h3 <h3
class="font-mono text-xs uppercase tracking-[0.22em]" class="font-eyebrow text-xs uppercase tracking-[0.22em]"
style="color: var(--color-accent);" style="color: var(--color-accent);"
> >
Education history Education history
@@ -244,7 +244,7 @@ const projects = (await getCollection("projects")).sort(
<Reveal delay={120}> <Reveal delay={120}>
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<h3 <h3
class="font-mono text-xs uppercase tracking-[0.22em]" class="font-eyebrow text-xs uppercase tracking-[0.22em]"
style="color: var(--color-accent);" style="color: var(--color-accent);"
> >
Certifications Certifications
@@ -275,7 +275,7 @@ const projects = (await getCollection("projects")).sort(
/> />
<ObfuscatedEmail <ObfuscatedEmail
address="contact@gregkilmer.com" address="contact@gregkilmer.com"
class="font-mono text-xs uppercase tracking-[0.18em]" class="font-eyebrow text-xs uppercase tracking-[0.18em]"
style="color: var(--color-accent);" style="color: var(--color-accent);"
/> />
</li> </li>
@@ -286,7 +286,7 @@ const projects = (await getCollection("projects")).sort(
style="color: var(--color-accent);" style="color: var(--color-accent);"
/> />
<span <span
class="font-mono text-xs uppercase tracking-[0.18em]" class="font-eyebrow text-xs uppercase tracking-[0.18em]"
style="color: var(--color-accent);" style="color: var(--color-accent);"
><a ><a
href="https://www.linkedin.com/in/gregory-kilmer-36646bb5/" href="https://www.linkedin.com/in/gregory-kilmer-36646bb5/"
@@ -302,7 +302,7 @@ const projects = (await getCollection("projects")).sort(
style="color: var(--color-accent);" style="color: var(--color-accent);"
/> />
<span <span
class="font-mono text-xs uppercase tracking-[0.18em]" class="font-eyebrow text-xs uppercase tracking-[0.18em]"
style="color: var(--color-accent);" style="color: var(--color-accent);"
><a ><a
href="https://github.com/patiun" href="https://github.com/patiun"
+1 -1
View File
@@ -43,7 +43,7 @@ const keyReqs = requirements.filter((r) =>
<Reveal> <Reveal>
<a <a
href="/store" href="/store"
class="inline-flex items-center gap-1.5 font-mono text-xs uppercase tracking-[0.18em]" class="inline-flex items-center gap-1.5 font-eyebrow text-xs uppercase tracking-[0.18em]"
style="color: var(--color-accent);" style="color: var(--color-accent);"
> >
<Icon name="arrow-left" class="h-4 w-4" /> <Icon name="arrow-left" class="h-4 w-4" />
+1 -1
View File
@@ -42,7 +42,7 @@ const keyReqs = requirements.filter(
<Reveal> <Reveal>
<a <a
href="/store" href="/store"
class="inline-flex items-center gap-1.5 font-mono text-xs uppercase tracking-[0.18em]" class="inline-flex items-center gap-1.5 font-eyebrow text-xs uppercase tracking-[0.18em]"
style="color: var(--color-accent);" style="color: var(--color-accent);"
> >
<Icon name="arrow-left" class="h-4 w-4" /> <Icon name="arrow-left" class="h-4 w-4" />
+6 -6
View File
@@ -35,7 +35,7 @@ const orderHref = "/store/order";
{/* pt-36/sm:pt-40 gives the bright hero content clear breathing room below {/* pt-36/sm:pt-40 gives the bright hero content clear breathing room below
the solid fixed StoreNav (64px tall), so the gold label and nav never the solid fixed StoreNav (64px tall), so the gold label and nav never
visually crowd each other. */} visually crowd each other. */}
<section class="relative overflow-hidden pb-8 pt-36 sm:pt-40"> <section class="on-sky relative overflow-hidden pb-8 pt-36 sm:pt-40">
<AnimatedBackground client:idle className="vt-sky" /> <AnimatedBackground client:idle className="vt-sky" />
<div <div
@@ -44,7 +44,7 @@ const orderHref = "/store/order";
<div class="flex flex-col items-start gap-4"> <div class="flex flex-col items-start gap-4">
<Reveal> <Reveal>
<span <span
class="inline-flex items-center gap-1.5 font-mono text-xs uppercase tracking-[0.18em]" class="inline-flex items-center gap-1.5 font-eyebrow text-xs uppercase tracking-[0.18em]"
style="color: var(--color-accent);" style="color: var(--color-accent);"
> >
<Icon name="store" class="h-4 w-4" /> <Icon name="store" class="h-4 w-4" />
@@ -64,8 +64,8 @@ const orderHref = "/store/order";
<Reveal delay={100}> <Reveal delay={100}>
<h2 <h2
class="font-sans font-bold tracking-tight" class="font-display font-bold"
style="color: var(--color-cream); font-size: clamp(1.75rem, 2vw + 1rem, 2.75rem); line-height: 1.15;" style="color: var(--color-starlight); font-size: clamp(1.75rem, 2vw + 1rem, 2.75rem); line-height: 1.15;"
> >
Proxy MTG cards &amp; 3D-printed deck boxes Proxy MTG cards &amp; 3D-printed deck boxes
</h2> </h2>
@@ -74,7 +74,7 @@ const orderHref = "/store/order";
<Reveal delay={140}> <Reveal delay={140}>
<p <p
class="max-w-xl border-l-2 pl-4 font-sans text-base italic sm:text-lg" class="max-w-xl border-l-2 pl-4 font-sans text-base italic sm:text-lg"
style="border-color: var(--color-accent); color: var(--color-cream); opacity: 0.92;" style="border-color: var(--color-accent); color: var(--color-starlight); opacity: 0.92;"
> >
Clearly-marked proxies for casual play, and matching 3D-printed Clearly-marked proxies for casual play, and matching 3D-printed
storage &amp; counters. Hand-made in small batches. storage &amp; counters. Hand-made in small batches.
@@ -228,7 +228,7 @@ const orderHref = "/store/order";
> >
<Reveal> <Reveal>
<h3 <h3
class="font-sans text-2xl font-bold" class="font-display text-2xl font-bold"
style="color: var(--color-text);" style="color: var(--color-text);"
> >
Ready to put in a request? Ready to put in a request?
+4 -4
View File
@@ -36,7 +36,7 @@ const siteKey = turnstileSiteKey();
<Reveal> <Reveal>
<a <a
href="/store" href="/store"
class="inline-flex items-center gap-1.5 font-mono text-xs uppercase tracking-[0.18em]" class="inline-flex items-center gap-1.5 font-eyebrow text-xs uppercase tracking-[0.18em]"
style="color: var(--color-accent);" style="color: var(--color-accent);"
> >
<Icon name="arrow-left" class="h-4 w-4" /> <Icon name="arrow-left" class="h-4 w-4" />
@@ -79,7 +79,7 @@ const siteKey = turnstileSiteKey();
{open ? 'Orders are open. Slots are limited.' : 'Orders are currently closed.'} {open ? 'Orders are open. Slots are limited.' : 'Orders are currently closed.'}
</span> </span>
</div> </div>
<span class="font-mono text-xs" style="color: var(--color-text-subtle);"> <span class="font-eyebrow text-xs" style="color: var(--color-text-subtle);">
Updated {av.lastUpdated} Updated {av.lastUpdated}
</span> </span>
</div> </div>
@@ -91,7 +91,7 @@ const siteKey = turnstileSiteKey();
<section class="flex flex-col gap-8 lg:grid lg:grid-cols-[1.6fr_1fr] lg:items-start"> <section class="flex flex-col gap-8 lg:grid lg:grid-cols-[1.6fr_1fr] lg:items-start">
<Reveal> <Reveal>
<div class="card flex flex-col gap-2"> <div class="card flex flex-col gap-2">
<h3 class="font-sans text-lg font-bold" style="color: var(--color-text);">Your request</h3> <h3 class="font-display text-lg font-bold" style="color: var(--color-text);">Your request</h3>
<p class="text-sm" style="color: var(--color-text-muted);"> <p class="text-sm" style="color: var(--color-text-muted);">
Fill in the details below. Remember: the card list is final once received, and Fill in the details below. Remember: the card list is final once received, and
double-sided cards count as 2 slots. double-sided cards count as 2 slots.
@@ -118,7 +118,7 @@ const siteKey = turnstileSiteKey();
<span class="inline-flex h-12 w-12 items-center justify-center rounded-full" style="background: color-mix(in srgb, #e55 20%, transparent); color: #e55;" aria-hidden="true"> <span class="inline-flex h-12 w-12 items-center justify-center rounded-full" style="background: color-mix(in srgb, #e55 20%, transparent); color: #e55;" aria-hidden="true">
<Icon name="lock" class="h-6 w-6" /> <Icon name="lock" class="h-6 w-6" />
</span> </span>
<h3 class="font-sans text-xl font-bold" style="color: var(--color-text);">Orders are not being taken right now</h3> <h3 class="font-display text-xl font-bold" style="color: var(--color-text);">Orders are not being taken right now</h3>
<p class="max-w-md text-sm" style="color: var(--color-text-muted);"> <p class="max-w-md text-sm" style="color: var(--color-text-muted);">
I open a limited number of slots at a time. Check back here &mdash; the meters above I open a limited number of slots at a time. Check back here &mdash; the meters above
show live availability, and the order form appears as soon as orders reopen. You can show live availability, and the order form appears as soon as orders reopen. You can
+49 -26
View File
@@ -28,17 +28,23 @@
* section wrapper (`data-surface`) instantly restyles every component. * section wrapper (`data-surface`) instantly restyles every component.
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
@theme { @theme {
/* Brand colors (fixed, do not change with theme). Night-sky mystical set: /* Brand colors (fixed, do not change with theme). The Patiun LLC palette,
* star gold, mystic violet, nebula teal, midnight indigo, star-white. */ * shared by Patiun (craft) and Greg Kilmer (web). Source of truth: the
--color-amber: #e8b54a; /* star gold */ * Patiun brand record / Claude Design tokens. Gold is structure and sparkle
--color-amber-soft: #f4cd72; /* brighter gold for hovers */ * (stars, rules, small labels), never a large fill. Violet and indigo are
--color-violet: #8b7bd8; /* mystic violet */ * background light only (nebula glows, gradient ends), never text. */
--color-violet-deep: #6f5fb6; --color-teal: #14a39a; /* Patiun Teal, primary: buttons, fills, the glyph */
--color-teal: #6fb6ad; /* nebula teal */ --color-teal-bright: #2bc2b7; /* teal for text/links on dark grounds */
--color-teal-deep: #4f8c84; --color-teal-deep: #0f8c84; /* teal on light grounds; hover/active icons */
--color-slate: #161b3a; /* deep indigo-slate */ --color-slate-teal: #2d4a58; /* dark end of the Tide gradient */
--color-ink: #0b1026; /* midnight indigo */ --color-gold: #c9a24a; /* Alchemist Gold, flat gold on screen */
--color-cream: #f4f1e8; /* star-white */ --color-gold-bright: #d8b35a; /* gold text/eyebrows on dark grounds */
--color-gold-deep: #8a6516; /* gold text under 24px on Starlight */
--color-violet: #6a2fe0; /* Nebula Violet, background light only */
--color-indigo: #3b1f8f; /* Astral Indigo, gradient midpoints */
--color-midnight: #131c2b; /* Midnight: panels, tiles, theme color */
--color-sky: #0e1522; /* night-sky ground */
--color-starlight: #f4f2ed; /* Starlight: text on dark, paper ground */
/* Semantic tokens (resolve per theme/section via variables). */ /* Semantic tokens (resolve per theme/section via variables). */
--color-surface: var(--surface); --color-surface: var(--surface);
@@ -53,9 +59,16 @@
--color-secondary: var(--secondary); --color-secondary: var(--secondary);
/* Typography. */ /* Typography. */
--font-sans: "Plus Jakarta Sans Variable", "Inter Variable", ui-sans-serif, /* Typography. Cinzel for names, wordmarks and headings (never paragraphs);
system-ui, sans-serif; * Montserrat for body, UI, labels and uppercase eyebrows. JetBrains Mono is
--font-body: "Inter Variable", ui-sans-serif, system-ui, sans-serif; * kept only for genuinely code-like blocks. */
--font-display: "Cinzel Variable", Cinzel, "Trajan Pro", Georgia, serif;
--font-sans: "Montserrat Variable", Montserrat, "Helvetica Neue", Arial,
ui-sans-serif, sans-serif;
--font-body: "Montserrat Variable", Montserrat, "Helvetica Neue", Arial,
ui-sans-serif, sans-serif;
--font-eyebrow: "Montserrat Variable", Montserrat, "Helvetica Neue", Arial,
ui-sans-serif, sans-serif;
--font-mono: "JetBrains Mono Variable", ui-monospace, "SFMono-Regular", monospace; --font-mono: "JetBrains Mono Variable", ui-monospace, "SFMono-Regular", monospace;
/* Motion tokens (used across reveal + transition classes). */ /* Motion tokens (used across reveal + transition classes). */
@@ -107,10 +120,11 @@
h2, h2,
h3, h3,
h4 { h4 {
font-family: var(--font-sans); font-family: var(--font-display);
font-weight: 700; font-weight: 700;
line-height: 1.15; line-height: 1.15;
letter-spacing: -0.02em; /* Cinzel is a capital-led display face: open tracking, never negative. */
letter-spacing: 0.04em;
color: var(--color-text); color: var(--color-text);
} }
@@ -131,7 +145,7 @@
transition: color 0.2s var(--ease-out-soft); transition: color 0.2s var(--ease-out-soft);
} }
a:hover { a:hover {
color: var(--color-amber-soft); color: var(--color-gold-bright);
} }
/* Visible, on-brand focus ring for keyboard users. */ /* Visible, on-brand focus ring for keyboard users. */
@@ -142,8 +156,8 @@
} }
::selection { ::selection {
background: var(--color-amber); background: var(--color-teal);
color: var(--color-ink); color: var(--color-midnight);
} }
/* Better default for images: never overflow, lazy by default in components. */ /* Better default for images: never overflow, lazy by default in components. */
@@ -184,7 +198,7 @@
} }
.card-interactive:hover { .card-interactive:hover {
transform: translateY(-4px); transform: translateY(-4px);
box-shadow: 0 18px 40px -24px rgba(11, 16, 38, 0.55); box-shadow: 0 18px 40px -24px rgba(14, 21, 34, 0.6);
border-color: var(--color-accent); border-color: var(--color-accent);
} }
@@ -202,7 +216,7 @@
border: 1px solid color-mix(in srgb, var(--color-accent) 35%, transparent); border: 1px solid color-mix(in srgb, var(--color-accent) 35%, transparent);
} }
/* Primary (amber) and secondary (outlined) CTA buttons. */ /* Primary (Patiun Teal) and secondary (outlined) CTA buttons. */
.btn { .btn {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -224,14 +238,15 @@
.btn:active { .btn:active {
transform: translateY(1px); transform: translateY(1px);
} }
/* Filled buttons: Patiun Teal with Midnight text, never white text. */
.btn-primary { .btn-primary {
background: var(--color-amber); background: var(--color-teal);
color: var(--color-ink); color: var(--color-midnight);
box-shadow: 0 10px 30px -12px rgba(245, 158, 11, 0.7); box-shadow: 0 10px 30px -12px rgba(20, 163, 154, 0.7);
} }
.btn-primary:hover { .btn-primary:hover {
background: var(--color-amber-soft); background: var(--color-teal-bright);
color: var(--color-ink); color: var(--color-midnight);
transform: translateY(-2px); transform: translateY(-2px);
} }
.btn-secondary { .btn-secondary {
@@ -246,6 +261,14 @@
} }
} }
/* Night-sky bands (nav, page heroes). The semantic Tailwind tokens resolve at
* :root, where the Starlight values live, so anything sitting on the dark sky
* re-points accent and gold to their on-dark brand values here. */
.on-sky {
--color-accent: var(--color-teal-bright);
--color-secondary: var(--color-gold-bright);
}
/* Visually hidden but available to screen readers. */ /* Visually hidden but available to screen readers. */
.sr-only { .sr-only {
position: absolute; position: absolute;
+78 -67
View File
@@ -5,9 +5,14 @@
* *
* 1. Global light/dark mode -> set on <html data-theme="light|dark"> * 1. Global light/dark mode -> set on <html data-theme="light|dark">
* 2. Section surface tone -> set on <body data-surface="..."> * 2. Section surface tone -> set on <body data-surface="...">
* data-surface="neutral" Home / About / Contact (deep night-sky) * data-surface="neutral" Home / About / Contact (Patiun night sky)
* data-surface="professional" Professional wing (obsidian + violet/gold) * data-surface="professional" Professional wing (Tide: slate-teal night)
* data-surface="interest" Interests wing (candlelit study) * data-surface="interest" Interests wing (Nebula: indigo night)
*
* PALETTE: every value here comes from the Patiun brand (Night sky theme for
* dark, Starlight theme for light). Text uses Starlight tones, links and
* accents use teal, small gold labels use the bright gold, and violet/indigo
* only tint the surfaces (never text).
* *
* SELECTOR MODEL: data-theme lives on <html>, data-surface on <body>. We match * SELECTOR MODEL: data-theme lives on <html>, data-surface on <body>. We match
* the descendant combination `html[data-theme=...] body[data-surface=...]`. * the descendant combination `html[data-theme=...] body[data-surface=...]`.
@@ -26,93 +31,99 @@
* DEFAULT / NEUTRAL SURFACE (Home, About, Contact) * DEFAULT / NEUTRAL SURFACE (Home, About, Contact)
* ======================================================================== */ * ======================================================================== */
/* Light + neutral — fallback (not currently used; dark is forced). */ /* Light + neutral: Starlight paper (fallback; not currently used, dark is
* forced in theme-init, but these are the values the content sections
* actually render with, since the Tailwind tokens resolve at :root). The
* brand's deep teal #0F8C84 is 3.7:1 on Starlight, so --accent uses a
* slightly darker teal that passes AA for small text. */
:root, :root,
html[data-theme='light'] body[data-surface='neutral'] { html[data-theme='light'] body[data-surface='neutral'] {
--surface: #efe7d2; --surface: #f4f2ed; /* Starlight */
--surface-raised: #f7f0dc; --surface-raised: #ffffff;
--surface-sunken: #e3d9bd; --surface-sunken: #e9e5dc;
--text: #1b1730; --text: #131c2b; /* Midnight */
--text-muted: #4a445e; --text-muted: #4a5f6b;
--text-subtle: #6b6582; --text-subtle: #5c6e78;
--border: #d2c6a2; --border: #d9d4c8;
--accent: #7e5512; --accent: #0a726b; /* deep teal, darkened to 5.2:1 so small links pass AA */
--accent-contrast: #f7f0dc; --accent-contrast: #f4f2ed;
--secondary: #5e4f9e; --secondary: #8a6516; /* deep gold, 4.75:1: small gold text and eyebrows */
} }
/* Dark + neutral — the canonical deep mystical nightscape. High-contrast text. */ /* Dark + neutral: the canonical Patiun night sky. High-contrast text. */
html[data-theme='dark'] body[data-surface='neutral'] { html[data-theme='dark'] body[data-surface='neutral'] {
--surface: #070a1c; /* near-void midnight indigo */ --surface: #0e1522; /* night sky */
--surface-raised: #11163a; /* indigo panel, violet-edged */ --surface-raised: #131c2b; /* Midnight panel */
--surface-sunken: #04061a; /* deep cosmic void */ --surface-sunken: #0a1019; /* deeper void */
--text: #f6f4ff; /* near-white for max legibility */ --text: #ece9e2; /* Starlight text, 15:1 */
--text-muted: #d4d0f0; /* bright enough to read clearly on deep blue */ --text-muted: #b9c8cf;
--text-subtle: #a8a2d8; /* lifted from the old #7e79b8 for contrast */ --text-subtle: #9fb3bd; /* brand muted, 7.9:1 on panels */
--border: #2e3470; --border: #26344a;
--accent: #f0c262; /* slightly lifted gold for pop on dark */ --accent: #2bc2b7; /* teal on dark, 7.7:1 */
--accent-contrast: #070a1c; --accent-contrast: #0e1522;
--secondary: #b3a4ec; /* brighter violet, easier on the eye */ --secondary: #d8b35a; /* gold accents and chips */
} }
/* =========================================================================== /* ===========================================================================
* PROFESSIONAL SURFACE — violet-dominant obsidian, dark by default. * PROFESSIONAL SURFACE: the web side. A slate-teal night (the Tide gradient),
* dark by default.
* ======================================================================== */ * ======================================================================== */
body[data-surface='professional'], body[data-surface='professional'],
html[data-theme='dark'] body[data-surface='professional'] { html[data-theme='dark'] body[data-surface='professional'] {
--surface: #080a20; --surface: #0c1820;
--surface-raised: #121638; --surface-raised: #12222c;
--surface-sunken: #040517; --surface-sunken: #081117;
--text: #f6f4ff; --text: #ece9e2;
--text-muted: #cec9f2; --text-muted: #b9c8cf;
--text-subtle: #a59fd6; --text-subtle: #9fb3bd;
--border: #2e3270; --border: #23394a;
--accent: #f0c262; --accent: #2bc2b7;
--accent-contrast: #080a20; --accent-contrast: #0c1820;
--secondary: #b3a4ec; --secondary: #d8b35a;
} }
html[data-theme='light'] body[data-surface='professional'] { html[data-theme='light'] body[data-surface='professional'] {
--surface: #21183f; --surface: #2d4a58; /* Slate Teal: the professional wing stays dark */
--surface-raised: #2c2152; --surface-raised: #355766;
--surface-sunken: #18113a; --surface-sunken: #243c48;
--text: #f1edff; --text: #f4f2ed;
--text-muted: #d5cff0; --text-muted: #d9e2e6;
--text-subtle: #aaa3d6; --text-subtle: #bccbd2;
--border: #3c3170; --border: #46687a;
--accent: #7e5512; --accent: #7fdcd5;
--accent-contrast: #f1edff; --accent-contrast: #131c2b;
--secondary: #b6a6e8; --secondary: #e2c57e;
} }
/* =========================================================================== /* ===========================================================================
* INTEREST SURFACE — warm plum-brown night, dark by default. * INTEREST SURFACE: the craft side. An indigo night lit by the Nebula
* gradient, dark by default.
* ======================================================================== */ * ======================================================================== */
body[data-surface='interest'], body[data-surface='interest'],
html[data-theme='dark'] body[data-surface='interest'] { html[data-theme='dark'] body[data-surface='interest'] {
--surface: #170f24; /* warm plum-brown night */ --surface: #110f22; /* indigo night */
--surface-raised: #221630; --surface-raised: #1a1730;
--surface-sunken: #100920; --surface-sunken: #0b0a18;
--text: #fbf2e4; /* warm near-white */ --text: #ece9e2;
--text-muted: #ecdcc6; /* lifted for contrast */ --text-muted: #c3bfd6;
--text-subtle: #c4ab8e; /* lifted from #a99a86 */ --text-subtle: #a49fbd;
--border: #3e2f4a; --border: #2e2a52;
--accent: #f0c262; --accent: #2bc2b7;
--accent-contrast: #170f24; --accent-contrast: #110f22;
--secondary: #c8abe0; --secondary: #d8b35a;
} }
html[data-theme='light'] body[data-surface='interest'] { html[data-theme='light'] body[data-surface='interest'] {
--surface: #f4e9cf; --surface: #f4f2ed;
--surface-raised: #fdf4d9; --surface-raised: #ffffff;
--surface-sunken: #e8d8b2; --surface-sunken: #e9e5dc;
--text: #22152e; --text: #131c2b;
--text-muted: #5c4733; --text-muted: #4a5f6b;
--text-subtle: #826a4f; --text-subtle: #5c6e78;
--border: #d8c290; --border: #d9d4c8;
--accent: #7e5512; --accent: #0f8c84;
--accent-contrast: #fdf4d9; --accent-contrast: #131c2b;
--secondary: #5e4f9e; --secondary: #8a6516;
} }