Use Patiun icons, motifs, seal logo and gold accents across the site

- Icons: Patiun stroke icon set (13 approved + 73 new) in src/lib/brand-icons.ts;
  Icon.astro and a React BrandIcon resolve mdi names to brand icons, keeping
  mdi only for third-party logos. Inline island icons (mobile menu, carousel,
  form success, board game shelf, product tile) switched too.
- Motifs: layered seal turns in the hero corner; astrolabe on the Professional
  hero; Starlight recolors of the transmutation seal and tria prima array
  behind light sections (outlined ring text, no fonts needed). Removed the
  generic sacred-geometry and astrolabe drawings.
- Gold accents: image frames and corner stars, quote bars, dividers (new
  star-line divider), nav group divider, scrolled nav rule, footer rule.
- Nav and footer use the Patiun seal; nav tagline back to "Developer. Maker.
  Storyteller."; "Web Development · a Patiun LLC company" moved to the
  footer copyright line.
- Mobile menu: Midnight drawer, visible close button.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
This commit is contained in:
Claude
2026-10-11 02:20:55 +00:00
parent 01431468e4
commit 9ecd4d0e59
35 changed files with 528 additions and 420 deletions
+17
View File
@@ -137,3 +137,20 @@ Self-hosted variable fonts via Fontsource (woff2, no external requests):
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.
- **Gold accents:** image frames, quote bars, dividers and rules use
`--color-gold`; teal stays for links, buttons and icons.
+76
View File
@@ -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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 45 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 41 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 72 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 61 KiB

