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
`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
* ---------------------------------------------------------------------------
* Inline mdi (Material Design Icons) renderer. Pulls icon path data at build
* time from `@iconify-json/mdi/icons.json` and emits a single inline <svg>, so
* there is no runtime icon library and no extra requests. The svg inherits
* `currentColor`, so color is controlled via text-* utilities or inline style.
*
* The spec calls for mdi icons everywhere instead of emojis; all spec emoji
* references are mapped to mdi names at the call sites.
* Inline icon renderer. Names resolve to the Patiun brand icon set first
* (src/lib/brand-icons.ts: stroke icons with a gold star accent), via either a
* brand name ("dice") or the mdi name the call sites already use ("dice-multiple").
* Anything not in the brand set, which is only third-party logos like GitHub or
* LinkedIn, falls back to mdi (Material Design Icons) path data. Either way the
* output is one inline <svg> that inherits `currentColor`.
*
* Usage: <Icon name="briefcase" class="h-6 w-6" />
*/
import mdi from '@iconify-json/mdi/icons.json';
import { brandIconBody } from '@lib/brand-icons';
interface Props {
/** mdi icon name WITHOUT prefix, e.g. "briefcase". */
@@ -27,8 +27,11 @@ interface Props {
const { name, title, class: className, size, style } = Astro.props;
const icon = (mdi as { icons: Record<string, { body: string }> }).icons[name];
if (!icon) {
const brand = brandIconBody(name);
const icon = brand
? undefined
: (mdi as { icons: Record<string, { body: string }> }).icons[name];
if (!brand && !icon) {
// Surface missing-icon names during dev so typos are caught immediately.
console.warn(`[Icon] unknown mdi icon: "${name}"`);
}
@@ -39,9 +42,14 @@ if (!icon) {
width={size ?? '1em'}
height={size ?? '1em'}
viewBox="0 0 24 24"
fill={brand ? 'none' : undefined}
stroke={brand ? 'currentColor' : undefined}
stroke-width={brand ? 1.6 : undefined}
stroke-linecap={brand ? 'round' : undefined}
stroke-linejoin={brand ? 'round' : undefined}
class={className}
style={style}
aria-hidden={title ? undefined : 'true'}
role={title ? 'img' : undefined}
set:html={(title ? `<title>${title}</title>` : '') + (icon?.body ?? '')}
set:html={(title ? `<title>${title}</title>` : '') + (brand ?? icon?.body ?? '')}
/>
+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>
+11 -49
View File
@@ -6,8 +6,8 @@
* 1. A CSS-driven night-sky gradient (deep blue, darker at top → lighter near
* horizon) plus a diagonal Milky Way galaxy band and teal/violet nebula
* clouds. GPU-friendly, animated via `animate-gradient-shift` keyframes.
* 2. A faint static SVG ornament layer (astrolabe + sacred geometry) for
* esoteric texture.
* 2. The Patiun layered magic seal, faint and slowly turning in the top-left
* corner (the surface's one focal motif).
* 3. An optional <canvas> star field: a dense field of slowly-twinkling stars
* with thin constellation lines between near neighbors, plus the occasional
* shooting star. The WHOLE field rotates together very slowly around the
@@ -359,55 +359,17 @@ export default function AnimatedBackground({
`,
}}
/>
{/* Faint astrolabe + sacred-geometry ornaments for esoteric texture. */}
<svg
className="absolute -left-40 -top-40 h-[34rem] w-[34rem] animate-spin-slow text-(--color-gold) opacity-[0.11]"
viewBox="0 0 100 100"
fill="none"
stroke="currentColor"
strokeWidth="0.5"
{/* The layered magic seal, turning slowly in the top-left corner: the
one focal motif on the night sky. Its ring text and symbols are
outlined to paths (public/motifs/), so no symbol font is needed. */}
<img
src="/motifs/layered-seal.svg"
alt=""
aria-hidden="true"
>
<circle cx="50" cy="50" r="49" strokeWidth="0.8" />
<circle cx="50" cy="50" r="42" />
<circle cx="50" cy="50" r="30" />
<circle cx="50" cy="50" r="18" />
{Array.from({ length: 36 }).map((_, i) => {
const rad = (i * 10 * Math.PI) / 180;
const major = i % 3 === 0;
return (
<line
key={i}
x1={50 + 49 * Math.cos(rad)}
y1={50 + 49 * Math.sin(rad)}
x2={50 + (major ? 43 : 45.5) * Math.cos(rad)}
y2={50 + (major ? 43 : 45.5) * Math.sin(rad)}
strokeWidth={major ? 0.8 : 0.4}
decoding="async"
draggable={false}
className="pointer-events-none absolute -left-40 -top-40 h-[34rem] w-[34rem] animate-spin-slow select-none opacity-[0.2]"
/>
);
})}
</svg>
<svg
className="absolute -right-44 bottom-[-12rem] h-[30rem] w-[30rem] text-(--color-violet) opacity-[0.11]"
viewBox="0 0 100 100"
fill="none"
stroke="currentColor"
strokeWidth="0.5"
aria-hidden="true"
>
{/* Compact flower-of-life cluster. */}
<circle cx="50" cy="50" r="10" />
{Array.from({ length: 6 }).map((_, i) => {
const rad = (Math.PI / 3) * i;
return <circle key={i} cx={50 + 10 * Math.cos(rad)} cy={50 + 10 * Math.sin(rad)} r="10" />;
})}
{Array.from({ length: 6 }).map((_, i) => {
const rad = (Math.PI / 3) * i + Math.PI / 6;
return (
<circle key={`o${i}`} cx={50 + 10 * Math.sqrt(3) * Math.cos(rad)} cy={50 + 10 * Math.sqrt(3) * Math.sin(rad)} r="10" />
);
})}
</svg>
{/* Subtle vignette for text legibility. */}
<div
+5 -5
View File
@@ -3,11 +3,11 @@
* Footer.astro
* ---------------------------------------------------------------------------
* 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.
*/
import Icon from '@components/Icon.astro';
import PatiunGlyph from '@components/brand/PatiunGlyph.astro';
import StarField from '@components/mystic/StarField.astro';
import { socials, footerMicrocopy, siteIdentity } from '@lib/site';
const year = new Date().getFullYear();
@@ -16,7 +16,7 @@ const socialList = Object.values(socials);
<footer
class="relative overflow-hidden border-t"
style="border-color: var(--color-border); background: var(--color-surface-sunken);"
style="border-color: color-mix(in srgb, var(--color-gold) 55%, transparent); background: var(--color-surface-sunken);"
>
{/* Faint static starfield — carries the night-sky treatment into the footer. */}
<StarField
@@ -26,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="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">
<p class="font-display text-sm font-bold uppercase tracking-[0.14em]" style="color: var(--color-text);">
{siteIdentity.name}
@@ -56,6 +56,6 @@ const socialList = Object.values(socials);
</ul>
</div>
<div class="container-prose relative z-10 pb-8 text-center text-xs" style="color: var(--color-text-subtle);">
&copy; {year} {siteIdentity.name}. All rights reserved.
&copy; {year} {siteIdentity.name} · {siteIdentity.lockupLine}. All rights reserved.
</div>
</footer>
+4 -4
View File
@@ -84,7 +84,7 @@ const {
<Reveal delay={140}>
<p
class="max-w-xl border-l-2 pl-4 font-sans text-base italic sm:text-lg"
style="border-color: var(--color-accent); color: var(--color-starlight); opacity: 0.92;"
style="border-color: var(--color-gold); color: var(--color-starlight); opacity: 0.92;"
>
{tagline}
</p>
@@ -97,7 +97,7 @@ const {
<Reveal delay={80} class="w-full">
<figure
class="relative w-full overflow-hidden rounded-2xl border"
style="border-color: color-mix(in srgb, var(--color-accent) 40%, transparent);"
style="border-color: color-mix(in srgb, var(--color-gold) 70%, transparent);"
>
<img
src={image}
@@ -107,8 +107,8 @@ const {
class="aspect-[4/3] w-full object-cover"
/>
<StarBurst
class="pointer-events-none absolute left-3 top-3 h-5 w-5 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-accent)_60%,transparent)]"
style="color: var(--color-accent);"
class="pointer-events-none absolute left-3 top-3 h-5 w-5 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-gold)_60%,transparent)]"
style="color: var(--color-gold);"
/>
</figure>
</Reveal>
+12 -33
View File
@@ -2,16 +2,13 @@
/**
* AlchemicalDivider.astro
* ---------------------------------------------------------------------------
* A horizontal section divider built from a thin rule with alchemical /
* celestial symbols along it: a central sun/star flanked by a crescent moon
* and small sparkles, with hairlines tapering to the edges. Replaces plain
* vertical gaps between sections on Home / About / Interests.
*
* Decorative: aria-hidden, inherits currentColor (set to --color-accent or
* --color-violet at the call site).
* Section break in the Patiun star-line style: a dotted (faint) run leading
* into a solid (main) run on each side, each stopping short of the stars, with
* a gold four-point star at the center flanked by two small ones. Inherits
* currentColor; call sites pass Alchemist Gold.
*
* Usage:
* <AlchemicalDivider class="my-4 h-8 w-full max-w-md mx-auto" style="color: var(--color-gold)" />
* <AlchemicalDivider class="mx-auto h-6 w-full max-w-sm" style="color: var(--color-gold)" />
*/
interface Props {
class?: string;
@@ -28,30 +25,12 @@ const { class: className = '', style = '' } = Astro.props;
stroke="currentColor"
stroke-linecap="round"
>
{/* Tapered hairlines either side of the central cluster. */}
<line x1="2" y1="12" x2="74" y2="12" stroke-width="0.8" opacity="0.7" />
<line x1="126" y1="12" x2="198" y2="12" stroke-width="0.8" opacity="0.7" />
<circle cx="8" cy="12" r="1" fill="currentColor" stroke="none" />
<circle cx="192" cy="12" r="1" fill="currentColor" stroke="none" />
{/* Crescent moon (left). */}
<path d="M86 12a6 6 0 1 0 5-5.9 4.7 4.7 0 0 1 0 11.8 6 6 0 0 0-5-5.9z" fill="currentColor" stroke="none" opacity="0.85" />
{/* Central radiant sun/star (alchemical sun: circle + rays). */}
<circle cx="100" cy="12" r="3.2" stroke-width="1" />
<g stroke-width="1">
<line x1="100" y1="2.5" x2="100" y2="5" />
<line x1="100" y1="19" x2="100" y2="21.5" />
<line x1="90.5" y1="12" x2="93" y2="12" />
<line x1="107" y1="12" x2="109.5" y2="12" />
<line x1="93.3" y1="5.3" x2="95.1" y2="7.1" />
<line x1="104.9" y1="16.9" x2="106.7" y2="18.7" />
<line x1="106.7" y1="5.3" x2="104.9" y2="7.1" />
<line x1="95.1" y1="16.9" x2="93.3" y2="18.7" />
</g>
{/* Small sparkles. */}
<path d="M114 12c.25 1.6.9 2.8 2.4 3.1-1.5.25-2.15 1.5-2.4 3.1-.25-1.6-.9-2.85-2.4-3.1 1.5-.3 2.15-1.5 2.4-3.1z" fill="currentColor" stroke="none" />
<path d="M76 12c.2 1.3.7 2.3 2 2.5-1.3.2-1.8 1.2-2 2.5-.2-1.3-.7-2.3-2-2.5 1.3-.2 1.8-1.2 2-2.5z" fill="currentColor" stroke="none" opacity="0.7" />
{/* Faint links: dotted runs at the outer ends. */}
<path d="M6 12h30M164 12h30" stroke-width="1" stroke-dasharray="0.1 4" />
{/* Main figure: solid runs that stop short of the stars. */}
<path d="M42 12h38M120 12h38" stroke-width="1" />
<polygon points="86.50,8.80 87.18,11.32 89.70,12.00 87.18,12.68 86.50,15.20 85.82,12.68 83.30,12.00 85.82,11.32" fill="currentColor" stroke="none" />
<polygon points="113.50,8.80 114.18,11.32 116.70,12.00 114.18,12.68 113.50,15.20 112.82,12.68 110.30,12.00 112.82,11.32" fill="currentColor" stroke="none" />
<polygon points="100.00,3.00 101.91,10.09 109.00,12.00 101.91,13.91 100.00,21.00 98.09,13.91 91.00,12.00 98.09,10.09" fill="currentColor" stroke="none" />
</svg>
</div>
-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
class:list={["relative", className]}
style={`border-radius: ${outerPx}px; border: ${bw}px solid color-mix(in srgb, var(--color-accent) 100%, transparent); `}
style={`border-radius: ${outerPx}px; border: ${bw}px solid var(--color-gold); `}
>
{/* Faint sacred-geometry tint behind the image (fills the full figure). */}
<div
@@ -56,23 +56,23 @@ const innerPx = Math.max(0, outerPx - bw);
<slot />
</div>
{/* Corner star-burst flourishes (sit on the border corners). */}
{/* Corner four-point stars in Alchemist Gold (sit on the border corners). */}
<StarBurst
class="pointer-events-none absolute -left-3 -top-3 h-6 w-6 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-accent)_60%,transparent)]"
style="color: var(--color-accent);"
class="pointer-events-none absolute -left-3 -top-3 h-6 w-6 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-gold)_60%,transparent)]"
style="color: var(--color-gold);"
/>
<StarBurst
class="pointer-events-none absolute -bottom-5 -right-5 h-7 w-7 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-accent)_60%,transparent)]"
style="color: var(--color-accent);"
class="pointer-events-none absolute -bottom-5 -right-5 h-7 w-7 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-gold)_60%,transparent)]"
style="color: var(--color-gold);"
/>
<StarBurst
variant="outline"
class="pointer-events-none absolute -right-2 -top-2 h-3 w-3"
style="color: var(--color-accent); opacity:.7;"
style="color: var(--color-gold); opacity:.7;"
/>
<StarBurst
variant="outline"
class="pointer-events-none absolute -left-2 -bottom-2 h-3 w-3"
style="color: var(--color-accent); opacity:.7;"
style="color: var(--color-gold); opacity:.7;"
/>
</figure>
@@ -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"
aria-hidden="true"
>
{/* Four-point sparkle: two perpendicular sharp diamonds + small inner core. */}
<path d="M12 1c.6 4.7 2.3 8.4 7 9-4.7.6-8.4 2.3-9 7-.6-4.7-2.3-8.4-7-9 4.7-.6 8.4-2.3 9-7z" />
{/* The Patiun four-point star: thin straight arms (inner radius 0.3 of the
outer), the same star used on the seal, the icons and the favicon. */}
<polygon points="12,1 14.33,9.67 23,12 14.33,14.33 12,23 9.67,14.33 1,12 9.67,9.67" />
</svg>
+14 -35
View File
@@ -12,6 +12,7 @@
*/
import { useEffect, useRef, useState } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import BrandIcon from '@components/brand/BrandIcon';
export interface MobileNavItem {
label: string;
@@ -28,31 +29,9 @@ interface Props {
currentPath: string;
}
// Small inline mdi-like icons keyed by name (keeps the menu self-contained).
const ICON_PATHS: Record<string, string> = {
home: 'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z',
account: 'M12 4a4 4 0 1 0 0 8 4 4 0 0 0 0-8zm0 10c-4.4 0-8 2.2-8 5v1h16v-1c0-2.8-3.6-5-8-5z',
briefcase:
'M10 16h4a1 1 0 0 0 1-1v-1H7a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v1h2a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-1v1a3 3 0 0 1-3 3h-4a3 3 0 0 1-3-3v-1z',
'dice-multiple':
'M6 3h5v5H6V3zm7 0h5v5h-5V3zM3 11h5v5H3v-5zm7 0h5v5h-5v-5zm7 0h-5v5h5v-5z',
email:
'M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2zm8 7L4 6v1l8 5 8-5V6l-8 5z',
// Store nav icons (kept here so the store mobile menu is self-contained).
store: 'M4 7h16l-1 13H5L4 7zm5-3a3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0 3 3',
'cards-playing': 'M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zm3 4v3m6 7v-3',
'printer-3d-nozzle':
'M7 2h10v4l3 3-4 2H8L4 9l3-3V2zm5 11v9',
'clipboard-list':
'M9 3h6v2H9V3zm-2 0v2H6a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-1V3M7 11h2m4 0h4M7 15h2m4 0h4',
};
// Patiun brand icons, resolved by the same names the nav config uses.
function Icon({ name, className }: { name: string; className?: string }) {
return (
<svg className={className} width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d={ICON_PATHS[name] ?? ''} />
</svg>
);
return <BrandIcon name={name} className={className} />;
}
function isActive(href: string, path: string): boolean {
@@ -90,17 +69,13 @@ export default function MobileMenu({ groups, currentPath }: Props) {
aria-expanded={open}
aria-controls="mobile-menu"
aria-label={open ? 'Close menu' : 'Open menu'}
className="inline-flex h-10 w-10 items-center justify-center rounded-lg border"
style={{ color: 'var(--color-text)', borderColor: 'var(--color-border)' }}
className="relative z-[60] inline-flex h-10 w-10 items-center justify-center rounded-lg border"
style={{ color: 'var(--color-starlight)', borderColor: 'color-mix(in srgb, var(--color-gold) 45%, transparent)' }}
>
{open ? (
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
<path d="M6 6l12 12M18 6L6 18" />
</svg>
<BrandIcon name="close" size={22} />
) : (
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
<path d="M4 7h16M4 12h16M4 17h16" />
</svg>
<BrandIcon name="menu" size={22} />
)}
</button>
@@ -124,7 +99,11 @@ export default function MobileMenu({ groups, currentPath }: Props) {
exit={{ x: '100%' }}
transition={{ type: 'tween', duration: 0.28 }}
className="fixed right-0 top-0 z-50 flex h-[100dvh] w-[min(82vw,22rem)] flex-col gap-6 overflow-y-auto p-6 pt-20 shadow-2xl"
style={{ backgroundColor: 'var(--color-surface-raised)' }}
style={{
// Night-sky drawer to match the nav it opens from.
backgroundColor: 'var(--color-midnight)',
borderLeft: '1px solid color-mix(in srgb, var(--color-gold) 45%, transparent)',
}}
>
{groups.map((group, gi) => (
<ul key={gi} className="flex flex-col gap-1">
@@ -137,9 +116,9 @@ export default function MobileMenu({ groups, currentPath }: Props) {
onClick={() => setOpen(false)}
className="flex items-center gap-3 rounded-lg px-3 py-3 text-lg font-medium transition-colors"
style={{
color: active ? 'var(--color-accent)' : 'var(--color-text)',
color: active ? 'var(--color-teal-bright)' : 'var(--color-starlight)',
backgroundColor: active
? 'color-mix(in srgb, var(--color-accent) 14%, transparent)'
? 'color-mix(in srgb, var(--color-teal-bright) 14%, transparent)'
: 'transparent',
}}
>
+15 -16
View File
@@ -12,7 +12,6 @@
*/
import Icon from "@components/Icon.astro";
import MobileMenu from "./MobileMenu.tsx";
import PatiunGlyph from "@components/brand/PatiunGlyph.astro";
import { navGroups, siteIdentity } from "@lib/site";
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"
aria-label="Primary"
>
<!-- Logo / identity: the approved Greg Kilmer lockup. Cinzel wordmark,
the "a Patiun LLC company" line in Montserrat, and the Patiun glyph
as a small maker's mark beside it (never overlapping). -->
<!-- Logo / identity: the Patiun seal (glyph in the gold degree ring) beside
the Cinzel name and the site tagline. The "a Patiun LLC company" line
lives in the footer with the copyright. -->
<a
href="/"
class="group flex items-center gap-2.5 leading-tight"
class="group flex items-center gap-3 leading-tight"
aria-label={`${siteIdentity.name} home`}
>
<PatiunGlyph
class="h-5 w-auto shrink-0 transition-transform duration-700 group-hover:-rotate-12"
<img
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
@@ -64,10 +67,10 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
{siteIdentity.name}
</span>
<span
class="hidden font-eyebrow text-[0.62rem] font-medium tracking-[0.08em] sm:block"
style="color: var(--color-text-subtle);"
class="hidden font-eyebrow text-[0.62rem] uppercase tracking-[0.18em] sm:block"
style="color: var(--color-gold-bright);"
>
{siteIdentity.lockupLine}
{siteIdentity.tagline}
</span>
</span>
</a>
@@ -81,7 +84,7 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
"flex items-center gap-1",
{ "border-l pl-5": gi > 0 },
]}
style={gi > 0 ? "border-color: var(--color-border);" : ""}
style={gi > 0 ? "border-color: color-mix(in srgb, var(--color-gold) 45%, transparent);" : ""}
>
{group.items.map((item) => {
const active = isActive(item.href);
@@ -136,11 +139,7 @@ const heroSkyClass = isHome ? "" : "vt-hero-sky";
[data-nav].scrolled [data-nav-inner] {
backdrop-filter: saturate(150%) blur(12px);
-webkit-backdrop-filter: saturate(150%) blur(12px);
border-bottom-color: color-mix(
in srgb,
var(--color-accent) 30%,
var(--color-border)
);
border-bottom-color: color-mix(in srgb, var(--color-gold) 35%, transparent);
box-shadow: 0 8px 30px -20px rgba(8, 12, 20, 0.7);
}
/* A subtle violet/gold nebula glow behind the nav, fixed to the top. */
+2 -3
View File
@@ -16,6 +16,7 @@
* skipped entirely so the form still works.
*/
import { useState, useRef, useEffect, type FormEvent } from 'react';
import BrandIcon from '@components/brand/BrandIcon';
type Status = 'idle' | 'submitting' | 'success' | 'error';
@@ -219,9 +220,7 @@ export default function ContactForm({ turnstileSiteKey }: Props) {
style={{ background: 'var(--color-accent)', color: 'var(--color-accent-contrast)' }}
aria-hidden="true"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
<path d="M20 6L9 17l-5-5" />
</svg>
<BrandIcon name="check" size={24} />
</span>
<h3 className="font-display text-lg font-semibold" style={{ color: 'var(--color-text)' }}>
Message sent.
+3 -6
View File
@@ -17,6 +17,7 @@
* visible text (not aria-hidden) so it's announced.
*/
import { useState, useRef, useCallback, type PointerEvent } from 'react';
import BrandIcon from '@components/brand/BrandIcon';
export interface CarouselSlide {
src: string;
@@ -177,9 +178,7 @@ export default function Carousel({ slides, label = 'Product examples' }: Props)
background: 'rgba(14,21,34,0.5)',
}}
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M15 18l-6-6 6-6" />
</svg>
<BrandIcon name="chevron-left" size={20} />
</button>
<button
type="button"
@@ -192,9 +191,7 @@ export default function Carousel({ slides, label = 'Product examples' }: Props)
background: 'rgba(14,21,34,0.5)',
}}
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M9 18l6-6-6-6" />
</svg>
<BrandIcon name="chevron-right" size={20} />
</button>
</>
)}
+2 -3
View File
@@ -14,6 +14,7 @@
* before it can be submitted.
*/
import { useState, useRef, useEffect, type FormEvent } from 'react';
import BrandIcon from '@components/brand/BrandIcon';
type Status = 'idle' | 'submitting' | 'success' | 'error';
type OrderType = 'cards' | 'deckBoxes' | 'both';
@@ -206,9 +207,7 @@ export default function OrderForm({ turnstileSiteKey, colors = [] }: Props) {
style={{ background: 'var(--color-accent)', color: 'var(--color-accent-contrast)' }}
aria-hidden="true"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
<path d="M20 6L9 17l-5-5" />
</svg>
<BrandIcon name="check" size={24} />
</span>
<h3 className="font-display text-lg font-semibold" style={{ color: 'var(--color-text)' }}>
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"
style="color: var(--color-starlight); background: rgba(14,21,34,0.6);"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<rect x="3" y="5" width="18" height="14" rx="2" />
<circle cx="9" cy="12" r="1.6" fill="currentColor" stroke="none" />
<circle cx="15" cy="12" r="1.6" fill="currentColor" stroke="none" />
</svg>
<Icon name="gallery" class="h-3.5 w-3.5" />
{product.images.length}
</span>
)}
+8 -5
View File
@@ -8,13 +8,12 @@
* two sites not be clearly linked yet.
*
* Visually it mirrors NavBar.astro's treatment — the dark nebula band, the
* scroll blur, and the 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
* domain label).
*/
import Icon from '@components/Icon.astro';
import MobileMenu from '@components/nav/MobileMenu.tsx';
import PatiunGlyph from '@components/brand/PatiunGlyph.astro';
interface NavEntry {
label: string;
@@ -45,9 +44,13 @@ const groups = [{ items: storeNav }];
<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">
<!-- 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">
<PatiunGlyph
class="h-5 w-auto shrink-0 transition-transform duration-700 group-hover:-rotate-12"
<a href="/store" class="group flex items-center gap-3 leading-tight" aria-label="Greg Kilmer Store — home">
<img
src="/brand/patiun-seal.svg"
alt=""
width="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="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 { AnimatePresence, motion } from 'framer-motion';
import BrandIcon from '@components/brand/BrandIcon';
export interface BoardGame {
name: string;
@@ -106,10 +107,8 @@ export default function BoardGameShelf({
color: isActive ? 'var(--color-accent)' : 'var(--color-secondary)',
}}
>
{/* dice / playing-card style glyph, inline to stay self-contained */}
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zm0 2v14h14V5H5zm4 2.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm9 0a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0 7a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm-9 0a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm4.5-3.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z" />
</svg>
{/* Patiun dice icon */}
<BrandIcon name="dice" size={16} />
</span>
<span className="flex flex-col gap-0.5">
<span className="font-sans text-base leading-tight underline-offset-4 group-hover:underline group-focus:underline">
@@ -133,7 +132,7 @@ export default function BoardGameShelf({
className="relative w-full overflow-hidden rounded-xl border"
style={{
height: SHELF_HEIGHT,
borderColor: 'color-mix(in srgb, var(--color-accent) 35%, transparent)',
borderColor: 'color-mix(in srgb, var(--color-gold) 70%, transparent)',
background: 'color-mix(in srgb, var(--color-secondary) 12%, transparent)',
}}
// When the pointer leaves the whole figure, snap back to the shelf.
+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>
<p
class="border-l-2 pl-5 font-sans text-xl leading-relaxed sm:text-2xl"
style="border-color: var(--color-accent); color: var(--color-text);"
style="border-color: var(--color-gold); color: var(--color-text);"
>
I am Greg: a software developer and technical team lead based in
Philadelphia who spends his free time running tabletop RPGs, making
@@ -155,7 +155,7 @@ const quickFacts = [
<div class="container-prose">
<AlchemicalDivider
class="mx-auto h-6 w-full max-w-sm"
style="color: var(--color-accent);"
style="color: var(--color-gold);"
/>
</div>
</Reveal>
+2 -2
View File
@@ -208,7 +208,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
<Reveal delay={120}>
<p
class="rounded-xl border-l-2 p-4 text-sm"
style="border-color: var(--color-accent); background: color-mix(in srgb, var(--color-accent) 8%, transparent); color: var(--color-text-muted);"
style="border-color: var(--color-gold); background: color-mix(in srgb, var(--color-accent) 8%, transparent); color: var(--color-text-muted);"
>
<strong style="color: var(--color-text);">Availability:</strong> Open to
opportunities in AI/ML, app and cloud development, SAP BTP, and technical
@@ -239,7 +239,7 @@ const turnstileSiteKey = getTurnstileSiteKey();
<Reveal>
<blockquote
class="mx-auto max-w-2xl border-l-2 pl-5 text-center font-sans text-lg italic"
style="border-color: var(--color-accent); color: var(--color-text-muted);"
style="border-color: var(--color-gold); color: var(--color-text-muted);"
>
Whether you want to talk cloud architecture, debate the best Gundam
series, or need a fourth for game night, reach out.
+8 -8
View File
@@ -16,7 +16,7 @@ import Icon from "@components/Icon.astro";
import SectionHeader from "@components/layout/SectionHeader.astro";
import CelestialImage from "@components/mystic/CelestialImage.astro";
import AlchemicalDivider from "@components/mystic/AlchemicalDivider.astro";
import SacredGeometry from "@components/mystic/SacredGeometry.astro";
import Motif from "@components/brand/Motif.astro";
import ConstellationField from "@components/mystic/ConstellationField.tsx";
// Snapshot grid content from the spec, with spec emojis mapped to mdi icons.
@@ -82,7 +82,7 @@ const cards = [
<blockquote
class="mt-7 max-w-2xl border-l-2 pl-5 text-lg leading-relaxed"
style="border-color: var(--color-accent); color: var(--text-muted);"
style="border-color: var(--color-gold); color: var(--text-muted);"
>
I build things with code during the day and with resin, plastic, and
imagination at night. I lead cloud and mobile development at Implico
@@ -130,11 +130,11 @@ const cards = [
/* Slow-rotating constellation field behind the section — a slice of sky. */
}
<ConstellationField client:idle className="opacity-60" />
{/* Faint sacred-geometry ornament behind the section. */}
<SacredGeometry
figure="flower"
class="pointer-events-none absolute -right-40 top-10 z-0 h-[40rem] w-[40rem]"
style="color: var(--color-violet); opacity:.08;"
{/* Faint transmutation seal behind the section (Starlight recolor). */}
<Motif
name="transmutation-seal-light"
class="absolute -right-40 top-10 z-0 h-[40rem] w-[40rem]"
style="opacity:.13;"
/>
<div class="container-prose relative z-10 flex flex-col gap-12">
<Reveal>
@@ -197,7 +197,7 @@ const cards = [
<Reveal>
<AlchemicalDivider
class="mx-auto h-6 w-full max-w-sm"
style="color: var(--color-accent);"
style="color: var(--color-gold);"
/>
</Reveal>
+5 -5
View File
@@ -11,7 +11,7 @@ import Reveal from "@components/motion/Reveal.astro";
import Icon from "@components/Icon.astro";
import SectionHeader from "@components/layout/SectionHeader.astro";
import AnimatedBackground from "@components/hero/AnimatedBackground.tsx";
import SacredGeometry from "@components/mystic/SacredGeometry.astro";
import Motif from "@components/brand/Motif.astro";
import ConstellationField from "@components/mystic/ConstellationField.tsx";
const interests = [
@@ -137,10 +137,10 @@ const interests = [
<!-- Interest grid -->
<section class="relative overflow-hidden py-12 sm:py-16">
<ConstellationField client:idle className="opacity-45" tint="teal" />
<SacredGeometry
figure="metatron"
class="pointer-events-none absolute -left-48 bottom-0 z-0 h-[38rem] w-[38rem]"
style="color: var(--color-accent); opacity:.07;"
<Motif
name="tria-prima-array-seal-light"
class="absolute -left-48 bottom-0 z-0 h-[38rem] w-[38rem]"
style="opacity:.12;"
/>
<div class="container-prose relative z-10">
<div class="grid gap-5 md:grid-cols-2 lg:grid-cols-3">
+1 -1
View File
@@ -180,7 +180,7 @@ const related = [
<Reveal delay={80}>
<blockquote
class="border-l-2 pl-5 text-lg leading-relaxed"
style="border-color: var(--color-secondary); color: var(--color-text-muted);"
style="border-color: var(--color-gold); color: var(--color-text-muted);"
>
Beyond RPGs, I maintain a growing board and card game collection.
Favorites span strategy, deck-building, and social deduction. Game
+8 -7
View File
@@ -21,7 +21,7 @@ import ProjectCard from "@components/professional/ProjectCard.astro";
import EducationHistory from "@components/professional/EducationHistory.astro";
import Certifications from "@components/professional/Certifications.astro";
import AlchemicalDivider from "@components/mystic/AlchemicalDivider.astro";
import Astrolabe from "@components/mystic/Astrolabe.astro";
import Motif from "@components/brand/Motif.astro";
import CircuitField from "@components/mystic/CircuitField.tsx";
import CelestialFrame from "@components/mystic/CelestialFrame.astro";
import ObfuscatedEmail from "@components/ObfuscatedEmail.astro";
@@ -56,9 +56,10 @@ const projects = (await getCollection("projects")).sort(
}
<div class="pointer-events-none absolute inset-0 overflow-hidden">
<CircuitField client:idle className="vt-sky opacity-100" />
<Astrolabe
class="absolute z-0 -right-48 -top-24 h-[34rem] w-[34rem] animate-spin-slow"
style="color: var(--color-accent); opacity:.13;"
<Motif
name="astrolabe"
class="absolute z-0 -right-40 -top-24 h-[38rem] w-[34rem] object-contain"
style="opacity:.2;"
/>
</div>
@@ -130,7 +131,7 @@ const projects = (await getCollection("projects")).sort(
<Reveal delay={80}>
<blockquote
class="border-l-2 pl-5 text-lg leading-relaxed"
style="border-color: var(--color-accent); color: var(--color-text-muted);"
style="border-color: var(--color-gold); color: var(--color-text-muted);"
>
Software developer and team lead with deep experience in cloud and
mobile application development, currently focused on SAP-adjacent
@@ -145,7 +146,7 @@ const projects = (await getCollection("projects")).sort(
<Reveal delay={120}>
<figure
class="relative overflow-hidden rounded-2xl border"
style="border-color: color-mix(in srgb, var(--color-accent) 35%, transparent);"
style="border-color: color-mix(in srgb, var(--color-gold) 70%, transparent);"
>
<img
src="/images/pro-workspace.jpg"
@@ -167,7 +168,7 @@ const projects = (await getCollection("projects")).sort(
<Reveal>
<AlchemicalDivider
class="mx-auto h-6 w-full max-w-sm"
style="color: var(--color-accent);"
style="color: var(--color-gold);"
/>
</Reveal>
+4 -4
View File
@@ -74,7 +74,7 @@ const orderHref = "/store/order";
<Reveal delay={140}>
<p
class="max-w-xl border-l-2 pl-4 font-sans text-base italic sm:text-lg"
style="border-color: var(--color-accent); color: var(--color-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
storage &amp; counters. Hand-made in small batches.
@@ -92,7 +92,7 @@ const orderHref = "/store/order";
<Reveal delay={80} class="w-full">
<figure
class="relative w-full overflow-hidden rounded-2xl border"
style="border-color: color-mix(in srgb, var(--color-accent) 40%, transparent);"
style="border-color: color-mix(in srgb, var(--color-gold) 70%, transparent);"
>
<img
src="/images/proxies.jpg"
@@ -102,8 +102,8 @@ const orderHref = "/store/order";
class="aspect-[4/3] w-full object-cover"
/>
<StarBurst
class="pointer-events-none absolute left-3 top-3 h-5 w-5 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-accent)_60%,transparent)]"
style="color: var(--color-accent);"
class="pointer-events-none absolute left-3 top-3 h-5 w-5 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-gold)_60%,transparent)]"
style="color: var(--color-gold);"
/>
</figure>
</Reveal>
+7
View File
@@ -267,6 +267,13 @@
.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. */