mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-07 19:58:24 +00:00
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:
@@ -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.
|
||||||
|
|||||||
@@ -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
@@ -2,17 +2,17 @@
|
|||||||
/**
|
/**
|
||||||
* Icon.astro
|
* Icon.astro
|
||||||
* ---------------------------------------------------------------------------
|
* ---------------------------------------------------------------------------
|
||||||
* Inline mdi (Material Design Icons) renderer. Pulls icon path data at build
|
* Inline icon renderer. Names resolve to the Patiun brand icon set first
|
||||||
* time from `@iconify-json/mdi/icons.json` and emits a single inline <svg>, so
|
* (src/lib/brand-icons.ts: stroke icons with a gold star accent), via either a
|
||||||
* there is no runtime icon library and no extra requests. The svg inherits
|
* brand name ("dice") or the mdi name the call sites already use ("dice-multiple").
|
||||||
* `currentColor`, so color is controlled via text-* utilities or inline style.
|
* Anything not in the brand set, which is only third-party logos like GitHub or
|
||||||
*
|
* LinkedIn, falls back to mdi (Material Design Icons) path data. Either way the
|
||||||
* The spec calls for mdi icons everywhere instead of emojis; all spec emoji
|
* output is one inline <svg> that inherits `currentColor`.
|
||||||
* references are mapped to mdi names at the call sites.
|
|
||||||
*
|
*
|
||||||
* Usage: <Icon name="briefcase" class="h-6 w-6" />
|
* Usage: <Icon name="briefcase" class="h-6 w-6" />
|
||||||
*/
|
*/
|
||||||
import mdi from '@iconify-json/mdi/icons.json';
|
import mdi from '@iconify-json/mdi/icons.json';
|
||||||
|
import { brandIconBody } from '@lib/brand-icons';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** mdi icon name WITHOUT prefix, e.g. "briefcase". */
|
/** mdi icon name WITHOUT prefix, e.g. "briefcase". */
|
||||||
@@ -27,8 +27,11 @@ interface Props {
|
|||||||
|
|
||||||
const { name, title, class: className, size, style } = Astro.props;
|
const { name, title, class: className, size, style } = Astro.props;
|
||||||
|
|
||||||
const icon = (mdi as { icons: Record<string, { body: string }> }).icons[name];
|
const brand = brandIconBody(name);
|
||||||
if (!icon) {
|
const icon = brand
|
||||||
|
? undefined
|
||||||
|
: (mdi as { icons: Record<string, { body: string }> }).icons[name];
|
||||||
|
if (!brand && !icon) {
|
||||||
// Surface missing-icon names during dev so typos are caught immediately.
|
// Surface missing-icon names during dev so typos are caught immediately.
|
||||||
console.warn(`[Icon] unknown mdi icon: "${name}"`);
|
console.warn(`[Icon] unknown mdi icon: "${name}"`);
|
||||||
}
|
}
|
||||||
@@ -39,9 +42,14 @@ if (!icon) {
|
|||||||
width={size ?? '1em'}
|
width={size ?? '1em'}
|
||||||
height={size ?? '1em'}
|
height={size ?? '1em'}
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
|
fill={brand ? 'none' : undefined}
|
||||||
|
stroke={brand ? 'currentColor' : undefined}
|
||||||
|
stroke-width={brand ? 1.6 : undefined}
|
||||||
|
stroke-linecap={brand ? 'round' : undefined}
|
||||||
|
stroke-linejoin={brand ? 'round' : undefined}
|
||||||
class={className}
|
class={className}
|
||||||
style={style}
|
style={style}
|
||||||
aria-hidden={title ? undefined : 'true'}
|
aria-hidden={title ? undefined : 'true'}
|
||||||
role={title ? 'img' : undefined}
|
role={title ? 'img' : undefined}
|
||||||
set:html={(title ? `<title>${title}</title>` : '') + (icon?.body ?? '')}
|
set:html={(title ? `<title>${title}</title>` : '') + (brand ?? icon?.body ?? '')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* BrandIcon.tsx
|
||||||
|
* ---------------------------------------------------------------------------
|
||||||
|
* React twin of Icon.astro for islands: renders a Patiun brand icon (by brand
|
||||||
|
* name or mdi alias) as an inline stroke SVG that inherits currentColor.
|
||||||
|
*/
|
||||||
|
import { brandIconBody } from '@lib/brand-icons';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
name: string;
|
||||||
|
size?: number;
|
||||||
|
className?: string;
|
||||||
|
/** Accessible label; omit when the icon sits beside visible text. */
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BrandIcon({ name, size = 20, className, title }: Props) {
|
||||||
|
const body = brandIconBody(name) ?? '';
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth={1.6}
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
className={className}
|
||||||
|
role={title ? 'img' : undefined}
|
||||||
|
aria-hidden={title ? undefined : true}
|
||||||
|
aria-label={title}
|
||||||
|
dangerouslySetInnerHTML={{ __html: body }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,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}
|
||||||
|
/>
|
||||||
@@ -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>
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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);">
|
||||||
© {year} {siteIdentity.name}. All rights reserved.
|
© {year} {siteIdentity.name} · {siteIdentity.lockupLine}. All rights reserved.
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import BrandIcon from '@components/brand/BrandIcon';
|
||||||
|
|
||||||
export interface MobileNavItem {
|
export interface MobileNavItem {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -28,31 +29,9 @@ interface Props {
|
|||||||
currentPath: string;
|
currentPath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Small inline mdi-like icons keyed by name (keeps the menu self-contained).
|
// Patiun brand icons, resolved by the same names the nav config uses.
|
||||||
const ICON_PATHS: Record<string, string> = {
|
|
||||||
home: 'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z',
|
|
||||||
account: 'M12 4a4 4 0 1 0 0 8 4 4 0 0 0 0-8zm0 10c-4.4 0-8 2.2-8 5v1h16v-1c0-2.8-3.6-5-8-5z',
|
|
||||||
briefcase:
|
|
||||||
'M10 16h4a1 1 0 0 0 1-1v-1H7a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v1h2a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-1v1a3 3 0 0 1-3 3h-4a3 3 0 0 1-3-3v-1z',
|
|
||||||
'dice-multiple':
|
|
||||||
'M6 3h5v5H6V3zm7 0h5v5h-5V3zM3 11h5v5H3v-5zm7 0h5v5h-5v-5zm7 0h-5v5h5v-5z',
|
|
||||||
email:
|
|
||||||
'M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2zm8 7L4 6v1l8 5 8-5V6l-8 5z',
|
|
||||||
// Store nav icons (kept here so the store mobile menu is self-contained).
|
|
||||||
store: 'M4 7h16l-1 13H5L4 7zm5-3a3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0 3 3',
|
|
||||||
'cards-playing': 'M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zm3 4v3m6 7v-3',
|
|
||||||
'printer-3d-nozzle':
|
|
||||||
'M7 2h10v4l3 3-4 2H8L4 9l3-3V2zm5 11v9',
|
|
||||||
'clipboard-list':
|
|
||||||
'M9 3h6v2H9V3zm-2 0v2H6a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-1V3M7 11h2m4 0h4M7 15h2m4 0h4',
|
|
||||||
};
|
|
||||||
|
|
||||||
function Icon({ name, className }: { name: string; className?: string }) {
|
function Icon({ name, className }: { name: string; className?: string }) {
|
||||||
return (
|
return <BrandIcon name={name} className={className} />;
|
||||||
<svg className={className} width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
||||||
<path d={ICON_PATHS[name] ?? ''} />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isActive(href: string, path: string): boolean {
|
function isActive(href: string, path: string): boolean {
|
||||||
@@ -90,17 +69,13 @@ export default function MobileMenu({ groups, currentPath }: Props) {
|
|||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
aria-controls="mobile-menu"
|
aria-controls="mobile-menu"
|
||||||
aria-label={open ? 'Close menu' : 'Open menu'}
|
aria-label={open ? 'Close menu' : 'Open menu'}
|
||||||
className="inline-flex h-10 w-10 items-center justify-center rounded-lg border"
|
className="relative z-[60] inline-flex h-10 w-10 items-center justify-center rounded-lg border"
|
||||||
style={{ color: 'var(--color-text)', borderColor: 'var(--color-border)' }}
|
style={{ color: 'var(--color-starlight)', borderColor: 'color-mix(in srgb, var(--color-gold) 45%, transparent)' }}
|
||||||
>
|
>
|
||||||
{open ? (
|
{open ? (
|
||||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
<BrandIcon name="close" size={22} />
|
||||||
<path d="M6 6l12 12M18 6L6 18" />
|
|
||||||
</svg>
|
|
||||||
) : (
|
) : (
|
||||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
<BrandIcon name="menu" size={22} />
|
||||||
<path d="M4 7h16M4 12h16M4 17h16" />
|
|
||||||
</svg>
|
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -124,7 +99,11 @@ export default function MobileMenu({ groups, currentPath }: Props) {
|
|||||||
exit={{ x: '100%' }}
|
exit={{ x: '100%' }}
|
||||||
transition={{ type: 'tween', duration: 0.28 }}
|
transition={{ type: 'tween', duration: 0.28 }}
|
||||||
className="fixed right-0 top-0 z-50 flex h-[100dvh] w-[min(82vw,22rem)] flex-col gap-6 overflow-y-auto p-6 pt-20 shadow-2xl"
|
className="fixed right-0 top-0 z-50 flex h-[100dvh] w-[min(82vw,22rem)] flex-col gap-6 overflow-y-auto p-6 pt-20 shadow-2xl"
|
||||||
style={{ backgroundColor: 'var(--color-surface-raised)' }}
|
style={{
|
||||||
|
// Night-sky drawer to match the nav it opens from.
|
||||||
|
backgroundColor: 'var(--color-midnight)',
|
||||||
|
borderLeft: '1px solid color-mix(in srgb, var(--color-gold) 45%, transparent)',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{groups.map((group, gi) => (
|
{groups.map((group, gi) => (
|
||||||
<ul key={gi} className="flex flex-col gap-1">
|
<ul key={gi} className="flex flex-col gap-1">
|
||||||
@@ -137,9 +116,9 @@ export default function MobileMenu({ groups, currentPath }: Props) {
|
|||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
className="flex items-center gap-3 rounded-lg px-3 py-3 text-lg font-medium transition-colors"
|
className="flex items-center gap-3 rounded-lg px-3 py-3 text-lg font-medium transition-colors"
|
||||||
style={{
|
style={{
|
||||||
color: active ? 'var(--color-accent)' : 'var(--color-text)',
|
color: active ? 'var(--color-teal-bright)' : 'var(--color-starlight)',
|
||||||
backgroundColor: active
|
backgroundColor: active
|
||||||
? 'color-mix(in srgb, var(--color-accent) 14%, transparent)'
|
? 'color-mix(in srgb, var(--color-teal-bright) 14%, transparent)'
|
||||||
: 'transparent',
|
: 'transparent',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
*/
|
*/
|
||||||
import Icon from "@components/Icon.astro";
|
import Icon from "@components/Icon.astro";
|
||||||
import MobileMenu from "./MobileMenu.tsx";
|
import MobileMenu from "./MobileMenu.tsx";
|
||||||
import 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. */
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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,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);">
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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] ?? ""];
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ const related = [
|
|||||||
<Reveal delay={80}>
|
<Reveal delay={80}>
|
||||||
<blockquote
|
<blockquote
|
||||||
class="border-l-2 pl-5 text-lg leading-relaxed"
|
class="border-l-2 pl-5 text-lg leading-relaxed"
|
||||||
style="border-color: var(--color-secondary); color: var(--color-text-muted);"
|
style="border-color: var(--color-gold); color: var(--color-text-muted);"
|
||||||
>
|
>
|
||||||
Beyond RPGs, I maintain a growing board and card game collection.
|
Beyond RPGs, I maintain a growing board and card game collection.
|
||||||
Favorites span strategy, deck-building, and social deduction. Game
|
Favorites span strategy, deck-building, and social deduction. Game
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import ProjectCard from "@components/professional/ProjectCard.astro";
|
|||||||
import EducationHistory from "@components/professional/EducationHistory.astro";
|
import EducationHistory from "@components/professional/EducationHistory.astro";
|
||||||
import Certifications from "@components/professional/Certifications.astro";
|
import Certifications from "@components/professional/Certifications.astro";
|
||||||
import AlchemicalDivider from "@components/mystic/AlchemicalDivider.astro";
|
import AlchemicalDivider from "@components/mystic/AlchemicalDivider.astro";
|
||||||
import Astrolabe from "@components/mystic/Astrolabe.astro";
|
import Motif from "@components/brand/Motif.astro";
|
||||||
import CircuitField from "@components/mystic/CircuitField.tsx";
|
import CircuitField from "@components/mystic/CircuitField.tsx";
|
||||||
import CelestialFrame from "@components/mystic/CelestialFrame.astro";
|
import CelestialFrame from "@components/mystic/CelestialFrame.astro";
|
||||||
import ObfuscatedEmail from "@components/ObfuscatedEmail.astro";
|
import ObfuscatedEmail from "@components/ObfuscatedEmail.astro";
|
||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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 & counters. Hand-made in small batches.
|
storage & counters. Hand-made in small batches.
|
||||||
@@ -92,7 +92,7 @@ const orderHref = "/store/order";
|
|||||||
<Reveal delay={80} class="w-full">
|
<Reveal delay={80} class="w-full">
|
||||||
<figure
|
<figure
|
||||||
class="relative w-full overflow-hidden rounded-2xl border"
|
class="relative w-full overflow-hidden rounded-2xl border"
|
||||||
style="border-color: color-mix(in srgb, var(--color-accent) 40%, transparent);"
|
style="border-color: color-mix(in srgb, var(--color-gold) 70%, transparent);"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="/images/proxies.jpg"
|
src="/images/proxies.jpg"
|
||||||
@@ -102,8 +102,8 @@ const orderHref = "/store/order";
|
|||||||
class="aspect-[4/3] w-full object-cover"
|
class="aspect-[4/3] w-full object-cover"
|
||||||
/>
|
/>
|
||||||
<StarBurst
|
<StarBurst
|
||||||
class="pointer-events-none absolute left-3 top-3 h-5 w-5 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-accent)_60%,transparent)]"
|
class="pointer-events-none absolute left-3 top-3 h-5 w-5 drop-shadow-[0_0_6px_color-mix(in_srgb,var(--color-gold)_60%,transparent)]"
|
||||||
style="color: var(--color-accent);"
|
style="color: var(--color-gold);"
|
||||||
/>
|
/>
|
||||||
</figure>
|
</figure>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
|
|||||||
@@ -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. */
|
||||||
|
|||||||
Reference in New Issue
Block a user