Compare commits
5 Commits
5edf0c6061
..
v0.1.0
| 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
|
## Palette
|
||||||
|
|
||||||
From the spec, exposed as CSS variables in `src/styles/themes.css` and mapped to
|
The site uses the Patiun LLC brand palette (shared by Patiun and Greg Kilmer),
|
||||||
Tailwind utilities in `src/styles/global.css`:
|
exposed as CSS variables in `src/styles/global.css` under `@theme`:
|
||||||
|
|
||||||
| Token | Value | Meaning |
|
| Token | Value | Meaning |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `--color-slate` | `#1E293B` | Professional confidence (primary) |
|
| `--color-teal` | `#14A39A` | Patiun Teal, primary: buttons, fills, the glyph |
|
||||||
| `--color-amber` | `#F59E0B` | Warm accent (the making/creative side) |
|
| `--color-teal-bright` | `#2BC2B7` | Teal text and links on dark grounds |
|
||||||
| `--color-teal` | `#699a94` | Soft teal (secondary, favorite) |
|
| `--color-teal-deep` | `#0F8C84` | Teal marks on light grounds |
|
||||||
| `--color-ink` | `#0F172A` | Near-black text on light |
|
| `--color-slate-teal` | `#2D4A58` | Dark end of the Tide gradient |
|
||||||
| `--color-cream` | `#FFFBEB` | Cream-white text on dark |
|
| `--color-gold` | `#C9A24A` | Alchemist Gold: stars, rules, seal lines (never a large fill) |
|
||||||
|
| `--color-gold-bright` | `#D8B35A` | Gold text on dark grounds |
|
||||||
|
| `--color-gold-deep` | `#8A6516` | Gold text under 24px on Starlight |
|
||||||
|
| `--color-violet` / `--color-indigo` | `#6A2FE0` / `#3B1F8F` | Nebula light only, never text |
|
||||||
|
| `--color-midnight` | `#131C2B` | Panels, tiles, browser theme color |
|
||||||
|
| `--color-sky` | `#0E1522` | Night-sky ground |
|
||||||
|
| `--color-starlight` | `#F4F2ED` | Text on dark; paper ground |
|
||||||
|
|
||||||
|
The semantic Tailwind tokens (`--color-accent`, `--color-surface`, ...) resolve
|
||||||
|
at `:root`, so content sections render with the Starlight values in
|
||||||
|
`themes.css`. Night-sky bands (the nav and page heroes) carry the `.on-sky`
|
||||||
|
class, which re-points accent and gold to their on-dark values.
|
||||||
|
|
||||||
Brand colors are fixed. **Semantic** tokens (`--surface`, `--text`, `--accent`,
|
Brand colors are fixed. **Semantic** tokens (`--surface`, `--text`, `--accent`,
|
||||||
`--secondary`, `--border`, ...) resolve differently per theme + section, and are
|
`--secondary`, `--border`, ...) resolve differently per theme + section, and are
|
||||||
@@ -118,9 +129,33 @@ reduce)')` before drawing, so even JS-driven motion is skipped.
|
|||||||
|
|
||||||
Self-hosted variable fonts via Fontsource (woff2, no external requests):
|
Self-hosted variable fonts via Fontsource (woff2, no external requests):
|
||||||
|
|
||||||
- **Headings / UI:** Plus Jakarta Sans Variable (geometric sans)
|
- **Headings, names, card titles:** Cinzel Variable (`font-display`, used by
|
||||||
- **Body:** Inter Variable
|
`h1`-`h4` automatically). Open tracking, never for paragraphs.
|
||||||
- **Mono / kickers / quotes:** JetBrains Mono Variable
|
- **Body and UI:** Montserrat Variable (`font-sans`, `font-body`).
|
||||||
|
- **Eyebrows / kickers:** Montserrat uppercase with wide tracking (`font-eyebrow`).
|
||||||
|
- **Code-like blocks only:** JetBrains Mono Variable (`font-mono`).
|
||||||
|
|
||||||
Font sizes use `clamp()` for fluid scaling (see the `h1`-`h4` rules in
|
Font sizes use `clamp()` for fluid scaling (see the `h1`-`h4` rules in
|
||||||
`global.css`).
|
`global.css`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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/mdx": "^7.0.2",
|
||||||
"@astrojs/node": "^11.0.2",
|
"@astrojs/node": "^11.0.2",
|
||||||
"@astrojs/react": "^6.0.2",
|
"@astrojs/react": "^6.0.2",
|
||||||
"@fontsource-variable/inter": "^5.2.8",
|
"@fontsource-variable/cinzel": "^5.3.0",
|
||||||
"@fontsource-variable/jetbrains-mono": "^5.2.8",
|
"@fontsource-variable/jetbrains-mono": "^5.2.8",
|
||||||
"@fontsource-variable/plus-jakarta-sans": "^5.2.8",
|
"@fontsource-variable/montserrat": "^5.3.0",
|
||||||
"@iconify-json/mdi": "^1.2.3",
|
"@iconify-json/mdi": "^1.2.3",
|
||||||
"@tailwindcss/vite": "^4.3.2",
|
"@tailwindcss/vite": "^4.3.2",
|
||||||
"@types/nodemailer": "^8.0.1",
|
"@types/nodemailer": "^8.0.1",
|
||||||
@@ -1389,10 +1389,10 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fontsource-variable/inter": {
|
"node_modules/@fontsource-variable/cinzel": {
|
||||||
"version": "5.3.0",
|
"version": "5.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource-variable/cinzel/-/cinzel-5.3.0.tgz",
|
||||||
"integrity": "sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==",
|
"integrity": "sha512-eFcU0TwaAP5QESF2pV8VoOPJJXJ6sSGm/6mXMwqTdMBPAJMaLhfa6oISHuOtFeddsoHP9RYm4Io0j1j3mNEz2Q==",
|
||||||
"license": "OFL-1.1",
|
"license": "OFL-1.1",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ayuhito"
|
"url": "https://github.com/sponsors/ayuhito"
|
||||||
@@ -1407,10 +1407,10 @@
|
|||||||
"url": "https://github.com/sponsors/ayuhito"
|
"url": "https://github.com/sponsors/ayuhito"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fontsource-variable/plus-jakarta-sans": {
|
"node_modules/@fontsource-variable/montserrat": {
|
||||||
"version": "5.3.0",
|
"version": "5.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/plus-jakarta-sans/-/plus-jakarta-sans-5.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource-variable/montserrat/-/montserrat-5.3.0.tgz",
|
||||||
"integrity": "sha512-/l/4r0yyWK9JzAlmA0LiYgGgmJe/Gswt4jTJEzr5QhJfwMbvJZDmYWyW0M4X7yCK69BajMVlV/Lx8g7WDc1+sw==",
|
"integrity": "sha512-7PaZoxaxrWLAyrhO46v65An9LhUhfkTExWLhfbywYZCnZEgg/W1rEHnlNmZKjNZ3nJTVYyicqxlJt10z/26yTA==",
|
||||||
"license": "OFL-1.1",
|
"license": "OFL-1.1",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ayuhito"
|
"url": "https://github.com/sponsors/ayuhito"
|
||||||
|
|||||||
@@ -19,9 +19,9 @@
|
|||||||
"@astrojs/mdx": "^7.0.2",
|
"@astrojs/mdx": "^7.0.2",
|
||||||
"@astrojs/node": "^11.0.2",
|
"@astrojs/node": "^11.0.2",
|
||||||
"@astrojs/react": "^6.0.2",
|
"@astrojs/react": "^6.0.2",
|
||||||
"@fontsource-variable/inter": "^5.2.8",
|
"@fontsource-variable/cinzel": "^5.3.0",
|
||||||
"@fontsource-variable/jetbrains-mono": "^5.2.8",
|
"@fontsource-variable/jetbrains-mono": "^5.2.8",
|
||||||
"@fontsource-variable/plus-jakarta-sans": "^5.2.8",
|
"@fontsource-variable/montserrat": "^5.3.0",
|
||||||
"@iconify-json/mdi": "^1.2.3",
|
"@iconify-json/mdi": "^1.2.3",
|
||||||
"@tailwindcss/vite": "^4.3.2",
|
"@tailwindcss/vite": "^4.3.2",
|
||||||
"@types/nodemailer": "^8.0.1",
|
"@types/nodemailer": "^8.0.1",
|
||||||
|
|||||||
|
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">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><linearGradient id="fgk" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#131C2B"/><stop offset="1" stop-color="#1F3B49"/></linearGradient></defs><rect width="64" height="64" rx="14" fill="url(#fgk)"/><circle cx="32" cy="32" r="26" fill="none" stroke="#14A39A" stroke-width="2.4" /><g fill="#F4F2ED" aria-label="GK"><path d="M754.5337524414062 280V115Q730.9266357421875 85 683.427001953125 55.0Q635.9273681640625 25 569.7135009765625 5.5Q503.4996337890625 -14 422.6427001953125 -14Q309.3572998046875 -13.60711669921875 224.41104125976562 30.67864990234375Q139.46478271484375 74.96441650390625 92.1434326171875 156.76797485351562Q44.82208251953125 238.571533203125 44.82208251953125 351.2142333984375Q44.82208251953125 462.6427001953125 91.53631591796875 544.0355834960938Q138.25054931640625 625.428466796875 224.714599609375 669.7142333984375Q311.17864990234375 714 428.7138671875 714Q483.7138671875 714 535.9099426269531 706.5177917480469Q588.1060180664062 699.0355834960938 632.1056518554688 686.8747253417969Q676.1052856445312 674.7138671875 705.1052856445312 659.3565673828125L706.8917846679688 488.000732421875H697.8917846679688Q679.2490844726562 556.3217163085938 643.2312927246094 596.4288330078125Q607.2135009765625 636.5359497070312 559.7668762207031 654.1968078613281Q512.3202514648438 671.857666015625 459.0697021484375 671.857666015625Q377.4981689453125 671.857666015625 321.39068603515625 632.2861328125Q265.283203125 592.714599609375 236.568603515625 519.8395080566406Q207.85400390625 446.96441650390625 207.85400390625 345.14306640625Q207.85400390625 248.7857666015625 234.44369506835938 177.428466796875Q261.03338623046875 106.0711669921875 311.9803771972656 67.30319213867188Q362.9273681640625 28.53521728515625 435.999267578125 28.142333984375Q473.32098388671875 28.142333984375 507.4462585449219 39.553009033203125Q541.571533203125 50.96368408203125 567.5541076660156 73.19570922851562Q593.5366821289062 95.427734375 604.5374145507812 127.28466796875L605.5374145507812 280Q605.5374145507812 325 554.5374145507812 325H538.5374145507812V335H819.5337524414062V325H804.5337524414062Q752.5337524414062 325 754.5337524414062 280Z" transform="translate(12.09,41.50) scale(0.02500,-0.02500)"/><path d="M412.851806640625 430.53375244140625 674.99560546875 72.24908447265625Q697.8169555664062 41.9281005859375 722.0489807128906 26.46405029296875Q746.281005859375 11 777.281005859375 10V0H694.8184204101562Q684.2113037109375 0 658.7294616699219 1.196441650390625Q633.2476196289062 2.39288330078125 601.1230773925781 9.410675048828125Q568.99853515625 16.428466796875 536.5704345703125 33.696075439453125Q504.142333984375 50.96368408203125 479.85693359375 83.10601806640625Q475 89.7843017578125 460.2320251464844 109.87362670898438Q445.46405029296875 129.96295166015625 425.24945068359375 157.80245971679688Q405.03485107421875 185.6419677734375 383.3380432128906 215.55300903320312Q361.6412353515625 245.46405029296875 343.04561360677087 271.22924296061194Q324.4499918619792 296.9944356282552 312.61641438802087 313.3904673258463Q300.7828369140625 329.7864990234375 300.7828369140625 329.7864990234375ZM663.6034545898438 693 362.7821044921875 335.857666015625 300.7828369140625 329.7864990234375 575.7828369140625 693ZM733.8176879882812 700V690.8438842773437H729.8176879882812Q696.8176879882812 690.8438842773437 668.3176879882812 677.6183837890625Q639.8176879882812 664.3928833007812 619.8176879882812 642L538.6041870117188 644Q550.9970703125 661 543.1041870117188 676.0Q535.2113037109375 691 514.8065592447916 691H504.60418701171875V700H729.8176879882812ZM268.7828369140625 700V0H118.7864990234375V700ZM121.7864990234375 73V0H41.7864990234375V10Q43.000732421875 10 48.590057373046875 10.0Q54.17938232421875 10 54.7864990234375 10Q80.7864990234375 10 99.2864990234375 28.5Q117.7864990234375 47 118.7864990234375 73ZM121.7864990234375 627H118.7864990234375Q117.7864990234375 653 99.2864990234375 671.5Q80.7864990234375 690 54.7864990234375 690Q54.17938232421875 690 48.590057373046875 690.0Q43.000732421875 690 41.7864990234375 690V700H121.7864990234375ZM265.7828369140625 73H268.7828369140625Q269.7828369140625 47 288.2828369140625 28.5Q306.7828369140625 10 332.7828369140625 10Q333.9970703125 10 339.43665489783655 10.0Q344.8762394831731 10 345.7828369140625 10V0H265.7828369140625ZM265.7828369140625 627V700H345.7828369140625V690Q344.7828369140625 690 339.38995361328125 690.0Q333.9970703125 690 332.7828369140625 690Q306.7828369140625 690 288.2828369140625 671.5Q269.7828369140625 653 268.7828369140625 627Z" transform="translate(32.99,41.50) scale(0.02500,-0.02500)"/></g><circle cx="32" cy="6" r="4" fill="#131C2B"/><polygon points="32.00,0.80 33.10,4.90 37.20,6.00 33.10,7.10 32.00,11.20 30.90,7.10 26.80,6.00 30.90,4.90" fill="#C9A24A"/></svg>
|
||||||
<!-- Greg Kilmer mark: a stylized "G" over a warm amber field with slate. -->
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
||||||
<stop offset="0" stop-color="#1e293b"/>
|
|
||||||
<stop offset="1" stop-color="#0f172a"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<rect width="64" height="64" rx="14" fill="url(#bg)"/>
|
|
||||||
<!-- Amber accent arc -->
|
|
||||||
<circle cx="32" cy="32" r="20" fill="none" stroke="#f59e0b" stroke-width="4" stroke-dasharray="94 200" stroke-linecap="round" transform="rotate(-30 32 32)" opacity="0.85"/>
|
|
||||||
<!-- G glyph -->
|
|
||||||
<path d="M40 26a12 12 0 1 0 0 12" fill="none" stroke="#fffbeb" stroke-width="5" stroke-linecap="round"/>
|
|
||||||
<line x1="32" y1="32" x2="42" y2="32" stroke="#fffbeb" stroke-width="5" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 846 B After Width: | Height: | Size: 4.7 KiB |
|
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
|
* Icon.astro
|
||||||
* ---------------------------------------------------------------------------
|
* ---------------------------------------------------------------------------
|
||||||
* Inline mdi (Material Design Icons) renderer. Pulls icon path data at build
|
* Inline icon renderer. Names resolve to the Patiun brand icon set first
|
||||||
* time from `@iconify-json/mdi/icons.json` and emits a single inline <svg>, so
|
* (src/lib/brand-icons.ts: stroke icons with a gold star accent), via either a
|
||||||
* there is no runtime icon library and no extra requests. The svg inherits
|
* brand name ("dice") or the mdi name the call sites already use ("dice-multiple").
|
||||||
* `currentColor`, so color is controlled via text-* utilities or inline style.
|
* 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
|
||||||
* The spec calls for mdi icons everywhere instead of emojis; all spec emoji
|
* output is one inline <svg> that inherits `currentColor`.
|
||||||
* references are mapped to mdi names at the call sites.
|
|
||||||
*
|
*
|
||||||
* Usage: <Icon name="briefcase" class="h-6 w-6" />
|
* Usage: <Icon name="briefcase" class="h-6 w-6" />
|
||||||
*/
|
*/
|
||||||
import mdi from '@iconify-json/mdi/icons.json';
|
import mdi from '@iconify-json/mdi/icons.json';
|
||||||
|
import { brandIconBody } from '@lib/brand-icons';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** mdi icon name WITHOUT prefix, e.g. "briefcase". */
|
/** mdi icon name WITHOUT prefix, e.g. "briefcase". */
|
||||||
@@ -27,8 +27,11 @@ interface Props {
|
|||||||
|
|
||||||
const { name, title, class: className, size, style } = Astro.props;
|
const { name, title, class: className, size, style } = Astro.props;
|
||||||
|
|
||||||
const icon = (mdi as { icons: Record<string, { body: string }> }).icons[name];
|
const brand = brandIconBody(name);
|
||||||
if (!icon) {
|
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.
|
// Surface missing-icon names during dev so typos are caught immediately.
|
||||||
console.warn(`[Icon] unknown mdi icon: "${name}"`);
|
console.warn(`[Icon] unknown mdi icon: "${name}"`);
|
||||||
}
|
}
|
||||||
@@ -39,9 +42,14 @@ if (!icon) {
|
|||||||
width={size ?? '1em'}
|
width={size ?? '1em'}
|
||||||
height={size ?? '1em'}
|
height={size ?? '1em'}
|
||||||
viewBox="0 0 24 24"
|
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}
|
class={className}
|
||||||
style={style}
|
style={style}
|
||||||
aria-hidden={title ? undefined : 'true'}
|
aria-hidden={title ? undefined : 'true'}
|
||||||
role={title ? 'img' : undefined}
|
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">
|
<div class="flex flex-col gap-2 p-4">
|
||||||
<h3
|
<h3
|
||||||
class="font-sans text-base leading-tight underline-offset-4 group-hover:underline"
|
class="font-display text-base leading-tight underline-offset-4 group-hover:underline"
|
||||||
style="color: var(--color-text);"
|
style="color: var(--color-text);"
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
</h3>
|
</h3>
|
||||||
<div
|
<div
|
||||||
class="flex flex-wrap items-center gap-2 font-mono text-[0.65rem] uppercase tracking-[0.16em]"
|
class="flex flex-wrap items-center gap-2 font-eyebrow text-[0.65rem] uppercase tracking-[0.16em]"
|
||||||
style="color: var(--color-text-muted);"
|
style="color: var(--color-text-muted);"
|
||||||
>
|
>
|
||||||
{formatLabel && <span>{formatLabel}</span>}
|
{formatLabel && <span>{formatLabel}</span>}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ const alt = artAlt ?? title;
|
|||||||
</span>
|
</span>
|
||||||
{year && (
|
{year && (
|
||||||
<span
|
<span
|
||||||
class="font-mono text-[0.65rem] uppercase tracking-[0.18em]"
|
class="font-eyebrow text-[0.65rem] uppercase tracking-[0.18em]"
|
||||||
style="color: var(--color-text-muted);"
|
style="color: var(--color-text-muted);"
|
||||||
>
|
>
|
||||||
{year}
|
{year}
|
||||||
@@ -75,7 +75,7 @@ const alt = artAlt ?? title;
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="font-sans text-lg" style="color: var(--color-text);">{title}</h3>
|
<h3 class="font-display text-lg" style="color: var(--color-text);">{title}</h3>
|
||||||
|
|
||||||
<p class="grow text-sm leading-relaxed" style="color: var(--color-text-muted);">
|
<p class="grow text-sm leading-relaxed" style="color: var(--color-text-muted);">
|
||||||
{blurb}
|
{blurb}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const { icon, title, status, currently = false, class: className = '' } = Astro.
|
|||||||
<Icon name={icon} class="h-6 w-6" />
|
<Icon name={icon} class="h-6 w-6" />
|
||||||
</span>
|
</span>
|
||||||
{currently && (
|
{currently && (
|
||||||
<span class="inline-flex items-center gap-1.5 font-mono text-[0.65rem] uppercase tracking-[0.18em]" style="color: var(--color-accent);">
|
<span class="inline-flex items-center gap-1.5 font-eyebrow text-[0.65rem] uppercase tracking-[0.18em]" style="color: var(--color-accent);">
|
||||||
<span class="h-2 w-2 rounded-full animate-pulse-glow" style="background: var(--color-accent);"></span>
|
<span class="h-2 w-2 rounded-full animate-pulse-glow" style="background: var(--color-accent);"></span>
|
||||||
Active
|
Active
|
||||||
</span>
|
</span>
|
||||||
@@ -50,7 +50,7 @@ const { icon, title, status, currently = false, class: className = '' } = Astro.
|
|||||||
<span class="pill">{status}</span>
|
<span class="pill">{status}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<h3 class="font-sans text-lg" style="color: var(--color-text);">{title}</h3>
|
<h3 class="font-display text-lg" style="color: var(--color-text);">{title}</h3>
|
||||||
<div class="grow text-sm leading-relaxed" style="color: var(--color-text-muted);">
|
<div class="grow text-sm leading-relaxed" style="color: var(--color-text-muted);">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ const detail = [year ? String(year) : null, count].filter(Boolean).join(' \u00b7
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2 p-4">
|
<div class="flex flex-col gap-2 p-4">
|
||||||
<div class="flex items-start justify-between gap-2">
|
<div class="flex items-start justify-between gap-2">
|
||||||
<h3 class="font-sans text-lg leading-tight" style="color: var(--color-text);">
|
<h3 class="font-display text-lg leading-tight" style="color: var(--color-text);">
|
||||||
{title}
|
{title}
|
||||||
</h3>
|
</h3>
|
||||||
{status && <span class="pill shrink-0">{status}</span>}
|
{status && <span class="pill shrink-0">{status}</span>}
|
||||||
@@ -89,7 +89,7 @@ const detail = [year ? String(year) : null, count].filter(Boolean).join(' \u00b7
|
|||||||
{
|
{
|
||||||
detail && (
|
detail && (
|
||||||
<span
|
<span
|
||||||
class="font-mono text-[0.65rem] uppercase tracking-[0.18em]"
|
class="font-eyebrow text-[0.65rem] uppercase tracking-[0.18em]"
|
||||||
style="color: var(--color-text-muted);"
|
style="color: var(--color-text-muted);"
|
||||||
>
|
>
|
||||||
{detail}
|
{detail}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
* 1. A CSS-driven night-sky gradient (deep blue, darker at top → lighter near
|
* 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
|
* horizon) plus a diagonal Milky Way galaxy band and teal/violet nebula
|
||||||
* clouds. GPU-friendly, animated via `animate-gradient-shift` keyframes.
|
* clouds. GPU-friendly, animated via `animate-gradient-shift` keyframes.
|
||||||
* 2. A faint static SVG ornament layer (astrolabe + sacred geometry) for
|
* 2. The Patiun layered magic seal, faint and slowly turning in the top-left
|
||||||
* esoteric texture.
|
* corner (the surface's one focal motif).
|
||||||
* 3. An optional <canvas> star field: a dense field of slowly-twinkling stars
|
* 3. An optional <canvas> star field: a dense field of slowly-twinkling stars
|
||||||
* with thin constellation lines between near neighbors, plus the occasional
|
* with thin constellation lines between near neighbors, plus the occasional
|
||||||
* shooting star. The WHOLE field rotates together very slowly around the
|
* 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.
|
// Resolve star colors from CSS variables so the field matches the theme.
|
||||||
const gold =
|
const gold =
|
||||||
getComputedStyle(document.documentElement).getPropertyValue('--color-amber').trim() ||
|
getComputedStyle(document.documentElement).getPropertyValue('--color-gold').trim() ||
|
||||||
'#e8b54a';
|
'#c9a24a';
|
||||||
const violet =
|
// Nebula Violet is background light only, so violet stars stay sparse; teal stars use the on-dark teal so they read at dot size.
|
||||||
getComputedStyle(document.documentElement).getPropertyValue('--color-violet').trim() ||
|
const violet = '#6a2fe0';
|
||||||
'#8b7bd8';
|
|
||||||
const teal =
|
const teal =
|
||||||
getComputedStyle(document.documentElement).getPropertyValue('--color-teal').trim() ||
|
getComputedStyle(document.documentElement).getPropertyValue('--color-teal-bright').trim() ||
|
||||||
'#6fb6ad';
|
'#2bc2b7';
|
||||||
const white =
|
const white =
|
||||||
getComputedStyle(document.documentElement).getPropertyValue('--color-cream').trim() ||
|
getComputedStyle(document.documentElement).getPropertyValue('--color-starlight').trim() ||
|
||||||
'#f4f1e8';
|
'#f4f2ed';
|
||||||
|
|
||||||
const hueColor = (h: Star['hue']) => (h === 'gold' ? gold : h === 'violet' ? violet : h === 'teal' ? teal : white);
|
const hueColor = (h: Star['hue']) => (h === 'gold' ? gold : h === 'violet' ? violet : h === 'teal' ? teal : white);
|
||||||
|
|
||||||
@@ -214,7 +213,7 @@ export default function AnimatedBackground({
|
|||||||
const proximity = 1 - Math.sqrt(d2) / LINK_DIST;
|
const proximity = 1 - Math.sqrt(d2) / LINK_DIST;
|
||||||
const pulse = 0.5 + 0.5 * Math.sin(now / 1400 + (i + j) * 0.7);
|
const pulse = 0.5 + 0.5 * Math.sin(now / 1400 + (i + j) * 0.7);
|
||||||
const alpha = proximity * 0.22 * (0.55 + 0.45 * pulse);
|
const alpha = proximity * 0.22 * (0.55 + 0.45 * pulse);
|
||||||
ctx.strokeStyle = `rgba(232, 181, 74, ${alpha.toFixed(3)})`;
|
ctx.strokeStyle = `rgba(201, 162, 74, ${alpha.toFixed(3)})`;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(A.x, A.y);
|
ctx.moveTo(A.x, A.y);
|
||||||
ctx.lineTo(B.x, B.y);
|
ctx.lineTo(B.x, B.y);
|
||||||
@@ -270,8 +269,8 @@ export default function AnimatedBackground({
|
|||||||
const grad = ctx.createLinearGradient(sh.x, sh.y, tailX, tailY);
|
const grad = ctx.createLinearGradient(sh.x, sh.y, tailX, tailY);
|
||||||
const headA = Math.max(0, Math.min(1, sh.life));
|
const headA = Math.max(0, Math.min(1, sh.life));
|
||||||
grad.addColorStop(0, `rgba(244, 241, 232, ${headA.toFixed(3)})`);
|
grad.addColorStop(0, `rgba(244, 241, 232, ${headA.toFixed(3)})`);
|
||||||
grad.addColorStop(0.4, `rgba(232, 181, 74, ${(headA * 0.6).toFixed(3)})`);
|
grad.addColorStop(0.4, `rgba(201, 162, 74, ${(headA * 0.6).toFixed(3)})`);
|
||||||
grad.addColorStop(1, 'rgba(232, 181, 74, 0)');
|
grad.addColorStop(1, 'rgba(201, 162, 74, 0)');
|
||||||
ctx.strokeStyle = grad;
|
ctx.strokeStyle = grad;
|
||||||
ctx.lineWidth = 1.6;
|
ctx.lineWidth = 1.6;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
@@ -332,7 +331,7 @@ export default function AnimatedBackground({
|
|||||||
className="absolute inset-0"
|
className="absolute inset-0"
|
||||||
style={{
|
style={{
|
||||||
background:
|
background:
|
||||||
'linear-gradient(180deg, #04061a 0%, #070b24 32%, #0a1230 58%, #0e1a3e 82%, #12224a 100%)',
|
'linear-gradient(180deg, #080c14 0%, #0b111c 32%, #0e1522 58%, #131c2b 82%, #1a2a3a 100%)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* Milky Way galaxy band — a soft diagonal luminous streak with a denser
|
{/* Milky Way galaxy band — a soft diagonal luminous streak with a denser
|
||||||
@@ -341,10 +340,10 @@ export default function AnimatedBackground({
|
|||||||
className="animate-gradient-shift absolute inset-0 opacity-90"
|
className="animate-gradient-shift absolute inset-0 opacity-90"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `
|
backgroundImage: `
|
||||||
radial-gradient(38% 70% at 68% 38%, rgba(111, 182, 173, 0.22), transparent 62%),
|
radial-gradient(38% 70% at 68% 38%, rgba(20, 163, 154, 0.22), transparent 62%),
|
||||||
radial-gradient(30% 55% at 72% 44%, rgba(139, 123, 216, 0.26), transparent 60%),
|
radial-gradient(30% 55% at 72% 44%, rgba(106, 47, 224, 0.26), transparent 60%),
|
||||||
radial-gradient(22% 40% at 76% 50%, rgba(232, 181, 74, 0.12), transparent 60%),
|
radial-gradient(22% 40% at 76% 50%, rgba(201, 162, 74, 0.12), transparent 60%),
|
||||||
linear-gradient(115deg, transparent 30%, rgba(236, 233, 255, 0.06) 45%, rgba(236, 233, 255, 0.09) 52%, rgba(236, 233, 255, 0.05) 60%, transparent 72%)
|
linear-gradient(115deg, transparent 30%, rgba(236, 233, 226, 0.06) 45%, rgba(236, 233, 226, 0.09) 52%, rgba(236, 233, 226, 0.05) 60%, transparent 72%)
|
||||||
`,
|
`,
|
||||||
backgroundSize: '200% 200%',
|
backgroundSize: '200% 200%',
|
||||||
}}
|
}}
|
||||||
@@ -354,67 +353,29 @@ export default function AnimatedBackground({
|
|||||||
className="absolute inset-0"
|
className="absolute inset-0"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `
|
backgroundImage: `
|
||||||
radial-gradient(45% 55% at 12% 12%, rgba(139, 123, 216, 0.18), transparent 60%),
|
radial-gradient(45% 55% at 12% 12%, rgba(106, 47, 224, 0.18), transparent 60%),
|
||||||
radial-gradient(40% 50% at 88% 88%, rgba(111, 182, 173, 0.16), transparent 62%)
|
radial-gradient(40% 50% at 88% 88%, rgba(20, 163, 154, 0.16), transparent 62%)
|
||||||
`,
|
`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* Faint astrolabe + sacred-geometry ornaments for esoteric texture. */}
|
{/* The layered magic seal, turning slowly in the top-left corner: the
|
||||||
<svg
|
one focal motif on the night sky. Its ring text and symbols are
|
||||||
className="absolute -left-40 -top-40 h-[34rem] w-[34rem] animate-spin-slow text-(--color-amber) opacity-[0.11]"
|
outlined to paths (public/motifs/), so no symbol font is needed. */}
|
||||||
viewBox="0 0 100 100"
|
<img
|
||||||
fill="none"
|
src="/motifs/layered-seal.svg"
|
||||||
stroke="currentColor"
|
alt=""
|
||||||
strokeWidth="0.5"
|
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
decoding="async"
|
||||||
<circle cx="50" cy="50" r="49" strokeWidth="0.8" />
|
draggable={false}
|
||||||
<circle cx="50" cy="50" r="42" />
|
className="pointer-events-none absolute -left-40 -top-40 h-[34rem] w-[34rem] animate-spin-slow select-none opacity-[0.2]"
|
||||||
<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}
|
|
||||||
/>
|
/>
|
||||||
);
|
|
||||||
})}
|
|
||||||
</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. */}
|
{/* Subtle vignette for text legibility. */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0"
|
className="absolute inset-0"
|
||||||
style={{
|
style={{
|
||||||
background:
|
background:
|
||||||
'radial-gradient(120% 80% at 50% 30%, transparent 40%, rgba(4, 6, 26, 0.7) 100%)',
|
'radial-gradient(120% 80% at 50% 30%, transparent 40%, rgba(8, 12, 20, 0.7) 100%)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* Canvas star field (skipped under reduced motion). */}
|
{/* Canvas star field (skipped under reduced motion). */}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
* Footer.astro
|
* Footer.astro
|
||||||
* ---------------------------------------------------------------------------
|
* ---------------------------------------------------------------------------
|
||||||
* Minimal footer on a deep night-sky surface: a faint static starfield accent,
|
* Minimal footer on a deep night-sky surface: a faint static starfield accent,
|
||||||
* a StarBurst mark beside the name, social links (mdi icons), copyright, and
|
* the Patiun 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.
|
* the spec microcopy "Built with intention. Powered by caffeine." No emojis.
|
||||||
*/
|
*/
|
||||||
import Icon from '@components/Icon.astro';
|
import Icon from '@components/Icon.astro';
|
||||||
import StarBurst from '@components/mystic/StarBurst.astro';
|
|
||||||
import StarField from '@components/mystic/StarField.astro';
|
import StarField from '@components/mystic/StarField.astro';
|
||||||
import { socials, footerMicrocopy, siteIdentity } from '@lib/site';
|
import { socials, footerMicrocopy, siteIdentity } from '@lib/site';
|
||||||
const year = new Date().getFullYear();
|
const year = new Date().getFullYear();
|
||||||
@@ -16,7 +16,7 @@ const socialList = Object.values(socials);
|
|||||||
|
|
||||||
<footer
|
<footer
|
||||||
class="relative overflow-hidden border-t"
|
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. */}
|
{/* Faint static starfield — carries the night-sky treatment into the footer. */}
|
||||||
<StarField
|
<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="container-prose relative z-10 flex flex-col items-center gap-6 py-10 text-center sm:flex-row sm:justify-between sm:text-left">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<StarBurst class="h-5 w-5 shrink-0" style="color: var(--color-accent);" />
|
<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">
|
<div class="flex flex-col gap-1">
|
||||||
<p class="font-sans text-sm font-semibold" style="color: var(--color-text);">
|
<p class="font-display text-sm font-bold uppercase tracking-[0.14em]" style="color: var(--color-text);">
|
||||||
{siteIdentity.name}
|
{siteIdentity.name}
|
||||||
</p>
|
</p>
|
||||||
<p class="font-mono text-xs italic" style="color: var(--color-text-subtle);">
|
<p class="font-eyebrow text-xs italic" style="color: var(--color-text-subtle);">
|
||||||
{footerMicrocopy}
|
{footerMicrocopy}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -56,6 +56,6 @@ const socialList = Object.values(socials);
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="container-prose relative z-10 pb-8 text-center text-xs" style="color: var(--color-text-subtle);">
|
<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>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* Milky Way field: a back-to-hub breadcrumb, a small icon mark, the title, and
|
* Milky Way field: a back-to-hub breadcrumb, a small icon mark, the title, and
|
||||||
* the italic tagline.
|
* the italic tagline.
|
||||||
*
|
*
|
||||||
* Tuned for the dark starfield: text uses bright cream tones (not the dim
|
* Tuned for the dark starfield: text uses bright Starlight tones (not the dim
|
||||||
* --color-text-subtle/--color-text which vanish on the deep sky). The title is
|
* --color-text-subtle/--color-text which vanish on the deep sky). The title is
|
||||||
* h2-sized (the page is a sub-page, not Home) so it doesn't dominate. An
|
* h2-sized (the page is a sub-page, not Home) so it doesn't dominate. An
|
||||||
* optional banner image sits above the heading.
|
* optional banner image sits above the heading.
|
||||||
@@ -39,7 +39,7 @@ const {
|
|||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class="relative overflow-hidden pb-8 pt-28 sm:pt-32">
|
<section class="on-sky relative overflow-hidden pb-8 pt-28 sm:pt-32">
|
||||||
<!-- Full animated Milky Way field + stars (matches the Home/Interests hero).
|
<!-- Full animated Milky Way field + stars (matches the Home/Interests hero).
|
||||||
Carries the per-page morph name (one .vt-sky per page snapshot). -->
|
Carries the per-page morph name (one .vt-sky per page snapshot). -->
|
||||||
<AnimatedBackground client:idle className="vt-sky" />
|
<AnimatedBackground client:idle className="vt-sky" />
|
||||||
@@ -54,8 +54,8 @@ const {
|
|||||||
<Reveal>
|
<Reveal>
|
||||||
<a
|
<a
|
||||||
href="/interests"
|
href="/interests"
|
||||||
class="inline-flex items-center gap-1.5 font-mono text-xs uppercase tracking-[0.18em] transition-colors"
|
class="inline-flex items-center gap-1.5 font-eyebrow text-xs uppercase tracking-[0.18em] transition-colors"
|
||||||
style="color: var(--color-cream); opacity: 0.8;"
|
style="color: var(--color-starlight); opacity: 0.8;"
|
||||||
>
|
>
|
||||||
<Icon name="arrow-left" class="h-4 w-4" />
|
<Icon name="arrow-left" class="h-4 w-4" />
|
||||||
{eyebrow}
|
{eyebrow}
|
||||||
@@ -74,8 +74,8 @@ const {
|
|||||||
|
|
||||||
<Reveal delay={100}>
|
<Reveal delay={100}>
|
||||||
<h2
|
<h2
|
||||||
class="font-sans font-bold tracking-tight"
|
class="font-display font-bold"
|
||||||
style="color: var(--color-cream); font-size: clamp(1.75rem, 2vw + 1rem, 2.5rem); line-height: 1.15;"
|
style="color: var(--color-starlight); font-size: clamp(1.75rem, 2vw + 1rem, 2.5rem); line-height: 1.15;"
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
@@ -84,7 +84,7 @@ const {
|
|||||||
<Reveal delay={140}>
|
<Reveal delay={140}>
|
||||||
<p
|
<p
|
||||||
class="max-w-xl border-l-2 pl-4 font-sans text-base italic sm:text-lg"
|
class="max-w-xl border-l-2 pl-4 font-sans text-base italic sm:text-lg"
|
||||||
style="border-color: var(--color-accent); color: var(--color-cream); opacity: 0.92;"
|
style="border-color: var(--color-gold); color: var(--color-starlight); opacity: 0.92;"
|
||||||
>
|
>
|
||||||
{tagline}
|
{tagline}
|
||||||
</p>
|
</p>
|
||||||
@@ -97,7 +97,7 @@ const {
|
|||||||
<Reveal delay={80} class="w-full">
|
<Reveal delay={80} class="w-full">
|
||||||
<figure
|
<figure
|
||||||
class="relative w-full overflow-hidden rounded-2xl border"
|
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
|
<img
|
||||||
src={image}
|
src={image}
|
||||||
@@ -107,8 +107,8 @@ const {
|
|||||||
class="aspect-[4/3] w-full object-cover"
|
class="aspect-[4/3] w-full object-cover"
|
||||||
/>
|
/>
|
||||||
<StarBurst
|
<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)]"
|
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-accent);"
|
style="color: var(--color-gold);"
|
||||||
/>
|
/>
|
||||||
</figure>
|
</figure>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const { label, hint, class: className = '' } = Astro.props;
|
|||||||
<Icon name="pencil" class="h-4 w-4" />
|
<Icon name="pencil" class="h-4 w-4" />
|
||||||
</span>
|
</span>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<span class="font-mono text-[0.65rem] font-bold uppercase tracking-[0.18em]" style="color: var(--color-accent);">
|
<span class="font-eyebrow text-[0.65rem] font-bold uppercase tracking-[0.18em]" style="color: var(--color-accent);">
|
||||||
TODO · {label}
|
TODO · {label}
|
||||||
</span>
|
</span>
|
||||||
{hint && <p class="text-sm" style="color: var(--color-text-muted);">{hint}</p>}
|
{hint && <p class="text-sm" style="color: var(--color-text-muted);">{hint}</p>}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const { items } = Astro.props;
|
|||||||
---
|
---
|
||||||
|
|
||||||
<section class="mt-16">
|
<section class="mt-16">
|
||||||
<h3 class="mb-5 font-mono text-xs uppercase tracking-[0.22em]" style="color: var(--color-accent);">
|
<h3 class="mb-5 font-eyebrow text-xs uppercase tracking-[0.22em]" style="color: var(--color-accent);">
|
||||||
You might also like
|
You might also like
|
||||||
</h3>
|
</h3>
|
||||||
<div class="grid gap-4 sm:grid-cols-2">
|
<div class="grid gap-4 sm:grid-cols-2">
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ const {
|
|||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
|
|
||||||
const isHero = tone === "hero";
|
const isHero = tone === "hero";
|
||||||
const titleColor = isHero ? "var(--color-cream)" : undefined;
|
const titleColor = isHero ? "var(--color-starlight)" : undefined;
|
||||||
const subtitleColor = isHero ? "var(--color-cream)" : "var(--color-text-muted)";
|
const subtitleColor = isHero ? "var(--color-starlight)" : "var(--color-text-muted)";
|
||||||
---
|
---
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -53,11 +53,11 @@ const subtitleColor = isHero ? "var(--color-cream)" : "var(--color-text-muted)";
|
|||||||
{
|
{
|
||||||
kicker && (
|
kicker && (
|
||||||
<span
|
<span
|
||||||
class="inline-flex items-center gap-2 font-mono text-xs uppercase tracking-[0.22em]"
|
class="inline-flex items-center gap-2 font-eyebrow text-xs uppercase tracking-[0.22em]"
|
||||||
style={
|
style={
|
||||||
isHero
|
isHero
|
||||||
? "color: var(--color-accent); text-shadow: 0 0 14px color-mix(in srgb, var(--color-accent) 45%, transparent);"
|
? "color: var(--color-accent); text-shadow: 0 0 14px color-mix(in srgb, var(--color-accent) 45%, transparent);"
|
||||||
: "color: var(--color-accent);"
|
: "color: var(--color-secondary);"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{icon && <Icon name={icon} class="h-4 w-4" />}
|
{icon && <Icon name={icon} class="h-4 w-4" />}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const {
|
|||||||
/>
|
/>
|
||||||
<span class="flex flex-col gap-1">
|
<span class="flex flex-col gap-1">
|
||||||
<span
|
<span
|
||||||
class="font-mono text-xs uppercase tracking-[0.18em]"
|
class="font-eyebrow text-xs uppercase tracking-[0.18em]"
|
||||||
style="color: var(--color-text-subtle); text-align:right;"
|
style="color: var(--color-text-subtle); text-align:right;"
|
||||||
>
|
>
|
||||||
The professional wing
|
The professional wing
|
||||||
@@ -59,7 +59,7 @@ const {
|
|||||||
>
|
>
|
||||||
<span class="flex flex-col gap-1">
|
<span class="flex flex-col gap-1">
|
||||||
<span
|
<span
|
||||||
class="font-mono text-xs uppercase tracking-[0.18em]"
|
class="font-eyebrow text-xs uppercase tracking-[0.18em]"
|
||||||
style="color: var(--color-text-subtle);"
|
style="color: var(--color-text-subtle);"
|
||||||
>
|
>
|
||||||
The personal wing
|
The personal wing
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ const { groups, pillIcon = 'play-circle' } = Astro.props;
|
|||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<div class="flex items-baseline gap-2.5">
|
<div class="flex items-baseline gap-2.5">
|
||||||
<h3
|
<h3
|
||||||
class="font-sans text-lg"
|
class="font-display text-lg"
|
||||||
style="color: var(--color-text);"
|
style="color: var(--color-text);"
|
||||||
>
|
>
|
||||||
{group.category}
|
{group.category}
|
||||||
</h3>
|
</h3>
|
||||||
<span
|
<span
|
||||||
class="font-mono text-[0.65rem] uppercase tracking-[0.18em]"
|
class="font-eyebrow text-[0.65rem] uppercase tracking-[0.18em]"
|
||||||
style="color: var(--color-text-muted);"
|
style="color: var(--color-text-muted);"
|
||||||
>
|
>
|
||||||
{group.items.length}
|
{group.items.length}
|
||||||
@@ -51,7 +51,7 @@ const { groups, pillIcon = 'play-circle' } = Astro.props;
|
|||||||
<span>{item.title}</span>
|
<span>{item.title}</span>
|
||||||
{item.status && (
|
{item.status && (
|
||||||
<span
|
<span
|
||||||
class="font-mono text-[0.6rem] uppercase tracking-[0.14em]"
|
class="font-eyebrow text-[0.6rem] uppercase tracking-[0.14em]"
|
||||||
style="color: var(--color-accent);"
|
style="color: var(--color-accent);"
|
||||||
>
|
>
|
||||||
{item.status}
|
{item.status}
|
||||||
@@ -59,7 +59,7 @@ const { groups, pillIcon = 'play-circle' } = Astro.props;
|
|||||||
)}
|
)}
|
||||||
{item.year && (
|
{item.year && (
|
||||||
<span
|
<span
|
||||||
class="font-mono text-[0.6rem] tracking-[0.04em]"
|
class="font-eyebrow text-[0.6rem] tracking-[0.04em]"
|
||||||
style="color: var(--color-text-muted);"
|
style="color: var(--color-text-muted);"
|
||||||
>
|
>
|
||||||
{item.year}
|
{item.year}
|
||||||
|
|||||||
@@ -2,16 +2,13 @@
|
|||||||
/**
|
/**
|
||||||
* AlchemicalDivider.astro
|
* AlchemicalDivider.astro
|
||||||
* ---------------------------------------------------------------------------
|
* ---------------------------------------------------------------------------
|
||||||
* A horizontal section divider built from a thin rule with alchemical /
|
* Section break in the Patiun star-line style: a dotted (faint) run leading
|
||||||
* celestial symbols along it: a central sun/star flanked by a crescent moon
|
* into a solid (main) run on each side, each stopping short of the stars, with
|
||||||
* and small sparkles, with hairlines tapering to the edges. Replaces plain
|
* a gold four-point star at the center flanked by two small ones. Inherits
|
||||||
* vertical gaps between sections on Home / About / Interests.
|
* currentColor; call sites pass Alchemist Gold.
|
||||||
*
|
|
||||||
* Decorative: aria-hidden, inherits currentColor (set to --color-accent or
|
|
||||||
* --color-violet at the call site).
|
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* <AlchemicalDivider class="my-4 h-8 w-full max-w-md mx-auto" style="color: var(--color-amber)" />
|
* <AlchemicalDivider class="mx-auto h-6 w-full max-w-sm" style="color: var(--color-gold)" />
|
||||||
*/
|
*/
|
||||||
interface Props {
|
interface Props {
|
||||||
class?: string;
|
class?: string;
|
||||||
@@ -28,30 +25,12 @@ const { class: className = '', style = '' } = Astro.props;
|
|||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
>
|
>
|
||||||
{/* Tapered hairlines either side of the central cluster. */}
|
{/* Faint links: dotted runs at the outer ends. */}
|
||||||
<line x1="2" y1="12" x2="74" y2="12" stroke-width="0.8" opacity="0.7" />
|
<path d="M6 12h30M164 12h30" stroke-width="1" stroke-dasharray="0.1 4" />
|
||||||
<line x1="126" y1="12" x2="198" y2="12" stroke-width="0.8" opacity="0.7" />
|
{/* Main figure: solid runs that stop short of the stars. */}
|
||||||
<circle cx="8" cy="12" r="1" fill="currentColor" stroke="none" />
|
<path d="M42 12h38M120 12h38" stroke-width="1" />
|
||||||
<circle cx="192" cy="12" r="1" fill="currentColor" stroke="none" />
|
<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" />
|
||||||
{/* Crescent moon (left). */}
|
<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" />
|
||||||
<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" />
|
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</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
|
<figure
|
||||||
class:list={["relative", className]}
|
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). */}
|
{/* Faint sacred-geometry tint behind the image (fills the full figure). */}
|
||||||
<div
|
<div
|
||||||
@@ -56,23 +56,23 @@ const innerPx = Math.max(0, outerPx - bw);
|
|||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Corner star-burst flourishes (sit on the border corners). */}
|
{/* Corner four-point stars in Alchemist Gold (sit on the border corners). */}
|
||||||
<StarBurst
|
<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)]"
|
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-accent);"
|
style="color: var(--color-gold);"
|
||||||
/>
|
/>
|
||||||
<StarBurst
|
<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)]"
|
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-accent);"
|
style="color: var(--color-gold);"
|
||||||
/>
|
/>
|
||||||
<StarBurst
|
<StarBurst
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="pointer-events-none absolute -right-2 -top-2 h-3 w-3"
|
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
|
<StarBurst
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="pointer-events-none absolute -left-2 -bottom-2 h-3 w-3"
|
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>
|
</figure>
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ const objectFit = fit === "contain" ? "object-contain" : "object-cover";
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
class="absolute bottom-3 left-1/2 -translate-x-1/2 font-mono text-[0.6rem] uppercase tracking-[0.2em]"
|
class="absolute bottom-3 left-1/2 -translate-x-1/2 font-eyebrow text-[0.6rem] uppercase tracking-[0.2em]"
|
||||||
style="color: var(--color-text-subtle); opacity:.8;"
|
style="color: var(--color-text-subtle); opacity:.8;"
|
||||||
>
|
>
|
||||||
{alt}
|
{alt}
|
||||||
|
|||||||
@@ -56,9 +56,9 @@ export default function CircuitField({
|
|||||||
let traces: Trace[] = [];
|
let traces: Trace[] = [];
|
||||||
let nodes: Node[] = [];
|
let nodes: Node[] = [];
|
||||||
|
|
||||||
const gold = '#e8b54a';
|
const gold = '#c9a24a';
|
||||||
const violet = '#8b7bd8';
|
const violet = '#6a2fe0';
|
||||||
const teal = '#6fb6ad';
|
const teal = '#2bc2b7';
|
||||||
|
|
||||||
function build() {
|
function build() {
|
||||||
if (!canvas || !ctx) return;
|
if (!canvas || !ctx) return;
|
||||||
@@ -116,7 +116,7 @@ export default function CircuitField({
|
|||||||
for (let i = 0; i < traces.length; i++) {
|
for (let i = 0; i < traces.length; i++) {
|
||||||
const t = traces[i];
|
const t = traces[i];
|
||||||
// Base trace — clearly visible violet conductor line.
|
// Base trace — clearly visible violet conductor line.
|
||||||
ctx.strokeStyle = 'rgba(168, 158, 220, 0.55)';
|
ctx.strokeStyle = 'rgba(43, 194, 183, 0.55)';
|
||||||
ctx.lineWidth = 1.4;
|
ctx.lineWidth = 1.4;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(t.x1, t.y1);
|
ctx.moveTo(t.x1, t.y1);
|
||||||
@@ -140,7 +140,7 @@ export default function CircuitField({
|
|||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
// Bright pulse head dot.
|
// Bright pulse head dot.
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
ctx.fillStyle = '#fff4d6';
|
ctx.fillStyle = '#fbf3dc';
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(px, py, 1.8, 0, Math.PI * 2);
|
ctx.arc(px, py, 1.8, 0, Math.PI * 2);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
@@ -172,7 +172,7 @@ export default function CircuitField({
|
|||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
ctx.clearRect(0, 0, width, height);
|
ctx.clearRect(0, 0, width, height);
|
||||||
for (const t of traces) {
|
for (const t of traces) {
|
||||||
ctx.strokeStyle = 'rgba(168, 158, 220, 0.5)';
|
ctx.strokeStyle = 'rgba(43, 194, 183, 0.5)';
|
||||||
ctx.lineWidth = 1.4;
|
ctx.lineWidth = 1.4;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(t.x1, t.y1);
|
ctx.moveTo(t.x1, t.y1);
|
||||||
@@ -243,7 +243,7 @@ export default function CircuitField({
|
|||||||
className="absolute inset-0"
|
className="absolute inset-0"
|
||||||
style={{
|
style={{
|
||||||
background:
|
background:
|
||||||
'linear-gradient(180deg, #04061a 0%, #070b24 32%, #0a1230 58%, #0e1a3e 82%, #12224a 100%)',
|
'linear-gradient(180deg, #080c14 0%, #0b111c 32%, #0e1522 58%, #131c2b 82%, #1a2a3a 100%)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* Circuit traces + nodes + signal pulses. */}
|
{/* Circuit traces + nodes + signal pulses. */}
|
||||||
|
|||||||
@@ -67,10 +67,10 @@ export default function ConstellationField({
|
|||||||
}
|
}
|
||||||
let stars: Star[] = [];
|
let stars: Star[] = [];
|
||||||
|
|
||||||
const gold = '#e8b54a';
|
const gold = '#c9a24a';
|
||||||
const violet = '#8b7bd8';
|
const violet = '#6a2fe0';
|
||||||
const teal = '#6fb6ad';
|
const teal = '#2bc2b7';
|
||||||
const white = '#f4f1e8';
|
const white = '#f4f2ed';
|
||||||
const accent = tint === 'violet' ? violet : tint === 'teal' ? teal : gold;
|
const accent = tint === 'violet' ? violet : tint === 'teal' ? teal : gold;
|
||||||
|
|
||||||
// One full rotation every ~10 minutes — barely perceptible, "planet turning".
|
// One full rotation every ~10 minutes — barely perceptible, "planet turning".
|
||||||
@@ -168,7 +168,7 @@ export default function ConstellationField({
|
|||||||
const proximity = 1 - Math.sqrt(d2) / LINK_DIST;
|
const proximity = 1 - Math.sqrt(d2) / LINK_DIST;
|
||||||
const pulse = 0.5 + 0.5 * Math.sin(now / 1600 + (i + j) * 0.7);
|
const pulse = 0.5 + 0.5 * Math.sin(now / 1600 + (i + j) * 0.7);
|
||||||
const alpha = proximity * 0.24 * (0.55 + 0.45 * pulse);
|
const alpha = proximity * 0.24 * (0.55 + 0.45 * pulse);
|
||||||
ctx.strokeStyle = `rgba(232, 181, 74, ${alpha.toFixed(3)})`;
|
ctx.strokeStyle = `rgba(201, 162, 74, ${alpha.toFixed(3)})`;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(A.x, A.y);
|
ctx.moveTo(A.x, A.y);
|
||||||
ctx.lineTo(B.x, B.y);
|
ctx.lineTo(B.x, B.y);
|
||||||
|
|||||||
@@ -1,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
|
* - `outline`: stroke-only, lighter
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* <StarBurst class="h-4 w-4" style="color: var(--color-amber)" />
|
* <StarBurst class="h-4 w-4" style="color: var(--color-gold)" />
|
||||||
* <StarBurst variant="outline" class="h-6 w-6" />
|
* <StarBurst variant="outline" class="h-6 w-6" />
|
||||||
*/
|
*/
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -34,6 +34,7 @@ const sw = variant === 'outline' ? 1.4 : 0;
|
|||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
{/* Four-point sparkle: two perpendicular sharp diamonds + small inner core. */}
|
{/* The Patiun four-point star: thin straight arms (inner radius 0.3 of the
|
||||||
<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" />
|
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>
|
</svg>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* cheap. Pointer-events disabled; inherits `currentColor`.
|
* cheap. Pointer-events disabled; inherits `currentColor`.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* <StarField class="absolute inset-0 h-full w-full" style="color: var(--color-amber); opacity:.5" />
|
* <StarField class="absolute inset-0 h-full w-full" style="color: var(--color-gold); opacity:.5" />
|
||||||
*/
|
*/
|
||||||
interface Props {
|
interface Props {
|
||||||
class?: string;
|
class?: string;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import BrandIcon from '@components/brand/BrandIcon';
|
||||||
|
|
||||||
export interface MobileNavItem {
|
export interface MobileNavItem {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -28,31 +29,9 @@ interface Props {
|
|||||||
currentPath: string;
|
currentPath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Small inline mdi-like icons keyed by name (keeps the menu self-contained).
|
// Patiun brand icons, resolved by the same names the nav config uses.
|
||||||
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',
|
|
||||||
};
|
|
||||||
|
|
||||||
function Icon({ name, className }: { name: string; className?: string }) {
|
function Icon({ name, className }: { name: string; className?: string }) {
|
||||||
return (
|
return <BrandIcon name={name} className={className} />;
|
||||||
<svg className={className} width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
||||||
<path d={ICON_PATHS[name] ?? ''} />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isActive(href: string, path: string): boolean {
|
function isActive(href: string, path: string): boolean {
|
||||||
@@ -90,17 +69,13 @@ export default function MobileMenu({ groups, currentPath }: Props) {
|
|||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
aria-controls="mobile-menu"
|
aria-controls="mobile-menu"
|
||||||
aria-label={open ? 'Close menu' : 'Open menu'}
|
aria-label={open ? 'Close menu' : 'Open menu'}
|
||||||
className="inline-flex h-10 w-10 items-center justify-center rounded-lg border"
|
className="relative z-[60] inline-flex h-10 w-10 items-center justify-center rounded-lg border"
|
||||||
style={{ color: 'var(--color-text)', borderColor: 'var(--color-border)' }}
|
style={{ color: 'var(--color-starlight)', borderColor: 'color-mix(in srgb, var(--color-gold) 45%, transparent)' }}
|
||||||
>
|
>
|
||||||
{open ? (
|
{open ? (
|
||||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
<BrandIcon name="close" size={22} />
|
||||||
<path d="M6 6l12 12M18 6L6 18" />
|
|
||||||
</svg>
|
|
||||||
) : (
|
) : (
|
||||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
<BrandIcon name="menu" size={22} />
|
||||||
<path d="M4 7h16M4 12h16M4 17h16" />
|
|
||||||
</svg>
|
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -124,7 +99,11 @@ export default function MobileMenu({ groups, currentPath }: Props) {
|
|||||||
exit={{ x: '100%' }}
|
exit={{ x: '100%' }}
|
||||||
transition={{ type: 'tween', duration: 0.28 }}
|
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"
|
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) => (
|
{groups.map((group, gi) => (
|
||||||
<ul key={gi} className="flex flex-col gap-1">
|
<ul key={gi} className="flex flex-col gap-1">
|
||||||
@@ -137,9 +116,9 @@ export default function MobileMenu({ groups, currentPath }: Props) {
|
|||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
className="flex items-center gap-3 rounded-lg px-3 py-3 text-lg font-medium transition-colors"
|
className="flex items-center gap-3 rounded-lg px-3 py-3 text-lg font-medium transition-colors"
|
||||||
style={{
|
style={{
|
||||||
color: active ? 'var(--color-accent)' : 'var(--color-text)',
|
color: active ? 'var(--color-teal-bright)' : 'var(--color-starlight)',
|
||||||
backgroundColor: active
|
backgroundColor: active
|
||||||
? 'color-mix(in srgb, var(--color-accent) 14%, transparent)'
|
? 'color-mix(in srgb, var(--color-teal-bright) 14%, transparent)'
|
||||||
: 'transparent',
|
: 'transparent',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
*/
|
*/
|
||||||
import Icon from "@components/Icon.astro";
|
import Icon from "@components/Icon.astro";
|
||||||
import MobileMenu from "./MobileMenu.tsx";
|
import MobileMenu from "./MobileMenu.tsx";
|
||||||
import StarBurst from "@components/mystic/StarBurst.astro";
|
|
||||||
import { navGroups, siteIdentity } from "@lib/site";
|
import { navGroups, siteIdentity } from "@lib/site";
|
||||||
|
|
||||||
const path = Astro.url.pathname;
|
const path = Astro.url.pathname;
|
||||||
@@ -33,7 +32,7 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
|
|||||||
id="site-nav"
|
id="site-nav"
|
||||||
data-nav
|
data-nav
|
||||||
class:list={[
|
class:list={[
|
||||||
"fixed inset-x-0 top-0 z-50 transition-[background,box-shadow,border-color] duration-300",
|
"on-sky fixed inset-x-0 top-0 z-50 transition-[background,box-shadow,border-color] duration-300",
|
||||||
heroSkyClass,
|
heroSkyClass,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -42,29 +41,34 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
|
|||||||
data-nav-inner
|
data-nav-inner
|
||||||
>
|
>
|
||||||
<nav
|
<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"
|
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
|
<a
|
||||||
href="/"
|
href="/"
|
||||||
class="group flex items-center gap-2 leading-tight"
|
class="group flex items-center gap-3 leading-tight"
|
||||||
aria-label={`${siteIdentity.name} home`}
|
aria-label={`${siteIdentity.name} home`}
|
||||||
>
|
>
|
||||||
<StarBurst
|
<img
|
||||||
class="h-4 w-4 shrink-0 transition-transform duration-700 group-hover:rotate-90"
|
src="/brand/patiun-seal.svg"
|
||||||
style="color: var(--color-accent);"
|
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="flex flex-col">
|
||||||
<span
|
<span
|
||||||
class="font-sans text-base font-bold tracking-tight"
|
class="font-display text-base font-bold uppercase tracking-[0.14em]"
|
||||||
style="color: var(--accent);"
|
style="color: var(--color-starlight);"
|
||||||
>
|
>
|
||||||
{siteIdentity.name}
|
{siteIdentity.name}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="hidden font-mono text-[0.65rem] uppercase tracking-[0.18em] sm:block"
|
class="hidden font-eyebrow text-[0.62rem] uppercase tracking-[0.18em] sm:block"
|
||||||
style="color: var(--color-text-subtle);"
|
style="color: var(--color-gold-bright);"
|
||||||
>
|
>
|
||||||
{siteIdentity.tagline}
|
{siteIdentity.tagline}
|
||||||
</span>
|
</span>
|
||||||
@@ -80,7 +84,7 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
|
|||||||
"flex items-center gap-1",
|
"flex items-center gap-1",
|
||||||
{ "border-l pl-5": gi > 0 },
|
{ "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) => {
|
{group.items.map((item) => {
|
||||||
const active = isActive(item.href);
|
const active = isActive(item.href);
|
||||||
@@ -93,7 +97,7 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
|
|||||||
style={
|
style={
|
||||||
active
|
active
|
||||||
? "color: var(--color-accent);"
|
? "color: var(--color-accent);"
|
||||||
: "color: var(--color-cream); opacity: 0.85;"
|
: "color: var(--color-starlight); opacity: 0.85;"
|
||||||
}
|
}
|
||||||
onmouseover="this.style.opacity='1'"
|
onmouseover="this.style.opacity='1'"
|
||||||
onmouseout="this.style.opacity='0.85'"
|
onmouseout="this.style.opacity='0.85'"
|
||||||
@@ -127,20 +131,16 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
|
|||||||
[data-nav-inner] {
|
[data-nav-inner] {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
180deg,
|
180deg,
|
||||||
rgba(8, 10, 32, 0.92) 0%,
|
rgba(14, 21, 34, 0.92) 0%,
|
||||||
rgba(12, 14, 40, 0.86) 100%
|
rgba(19, 28, 43, 0.86) 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
/* Scrolled state: stronger blur + hairline border + shadow. */
|
/* Scrolled state: stronger blur + hairline border + shadow. */
|
||||||
[data-nav].scrolled [data-nav-inner] {
|
[data-nav].scrolled [data-nav-inner] {
|
||||||
backdrop-filter: saturate(150%) blur(12px);
|
backdrop-filter: saturate(150%) blur(12px);
|
||||||
-webkit-backdrop-filter: saturate(150%) blur(12px);
|
-webkit-backdrop-filter: saturate(150%) blur(12px);
|
||||||
border-bottom-color: color-mix(
|
border-bottom-color: color-mix(in srgb, var(--color-gold) 35%, transparent);
|
||||||
in srgb,
|
box-shadow: 0 8px 30px -20px rgba(8, 12, 20, 0.7);
|
||||||
var(--color-accent) 30%,
|
|
||||||
var(--color-border)
|
|
||||||
);
|
|
||||||
box-shadow: 0 8px 30px -20px rgba(11, 16, 38, 0.7);
|
|
||||||
}
|
}
|
||||||
/* A subtle violet/gold nebula glow behind the nav, fixed to the top. */
|
/* A subtle violet/gold nebula glow behind the nav, fixed to the top. */
|
||||||
[data-nav]::before {
|
[data-nav]::before {
|
||||||
@@ -152,12 +152,12 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
|
|||||||
background:
|
background:
|
||||||
radial-gradient(
|
radial-gradient(
|
||||||
60% 120% at 85% -20%,
|
60% 120% at 85% -20%,
|
||||||
rgba(139, 123, 216, 0.22),
|
rgba(106, 47, 224, 0.22),
|
||||||
transparent 70%
|
transparent 70%
|
||||||
),
|
),
|
||||||
radial-gradient(
|
radial-gradient(
|
||||||
50% 120% at 10% -10%,
|
50% 120% at 10% -10%,
|
||||||
rgba(232, 181, 74, 0.1),
|
rgba(201, 162, 74, 0.1),
|
||||||
transparent 70%
|
transparent 70%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,14 +58,14 @@ const entries: CertEntry[] = [
|
|||||||
{c.status && <span class="pill">{c.status}</span>}
|
{c.status && <span class="pill">{c.status}</span>}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<h3 class="font-sans text-base" style="color: var(--color-text);">
|
<h3 class="font-display text-base" style="color: var(--color-text);">
|
||||||
{c.name}
|
{c.name}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm font-medium" style="color: var(--color-accent);">
|
<p class="text-sm font-medium" style="color: var(--color-accent);">
|
||||||
{c.issuer}
|
{c.issuer}
|
||||||
</p>
|
</p>
|
||||||
<span
|
<span
|
||||||
class="font-mono text-xs uppercase tracking-[0.18em]"
|
class="font-eyebrow text-xs uppercase tracking-[0.18em]"
|
||||||
style="color: var(--color-text-subtle);"
|
style="color: var(--color-text-subtle);"
|
||||||
>
|
>
|
||||||
{c.year}
|
{c.year}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
* skipped entirely so the form still works.
|
* skipped entirely so the form still works.
|
||||||
*/
|
*/
|
||||||
import { useState, useRef, useEffect, type FormEvent } from 'react';
|
import { useState, useRef, useEffect, type FormEvent } from 'react';
|
||||||
|
import BrandIcon from '@components/brand/BrandIcon';
|
||||||
|
|
||||||
type Status = 'idle' | 'submitting' | 'success' | 'error';
|
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)' }}
|
style={{ background: 'var(--color-accent)', color: 'var(--color-accent-contrast)' }}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
|
<BrandIcon name="check" size={24} />
|
||||||
<path d="M20 6L9 17l-5-5" />
|
|
||||||
</svg>
|
|
||||||
</span>
|
</span>
|
||||||
<h3 className="font-sans text-lg font-semibold" style={{ color: 'var(--color-text)' }}>
|
<h3 className="font-display text-lg font-semibold" style={{ color: 'var(--color-text)' }}>
|
||||||
Message sent.
|
Message sent.
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm" style={{ color: 'var(--color-text-muted)' }}>
|
<p className="text-sm" style={{ color: 'var(--color-text-muted)' }}>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const entries: EduEntry[] = [
|
|||||||
<Icon name={e.icon} class="h-5 w-5" />
|
<Icon name={e.icon} class="h-5 w-5" />
|
||||||
</span>
|
</span>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<h3 class="font-sans text-lg" style="color: var(--color-text);">
|
<h3 class="font-display text-lg" style="color: var(--color-text);">
|
||||||
{e.credential}
|
{e.credential}
|
||||||
</h3>
|
</h3>
|
||||||
<p
|
<p
|
||||||
@@ -55,7 +55,7 @@ const entries: EduEntry[] = [
|
|||||||
{e.institution}
|
{e.institution}
|
||||||
</p>
|
</p>
|
||||||
<span
|
<span
|
||||||
class="font-mono text-xs uppercase tracking-[0.18em]"
|
class="font-eyebrow text-xs uppercase tracking-[0.18em]"
|
||||||
style="color: var(--color-text-subtle);"
|
style="color: var(--color-text-subtle);"
|
||||||
>
|
>
|
||||||
{e.period}
|
{e.period}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const links = [
|
|||||||
<div class="flex items-start justify-between gap-3">
|
<div class="flex items-start justify-between gap-3">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<h3 style="color: var(--color-text);">{d.title}</h3>
|
<h3 style="color: var(--color-text);">{d.title}</h3>
|
||||||
<p class="font-mono text-xs uppercase tracking-[0.18em]" style="color: var(--color-text-subtle);">
|
<p class="font-eyebrow text-xs uppercase tracking-[0.18em]" style="color: var(--color-text-subtle);">
|
||||||
{d.role}
|
{d.role}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
* Three bugs previously fixed here:
|
* Three bugs previously fixed here:
|
||||||
* 1. Contrast: the rail used surface-relative tokens (--color-surface-raised /
|
* 1. Contrast: the rail used surface-relative tokens (--color-surface-raised /
|
||||||
* --color-cream) which collapsed to the same tone on light surfaces (the
|
* --color-starlight) which collapsed to the same tone on light surfaces (the
|
||||||
* interest-surface cream made icons/text invisible). The rail now uses
|
* interest-surface cream made icons/text invisible). The rail now uses
|
||||||
* FIXED dark colors regardless of the page's surface tone.
|
* FIXED dark colors regardless of the page's surface tone.
|
||||||
* 2. Progress line clipping: the line sat at the icon column's horizontal
|
* 2. Progress line clipping: the line sat at the icon column's horizontal
|
||||||
@@ -41,7 +41,7 @@ const sections = [
|
|||||||
for the progress line, then the icon+label list. */}
|
for the progress line, then the icon+label list. */}
|
||||||
<div
|
<div
|
||||||
class="relative flex gap-2 rounded-2xl border p-3 shadow-2xl"
|
class="relative flex gap-2 rounded-2xl border p-3 shadow-2xl"
|
||||||
style="border-color: rgba(232, 181, 74, 0.22); background: linear-gradient(180deg, rgba(12, 14, 40, 0.97), rgba(8, 10, 32, 0.97)); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); box-shadow: 0 18px 40px -24px rgba(11, 16, 38, 0.7);"
|
style="border-color: rgba(201, 162, 74, 0.22); background: linear-gradient(180deg, rgba(19, 28, 43, 0.97), rgba(14, 21, 34, 0.97)); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); box-shadow: 0 18px 40px -24px rgba(8, 12, 20, 0.7);"
|
||||||
>
|
>
|
||||||
{/* --- Left gutter: the progress track + fill. Sits LEFT of the icons. --- */}
|
{/* --- Left gutter: the progress track + fill. Sits LEFT of the icons. --- */}
|
||||||
<div class="relative my-1 w-2 shrink-0">
|
<div class="relative my-1 w-2 shrink-0">
|
||||||
@@ -69,11 +69,11 @@ const sections = [
|
|||||||
href={`#${s.id}`}
|
href={`#${s.id}`}
|
||||||
data-section-link={s.id}
|
data-section-link={s.id}
|
||||||
class="group relative flex items-center gap-2.5 rounded-lg px-2.5 py-2 font-sans text-sm font-semibold transition-all"
|
class="group relative flex items-center gap-2.5 rounded-lg px-2.5 py-2 font-sans text-sm font-semibold transition-all"
|
||||||
style="color: #e8e6f5;"
|
style="color: #ece9e2;"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="relative z-10 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-lg transition-colors"
|
class="relative z-10 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-lg transition-colors"
|
||||||
style="background: rgba(232, 181, 74, 0.14); color: #f4cd72;"
|
style="background: rgba(201, 162, 74, 0.14); color: #d8b35a;"
|
||||||
data-section-dot={s.id}
|
data-section-dot={s.id}
|
||||||
>
|
>
|
||||||
<Icon name={s.icon} class="h-[1.15rem] w-[1.15rem]" />
|
<Icon name={s.icon} class="h-[1.15rem] w-[1.15rem]" />
|
||||||
@@ -92,19 +92,19 @@ const sections = [
|
|||||||
}
|
}
|
||||||
.pro-section-nav a[data-section-link]:hover {
|
.pro-section-nav a[data-section-link]:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
background: rgba(232, 181, 74, 0.08);
|
background: rgba(201, 162, 74, 0.08);
|
||||||
}
|
}
|
||||||
/* Active link: bright accent text + accent wash. */
|
/* Active link: bright accent text + accent wash. */
|
||||||
.pro-section-nav a[data-active="true"] {
|
.pro-section-nav a[data-active="true"] {
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
color: var(--color-accent) !important;
|
color: var(--color-accent) !important;
|
||||||
background: rgba(232, 181, 74, 0.14);
|
background: rgba(201, 162, 74, 0.14);
|
||||||
}
|
}
|
||||||
/* Active dot: filled accent, dark icon, glow ring. */
|
/* Active dot: filled accent, dark icon, glow ring. */
|
||||||
.pro-section-nav a[data-active="true"] [data-section-dot] {
|
.pro-section-nav a[data-active="true"] [data-section-dot] {
|
||||||
background: var(--color-accent) !important;
|
background: var(--color-accent) !important;
|
||||||
color: #0b1026 !important;
|
color: #131c2b !important;
|
||||||
box-shadow: 0 0 12px rgba(232, 181, 74, 0.65);
|
box-shadow: 0 0 12px rgba(201, 162, 74, 0.65);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const techStack = [
|
|||||||
<div class="flex flex-col gap-8">
|
<div class="flex flex-col gap-8">
|
||||||
<!-- Core Competencies -->
|
<!-- Core Competencies -->
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<h3 class="font-sans" style="color: var(--color-text);">
|
<h3 class="font-display" style="color: var(--color-text);">
|
||||||
Core Competencies
|
Core Competencies
|
||||||
</h3>
|
</h3>
|
||||||
<ul class="grid gap-3 sm:grid-cols-2">
|
<ul class="grid gap-3 sm:grid-cols-2">
|
||||||
@@ -112,7 +112,7 @@ const techStack = [
|
|||||||
|
|
||||||
<!-- Technical Stack -->
|
<!-- Technical Stack -->
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<h3 class="font-sans" style="color: var(--color-text);">Technical Stack</h3>
|
<h3 class="font-display" style="color: var(--color-text);">Technical Stack</h3>
|
||||||
<div class="grid gap-4 sm:grid-cols-2">
|
<div class="grid gap-4 sm:grid-cols-2">
|
||||||
{
|
{
|
||||||
techStack.map((item) => (
|
techStack.map((item) => (
|
||||||
@@ -151,7 +151,7 @@ const techStack = [
|
|||||||
|
|
||||||
<!-- Emerging & Exploring -->
|
<!-- Emerging & Exploring -->
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<h3 class="font-sans" style="color: var(--color-text);">
|
<h3 class="font-display" style="color: var(--color-text);">
|
||||||
Emerging & Exploring
|
Emerging & Exploring
|
||||||
</h3>
|
</h3>
|
||||||
<ul class="grid gap-3 sm:grid-cols-3">
|
<ul class="grid gap-3 sm:grid-cols-3">
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const rendered = await Promise.all(
|
|||||||
</span>
|
</span>
|
||||||
{entry.data.company}
|
{entry.data.company}
|
||||||
</h3>
|
</h3>
|
||||||
<span class="font-mono text-xs uppercase tracking-[0.18em]" style="color: var(--color-text-subtle);">
|
<span class="font-eyebrow text-xs uppercase tracking-[0.18em]" style="color: var(--color-text-subtle);">
|
||||||
{entry.data.period}
|
{entry.data.period}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
* visible text (not aria-hidden) so it's announced.
|
* visible text (not aria-hidden) so it's announced.
|
||||||
*/
|
*/
|
||||||
import { useState, useRef, useCallback, type PointerEvent } from 'react';
|
import { useState, useRef, useCallback, type PointerEvent } from 'react';
|
||||||
|
import BrandIcon from '@components/brand/BrandIcon';
|
||||||
|
|
||||||
export interface CarouselSlide {
|
export interface CarouselSlide {
|
||||||
src: string;
|
src: string;
|
||||||
@@ -150,11 +151,11 @@ export default function Carousel({ slides, label = 'Product examples' }: Props)
|
|||||||
{/* Caption gradient + label, always visible for the active slide. */}
|
{/* Caption gradient + label, always visible for the active slide. */}
|
||||||
<div
|
<div
|
||||||
className="pointer-events-none absolute inset-x-0 bottom-0 flex items-end p-4"
|
className="pointer-events-none absolute inset-x-0 bottom-0 flex items-end p-4"
|
||||||
style={{ background: 'linear-gradient(to top, rgba(7,10,28,0.85), transparent)' }}
|
style={{ background: 'linear-gradient(to top, rgba(14,21,34,0.85), transparent)' }}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="font-sans text-sm font-semibold sm:text-base"
|
className="font-sans text-sm font-semibold sm:text-base"
|
||||||
style={{ color: 'var(--color-cream)' }}
|
style={{ color: 'var(--color-starlight)' }}
|
||||||
>
|
>
|
||||||
{s.label}
|
{s.label}
|
||||||
</span>
|
</span>
|
||||||
@@ -172,14 +173,12 @@ export default function Carousel({ slides, label = 'Product examples' }: Props)
|
|||||||
aria-label="Previous slide"
|
aria-label="Previous slide"
|
||||||
className="absolute left-2 top-1/2 inline-flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full border backdrop-blur-sm transition-colors"
|
className="absolute left-2 top-1/2 inline-flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full border backdrop-blur-sm transition-colors"
|
||||||
style={{
|
style={{
|
||||||
color: 'var(--color-cream)',
|
color: 'var(--color-starlight)',
|
||||||
borderColor: 'color-mix(in srgb, var(--color-accent) 40%, transparent)',
|
borderColor: 'color-mix(in srgb, var(--color-accent) 40%, transparent)',
|
||||||
background: 'rgba(7,10,28,0.5)',
|
background: 'rgba(14,21,34,0.5)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
<BrandIcon name="chevron-left" size={20} />
|
||||||
<path d="M15 18l-6-6 6-6" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -187,22 +186,20 @@ export default function Carousel({ slides, label = 'Product examples' }: Props)
|
|||||||
aria-label="Next slide"
|
aria-label="Next slide"
|
||||||
className="absolute right-2 top-1/2 inline-flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full border backdrop-blur-sm transition-colors"
|
className="absolute right-2 top-1/2 inline-flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full border backdrop-blur-sm transition-colors"
|
||||||
style={{
|
style={{
|
||||||
color: 'var(--color-cream)',
|
color: 'var(--color-starlight)',
|
||||||
borderColor: 'color-mix(in srgb, var(--color-accent) 40%, transparent)',
|
borderColor: 'color-mix(in srgb, var(--color-accent) 40%, transparent)',
|
||||||
background: 'rgba(7,10,28,0.5)',
|
background: 'rgba(14,21,34,0.5)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
<BrandIcon name="chevron-right" size={20} />
|
||||||
<path d="M9 18l6-6-6-6" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Slide counter (top-right). */}
|
{/* Slide counter (top-right). */}
|
||||||
<span
|
<span
|
||||||
className="pointer-events-none absolute right-3 top-3 rounded-full px-2.5 py-1 font-mono text-xs"
|
className="pointer-events-none absolute right-3 top-3 rounded-full px-2.5 py-1 font-eyebrow text-xs"
|
||||||
style={{ color: 'var(--color-cream)', background: 'rgba(7,10,28,0.6)' }}
|
style={{ color: 'var(--color-starlight)', background: 'rgba(14,21,34,0.6)' }}
|
||||||
>
|
>
|
||||||
{index + 1} / {count}
|
{index + 1} / {count}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const inStockCount = colors.filter((c) => c.inStock).length;
|
|||||||
<span class="text-sm" style="color: var(--color-text-muted);">
|
<span class="text-sm" style="color: var(--color-text-muted);">
|
||||||
Choose from the available filament colors.
|
Choose from the available filament colors.
|
||||||
</span>
|
</span>
|
||||||
<span class="font-mono text-xs" style="color: var(--color-text-subtle);">
|
<span class="font-eyebrow text-xs" style="color: var(--color-text-subtle);">
|
||||||
{inStockCount} of {colors.length} in stock
|
{inStockCount} of {colors.length} in stock
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
* before it can be submitted.
|
* before it can be submitted.
|
||||||
*/
|
*/
|
||||||
import { useState, useRef, useEffect, type FormEvent } from 'react';
|
import { useState, useRef, useEffect, type FormEvent } from 'react';
|
||||||
|
import BrandIcon from '@components/brand/BrandIcon';
|
||||||
|
|
||||||
type Status = 'idle' | 'submitting' | 'success' | 'error';
|
type Status = 'idle' | 'submitting' | 'success' | 'error';
|
||||||
type OrderType = 'cards' | 'deckBoxes' | 'both';
|
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)' }}
|
style={{ background: 'var(--color-accent)', color: 'var(--color-accent-contrast)' }}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
|
<BrandIcon name="check" size={24} />
|
||||||
<path d="M20 6L9 17l-5-5" />
|
|
||||||
</svg>
|
|
||||||
</span>
|
</span>
|
||||||
<h3 className="font-sans text-lg font-semibold" style={{ color: 'var(--color-text)' }}>
|
<h3 className="font-display text-lg font-semibold" style={{ color: 'var(--color-text)' }}>
|
||||||
Order request sent.
|
Order request sent.
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm" style={{ color: 'var(--color-text-muted)' }}>
|
<p className="text-sm" style={{ color: 'var(--color-text-muted)' }}>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ const { icon, caption, tiers, priceHeader = 'Price', subtitle } = Astro.props;
|
|||||||
<span class="ml-2 text-xs" style="color: var(--color-text-subtle);">({tier.note})</span>
|
<span class="ml-2 text-xs" style="color: var(--color-text-subtle);">({tier.note})</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-right font-mono font-semibold" style="color: var(--color-accent);">
|
<td class="px-4 py-3 text-right font-semibold" style="color: var(--color-accent);">
|
||||||
{tier.price}
|
{tier.price}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -37,14 +37,10 @@ const cover = product.images[0];
|
|||||||
/>
|
/>
|
||||||
{product.images.length > 1 && (
|
{product.images.length > 1 && (
|
||||||
<span
|
<span
|
||||||
class="pointer-events-none absolute right-2 top-2 inline-flex items-center gap-1 rounded-full px-2 py-1 font-mono text-xs"
|
class="pointer-events-none absolute right-2 top-2 inline-flex items-center gap-1 rounded-full px-2 py-1 font-eyebrow text-xs"
|
||||||
style="color: var(--color-cream); background: rgba(7,10,28,0.6);"
|
style="color: var(--color-starlight); background: rgba(14,21,34,0.6);"
|
||||||
>
|
>
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
<Icon name="gallery" class="h-3.5 w-3.5" />
|
||||||
<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>
|
|
||||||
{product.images.length}
|
{product.images.length}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const { items, heading } = Astro.props;
|
|||||||
function toneColor(icon: Requirement['icon']): string {
|
function toneColor(icon: Requirement['icon']): string {
|
||||||
switch (icon) {
|
switch (icon) {
|
||||||
case 'alert':
|
case 'alert':
|
||||||
return '#e8b54a'; // amber — caution
|
return '#d8b35a'; // gold — caution
|
||||||
case 'close-circle':
|
case 'close-circle':
|
||||||
return '#e5655a'; // red — hard no
|
return '#e5655a'; // red — hard no
|
||||||
case 'check-decagram':
|
case 'check-decagram':
|
||||||
@@ -36,7 +36,7 @@ function toneColor(icon: Requirement['icon']): string {
|
|||||||
---
|
---
|
||||||
|
|
||||||
{heading && (
|
{heading && (
|
||||||
<h3 class="font-sans text-xl font-bold" style="color: var(--color-text);">{heading}</h3>
|
<h3 class="font-display text-xl font-bold" style="color: var(--color-text);">{heading}</h3>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ul class="flex flex-col gap-3">
|
<ul class="flex flex-col gap-3">
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const pct = total > 0 ? Math.max(0, Math.min(100, (open / total) * 100)) : 0;
|
|||||||
|
|
||||||
<div class="flex items-center justify-between text-sm" style="color: var(--color-text-muted);">
|
<div class="flex items-center justify-between text-sm" style="color: var(--color-text-muted);">
|
||||||
<span>{open} of {total} slots open</span>
|
<span>{open} of {total} slots open</span>
|
||||||
<span class="font-mono text-xs" style="color: var(--color-text-subtle);">{closed ? '0%' : `${Math.round(pct)}%`}</span>
|
<span class="font-eyebrow text-xs" style="color: var(--color-text-subtle);">{closed ? '0%' : `${Math.round(pct)}%`}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -8,13 +8,12 @@
|
|||||||
* two sites not be clearly linked yet.
|
* two sites not be clearly linked yet.
|
||||||
*
|
*
|
||||||
* Visually it mirrors NavBar.astro's treatment — the dark nebula band, the
|
* Visually it mirrors NavBar.astro's treatment — the dark nebula band, the
|
||||||
* scroll blur, and the StarBurst mark — so it reads as the same house while
|
* scroll blur, and the Patiun seal — so it reads as the same house while
|
||||||
* being unmistakably a store (a "Store" wordmark + the store.gregkilmer.com
|
* being unmistakably a store (a "Store" wordmark + the store.gregkilmer.com
|
||||||
* domain label).
|
* domain label).
|
||||||
*/
|
*/
|
||||||
import Icon from '@components/Icon.astro';
|
import Icon from '@components/Icon.astro';
|
||||||
import MobileMenu from '@components/nav/MobileMenu.tsx';
|
import MobileMenu from '@components/nav/MobileMenu.tsx';
|
||||||
import StarBurst from '@components/mystic/StarBurst.astro';
|
|
||||||
|
|
||||||
interface NavEntry {
|
interface NavEntry {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -40,25 +39,28 @@ const groups = [{ items: storeNav }];
|
|||||||
<header
|
<header
|
||||||
id="store-nav"
|
id="store-nav"
|
||||||
data-nav
|
data-nav
|
||||||
class="fixed inset-x-0 top-0 z-50 transition-[background,box-shadow,border-color] duration-300"
|
class="on-sky fixed inset-x-0 top-0 z-50 transition-[background,box-shadow,border-color] duration-300"
|
||||||
>
|
>
|
||||||
<div class="border-b border-transparent transition-colors duration-300" data-nav-inner>
|
<div class="border-b border-transparent transition-colors duration-300" data-nav-inner>
|
||||||
<nav class="container-prose flex h-16 items-center justify-between gap-4" aria-label="Store">
|
<nav class="container-prose flex h-20 items-center justify-between gap-4" aria-label="Store">
|
||||||
<!-- Wordmark: "Greg Kilmer · Store" + the store domain label -->
|
<!-- Wordmark: "Greg Kilmer · Store" + the store domain label -->
|
||||||
<a href="/store" class="group flex items-center gap-2 leading-tight" aria-label="Greg Kilmer Store — home">
|
<a href="/store" class="group flex items-center gap-3 leading-tight" aria-label="Greg Kilmer Store — home">
|
||||||
<StarBurst
|
<img
|
||||||
class="h-4 w-4 shrink-0 transition-transform duration-700 group-hover:rotate-90"
|
src="/brand/patiun-seal.svg"
|
||||||
style="color: var(--color-accent);"
|
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="flex flex-col">
|
||||||
<span class="font-sans text-base font-bold tracking-tight" style="color: var(--accent);">
|
<span class="font-display text-base font-bold uppercase tracking-[0.12em]" style="color: var(--color-starlight);">
|
||||||
Greg Kilmer · Store
|
Greg Kilmer · Store
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="hidden font-mono text-[0.65rem] uppercase tracking-[0.18em] sm:block"
|
class="hidden font-eyebrow text-[0.7rem] tracking-[0.08em] sm:block"
|
||||||
style="color: var(--color-text-subtle);"
|
style="color: var(--color-text-subtle);"
|
||||||
>
|
>
|
||||||
store.gregkilmer.com
|
Store.GregKilmer.com
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -71,7 +73,7 @@ const groups = [{ items: storeNav }];
|
|||||||
<a
|
<a
|
||||||
href={item.href}
|
href={item.href}
|
||||||
class="inline-flex items-center gap-1.5 rounded-md px-3 py-2 text-sm font-medium transition-colors"
|
class="inline-flex items-center gap-1.5 rounded-md px-3 py-2 text-sm font-medium transition-colors"
|
||||||
style={active ? 'color: var(--color-accent);' : 'color: var(--color-cream); opacity: 0.85;'}
|
style={active ? 'color: var(--color-accent);' : 'color: var(--color-starlight); opacity: 0.85;'}
|
||||||
onmouseover="this.style.opacity='1'"
|
onmouseover="this.style.opacity='1'"
|
||||||
onmouseout="this.style.opacity='0.85'"
|
onmouseout="this.style.opacity='0.85'"
|
||||||
aria-current={active ? 'page' : undefined}
|
aria-current={active ? 'page' : undefined}
|
||||||
@@ -100,8 +102,8 @@ const groups = [{ items: storeNav }];
|
|||||||
[data-nav-inner] {
|
[data-nav-inner] {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
180deg,
|
180deg,
|
||||||
rgb(8, 10, 32) 0%,
|
rgb(14, 21, 34) 0%,
|
||||||
rgb(12, 14, 40) 100%
|
rgb(19, 28, 43) 100%
|
||||||
);
|
);
|
||||||
border-bottom: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
|
border-bottom: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
|
||||||
}
|
}
|
||||||
@@ -113,7 +115,7 @@ const groups = [{ items: storeNav }];
|
|||||||
var(--color-accent) 30%,
|
var(--color-accent) 30%,
|
||||||
var(--color-border)
|
var(--color-border)
|
||||||
);
|
);
|
||||||
box-shadow: 0 8px 30px -20px rgba(11, 16, 38, 0.7);
|
box-shadow: 0 8px 30px -20px rgba(8, 12, 20, 0.7);
|
||||||
}
|
}
|
||||||
[data-nav]::before {
|
[data-nav]::before {
|
||||||
content: '';
|
content: '';
|
||||||
@@ -122,8 +124,8 @@ const groups = [{ items: storeNav }];
|
|||||||
z-index: -1;
|
z-index: -1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background:
|
background:
|
||||||
radial-gradient(60% 120% at 85% -20%, rgba(139, 123, 216, 0.22), transparent 70%),
|
radial-gradient(60% 120% at 85% -20%, rgba(106, 47, 224, 0.22), transparent 70%),
|
||||||
radial-gradient(50% 120% at 10% -10%, rgba(232, 181, 74, 0.1), transparent 70%);
|
radial-gradient(50% 120% at 10% -10%, rgba(201, 162, 74, 0.1), transparent 70%);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import BrandIcon from '@components/brand/BrandIcon';
|
||||||
|
|
||||||
export interface BoardGame {
|
export interface BoardGame {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -106,10 +107,8 @@ export default function BoardGameShelf({
|
|||||||
color: isActive ? 'var(--color-accent)' : 'var(--color-secondary)',
|
color: isActive ? 'var(--color-accent)' : 'var(--color-secondary)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* dice / playing-card style glyph, inline to stay self-contained */}
|
{/* Patiun dice icon */}
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
<BrandIcon name="dice" size={16} />
|
||||||
<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>
|
|
||||||
</span>
|
</span>
|
||||||
<span className="flex flex-col gap-0.5">
|
<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">
|
<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"
|
className="relative w-full overflow-hidden rounded-xl border"
|
||||||
style={{
|
style={{
|
||||||
height: SHELF_HEIGHT,
|
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)',
|
background: 'color-mix(in srgb, var(--color-secondary) 12%, transparent)',
|
||||||
}}
|
}}
|
||||||
// When the pointer leaves the whole figure, snap back to the shelf.
|
// When the pointer leaves the whole figure, snap back to the shelf.
|
||||||
@@ -173,7 +172,7 @@ export default function BoardGameShelf({
|
|||||||
|
|
||||||
<figcaption
|
<figcaption
|
||||||
className="absolute inset-x-0 bottom-0 flex flex-col gap-0.5 p-4"
|
className="absolute inset-x-0 bottom-0 flex flex-col gap-0.5 p-4"
|
||||||
style={{ color: 'var(--color-cream)' }}
|
style={{ color: 'var(--color-starlight)' }}
|
||||||
>
|
>
|
||||||
<span className="font-sans text-lg leading-tight drop-shadow">
|
<span className="font-sans text-lg leading-tight drop-shadow">
|
||||||
{activeGame?.name ?? shelfCaption}
|
{activeGame?.name ?? shelfCaption}
|
||||||
|
|||||||
@@ -15,8 +15,10 @@
|
|||||||
import "@/styles/global.css";
|
import "@/styles/global.css";
|
||||||
|
|
||||||
// Self-hosted fonts (woff2, variable). Avoids external requests for perf + privacy.
|
// Self-hosted fonts (woff2, variable). Avoids external requests for perf + privacy.
|
||||||
import "@fontsource-variable/inter";
|
// Patiun brand type: Cinzel (headings, names) + Montserrat (body, UI).
|
||||||
import "@fontsource-variable/plus-jakarta-sans";
|
// JetBrains Mono stays for the rare code-like block.
|
||||||
|
import "@fontsource-variable/cinzel";
|
||||||
|
import "@fontsource-variable/montserrat";
|
||||||
import "@fontsource-variable/jetbrains-mono";
|
import "@fontsource-variable/jetbrains-mono";
|
||||||
|
|
||||||
import { ClientRouter } from "astro:transitions";
|
import { ClientRouter } from "astro:transitions";
|
||||||
@@ -104,7 +106,7 @@ const ogImageUrl = ogImage
|
|||||||
<meta name="description" content={description} />
|
<meta name="description" content={description} />
|
||||||
<link rel="canonical" href={canonical} />
|
<link rel="canonical" href={canonical} />
|
||||||
{noindex && <meta name="robots" content="noindex, nofollow" />}
|
{noindex && <meta name="robots" content="noindex, nofollow" />}
|
||||||
<meta name="theme-color" content="#0b1026" />
|
<meta name="theme-color" content="#131c2b" />
|
||||||
|
|
||||||
<!-- Open Graph / social cards -->
|
<!-- Open Graph / social cards -->
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
@@ -115,7 +117,12 @@ const ogImageUrl = ogImage
|
|||||||
{ogImageUrl && <meta property="og:image" content={ogImageUrl} />}
|
{ogImageUrl && <meta property="og:image" content={ogImageUrl} />}
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
<!-- Favicons and app icons: the Greg Kilmer package from the Patiun brand
|
||||||
|
files (GK monogram on Midnight). -->
|
||||||
|
<link rel="icon" href="/favicon.ico" sizes="48x48" />
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||||
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||||
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
|
|
||||||
<!-- Theme boot: blocking inline script prevents flash of the wrong theme.
|
<!-- Theme boot: blocking inline script prevents flash of the wrong theme.
|
||||||
Dark mode is currently forced (light mode deferred), so this just pins
|
Dark mode is currently forced (light mode deferred), so this just pins
|
||||||
@@ -149,7 +156,7 @@ const ogImageUrl = ogImage
|
|||||||
<body data-surface={surface} data-page={isHome ? "home" : "other"}>
|
<body data-surface={surface} data-page={isHome ? "home" : "other"}>
|
||||||
<a
|
<a
|
||||||
href="#main"
|
href="#main"
|
||||||
class="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-[100] focus:rounded-lg focus:bg-(--color-amber) focus:px-4 focus:py-2 focus:text-(--color-ink)"
|
class="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-[100] focus:rounded-lg focus:bg-(--color-teal) focus:px-4 focus:py-2 focus:text-(--color-midnight)"
|
||||||
>
|
>
|
||||||
Skip to content
|
Skip to content
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -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',
|
name: 'Greg Kilmer',
|
||||||
/** Spec tagline; appears beside the logo. */
|
/** Spec tagline; appears beside the logo. */
|
||||||
tagline: 'Developer. Maker. Storyteller.',
|
tagline: 'Developer. Maker. Storyteller.',
|
||||||
/** Spec domain. */
|
/** Brand lockup line shown under the name in the header (Patiun brand). */
|
||||||
domain: 'gregkilmer.com',
|
lockupLine: 'Web Development · a Patiun LLC company',
|
||||||
|
/** Domain, written capitalized per the Patiun brand. */
|
||||||
|
domain: 'GregKilmer.com',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const quickFacts = [
|
|||||||
description="The human story behind the resume: Greg Kilmer on curiosity, tabletop RPGs, resin dice, and a Philadelphia apartment with too many board games."
|
description="The human story behind the resume: Greg Kilmer on curiosity, tabletop RPGs, resin dice, and a Philadelphia apartment with too many board games."
|
||||||
>
|
>
|
||||||
<!-- Hero — full animated Milky Way field, matching the Interests hero. -->
|
<!-- Hero — full animated Milky Way field, matching the Interests hero. -->
|
||||||
<section class="relative overflow-hidden pb-8 pt-28 sm:pt-32">
|
<section class="on-sky relative overflow-hidden pb-8 pt-28 sm:pt-32">
|
||||||
<AnimatedBackground client:idle className="vt-sky" />
|
<AnimatedBackground client:idle className="vt-sky" />
|
||||||
<div class="container-prose relative z-10 flex flex-col gap-8 py-8">
|
<div class="container-prose relative z-10 flex flex-col gap-8 py-8">
|
||||||
<Reveal>
|
<Reveal>
|
||||||
@@ -58,7 +58,7 @@ const quickFacts = [
|
|||||||
<Reveal>
|
<Reveal>
|
||||||
<p
|
<p
|
||||||
class="border-l-2 pl-5 font-sans text-xl leading-relaxed sm:text-2xl"
|
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
|
I am Greg: a software developer and technical team lead based in
|
||||||
Philadelphia who spends his free time running tabletop RPGs, making
|
Philadelphia who spends his free time running tabletop RPGs, making
|
||||||
@@ -124,7 +124,7 @@ const quickFacts = [
|
|||||||
<Reveal delay={120}>
|
<Reveal delay={120}>
|
||||||
<aside class="card flex flex-col gap-4 lg:sticky lg:top-24">
|
<aside class="card flex flex-col gap-4 lg:sticky lg:top-24">
|
||||||
<h3
|
<h3
|
||||||
class="font-mono text-xs uppercase tracking-[0.22em]"
|
class="font-eyebrow text-xs uppercase tracking-[0.22em]"
|
||||||
style="color: var(--color-accent);"
|
style="color: var(--color-accent);"
|
||||||
>
|
>
|
||||||
Quick facts
|
Quick facts
|
||||||
@@ -155,7 +155,7 @@ const quickFacts = [
|
|||||||
<div class="container-prose">
|
<div class="container-prose">
|
||||||
<AlchemicalDivider
|
<AlchemicalDivider
|
||||||
class="mx-auto h-6 w-full max-w-sm"
|
class="mx-auto h-6 w-full max-w-sm"
|
||||||
style="color: var(--color-accent);"
|
style="color: var(--color-gold);"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
* Placeholder slots.
|
* Placeholder slots.
|
||||||
*/
|
*/
|
||||||
import BaseLayout from "@layouts/BaseLayout.astro";
|
import BaseLayout from "@layouts/BaseLayout.astro";
|
||||||
|
import PageSeal from "@components/brand/PageSeal.astro";
|
||||||
import Reveal from "@components/motion/Reveal.astro";
|
import Reveal from "@components/motion/Reveal.astro";
|
||||||
import Icon from "@components/Icon.astro";
|
import Icon from "@components/Icon.astro";
|
||||||
import SectionHeader from "@components/layout/SectionHeader.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."
|
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}
|
noindex={true}
|
||||||
>
|
>
|
||||||
|
<PageSeal name="zodiac-seal-light" />
|
||||||
<section class="container-prose flex flex-col gap-10 pt-28 sm:pt-32">
|
<section class="container-prose flex flex-col gap-10 pt-28 sm:pt-32">
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<SectionHeader
|
<SectionHeader
|
||||||
@@ -89,7 +91,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
|
|||||||
<Reveal>
|
<Reveal>
|
||||||
<section class="flex flex-col gap-4">
|
<section class="flex flex-col gap-4">
|
||||||
<h2
|
<h2
|
||||||
class="font-mono text-xs uppercase tracking-[0.22em]"
|
class="font-eyebrow text-xs uppercase tracking-[0.22em]"
|
||||||
style="color: var(--color-accent);"
|
style="color: var(--color-accent);"
|
||||||
>
|
>
|
||||||
Professional
|
Professional
|
||||||
@@ -117,7 +119,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
|
|||||||
<span class="text-xs" style="color: var(--color-text-subtle);">
|
<span class="text-xs" style="color: var(--color-text-subtle);">
|
||||||
<ObfuscatedEmail
|
<ObfuscatedEmail
|
||||||
address={emailAddress}
|
address={emailAddress}
|
||||||
class="font-mono uppercase tracking-[0.1em]"
|
class="font-eyebrow uppercase tracking-[0.1em]"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -164,7 +166,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
|
|||||||
<Reveal delay={80}>
|
<Reveal delay={80}>
|
||||||
<section class="flex flex-col gap-4">
|
<section class="flex flex-col gap-4">
|
||||||
<h2
|
<h2
|
||||||
class="font-mono text-xs uppercase tracking-[0.22em]"
|
class="font-eyebrow text-xs uppercase tracking-[0.22em]"
|
||||||
style="color: var(--color-secondary);"
|
style="color: var(--color-secondary);"
|
||||||
>
|
>
|
||||||
Personal / Social
|
Personal / Social
|
||||||
@@ -208,7 +210,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
|
|||||||
<Reveal delay={120}>
|
<Reveal delay={120}>
|
||||||
<p
|
<p
|
||||||
class="rounded-xl border-l-2 p-4 text-sm"
|
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
|
<strong style="color: var(--color-text);">Availability:</strong> Open to
|
||||||
opportunities in AI/ML, app and cloud development, SAP BTP, and technical
|
opportunities in AI/ML, app and cloud development, SAP BTP, and technical
|
||||||
@@ -222,7 +224,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
|
|||||||
<Reveal delay={100}>
|
<Reveal delay={100}>
|
||||||
<section class="card flex flex-col gap-5">
|
<section class="card flex flex-col gap-5">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<h2 class="font-sans" style="color: var(--color-text);">
|
<h2 class="font-display" style="color: var(--color-text);">
|
||||||
Send a message
|
Send a message
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-sm" style="color: var(--color-text-muted);">
|
<p class="text-sm" style="color: var(--color-text-muted);">
|
||||||
@@ -239,7 +241,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
|
|||||||
<Reveal>
|
<Reveal>
|
||||||
<blockquote
|
<blockquote
|
||||||
class="mx-auto max-w-2xl border-l-2 pl-5 text-center font-sans text-lg italic"
|
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
|
Whether you want to talk cloud architecture, debate the best Gundam
|
||||||
series, or need a fourth for game night, reach out.
|
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 SectionHeader from "@components/layout/SectionHeader.astro";
|
||||||
import CelestialImage from "@components/mystic/CelestialImage.astro";
|
import CelestialImage from "@components/mystic/CelestialImage.astro";
|
||||||
import AlchemicalDivider from "@components/mystic/AlchemicalDivider.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";
|
import ConstellationField from "@components/mystic/ConstellationField.tsx";
|
||||||
|
|
||||||
// Snapshot grid content from the spec, with spec emojis mapped to mdi icons.
|
// Snapshot grid content from the spec, with spec emojis mapped to mdi icons.
|
||||||
@@ -52,7 +52,7 @@ const cards = [
|
|||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
<!-- ===================== HERO ===================== -->
|
<!-- ===================== HERO ===================== -->
|
||||||
<section
|
<section
|
||||||
class="vt-hero-sky relative flex min-h-[88vh] items-center overflow-hidden pt-16"
|
class="on-sky vt-hero-sky relative flex min-h-[88vh] items-center overflow-hidden pt-20"
|
||||||
>
|
>
|
||||||
<AnimatedBackground client:idle className="vt-sky" />
|
<AnimatedBackground client:idle className="vt-sky" />
|
||||||
|
|
||||||
@@ -62,8 +62,8 @@ const cards = [
|
|||||||
<div class="flex w-full flex-col gap-10 lg:flex-row lg:items-center">
|
<div class="flex w-full flex-col gap-10 lg:flex-row lg:items-center">
|
||||||
<Reveal as="div" class="max-w-2xl grow">
|
<Reveal as="div" class="max-w-2xl grow">
|
||||||
<span
|
<span
|
||||||
class="mb-5 inline-flex items-center gap-2 rounded-full border px-3 py-1 font-mono text-xs uppercase tracking-[0.18em]"
|
class="mb-5 inline-flex items-center gap-2 rounded-full border px-3 py-1 font-eyebrow text-xs uppercase tracking-[0.18em]"
|
||||||
style="border-color: var(--color-border); color: var(--accent-contrast); background: color-mix(in srgb, var(--color-surface-raised) 75%, transparent);"
|
style="border-color: color-mix(in srgb, var(--color-gold) 40%, transparent); color: var(--color-gold-bright); background: color-mix(in srgb, var(--color-midnight) 75%, transparent);"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="h-2 w-2 rounded-full animate-pulse-glow"
|
class="h-2 w-2 rounded-full animate-pulse-glow"
|
||||||
@@ -82,7 +82,7 @@ const cards = [
|
|||||||
|
|
||||||
<blockquote
|
<blockquote
|
||||||
class="mt-7 max-w-2xl border-l-2 pl-5 text-lg leading-relaxed"
|
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
|
I build things with code during the day and with resin, plastic, and
|
||||||
imagination at night. I lead cloud and mobile development at Implico
|
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. */
|
/* Slow-rotating constellation field behind the section — a slice of sky. */
|
||||||
}
|
}
|
||||||
<ConstellationField client:idle className="opacity-60" />
|
<ConstellationField client:idle className="opacity-60" />
|
||||||
{/* Faint sacred-geometry ornament behind the section. */}
|
{/* Faint transmutation seal behind the section (Starlight recolor). */}
|
||||||
<SacredGeometry
|
<Motif
|
||||||
figure="flower"
|
name="transmutation-seal-light"
|
||||||
class="pointer-events-none absolute -right-40 top-10 z-0 h-[40rem] w-[40rem]"
|
class="absolute -right-40 top-10 z-0 h-[40rem] w-[40rem]"
|
||||||
style="color: var(--color-violet); opacity:.08;"
|
style="opacity:.13;"
|
||||||
/>
|
/>
|
||||||
<div class="container-prose relative z-10 flex flex-col gap-12">
|
<div class="container-prose relative z-10 flex flex-col gap-12">
|
||||||
<Reveal>
|
<Reveal>
|
||||||
@@ -171,7 +171,7 @@ const cards = [
|
|||||||
>
|
>
|
||||||
<Icon name={card.icon} class="h-6 w-6" />
|
<Icon name={card.icon} class="h-6 w-6" />
|
||||||
</span>
|
</span>
|
||||||
<h3 class="font-sans" style="color: var(--color-text);">
|
<h3 class="font-display" style="color: var(--color-text);">
|
||||||
{card.title}
|
{card.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p
|
<p
|
||||||
@@ -197,7 +197,7 @@ const cards = [
|
|||||||
<Reveal>
|
<Reveal>
|
||||||
<AlchemicalDivider
|
<AlchemicalDivider
|
||||||
class="mx-auto h-6 w-full max-w-sm"
|
class="mx-auto h-6 w-full max-w-sm"
|
||||||
style="color: var(--color-accent);"
|
style="color: var(--color-gold);"
|
||||||
/>
|
/>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
|
|
||||||
@@ -221,7 +221,7 @@ const cards = [
|
|||||||
</span>
|
</span>
|
||||||
<span class="flex flex-col gap-1 px-5 py-4 sm:px-0 sm:py-0">
|
<span class="flex flex-col gap-1 px-5 py-4 sm:px-0 sm:py-0">
|
||||||
<span
|
<span
|
||||||
class="font-mono text-xs uppercase tracking-[0.18em]"
|
class="font-eyebrow text-xs uppercase tracking-[0.18em]"
|
||||||
style="color: var(--color-text-subtle);"
|
style="color: var(--color-text-subtle);"
|
||||||
>
|
>
|
||||||
Reading & Media
|
Reading & Media
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ const related = [
|
|||||||
<Icon name={g.icon} class="h-5 w-5" />
|
<Icon name={g.icon} class="h-5 w-5" />
|
||||||
</span>
|
</span>
|
||||||
<h3
|
<h3
|
||||||
class="font-sans text-base"
|
class="font-display text-base"
|
||||||
style="color: var(--color-text);"
|
style="color: var(--color-text);"
|
||||||
>
|
>
|
||||||
{g.name}
|
{g.name}
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
* ---------------------------------------------------------------------------
|
* ---------------------------------------------------------------------------
|
||||||
* The "fun wing" landing page. A warm grid of cards linking into each interest
|
* The "fun wing" landing page. A warm grid of cards linking into each interest
|
||||||
* sub-page, using the spec's taglines. Sets surface="interest" so the whole
|
* sub-page, using the spec's taglines. Sets surface="interest" so the whole
|
||||||
* page reads warm (cream/amber) regardless of the global light/dark toggle.
|
* page reads as the craft-side night sky regardless of the global light/dark toggle.
|
||||||
*/
|
*/
|
||||||
import BaseLayout from "@layouts/BaseLayout.astro";
|
import BaseLayout from "@layouts/BaseLayout.astro";
|
||||||
import Reveal from "@components/motion/Reveal.astro";
|
import Reveal from "@components/motion/Reveal.astro";
|
||||||
import Icon from "@components/Icon.astro";
|
import Icon from "@components/Icon.astro";
|
||||||
import SectionHeader from "@components/layout/SectionHeader.astro";
|
import SectionHeader from "@components/layout/SectionHeader.astro";
|
||||||
import AnimatedBackground from "@components/hero/AnimatedBackground.tsx";
|
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";
|
import ConstellationField from "@components/mystic/ConstellationField.tsx";
|
||||||
|
|
||||||
const interests = [
|
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."
|
description="The fun wing: tabletop RPGs, gaming, reading, resin dice and 3D printing, and Pokémon. Where Greg's passions live as stories and collections."
|
||||||
>
|
>
|
||||||
<!-- Hero -->
|
<!-- Hero -->
|
||||||
<section class="relative overflow-hidden pb-8 pt-28 sm:pt-32">
|
<section class="on-sky relative overflow-hidden pb-8 pt-28 sm:pt-32">
|
||||||
{
|
{
|
||||||
/* Full animated Milky Way field + stars, matching the Home hero treatment. */
|
/* Full animated Milky Way field + stars, matching the Home hero treatment. */
|
||||||
}
|
}
|
||||||
@@ -137,10 +137,10 @@ const interests = [
|
|||||||
<!-- Interest grid -->
|
<!-- Interest grid -->
|
||||||
<section class="relative overflow-hidden py-12 sm:py-16">
|
<section class="relative overflow-hidden py-12 sm:py-16">
|
||||||
<ConstellationField client:idle className="opacity-45" tint="teal" />
|
<ConstellationField client:idle className="opacity-45" tint="teal" />
|
||||||
<SacredGeometry
|
<Motif
|
||||||
figure="metatron"
|
name="tria-prima-array-seal-light"
|
||||||
class="pointer-events-none absolute -left-48 bottom-0 z-0 h-[38rem] w-[38rem]"
|
class="absolute -left-48 bottom-0 z-0 h-[38rem] w-[38rem]"
|
||||||
style="color: var(--color-accent); opacity:.07;"
|
style="opacity:.12;"
|
||||||
/>
|
/>
|
||||||
<div class="container-prose relative z-10">
|
<div class="container-prose relative z-10">
|
||||||
<div class="grid gap-5 md:grid-cols-2 lg:grid-cols-3">
|
<div class="grid gap-5 md:grid-cols-2 lg:grid-cols-3">
|
||||||
@@ -176,7 +176,7 @@ const interests = [
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<h3 class="font-sans" style="color: var(--color-text);">
|
<h3 class="font-display" style="color: var(--color-text);">
|
||||||
{item.title}
|
{item.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p
|
<p
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ const related = [
|
|||||||
<Icon name="robot" class="h-10 w-10" />
|
<Icon name="robot" class="h-10 w-10" />
|
||||||
</span>
|
</span>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<span class="font-mono text-xs uppercase tracking-[0.22em]" style="color: var(--color-accent);">Favorite Pokémon</span>
|
<span class="font-eyebrow text-xs uppercase tracking-[0.22em]" style="color: var(--color-accent);">Favorite Pokémon</span>
|
||||||
<h2 style="color: var(--color-text);">Metagross</h2>
|
<h2 style="color: var(--color-text);">Metagross</h2>
|
||||||
<p class="text-sm" style="color: var(--color-text-muted);">Steel / Psychic. The iron leg Pokémon.</p>
|
<p class="text-sm" style="color: var(--color-text-muted);">Steel / Psychic. The iron leg Pokémon.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ const related = [
|
|||||||
<Reveal delay={80}>
|
<Reveal delay={80}>
|
||||||
<blockquote
|
<blockquote
|
||||||
class="border-l-2 pl-5 text-lg leading-relaxed"
|
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.
|
Beyond RPGs, I maintain a growing board and card game collection.
|
||||||
Favorites span strategy, deck-building, and social deduction. Game
|
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 EducationHistory from "@components/professional/EducationHistory.astro";
|
||||||
import Certifications from "@components/professional/Certifications.astro";
|
import Certifications from "@components/professional/Certifications.astro";
|
||||||
import AlchemicalDivider from "@components/mystic/AlchemicalDivider.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 CircuitField from "@components/mystic/CircuitField.tsx";
|
||||||
import CelestialFrame from "@components/mystic/CelestialFrame.astro";
|
import CelestialFrame from "@components/mystic/CelestialFrame.astro";
|
||||||
import ObfuscatedEmail from "@components/ObfuscatedEmail.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
|
puts its center on the boundary: half in the hero, half over the next
|
||||||
section, in front of both (z-50). */
|
section, in front of both (z-50). */
|
||||||
}
|
}
|
||||||
<section class="relative overflow-visible pt-28 pb-28 sm:pt-32 sm:pb-36">
|
<section class="on-sky relative overflow-visible pt-28 pb-28 sm:pt-32 sm:pb-36">
|
||||||
{
|
{
|
||||||
/* Background layer — clipped to the hero so the circuit canvas/astrolabe
|
/* Background layer — clipped to the hero so the circuit canvas/astrolabe
|
||||||
don't bleed into the content below. */
|
don't bleed into the content below. */
|
||||||
}
|
}
|
||||||
<div class="pointer-events-none absolute inset-0 overflow-hidden">
|
<div class="pointer-events-none absolute inset-0 overflow-hidden">
|
||||||
<CircuitField client:idle className="vt-sky opacity-100" />
|
<CircuitField client:idle className="vt-sky opacity-100" />
|
||||||
<Astrolabe
|
<Motif
|
||||||
class="absolute z-0 -right-48 -top-24 h-[34rem] w-[34rem] animate-spin-slow"
|
name="astrolabe"
|
||||||
style="color: var(--color-accent); opacity:.13;"
|
class="absolute z-0 -right-40 -top-24 h-[38rem] w-[34rem] object-contain"
|
||||||
|
style="opacity:.2;"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -130,7 +131,7 @@ const projects = (await getCollection("projects")).sort(
|
|||||||
<Reveal delay={80}>
|
<Reveal delay={80}>
|
||||||
<blockquote
|
<blockquote
|
||||||
class="border-l-2 pl-5 text-lg leading-relaxed"
|
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
|
Software developer and team lead with deep experience in cloud and
|
||||||
mobile application development, currently focused on SAP-adjacent
|
mobile application development, currently focused on SAP-adjacent
|
||||||
@@ -145,7 +146,7 @@ const projects = (await getCollection("projects")).sort(
|
|||||||
<Reveal delay={120}>
|
<Reveal delay={120}>
|
||||||
<figure
|
<figure
|
||||||
class="relative overflow-hidden rounded-2xl border"
|
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
|
<img
|
||||||
src="/images/pro-workspace.jpg"
|
src="/images/pro-workspace.jpg"
|
||||||
@@ -155,8 +156,8 @@ const projects = (await getCollection("projects")).sort(
|
|||||||
class="aspect-[21/9] w-full object-cover"
|
class="aspect-[21/9] w-full object-cover"
|
||||||
/>
|
/>
|
||||||
<figcaption
|
<figcaption
|
||||||
class="absolute bottom-0 left-0 m-3 rounded-md px-3 py-1 font-mono text-[0.65rem] uppercase tracking-[0.18em] backdrop-blur-sm"
|
class="absolute bottom-0 left-0 m-3 rounded-md px-3 py-1 font-eyebrow text-[0.65rem] uppercase tracking-[0.18em] backdrop-blur-sm"
|
||||||
style="background: color-mix(in srgb, var(--color-ink) 60%, transparent); color: var(--color-cream);"
|
style="background: color-mix(in srgb, var(--color-midnight) 60%, transparent); color: var(--color-starlight);"
|
||||||
>
|
>
|
||||||
Where the day-job work happens
|
Where the day-job work happens
|
||||||
</figcaption>
|
</figcaption>
|
||||||
@@ -167,7 +168,7 @@ const projects = (await getCollection("projects")).sort(
|
|||||||
<Reveal>
|
<Reveal>
|
||||||
<AlchemicalDivider
|
<AlchemicalDivider
|
||||||
class="mx-auto h-6 w-full max-w-sm"
|
class="mx-auto h-6 w-full max-w-sm"
|
||||||
style="color: var(--color-accent);"
|
style="color: var(--color-gold);"
|
||||||
/>
|
/>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
|
|
||||||
@@ -233,7 +234,7 @@ const projects = (await getCollection("projects")).sort(
|
|||||||
<Reveal delay={60}>
|
<Reveal delay={60}>
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<h3
|
<h3
|
||||||
class="font-mono text-xs uppercase tracking-[0.22em]"
|
class="font-eyebrow text-xs uppercase tracking-[0.22em]"
|
||||||
style="color: var(--color-accent);"
|
style="color: var(--color-accent);"
|
||||||
>
|
>
|
||||||
Education history
|
Education history
|
||||||
@@ -244,7 +245,7 @@ const projects = (await getCollection("projects")).sort(
|
|||||||
<Reveal delay={120}>
|
<Reveal delay={120}>
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<h3
|
<h3
|
||||||
class="font-mono text-xs uppercase tracking-[0.22em]"
|
class="font-eyebrow text-xs uppercase tracking-[0.22em]"
|
||||||
style="color: var(--color-accent);"
|
style="color: var(--color-accent);"
|
||||||
>
|
>
|
||||||
Certifications
|
Certifications
|
||||||
@@ -255,7 +256,14 @@ const projects = (await getCollection("projects")).sort(
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ===== 6. Contact ===== */}
|
{/* ===== 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>
|
<Reveal>
|
||||||
<SectionHeader
|
<SectionHeader
|
||||||
kicker="06"
|
kicker="06"
|
||||||
@@ -275,7 +283,7 @@ const projects = (await getCollection("projects")).sort(
|
|||||||
/>
|
/>
|
||||||
<ObfuscatedEmail
|
<ObfuscatedEmail
|
||||||
address="contact@gregkilmer.com"
|
address="contact@gregkilmer.com"
|
||||||
class="font-mono text-xs uppercase tracking-[0.18em]"
|
class="font-eyebrow text-xs uppercase tracking-[0.18em]"
|
||||||
style="color: var(--color-accent);"
|
style="color: var(--color-accent);"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
@@ -286,7 +294,7 @@ const projects = (await getCollection("projects")).sort(
|
|||||||
style="color: var(--color-accent);"
|
style="color: var(--color-accent);"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
class="font-mono text-xs uppercase tracking-[0.18em]"
|
class="font-eyebrow text-xs uppercase tracking-[0.18em]"
|
||||||
style="color: var(--color-accent);"
|
style="color: var(--color-accent);"
|
||||||
><a
|
><a
|
||||||
href="https://www.linkedin.com/in/gregory-kilmer-36646bb5/"
|
href="https://www.linkedin.com/in/gregory-kilmer-36646bb5/"
|
||||||
@@ -302,7 +310,7 @@ const projects = (await getCollection("projects")).sort(
|
|||||||
style="color: var(--color-accent);"
|
style="color: var(--color-accent);"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
class="font-mono text-xs uppercase tracking-[0.18em]"
|
class="font-eyebrow text-xs uppercase tracking-[0.18em]"
|
||||||
style="color: var(--color-accent);"
|
style="color: var(--color-accent);"
|
||||||
><a
|
><a
|
||||||
href="https://github.com/patiun"
|
href="https://github.com/patiun"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
* the carousel only.
|
* the carousel only.
|
||||||
*/
|
*/
|
||||||
import StoreLayout from '@layouts/StoreLayout.astro';
|
import StoreLayout from '@layouts/StoreLayout.astro';
|
||||||
|
import PageSeal from "@components/brand/PageSeal.astro";
|
||||||
import Reveal from '@components/motion/Reveal.astro';
|
import Reveal from '@components/motion/Reveal.astro';
|
||||||
import SectionHeader from '@components/layout/SectionHeader.astro';
|
import SectionHeader from '@components/layout/SectionHeader.astro';
|
||||||
import Icon from '@components/Icon.astro';
|
import Icon from '@components/Icon.astro';
|
||||||
@@ -37,13 +38,14 @@ const keyReqs = requirements.filter((r) =>
|
|||||||
title="Proxy MTG Cards"
|
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."
|
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">
|
<div class="container-prose flex flex-col gap-14 py-28 sm:py-32">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<section class="flex flex-col gap-5">
|
<section class="flex flex-col gap-5">
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<a
|
<a
|
||||||
href="/store"
|
href="/store"
|
||||||
class="inline-flex items-center gap-1.5 font-mono text-xs uppercase tracking-[0.18em]"
|
class="inline-flex items-center gap-1.5 font-eyebrow text-xs uppercase tracking-[0.18em]"
|
||||||
style="color: var(--color-accent);"
|
style="color: var(--color-accent);"
|
||||||
>
|
>
|
||||||
<Icon name="arrow-left" class="h-4 w-4" />
|
<Icon name="arrow-left" class="h-4 w-4" />
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
* (deckBoxProduct.images). The specs block is image-free now; examples live in
|
* (deckBoxProduct.images). The specs block is image-free now; examples live in
|
||||||
* the carousel only.
|
* the carousel only.
|
||||||
*/
|
*/
|
||||||
|
import PageSeal from "@components/brand/PageSeal.astro";
|
||||||
import StoreLayout from "@layouts/StoreLayout.astro";
|
import StoreLayout from "@layouts/StoreLayout.astro";
|
||||||
import Reveal from "@components/motion/Reveal.astro";
|
import Reveal from "@components/motion/Reveal.astro";
|
||||||
import SectionHeader from "@components/layout/SectionHeader.astro";
|
import SectionHeader from "@components/layout/SectionHeader.astro";
|
||||||
@@ -36,13 +37,14 @@ const keyReqs = requirements.filter(
|
|||||||
title="3D-Printed Deck Boxes & Counters"
|
title="3D-Printed Deck Boxes & Counters"
|
||||||
description="3D-printed deck boxes, life counters, and tokens. Specs and pricing for printed storage and trackers."
|
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">
|
<div class="container-prose flex flex-col gap-14 py-28 sm:py-32">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<section class="flex flex-col gap-5">
|
<section class="flex flex-col gap-5">
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<a
|
<a
|
||||||
href="/store"
|
href="/store"
|
||||||
class="inline-flex items-center gap-1.5 font-mono text-xs uppercase tracking-[0.18em]"
|
class="inline-flex items-center gap-1.5 font-eyebrow text-xs uppercase tracking-[0.18em]"
|
||||||
style="color: var(--color-accent);"
|
style="color: var(--color-accent);"
|
||||||
>
|
>
|
||||||
<Icon name="arrow-left" class="h-4 w-4" />
|
<Icon name="arrow-left" class="h-4 w-4" />
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const orderHref = "/store/order";
|
|||||||
{/* pt-36/sm:pt-40 gives the bright hero content clear breathing room below
|
{/* pt-36/sm:pt-40 gives the bright hero content clear breathing room below
|
||||||
the solid fixed StoreNav (64px tall), so the gold label and nav never
|
the solid fixed StoreNav (64px tall), so the gold label and nav never
|
||||||
visually crowd each other. */}
|
visually crowd each other. */}
|
||||||
<section class="relative overflow-hidden pb-8 pt-36 sm:pt-40">
|
<section class="on-sky relative overflow-hidden pb-8 pt-36 sm:pt-40">
|
||||||
<AnimatedBackground client:idle className="vt-sky" />
|
<AnimatedBackground client:idle className="vt-sky" />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -44,11 +44,11 @@ const orderHref = "/store/order";
|
|||||||
<div class="flex flex-col items-start gap-4">
|
<div class="flex flex-col items-start gap-4">
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<span
|
<span
|
||||||
class="inline-flex items-center gap-1.5 font-mono text-xs uppercase tracking-[0.18em]"
|
class="inline-flex items-center gap-1.5 font-eyebrow text-sm tracking-[0.06em]"
|
||||||
style="color: var(--color-accent);"
|
style="color: var(--color-accent);"
|
||||||
>
|
>
|
||||||
<Icon name="store" class="h-4 w-4" />
|
<Icon name="store" class="h-4 w-4" />
|
||||||
store.gregkilmer.com
|
Store.GregKilmer.com
|
||||||
</span>
|
</span>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
|
|
||||||
@@ -64,8 +64,8 @@ const orderHref = "/store/order";
|
|||||||
|
|
||||||
<Reveal delay={100}>
|
<Reveal delay={100}>
|
||||||
<h2
|
<h2
|
||||||
class="font-sans font-bold tracking-tight"
|
class="font-display font-bold"
|
||||||
style="color: var(--color-cream); font-size: clamp(1.75rem, 2vw + 1rem, 2.75rem); line-height: 1.15;"
|
style="color: var(--color-starlight); font-size: clamp(1.75rem, 2vw + 1rem, 2.75rem); line-height: 1.15;"
|
||||||
>
|
>
|
||||||
Proxy MTG cards & 3D-printed deck boxes
|
Proxy MTG cards & 3D-printed deck boxes
|
||||||
</h2>
|
</h2>
|
||||||
@@ -74,7 +74,7 @@ const orderHref = "/store/order";
|
|||||||
<Reveal delay={140}>
|
<Reveal delay={140}>
|
||||||
<p
|
<p
|
||||||
class="max-w-xl border-l-2 pl-4 font-sans text-base italic sm:text-lg"
|
class="max-w-xl border-l-2 pl-4 font-sans text-base italic sm:text-lg"
|
||||||
style="border-color: var(--color-accent); color: var(--color-cream); opacity: 0.92;"
|
style="border-color: var(--color-gold); color: var(--color-starlight); opacity: 0.92;"
|
||||||
>
|
>
|
||||||
Clearly-marked proxies for casual play, and matching 3D-printed
|
Clearly-marked proxies for casual play, and matching 3D-printed
|
||||||
storage & counters. Hand-made in small batches.
|
storage & counters. Hand-made in small batches.
|
||||||
@@ -92,7 +92,7 @@ const orderHref = "/store/order";
|
|||||||
<Reveal delay={80} class="w-full">
|
<Reveal delay={80} class="w-full">
|
||||||
<figure
|
<figure
|
||||||
class="relative w-full overflow-hidden rounded-2xl border"
|
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
|
<img
|
||||||
src="/images/proxies.jpg"
|
src="/images/proxies.jpg"
|
||||||
@@ -102,8 +102,8 @@ const orderHref = "/store/order";
|
|||||||
class="aspect-[4/3] w-full object-cover"
|
class="aspect-[4/3] w-full object-cover"
|
||||||
/>
|
/>
|
||||||
<StarBurst
|
<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)]"
|
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-accent);"
|
style="color: var(--color-gold);"
|
||||||
/>
|
/>
|
||||||
</figure>
|
</figure>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
@@ -228,7 +228,7 @@ const orderHref = "/store/order";
|
|||||||
>
|
>
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<h3
|
<h3
|
||||||
class="font-sans text-2xl font-bold"
|
class="font-display text-2xl font-bold"
|
||||||
style="color: var(--color-text);"
|
style="color: var(--color-text);"
|
||||||
>
|
>
|
||||||
Ready to put in a request?
|
Ready to put in a request?
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
* closed, so the gate can't be bypassed by POSTing directly.
|
* closed, so the gate can't be bypassed by POSTing directly.
|
||||||
*/
|
*/
|
||||||
import StoreLayout from '@layouts/StoreLayout.astro';
|
import StoreLayout from '@layouts/StoreLayout.astro';
|
||||||
|
import PageSeal from "@components/brand/PageSeal.astro";
|
||||||
import Reveal from '@components/motion/Reveal.astro';
|
import Reveal from '@components/motion/Reveal.astro';
|
||||||
import SectionHeader from '@components/layout/SectionHeader.astro';
|
import SectionHeader from '@components/layout/SectionHeader.astro';
|
||||||
import Icon from '@components/Icon.astro';
|
import Icon from '@components/Icon.astro';
|
||||||
@@ -31,12 +32,13 @@ const siteKey = turnstileSiteKey();
|
|||||||
title="Order Request"
|
title="Order Request"
|
||||||
description="Submit a proxy-card or 3D-print order request. No payment is taken through the site."
|
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">
|
<div class="container-prose flex flex-col gap-14 py-28 sm:py-32">
|
||||||
<section class="flex flex-col gap-5">
|
<section class="flex flex-col gap-5">
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<a
|
<a
|
||||||
href="/store"
|
href="/store"
|
||||||
class="inline-flex items-center gap-1.5 font-mono text-xs uppercase tracking-[0.18em]"
|
class="inline-flex items-center gap-1.5 font-eyebrow text-xs uppercase tracking-[0.18em]"
|
||||||
style="color: var(--color-accent);"
|
style="color: var(--color-accent);"
|
||||||
>
|
>
|
||||||
<Icon name="arrow-left" class="h-4 w-4" />
|
<Icon name="arrow-left" class="h-4 w-4" />
|
||||||
@@ -79,7 +81,7 @@ const siteKey = turnstileSiteKey();
|
|||||||
{open ? 'Orders are open. Slots are limited.' : 'Orders are currently closed.'}
|
{open ? 'Orders are open. Slots are limited.' : 'Orders are currently closed.'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="font-mono text-xs" style="color: var(--color-text-subtle);">
|
<span class="font-eyebrow text-xs" style="color: var(--color-text-subtle);">
|
||||||
Updated {av.lastUpdated}
|
Updated {av.lastUpdated}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -91,7 +93,7 @@ const siteKey = turnstileSiteKey();
|
|||||||
<section class="flex flex-col gap-8 lg:grid lg:grid-cols-[1.6fr_1fr] lg:items-start">
|
<section class="flex flex-col gap-8 lg:grid lg:grid-cols-[1.6fr_1fr] lg:items-start">
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<div class="card flex flex-col gap-2">
|
<div class="card flex flex-col gap-2">
|
||||||
<h3 class="font-sans text-lg font-bold" style="color: var(--color-text);">Your request</h3>
|
<h3 class="font-display text-lg font-bold" style="color: var(--color-text);">Your request</h3>
|
||||||
<p class="text-sm" style="color: var(--color-text-muted);">
|
<p class="text-sm" style="color: var(--color-text-muted);">
|
||||||
Fill in the details below. Remember: the card list is final once received, and
|
Fill in the details below. Remember: the card list is final once received, and
|
||||||
double-sided cards count as 2 slots.
|
double-sided cards count as 2 slots.
|
||||||
@@ -118,7 +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">
|
<span class="inline-flex h-12 w-12 items-center justify-center rounded-full" style="background: color-mix(in srgb, #e55 20%, transparent); color: #e55;" aria-hidden="true">
|
||||||
<Icon name="lock" class="h-6 w-6" />
|
<Icon name="lock" class="h-6 w-6" />
|
||||||
</span>
|
</span>
|
||||||
<h3 class="font-sans text-xl font-bold" style="color: var(--color-text);">Orders are not being taken right now</h3>
|
<h3 class="font-display text-xl font-bold" style="color: var(--color-text);">Orders are not being taken right now</h3>
|
||||||
<p class="max-w-md text-sm" style="color: var(--color-text-muted);">
|
<p class="max-w-md text-sm" style="color: var(--color-text-muted);">
|
||||||
I open a limited number of slots at a time. Check back here — the meters above
|
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
|
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.
|
* section wrapper (`data-surface`) instantly restyles every component.
|
||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
@theme {
|
@theme {
|
||||||
/* Brand colors (fixed, do not change with theme). Night-sky mystical set:
|
/* Brand corner radius: cards and panels are 10px; buttons and tags are pills. */
|
||||||
* star gold, mystic violet, nebula teal, midnight indigo, star-white. */
|
--radius-xl: 10px;
|
||||||
--color-amber: #e8b54a; /* star gold */
|
--radius-2xl: 10px;
|
||||||
--color-amber-soft: #f4cd72; /* brighter gold for hovers */
|
--radius-3xl: 10px;
|
||||||
--color-violet: #8b7bd8; /* mystic violet */
|
|
||||||
--color-violet-deep: #6f5fb6;
|
/* Brand colors (fixed, do not change with theme). The Patiun LLC palette,
|
||||||
--color-teal: #6fb6ad; /* nebula teal */
|
* shared by Patiun (craft) and Greg Kilmer (web). Source of truth: the
|
||||||
--color-teal-deep: #4f8c84;
|
* Patiun brand record / Claude Design tokens. Gold is structure and sparkle
|
||||||
--color-slate: #161b3a; /* deep indigo-slate */
|
* (stars, rules, small labels), never a large fill. Violet and indigo are
|
||||||
--color-ink: #0b1026; /* midnight indigo */
|
* background light only (nebula glows, gradient ends), never text. */
|
||||||
--color-cream: #f4f1e8; /* star-white */
|
--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). */
|
/* Semantic tokens (resolve per theme/section via variables). */
|
||||||
--color-surface: var(--surface);
|
--color-surface: var(--surface);
|
||||||
@@ -53,9 +64,16 @@
|
|||||||
--color-secondary: var(--secondary);
|
--color-secondary: var(--secondary);
|
||||||
|
|
||||||
/* Typography. */
|
/* Typography. */
|
||||||
--font-sans: "Plus Jakarta Sans Variable", "Inter Variable", ui-sans-serif,
|
/* Typography. Cinzel for names, wordmarks and headings (never paragraphs);
|
||||||
system-ui, sans-serif;
|
* Montserrat for body, UI, labels and uppercase eyebrows. JetBrains Mono is
|
||||||
--font-body: "Inter Variable", ui-sans-serif, system-ui, sans-serif;
|
* kept only for genuinely code-like blocks. */
|
||||||
|
--font-display: "Cinzel Variable", Cinzel, "Trajan Pro", Georgia, serif;
|
||||||
|
--font-sans: "Montserrat Variable", Montserrat, "Helvetica Neue", Arial,
|
||||||
|
ui-sans-serif, sans-serif;
|
||||||
|
--font-body: "Montserrat Variable", Montserrat, "Helvetica Neue", Arial,
|
||||||
|
ui-sans-serif, sans-serif;
|
||||||
|
--font-eyebrow: "Montserrat Variable", Montserrat, "Helvetica Neue", Arial,
|
||||||
|
ui-sans-serif, sans-serif;
|
||||||
--font-mono: "JetBrains Mono Variable", ui-monospace, "SFMono-Regular", monospace;
|
--font-mono: "JetBrains Mono Variable", ui-monospace, "SFMono-Regular", monospace;
|
||||||
|
|
||||||
/* Motion tokens (used across reveal + transition classes). */
|
/* Motion tokens (used across reveal + transition classes). */
|
||||||
@@ -107,10 +125,11 @@
|
|||||||
h2,
|
h2,
|
||||||
h3,
|
h3,
|
||||||
h4 {
|
h4 {
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-display);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.15;
|
line-height: 1.15;
|
||||||
letter-spacing: -0.02em;
|
/* Cinzel is a capital-led display face: open tracking, never negative. */
|
||||||
|
letter-spacing: 0.04em;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +150,7 @@
|
|||||||
transition: color 0.2s var(--ease-out-soft);
|
transition: color 0.2s var(--ease-out-soft);
|
||||||
}
|
}
|
||||||
a:hover {
|
a:hover {
|
||||||
color: var(--color-amber-soft);
|
color: var(--color-gold-bright);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Visible, on-brand focus ring for keyboard users. */
|
/* Visible, on-brand focus ring for keyboard users. */
|
||||||
@@ -142,8 +161,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
background: var(--color-amber);
|
background: var(--color-teal);
|
||||||
color: var(--color-ink);
|
color: var(--color-midnight);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Better default for images: never overflow, lazy by default in components. */
|
/* Better default for images: never overflow, lazy by default in components. */
|
||||||
@@ -175,7 +194,7 @@
|
|||||||
.card {
|
.card {
|
||||||
background-color: var(--color-surface-raised);
|
background-color: var(--color-surface-raised);
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
border-radius: 1rem;
|
border-radius: 10px;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
transition:
|
transition:
|
||||||
transform 0.3s var(--ease-out-soft),
|
transform 0.3s var(--ease-out-soft),
|
||||||
@@ -184,7 +203,7 @@
|
|||||||
}
|
}
|
||||||
.card-interactive:hover {
|
.card-interactive:hover {
|
||||||
transform: translateY(-4px);
|
transform: translateY(-4px);
|
||||||
box-shadow: 0 18px 40px -24px rgba(11, 16, 38, 0.55);
|
box-shadow: 0 18px 40px -24px rgba(14, 21, 34, 0.6);
|
||||||
border-color: var(--color-accent);
|
border-color: var(--color-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,14 +221,14 @@
|
|||||||
border: 1px solid color-mix(in srgb, var(--color-accent) 35%, transparent);
|
border: 1px solid color-mix(in srgb, var(--color-accent) 35%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Primary (amber) and secondary (outlined) CTA buttons. */
|
/* Primary (Patiun Teal) and secondary (outlined) CTA buttons. */
|
||||||
.btn {
|
.btn {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
padding: 0.75rem 1.5rem;
|
padding: 0.75rem 1.5rem;
|
||||||
border-radius: 0.6rem;
|
border-radius: 999px;
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -224,14 +243,15 @@
|
|||||||
.btn:active {
|
.btn:active {
|
||||||
transform: translateY(1px);
|
transform: translateY(1px);
|
||||||
}
|
}
|
||||||
|
/* Filled buttons: Patiun Teal with Midnight text, never white text. */
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background: var(--color-amber);
|
background: var(--color-teal);
|
||||||
color: var(--color-ink);
|
color: var(--color-midnight);
|
||||||
box-shadow: 0 10px 30px -12px rgba(245, 158, 11, 0.7);
|
box-shadow: 0 10px 30px -12px rgba(20, 163, 154, 0.7);
|
||||||
}
|
}
|
||||||
.btn-primary:hover {
|
.btn-primary:hover {
|
||||||
background: var(--color-amber-soft);
|
background: var(--color-teal-bright);
|
||||||
color: var(--color-ink);
|
color: var(--color-midnight);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
@@ -246,6 +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. */
|
/* Visually hidden but available to screen readers. */
|
||||||
.sr-only {
|
.sr-only {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -5,9 +5,14 @@
|
|||||||
*
|
*
|
||||||
* 1. Global light/dark mode -> set on <html data-theme="light|dark">
|
* 1. Global light/dark mode -> set on <html data-theme="light|dark">
|
||||||
* 2. Section surface tone -> set on <body data-surface="...">
|
* 2. Section surface tone -> set on <body data-surface="...">
|
||||||
* data-surface="neutral" Home / About / Contact (deep night-sky)
|
* data-surface="neutral" Home / About / Contact (Patiun night sky)
|
||||||
* data-surface="professional" Professional wing (obsidian + violet/gold)
|
* data-surface="professional" Professional wing (Tide: slate-teal night)
|
||||||
* data-surface="interest" Interests wing (candlelit study)
|
* data-surface="interest" Interests wing (Nebula: indigo night)
|
||||||
|
*
|
||||||
|
* PALETTE: every value here comes from the Patiun brand (Night sky theme for
|
||||||
|
* dark, Starlight theme for light). Text uses Starlight tones, links and
|
||||||
|
* accents use teal, small gold labels use the bright gold, and violet/indigo
|
||||||
|
* only tint the surfaces (never text).
|
||||||
*
|
*
|
||||||
* SELECTOR MODEL: data-theme lives on <html>, data-surface on <body>. We match
|
* SELECTOR MODEL: data-theme lives on <html>, data-surface on <body>. We match
|
||||||
* the descendant combination `html[data-theme=...] body[data-surface=...]`.
|
* the descendant combination `html[data-theme=...] body[data-surface=...]`.
|
||||||
@@ -26,93 +31,99 @@
|
|||||||
* DEFAULT / NEUTRAL SURFACE (Home, About, Contact)
|
* DEFAULT / NEUTRAL SURFACE (Home, About, Contact)
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|
||||||
/* Light + neutral — fallback (not currently used; dark is forced). */
|
/* Light + neutral: Starlight paper (fallback; not currently used, dark is
|
||||||
|
* forced in theme-init, but these are the values the content sections
|
||||||
|
* actually render with, since the Tailwind tokens resolve at :root). The
|
||||||
|
* brand's deep teal #0F8C84 is 3.7:1 on Starlight, so --accent uses a
|
||||||
|
* slightly darker teal that passes AA for small text. */
|
||||||
:root,
|
:root,
|
||||||
html[data-theme='light'] body[data-surface='neutral'] {
|
html[data-theme='light'] body[data-surface='neutral'] {
|
||||||
--surface: #efe7d2;
|
--surface: #f4f2ed; /* Starlight */
|
||||||
--surface-raised: #f7f0dc;
|
--surface-raised: #ffffff;
|
||||||
--surface-sunken: #e3d9bd;
|
--surface-sunken: #e9e5dc;
|
||||||
--text: #1b1730;
|
--text: #131c2b; /* Midnight */
|
||||||
--text-muted: #4a445e;
|
--text-muted: #4a5f6b;
|
||||||
--text-subtle: #6b6582;
|
--text-subtle: #5c6e78;
|
||||||
--border: #d2c6a2;
|
--border: #d9d4c8;
|
||||||
--accent: #7e5512;
|
--accent: #0a726b; /* deep teal, darkened to 5.2:1 so small links pass AA */
|
||||||
--accent-contrast: #f7f0dc;
|
--accent-contrast: #f4f2ed;
|
||||||
--secondary: #5e4f9e;
|
--secondary: #8a6516; /* deep gold, 4.75:1: small gold text and eyebrows */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark + neutral — the canonical deep mystical nightscape. High-contrast text. */
|
/* Dark + neutral: the canonical Patiun night sky. High-contrast text. */
|
||||||
html[data-theme='dark'] body[data-surface='neutral'] {
|
html[data-theme='dark'] body[data-surface='neutral'] {
|
||||||
--surface: #070a1c; /* near-void midnight indigo */
|
--surface: #0e1522; /* night sky */
|
||||||
--surface-raised: #11163a; /* indigo panel, violet-edged */
|
--surface-raised: #131c2b; /* Midnight panel */
|
||||||
--surface-sunken: #04061a; /* deep cosmic void */
|
--surface-sunken: #0a1019; /* deeper void */
|
||||||
--text: #f6f4ff; /* near-white for max legibility */
|
--text: #ece9e2; /* Starlight text, 15:1 */
|
||||||
--text-muted: #d4d0f0; /* bright enough to read clearly on deep blue */
|
--text-muted: #b9c8cf;
|
||||||
--text-subtle: #a8a2d8; /* lifted from the old #7e79b8 for contrast */
|
--text-subtle: #9fb3bd; /* brand muted, 7.9:1 on panels */
|
||||||
--border: #2e3470;
|
--border: #26344a;
|
||||||
--accent: #f0c262; /* slightly lifted gold for pop on dark */
|
--accent: #2bc2b7; /* teal on dark, 7.7:1 */
|
||||||
--accent-contrast: #070a1c;
|
--accent-contrast: #0e1522;
|
||||||
--secondary: #b3a4ec; /* brighter violet, easier on the eye */
|
--secondary: #d8b35a; /* gold accents and chips */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===========================================================================
|
/* ===========================================================================
|
||||||
* PROFESSIONAL SURFACE — violet-dominant obsidian, dark by default.
|
* PROFESSIONAL SURFACE: the web side. A slate-teal night (the Tide gradient),
|
||||||
|
* dark by default.
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|
||||||
body[data-surface='professional'],
|
body[data-surface='professional'],
|
||||||
html[data-theme='dark'] body[data-surface='professional'] {
|
html[data-theme='dark'] body[data-surface='professional'] {
|
||||||
--surface: #080a20;
|
--surface: #0c1820;
|
||||||
--surface-raised: #121638;
|
--surface-raised: #12222c;
|
||||||
--surface-sunken: #040517;
|
--surface-sunken: #081117;
|
||||||
--text: #f6f4ff;
|
--text: #ece9e2;
|
||||||
--text-muted: #cec9f2;
|
--text-muted: #b9c8cf;
|
||||||
--text-subtle: #a59fd6;
|
--text-subtle: #9fb3bd;
|
||||||
--border: #2e3270;
|
--border: #23394a;
|
||||||
--accent: #f0c262;
|
--accent: #2bc2b7;
|
||||||
--accent-contrast: #080a20;
|
--accent-contrast: #0c1820;
|
||||||
--secondary: #b3a4ec;
|
--secondary: #d8b35a;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-theme='light'] body[data-surface='professional'] {
|
html[data-theme='light'] body[data-surface='professional'] {
|
||||||
--surface: #21183f;
|
--surface: #2d4a58; /* Slate Teal: the professional wing stays dark */
|
||||||
--surface-raised: #2c2152;
|
--surface-raised: #355766;
|
||||||
--surface-sunken: #18113a;
|
--surface-sunken: #243c48;
|
||||||
--text: #f1edff;
|
--text: #f4f2ed;
|
||||||
--text-muted: #d5cff0;
|
--text-muted: #d9e2e6;
|
||||||
--text-subtle: #aaa3d6;
|
--text-subtle: #bccbd2;
|
||||||
--border: #3c3170;
|
--border: #46687a;
|
||||||
--accent: #7e5512;
|
--accent: #7fdcd5;
|
||||||
--accent-contrast: #f1edff;
|
--accent-contrast: #131c2b;
|
||||||
--secondary: #b6a6e8;
|
--secondary: #e2c57e;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===========================================================================
|
/* ===========================================================================
|
||||||
* INTEREST SURFACE — warm plum-brown night, dark by default.
|
* INTEREST SURFACE: the craft side. An indigo night lit by the Nebula
|
||||||
|
* gradient, dark by default.
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|
||||||
body[data-surface='interest'],
|
body[data-surface='interest'],
|
||||||
html[data-theme='dark'] body[data-surface='interest'] {
|
html[data-theme='dark'] body[data-surface='interest'] {
|
||||||
--surface: #170f24; /* warm plum-brown night */
|
--surface: #110f22; /* indigo night */
|
||||||
--surface-raised: #221630;
|
--surface-raised: #1a1730;
|
||||||
--surface-sunken: #100920;
|
--surface-sunken: #0b0a18;
|
||||||
--text: #fbf2e4; /* warm near-white */
|
--text: #ece9e2;
|
||||||
--text-muted: #ecdcc6; /* lifted for contrast */
|
--text-muted: #c3bfd6;
|
||||||
--text-subtle: #c4ab8e; /* lifted from #a99a86 */
|
--text-subtle: #a49fbd;
|
||||||
--border: #3e2f4a;
|
--border: #2e2a52;
|
||||||
--accent: #f0c262;
|
--accent: #2bc2b7;
|
||||||
--accent-contrast: #170f24;
|
--accent-contrast: #110f22;
|
||||||
--secondary: #c8abe0;
|
--secondary: #d8b35a;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-theme='light'] body[data-surface='interest'] {
|
html[data-theme='light'] body[data-surface='interest'] {
|
||||||
--surface: #f4e9cf;
|
--surface: #f4f2ed;
|
||||||
--surface-raised: #fdf4d9;
|
--surface-raised: #ffffff;
|
||||||
--surface-sunken: #e8d8b2;
|
--surface-sunken: #e9e5dc;
|
||||||
--text: #22152e;
|
--text: #131c2b;
|
||||||
--text-muted: #5c4733;
|
--text-muted: #4a5f6b;
|
||||||
--text-subtle: #826a4f;
|
--text-subtle: #5c6e78;
|
||||||
--border: #d8c290;
|
--border: #d9d4c8;
|
||||||
--accent: #7e5512;
|
--accent: #0f8c84;
|
||||||
--accent-contrast: #fdf4d9;
|
--accent-contrast: #131c2b;
|
||||||
--secondary: #5e4f9e;
|
--secondary: #8a6516;
|
||||||
}
|
}
|
||||||
|
|||||||