+18 -10
View File
@@ -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 ?? '')}
/> />
+37
View File
@@ -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 }}
/>
);
}
+39
View File
@@ -0,0 +1,39 @@
---
/**
* 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" />
*/
interface Props {
name:
| "layered-seal"
| "astrolabe"
| "transmutation-seal-light"
| "tria-prima-array-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}
/>
-40
View File
@@ -1,40 +0,0 @@
---
/**
* PatiunGlyph.astro
* ---------------------------------------------------------------------------
* The Patiun LLC main mark: the alchemical symbol for platinum (the moon of
* silver joined to the sun of gold). Used small as the maker's mark in the
* Greg Kilmer lockup. Geometry is copied unchanged from the brand file
* patiun-symbol-A-exact-teal.svg; only the color is themable.
*
* Brand rules: Patiun Teal by default (gold, black or white are the only
* other allowed colors), no rings around it at small sizes, minimum 16px, and
* text sits beside it, never over it.
*
* Usage: <PatiunGlyph class="h-6 w-auto" />
*/
interface Props {
class?: string;
style?: string;
/** Accessible name. Omit (default) when the mark sits next to visible text. */
title?: string;
}
const { class: className, style = "color: var(--color-teal);", title } = Astro.props;
---
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="20 33 152 127"
class={className}
style={style}
role={title ? "img" : undefined}
aria-hidden={title ? undefined : "true"}
aria-label={title}
focusable="false"
>
<g fill="none" stroke="currentColor" stroke-width="7.2" stroke-linejoin="round">
<path d="M31,46.75 A54.3,54.3 0 1 1 31,146.25 A53.4,53.4 0 0 0 31,46.75 Z" />
<circle cx="134" cy="96.5" r="26.9" />
</g>
<circle cx="134" cy="96.5" r="10.3" fill="currentColor" />
</svg>
+12 -50
View File
@@ -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
@@ -359,55 +359,17 @@ export default function AnimatedBackground({
`, `,
}} }}
/> />
{/* 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-gold) 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
+5 -5
View File
@@ -3,11 +3,11 @@
* Footer.astro * Footer.astro
* --------------------------------------------------------------------------- * ---------------------------------------------------------------------------
* Minimal footer on a deep night-sky surface: a faint static starfield accent, * Minimal footer on a deep night-sky surface: a faint static starfield accent,
* the Patiun glyph as a maker's 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 PatiunGlyph from '@components/brand/PatiunGlyph.astro';
import StarField from '@components/mystic/StarField.astro'; import StarField from '@components/mystic/StarField.astro';
import { socials, footerMicrocopy, siteIdentity } from '@lib/site'; import { socials, footerMicrocopy, siteIdentity } from '@lib/site';
const year = new Date().getFullYear(); const year = new Date().getFullYear();
@@ -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,7 +26,7 @@ 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">
<PatiunGlyph class="h-6 w-auto shrink-0" /> <img src="/brand/patiun-seal.svg" alt="" width="48" height="48" class="h-12 w-12 shrink-0" />
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<p class="font-display text-sm font-bold uppercase tracking-[0.14em]" 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}
@@ -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);">
&copy; {year} {siteIdentity.name}. All rights reserved. &copy; {year} {siteIdentity.name} · {siteIdentity.lockupLine}. All rights reserved.
</div> </div>
</footer> </footer>
+4 -4
View File
@@ -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-starlight); 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>
+12 -33
View File
@@ -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-gold)" /> * <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>
-76
View File
@@ -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-gold); 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>
+8 -8
View File
@@ -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>
@@ -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-gold); 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>
+3 -2
View File
@@ -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>
+14 -35
View File
@@ -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',
}} }}
> >
+15 -16
View File
@@ -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 PatiunGlyph from "@components/brand/PatiunGlyph.astro";
import { navGroups, siteIdentity } from "@lib/site"; import { navGroups, siteIdentity } from "@lib/site";
const path = Astro.url.pathname; const path = Astro.url.pathname;
@@ -45,16 +44,20 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
class="container-prose flex h-16 items-center justify-between gap-4" class="container-prose flex h-16 items-center justify-between gap-4"
aria-label="Primary" aria-label="Primary"
> >
<!-- Logo / identity: the approved Greg Kilmer lockup. Cinzel wordmark, <!-- Logo / identity: the Patiun seal (glyph in the gold degree ring) beside
the "a Patiun LLC company" line in Montserrat, and the Patiun glyph the Cinzel name and the site tagline. The "a Patiun LLC company" line
as a small maker's mark beside it (never overlapping). --> lives in the footer with the copyright. -->
<a <a
href="/" href="/"
class="group flex items-center gap-2.5 leading-tight" class="group flex items-center gap-3 leading-tight"
aria-label={`${siteIdentity.name} home`} aria-label={`${siteIdentity.name} home`}
> >
<PatiunGlyph <img
class="h-5 w-auto shrink-0 transition-transform duration-700 group-hover:-rotate-12" src="/brand/patiun-seal.svg"
alt=""
width="44"
height="44"
class="h-11 w-11 shrink-0 transition-transform duration-700 group-hover:rotate-[30deg]"
/> />
<span class="flex flex-col"> <span class="flex flex-col">
<span <span
@@ -64,10 +67,10 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
{siteIdentity.name} {siteIdentity.name}
</span> </span>
<span <span
class="hidden font-eyebrow text-[0.62rem] font-medium tracking-[0.08em] 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.lockupLine} {siteIdentity.tagline}
</span> </span>
</span> </span>
</a> </a>
@@ -81,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);
@@ -136,11 +139,7 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
[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,
var(--color-accent) 30%,
var(--color-border)
);
box-shadow: 0 8px 30px -20px rgba(8, 12, 20, 0.7); box-shadow: 0 8px 30px -20px rgba(8, 12, 20, 0.7);
} }
/* A subtle violet/gold nebula glow behind the nav, fixed to the top. */ /* A subtle violet/gold nebula glow behind the nav, fixed to the top. */
+2 -3
View File
@@ -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,9 +220,7 @@ 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-display 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.
+3 -6
View File
@@ -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;
@@ -177,9 +178,7 @@ export default function Carousel({ slides, label = 'Product examples' }: Props)
background: 'rgba(14,21,34,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"
@@ -192,9 +191,7 @@ export default function Carousel({ slides, label = 'Product examples' }: Props)
background: 'rgba(14,21,34,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>
</> </>
)} )}
+2 -3
View File
@@ -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,9 +207,7 @@ 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-display 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.
+1 -5
View File
@@ -40,11 +40,7 @@ const cover = product.images[0];
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" class="pointer-events-none absolute right-2 top-2 inline-flex items-center gap-1 rounded-full px-2 py-1 font-eyebrow text-xs"
style="color: var(--color-starlight); background: rgba(14,21,34,0.6);" 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>
)} )}
+8 -5
View File
@@ -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 Patiun glyph 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 PatiunGlyph from '@components/brand/PatiunGlyph.astro';
interface NavEntry { interface NavEntry {
label: string; label: string;
@@ -45,9 +44,13 @@ const groups = [{ items: storeNav }];
<div class="border-b border-transparent transition-colors duration-300" data-nav-inner> <div class="border-b border-transparent transition-colors duration-300" data-nav-inner>
<nav class="container-prose flex h-16 items-center justify-between gap-4" aria-label="Store"> <nav class="container-prose flex h-16 items-center justify-between gap-4" aria-label="Store">
<!-- Wordmark: "Greg Kilmer · Store" + the store domain label --> <!-- Wordmark: "Greg Kilmer · Store" + the store domain label -->
<a href="/store" class="group flex items-center gap-2 leading-tight" aria-label="Greg Kilmer Store — home"> <a href="/store" class="group flex items-center gap-3 leading-tight" aria-label="Greg Kilmer Store — home">
<PatiunGlyph <img
class="h-5 w-auto shrink-0 transition-transform duration-700 group-hover:-rotate-12" src="/brand/patiun-seal.svg"
alt=""
width="44"
height="44"
class="h-11 w-11 shrink-0 transition-transform duration-700 group-hover:rotate-[30deg]"
/> />
<span class="flex flex-col"> <span class="flex flex-col">
<span class="font-display text-base font-bold uppercase tracking-[0.12em]" style="color: var(--color-starlight);"> <span class="font-display text-base font-bold uppercase tracking-[0.12em]" style="color: var(--color-starlight);">
+4 -5
View File
@@ -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.
+203
View File
@@ -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] ?? ""];
}
+2 -2
View File
@@ -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
@@ -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>
+2 -2
View File
@@ -208,7 +208,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
@@ -239,7 +239,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.
+8 -8
View File
@@ -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.
@@ -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>
@@ -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>
+5 -5
View File
@@ -11,7 +11,7 @@ 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 = [
@@ -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">
+1 -1
View File
@@ -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
+8 -7
View File
@@ -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";
@@ -56,9 +56,10 @@ const projects = (await getCollection("projects")).sort(
} }
<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"
@@ -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>
+4 -4
View File
@@ -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-starlight); 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 &amp; counters. Hand-made in small batches. storage &amp; 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>
+7
View File
@@ -267,6 +267,13 @@
.on-sky { .on-sky {
--color-accent: var(--color-teal-bright); --color-accent: var(--color-teal-bright);
--color-secondary: var(--color-gold-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. */