Compare commits
5 Commits
5edf0c6061
...
4ded4b277b
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ded4b277b | |||
| 94dfd4df5b | |||
| 35c09f2854 | |||
| 9ecd4d0e59 | |||
| 01431468e4 |
@@ -6,16 +6,27 @@ How color, light/dark mode, section tone, and animation work on this site.
|
||||
|
||||
## Palette
|
||||
|
||||
From the spec, exposed as CSS variables in `src/styles/themes.css` and mapped to
|
||||
Tailwind utilities in `src/styles/global.css`:
|
||||
The site uses the Patiun LLC brand palette (shared by Patiun and Greg Kilmer),
|
||||
exposed as CSS variables in `src/styles/global.css` under `@theme`:
|
||||
|
||||
| Token | Value | Meaning |
|
||||
|---|---|---|
|
||||
| `--color-slate` | `#1E293B` | Professional confidence (primary) |
|
||||
| `--color-amber` | `#F59E0B` | Warm accent (the making/creative side) |
|
||||
| `--color-teal` | `#699a94` | Soft teal (secondary, favorite) |
|
||||
| `--color-ink` | `#0F172A` | Near-black text on light |
|
||||
| `--color-cream` | `#FFFBEB` | Cream-white text on dark |
|
||||
| `--color-teal` | `#14A39A` | Patiun Teal, primary: buttons, fills, the glyph |
|
||||
| `--color-teal-bright` | `#2BC2B7` | Teal text and links on dark grounds |
|
||||
| `--color-teal-deep` | `#0F8C84` | Teal marks on light grounds |
|
||||
| `--color-slate-teal` | `#2D4A58` | Dark end of the Tide gradient |
|
||||
| `--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`,
|
||||
`--secondary`, `--border`, ...) resolve differently per theme + section, and are
|
||||
@@ -118,9 +129,33 @@ reduce)')` before drawing, so even JS-driven motion is skipped.
|
||||
|
||||
Self-hosted variable fonts via Fontsource (woff2, no external requests):
|
||||
|
||||
- **Headings / UI:** Plus Jakarta Sans Variable (geometric sans)
|
||||
- **Body:** Inter Variable
|
||||
- **Mono / kickers / quotes:** JetBrains Mono Variable
|
||||
- **Headings, names, card titles:** Cinzel Variable (`font-display`, used by
|
||||
`h1`-`h4` automatically). Open tracking, never for paragraphs.
|
||||
- **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
|
||||
`global.css`).
|
||||
|
||||
---
|
||||
|
||||
## Icons and motifs
|
||||
|
||||
- **Icons:** `<Icon name="..." />` (Astro) and `<BrandIcon name="..." />` (React
|
||||
islands) render the Patiun stroke icon set in `src/lib/brand-icons.ts`. Call
|
||||
sites can use either the brand name (`dice`) or the mdi name (`dice-multiple`);
|
||||
`brandIconAliases` maps one to the other. Only third-party logos (GitHub,
|
||||
LinkedIn, Steam, ...) fall back to mdi. The gold star accent uses
|
||||
`--icon-gold` (deep gold on Starlight, flat gold inside `.on-sky`). The file is
|
||||
generated from the brand source (`newicons.py`); add icons there.
|
||||
- **Motifs:** `<Motif name="..." />` shows a brand motif from `public/motifs/`,
|
||||
copied from the brand files with ring text outlined to paths. `-light` files
|
||||
are the Starlight recolor. One focal motif per surface, kept faint. Every
|
||||
page carries at least one seal: night-sky heroes have the turning layered
|
||||
seal, and pages without a hero use `<PageSeal name="..." />` behind the header.
|
||||
- **Shape:** cards and panels have 10px corners (`--radius-xl`/`2xl`/`3xl` are
|
||||
pinned to 10px); buttons and tags are pills. The Patiun seal is never shown
|
||||
below 72px (nav and footer use 72px).
|
||||
- **Gold accents:** image frames, quote bars, dividers and rules use
|
||||
`--color-gold`; teal stays for links, buttons and icons.
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
"@astrojs/mdx": "^7.0.2",
|
||||
"@astrojs/node": "^11.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/plus-jakarta-sans": "^5.2.8",
|
||||
"@fontsource-variable/montserrat": "^5.3.0",
|
||||
"@iconify-json/mdi": "^1.2.3",
|
||||
"@tailwindcss/vite": "^4.3.2",
|
||||
"@types/nodemailer": "^8.0.1",
|
||||
@@ -1389,10 +1389,10 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource-variable/inter": {
|
||||
"node_modules/@fontsource-variable/cinzel": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.3.0.tgz",
|
||||
"integrity": "sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/cinzel/-/cinzel-5.3.0.tgz",
|
||||
"integrity": "sha512-eFcU0TwaAP5QESF2pV8VoOPJJXJ6sSGm/6mXMwqTdMBPAJMaLhfa6oISHuOtFeddsoHP9RYm4Io0j1j3mNEz2Q==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
@@ -1407,10 +1407,10 @@
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource-variable/plus-jakarta-sans": {
|
||||
"node_modules/@fontsource-variable/montserrat": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/plus-jakarta-sans/-/plus-jakarta-sans-5.3.0.tgz",
|
||||
"integrity": "sha512-/l/4r0yyWK9JzAlmA0LiYgGgmJe/Gswt4jTJEzr5QhJfwMbvJZDmYWyW0M4X7yCK69BajMVlV/Lx8g7WDc1+sw==",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/montserrat/-/montserrat-5.3.0.tgz",
|
||||
"integrity": "sha512-7PaZoxaxrWLAyrhO46v65An9LhUhfkTExWLhfbywYZCnZEgg/W1rEHnlNmZKjNZ3nJTVYyicqxlJt10z/26yTA==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
"@astrojs/mdx": "^7.0.2",
|
||||
"@astrojs/node": "^11.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/plus-jakarta-sans": "^5.2.8",
|
||||
"@fontsource-variable/montserrat": "^5.3.0",
|
||||
"@iconify-json/mdi": "^1.2.3",
|
||||
"@tailwindcss/vite": "^4.3.2",
|
||||
"@types/nodemailer": "^8.0.1",
|
||||
|
||||
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 482 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,76 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Patiun LLC seal" viewBox="6 6 180 181"><g transform="translate(102,96.5) scale(0.82) translate(-96,-96.5)"><g fill="none" stroke="#14A39A" 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="#14A39A"/></g>
|
||||
<circle cx="96" cy="96.5" r="84" fill="none" stroke="#C9A24A" stroke-width="1.8"/>
|
||||
<circle cx="96" cy="96.5" r="76" fill="none" stroke="#C9A24A" stroke-width="1"/>
|
||||
<line x1="171.71" y1="103.12" x2="174.90" y2="103.40" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="170.85" y1="109.70" x2="174.00" y2="110.25" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="169.41" y1="116.17" x2="172.50" y2="117.00" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="167.42" y1="122.49" x2="170.42" y2="123.59" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="164.88" y1="128.62" x2="167.78" y2="129.97" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="161.82" y1="134.50" x2="168.75" y2="138.50" stroke="#C9A24A" stroke-width="1.3"/>
|
||||
<line x1="158.26" y1="140.09" x2="160.88" y2="141.93" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="154.22" y1="145.35" x2="156.67" y2="147.41" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="149.74" y1="150.24" x2="152.00" y2="152.50" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="144.85" y1="154.72" x2="146.91" y2="157.17" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="139.59" y1="158.76" x2="141.43" y2="161.38" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="134.00" y1="162.32" x2="138.00" y2="169.25" stroke="#C9A24A" stroke-width="1.3"/>
|
||||
<line x1="128.12" y1="165.38" x2="129.47" y2="168.28" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="121.99" y1="167.92" x2="123.09" y2="170.92" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="115.67" y1="169.91" x2="116.50" y2="173.00" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="109.20" y1="171.35" x2="109.75" y2="174.50" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="102.62" y1="172.21" x2="102.90" y2="175.40" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="89.38" y1="172.21" x2="89.10" y2="175.40" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="82.80" y1="171.35" x2="82.25" y2="174.50" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="76.33" y1="169.91" x2="75.50" y2="173.00" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="70.01" y1="167.92" x2="68.91" y2="170.92" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="63.88" y1="165.38" x2="62.53" y2="168.28" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="58.00" y1="162.32" x2="54.00" y2="169.25" stroke="#C9A24A" stroke-width="1.3"/>
|
||||
<line x1="52.41" y1="158.76" x2="50.57" y2="161.38" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="47.15" y1="154.72" x2="45.09" y2="157.17" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="42.26" y1="150.24" x2="40.00" y2="152.50" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="37.78" y1="145.35" x2="35.33" y2="147.41" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="33.74" y1="140.09" x2="31.12" y2="141.93" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="30.18" y1="134.50" x2="23.25" y2="138.50" stroke="#C9A24A" stroke-width="1.3"/>
|
||||
<line x1="27.12" y1="128.62" x2="24.22" y2="129.97" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="24.58" y1="122.49" x2="21.58" y2="123.59" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="22.59" y1="116.17" x2="19.50" y2="117.00" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="21.15" y1="109.70" x2="18.00" y2="110.25" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="20.29" y1="103.12" x2="17.10" y2="103.40" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="20.29" y1="89.88" x2="17.10" y2="89.60" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="21.15" y1="83.30" x2="18.00" y2="82.75" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="22.59" y1="76.83" x2="19.50" y2="76.00" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="24.58" y1="70.51" x2="21.58" y2="69.41" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="27.12" y1="64.38" x2="24.22" y2="63.03" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="30.18" y1="58.50" x2="23.25" y2="54.50" stroke="#C9A24A" stroke-width="1.3"/>
|
||||
<line x1="33.74" y1="52.91" x2="31.12" y2="51.07" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="37.78" y1="47.65" x2="35.33" y2="45.59" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="42.26" y1="42.76" x2="40.00" y2="40.50" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="47.15" y1="38.28" x2="45.09" y2="35.83" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="52.41" y1="34.24" x2="50.57" y2="31.62" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="58.00" y1="30.68" x2="54.00" y2="23.75" stroke="#C9A24A" stroke-width="1.3"/>
|
||||
<line x1="63.88" y1="27.62" x2="62.53" y2="24.72" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="70.01" y1="25.08" x2="68.91" y2="22.08" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="76.33" y1="23.09" x2="75.50" y2="20.00" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="82.80" y1="21.65" x2="82.25" y2="18.50" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="89.38" y1="20.79" x2="89.10" y2="17.60" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="102.62" y1="20.79" x2="102.90" y2="17.60" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="109.20" y1="21.65" x2="109.75" y2="18.50" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="115.67" y1="23.09" x2="116.50" y2="20.00" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="121.99" y1="25.08" x2="123.09" y2="22.08" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="128.12" y1="27.62" x2="129.47" y2="24.72" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="134.00" y1="30.68" x2="138.00" y2="23.75" stroke="#C9A24A" stroke-width="1.3"/>
|
||||
<line x1="139.59" y1="34.24" x2="141.43" y2="31.62" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="144.85" y1="38.28" x2="146.91" y2="35.83" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="149.74" y1="42.76" x2="152.00" y2="40.50" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="154.22" y1="47.65" x2="156.67" y2="45.59" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="158.26" y1="52.91" x2="160.88" y2="51.07" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="161.82" y1="58.50" x2="168.75" y2="54.50" stroke="#C9A24A" stroke-width="1.3"/>
|
||||
<line x1="164.88" y1="64.38" x2="167.78" y2="63.03" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="167.42" y1="70.51" x2="170.42" y2="69.41" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="169.41" y1="76.83" x2="172.50" y2="76.00" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="170.85" y1="83.30" x2="174.00" y2="82.75" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<line x1="171.71" y1="89.88" x2="174.90" y2="89.60" stroke="#C9A24A" stroke-width="0.8"/>
|
||||
<polygon points="96.00,7.00 97.88,14.62 105.50,16.50 97.88,18.38 96.00,26.00 94.12,18.38 86.50,16.50 94.12,14.62" fill="#C9A24A"/>
|
||||
<polygon points="176.00,87.00 177.88,94.62 185.50,96.50 177.88,98.38 176.00,106.00 174.12,98.38 166.50,96.50 174.12,94.62" fill="#C9A24A"/>
|
||||
<polygon points="96.00,167.00 97.88,174.62 105.50,176.50 97.88,178.38 96.00,186.00 94.12,178.38 86.50,176.50 94.12,174.62" fill="#C9A24A"/>
|
||||
<polygon points="16.00,87.00 17.88,94.62 25.50,96.50 17.88,98.38 16.00,106.00 14.12,98.38 6.50,96.50 14.12,94.62" fill="#C9A24A"/></svg>
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 912 B |
|
After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 5.9 KiB |
@@ -1,15 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
|
||||
<!-- 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>
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 846 B After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 95 KiB |
@@ -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": "/"
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
/**
|
||||
* Icon.astro
|
||||
* ---------------------------------------------------------------------------
|
||||
* Inline mdi (Material Design Icons) renderer. Pulls icon path data at build
|
||||
* time from `@iconify-json/mdi/icons.json` and emits a single inline <svg>, so
|
||||
* there is no runtime icon library and no extra requests. The svg inherits
|
||||
* `currentColor`, so color is controlled via text-* utilities or inline style.
|
||||
*
|
||||
* The spec calls for mdi icons everywhere instead of emojis; all spec emoji
|
||||
* references are mapped to mdi names at the call sites.
|
||||
* Inline icon renderer. Names resolve to the Patiun brand icon set first
|
||||
* (src/lib/brand-icons.ts: stroke icons with a gold star accent), via either a
|
||||
* brand name ("dice") or the mdi name the call sites already use ("dice-multiple").
|
||||
* Anything not in the brand set, which is only third-party logos like GitHub or
|
||||
* LinkedIn, falls back to mdi (Material Design Icons) path data. Either way the
|
||||
* output is one inline <svg> that inherits `currentColor`.
|
||||
*
|
||||
* Usage: <Icon name="briefcase" class="h-6 w-6" />
|
||||
*/
|
||||
import mdi from '@iconify-json/mdi/icons.json';
|
||||
import { brandIconBody } from '@lib/brand-icons';
|
||||
|
||||
interface Props {
|
||||
/** mdi icon name WITHOUT prefix, e.g. "briefcase". */
|
||||
@@ -27,8 +27,11 @@ interface Props {
|
||||
|
||||
const { name, title, class: className, size, style } = Astro.props;
|
||||
|
||||
const icon = (mdi as { icons: Record<string, { body: string }> }).icons[name];
|
||||
if (!icon) {
|
||||
const brand = brandIconBody(name);
|
||||
const icon = brand
|
||||
? undefined
|
||||
: (mdi as { icons: Record<string, { body: string }> }).icons[name];
|
||||
if (!brand && !icon) {
|
||||
// Surface missing-icon names during dev so typos are caught immediately.
|
||||
console.warn(`[Icon] unknown mdi icon: "${name}"`);
|
||||
}
|
||||
@@ -39,9 +42,14 @@ if (!icon) {
|
||||
width={size ?? '1em'}
|
||||
height={size ?? '1em'}
|
||||
viewBox="0 0 24 24"
|
||||
fill={brand ? 'none' : undefined}
|
||||
stroke={brand ? 'currentColor' : undefined}
|
||||
stroke-width={brand ? 1.6 : undefined}
|
||||
stroke-linecap={brand ? 'round' : undefined}
|
||||
stroke-linejoin={brand ? 'round' : undefined}
|
||||
class={className}
|
||||
style={style}
|
||||
aria-hidden={title ? undefined : 'true'}
|
||||
role={title ? 'img' : undefined}
|
||||
set:html={(title ? `<title>${title}</title>` : '') + (icon?.body ?? '')}
|
||||
set:html={(title ? `<title>${title}</title>` : '') + (brand ?? icon?.body ?? '')}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* BrandIcon.tsx
|
||||
* ---------------------------------------------------------------------------
|
||||
* React twin of Icon.astro for islands: renders a Patiun brand icon (by brand
|
||||
* name or mdi alias) as an inline stroke SVG that inherits currentColor.
|
||||
*/
|
||||
import { brandIconBody } from '@lib/brand-icons';
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
size?: number;
|
||||
className?: string;
|
||||
/** Accessible label; omit when the icon sits beside visible text. */
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export default function BrandIcon({ name, size = 20, className, title }: Props) {
|
||||
const body = brandIconBody(name) ?? '';
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.6}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
role={title ? 'img' : undefined}
|
||||
aria-hidden={title ? undefined : true}
|
||||
aria-label={title}
|
||||
dangerouslySetInnerHTML={{ __html: body }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
/**
|
||||
* Motif.astro
|
||||
* ---------------------------------------------------------------------------
|
||||
* A Patiun brand motif ("The Alchemist's Instrument") as a decorative image.
|
||||
* Files live in public/motifs/ and are copied from branding/motifs/svg with
|
||||
* their ring text and symbols outlined to paths, so they render without the
|
||||
* Cinzel or Noto Sans Symbols fonts. `-light` files are the Starlight-ground
|
||||
* recolor (deep teal, deep gold, Midnight symbols); the rest are drawn for the
|
||||
* night sky.
|
||||
*
|
||||
* Brand rules: one focal motif per surface, and keep motifs faint behind
|
||||
* content (they are texture, not illustration).
|
||||
*
|
||||
* Usage:
|
||||
* <Motif name="transmutation-seal-light" class="absolute -right-40 top-10 h-[40rem] w-[40rem]" style="opacity:.1" />
|
||||
*/
|
||||
export interface Props {
|
||||
name:
|
||||
| "layered-seal"
|
||||
| "astrolabe"
|
||||
| "transmutation-seal-light"
|
||||
| "tria-prima-array-seal-light"
|
||||
| "zodiac-seal-light"
|
||||
| "craft-seal-print-light"
|
||||
| "squared-circle-seal-light"
|
||||
| "kepler-nest-seal-light";
|
||||
class?: string;
|
||||
style?: string;
|
||||
}
|
||||
const { name, class: className, style } = Astro.props;
|
||||
---
|
||||
|
||||
<img
|
||||
src={`/motifs/${name}.svg`}
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
draggable="false"
|
||||
class:list={["pointer-events-none select-none", className]}
|
||||
style={style}
|
||||
/>
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
/**
|
||||
* PageSeal.astro
|
||||
* ---------------------------------------------------------------------------
|
||||
* A faint Patiun seal behind the top-right of a page header, for pages
|
||||
* without a night-sky hero (those already carry the turning layered seal).
|
||||
* Sits behind content (-z-10) and is clipped so it never widens the page.
|
||||
*
|
||||
* Usage (first thing inside the page): <PageSeal name="zodiac-seal-light" />
|
||||
*/
|
||||
import Motif from "@components/brand/Motif.astro";
|
||||
import type { Props as MotifProps } from "@components/brand/Motif.astro";
|
||||
|
||||
interface Props {
|
||||
name: MotifProps["name"];
|
||||
}
|
||||
const { name } = Astro.props;
|
||||
---
|
||||
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="pointer-events-none absolute inset-x-0 top-0 -z-10 h-[46rem] overflow-hidden"
|
||||
>
|
||||
<Motif
|
||||
name={name}
|
||||
class="absolute -right-40 top-20 h-[30rem] w-[30rem] sm:-right-24 sm:top-24 sm:h-[36rem] sm:w-[36rem] lg:right-[max(-6rem,calc((100vw-72rem)/2-12rem))]"
|
||||
style="opacity:.14;"
|
||||
/>
|
||||
</div>
|
||||
@@ -96,13 +96,13 @@ const updated = updatedAt ? new Date(updatedAt).toLocaleDateString('en-US', {
|
||||
|
||||
<div class="flex flex-col gap-2 p-4">
|
||||
<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);"
|
||||
>
|
||||
{name}
|
||||
</h3>
|
||||
<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);"
|
||||
>
|
||||
{formatLabel && <span>{formatLabel}</span>}
|
||||
|
||||
@@ -67,7 +67,7 @@ const alt = artAlt ?? title;
|
||||
</span>
|
||||
{year && (
|
||||
<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);"
|
||||
>
|
||||
{year}
|
||||
@@ -75,7 +75,7 @@ const alt = artAlt ?? title;
|
||||
)}
|
||||
</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);">
|
||||
{blurb}
|
||||
|
||||
@@ -41,7 +41,7 @@ const { icon, title, status, currently = false, class: className = '' } = Astro.
|
||||
<Icon name={icon} class="h-6 w-6" />
|
||||
</span>
|
||||
{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>
|
||||
Active
|
||||
</span>
|
||||
@@ -50,7 +50,7 @@ const { icon, title, status, currently = false, class: className = '' } = Astro.
|
||||
<span class="pill">{status}</span>
|
||||
)}
|
||||
</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);">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -81,7 +81,7 @@ const detail = [year ? String(year) : null, count].filter(Boolean).join(' \u00b7
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 p-4">
|
||||
<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}
|
||||
</h3>
|
||||
{status && <span class="pill shrink-0">{status}</span>}
|
||||
@@ -89,7 +89,7 @@ const detail = [year ? String(year) : null, count].filter(Boolean).join(' \u00b7
|
||||
{
|
||||
detail && (
|
||||
<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);"
|
||||
>
|
||||
{detail}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
* 1. A CSS-driven night-sky gradient (deep blue, darker at top → lighter near
|
||||
* horizon) plus a diagonal Milky Way galaxy band and teal/violet nebula
|
||||
* clouds. GPU-friendly, animated via `animate-gradient-shift` keyframes.
|
||||
* 2. A faint static SVG ornament layer (astrolabe + sacred geometry) for
|
||||
* esoteric texture.
|
||||
* 2. The Patiun layered magic seal, faint and slowly turning in the top-left
|
||||
* corner (the surface's one focal motif).
|
||||
* 3. An optional <canvas> star field: a dense field of slowly-twinkling stars
|
||||
* with thin constellation lines between near neighbors, plus the occasional
|
||||
* shooting star. The WHOLE field rotates together very slowly around the
|
||||
@@ -85,17 +85,16 @@ export default function AnimatedBackground({
|
||||
|
||||
// Resolve star colors from CSS variables so the field matches the theme.
|
||||
const gold =
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--color-amber').trim() ||
|
||||
'#e8b54a';
|
||||
const violet =
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--color-violet').trim() ||
|
||||
'#8b7bd8';
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--color-gold').trim() ||
|
||||
'#c9a24a';
|
||||
// Nebula Violet is background light only, so violet stars stay sparse; teal stars use the on-dark teal so they read at dot size.
|
||||
const violet = '#6a2fe0';
|
||||
const teal =
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--color-teal').trim() ||
|
||||
'#6fb6ad';
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--color-teal-bright').trim() ||
|
||||
'#2bc2b7';
|
||||
const white =
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--color-cream').trim() ||
|
||||
'#f4f1e8';
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--color-starlight').trim() ||
|
||||
'#f4f2ed';
|
||||
|
||||
const hueColor = (h: Star['hue']) => (h === 'gold' ? gold : h === 'violet' ? violet : h === 'teal' ? teal : white);
|
||||
|
||||
@@ -214,7 +213,7 @@ export default function AnimatedBackground({
|
||||
const proximity = 1 - Math.sqrt(d2) / LINK_DIST;
|
||||
const pulse = 0.5 + 0.5 * Math.sin(now / 1400 + (i + j) * 0.7);
|
||||
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.moveTo(A.x, A.y);
|
||||
ctx.lineTo(B.x, B.y);
|
||||
@@ -270,8 +269,8 @@ export default function AnimatedBackground({
|
||||
const grad = ctx.createLinearGradient(sh.x, sh.y, tailX, tailY);
|
||||
const headA = Math.max(0, Math.min(1, sh.life));
|
||||
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(1, 'rgba(232, 181, 74, 0)');
|
||||
grad.addColorStop(0.4, `rgba(201, 162, 74, ${(headA * 0.6).toFixed(3)})`);
|
||||
grad.addColorStop(1, 'rgba(201, 162, 74, 0)');
|
||||
ctx.strokeStyle = grad;
|
||||
ctx.lineWidth = 1.6;
|
||||
ctx.beginPath();
|
||||
@@ -332,7 +331,7 @@ export default function AnimatedBackground({
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
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
|
||||
@@ -341,10 +340,10 @@ export default function AnimatedBackground({
|
||||
className="animate-gradient-shift absolute inset-0 opacity-90"
|
||||
style={{
|
||||
backgroundImage: `
|
||||
radial-gradient(38% 70% at 68% 38%, rgba(111, 182, 173, 0.22), transparent 62%),
|
||||
radial-gradient(30% 55% at 72% 44%, rgba(139, 123, 216, 0.26), transparent 60%),
|
||||
radial-gradient(22% 40% at 76% 50%, rgba(232, 181, 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%)
|
||||
radial-gradient(38% 70% at 68% 38%, rgba(20, 163, 154, 0.22), transparent 62%),
|
||||
radial-gradient(30% 55% at 72% 44%, rgba(106, 47, 224, 0.26), 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, 226, 0.06) 45%, rgba(236, 233, 226, 0.09) 52%, rgba(236, 233, 226, 0.05) 60%, transparent 72%)
|
||||
`,
|
||||
backgroundSize: '200% 200%',
|
||||
}}
|
||||
@@ -354,67 +353,29 @@ export default function AnimatedBackground({
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
backgroundImage: `
|
||||
radial-gradient(45% 55% at 12% 12%, rgba(139, 123, 216, 0.18), transparent 60%),
|
||||
radial-gradient(40% 50% at 88% 88%, rgba(111, 182, 173, 0.16), transparent 62%)
|
||||
radial-gradient(45% 55% at 12% 12%, rgba(106, 47, 224, 0.18), transparent 60%),
|
||||
radial-gradient(40% 50% at 88% 88%, rgba(20, 163, 154, 0.16), transparent 62%)
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
{/* Faint astrolabe + sacred-geometry ornaments for esoteric texture. */}
|
||||
<svg
|
||||
className="absolute -left-40 -top-40 h-[34rem] w-[34rem] animate-spin-slow text-(--color-amber) opacity-[0.11]"
|
||||
viewBox="0 0 100 100"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="0.5"
|
||||
{/* The layered magic seal, turning slowly in the top-left corner: the
|
||||
one focal motif on the night sky. Its ring text and symbols are
|
||||
outlined to paths (public/motifs/), so no symbol font is needed. */}
|
||||
<img
|
||||
src="/motifs/layered-seal.svg"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="50" cy="50" r="49" strokeWidth="0.8" />
|
||||
<circle cx="50" cy="50" r="42" />
|
||||
<circle cx="50" cy="50" r="30" />
|
||||
<circle cx="50" cy="50" r="18" />
|
||||
{Array.from({ length: 36 }).map((_, i) => {
|
||||
const rad = (i * 10 * Math.PI) / 180;
|
||||
const major = i % 3 === 0;
|
||||
return (
|
||||
<line
|
||||
key={i}
|
||||
x1={50 + 49 * Math.cos(rad)}
|
||||
y1={50 + 49 * Math.sin(rad)}
|
||||
x2={50 + (major ? 43 : 45.5) * Math.cos(rad)}
|
||||
y2={50 + (major ? 43 : 45.5) * Math.sin(rad)}
|
||||
strokeWidth={major ? 0.8 : 0.4}
|
||||
decoding="async"
|
||||
draggable={false}
|
||||
className="pointer-events-none absolute -left-40 -top-40 h-[34rem] w-[34rem] animate-spin-slow select-none opacity-[0.2]"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
<svg
|
||||
className="absolute -right-44 bottom-[-12rem] h-[30rem] w-[30rem] text-(--color-violet) opacity-[0.11]"
|
||||
viewBox="0 0 100 100"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="0.5"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{/* Compact flower-of-life cluster. */}
|
||||
<circle cx="50" cy="50" r="10" />
|
||||
{Array.from({ length: 6 }).map((_, i) => {
|
||||
const rad = (Math.PI / 3) * i;
|
||||
return <circle key={i} cx={50 + 10 * Math.cos(rad)} cy={50 + 10 * Math.sin(rad)} r="10" />;
|
||||
})}
|
||||
{Array.from({ length: 6 }).map((_, i) => {
|
||||
const rad = (Math.PI / 3) * i + Math.PI / 6;
|
||||
return (
|
||||
<circle key={`o${i}`} cx={50 + 10 * Math.sqrt(3) * Math.cos(rad)} cy={50 + 10 * Math.sqrt(3) * Math.sin(rad)} r="10" />
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
|
||||
{/* Subtle vignette for text legibility. */}
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
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). */}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
* Footer.astro
|
||||
* ---------------------------------------------------------------------------
|
||||
* 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 seal beside the name, social links, the copyright with the
|
||||
* "a Patiun LLC company" line, and
|
||||
* the spec microcopy "Built with intention. Powered by caffeine." No emojis.
|
||||
*/
|
||||
import Icon from '@components/Icon.astro';
|
||||
import StarBurst from '@components/mystic/StarBurst.astro';
|
||||
import StarField from '@components/mystic/StarField.astro';
|
||||
import { socials, footerMicrocopy, siteIdentity } from '@lib/site';
|
||||
const year = new Date().getFullYear();
|
||||
@@ -16,7 +16,7 @@ const socialList = Object.values(socials);
|
||||
|
||||
<footer
|
||||
class="relative overflow-hidden border-t"
|
||||
style="border-color: var(--color-border); background: var(--color-surface-sunken);"
|
||||
style="border-color: color-mix(in srgb, var(--color-gold) 55%, transparent); background: var(--color-surface-sunken);"
|
||||
>
|
||||
{/* Faint static starfield — carries the night-sky treatment into the footer. */}
|
||||
<StarField
|
||||
@@ -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="flex items-center gap-3">
|
||||
<StarBurst class="h-5 w-5 shrink-0" style="color: var(--color-accent);" />
|
||||
<img src="/brand/patiun-seal.svg" alt="" width="72" height="72" class="h-[72px] w-[72px] shrink-0" />
|
||||
<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}
|
||||
</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}
|
||||
</p>
|
||||
</div>
|
||||
@@ -56,6 +56,6 @@ const socialList = Object.values(socials);
|
||||
</ul>
|
||||
</div>
|
||||
<div class="container-prose relative z-10 pb-8 text-center text-xs" style="color: var(--color-text-subtle);">
|
||||
© {year} {siteIdentity.name}. All rights reserved.
|
||||
© {year} {siteIdentity.name} · {siteIdentity.lockupLine}. All rights reserved.
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* Milky Way field: a back-to-hub breadcrumb, a small icon mark, the title, and
|
||||
* 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
|
||||
* h2-sized (the page is a sub-page, not Home) so it doesn't dominate. An
|
||||
* optional banner image sits above the heading.
|
||||
@@ -39,7 +39,7 @@ const {
|
||||
} = 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).
|
||||
Carries the per-page morph name (one .vt-sky per page snapshot). -->
|
||||
<AnimatedBackground client:idle className="vt-sky" />
|
||||
@@ -54,8 +54,8 @@ const {
|
||||
<Reveal>
|
||||
<a
|
||||
href="/interests"
|
||||
class="inline-flex items-center gap-1.5 font-mono text-xs uppercase tracking-[0.18em] transition-colors"
|
||||
style="color: var(--color-cream); opacity: 0.8;"
|
||||
class="inline-flex items-center gap-1.5 font-eyebrow text-xs uppercase tracking-[0.18em] transition-colors"
|
||||
style="color: var(--color-starlight); opacity: 0.8;"
|
||||
>
|
||||
<Icon name="arrow-left" class="h-4 w-4" />
|
||||
{eyebrow}
|
||||
@@ -74,8 +74,8 @@ const {
|
||||
|
||||
<Reveal delay={100}>
|
||||
<h2
|
||||
class="font-sans font-bold tracking-tight"
|
||||
style="color: var(--color-cream); font-size: clamp(1.75rem, 2vw + 1rem, 2.5rem); line-height: 1.15;"
|
||||
class="font-display font-bold"
|
||||
style="color: var(--color-starlight); font-size: clamp(1.75rem, 2vw + 1rem, 2.5rem); line-height: 1.15;"
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
@@ -84,7 +84,7 @@ const {
|
||||
<Reveal delay={140}>
|
||||
<p
|
||||
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-gold); color: var(--color-starlight); opacity: 0.92;"
|
||||
>
|
||||
{tagline}
|
||||
</p>
|
||||
@@ -97,7 +97,7 @@ const {
|
||||
<Reveal delay={80} class="w-full">
|
||||
<figure
|
||||
class="relative w-full overflow-hidden rounded-2xl border"
|
||||
style="border-color: color-mix(in srgb, var(--color-accent) 40%, transparent);"
|
||||
style="border-color: color-mix(in srgb, var(--color-gold) 70%, transparent);"
|
||||
>
|
||||
<img
|
||||
src={image}
|
||||
@@ -107,8 +107,8 @@ const {
|
||||
class="aspect-[4/3] w-full object-cover"
|
||||
/>
|
||||
<StarBurst
|
||||
class="pointer-events-none absolute left-3 top-3 h-5 w-5 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-accent)_60%,transparent)]"
|
||||
style="color: var(--color-accent);"
|
||||
class="pointer-events-none absolute left-3 top-3 h-5 w-5 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-gold)_60%,transparent)]"
|
||||
style="color: var(--color-gold);"
|
||||
/>
|
||||
</figure>
|
||||
</Reveal>
|
||||
|
||||
@@ -40,7 +40,7 @@ const { label, hint, class: className = '' } = Astro.props;
|
||||
<Icon name="pencil" class="h-4 w-4" />
|
||||
</span>
|
||||
<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 · {label}
|
||||
</span>
|
||||
{hint && <p class="text-sm" style="color: var(--color-text-muted);">{hint}</p>}
|
||||
|
||||
@@ -23,7 +23,7 @@ const { items } = Astro.props;
|
||||
---
|
||||
|
||||
<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
|
||||
</h3>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
|
||||
@@ -39,8 +39,8 @@ const {
|
||||
} = Astro.props;
|
||||
|
||||
const isHero = tone === "hero";
|
||||
const titleColor = isHero ? "var(--color-cream)" : undefined;
|
||||
const subtitleColor = isHero ? "var(--color-cream)" : "var(--color-text-muted)";
|
||||
const titleColor = isHero ? "var(--color-starlight)" : undefined;
|
||||
const subtitleColor = isHero ? "var(--color-starlight)" : "var(--color-text-muted)";
|
||||
---
|
||||
|
||||
<div
|
||||
@@ -53,11 +53,11 @@ const subtitleColor = isHero ? "var(--color-cream)" : "var(--color-text-muted)";
|
||||
{
|
||||
kicker && (
|
||||
<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={
|
||||
isHero
|
||||
? "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" />}
|
||||
|
||||
@@ -39,7 +39,7 @@ const {
|
||||
/>
|
||||
<span class="flex flex-col gap-1">
|
||||
<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;"
|
||||
>
|
||||
The professional wing
|
||||
@@ -59,7 +59,7 @@ const {
|
||||
>
|
||||
<span class="flex flex-col gap-1">
|
||||
<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);"
|
||||
>
|
||||
The personal wing
|
||||
|
||||
@@ -29,13 +29,13 @@ const { groups, pillIcon = 'play-circle' } = Astro.props;
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="flex items-baseline gap-2.5">
|
||||
<h3
|
||||
class="font-sans text-lg"
|
||||
class="font-display text-lg"
|
||||
style="color: var(--color-text);"
|
||||
>
|
||||
{group.category}
|
||||
</h3>
|
||||
<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);"
|
||||
>
|
||||
{group.items.length}
|
||||
@@ -51,7 +51,7 @@ const { groups, pillIcon = 'play-circle' } = Astro.props;
|
||||
<span>{item.title}</span>
|
||||
{item.status && (
|
||||
<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);"
|
||||
>
|
||||
{item.status}
|
||||
@@ -59,7 +59,7 @@ const { groups, pillIcon = 'play-circle' } = Astro.props;
|
||||
)}
|
||||
{item.year && (
|
||||
<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);"
|
||||
>
|
||||
{item.year}
|
||||
|
||||
@@ -2,16 +2,13 @@
|
||||
/**
|
||||
* AlchemicalDivider.astro
|
||||
* ---------------------------------------------------------------------------
|
||||
* A horizontal section divider built from a thin rule with alchemical /
|
||||
* celestial symbols along it: a central sun/star flanked by a crescent moon
|
||||
* and small sparkles, with hairlines tapering to the edges. Replaces plain
|
||||
* vertical gaps between sections on Home / About / Interests.
|
||||
*
|
||||
* Decorative: aria-hidden, inherits currentColor (set to --color-accent or
|
||||
* --color-violet at the call site).
|
||||
* Section break in the Patiun star-line style: a dotted (faint) run leading
|
||||
* into a solid (main) run on each side, each stopping short of the stars, with
|
||||
* a gold four-point star at the center flanked by two small ones. Inherits
|
||||
* currentColor; call sites pass Alchemist Gold.
|
||||
*
|
||||
* Usage:
|
||||
* <AlchemicalDivider class="my-4 h-8 w-full max-w-md mx-auto" style="color: var(--color-amber)" />
|
||||
* <AlchemicalDivider class="mx-auto h-6 w-full max-w-sm" style="color: var(--color-gold)" />
|
||||
*/
|
||||
interface Props {
|
||||
class?: string;
|
||||
@@ -28,30 +25,12 @@ const { class: className = '', style = '' } = Astro.props;
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
>
|
||||
{/* Tapered hairlines either side of the central cluster. */}
|
||||
<line x1="2" y1="12" x2="74" y2="12" stroke-width="0.8" opacity="0.7" />
|
||||
<line x1="126" y1="12" x2="198" y2="12" stroke-width="0.8" opacity="0.7" />
|
||||
<circle cx="8" cy="12" r="1" fill="currentColor" stroke="none" />
|
||||
<circle cx="192" cy="12" r="1" fill="currentColor" stroke="none" />
|
||||
|
||||
{/* Crescent moon (left). */}
|
||||
<path d="M86 12a6 6 0 1 0 5-5.9 4.7 4.7 0 0 1 0 11.8 6 6 0 0 0-5-5.9z" fill="currentColor" stroke="none" opacity="0.85" />
|
||||
|
||||
{/* Central radiant sun/star (alchemical sun: circle + rays). */}
|
||||
<circle cx="100" cy="12" r="3.2" stroke-width="1" />
|
||||
<g stroke-width="1">
|
||||
<line x1="100" y1="2.5" x2="100" y2="5" />
|
||||
<line x1="100" y1="19" x2="100" y2="21.5" />
|
||||
<line x1="90.5" y1="12" x2="93" y2="12" />
|
||||
<line x1="107" y1="12" x2="109.5" y2="12" />
|
||||
<line x1="93.3" y1="5.3" x2="95.1" y2="7.1" />
|
||||
<line x1="104.9" y1="16.9" x2="106.7" y2="18.7" />
|
||||
<line x1="106.7" y1="5.3" x2="104.9" y2="7.1" />
|
||||
<line x1="95.1" y1="16.9" x2="93.3" y2="18.7" />
|
||||
</g>
|
||||
|
||||
{/* Small sparkles. */}
|
||||
<path d="M114 12c.25 1.6.9 2.8 2.4 3.1-1.5.25-2.15 1.5-2.4 3.1-.25-1.6-.9-2.85-2.4-3.1 1.5-.3 2.15-1.5 2.4-3.1z" fill="currentColor" stroke="none" />
|
||||
<path d="M76 12c.2 1.3.7 2.3 2 2.5-1.3.2-1.8 1.2-2 2.5-.2-1.3-.7-2.3-2-2.5 1.3-.2 1.8-1.2 2-2.5z" fill="currentColor" stroke="none" opacity="0.7" />
|
||||
{/* Faint links: dotted runs at the outer ends. */}
|
||||
<path d="M6 12h30M164 12h30" stroke-width="1" stroke-dasharray="0.1 4" />
|
||||
{/* Main figure: solid runs that stop short of the stars. */}
|
||||
<path d="M42 12h38M120 12h38" stroke-width="1" />
|
||||
<polygon points="86.50,8.80 87.18,11.32 89.70,12.00 87.18,12.68 86.50,15.20 85.82,12.68 83.30,12.00 85.82,11.32" fill="currentColor" stroke="none" />
|
||||
<polygon points="113.50,8.80 114.18,11.32 116.70,12.00 114.18,12.68 113.50,15.20 112.82,12.68 110.30,12.00 112.82,11.32" fill="currentColor" stroke="none" />
|
||||
<polygon points="100.00,3.00 101.91,10.09 109.00,12.00 101.91,13.91 100.00,21.00 98.09,13.91 91.00,12.00 98.09,10.09" fill="currentColor" stroke="none" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
---
|
||||
/**
|
||||
* Astrolabe.astro
|
||||
* ---------------------------------------------------------------------------
|
||||
* Inline-SVG astrolabe / celestial-chart line art: concentric rings, tick
|
||||
* marks like a degree scale, zodiac-like division lines, and a star at the
|
||||
* pivot. Reads as a "mystic instrument" silhouette. Intended as a hero-corner
|
||||
* ornament where it can slowly rotate (animate-spin-slow).
|
||||
*
|
||||
* Decorative: aria-hidden, pointer-events none, inherits currentColor. Apply
|
||||
* opacity + color at the call site so it reads as a faint engraving.
|
||||
*
|
||||
* 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" />
|
||||
*/
|
||||
interface Props {
|
||||
class?: string;
|
||||
style?: string;
|
||||
}
|
||||
const { class: className = '', style = '' } = Astro.props;
|
||||
|
||||
// Degree ticks every 10deg around the outer ring (r=48, on a 100x100 canvas).
|
||||
const ticks: { x1: number; y1: number; x2: number; y2: number; major: boolean }[] = [];
|
||||
const cx = 50;
|
||||
const cy = 50;
|
||||
for (let deg = 0; deg < 360; deg += 10) {
|
||||
const rad = (deg * Math.PI) / 180;
|
||||
const major = deg % 30 === 0;
|
||||
const outer = 48;
|
||||
const inner = major ? 43 : 45.5;
|
||||
ticks.push({
|
||||
x1: cx + outer * Math.cos(rad),
|
||||
y1: cy + outer * Math.sin(rad),
|
||||
x2: cx + inner * Math.cos(rad),
|
||||
y2: cy + inner * Math.sin(rad),
|
||||
major,
|
||||
});
|
||||
}
|
||||
// Zodiac-like spokes (12 divisions).
|
||||
const spokes: [number, number][] = [];
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const rad = (i * 30 * Math.PI) / 180;
|
||||
spokes.push([cx + 40 * Math.cos(rad), cy + 40 * Math.sin(rad)]);
|
||||
}
|
||||
---
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 100 100"
|
||||
class={`pointer-events-none ${className}`}
|
||||
style={style}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="0.5"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{/* Outer + inner rings. */}
|
||||
<circle cx={cx} cy={cy} r="49" stroke-width="0.8" />
|
||||
<circle cx={cx} cy={cy} r="42" />
|
||||
<circle cx={cx} cy={cy} r="30" />
|
||||
<circle cx={cx} cy={cy} r="18" />
|
||||
{/* Degree ticks. */}
|
||||
{ticks.map((t) => (
|
||||
<line x1={t.x1} y1={t.y1} x2={t.x2} y2={t.y2} stroke-width={t.major ? 0.8 : 0.4} />
|
||||
))}
|
||||
{/* 12 spokes. */}
|
||||
{spokes.map(([x, y]) => (
|
||||
<line x1={cx} y1={cy} x2={x} y2={y} stroke-width="0.3" opacity="0.7" />
|
||||
))}
|
||||
{/* Central pivot star. */}
|
||||
<path
|
||||
d="M50 44c.4 3 1.4 5.3 4 5.7-2.6.4-3.6 2.7-4 5.7-.4-3-1.4-5.3-4-5.7 2.6-.4 3.6-2.7 4-5.7z"
|
||||
fill="currentColor"
|
||||
stroke="none"
|
||||
/>
|
||||
<circle cx={cx} cy={cy} r="1.2" fill="currentColor" stroke="none" />
|
||||
</svg>
|
||||
@@ -36,7 +36,7 @@ const innerPx = Math.max(0, outerPx - bw);
|
||||
|
||||
<figure
|
||||
class:list={["relative", className]}
|
||||
style={`border-radius: ${outerPx}px; border: ${bw}px solid color-mix(in srgb, var(--color-accent) 100%, transparent); `}
|
||||
style={`border-radius: ${outerPx}px; border: ${bw}px solid var(--color-gold); `}
|
||||
>
|
||||
{/* Faint sacred-geometry tint behind the image (fills the full figure). */}
|
||||
<div
|
||||
@@ -56,23 +56,23 @@ const innerPx = Math.max(0, outerPx - bw);
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
{/* Corner star-burst flourishes (sit on the border corners). */}
|
||||
{/* Corner four-point stars in Alchemist Gold (sit on the border corners). */}
|
||||
<StarBurst
|
||||
class="pointer-events-none absolute -left-3 -top-3 h-6 w-6 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-accent)_60%,transparent)]"
|
||||
style="color: var(--color-accent);"
|
||||
class="pointer-events-none absolute -left-3 -top-3 h-6 w-6 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-gold)_60%,transparent)]"
|
||||
style="color: var(--color-gold);"
|
||||
/>
|
||||
<StarBurst
|
||||
class="pointer-events-none absolute -bottom-5 -right-5 h-7 w-7 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-accent)_60%,transparent)]"
|
||||
style="color: var(--color-accent);"
|
||||
class="pointer-events-none absolute -bottom-5 -right-5 h-7 w-7 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-gold)_60%,transparent)]"
|
||||
style="color: var(--color-gold);"
|
||||
/>
|
||||
<StarBurst
|
||||
variant="outline"
|
||||
class="pointer-events-none absolute -right-2 -top-2 h-3 w-3"
|
||||
style="color: var(--color-accent); opacity:.7;"
|
||||
style="color: var(--color-gold); opacity:.7;"
|
||||
/>
|
||||
<StarBurst
|
||||
variant="outline"
|
||||
class="pointer-events-none absolute -left-2 -bottom-2 h-3 w-3"
|
||||
style="color: var(--color-accent); opacity:.7;"
|
||||
style="color: var(--color-gold); opacity:.7;"
|
||||
/>
|
||||
</figure>
|
||||
|
||||
@@ -80,7 +80,7 @@ const objectFit = fit === "contain" ? "object-contain" : "object-cover";
|
||||
</svg>
|
||||
</div>
|
||||
<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;"
|
||||
>
|
||||
{alt}
|
||||
|
||||
@@ -56,9 +56,9 @@ export default function CircuitField({
|
||||
let traces: Trace[] = [];
|
||||
let nodes: Node[] = [];
|
||||
|
||||
const gold = '#e8b54a';
|
||||
const violet = '#8b7bd8';
|
||||
const teal = '#6fb6ad';
|
||||
const gold = '#c9a24a';
|
||||
const violet = '#6a2fe0';
|
||||
const teal = '#2bc2b7';
|
||||
|
||||
function build() {
|
||||
if (!canvas || !ctx) return;
|
||||
@@ -116,7 +116,7 @@ export default function CircuitField({
|
||||
for (let i = 0; i < traces.length; i++) {
|
||||
const t = traces[i];
|
||||
// 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.beginPath();
|
||||
ctx.moveTo(t.x1, t.y1);
|
||||
@@ -140,7 +140,7 @@ export default function CircuitField({
|
||||
ctx.stroke();
|
||||
// Bright pulse head dot.
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.fillStyle = '#fff4d6';
|
||||
ctx.fillStyle = '#fbf3dc';
|
||||
ctx.beginPath();
|
||||
ctx.arc(px, py, 1.8, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
@@ -172,7 +172,7 @@ export default function CircuitField({
|
||||
if (!ctx) return;
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
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.beginPath();
|
||||
ctx.moveTo(t.x1, t.y1);
|
||||
@@ -243,7 +243,7 @@ export default function CircuitField({
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
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. */}
|
||||
|
||||
@@ -67,10 +67,10 @@ export default function ConstellationField({
|
||||
}
|
||||
let stars: Star[] = [];
|
||||
|
||||
const gold = '#e8b54a';
|
||||
const violet = '#8b7bd8';
|
||||
const teal = '#6fb6ad';
|
||||
const white = '#f4f1e8';
|
||||
const gold = '#c9a24a';
|
||||
const violet = '#6a2fe0';
|
||||
const teal = '#2bc2b7';
|
||||
const white = '#f4f2ed';
|
||||
const accent = tint === 'violet' ? violet : tint === 'teal' ? teal : gold;
|
||||
|
||||
// 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 pulse = 0.5 + 0.5 * Math.sin(now / 1600 + (i + j) * 0.7);
|
||||
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.moveTo(A.x, A.y);
|
||||
ctx.lineTo(B.x, B.y);
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
---
|
||||
/**
|
||||
* SacredGeometry.astro
|
||||
* ---------------------------------------------------------------------------
|
||||
* Inline-SVG sacred-geometry line art: a flower-of-life / metatron's-cube
|
||||
* inspired figure rendered as thin gold strokes. Used as an extremely faint
|
||||
* (low-opacity) decorative background behind hero blocks and section openers —
|
||||
* it should read as texture, never compete with content.
|
||||
*
|
||||
* Purely decorative: aria-hidden, pointer-events none, inherits currentColor.
|
||||
* Apply opacity + sizing + color at the call site.
|
||||
*
|
||||
* Usage:
|
||||
* <SacredGeometry class="pointer-events-none absolute -right-24 -top-24 h-96 w-96" style="color: var(--color-amber); opacity:.08" />
|
||||
*/
|
||||
interface Props {
|
||||
class?: string;
|
||||
style?: string;
|
||||
/** Which figure to draw. */
|
||||
figure?: 'flower' | 'metatron';
|
||||
}
|
||||
const { class: className = '', style = '', figure = 'flower' } = Astro.props;
|
||||
|
||||
// Flower of life: a central circle surrounded by 6, then 12, then edge circles
|
||||
// of the same radius, all r=10 on a 100x100 canvas centered at 50,50.
|
||||
const r = 10;
|
||||
const cx = 50;
|
||||
const cy = 50;
|
||||
const rings: [number, number][] = [];
|
||||
// Ring 1: 6 circles around center.
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const a = (Math.PI / 3) * i;
|
||||
rings.push([cx + r * Math.cos(a), cy + r * Math.sin(a)]);
|
||||
}
|
||||
// Ring 2: 6 more, one step further on alternating angles (the classic pattern).
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const a = (Math.PI / 3) * i + Math.PI / 6;
|
||||
rings.push([cx + r * Math.sqrt(3) * Math.cos(a), cy + r * Math.sqrt(3) * Math.sin(a)]);
|
||||
}
|
||||
// Ring 3 (outer partial): completes the visible flower.
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const a = (Math.PI / 3) * i;
|
||||
rings.push([cx + 2 * r * Math.cos(a), cy + 2 * r * Math.sin(a)]);
|
||||
}
|
||||
const flowerCircles: [number, number][] = [[cx, cy], ...rings];
|
||||
|
||||
// Metatron's cube: 13 circles on a triangular grid + all internal connecting lines.
|
||||
const metatronCenters: [number, number][] = [[cx, cy]];
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const a = (Math.PI / 3) * i;
|
||||
metatronCenters.push([cx + 2 * r * Math.cos(a), cy + 2 * r * Math.sin(a)]);
|
||||
}
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const a = (Math.PI / 3) * i + Math.PI / 6;
|
||||
metatronCenters.push([cx + 2 * r * Math.sqrt(3) * Math.cos(a), cy + 2 * r * Math.sqrt(3) * Math.sin(a)]);
|
||||
}
|
||||
---
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 100 100"
|
||||
class={`pointer-events-none ${className}`}
|
||||
style={style}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="0.6"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{figure === 'flower' ? (
|
||||
flowerCircles.map(([x, y]) => (
|
||||
<circle cx={x} cy={y} r={r} />
|
||||
))
|
||||
) : (
|
||||
<>
|
||||
{metatronCenters.map(([x, y]) => (
|
||||
<circle cx={x} cy={y} r={r * 0.75} />
|
||||
))}
|
||||
{/* Connect every pair of the 13 nodes with thin lines. */}
|
||||
{metatronCenters.map((a, i) =>
|
||||
metatronCenters.slice(i + 1).map((b) => (
|
||||
<line x1={a[0]} y1={a[1]} x2={b[0]} y2={b[1]} />
|
||||
))
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</svg>
|
||||
@@ -11,7 +11,7 @@
|
||||
* - `outline`: stroke-only, lighter
|
||||
*
|
||||
* 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" />
|
||||
*/
|
||||
interface Props {
|
||||
@@ -34,6 +34,7 @@ const sw = variant === 'outline' ? 1.4 : 0;
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{/* Four-point sparkle: two perpendicular sharp diamonds + small inner core. */}
|
||||
<path d="M12 1c.6 4.7 2.3 8.4 7 9-4.7.6-8.4 2.3-9 7-.6-4.7-2.3-8.4-7-9 4.7-.6 8.4-2.3 9-7z" />
|
||||
{/* The Patiun four-point star: thin straight arms (inner radius 0.3 of the
|
||||
outer), the same star used on the seal, the icons and the favicon. */}
|
||||
<polygon points="12,1 14.33,9.67 23,12 14.33,14.33 12,23 9.67,14.33 1,12 9.67,9.67" />
|
||||
</svg>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* cheap. Pointer-events disabled; inherits `currentColor`.
|
||||
*
|
||||
* 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 {
|
||||
class?: string;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
*/
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import BrandIcon from '@components/brand/BrandIcon';
|
||||
|
||||
export interface MobileNavItem {
|
||||
label: string;
|
||||
@@ -28,31 +29,9 @@ interface Props {
|
||||
currentPath: string;
|
||||
}
|
||||
|
||||
// Small inline mdi-like icons keyed by name (keeps the menu self-contained).
|
||||
const ICON_PATHS: Record<string, string> = {
|
||||
home: 'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z',
|
||||
account: 'M12 4a4 4 0 1 0 0 8 4 4 0 0 0 0-8zm0 10c-4.4 0-8 2.2-8 5v1h16v-1c0-2.8-3.6-5-8-5z',
|
||||
briefcase:
|
||||
'M10 16h4a1 1 0 0 0 1-1v-1H7a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v1h2a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-1v1a3 3 0 0 1-3 3h-4a3 3 0 0 1-3-3v-1z',
|
||||
'dice-multiple':
|
||||
'M6 3h5v5H6V3zm7 0h5v5h-5V3zM3 11h5v5H3v-5zm7 0h5v5h-5v-5zm7 0h-5v5h5v-5z',
|
||||
email:
|
||||
'M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2zm8 7L4 6v1l8 5 8-5V6l-8 5z',
|
||||
// Store nav icons (kept here so the store mobile menu is self-contained).
|
||||
store: 'M4 7h16l-1 13H5L4 7zm5-3a3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0 3 3',
|
||||
'cards-playing': 'M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zm3 4v3m6 7v-3',
|
||||
'printer-3d-nozzle':
|
||||
'M7 2h10v4l3 3-4 2H8L4 9l3-3V2zm5 11v9',
|
||||
'clipboard-list':
|
||||
'M9 3h6v2H9V3zm-2 0v2H6a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-1V3M7 11h2m4 0h4M7 15h2m4 0h4',
|
||||
};
|
||||
|
||||
// Patiun brand icons, resolved by the same names the nav config uses.
|
||||
function Icon({ name, className }: { name: string; className?: string }) {
|
||||
return (
|
||||
<svg className={className} width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d={ICON_PATHS[name] ?? ''} />
|
||||
</svg>
|
||||
);
|
||||
return <BrandIcon name={name} className={className} />;
|
||||
}
|
||||
|
||||
function isActive(href: string, path: string): boolean {
|
||||
@@ -90,17 +69,13 @@ export default function MobileMenu({ groups, currentPath }: Props) {
|
||||
aria-expanded={open}
|
||||
aria-controls="mobile-menu"
|
||||
aria-label={open ? 'Close menu' : 'Open menu'}
|
||||
className="inline-flex h-10 w-10 items-center justify-center rounded-lg border"
|
||||
style={{ color: 'var(--color-text)', borderColor: 'var(--color-border)' }}
|
||||
className="relative z-[60] inline-flex h-10 w-10 items-center justify-center rounded-lg border"
|
||||
style={{ color: 'var(--color-starlight)', borderColor: 'color-mix(in srgb, var(--color-gold) 45%, transparent)' }}
|
||||
>
|
||||
{open ? (
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
||||
<path d="M6 6l12 12M18 6L6 18" />
|
||||
</svg>
|
||||
<BrandIcon name="close" size={22} />
|
||||
) : (
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
||||
<path d="M4 7h16M4 12h16M4 17h16" />
|
||||
</svg>
|
||||
<BrandIcon name="menu" size={22} />
|
||||
)}
|
||||
</button>
|
||||
|
||||
@@ -124,7 +99,11 @@ export default function MobileMenu({ groups, currentPath }: Props) {
|
||||
exit={{ x: '100%' }}
|
||||
transition={{ type: 'tween', duration: 0.28 }}
|
||||
className="fixed right-0 top-0 z-50 flex h-[100dvh] w-[min(82vw,22rem)] flex-col gap-6 overflow-y-auto p-6 pt-20 shadow-2xl"
|
||||
style={{ backgroundColor: 'var(--color-surface-raised)' }}
|
||||
style={{
|
||||
// Night-sky drawer to match the nav it opens from.
|
||||
backgroundColor: 'var(--color-midnight)',
|
||||
borderLeft: '1px solid color-mix(in srgb, var(--color-gold) 45%, transparent)',
|
||||
}}
|
||||
>
|
||||
{groups.map((group, gi) => (
|
||||
<ul key={gi} className="flex flex-col gap-1">
|
||||
@@ -137,9 +116,9 @@ export default function MobileMenu({ groups, currentPath }: Props) {
|
||||
onClick={() => setOpen(false)}
|
||||
className="flex items-center gap-3 rounded-lg px-3 py-3 text-lg font-medium transition-colors"
|
||||
style={{
|
||||
color: active ? 'var(--color-accent)' : 'var(--color-text)',
|
||||
color: active ? 'var(--color-teal-bright)' : 'var(--color-starlight)',
|
||||
backgroundColor: active
|
||||
? 'color-mix(in srgb, var(--color-accent) 14%, transparent)'
|
||||
? 'color-mix(in srgb, var(--color-teal-bright) 14%, transparent)'
|
||||
: 'transparent',
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
*/
|
||||
import Icon from "@components/Icon.astro";
|
||||
import MobileMenu from "./MobileMenu.tsx";
|
||||
import StarBurst from "@components/mystic/StarBurst.astro";
|
||||
import { navGroups, siteIdentity } from "@lib/site";
|
||||
|
||||
const path = Astro.url.pathname;
|
||||
@@ -33,7 +32,7 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
|
||||
id="site-nav"
|
||||
data-nav
|
||||
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,
|
||||
]}
|
||||
>
|
||||
@@ -42,29 +41,34 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
|
||||
data-nav-inner
|
||||
>
|
||||
<nav
|
||||
class="container-prose flex h-16 items-center justify-between gap-4"
|
||||
class="container-prose flex h-20 items-center justify-between gap-4"
|
||||
aria-label="Primary"
|
||||
>
|
||||
<!-- Logo / identity -->
|
||||
<!-- Logo / identity: the Patiun seal (glyph in the gold degree ring) beside
|
||||
the Cinzel name and the site tagline. The "a Patiun LLC company" line
|
||||
lives in the footer with the copyright. -->
|
||||
<a
|
||||
href="/"
|
||||
class="group flex items-center gap-2 leading-tight"
|
||||
class="group flex items-center gap-3 leading-tight"
|
||||
aria-label={`${siteIdentity.name} home`}
|
||||
>
|
||||
<StarBurst
|
||||
class="h-4 w-4 shrink-0 transition-transform duration-700 group-hover:rotate-90"
|
||||
style="color: var(--color-accent);"
|
||||
<img
|
||||
src="/brand/patiun-seal.svg"
|
||||
alt=""
|
||||
width="72"
|
||||
height="72"
|
||||
class="h-[72px] w-[72px] shrink-0 transition-transform duration-700 group-hover:rotate-[30deg]"
|
||||
/>
|
||||
<span class="flex flex-col">
|
||||
<span
|
||||
class="font-sans text-base font-bold tracking-tight"
|
||||
style="color: var(--accent);"
|
||||
class="font-display text-base font-bold uppercase tracking-[0.14em]"
|
||||
style="color: var(--color-starlight);"
|
||||
>
|
||||
{siteIdentity.name}
|
||||
</span>
|
||||
<span
|
||||
class="hidden font-mono text-[0.65rem] uppercase tracking-[0.18em] sm:block"
|
||||
style="color: var(--color-text-subtle);"
|
||||
class="hidden font-eyebrow text-[0.62rem] uppercase tracking-[0.18em] sm:block"
|
||||
style="color: var(--color-gold-bright);"
|
||||
>
|
||||
{siteIdentity.tagline}
|
||||
</span>
|
||||
@@ -80,7 +84,7 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
|
||||
"flex items-center gap-1",
|
||||
{ "border-l pl-5": gi > 0 },
|
||||
]}
|
||||
style={gi > 0 ? "border-color: var(--color-border);" : ""}
|
||||
style={gi > 0 ? "border-color: color-mix(in srgb, var(--color-gold) 45%, transparent);" : ""}
|
||||
>
|
||||
{group.items.map((item) => {
|
||||
const active = isActive(item.href);
|
||||
@@ -93,7 +97,7 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
|
||||
style={
|
||||
active
|
||||
? "color: var(--color-accent);"
|
||||
: "color: var(--color-cream); opacity: 0.85;"
|
||||
: "color: var(--color-starlight); opacity: 0.85;"
|
||||
}
|
||||
onmouseover="this.style.opacity='1'"
|
||||
onmouseout="this.style.opacity='0.85'"
|
||||
@@ -127,20 +131,16 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
|
||||
[data-nav-inner] {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(8, 10, 32, 0.92) 0%,
|
||||
rgba(12, 14, 40, 0.86) 100%
|
||||
rgba(14, 21, 34, 0.92) 0%,
|
||||
rgba(19, 28, 43, 0.86) 100%
|
||||
);
|
||||
}
|
||||
/* Scrolled state: stronger blur + hairline border + shadow. */
|
||||
[data-nav].scrolled [data-nav-inner] {
|
||||
backdrop-filter: saturate(150%) blur(12px);
|
||||
-webkit-backdrop-filter: saturate(150%) blur(12px);
|
||||
border-bottom-color: color-mix(
|
||||
in srgb,
|
||||
var(--color-accent) 30%,
|
||||
var(--color-border)
|
||||
);
|
||||
box-shadow: 0 8px 30px -20px rgba(11, 16, 38, 0.7);
|
||||
border-bottom-color: color-mix(in srgb, var(--color-gold) 35%, transparent);
|
||||
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. */
|
||||
[data-nav]::before {
|
||||
@@ -152,12 +152,12 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
|
||||
background:
|
||||
radial-gradient(
|
||||
60% 120% at 85% -20%,
|
||||
rgba(139, 123, 216, 0.22),
|
||||
rgba(106, 47, 224, 0.22),
|
||||
transparent 70%
|
||||
),
|
||||
radial-gradient(
|
||||
50% 120% at 10% -10%,
|
||||
rgba(232, 181, 74, 0.1),
|
||||
rgba(201, 162, 74, 0.1),
|
||||
transparent 70%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -58,14 +58,14 @@ const entries: CertEntry[] = [
|
||||
{c.status && <span class="pill">{c.status}</span>}
|
||||
</div>
|
||||
<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}
|
||||
</h3>
|
||||
<p class="text-sm font-medium" style="color: var(--color-accent);">
|
||||
{c.issuer}
|
||||
</p>
|
||||
<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);"
|
||||
>
|
||||
{c.year}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
* skipped entirely so the form still works.
|
||||
*/
|
||||
import { useState, useRef, useEffect, type FormEvent } from 'react';
|
||||
import BrandIcon from '@components/brand/BrandIcon';
|
||||
|
||||
type Status = 'idle' | 'submitting' | 'success' | 'error';
|
||||
|
||||
@@ -219,11 +220,9 @@ export default function ContactForm({ turnstileSiteKey }: Props) {
|
||||
style={{ background: 'var(--color-accent)', color: 'var(--color-accent-contrast)' }}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M20 6L9 17l-5-5" />
|
||||
</svg>
|
||||
<BrandIcon name="check" size={24} />
|
||||
</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.
|
||||
</h3>
|
||||
<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" />
|
||||
</span>
|
||||
<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}
|
||||
</h3>
|
||||
<p
|
||||
@@ -55,7 +55,7 @@ const entries: EduEntry[] = [
|
||||
{e.institution}
|
||||
</p>
|
||||
<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);"
|
||||
>
|
||||
{e.period}
|
||||
|
||||
@@ -33,7 +33,7 @@ const links = [
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex flex-col gap-1">
|
||||
<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}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
* Three bugs previously fixed here:
|
||||
* 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
|
||||
* FIXED dark colors regardless of the page's surface tone.
|
||||
* 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. */}
|
||||
<div
|
||||
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. --- */}
|
||||
<div class="relative my-1 w-2 shrink-0">
|
||||
@@ -69,11 +69,11 @@ const sections = [
|
||||
href={`#${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"
|
||||
style="color: #e8e6f5;"
|
||||
style="color: #ece9e2;"
|
||||
>
|
||||
<span
|
||||
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}
|
||||
>
|
||||
<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 {
|
||||
opacity: 1;
|
||||
background: rgba(232, 181, 74, 0.08);
|
||||
background: rgba(201, 162, 74, 0.08);
|
||||
}
|
||||
/* Active link: bright accent text + accent wash. */
|
||||
.pro-section-nav a[data-active="true"] {
|
||||
opacity: 1 !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. */
|
||||
.pro-section-nav a[data-active="true"] [data-section-dot] {
|
||||
background: var(--color-accent) !important;
|
||||
color: #0b1026 !important;
|
||||
box-shadow: 0 0 12px rgba(232, 181, 74, 0.65);
|
||||
color: #131c2b !important;
|
||||
box-shadow: 0 0 12px rgba(201, 162, 74, 0.65);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ const techStack = [
|
||||
<div class="flex flex-col gap-8">
|
||||
<!-- Core Competencies -->
|
||||
<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
|
||||
</h3>
|
||||
<ul class="grid gap-3 sm:grid-cols-2">
|
||||
@@ -112,7 +112,7 @@ const techStack = [
|
||||
|
||||
<!-- Technical Stack -->
|
||||
<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">
|
||||
{
|
||||
techStack.map((item) => (
|
||||
@@ -151,7 +151,7 @@ const techStack = [
|
||||
|
||||
<!-- Emerging & Exploring -->
|
||||
<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 & Exploring
|
||||
</h3>
|
||||
<ul class="grid gap-3 sm:grid-cols-3">
|
||||
|
||||
@@ -50,7 +50,7 @@ const rendered = await Promise.all(
|
||||
</span>
|
||||
{entry.data.company}
|
||||
</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}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* visible text (not aria-hidden) so it's announced.
|
||||
*/
|
||||
import { useState, useRef, useCallback, type PointerEvent } from 'react';
|
||||
import BrandIcon from '@components/brand/BrandIcon';
|
||||
|
||||
export interface CarouselSlide {
|
||||
src: string;
|
||||
@@ -150,11 +151,11 @@ export default function Carousel({ slides, label = 'Product examples' }: Props)
|
||||
{/* Caption gradient + label, always visible for the active slide. */}
|
||||
<div
|
||||
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
|
||||
className="font-sans text-sm font-semibold sm:text-base"
|
||||
style={{ color: 'var(--color-cream)' }}
|
||||
style={{ color: 'var(--color-starlight)' }}
|
||||
>
|
||||
{s.label}
|
||||
</span>
|
||||
@@ -172,14 +173,12 @@ export default function Carousel({ slides, label = 'Product examples' }: Props)
|
||||
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"
|
||||
style={{
|
||||
color: 'var(--color-cream)',
|
||||
color: 'var(--color-starlight)',
|
||||
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">
|
||||
<path d="M15 18l-6-6 6-6" />
|
||||
</svg>
|
||||
<BrandIcon name="chevron-left" size={20} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -187,22 +186,20 @@ export default function Carousel({ slides, label = 'Product examples' }: Props)
|
||||
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"
|
||||
style={{
|
||||
color: 'var(--color-cream)',
|
||||
color: 'var(--color-starlight)',
|
||||
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">
|
||||
<path d="M9 18l6-6-6-6" />
|
||||
</svg>
|
||||
<BrandIcon name="chevron-right" size={20} />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Slide counter (top-right). */}
|
||||
<span
|
||||
className="pointer-events-none absolute right-3 top-3 rounded-full px-2.5 py-1 font-mono text-xs"
|
||||
style={{ color: 'var(--color-cream)', background: 'rgba(7,10,28,0.6)' }}
|
||||
className="pointer-events-none absolute right-3 top-3 rounded-full px-2.5 py-1 font-eyebrow text-xs"
|
||||
style={{ color: 'var(--color-starlight)', background: 'rgba(14,21,34,0.6)' }}
|
||||
>
|
||||
{index + 1} / {count}
|
||||
</span>
|
||||
|
||||
@@ -27,7 +27,7 @@ const inStockCount = colors.filter((c) => c.inStock).length;
|
||||
<span class="text-sm" style="color: var(--color-text-muted);">
|
||||
Choose from the available filament colors.
|
||||
</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
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* before it can be submitted.
|
||||
*/
|
||||
import { useState, useRef, useEffect, type FormEvent } from 'react';
|
||||
import BrandIcon from '@components/brand/BrandIcon';
|
||||
|
||||
type Status = 'idle' | 'submitting' | 'success' | 'error';
|
||||
type OrderType = 'cards' | 'deckBoxes' | 'both';
|
||||
@@ -206,11 +207,9 @@ export default function OrderForm({ turnstileSiteKey, colors = [] }: Props) {
|
||||
style={{ background: 'var(--color-accent)', color: 'var(--color-accent-contrast)' }}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M20 6L9 17l-5-5" />
|
||||
</svg>
|
||||
<BrandIcon name="check" size={24} />
|
||||
</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.
|
||||
</h3>
|
||||
<p className="text-sm" style={{ color: 'var(--color-text-muted)' }}>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</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}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -37,14 +37,10 @@ const cover = product.images[0];
|
||||
/>
|
||||
{product.images.length > 1 && (
|
||||
<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"
|
||||
style="color: var(--color-cream); background: rgba(7,10,28,0.6);"
|
||||
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-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">
|
||||
<rect x="3" y="5" width="18" height="14" rx="2" />
|
||||
<circle cx="9" cy="12" r="1.6" fill="currentColor" stroke="none" />
|
||||
<circle cx="15" cy="12" r="1.6" fill="currentColor" stroke="none" />
|
||||
</svg>
|
||||
<Icon name="gallery" class="h-3.5 w-3.5" />
|
||||
{product.images.length}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -24,7 +24,7 @@ const { items, heading } = Astro.props;
|
||||
function toneColor(icon: Requirement['icon']): string {
|
||||
switch (icon) {
|
||||
case 'alert':
|
||||
return '#e8b54a'; // amber — caution
|
||||
return '#d8b35a'; // gold — caution
|
||||
case 'close-circle':
|
||||
return '#e5655a'; // red — hard no
|
||||
case 'check-decagram':
|
||||
@@ -36,7 +36,7 @@ function toneColor(icon: Requirement['icon']): string {
|
||||
---
|
||||
|
||||
{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">
|
||||
|
||||
@@ -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);">
|
||||
<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
|
||||
|
||||
@@ -8,13 +8,12 @@
|
||||
* two sites not be clearly linked yet.
|
||||
*
|
||||
* 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 seal — so it reads as the same house while
|
||||
* being unmistakably a store (a "Store" wordmark + the store.gregkilmer.com
|
||||
* domain label).
|
||||
*/
|
||||
import Icon from '@components/Icon.astro';
|
||||
import MobileMenu from '@components/nav/MobileMenu.tsx';
|
||||
import StarBurst from '@components/mystic/StarBurst.astro';
|
||||
|
||||
interface NavEntry {
|
||||
label: string;
|
||||
@@ -40,25 +39,28 @@ const groups = [{ items: storeNav }];
|
||||
<header
|
||||
id="store-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>
|
||||
<nav class="container-prose flex h-16 items-center justify-between gap-4" aria-label="Store">
|
||||
<nav class="container-prose flex h-20 items-center justify-between gap-4" aria-label="Store">
|
||||
<!-- 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">
|
||||
<StarBurst
|
||||
class="h-4 w-4 shrink-0 transition-transform duration-700 group-hover:rotate-90"
|
||||
style="color: var(--color-accent);"
|
||||
<a href="/store" class="group flex items-center gap-3 leading-tight" aria-label="Greg Kilmer Store — home">
|
||||
<img
|
||||
src="/brand/patiun-seal.svg"
|
||||
alt=""
|
||||
width="72"
|
||||
height="72"
|
||||
class="h-[72px] w-[72px] shrink-0 transition-transform duration-700 group-hover:rotate-[30deg]"
|
||||
/>
|
||||
<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
|
||||
</span>
|
||||
<span
|
||||
class="hidden font-mono text-[0.65rem] uppercase tracking-[0.18em] sm:block"
|
||||
class="hidden font-eyebrow text-[0.7rem] tracking-[0.08em] sm:block"
|
||||
style="color: var(--color-text-subtle);"
|
||||
>
|
||||
store.gregkilmer.com
|
||||
Store.GregKilmer.com
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
@@ -71,7 +73,7 @@ const groups = [{ items: storeNav }];
|
||||
<a
|
||||
href={item.href}
|
||||
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'"
|
||||
onmouseout="this.style.opacity='0.85'"
|
||||
aria-current={active ? 'page' : undefined}
|
||||
@@ -100,8 +102,8 @@ const groups = [{ items: storeNav }];
|
||||
[data-nav-inner] {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgb(8, 10, 32) 0%,
|
||||
rgb(12, 14, 40) 100%
|
||||
rgb(14, 21, 34) 0%,
|
||||
rgb(19, 28, 43) 100%
|
||||
);
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
|
||||
}
|
||||
@@ -113,7 +115,7 @@ const groups = [{ items: storeNav }];
|
||||
var(--color-accent) 30%,
|
||||
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 {
|
||||
content: '';
|
||||
@@ -122,8 +124,8 @@ const groups = [{ items: storeNav }];
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
background:
|
||||
radial-gradient(60% 120% at 85% -20%, rgba(139, 123, 216, 0.22), transparent 70%),
|
||||
radial-gradient(50% 120% at 10% -10%, rgba(232, 181, 74, 0.1), 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(201, 162, 74, 0.1), transparent 70%);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
import { useState } from 'react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import BrandIcon from '@components/brand/BrandIcon';
|
||||
|
||||
export interface BoardGame {
|
||||
name: string;
|
||||
@@ -106,10 +107,8 @@ export default function BoardGameShelf({
|
||||
color: isActive ? 'var(--color-accent)' : 'var(--color-secondary)',
|
||||
}}
|
||||
>
|
||||
{/* dice / playing-card style glyph, inline to stay self-contained */}
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zm0 2v14h14V5H5zm4 2.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm9 0a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0 7a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm-9 0a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm4.5-3.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z" />
|
||||
</svg>
|
||||
{/* Patiun dice icon */}
|
||||
<BrandIcon name="dice" size={16} />
|
||||
</span>
|
||||
<span className="flex flex-col gap-0.5">
|
||||
<span className="font-sans text-base leading-tight underline-offset-4 group-hover:underline group-focus:underline">
|
||||
@@ -133,7 +132,7 @@ export default function BoardGameShelf({
|
||||
className="relative w-full overflow-hidden rounded-xl border"
|
||||
style={{
|
||||
height: SHELF_HEIGHT,
|
||||
borderColor: 'color-mix(in srgb, var(--color-accent) 35%, transparent)',
|
||||
borderColor: 'color-mix(in srgb, var(--color-gold) 70%, transparent)',
|
||||
background: 'color-mix(in srgb, var(--color-secondary) 12%, transparent)',
|
||||
}}
|
||||
// When the pointer leaves the whole figure, snap back to the shelf.
|
||||
@@ -173,7 +172,7 @@ export default function BoardGameShelf({
|
||||
|
||||
<figcaption
|
||||
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">
|
||||
{activeGame?.name ?? shelfCaption}
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
import "@/styles/global.css";
|
||||
|
||||
// Self-hosted fonts (woff2, variable). Avoids external requests for perf + privacy.
|
||||
import "@fontsource-variable/inter";
|
||||
import "@fontsource-variable/plus-jakarta-sans";
|
||||
// Patiun brand type: Cinzel (headings, names) + Montserrat (body, UI).
|
||||
// JetBrains Mono stays for the rare code-like block.
|
||||
import "@fontsource-variable/cinzel";
|
||||
import "@fontsource-variable/montserrat";
|
||||
import "@fontsource-variable/jetbrains-mono";
|
||||
|
||||
import { ClientRouter } from "astro:transitions";
|
||||
@@ -104,7 +106,7 @@ const ogImageUrl = ogImage
|
||||
<meta name="description" content={description} />
|
||||
<link rel="canonical" href={canonical} />
|
||||
{noindex && <meta name="robots" content="noindex, nofollow" />}
|
||||
<meta name="theme-color" content="#0b1026" />
|
||||
<meta name="theme-color" content="#131c2b" />
|
||||
|
||||
<!-- Open Graph / social cards -->
|
||||
<meta property="og:type" content="website" />
|
||||
@@ -115,7 +117,12 @@ const ogImageUrl = ogImage
|
||||
{ogImageUrl && <meta property="og:image" content={ogImageUrl} />}
|
||||
<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.
|
||||
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"}>
|
||||
<a
|
||||
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
|
||||
</a>
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* brand-icons.ts
|
||||
* ---------------------------------------------------------------------------
|
||||
* The Patiun UI icon set (24px grid, 1.6 stroke, round caps, at most one gold
|
||||
* four-point star accent). Generated from the Patiun brand icon source
|
||||
* (branding/source, newicons.py); edit there and regenerate rather than by hand.
|
||||
*
|
||||
* Each entry is the SVG body only. Lines use currentColor; the gold accent uses
|
||||
* var(--icon-gold), which global.css sets per ground (deep gold on Starlight,
|
||||
* flat gold on the night sky).
|
||||
*/
|
||||
|
||||
export const brandIcons: Record<string, string> = {
|
||||
"alert": "<path d=\"M12 4 21 19.5H3z\"/><path d=\"M12 10v4.5\"/><circle cx=\"12\" cy=\"17.2\" r=\"0.9\" fill=\"currentColor\" stroke=\"none\"/>",
|
||||
"animation": "<rect x=\"7.5\" y=\"7.5\" width=\"13\" height=\"12\" rx=\"1.5\"/><path d=\"M5 16.5V6.5A1.5 1.5 0 0 1 6.5 5H16\"/><path d=\"M3 13.5V4A1 1 0 0 1 4 3h9\"/><path d=\"M12.5 10.8v5.4l4.3-2.7z\"/>",
|
||||
"arrow-left": "<path d=\"M19.5 12h-15M10 6.5 4.5 12l5.5 5.5\"/>",
|
||||
"arrow-right": "<path d=\"M4.5 12h15M14 6.5l5.5 5.5-5.5 5.5\"/>",
|
||||
"bookshelf": "<path d=\"M3.5 20.5h17\"/><rect x=\"5\" y=\"6\" width=\"3\" height=\"14.5\" rx=\"0.5\"/><rect x=\"8.5\" y=\"4\" width=\"3\" height=\"16.5\" rx=\"0.5\"/><path d=\"M13 7.2l2.9-.8 3.9 13.6-2.9.8z\"/>",
|
||||
"brain": "<path d=\"M12 5.5a3 3 0 0 0-5.6-1.2A3 3 0 0 0 4 8.8a3.2 3.2 0 0 0 .3 5.4A3 3 0 0 0 7.5 18.6 3 3 0 0 0 12 19.5z\"/><path d=\"M12 5.5a3 3 0 0 1 5.6-1.2A3 3 0 0 1 20 8.8a3.2 3.2 0 0 1-.3 5.4 3 3 0 0 1-3.2 4.4A3 3 0 0 1 12 19.5z\"/><path d=\"M12 5.5v14\"/>",
|
||||
"briefcase": "<rect x=\"3.5\" y=\"7.5\" width=\"17\" height=\"12\" rx=\"1.8\"/><path d=\"M9 7.5V5.8A1.3 1.3 0 0 1 10.3 4.5h3.4A1.3 1.3 0 0 1 15 5.8v1.7\"/><path d=\"M3.5 12.5h17\"/><polygon class=\"acc\" points=\"12.00,9.84 12.56,11.94 14.66,12.50 12.56,13.06 12.00,15.16 11.44,13.06 9.34,12.50 11.44,11.94\" fill=\"var(--icon-gold, #C9A24A)\" stroke=\"none\"/>",
|
||||
"calendar": "<rect x=\"3.5\" y=\"5\" width=\"17\" height=\"15.5\" rx=\"1.8\"/><path d=\"M3.5 9.5h17M8 3v4M16 3v4\"/><path d=\"M12 12.3v2.7l1.8 1.1\"/>",
|
||||
"castle": "<path d=\"M4 20.5V8h3v2h2.5V8h5v2H17V8h3v12.5z\"/><path d=\"M10 20.5v-4a2 2 0 0 1 4 0v4\"/><polygon class=\"acc\" points=\"12.00,1.68 12.53,3.67 14.52,4.20 12.53,4.73 12.00,6.72 11.47,4.73 9.48,4.20 11.47,3.67\" fill=\"var(--icon-gold, #C9A24A)\" stroke=\"none\"/>",
|
||||
"chart-bar": "<path d=\"M4 20h16\"/><path d=\"M7 20v-6M12 20V8M17 20v-9.5\"/><polygon class=\"acc\" points=\"12.00,1.84 12.56,3.94 14.66,4.50 12.56,5.06 12.00,7.16 11.44,5.06 9.34,4.50 11.44,3.94\" fill=\"var(--icon-gold, #C9A24A)\" stroke=\"none\"/>",
|
||||
"chart-line": "<path d=\"M4 4v16h16\"/><path d=\"M7.5 15.5l3.5-4 3 2.5 5-6.5\"/>",
|
||||
"check": "<path d=\"M5 12.5l4.5 4.5L19 7.5\"/>",
|
||||
"chess": "<path d=\"M8 20.5h8M7 18h10M8.5 18l1-6h5l1 6\"/><path d=\"M8.5 12h7M10 12 9 8.5h6L14 12\"/><path d=\"M12 3v3.5M10.5 4.7h3\"/>",
|
||||
"chevron-down": "<path d=\"M6 9.5l6 6 6-6\"/>",
|
||||
"chevron-left": "<path d=\"M14.5 6 8.5 12l6 6\"/>",
|
||||
"chevron-right": "<path d=\"M9.5 6l6 6-6 6\"/>",
|
||||
"clapper": "<rect x=\"3.5\" y=\"9.5\" width=\"17\" height=\"11\" rx=\"1.5\"/><path d=\"M3.5 9.5 19 5.3l.8 2.9\"/><path d=\"M8.2 8.2l1.6 2.9M13.5 6.8l1.6 2.9\"/>",
|
||||
"clipboard": "<rect x=\"5\" y=\"5\" width=\"14\" height=\"16\" rx=\"1.8\"/><path d=\"M9 5V4.2A1.2 1.2 0 0 1 10.2 3h3.6A1.2 1.2 0 0 1 15 4.2V5\"/><path d=\"M8.5 10h7M8.5 13.5h7M8.5 17h4.5\"/>",
|
||||
"clipboard-check": "<rect x=\"5\" y=\"5\" width=\"14\" height=\"16\" rx=\"1.8\"/><path d=\"M9 5V4.2A1.2 1.2 0 0 1 10.2 3h3.6A1.2 1.2 0 0 1 15 4.2V5\"/><path d=\"M8.5 13l2.5 2.5 4.5-5\"/>",
|
||||
"clock": "<circle cx=\"12\" cy=\"12\" r=\"8.5\"/><path d=\"M12 7v5l3.2 2\"/>",
|
||||
"close": "<path d=\"M6 6l12 12M18 6 6 18\"/>",
|
||||
"cloud": "<path d=\"M7 18.5a4 4 0 0 1-.6-8A5.5 5.5 0 0 1 17 9.2a4.7 4.7 0 0 1 .2 9.3z\"/>",
|
||||
"cog": "<polygon points=\"12.76,3.43 14.58,3.80 15.05,6.15 16.24,6.94 18.59,6.48 19.62,8.02 18.30,10.02 18.57,11.42 20.57,12.76 20.20,14.58 17.85,15.05 17.06,16.24 17.52,18.59 15.98,19.62 13.98,18.30 12.58,18.57 11.24,20.57 9.42,20.20 8.95,17.85 7.76,17.06 5.41,17.52 4.38,15.98 5.70,13.98 5.43,12.58 3.43,11.24 3.80,9.42 6.15,8.95 6.94,7.76 6.48,5.41 8.02,4.38 10.02,5.70 11.42,5.43\"/><circle cx=\"12\" cy=\"12\" r=\"2.8\"/>",
|
||||
"commission": "<path d=\"M15.5 3.5 20.5 8.5 11 18l-5.5 1.5L7 14z\"/><path d=\"M13.5 5.5l5 5\"/><path d=\"M5.5 19.5 9 16\"/><polygon class=\"acc\" points=\"19.00,14.36 19.77,17.23 22.64,18.00 19.77,18.77 19.00,21.64 18.23,18.77 15.36,18.00 18.23,17.23\" fill=\"var(--icon-gold, #C9A24A)\" stroke=\"none\"/>",
|
||||
"contact": "<rect x=\"3\" y=\"5.5\" width=\"18\" height=\"13\" rx=\"1.5\"/><path d=\"m3.5 6.5 8.5 6.5 8.5-6.5\"/>",
|
||||
"cube": "<path d=\"M12 3.5 20 8v8l-8 4.5L4 16V8z\"/><path d=\"M4 8l8 4.5L20 8M12 12.5v8\"/>",
|
||||
"devices": "<rect x=\"3\" y=\"6\" width=\"11\" height=\"8.5\" rx=\"1.2\"/><path d=\"M2 17.5h13\"/><rect x=\"16\" y=\"8\" width=\"5.5\" height=\"12\" rx=\"1.2\"/><path d=\"M18 18h1.5\"/>",
|
||||
"dice": "<polygon points=\"12.00,3.00 19.79,7.50 19.79,16.50 12.00,21.00 4.21,16.50 4.21,7.50\"/><path d=\"M12 6.5 7.3 14.6h9.4z\"/><path d=\"M12 3v3.5M4.2 16.5l3.1-1.9M19.8 16.5l-3.1-1.9\"/>",
|
||||
"dog": "<path d=\"M8.2 6.5C9.5 5.6 14.5 5.6 15.8 6.5l1.2 5.5c0 4-2.2 7.5-5 7.5s-5-3.5-5-7.5z\"/><path d=\"M8.2 6.5C6 5.8 3.8 7.8 4.3 11.8c.3 1.6 1.8 1.6 2.7.2M15.8 6.5c2.2-.7 4.4 1.3 3.9 5.3-.3 1.6-1.8 1.6-2.7.2\"/><circle cx=\"10\" cy=\"11\" r=\"0.8\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"14\" cy=\"11\" r=\"0.8\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M11 14.5h2l-1 1.2z\"/>",
|
||||
"external": "<path d=\"M13.5 4.5h6v6M19.5 4.5 11 13\"/><path d=\"M17.5 13.5v5a1.5 1.5 0 0 1-1.5 1.5H6A1.5 1.5 0 0 1 4.5 18.5V8A1.5 1.5 0 0 1 6 6.5h5\"/>",
|
||||
"factory": "<path d=\"M3.5 20.5V11l5 3V11l5 3V11l5 3V5h2v15.5z\"/><path d=\"M7 17.5h2M11.5 17.5h2M16 17.5h2\"/>",
|
||||
"favorite": "<path d=\"M12 3.5 13.6 10.4 20.5 12 13.6 13.6 12 20.5 10.4 13.6 3.5 12 10.4 10.4z\"/>",
|
||||
"film": "<rect x=\"4\" y=\"3.5\" width=\"16\" height=\"17\" rx=\"1.5\"/><path d=\"M8 3.5v17M16 3.5v17M4 8h4M4 12h4M4 16h4M16 8h4M16 12h4M16 16h4\"/>",
|
||||
"gallery": "<rect x=\"7\" y=\"3.5\" width=\"13.5\" height=\"12\" rx=\"1.5\"/><path d=\"M4 7v11.5A1.5 1.5 0 0 0 5.5 20H17\"/><path d=\"M8.5 13.5l3.5-3.5 3 3 1.5-1.5 2.5 2.5\"/><polygon class=\"acc\" points=\"16.20,5.02 16.70,6.90 18.58,7.40 16.70,7.90 16.20,9.78 15.70,7.90 13.82,7.40 15.70,6.90\" fill=\"var(--icon-gold, #C9A24A)\" stroke=\"none\"/>",
|
||||
"gamepad": "<path d=\"M7 7.5h10c2.4 0 3.9 1.6 4.3 4l.6 4.2c.3 2-1.9 3.4-3.5 2l-2.4-2.2H8l-2.4 2.2c-1.6 1.4-3.8 0-3.5-2l.6-4.2c.4-2.4 1.9-4 4.3-4z\"/><path d=\"M7.5 10.5v3M6 12h3\"/><circle cx=\"15.5\" cy=\"11\" r=\"0.9\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"17.5\" cy=\"13\" r=\"0.9\" fill=\"currentColor\" stroke=\"none\"/>",
|
||||
"gauge": "<path d=\"M4 17a8.5 8.5 0 1 1 16 0\"/><path d=\"M12 15l4-5\"/><circle cx=\"12\" cy=\"15\" r=\"1.1\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M6.5 12.5h1M12 6.8v1M17.5 12.5h-1\"/>",
|
||||
"gem": "<path d=\"M7 4h10l3.5 5L12 20.5 3.5 9z\"/><path d=\"M3.5 9h17M9.5 9 12 20.5 14.5 9M9.5 9 12 4l2.5 5\"/>",
|
||||
"graduation": "<path d=\"M2.5 9.5 12 5l9.5 4.5L12 14z\"/><path d=\"M6.5 11.5v4.5c3.5 2.6 7.5 2.6 11 0v-4.5\"/><path d=\"M21.5 9.5v5\"/>",
|
||||
"grid": "<rect x=\"4\" y=\"4\" width=\"6.5\" height=\"6.5\" rx=\"1.2\"/><rect x=\"13.5\" y=\"4\" width=\"6.5\" height=\"6.5\" rx=\"1.2\"/><rect x=\"4\" y=\"13.5\" width=\"6.5\" height=\"6.5\" rx=\"1.2\"/><rect x=\"13.5\" y=\"13.5\" width=\"6.5\" height=\"6.5\" rx=\"1.2\"/>",
|
||||
"hammer": "<rect x=\"4\" y=\"4.5\" width=\"12.5\" height=\"4.5\" rx=\"1.2\"/><path d=\"M16.5 6.75h3\"/><path d=\"M10.25 9V20.5\"/>",
|
||||
"heart": "<path d=\"M12 19.5S4 14.8 4 9.3A4 4 0 0 1 12 7.6a4 4 0 0 1 8 1.7c0 5.5-8 10.2-8 10.2z\"/>",
|
||||
"hearth": "<path d=\"M4 11 12 4.5 20 11\"/><path d=\"M6 9.5V19.5h12V9.5\"/><path d=\"M12 17.5s-3.2-2-3.2-4.1a1.6 1.6 0 0 1 3.2-.5 1.6 1.6 0 0 1 3.2.5c0 2.1-3.2 4.1-3.2 4.1z\"/>",
|
||||
"home": "<path d=\"M4 11 12 4.5 20 11\"/><path d=\"M6 9.5V19.5h12V9.5\"/><path d=\"M10 19.5v-5h4v5\"/>",
|
||||
"info": "<circle cx=\"12\" cy=\"12\" r=\"8.5\"/><path d=\"M12 11v5.5\"/><circle cx=\"12\" cy=\"7.9\" r=\"0.9\" fill=\"currentColor\" stroke=\"none\"/>",
|
||||
"leather": "<path d=\"M7 4c1.5 1 3 1.3 5 1.3S15.5 5 17 4c.6 1.6 1.8 2.6 3 3-.9 2-1 4-.3 6.2.5 1.6.2 3.4-1 4.6-1 .9-2.4 1.3-3.7 2.2H9c-1.3-.9-2.7-1.3-3.7-2.2-1.2-1.2-1.5-3-1-4.6.7-2.2.6-4.2-.3-6.2 1.2-.4 2.4-1.4 3-3z\"/><path d=\"M8 9.5v7M16 9.5v7\" stroke-dasharray=\".1 2.2\"/>",
|
||||
"link": "<path d=\"M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1.2 1.2\"/><path d=\"M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1.2-1.2\"/>",
|
||||
"list": "<circle cx=\"5\" cy=\"6.5\" r=\"0.9\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"5\" cy=\"12\" r=\"0.9\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"5\" cy=\"17.5\" r=\"0.9\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M9 6.5h11M9 12h11M9 17.5h11\"/>",
|
||||
"list-text": "<path d=\"M4 6h16M4 10h10M4 14h16M4 18h10\"/>",
|
||||
"location": "<path d=\"M12 21s-6.5-6.2-6.5-11.2a6.5 6.5 0 0 1 13 0C18.5 14.8 12 21 12 21z\"/><polygon class=\"acc\" points=\"12.00,5.88 12.83,8.97 15.92,9.80 12.83,10.63 12.00,13.72 11.17,10.63 8.08,9.80 11.17,8.97\" fill=\"var(--icon-gold, #C9A24A)\" stroke=\"none\"/>",
|
||||
"lock": "<rect x=\"5\" y=\"10.5\" width=\"14\" height=\"10\" rx=\"1.8\"/><path d=\"M8 10.5V8a4 4 0 0 1 8 0v2.5\"/><path d=\"M12 14.5v2\"/>",
|
||||
"menu": "<path d=\"M4 7h16M4 12h11M4 17h16\"/><polygon class=\"acc\" points=\"19.20,8.92 19.85,11.35 22.28,12.00 19.85,12.65 19.20,15.08 18.55,12.65 16.12,12.00 18.55,11.35\" fill=\"var(--icon-gold, #C9A24A)\" stroke=\"none\"/>",
|
||||
"monitor": "<rect x=\"3\" y=\"4\" width=\"18\" height=\"12.5\" rx=\"1.5\"/><path d=\"M9 20.5h6M12 16.5v4\"/>",
|
||||
"our-story": "<path d=\"M12 6.5C10 5 7 4.6 3.5 5v13c3.5-.4 6.5 0 8.5 1.5 2-1.5 5-1.9 8.5-1.5V5C17 4.6 14 5 12 6.5z\"/><path d=\"M12 6.5v13\"/><polygon class=\"acc\" points=\"7.70,8.42 8.35,10.85 10.78,11.50 8.35,12.15 7.70,14.58 7.05,12.15 4.62,11.50 7.05,10.85\" fill=\"var(--icon-gold, #C9A24A)\" stroke=\"none\"/>",
|
||||
"palette": "<path d=\"M12 3.5a8.5 8.5 0 0 0 0 17c1.3 0 1.9-.9 1.6-2-.4-1.4.3-2.5 1.8-2.5H17a3.5 3.5 0 0 0 3.5-3.5c0-5-3.8-9-8.5-9z\"/><circle cx=\"7.8\" cy=\"11.5\" r=\"1.1\"/><circle cx=\"10\" cy=\"7.6\" r=\"1.1\"/><circle cx=\"14.5\" cy=\"7.6\" r=\"1.1\"/><polygon class=\"acc\" points=\"17.00,9.26 17.48,11.02 19.24,11.50 17.48,11.98 17.00,13.74 16.52,11.98 14.76,11.50 16.52,11.02\" fill=\"var(--icon-gold, #C9A24A)\" stroke=\"none\"/>",
|
||||
"people": "<circle cx=\"9\" cy=\"8.5\" r=\"3\"/><path d=\"M3 19c.6-3.2 3-5 6-5s5.4 1.8 6 5\"/><path d=\"M15.2 5.8a3 3 0 0 1 0 5.6M17.4 14.3c1.8.7 3 2.5 3.4 4.7\"/>",
|
||||
"person": "<circle cx=\"12\" cy=\"8\" r=\"3.6\"/><path d=\"M4.5 20c.8-3.8 3.8-6 7.5-6s6.7 2.2 7.5 6\"/>",
|
||||
"person-details": "<circle cx=\"8.5\" cy=\"8.5\" r=\"3\"/><path d=\"M2.8 19c.6-3.2 3-5 5.7-5s5.1 1.8 5.7 5\"/><path d=\"M15 8h6M15 11.5h6M17 15h4\"/>",
|
||||
"play": "<circle cx=\"12\" cy=\"12\" r=\"8.5\"/><path d=\"M10 8.5v7l5.5-3.5z\"/>",
|
||||
"price": "<path d=\"M3.5 12.8V4.5a1 1 0 0 1 1-1h8.3l8.2 8.2-9 9z\"/><circle cx=\"8\" cy=\"8\" r=\"1.5\"/><polygon class=\"acc\" points=\"14.60,11.60 15.19,13.81 17.40,14.40 15.19,14.99 14.60,17.20 14.01,14.99 11.80,14.40 14.01,13.81\" fill=\"var(--icon-gold, #C9A24A)\" stroke=\"none\"/>",
|
||||
"print": "<path d=\"M12 3.5 20 8l-8 4.5L4 8z\"/><path d=\"M4 11.5l8 4.5 8-4.5\"/><path d=\"M4 15l8 4.5 8-4.5\"/>",
|
||||
"proxy-cards": "<path d=\"M8.5 6.5H6A1.5 1.5 0 0 0 4.5 8v12A1.5 1.5 0 0 0 6 21.5h8a1.5 1.5 0 0 0 1.5-1.5v-.5\"/><rect x=\"8.5\" y=\"3\" width=\"11\" height=\"16.5\" rx=\"1.5\"/><rect x=\"10.6\" y=\"5.1\" width=\"6.8\" height=\"12.3\" rx=\".6\" stroke-width=\".9\"/><polygon class=\"acc\" points=\"14.00,7.75 14.74,10.51 17.50,11.25 14.74,11.99 14.00,14.75 13.26,11.99 10.50,11.25 13.26,10.51\" fill=\"var(--icon-gold, #C9A24A)\" stroke=\"none\"/>",
|
||||
"robot": "<rect x=\"5\" y=\"8\" width=\"14\" height=\"11\" rx=\"2.5\"/><path d=\"M12 8V5\"/><circle cx=\"12\" cy=\"4\" r=\"1\"/><circle cx=\"9.3\" cy=\"13\" r=\"1.2\"/><circle cx=\"14.7\" cy=\"13\" r=\"1.2\"/><path d=\"M9.5 16.5h5M3 12.5v2.5M21 12.5v2.5\"/>",
|
||||
"round-knife": "<path d=\"M4 12.5h16a8 8 0 0 1-16 0z\"/><path d=\"M12 12.5V7\"/><rect x=\"10.3\" y=\"3\" width=\"3.4\" height=\"4\" rx=\"1\"/>",
|
||||
"ruler": "<path d=\"M3.5 15.5 15.5 3.5l5 5-12 12z\"/><path d=\"M7 12l1.8 1.8M9.5 9.5l1.2 1.2M12 7l1.8 1.8M14.5 4.5l1.2 1.2\"/>",
|
||||
"scissors": "<circle cx=\"6.5\" cy=\"17.5\" r=\"2.5\"/><circle cx=\"6.5\" cy=\"6.5\" r=\"2.5\"/><path d=\"M8.6 7.9 20 17M8.6 16.1 20 7\"/>",
|
||||
"search": "<circle cx=\"10.5\" cy=\"10.5\" r=\"6.5\"/><path d=\"m15.5 15.5 5 5\"/><polygon class=\"acc\" points=\"10.50,7.14 11.21,9.79 13.86,10.50 11.21,11.21 10.50,13.86 9.79,11.21 7.14,10.50 9.79,9.79\" fill=\"var(--icon-gold, #C9A24A)\" stroke=\"none\"/>",
|
||||
"server": "<rect x=\"4\" y=\"3.5\" width=\"16\" height=\"6.5\" rx=\"1.5\"/><rect x=\"4\" y=\"12\" width=\"16\" height=\"6.5\" rx=\"1.5\"/><circle cx=\"7.5\" cy=\"6.8\" r=\"0.8\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"7.5\" cy=\"15.3\" r=\"0.8\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M12 18.5v2.5M7 21h10\"/>",
|
||||
"shapes": "<circle cx=\"7.5\" cy=\"7.5\" r=\"4\"/><rect x=\"13\" y=\"13\" width=\"7.5\" height=\"7.5\" rx=\"1\"/><path d=\"M17 3.5l4 7h-8z\"/><path d=\"M3.5 20.5 7.5 13.5l4 7z\"/>",
|
||||
"shield-moon": "<path d=\"M12 3.5 19.5 6v5.5c0 4.5-3.1 7.9-7.5 9.5-4.4-1.6-7.5-5-7.5-9.5V6z\"/><path d=\"M13.8 8.8a3.6 3.6 0 1 0 1.5 5.6 3 3 0 0 1-1.5-5.6z\"/>",
|
||||
"shop": "<path d=\"M5 8h14l-1.2 12H6.2z\"/><path d=\"M9 8V6.5a3 3 0 0 1 6 0V8\"/><polygon class=\"acc\" points=\"12.00,10.64 12.71,13.29 15.36,14.00 12.71,14.71 12.00,17.36 11.29,14.71 8.64,14.00 11.29,13.29\" fill=\"var(--icon-gold, #C9A24A)\" stroke=\"none\"/>",
|
||||
"split": "<path d=\"M12 21v-7.5L6 7M12 13.5 18 7\"/><path d=\"M6 11V7h4M18 11V7h-4\"/>",
|
||||
"staff": "<path d=\"M6 21 14.5 9.5\"/><circle cx=\"16.5\" cy=\"7\" r=\"3.2\"/><polygon class=\"acc\" points=\"16.50,4.34 17.06,6.44 19.16,7.00 17.06,7.56 16.50,9.66 15.94,7.56 13.84,7.00 15.94,6.44\" fill=\"var(--icon-gold, #C9A24A)\" stroke=\"none\"/><path d=\"M4.5 18.5l3 3\"/>",
|
||||
"swords": "<path d=\"M4.5 4.5 14.5 14.5M19.5 4.5 9.5 14.5\"/><path d=\"M12.5 16.5l4-4M7.5 12.5l4 4\"/><path d=\"M15 15l3.5 3.5M9 15l-3.5 3.5\"/><circle cx=\"19.2\" cy=\"19.2\" r=\"0.9\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"4.8\" cy=\"19.2\" r=\"0.9\" fill=\"currentColor\" stroke=\"none\"/>",
|
||||
"target": "<circle cx=\"12\" cy=\"12\" r=\"8.5\"/><circle cx=\"12\" cy=\"12\" r=\"4.8\"/><polygon class=\"acc\" points=\"12.00,9.20 12.59,11.41 14.80,12.00 12.59,12.59 12.00,14.80 11.41,12.59 9.20,12.00 11.41,11.41\" fill=\"var(--icon-gold, #C9A24A)\" stroke=\"none\"/>",
|
||||
"tea": "<path d=\"M5 10h12v3.5a6 6 0 0 1-12 0z\"/><path d=\"M17 11.5h1.5a2 2 0 0 1 0 4H16.5\"/><path d=\"M3.5 21h15\"/><path d=\"M9 3.5c-.8 1 .8 2-.1 3.2M12.5 3.5c-.8 1 .8 2-.1 3.2\"/>",
|
||||
"timeline": "<path d=\"M6 3.5v17\"/><circle cx=\"6\" cy=\"7\" r=\"1.5\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"6\" cy=\"12\" r=\"1.5\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"6\" cy=\"17\" r=\"1.5\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M10.5 7h9.5M10.5 12h7M10.5 17h8.5\"/>",
|
||||
"toolbox": "<rect x=\"3.5\" y=\"9\" width=\"17\" height=\"10.5\" rx=\"1.5\"/><path d=\"M8.5 9V6.5A1.5 1.5 0 0 1 10 5h4a1.5 1.5 0 0 1 1.5 1.5V9\"/><path d=\"M3.5 13.5h17M10.5 12.3v2.4M13.5 12.3v2.4\"/>",
|
||||
"translate": "<path d=\"M3.5 4.5h10v8h-5.5l-3 2.5v-2.5h-1.5z\"/><path d=\"M15.5 9.5h5v8H19v2.5l-3-2.5h-5.5v-3\"/><path d=\"M6.5 10.5l2-4.5 2 4.5M7.2 9h2.6\"/><path d=\"M13.5 13h4.5M13.5 15h3\"/>",
|
||||
"trophy": "<path d=\"M7.5 4h9v5a4.5 4.5 0 0 1-9 0z\"/><path d=\"M7.5 6H4.5a3 3 0 0 0 3 4M16.5 6h3a3 3 0 0 1-3 4\"/><path d=\"M12 13.5v3.5M8 20.5h8M9.5 20.5V17h5v3.5\"/><polygon class=\"acc\" points=\"12.00,5.96 12.48,7.72 14.24,8.20 12.48,8.68 12.00,10.44 11.52,8.68 9.76,8.20 11.52,7.72\" fill=\"var(--icon-gold, #C9A24A)\" stroke=\"none\"/>",
|
||||
"tv": "<rect x=\"3\" y=\"7\" width=\"18\" height=\"12.5\" rx=\"2\"/><path d=\"M8.5 3.5 12 7l3.5-3.5\"/><path d=\"M7 21h10\"/>",
|
||||
"unavailable": "<circle cx=\"12\" cy=\"12\" r=\"8.5\"/><path d=\"M9 9l6 6M15 9l-6 6\"/>",
|
||||
"verified": "<path d=\"M12 3.2l2.2 1.6 2.7-.2.8 2.6 2.3 1.4-.9 2.6.9 2.6-2.3 1.4-.8 2.6-2.7-.2L12 20.8l-2.2-1.6-2.7.2-.8-2.6-2.3-1.4.9-2.6-.9-2.6 2.3-1.4.8-2.6 2.7.2z\"/><path d=\"M8.5 12.2l2.4 2.4 4.6-4.8\"/>",
|
||||
"wand": "<path d=\"M4 20 15 9\"/><path d=\"M13 7l4 4\"/><polygon class=\"acc\" points=\"18.50,1.86 19.27,4.73 22.14,5.50 19.27,6.27 18.50,9.14 17.73,6.27 14.86,5.50 17.73,4.73\" fill=\"var(--icon-gold, #C9A24A)\" stroke=\"none\"/><path d=\"M8 5.5v2M7 6.5h2M19 15v2M18 16h2\"/>",
|
||||
"web": "<path d=\"M8.5 7 3.5 12l5 5M15.5 7l5 5-5 5\"/><polygon class=\"acc\" points=\"12.00,7.52 12.95,11.05 16.48,12.00 12.95,12.95 12.00,16.48 11.05,12.95 7.52,12.00 11.05,11.05\" fill=\"var(--icon-gold, #C9A24A)\" stroke=\"none\"/>",
|
||||
"wind": "<path d=\"M3.5 9h11a3 3 0 1 0-3-3\"/><path d=\"M3.5 13h15a3 3 0 1 1-3 3\"/><path d=\"M3.5 17h7\"/>",
|
||||
"wrench": "<path d=\"M14.8 4.2a4.5 4.5 0 0 0-5.6 5.6L3.8 15.2a2 2 0 0 0 2.8 2.8l5.4-5.4a4.5 4.5 0 0 0 5.6-5.6l-2.7 2.7-2.6-.6-.6-2.6z\"/>",
|
||||
};
|
||||
|
||||
/** Material Design icon names used around the site, mapped to brand icons.
|
||||
* Third-party logos (GitHub, LinkedIn, Steam, PlayStation, Nintendo Switch,
|
||||
* Azure DevOps, Git, Discord, Poke Ball) are deliberately absent: they stay as
|
||||
* their owners drew them. */
|
||||
export const brandIconAliases: Record<string, string> = {
|
||||
"account": "person",
|
||||
"account-details": "person-details",
|
||||
"account-group": "people",
|
||||
"alert": "alert",
|
||||
"animation-play": "animation",
|
||||
"arrow-left": "arrow-left",
|
||||
"arrow-right": "arrow-right",
|
||||
"auto-fix": "wand",
|
||||
"book-open-page-variant": "our-story",
|
||||
"book-open-variant": "our-story",
|
||||
"bookshelf": "bookshelf",
|
||||
"brain": "brain",
|
||||
"briefcase": "briefcase",
|
||||
"calendar-clock": "calendar",
|
||||
"call-split": "split",
|
||||
"cards": "proxy-cards",
|
||||
"cards-outline": "proxy-cards",
|
||||
"cards-playing": "proxy-cards",
|
||||
"castle": "castle",
|
||||
"cellphone-link": "devices",
|
||||
"chart-bar": "chart-bar",
|
||||
"chart-line": "chart-line",
|
||||
"check": "check",
|
||||
"check-decagram": "verified",
|
||||
"chess-king": "chess",
|
||||
"chevron-down": "chevron-down",
|
||||
"chevron-left": "chevron-left",
|
||||
"chevron-right": "chevron-right",
|
||||
"clipboard-check": "clipboard-check",
|
||||
"clipboard-list": "clipboard",
|
||||
"clock-outline": "clock",
|
||||
"close": "close",
|
||||
"close-circle": "unavailable",
|
||||
"cloud": "cloud",
|
||||
"code-braces": "web",
|
||||
"cog-refresh": "cog",
|
||||
"content-cut": "scissors",
|
||||
"cube-outline": "cube",
|
||||
"currency-usd": "price",
|
||||
"diamond-stone": "gem",
|
||||
"dice-multiple": "dice",
|
||||
"dog": "dog",
|
||||
"email": "contact",
|
||||
"email-outline": "contact",
|
||||
"factory": "factory",
|
||||
"filmstrip": "film",
|
||||
"format-list-bulleted": "list",
|
||||
"format-list-text": "list-text",
|
||||
"gamepad-variant": "gamepad",
|
||||
"gauge": "gauge",
|
||||
"hammer": "hammer",
|
||||
"heart": "heart",
|
||||
"home": "home",
|
||||
"home-heart": "hearth",
|
||||
"image-multiple": "gallery",
|
||||
"information": "info",
|
||||
"knife-military": "round-knife",
|
||||
"link-variant": "link",
|
||||
"lock": "lock",
|
||||
"magic-staff": "staff",
|
||||
"map-marker": "location",
|
||||
"menu": "menu",
|
||||
"monitor": "monitor",
|
||||
"movie-open": "clapper",
|
||||
"open-in-new": "external",
|
||||
"palette": "palette",
|
||||
"pencil": "commission",
|
||||
"play-circle": "play",
|
||||
"printer-3d": "print",
|
||||
"printer-3d-nozzle": "print",
|
||||
"robot": "robot",
|
||||
"ruler": "ruler",
|
||||
"school": "graduation",
|
||||
"scissors-cutting": "scissors",
|
||||
"server-network": "server",
|
||||
"shape": "shapes",
|
||||
"shield-moon-outline": "shield-moon",
|
||||
"star": "favorite",
|
||||
"star-four-points": "favorite",
|
||||
"store": "shop",
|
||||
"sword-cross": "swords",
|
||||
"target": "target",
|
||||
"tea": "tea",
|
||||
"television-classic": "tv",
|
||||
"timeline-text": "timeline",
|
||||
"toolbox": "toolbox",
|
||||
"translate": "translate",
|
||||
"trophy": "trophy",
|
||||
"view-grid": "grid",
|
||||
"weather-windy": "wind",
|
||||
"wrench": "wrench",
|
||||
};
|
||||
|
||||
/** Resolve a brand icon body by brand name or mdi alias, or undefined. */
|
||||
export function brandIconBody(name: string): string | undefined {
|
||||
return brandIcons[name] ?? brandIcons[brandIconAliases[name] ?? ""];
|
||||
}
|
||||
@@ -28,8 +28,10 @@ export const siteIdentity = {
|
||||
name: 'Greg Kilmer',
|
||||
/** Spec tagline; appears beside the logo. */
|
||||
tagline: 'Developer. Maker. Storyteller.',
|
||||
/** Spec domain. */
|
||||
domain: 'gregkilmer.com',
|
||||
/** Brand lockup line shown under the name in the header (Patiun brand). */
|
||||
lockupLine: 'Web Development · a Patiun LLC company',
|
||||
/** Domain, written capitalized per the Patiun brand. */
|
||||
domain: 'GregKilmer.com',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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."
|
||||
>
|
||||
<!-- 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" />
|
||||
<div class="container-prose relative z-10 flex flex-col gap-8 py-8">
|
||||
<Reveal>
|
||||
@@ -58,7 +58,7 @@ const quickFacts = [
|
||||
<Reveal>
|
||||
<p
|
||||
class="border-l-2 pl-5 font-sans text-xl leading-relaxed sm:text-2xl"
|
||||
style="border-color: var(--color-accent); color: var(--color-text);"
|
||||
style="border-color: var(--color-gold); color: var(--color-text);"
|
||||
>
|
||||
I am Greg: a software developer and technical team lead based in
|
||||
Philadelphia who spends his free time running tabletop RPGs, making
|
||||
@@ -124,7 +124,7 @@ const quickFacts = [
|
||||
<Reveal delay={120}>
|
||||
<aside class="card flex flex-col gap-4 lg:sticky lg:top-24">
|
||||
<h3
|
||||
class="font-mono text-xs uppercase tracking-[0.22em]"
|
||||
class="font-eyebrow text-xs uppercase tracking-[0.22em]"
|
||||
style="color: var(--color-accent);"
|
||||
>
|
||||
Quick facts
|
||||
@@ -155,7 +155,7 @@ const quickFacts = [
|
||||
<div class="container-prose">
|
||||
<AlchemicalDivider
|
||||
class="mx-auto h-6 w-full max-w-sm"
|
||||
style="color: var(--color-accent);"
|
||||
style="color: var(--color-gold);"
|
||||
/>
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
* Placeholder slots.
|
||||
*/
|
||||
import BaseLayout from "@layouts/BaseLayout.astro";
|
||||
import PageSeal from "@components/brand/PageSeal.astro";
|
||||
import Reveal from "@components/motion/Reveal.astro";
|
||||
import Icon from "@components/Icon.astro";
|
||||
import SectionHeader from "@components/layout/SectionHeader.astro";
|
||||
@@ -71,6 +72,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
|
||||
description="Reach Greg Kilmer for cloud development, SAP BTP, technical leadership, or just to debate the best Gundam series or find a fourth for game night."
|
||||
noindex={true}
|
||||
>
|
||||
<PageSeal name="zodiac-seal-light" />
|
||||
<section class="container-prose flex flex-col gap-10 pt-28 sm:pt-32">
|
||||
<Reveal>
|
||||
<SectionHeader
|
||||
@@ -89,7 +91,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
|
||||
<Reveal>
|
||||
<section class="flex flex-col gap-4">
|
||||
<h2
|
||||
class="font-mono text-xs uppercase tracking-[0.22em]"
|
||||
class="font-eyebrow text-xs uppercase tracking-[0.22em]"
|
||||
style="color: var(--color-accent);"
|
||||
>
|
||||
Professional
|
||||
@@ -117,7 +119,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
|
||||
<span class="text-xs" style="color: var(--color-text-subtle);">
|
||||
<ObfuscatedEmail
|
||||
address={emailAddress}
|
||||
class="font-mono uppercase tracking-[0.1em]"
|
||||
class="font-eyebrow uppercase tracking-[0.1em]"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
@@ -164,7 +166,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
|
||||
<Reveal delay={80}>
|
||||
<section class="flex flex-col gap-4">
|
||||
<h2
|
||||
class="font-mono text-xs uppercase tracking-[0.22em]"
|
||||
class="font-eyebrow text-xs uppercase tracking-[0.22em]"
|
||||
style="color: var(--color-secondary);"
|
||||
>
|
||||
Personal / Social
|
||||
@@ -208,7 +210,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
|
||||
<Reveal delay={120}>
|
||||
<p
|
||||
class="rounded-xl border-l-2 p-4 text-sm"
|
||||
style="border-color: var(--color-accent); background: color-mix(in srgb, var(--color-accent) 8%, transparent); color: var(--color-text-muted);"
|
||||
style="border-color: var(--color-gold); background: color-mix(in srgb, var(--color-accent) 8%, transparent); color: var(--color-text-muted);"
|
||||
>
|
||||
<strong style="color: var(--color-text);">Availability:</strong> Open to
|
||||
opportunities in AI/ML, app and cloud development, SAP BTP, and technical
|
||||
@@ -222,7 +224,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
|
||||
<Reveal delay={100}>
|
||||
<section class="card flex flex-col gap-5">
|
||||
<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
|
||||
</h2>
|
||||
<p class="text-sm" style="color: var(--color-text-muted);">
|
||||
@@ -239,7 +241,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
|
||||
<Reveal>
|
||||
<blockquote
|
||||
class="mx-auto max-w-2xl border-l-2 pl-5 text-center font-sans text-lg italic"
|
||||
style="border-color: var(--color-accent); color: var(--color-text-muted);"
|
||||
style="border-color: var(--color-gold); color: var(--color-text-muted);"
|
||||
>
|
||||
Whether you want to talk cloud architecture, debate the best Gundam
|
||||
series, or need a fourth for game night, reach out.
|
||||
|
||||
@@ -16,7 +16,7 @@ import Icon from "@components/Icon.astro";
|
||||
import SectionHeader from "@components/layout/SectionHeader.astro";
|
||||
import CelestialImage from "@components/mystic/CelestialImage.astro";
|
||||
import AlchemicalDivider from "@components/mystic/AlchemicalDivider.astro";
|
||||
import SacredGeometry from "@components/mystic/SacredGeometry.astro";
|
||||
import Motif from "@components/brand/Motif.astro";
|
||||
import ConstellationField from "@components/mystic/ConstellationField.tsx";
|
||||
|
||||
// Snapshot grid content from the spec, with spec emojis mapped to mdi icons.
|
||||
@@ -52,7 +52,7 @@ const cards = [
|
||||
<BaseLayout>
|
||||
<!-- ===================== HERO ===================== -->
|
||||
<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-20"
|
||||
>
|
||||
<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">
|
||||
<Reveal as="div" class="max-w-2xl grow">
|
||||
<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]"
|
||||
style="border-color: var(--color-border); color: var(--accent-contrast); background: color-mix(in srgb, var(--color-surface-raised) 75%, transparent);"
|
||||
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: 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
|
||||
class="h-2 w-2 rounded-full animate-pulse-glow"
|
||||
@@ -82,7 +82,7 @@ const cards = [
|
||||
|
||||
<blockquote
|
||||
class="mt-7 max-w-2xl border-l-2 pl-5 text-lg leading-relaxed"
|
||||
style="border-color: var(--color-accent); color: var(--text-muted);"
|
||||
style="border-color: var(--color-gold); color: var(--text-muted);"
|
||||
>
|
||||
I build things with code during the day and with resin, plastic, and
|
||||
imagination at night. I lead cloud and mobile development at Implico
|
||||
@@ -130,11 +130,11 @@ const cards = [
|
||||
/* Slow-rotating constellation field behind the section — a slice of sky. */
|
||||
}
|
||||
<ConstellationField client:idle className="opacity-60" />
|
||||
{/* Faint sacred-geometry ornament behind the section. */}
|
||||
<SacredGeometry
|
||||
figure="flower"
|
||||
class="pointer-events-none absolute -right-40 top-10 z-0 h-[40rem] w-[40rem]"
|
||||
style="color: var(--color-violet); opacity:.08;"
|
||||
{/* Faint transmutation seal behind the section (Starlight recolor). */}
|
||||
<Motif
|
||||
name="transmutation-seal-light"
|
||||
class="absolute -right-40 top-10 z-0 h-[40rem] w-[40rem]"
|
||||
style="opacity:.13;"
|
||||
/>
|
||||
<div class="container-prose relative z-10 flex flex-col gap-12">
|
||||
<Reveal>
|
||||
@@ -171,7 +171,7 @@ const cards = [
|
||||
>
|
||||
<Icon name={card.icon} class="h-6 w-6" />
|
||||
</span>
|
||||
<h3 class="font-sans" style="color: var(--color-text);">
|
||||
<h3 class="font-display" style="color: var(--color-text);">
|
||||
{card.title}
|
||||
</h3>
|
||||
<p
|
||||
@@ -197,7 +197,7 @@ const cards = [
|
||||
<Reveal>
|
||||
<AlchemicalDivider
|
||||
class="mx-auto h-6 w-full max-w-sm"
|
||||
style="color: var(--color-accent);"
|
||||
style="color: var(--color-gold);"
|
||||
/>
|
||||
</Reveal>
|
||||
|
||||
@@ -221,7 +221,7 @@ const cards = [
|
||||
</span>
|
||||
<span class="flex flex-col gap-1 px-5 py-4 sm:px-0 sm:py-0">
|
||||
<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);"
|
||||
>
|
||||
Reading & Media
|
||||
|
||||
@@ -188,7 +188,7 @@ const related = [
|
||||
<Icon name={g.icon} class="h-5 w-5" />
|
||||
</span>
|
||||
<h3
|
||||
class="font-sans text-base"
|
||||
class="font-display text-base"
|
||||
style="color: var(--color-text);"
|
||||
>
|
||||
{g.name}
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
* ---------------------------------------------------------------------------
|
||||
* 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
|
||||
* 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 Reveal from "@components/motion/Reveal.astro";
|
||||
import Icon from "@components/Icon.astro";
|
||||
import SectionHeader from "@components/layout/SectionHeader.astro";
|
||||
import AnimatedBackground from "@components/hero/AnimatedBackground.tsx";
|
||||
import SacredGeometry from "@components/mystic/SacredGeometry.astro";
|
||||
import Motif from "@components/brand/Motif.astro";
|
||||
import ConstellationField from "@components/mystic/ConstellationField.tsx";
|
||||
|
||||
const interests = [
|
||||
@@ -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."
|
||||
>
|
||||
<!-- 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. */
|
||||
}
|
||||
@@ -137,10 +137,10 @@ const interests = [
|
||||
<!-- Interest grid -->
|
||||
<section class="relative overflow-hidden py-12 sm:py-16">
|
||||
<ConstellationField client:idle className="opacity-45" tint="teal" />
|
||||
<SacredGeometry
|
||||
figure="metatron"
|
||||
class="pointer-events-none absolute -left-48 bottom-0 z-0 h-[38rem] w-[38rem]"
|
||||
style="color: var(--color-accent); opacity:.07;"
|
||||
<Motif
|
||||
name="tria-prima-array-seal-light"
|
||||
class="absolute -left-48 bottom-0 z-0 h-[38rem] w-[38rem]"
|
||||
style="opacity:.12;"
|
||||
/>
|
||||
<div class="container-prose relative z-10">
|
||||
<div class="grid gap-5 md:grid-cols-2 lg:grid-cols-3">
|
||||
@@ -176,7 +176,7 @@ const interests = [
|
||||
/>
|
||||
</div>
|
||||
<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}
|
||||
</h3>
|
||||
<p
|
||||
|
||||
@@ -58,7 +58,7 @@ const related = [
|
||||
<Icon name="robot" class="h-10 w-10" />
|
||||
</span>
|
||||
<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>
|
||||
<p class="text-sm" style="color: var(--color-text-muted);">Steel / Psychic. The iron leg Pokémon.</p>
|
||||
</div>
|
||||
|
||||
@@ -180,7 +180,7 @@ const related = [
|
||||
<Reveal delay={80}>
|
||||
<blockquote
|
||||
class="border-l-2 pl-5 text-lg leading-relaxed"
|
||||
style="border-color: var(--color-secondary); color: var(--color-text-muted);"
|
||||
style="border-color: var(--color-gold); color: var(--color-text-muted);"
|
||||
>
|
||||
Beyond RPGs, I maintain a growing board and card game collection.
|
||||
Favorites span strategy, deck-building, and social deduction. Game
|
||||
|
||||
@@ -21,7 +21,7 @@ import ProjectCard from "@components/professional/ProjectCard.astro";
|
||||
import EducationHistory from "@components/professional/EducationHistory.astro";
|
||||
import Certifications from "@components/professional/Certifications.astro";
|
||||
import AlchemicalDivider from "@components/mystic/AlchemicalDivider.astro";
|
||||
import Astrolabe from "@components/mystic/Astrolabe.astro";
|
||||
import Motif from "@components/brand/Motif.astro";
|
||||
import CircuitField from "@components/mystic/CircuitField.tsx";
|
||||
import CelestialFrame from "@components/mystic/CelestialFrame.astro";
|
||||
import ObfuscatedEmail from "@components/ObfuscatedEmail.astro";
|
||||
@@ -49,16 +49,17 @@ const projects = (await getCollection("projects")).sort(
|
||||
puts its center on the boundary: half in the hero, half over the next
|
||||
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
|
||||
don't bleed into the content below. */
|
||||
}
|
||||
<div class="pointer-events-none absolute inset-0 overflow-hidden">
|
||||
<CircuitField client:idle className="vt-sky opacity-100" />
|
||||
<Astrolabe
|
||||
class="absolute z-0 -right-48 -top-24 h-[34rem] w-[34rem] animate-spin-slow"
|
||||
style="color: var(--color-accent); opacity:.13;"
|
||||
<Motif
|
||||
name="astrolabe"
|
||||
class="absolute z-0 -right-40 -top-24 h-[38rem] w-[34rem] object-contain"
|
||||
style="opacity:.2;"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -130,7 +131,7 @@ const projects = (await getCollection("projects")).sort(
|
||||
<Reveal delay={80}>
|
||||
<blockquote
|
||||
class="border-l-2 pl-5 text-lg leading-relaxed"
|
||||
style="border-color: var(--color-accent); color: var(--color-text-muted);"
|
||||
style="border-color: var(--color-gold); color: var(--color-text-muted);"
|
||||
>
|
||||
Software developer and team lead with deep experience in cloud and
|
||||
mobile application development, currently focused on SAP-adjacent
|
||||
@@ -145,7 +146,7 @@ const projects = (await getCollection("projects")).sort(
|
||||
<Reveal delay={120}>
|
||||
<figure
|
||||
class="relative overflow-hidden rounded-2xl border"
|
||||
style="border-color: color-mix(in srgb, var(--color-accent) 35%, transparent);"
|
||||
style="border-color: color-mix(in srgb, var(--color-gold) 70%, transparent);"
|
||||
>
|
||||
<img
|
||||
src="/images/pro-workspace.jpg"
|
||||
@@ -155,8 +156,8 @@ const projects = (await getCollection("projects")).sort(
|
||||
class="aspect-[21/9] w-full object-cover"
|
||||
/>
|
||||
<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"
|
||||
style="background: color-mix(in srgb, var(--color-ink) 60%, transparent); color: var(--color-cream);"
|
||||
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-midnight) 60%, transparent); color: var(--color-starlight);"
|
||||
>
|
||||
Where the day-job work happens
|
||||
</figcaption>
|
||||
@@ -167,7 +168,7 @@ const projects = (await getCollection("projects")).sort(
|
||||
<Reveal>
|
||||
<AlchemicalDivider
|
||||
class="mx-auto h-6 w-full max-w-sm"
|
||||
style="color: var(--color-accent);"
|
||||
style="color: var(--color-gold);"
|
||||
/>
|
||||
</Reveal>
|
||||
|
||||
@@ -233,7 +234,7 @@ const projects = (await getCollection("projects")).sort(
|
||||
<Reveal delay={60}>
|
||||
<div class="flex flex-col gap-3">
|
||||
<h3
|
||||
class="font-mono text-xs uppercase tracking-[0.22em]"
|
||||
class="font-eyebrow text-xs uppercase tracking-[0.22em]"
|
||||
style="color: var(--color-accent);"
|
||||
>
|
||||
Education history
|
||||
@@ -244,7 +245,7 @@ const projects = (await getCollection("projects")).sort(
|
||||
<Reveal delay={120}>
|
||||
<div class="flex flex-col gap-3">
|
||||
<h3
|
||||
class="font-mono text-xs uppercase tracking-[0.22em]"
|
||||
class="font-eyebrow text-xs uppercase tracking-[0.22em]"
|
||||
style="color: var(--color-accent);"
|
||||
>
|
||||
Certifications
|
||||
@@ -255,7 +256,14 @@ const projects = (await getCollection("projects")).sort(
|
||||
</section>
|
||||
|
||||
{/* ===== 6. Contact ===== */}
|
||||
<section id="contact" class="scroll-mt-28 flex flex-col gap-6">
|
||||
<section id="contact" class="relative scroll-mt-28 flex flex-col gap-6">
|
||||
{/* A faint seal beside the closing call to action (the astrolabe is the
|
||||
hero's motif, so the seal sits down here rather than competing). */}
|
||||
<Motif
|
||||
name="kepler-nest-seal-light"
|
||||
class="absolute -top-20 right-0 -z-10 h-64 w-64 sm:-top-28 sm:h-96 sm:w-96"
|
||||
style="opacity:.16;"
|
||||
/>
|
||||
<Reveal>
|
||||
<SectionHeader
|
||||
kicker="06"
|
||||
@@ -275,7 +283,7 @@ const projects = (await getCollection("projects")).sort(
|
||||
/>
|
||||
<ObfuscatedEmail
|
||||
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);"
|
||||
/>
|
||||
</li>
|
||||
@@ -286,7 +294,7 @@ const projects = (await getCollection("projects")).sort(
|
||||
style="color: var(--color-accent);"
|
||||
/>
|
||||
<span
|
||||
class="font-mono text-xs uppercase tracking-[0.18em]"
|
||||
class="font-eyebrow text-xs uppercase tracking-[0.18em]"
|
||||
style="color: var(--color-accent);"
|
||||
><a
|
||||
href="https://www.linkedin.com/in/gregory-kilmer-36646bb5/"
|
||||
@@ -302,7 +310,7 @@ const projects = (await getCollection("projects")).sort(
|
||||
style="color: var(--color-accent);"
|
||||
/>
|
||||
<span
|
||||
class="font-mono text-xs uppercase tracking-[0.18em]"
|
||||
class="font-eyebrow text-xs uppercase tracking-[0.18em]"
|
||||
style="color: var(--color-accent);"
|
||||
><a
|
||||
href="https://github.com/patiun"
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
* the carousel only.
|
||||
*/
|
||||
import StoreLayout from '@layouts/StoreLayout.astro';
|
||||
import PageSeal from "@components/brand/PageSeal.astro";
|
||||
import Reveal from '@components/motion/Reveal.astro';
|
||||
import SectionHeader from '@components/layout/SectionHeader.astro';
|
||||
import Icon from '@components/Icon.astro';
|
||||
@@ -37,13 +38,14 @@ const keyReqs = requirements.filter((r) =>
|
||||
title="Proxy MTG Cards"
|
||||
description="Proxy MTG cards printed to standard size with a solid white back — clearly marked proxies for casual play. Specs and pricing."
|
||||
>
|
||||
<PageSeal name="squared-circle-seal-light" />
|
||||
<div class="container-prose flex flex-col gap-14 py-28 sm:py-32">
|
||||
{/* Header */}
|
||||
<section class="flex flex-col gap-5">
|
||||
<Reveal>
|
||||
<a
|
||||
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);"
|
||||
>
|
||||
<Icon name="arrow-left" class="h-4 w-4" />
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
* (deckBoxProduct.images). The specs block is image-free now; examples live in
|
||||
* the carousel only.
|
||||
*/
|
||||
import PageSeal from "@components/brand/PageSeal.astro";
|
||||
import StoreLayout from "@layouts/StoreLayout.astro";
|
||||
import Reveal from "@components/motion/Reveal.astro";
|
||||
import SectionHeader from "@components/layout/SectionHeader.astro";
|
||||
@@ -36,13 +37,14 @@ const keyReqs = requirements.filter(
|
||||
title="3D-Printed Deck Boxes & Counters"
|
||||
description="3D-printed deck boxes, life counters, and tokens. Specs and pricing for printed storage and trackers."
|
||||
>
|
||||
<PageSeal name="craft-seal-print-light" />
|
||||
<div class="container-prose flex flex-col gap-14 py-28 sm:py-32">
|
||||
{/* Header */}
|
||||
<section class="flex flex-col gap-5">
|
||||
<Reveal>
|
||||
<a
|
||||
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);"
|
||||
>
|
||||
<Icon name="arrow-left" class="h-4 w-4" />
|
||||
|
||||
@@ -35,7 +35,7 @@ const orderHref = "/store/order";
|
||||
{/* 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
|
||||
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" />
|
||||
|
||||
<div
|
||||
@@ -44,11 +44,11 @@ const orderHref = "/store/order";
|
||||
<div class="flex flex-col items-start gap-4">
|
||||
<Reveal>
|
||||
<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-sm tracking-[0.06em]"
|
||||
style="color: var(--color-accent);"
|
||||
>
|
||||
<Icon name="store" class="h-4 w-4" />
|
||||
store.gregkilmer.com
|
||||
Store.GregKilmer.com
|
||||
</span>
|
||||
</Reveal>
|
||||
|
||||
@@ -64,8 +64,8 @@ const orderHref = "/store/order";
|
||||
|
||||
<Reveal delay={100}>
|
||||
<h2
|
||||
class="font-sans font-bold tracking-tight"
|
||||
style="color: var(--color-cream); font-size: clamp(1.75rem, 2vw + 1rem, 2.75rem); line-height: 1.15;"
|
||||
class="font-display font-bold"
|
||||
style="color: var(--color-starlight); font-size: clamp(1.75rem, 2vw + 1rem, 2.75rem); line-height: 1.15;"
|
||||
>
|
||||
Proxy MTG cards & 3D-printed deck boxes
|
||||
</h2>
|
||||
@@ -74,7 +74,7 @@ const orderHref = "/store/order";
|
||||
<Reveal delay={140}>
|
||||
<p
|
||||
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-gold); color: var(--color-starlight); opacity: 0.92;"
|
||||
>
|
||||
Clearly-marked proxies for casual play, and matching 3D-printed
|
||||
storage & counters. Hand-made in small batches.
|
||||
@@ -92,7 +92,7 @@ const orderHref = "/store/order";
|
||||
<Reveal delay={80} class="w-full">
|
||||
<figure
|
||||
class="relative w-full overflow-hidden rounded-2xl border"
|
||||
style="border-color: color-mix(in srgb, var(--color-accent) 40%, transparent);"
|
||||
style="border-color: color-mix(in srgb, var(--color-gold) 70%, transparent);"
|
||||
>
|
||||
<img
|
||||
src="/images/proxies.jpg"
|
||||
@@ -102,8 +102,8 @@ const orderHref = "/store/order";
|
||||
class="aspect-[4/3] w-full object-cover"
|
||||
/>
|
||||
<StarBurst
|
||||
class="pointer-events-none absolute left-3 top-3 h-5 w-5 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-accent)_60%,transparent)]"
|
||||
style="color: var(--color-accent);"
|
||||
class="pointer-events-none absolute left-3 top-3 h-5 w-5 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-gold)_60%,transparent)]"
|
||||
style="color: var(--color-gold);"
|
||||
/>
|
||||
</figure>
|
||||
</Reveal>
|
||||
@@ -228,7 +228,7 @@ const orderHref = "/store/order";
|
||||
>
|
||||
<Reveal>
|
||||
<h3
|
||||
class="font-sans text-2xl font-bold"
|
||||
class="font-display text-2xl font-bold"
|
||||
style="color: var(--color-text);"
|
||||
>
|
||||
Ready to put in a request?
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
* closed, so the gate can't be bypassed by POSTing directly.
|
||||
*/
|
||||
import StoreLayout from '@layouts/StoreLayout.astro';
|
||||
import PageSeal from "@components/brand/PageSeal.astro";
|
||||
import Reveal from '@components/motion/Reveal.astro';
|
||||
import SectionHeader from '@components/layout/SectionHeader.astro';
|
||||
import Icon from '@components/Icon.astro';
|
||||
@@ -31,12 +32,13 @@ const siteKey = turnstileSiteKey();
|
||||
title="Order Request"
|
||||
description="Submit a proxy-card or 3D-print order request. No payment is taken through the site."
|
||||
>
|
||||
<PageSeal name="transmutation-seal-light" />
|
||||
<div class="container-prose flex flex-col gap-14 py-28 sm:py-32">
|
||||
<section class="flex flex-col gap-5">
|
||||
<Reveal>
|
||||
<a
|
||||
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);"
|
||||
>
|
||||
<Icon name="arrow-left" class="h-4 w-4" />
|
||||
@@ -79,7 +81,7 @@ const siteKey = turnstileSiteKey();
|
||||
{open ? 'Orders are open. Slots are limited.' : 'Orders are currently closed.'}
|
||||
</span>
|
||||
</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}
|
||||
</span>
|
||||
</div>
|
||||
@@ -91,7 +93,7 @@ const siteKey = turnstileSiteKey();
|
||||
<section class="flex flex-col gap-8 lg:grid lg:grid-cols-[1.6fr_1fr] lg:items-start">
|
||||
<Reveal>
|
||||
<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);">
|
||||
Fill in the details below. Remember: the card list is final once received, and
|
||||
double-sided cards count as 2 slots.
|
||||
@@ -118,7 +120,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">
|
||||
<Icon name="lock" class="h-6 w-6" />
|
||||
</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);">
|
||||
I open a limited number of slots at a time. Check back here — the meters above
|
||||
show live availability, and the order form appears as soon as orders reopen. You can
|
||||
|
||||
@@ -28,17 +28,28 @@
|
||||
* section wrapper (`data-surface`) instantly restyles every component.
|
||||
* ------------------------------------------------------------------------- */
|
||||
@theme {
|
||||
/* Brand colors (fixed, do not change with theme). Night-sky mystical set:
|
||||
* star gold, mystic violet, nebula teal, midnight indigo, star-white. */
|
||||
--color-amber: #e8b54a; /* star gold */
|
||||
--color-amber-soft: #f4cd72; /* brighter gold for hovers */
|
||||
--color-violet: #8b7bd8; /* mystic violet */
|
||||
--color-violet-deep: #6f5fb6;
|
||||
--color-teal: #6fb6ad; /* nebula teal */
|
||||
--color-teal-deep: #4f8c84;
|
||||
--color-slate: #161b3a; /* deep indigo-slate */
|
||||
--color-ink: #0b1026; /* midnight indigo */
|
||||
--color-cream: #f4f1e8; /* star-white */
|
||||
/* Brand corner radius: cards and panels are 10px; buttons and tags are pills. */
|
||||
--radius-xl: 10px;
|
||||
--radius-2xl: 10px;
|
||||
--radius-3xl: 10px;
|
||||
|
||||
/* Brand colors (fixed, do not change with theme). The Patiun LLC palette,
|
||||
* shared by Patiun (craft) and Greg Kilmer (web). Source of truth: the
|
||||
* Patiun brand record / Claude Design tokens. Gold is structure and sparkle
|
||||
* (stars, rules, small labels), never a large fill. Violet and indigo are
|
||||
* background light only (nebula glows, gradient ends), never text. */
|
||||
--color-teal: #14a39a; /* Patiun Teal, primary: buttons, fills, the glyph */
|
||||
--color-teal-bright: #2bc2b7; /* teal for text/links on dark grounds */
|
||||
--color-teal-deep: #0f8c84; /* teal on light grounds; hover/active icons */
|
||||
--color-slate-teal: #2d4a58; /* dark end of the Tide gradient */
|
||||
--color-gold: #c9a24a; /* Alchemist Gold, flat gold on screen */
|
||||
--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). */
|
||||
--color-surface: var(--surface);
|
||||
@@ -53,9 +64,16 @@
|
||||
--color-secondary: var(--secondary);
|
||||
|
||||
/* Typography. */
|
||||
--font-sans: "Plus Jakarta Sans Variable", "Inter Variable", ui-sans-serif,
|
||||
system-ui, sans-serif;
|
||||
--font-body: "Inter Variable", ui-sans-serif, system-ui, sans-serif;
|
||||
/* Typography. Cinzel for names, wordmarks and headings (never paragraphs);
|
||||
* Montserrat for body, UI, labels and uppercase eyebrows. JetBrains Mono is
|
||||
* 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;
|
||||
|
||||
/* Motion tokens (used across reveal + transition classes). */
|
||||
@@ -107,10 +125,11 @@
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
font-family: var(--font-sans);
|
||||
font-family: var(--font-display);
|
||||
font-weight: 700;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -131,7 +150,7 @@
|
||||
transition: color 0.2s var(--ease-out-soft);
|
||||
}
|
||||
a:hover {
|
||||
color: var(--color-amber-soft);
|
||||
color: var(--color-gold-bright);
|
||||
}
|
||||
|
||||
/* Visible, on-brand focus ring for keyboard users. */
|
||||
@@ -142,8 +161,8 @@
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--color-amber);
|
||||
color: var(--color-ink);
|
||||
background: var(--color-teal);
|
||||
color: var(--color-midnight);
|
||||
}
|
||||
|
||||
/* Better default for images: never overflow, lazy by default in components. */
|
||||
@@ -175,7 +194,7 @@
|
||||
.card {
|
||||
background-color: var(--color-surface-raised);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 1rem;
|
||||
border-radius: 10px;
|
||||
padding: 1.5rem;
|
||||
transition:
|
||||
transform 0.3s var(--ease-out-soft),
|
||||
@@ -184,7 +203,7 @@
|
||||
}
|
||||
.card-interactive:hover {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -202,14 +221,14 @@
|
||||
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 {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.6rem;
|
||||
border-radius: 999px;
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
@@ -224,14 +243,15 @@
|
||||
.btn:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
/* Filled buttons: Patiun Teal with Midnight text, never white text. */
|
||||
.btn-primary {
|
||||
background: var(--color-amber);
|
||||
color: var(--color-ink);
|
||||
box-shadow: 0 10px 30px -12px rgba(245, 158, 11, 0.7);
|
||||
background: var(--color-teal);
|
||||
color: var(--color-midnight);
|
||||
box-shadow: 0 10px 30px -12px rgba(20, 163, 154, 0.7);
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: var(--color-amber-soft);
|
||||
color: var(--color-ink);
|
||||
background: var(--color-teal-bright);
|
||||
color: var(--color-midnight);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.btn-secondary {
|
||||
@@ -246,6 +266,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* 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);
|
||||
--icon-gold: var(--color-gold);
|
||||
}
|
||||
|
||||
/* Gold star accent inside the brand icons: deep gold on Starlight (matches the
|
||||
* light icon set), flat Alchemist Gold on the night sky (.on-sky above). */
|
||||
:root {
|
||||
--icon-gold: var(--color-gold-deep);
|
||||
}
|
||||
|
||||
/* Visually hidden but available to screen readers. */
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
|
||||
@@ -5,9 +5,14 @@
|
||||
*
|
||||
* 1. Global light/dark mode -> set on <html data-theme="light|dark">
|
||||
* 2. Section surface tone -> set on <body data-surface="...">
|
||||
* data-surface="neutral" Home / About / Contact (deep night-sky)
|
||||
* data-surface="professional" Professional wing (obsidian + violet/gold)
|
||||
* data-surface="interest" Interests wing (candlelit study)
|
||||
* data-surface="neutral" Home / About / Contact (Patiun night sky)
|
||||
* data-surface="professional" Professional wing (Tide: slate-teal night)
|
||||
* 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
|
||||
* the descendant combination `html[data-theme=...] body[data-surface=...]`.
|
||||
@@ -26,93 +31,99 @@
|
||||
* 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,
|
||||
html[data-theme='light'] body[data-surface='neutral'] {
|
||||
--surface: #efe7d2;
|
||||
--surface-raised: #f7f0dc;
|
||||
--surface-sunken: #e3d9bd;
|
||||
--text: #1b1730;
|
||||
--text-muted: #4a445e;
|
||||
--text-subtle: #6b6582;
|
||||
--border: #d2c6a2;
|
||||
--accent: #7e5512;
|
||||
--accent-contrast: #f7f0dc;
|
||||
--secondary: #5e4f9e;
|
||||
--surface: #f4f2ed; /* Starlight */
|
||||
--surface-raised: #ffffff;
|
||||
--surface-sunken: #e9e5dc;
|
||||
--text: #131c2b; /* Midnight */
|
||||
--text-muted: #4a5f6b;
|
||||
--text-subtle: #5c6e78;
|
||||
--border: #d9d4c8;
|
||||
--accent: #0a726b; /* deep teal, darkened to 5.2:1 so small links pass AA */
|
||||
--accent-contrast: #f4f2ed;
|
||||
--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'] {
|
||||
--surface: #070a1c; /* near-void midnight indigo */
|
||||
--surface-raised: #11163a; /* indigo panel, violet-edged */
|
||||
--surface-sunken: #04061a; /* deep cosmic void */
|
||||
--text: #f6f4ff; /* near-white for max legibility */
|
||||
--text-muted: #d4d0f0; /* bright enough to read clearly on deep blue */
|
||||
--text-subtle: #a8a2d8; /* lifted from the old #7e79b8 for contrast */
|
||||
--border: #2e3470;
|
||||
--accent: #f0c262; /* slightly lifted gold for pop on dark */
|
||||
--accent-contrast: #070a1c;
|
||||
--secondary: #b3a4ec; /* brighter violet, easier on the eye */
|
||||
--surface: #0e1522; /* night sky */
|
||||
--surface-raised: #131c2b; /* Midnight panel */
|
||||
--surface-sunken: #0a1019; /* deeper void */
|
||||
--text: #ece9e2; /* Starlight text, 15:1 */
|
||||
--text-muted: #b9c8cf;
|
||||
--text-subtle: #9fb3bd; /* brand muted, 7.9:1 on panels */
|
||||
--border: #26344a;
|
||||
--accent: #2bc2b7; /* teal on dark, 7.7:1 */
|
||||
--accent-contrast: #0e1522;
|
||||
--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'],
|
||||
html[data-theme='dark'] body[data-surface='professional'] {
|
||||
--surface: #080a20;
|
||||
--surface-raised: #121638;
|
||||
--surface-sunken: #040517;
|
||||
--text: #f6f4ff;
|
||||
--text-muted: #cec9f2;
|
||||
--text-subtle: #a59fd6;
|
||||
--border: #2e3270;
|
||||
--accent: #f0c262;
|
||||
--accent-contrast: #080a20;
|
||||
--secondary: #b3a4ec;
|
||||
--surface: #0c1820;
|
||||
--surface-raised: #12222c;
|
||||
--surface-sunken: #081117;
|
||||
--text: #ece9e2;
|
||||
--text-muted: #b9c8cf;
|
||||
--text-subtle: #9fb3bd;
|
||||
--border: #23394a;
|
||||
--accent: #2bc2b7;
|
||||
--accent-contrast: #0c1820;
|
||||
--secondary: #d8b35a;
|
||||
}
|
||||
|
||||
html[data-theme='light'] body[data-surface='professional'] {
|
||||
--surface: #21183f;
|
||||
--surface-raised: #2c2152;
|
||||
--surface-sunken: #18113a;
|
||||
--text: #f1edff;
|
||||
--text-muted: #d5cff0;
|
||||
--text-subtle: #aaa3d6;
|
||||
--border: #3c3170;
|
||||
--accent: #7e5512;
|
||||
--accent-contrast: #f1edff;
|
||||
--secondary: #b6a6e8;
|
||||
--surface: #2d4a58; /* Slate Teal: the professional wing stays dark */
|
||||
--surface-raised: #355766;
|
||||
--surface-sunken: #243c48;
|
||||
--text: #f4f2ed;
|
||||
--text-muted: #d9e2e6;
|
||||
--text-subtle: #bccbd2;
|
||||
--border: #46687a;
|
||||
--accent: #7fdcd5;
|
||||
--accent-contrast: #131c2b;
|
||||
--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'],
|
||||
html[data-theme='dark'] body[data-surface='interest'] {
|
||||
--surface: #170f24; /* warm plum-brown night */
|
||||
--surface-raised: #221630;
|
||||
--surface-sunken: #100920;
|
||||
--text: #fbf2e4; /* warm near-white */
|
||||
--text-muted: #ecdcc6; /* lifted for contrast */
|
||||
--text-subtle: #c4ab8e; /* lifted from #a99a86 */
|
||||
--border: #3e2f4a;
|
||||
--accent: #f0c262;
|
||||
--accent-contrast: #170f24;
|
||||
--secondary: #c8abe0;
|
||||
--surface: #110f22; /* indigo night */
|
||||
--surface-raised: #1a1730;
|
||||
--surface-sunken: #0b0a18;
|
||||
--text: #ece9e2;
|
||||
--text-muted: #c3bfd6;
|
||||
--text-subtle: #a49fbd;
|
||||
--border: #2e2a52;
|
||||
--accent: #2bc2b7;
|
||||
--accent-contrast: #110f22;
|
||||
--secondary: #d8b35a;
|
||||
}
|
||||
|
||||
html[data-theme='light'] body[data-surface='interest'] {
|
||||
--surface: #f4e9cf;
|
||||
--surface-raised: #fdf4d9;
|
||||
--surface-sunken: #e8d8b2;
|
||||
--text: #22152e;
|
||||
--text-muted: #5c4733;
|
||||
--text-subtle: #826a4f;
|
||||
--border: #d8c290;
|
||||
--accent: #7e5512;
|
||||
--accent-contrast: #fdf4d9;
|
||||
--secondary: #5e4f9e;
|
||||
--surface: #f4f2ed;
|
||||
--surface-raised: #ffffff;
|
||||
--surface-sunken: #e9e5dc;
|
||||
--text: #131c2b;
|
||||
--text-muted: #4a5f6b;
|
||||
--text-subtle: #5c6e78;
|
||||
--border: #d9d4c8;
|
||||
--accent: #0f8c84;
|
||||
--accent-contrast: #131c2b;
|
||||
--secondary: #8a6516;
|
||||
}
|
||||
|
||||