@import url("fonts.css");
@import url("reset.css");

/* Root variables */

:root {
    /* colors */
    --bg: #0b0b0c;
    --surface: #141416;
    --surface-hi: #1b1b1f;
    --fg: #f4f3f0;
    --muted: #8b8b93;
    --rule: color-mix(in oklab, var(--fg) 12%, transparent);
    --accent: #FFD600;
    --chip: #2a2a30;

    /* layout */
    --col: 21rem;
    --gap: 0.75rem;
    --radius: 1.25rem;

    /* body text */
    --text-xs: 0.75rem;
    --text-sm: 0.875rem;
    --text-base: 1rem;
    --text-lg: 1.125rem;
    --text-xl: 1.5rem;
    --text-2xl: 2rem;

    /* heading scale: each step is clearly smaller than the one above it */
    --h1: clamp(2.5rem, 7vw, 4.5rem);
    --h2: clamp(1.75rem, 3.2vw, 2.5rem);
    --h3: clamp(1.25rem, 1.9vw, 1.5rem);
    --h4: 1.0625rem;
    --h5: var(--text-xs);

    /* the arrow mask, shared by the icon and the list bullet */
    --arrow: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M5%2012h14%22%2F%3E%3Cpath%20d%3D%22m12%205%207%207-7%207%22%2F%3E%3C%2Fsvg%3E");

    /* button geometry, shared by every pill on the site */
    --btn-h: 2.5rem;
    --btn-pad: 1.15rem;
}

/* Global styles */

html {
    font-size: 16px;
    font-synthesis: none;
    scroll-behavior: smooth;
}

::selection {
    background-color: var(--accent);
    color: var(--bg);
}

body {
    min-height: 100dvh;
    background-color: var(--bg);
    color: var(--fg);
    font-family: "Nohemi", sans-serif;
    font-weight: 400;
    font-variation-settings: "wght" 400;
    line-height: 1.45;
    text-wrap: pretty;
}

a {
    color: inherit;
    text-decoration: none;
}

/* Type: headings balance their last line, body copy avoids orphans */

h1,
h2,
h3,
h4,
h5,
h6 {
    font-family: "Techboi Pixels", "Nohemi", sans-serif;
    text-wrap: balance;
    letter-spacing: -0.02em;
    line-height: 1.12;
}

p,
li,
dd {
    text-wrap: pretty;
}

code,
pre {
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 0.9em;
}

code {
    padding: 0.15em 0.4em;
    border-radius: 0.35rem;
    background-color: var(--chip);
}

/* Button component. One geometry, two fills, so every pill on the site
   lines up with every other one. */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.45rem;
    min-height: var(--btn-h);
    padding-inline: var(--btn-pad);
    border: 1px solid transparent;
    border-radius: 2rem;
    font-size: var(--text-sm);
    font-weight: 600;
    font-variation-settings: "wght" 600;
    white-space: nowrap;
    cursor: pointer;
    transition:
        background-color 0.2s,
        border-color 0.2s,
        color 0.2s,
        transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.btn-solid {
    background-color: var(--accent);
    color: var(--bg);
}

.btn-solid:hover {
    background-color: var(--fg);
    transform: translateY(-2px);
}

.btn-ghost {
    border-color: var(--rule);
    color: var(--fg);
}

.btn-ghost:hover {
    border-color: transparent;
    background-color: var(--accent);
    color: var(--bg);
}

.btn-sm {
    --btn-h: 1.9rem;
    --btn-pad: 0.7rem;

    font-size: var(--text-xs);
    font-weight: 400;
    font-variation-settings: "wght" 400;
}

ul {
    padding: 0;
    list-style: none;
}

/* Lucide icons, drawn as masks so they take colour from the text they sit in */

.ico {
    flex: none;
    display: inline-block;
    width: 1em;
    height: 1em;
    background-color: currentColor;
    mask: var(--ico) center / contain no-repeat;
}

.ico-arrow-left { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22m12%2019-7-7%207-7%22%2F%3E%3Cpath%20d%3D%22M19%2012H5%22%2F%3E%3C%2Fsvg%3E"); }
.ico-external { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M15%203h6v6%22%2F%3E%3Cpath%20d%3D%22M10%2014%2021%203%22%2F%3E%3Cpath%20d%3D%22M18%2013v6a2%202%200%200%201-2%202H5a2%202%200%200%201-2-2V5a2%202%200%200%201%202-2h6%22%2F%3E%3C%2Fsvg%3E"); }
.ico-palette { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Ccircle%20cx%3D%2213.5%22%20cy%3D%226.5%22%20r%3D%22.5%22%2F%3E%3Ccircle%20cx%3D%2217.5%22%20cy%3D%2210.5%22%20r%3D%22.5%22%2F%3E%3Ccircle%20cx%3D%228.5%22%20cy%3D%227.5%22%20r%3D%22.5%22%2F%3E%3Ccircle%20cx%3D%226.5%22%20cy%3D%2212.5%22%20r%3D%22.5%22%2F%3E%3Cpath%20d%3D%22M12%202C6.5%202%202%206.5%202%2012s4.5%2010%2010%2010c.926%200%201.648-.746%201.648-1.688%200-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64%201.64%200%200%201%201.668-1.668h1.996c3.051%200%205.555-2.503%205.555-5.554C21.965%206.012%2017.461%202%2012%202z%22%2F%3E%3C%2Fsvg%3E"); }
.ico-sparkles { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M9.94%2015.5A2%202%200%200%200%208.5%2014.06l-6.14-1.58a.5.5%200%200%201%200-.96L8.5%209.94A2%202%200%200%200%209.94%208.5l1.58-6.14a.5.5%200%200%201%20.96%200L14.06%208.5A2%202%200%200%200%2015.5%209.94l6.14%201.58a.5.5%200%200%201%200%20.96L15.5%2014.06a2%202%200%200%200-1.44%201.44l-1.58%206.14a.5.5%200%200%201-.96%200z%22%2F%3E%3Cpath%20d%3D%22M20%203v4%22%2F%3E%3Cpath%20d%3D%22M22%205h-4%22%2F%3E%3Cpath%20d%3D%22M4%2017v2%22%2F%3E%3Cpath%20d%3D%22M5%2018H3%22%2F%3E%3C%2Fsvg%3E"); }
.ico-film { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20width%3D%2218%22%20height%3D%2218%22%20x%3D%223%22%20y%3D%223%22%20rx%3D%222%22%2F%3E%3Cpath%20d%3D%22M7%203v18%22%2F%3E%3Cpath%20d%3D%22M3%207.5h4%22%2F%3E%3Cpath%20d%3D%22M3%2012h18%22%2F%3E%3Cpath%20d%3D%22M3%2016.5h4%22%2F%3E%3Cpath%20d%3D%22M17%203v18%22%2F%3E%3Cpath%20d%3D%22M17%207.5h4%22%2F%3E%3Cpath%20d%3D%22M17%2016.5h4%22%2F%3E%3C%2Fsvg%3E"); }
.ico-mic { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M12%202a3%203%200%200%200-3%203v7a3%203%200%200%200%206%200V5a3%203%200%200%200-3-3Z%22%2F%3E%3Cpath%20d%3D%22M19%2010v2a7%207%200%200%201-14%200v-2%22%2F%3E%3Cline%20x1%3D%2212%22%20x2%3D%2212%22%20y1%3D%2219%22%20y2%3D%2222%22%2F%3E%3C%2Fsvg%3E"); }
.ico-scissors { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%2F%3E%3Cpath%20d%3D%22M8.12%208.12%2012%2012%22%2F%3E%3Cpath%20d%3D%22M20%204%208.12%2015.88%22%2F%3E%3Ccircle%20cx%3D%226%22%20cy%3D%2218%22%20r%3D%223%22%2F%3E%3Cpath%20d%3D%22M14.8%2014.8%2020%2020%22%2F%3E%3C%2Fsvg%3E"); }
.ico-code { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22m18%2016%204-4-4-4%22%2F%3E%3Cpath%20d%3D%22m6%208-4%204%204%204%22%2F%3E%3Cpath%20d%3D%22m14.5%204-5%2016%22%2F%3E%3C%2Fsvg%3E"); }
.ico-braces { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M8%203H7a2%202%200%200%200-2%202v5a2%202%200%200%201-2%202%202%202%200%200%201%202%202v5a2%202%200%200%200%202%202h1%22%2F%3E%3Cpath%20d%3D%22M16%2021h1a2%202%200%200%200%202-2v-5a2%202%200%200%201%202-2%202%202%200%200%201-2-2V5a2%202%200%200%200-2-2h-1%22%2F%3E%3C%2Fsvg%3E"); }
.ico-bot { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M12%208V4H8%22%2F%3E%3Crect%20width%3D%2216%22%20height%3D%2212%22%20x%3D%224%22%20y%3D%228%22%20rx%3D%222%22%2F%3E%3Cpath%20d%3D%22M2%2014h2%22%2F%3E%3Cpath%20d%3D%22M20%2014h2%22%2F%3E%3Cpath%20d%3D%22M15%2013v2%22%2F%3E%3Cpath%20d%3D%22M9%2013v2%22%2F%3E%3C%2Fsvg%3E"); }
.ico-merge { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Ccircle%20cx%3D%2218%22%20cy%3D%2218%22%20r%3D%223%22%2F%3E%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%2F%3E%3Cpath%20d%3D%22M6%2021V9a9%209%200%200%200%209%209%22%2F%3E%3C%2Fsvg%3E"); }
.ico-messages { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M14%209a2%202%200%200%201-2%202H6l-4%204V4c0-1.1.9-2%202-2h8a2%202%200%200%201%202%202z%22%2F%3E%3Cpath%20d%3D%22M18%209h2a2%202%200%200%201%202%202v11l-4-4h-6a2%202%200%200%201-2-2v-1%22%2F%3E%3C%2Fsvg%3E"); }
.ico-bulb { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M15%2014c.2-1%20.7-1.7%201.5-2.5%201-.9%201.5-2.2%201.5-3.5A6%206%200%200%200%206%208c0%201%20.2%202.2%201.5%203.5.7.7%201.3%201.5%201.5%202.5%22%2F%3E%3Cpath%20d%3D%22M9%2018h6%22%2F%3E%3Cpath%20d%3D%22M10%2022h4%22%2F%3E%3C%2Fsvg%3E"); }
.ico-layers { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M12.83%202.18a2%202%200%200%200-1.66%200L2.6%206.08a1%201%200%200%200%200%201.83l8.58%203.91a2%202%200%200%200%201.66%200l8.58-3.9a1%201%200%200%200%200-1.83Z%22%2F%3E%3Cpath%20d%3D%22m22%2017.65-9.17%204.16a2%202%200%200%201-1.66%200L2%2017.65%22%2F%3E%3Cpath%20d%3D%22m22%2012.65-9.17%204.16a2%202%200%200%201-1.66%200L2%2012.65%22%2F%3E%3C%2Fsvg%3E"); }
.ico-columns { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20width%3D%2218%22%20height%3D%2218%22%20x%3D%223%22%20y%3D%223%22%20rx%3D%222%22%2F%3E%3Cpath%20d%3D%22M9%203v18%22%2F%3E%3Cpath%20d%3D%22M15%203v18%22%2F%3E%3C%2Fsvg%3E"); }
.ico-minimize { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpolyline%20points%3D%224%2014%2010%2014%2010%2020%22%2F%3E%3Cpolyline%20points%3D%2220%2010%2014%2010%2014%204%22%2F%3E%3Cline%20x1%3D%2214%22%20x2%3D%2221%22%20y1%3D%2210%22%20y2%3D%223%22%2F%3E%3Cline%20x1%3D%223%22%20x2%3D%2210%22%20y1%3D%2221%22%20y2%3D%2214%22%2F%3E%3C%2Fsvg%3E"); }
.ico-pointer { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22m9%209%205%2012%201.8-5.2L21%2014Z%22%2F%3E%3Cpath%20d%3D%22M7.2%202.2%208%205.1%22%2F%3E%3Cpath%20d%3D%22m5.1%208-2.9-.8%22%2F%3E%3Cpath%20d%3D%22M14%204.1%2012%206%22%2F%3E%3Cpath%20d%3D%22m6%2012-1.9%202%22%2F%3E%3C%2Fsvg%3E"); }
.ico-maximize { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpolyline%20points%3D%2215%203%2021%203%2021%209%22%2F%3E%3Cpolyline%20points%3D%229%2021%203%2021%203%2015%22%2F%3E%3Cline%20x1%3D%2221%22%20x2%3D%2214%22%20y1%3D%223%22%20y2%3D%2210%22%2F%3E%3Cline%20x1%3D%223%22%20x2%3D%2210%22%20y1%3D%2221%22%20y2%3D%2214%22%2F%3E%3C%2Fsvg%3E"); }
.ico-apps { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20x%3D%222%22%20y%3D%224%22%20width%3D%2220%22%20height%3D%2216%22%20rx%3D%222%22%2F%3E%3Cpath%20d%3D%22M2%209h20%22%2F%3E%3Cpath%20d%3D%22M6%204v5%22%2F%3E%3Cpath%20d%3D%22M10%204v5%22%2F%3E%3C%2Fsvg%3E"); }
.ico-zap { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M4%2014a1%201%200%200%201-.78-1.63l9.9-10.2a.5.5%200%200%201%20.86.46l-1.92%206.02A1%201%200%200%200%2013%2010h7a1%201%200%200%201%20.78%201.63l-9.9%2010.2a.5.5%200%200%201-.86-.46l1.92-6.02A1%201%200%200%200%2011%2014z%22%2F%3E%3C%2Fsvg%3E"); }
.ico-languages { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22m5%208%206%206%22%2F%3E%3Cpath%20d%3D%22m4%2014%206-6%202-3%22%2F%3E%3Cpath%20d%3D%22M2%205h12%22%2F%3E%3Cpath%20d%3D%22M7%202h1%22%2F%3E%3Cpath%20d%3D%22m22%2022-5-10-5%2010%22%2F%3E%3Cpath%20d%3D%22M14%2018h6%22%2F%3E%3C%2Fsvg%3E"); }
.ico-heart { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M19%2014c1.49-1.46%203-3.21%203-5.5A5.5%205.5%200%200%200%2016.5%203c-1.76%200-3%20.5-4.5%202-1.5-1.5-2.74-2-4.5-2A5.5%205.5%200%200%200%202%208.5c0%202.3%201.5%204.05%203%205.5l7%207Z%22%2F%3E%3C%2Fsvg%3E"); }
.ico-mail { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20width%3D%2220%22%20height%3D%2216%22%20x%3D%222%22%20y%3D%224%22%20rx%3D%222%22%2F%3E%3Cpath%20d%3D%22m22%207-8.97%205.7a1.94%201.94%200%200%201-2.06%200L2%207%22%2F%3E%3C%2Fsvg%3E"); }
.ico-grid { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20width%3D%227%22%20height%3D%227%22%20x%3D%223%22%20y%3D%223%22%20rx%3D%221%22%2F%3E%3Crect%20width%3D%227%22%20height%3D%227%22%20x%3D%2214%22%20y%3D%223%22%20rx%3D%221%22%2F%3E%3Crect%20width%3D%227%22%20height%3D%227%22%20x%3D%2214%22%20y%3D%2214%22%20rx%3D%221%22%2F%3E%3Crect%20width%3D%227%22%20height%3D%227%22%20x%3D%223%22%20y%3D%2214%22%20rx%3D%221%22%2F%3E%3C%2Fsvg%3E"); }
.ico-moon { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M12%203a6%206%200%200%200%209%209%209%209%200%201%201-9-9Z%22%2F%3E%3C%2Fsvg%3E"); }
.ico-linkedin { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M16%208a6%206%200%200%201%206%206v7h-4v-7a2%202%200%200%200-2-2%202%202%200%200%200-2%202v7h-4v-7a6%206%200%200%201%206-6z%22%2F%3E%3Crect%20width%3D%224%22%20height%3D%2212%22%20x%3D%222%22%20y%3D%229%22%2F%3E%3Ccircle%20cx%3D%224%22%20cy%3D%224%22%20r%3D%222%22%2F%3E%3C%2Fsvg%3E"); }
.ico-github { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M15%2022v-4a4.8%204.8%200%200%200-1-3.5c3%200%206-2%206-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35%200-3.5%200%200-1%200-3%201.5-2.64-.5-5.36-.5-8%200C6%202%205%202%205%202c-.3%201.15-.3%202.35%200%203.5A5.403%205.403%200%200%200%204%209c0%203.5%203%205.5%206%205.5-.39.49-.68%201.05-.85%201.65-.17.6-.22%201.23-.15%201.85v4%22%2F%3E%3Cpath%20d%3D%22M9%2018c-4.51%202-5-2-7-2%22%2F%3E%3C%2Fsvg%3E"); }
.ico-instagram { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20width%3D%2220%22%20height%3D%2220%22%20x%3D%222%22%20y%3D%222%22%20rx%3D%225%22%20ry%3D%225%22%2F%3E%3Cpath%20d%3D%22M16%2011.37A4%204%200%201%201%2012.63%208%204%204%200%200%201%2016%2011.37z%22%2F%3E%3Cline%20x1%3D%2217.5%22%20x2%3D%2217.51%22%20y1%3D%226.5%22%20y2%3D%226.5%22%2F%3E%3C%2Fsvg%3E"); }
.ico-cv { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M15%202H6a2%202%200%200%200-2%202v16a2%202%200%200%200%202%202h12a2%202%200%200%200%202-2V7Z%22%2F%3E%3Cpath%20d%3D%22M14%202v4a2%202%200%200%200%202%202h4%22%2F%3E%3Cpath%20d%3D%22M10%209H8%22%2F%3E%3Cpath%20d%3D%22M16%2013H8%22%2F%3E%3Cpath%20d%3D%22M16%2017H8%22%2F%3E%3C%2Fsvg%3E"); }
.ico-rotate { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M3%2012a9%209%200%201%200%209-9%209.75%209.75%200%200%200-6.74%202.74L3%208%22%2F%3E%3Cpath%20d%3D%22M3%203v5h5%22%2F%3E%3C%2Fsvg%3E"); }
.ico-arrow-right { --ico: var(--arrow); }
.ico-download { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M21%2015v4a2%202%200%200%201-2%202H5a2%202%200%200%201-2-2v-4%22%2F%3E%3Cpath%20d%3D%22M7%2010l5%205%205-5%22%2F%3E%3Cpath%20d%3D%22M12%2015V3%22%2F%3E%3C%2Fsvg%3E"); }
.ico-flip { --ico: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M8%203H5a2%202%200%200%200-2%202v14a2%202%200%200%200%202%202h3%22%2F%3E%3Cpath%20d%3D%22M16%203h3a2%202%200%200%201%202%202v14a2%202%200%200%201-2%202h-3%22%2F%3E%3Cpath%20d%3D%22M12%2020v2%22%2F%3E%3Cpath%20d%3D%22M12%2014v2%22%2F%3E%3Cpath%20d%3D%22M12%208v2%22%2F%3E%3Cpath%20d%3D%22M12%202v2%22%2F%3E%3C%2Fsvg%3E"); }

/* Home layout: sidebar holds the bio and the two feeds, projects
   masonry fills the rest. Multi-column keeps the masonry script free. */

body.home {
    display: grid;
    grid-template-columns: var(--col) minmax(0, 1fr);
    gap: var(--gap);
    padding: var(--gap);
    align-items: start;
}

.side {
    display: grid;
    gap: var(--gap);
}

.grid {
    columns: var(--col);
    column-gap: var(--gap);
}

/* grid.js deals the cards into real columns so they read left to right */

.grid.dealt {
    columns: auto;
    display: flex;
    gap: var(--gap);
    align-items: start;
}

.col {
    flex: 1;
    min-width: 0;
}

/* Bio card: a broad wash of the highlight that lifts on hover, and a flip
   to the avatar on the back. Registering the mix amount is what lets it
   transition - gradients themselves do not interpolate reliably. */

@property --glow {
    syntax: "<percentage>";
    inherits: false;
    initial-value: 26%;
}

.flip {
    position: relative;
}

.flip-in { /* hidden but focusable, so the flip works from the keyboard */
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
}

.flip-card {
    position: relative;
    transform: perspective(900px) rotateY(var(--flip, 0deg));
    transform-style: preserve-3d;
    transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}

.flip:has(.flip-in:checked) .flip-card {
    --flip: 180deg;
}

.bio,
.bio-back {
    backface-visibility: hidden;
}

.bio {
    --glow: 26%;

    padding: 1.5rem;
    border: 1px solid color-mix(in oklab, var(--accent) 30%, var(--rule));
    border-radius: var(--radius);

    /* background-origin defaults to padding-box while background-clip defaults
       to border-box, so the gradient was tiling and the 1px border strip showed
       the warm top of the next tile. Sizing it to the border box removes that. */
    background-color: var(--surface);
    background-origin: border-box;
    background-repeat: no-repeat;

    /* two layers: a glancing sheen over the accent wash underneath */
    background-image:
        linear-gradient(142deg, rgb(255 255 255 / 0.11), transparent 46%),
        radial-gradient(
            525% 100% at 92% 0%,
            color-mix(in oklab, var(--accent) var(--glow), var(--surface)),
            var(--surface) 100%
        );

    /* a lit top edge, a seat shadow, and a soft accent bloom beneath */
    box-shadow:
        inset 0 1px 0 rgb(255 255 255 / 0.16),
        inset 0 0 0 1px rgb(255 255 255 / 0.03),
        0 2px 4px rgb(0 0 0 / 0.5),
        0 20px 44px -26px color-mix(in oklab, var(--accent) 60%, transparent);
    transition:
        --glow 0.35s ease-out,
        border-color 0.35s ease-out,
        box-shadow 0.35s ease-out;
}

.flip:hover .bio {
    --glow: 52%;

    border-color: color-mix(in oklab, var(--accent) 55%, var(--rule));
    box-shadow:
        inset 0 1px 0 rgb(255 255 255 / 0.22),
        inset 0 0 0 1px rgb(255 255 255 / 0.05),
        0 2px 4px rgb(0 0 0 / 0.5),
        0 26px 56px -24px color-mix(in oklab, var(--accent) 75%, transparent);
}

.bio-back { /* flat highlight, no gradient */
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    padding: 1.5rem;
    border-radius: var(--radius);
    background-color: var(--accent);
    transform: rotateY(180deg);
}

.bio-back img {
    width: 72%;
    height: auto;
}

/* One toggle for both faces. It sits outside .flip-card, because a face
   rotated away from the viewer does not reliably take real mouse clicks.
   The label is an invisible corner the button only appears inside of. */

.flip-zone {
    position: absolute;
    z-index: 2;
    top: 0;
    right: 0;
    width: 5rem;
    height: 5rem;
    cursor: pointer;
}

.flip-btn {
    position: absolute;
    top: 1rem;
    right: 1rem;
    display: grid;
    place-items: center;
    width: 1.9rem;
    height: 1.9rem;
    border: 1px solid var(--rule);
    border-radius: 50%;
    color: var(--muted);
    opacity: 0;
    transform: scale(0.8);
    transition:
        opacity 0.25s,
        transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1),
        color 0.2s,
        border-color 0.2s,
        background-color 0.2s;
}

.flip-btn .ico {
    font-size: 1rem;
}

.flip-zone:hover .flip-btn,
.flip-in:focus-visible + .flip-zone .flip-btn {
    opacity: 1;
    transform: scale(1);
    border-color: transparent;
    background-color: var(--accent);
    color: var(--bg);
}

/* Flipped: the button now sits on the highlight, so invert it */

.flip:has(.flip-in:checked) .flip-zone:hover .flip-btn,
.flip:has(.flip-in:checked) .flip-in:focus-visible + .flip-zone .flip-btn {
    background-color: var(--bg);
    color: var(--accent);
}

@media (hover: none) { /* no hover to reveal it on touch */
    .flip-btn {
        opacity: 1;
        transform: none;
    }
}

.avatar {
    width: 6rem;
    height: 6rem;
    margin-bottom: 1.5rem;
    border-radius: 50%;
    object-fit: cover;
}

.bio h1 {
    font-size: var(--text-2xl);
    font-weight: 800;
    font-variation-settings: "wght" 800;
    letter-spacing: -0.03em;
    line-height: 1.1;
}

.role {
    margin-bottom: 1.25rem;
    color: var(--muted);
    font-size: var(--text-sm);
}

.bio > p:not(.role) {
    color: color-mix(in oklab, var(--fg) 72%, var(--muted));
    font-size: var(--text-sm);
}

.social {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin: 1.5rem 0 1rem;
}

/* .social a and .stack li use .btn-sm geometry via the shared variables */

/* Stack card: same pill treatment as the bio links */

.panel { /* shared chrome for the side cards */
    padding: 1.5rem;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background-color: var(--surface);
}

.panel h2 {
    color: var(--muted);
    font-size: var(--text-xs);
    font-weight: 400;
    font-variation-settings: "wght" 400;
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

.stack-head { /* Title and reset share one line */
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}

.panel ul {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: 0.75rem;
}

.stack > h2 {
    margin-top: 1.5rem;
}

.stack li {
    display: inline-flex;
    align-items: center;
    min-height: 1.9rem;
    padding-inline: 0.7rem;
    border-radius: 2rem;
    background-color: var(--chip);
    color: var(--fg);
    font-size: var(--text-xs);
    cursor: grab;
    touch-action: none;
    user-select: none;
    transition:
        transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1),
        background-color 0.2s,
        color 0.2s;
}

.stack li:hover {
    transform: rotate(-6deg);
    background-color: var(--accent);
    color: var(--bg);
}

.stack li.dragging {
    position: relative;
    z-index: 2;
    cursor: grabbing;
    background-color: var(--accent);
    color: var(--bg);
    box-shadow: 0 0.6rem 1.4rem rgb(0 0 0 / 0.5);
    transition: none;
}

.reset {
    flex: none;
    display: grid;
    place-items: center;
    width: 1.5rem;
    height: 1.5rem;
    padding: 0;
    border: 1px solid var(--rule);
    border-radius: 50%;
    background: none;
    color: var(--muted);
    font-size: 0.8rem;
    line-height: 1;
    cursor: pointer;
    transition:
        color 0.2s,
        border-color 0.2s,
        transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.reset:hover {
    border-color: transparent;
    background-color: var(--accent);
    color: var(--bg);
    transform: rotate(-180deg);
}

/* Cards: artwork edge to edge, the name rides in on hover */

.card {
    position: relative;
    display: block;
    overflow: hidden;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background-color: var(--surface);
    transition:
        border-color 0.25s,
        transform 0.08s;
}

.card {
    break-inside: avoid;
    margin-bottom: var(--gap);
}

.card:hover {
    border-color: color-mix(in oklab, var(--fg) 28%, transparent);
}

.card img,
.card video {
    display: block;
    width: 100%;
    height: auto;
    transition: filter 0.25s;
}

.card:hover img,
.card:hover video {
    filter: brightness(0.55);
}

.card::after { /* Diagonal arrow, rides in from below-left on hover */
    content: "";
    position: absolute;
    top: 16px;
    right: 16px;
    width: 1rem;
    height: 1rem;
    background: url("../assets/arrow.svg") center / contain no-repeat;
    opacity: 0;
    transform: translate(-0.45rem, 0.55rem);
    transition:
        opacity 0.3s,
        transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.card:hover::after,
.card:focus-visible::after {
    opacity: 1;
    transform: translate(0, 0);
}

.label {
    position: absolute;
    inset: auto 0 0 0;
    display: grid;
    gap: 0.1rem;
    padding: 3rem 1rem 1rem;
    background: linear-gradient(transparent, rgb(0 0 0 / 0.8));
    color: var(--muted);
    font-size: var(--text-xs);
    opacity: 0;
    transition: opacity 0.25s;
}

.label strong { /* White pill, slides up from below and rests at -3deg */
    justify-self: start;
    margin-bottom: calc(0.15rem + 5px); /* the -3deg tilt needs clearance */
    padding: 0.2em 0.75em;
    border-radius: 2rem;
    background-color: #fff;
    color: var(--bg);
    font-size: var(--text-base);
    font-weight: 600;
    font-variation-settings: "wght" 600;
    letter-spacing: -0.02em;
    transform: translateY(150%) rotate(-3deg);
    transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.card:hover .label strong,
.card:focus-visible .label strong {
    transform: translateY(0) rotate(-3deg);
}

.card:hover .label,
.card:focus-visible .label {
    opacity: 1;
}

/* Responsive styles */

@media (max-width: 52rem) {
    /* Single column: bio on top, work, then the feeds at the very bottom */

    body.home {
        grid-template-columns: minmax(0, 1fr);
    }

    .side {
        display: contents;
    }

    .flip {
        order: 1;
    }

    /* work first on a phone; the reference cards go under it */

    .grid {
        order: 2;
    }

    .stack {
        order: 3;
    }

    .hobbies {
        order: 4;
    }

}

@media (max-width: 30rem) {
    :root {
        --gap: 0.625rem;
        --radius: 1rem;
    }

    .bio,
    .bio-back {
        padding: 1.25rem;
    }
}

/* Project page */

.project {
    --measure: 82rem;
}

.project-foot a {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: var(--text-sm);
    color: var(--muted);
    transition:
        color 0.2s,
        transform 0.08s;
}

.project-foot a:hover {
    color: var(--fg);
}

/* Avatar mark, pinned top left, links back to the index */

.home-mark {
    position: fixed;
    top: var(--gap);
    z-index: 10;
    display: block;
    overflow: hidden;
    width: 6rem;
    height: 6rem;
    margin-inline: var(--gap) auto;

    transition:
        transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.home-mark:hover {
    transform: scale(1.08);
}

.home-mark img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* A 96px mark is heavy on a phone, and the title has to clear it */

@media (max-width: 52rem) {
    .home-mark {
        width: 2.75rem;
        height: 2.75rem;
    }
}

/* Return pill, parked at the bottom left of the viewport. Geometry comes
   from .btn; this adds the pinning and the blur behind it. */

.back {
    position: fixed;
    left: var(--gap);
    bottom: var(--gap);
    z-index: 10;
    border-color: var(--rule);
    background-color: color-mix(in oklab, var(--surface) 82%, transparent);
    backdrop-filter: blur(12px);
    color: var(--muted);
    font-weight: 400;
    font-variation-settings: "wght" 400;
}

.back::before { /* arrow sits before the label */
    content: "";
    width: 1em;
    height: 1em;
    background-color: currentColor;
    mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22m12%2019-7-7%207-7%22%2F%3E%3Cpath%20d%3D%22M19%2012H5%22%2F%3E%3C%2Fsvg%3E") center / contain no-repeat;
}

.back:hover {
    border-color: transparent;
    background-color: var(--accent);
    color: var(--bg);
    transform: none;
}

.project > section,
.intro {
    max-width: var(--measure);
    margin-inline: auto;
    padding: 0 1.5rem;
}

.intro {
    /* The mark is fixed at the top left. Where the measure is wide enough to
       clear it, the title sits level with it; below that the content starts
       just under it instead of running behind it. */
    padding-top: 3rem;
    padding-bottom: 2rem;
}

@media (max-width: 94rem) {
    .intro {
        padding-top: 9rem;
    }
}

@media (max-width: 52rem) {
    .intro {
        padding-top: 5.5rem;
    }
}

.intro h1 {
    font-size: clamp(2.5rem, 7vw, 4.5rem);
    font-weight: 800;
    font-variation-settings: "wght" 800;
    letter-spacing: -0.04em;
    line-height: 1;
}

.intro .role {
    margin-top: 0.5rem;
    font-size: var(--text-xl);
    color: var(--accent);
    letter-spacing: -0.02em;
}

.meta {
    display: flex;
    flex-wrap: wrap;
    gap: 2rem;
    margin: 2rem 0;
    padding: 1.25rem 0;
    border-block: 1px solid var(--rule);
}

.meta dt {
    color: var(--muted);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

.meta dd {
    margin: 0.25rem 0 0;
    font-size: var(--text-sm);
}

.summary {
    font-size: var(--text-lg);
    color: var(--muted);
}

.project h2 { /* section */
    margin-top: 5rem;
    font-size: var(--h2);
    font-weight: 700;
    font-variation-settings: "wght" 700;
    letter-spacing: -0.03em;
}

.project h3 { /* subsection */
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 2.75rem;
    font-size: var(--h3);
    font-weight: 600;
    font-variation-settings: "wght" 600;
}

.project h4 { /* card and figure titles */
    margin-top: 1.75rem;
    font-size: var(--h4);
    font-weight: 600;
    font-variation-settings: "wght" 600;
}

.project h5 { /* label above a group */
    margin-top: 1.5rem;
    color: var(--muted);
    font-size: var(--h5);
    font-weight: 400;
    font-variation-settings: "wght" 400;
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

.project p {
    max-width: 62ch;
    margin-top: 1rem;
    color: var(--muted);
}

.lede {
    font-size: var(--text-lg);
}

/* Intro sits as two columns so the summary is not a narrow ribbon */

.intro-body {
    display: grid;
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    gap: 3rem;
    align-items: start;
    margin-top: 2rem;
}

.meta { /* no top padding, so it starts level with the summary */
    display: grid;
    gap: 1.25rem;
    margin: 0;
    padding: 0;
    border-block: 0;
}

.intro-body > .summary,
.intro-body > .meta { /* beats `.project p`, which sets a top margin */
    margin-top: 0;
}

/* Body copy runs in two columns, everywhere on the page */

.copy {
    padding-block: 1.5rem;
    max-width: 76ch;
}

/* Two columns only when there is enough text to fill them. A lone paragraph
   stays one column, which avoids a stranded word in the second. */

.copy:has(p + p) {
    columns: 2;
    column-gap: 3rem;
    max-width: none;
    orphans: 2;
    widows: 2;
}

.copy p {
    max-width: none;
    margin-top: 0;
}

.copy p + p {
    margin-top: 1rem;
}

@media (max-width: 52rem) {
    .copy {
        columns: 1;
    }
}

.out { /* outbound link, styled as the solid button */
    margin-top: 1.5rem;
}

.links { /* a row of live-project buttons */
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    padding-block: 0 1.5rem;
}

.links .out {
    margin-top: 0;
}

/* Card grid: for groups of short titled notes that are not images */

.cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
    gap: var(--gap);
    padding-block: 1.5rem;
}

.note {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 1.5rem;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background-color: var(--surface);
    transition:
        border-color 0.25s,
        transform 0.08s;
}

.note:hover {
    border-color: color-mix(in oklab, var(--accent) 45%, transparent);
}

.note h4 {
    margin: 0;
    font-size: var(--h4);
}

.note p {
    max-width: none;
    margin: 0;
    font-size: var(--text-sm);
}

/* Image groups: masonry, with wide shots spanning the full measure */

.shots {
    columns: 26rem;
    column-gap: var(--gap);
    padding-block: 1.5rem;
}

.shots:has(> :only-child), /* nothing to balance, so use the full measure */
.shots-single {
    columns: 1;
}

.shots-square { /* square covers tile tighter than 16:9 shots */
    columns: 18rem;
}

.shots-narrow { /* portrait app screens, so more of them per row */
    columns: 17rem;
}

.lead-shot { /* opens a section, before the copy */
    margin-top: 1.5rem;
}

/* Heading and its link share a line */

.head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-top: 2.75rem;
}

.head h3,
.head .out {
    margin-top: 0;
}

.head-close { /* button sits 32px after the heading, not at the far edge */
    justify-content: flex-start;
    gap: 2rem;
}

video.shot { /* something to look at while the file arrives */
    background-color: var(--surface-hi);
}

.shot {
    display: block;
    width: 100%;
    height: auto;
    break-inside: avoid;
    margin-bottom: var(--gap);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background-color: var(--surface);
}


.project-foot {
    display: flex;
    justify-content: space-between;
    gap: var(--gap);
    max-width: var(--measure);
    margin: 6rem auto 0;
    padding: 2rem 1.5rem 4rem;
    border-top: 1px solid var(--rule);
}

@media (max-width: 52rem) {
    .intro-body {
        grid-template-columns: 1fr;
        gap: 2rem;
    }
}

/* Techboi Pixels specimen and the looping glyph strip */

.project .specimen { /* beats `.project p`, which mutes the colour */
    max-width: none;
    margin-top: 1.5rem;
    color: var(--fg);
    font-family: "Techboi Pixels", "Nohemi", sans-serif;
    font-size: clamp(2rem, 6vw, 5rem);
    line-height: 1.35; /* the glyphs overshoot the em box badly */
    text-transform: uppercase;
    text-wrap: balance;
}

.marquee {
    overflow: hidden;
    max-width: var(--measure);
    margin: 1.5rem auto 0;
    padding-inline: 1.5rem;
    mask: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}

.marquee-track {
    display: flex;
    width: max-content;
    animation: slide 38s linear infinite;
}

.marquee img {
    display: block;
    height: clamp(3rem, 7vw, 6rem);
    width: auto;
    padding-right: 3rem;
    filter: invert(1); /* glyphs are black on transparent, so flip to light */
}

@keyframes slide {
    to {
        transform: translateX(-50%);
    }
}

@media (prefers-reduced-motion: reduce) {
    .marquee-track {
        animation: none;
    }
}

/* Feature lists: arrow bullets in the highlight colour */

.list {
    columns: 2;
    column-gap: 3rem;
    padding-block: 1.5rem;
}

.list li {
    display: flex;
    gap: 0.6rem;
    break-inside: avoid;
    margin-bottom: 0.65rem;
    color: var(--muted);
}

.list li::before {
    content: "";
    flex: none;
    width: 1em;
    height: 1em;
    margin-top: 0.24em;
    background-color: var(--accent);
    mask: var(--arrow) center / contain no-repeat;
}

@media (max-width: 52rem) {
    .list {
        columns: 1;
    }
}

/* Before/after pair. The second column is 30% wider than the first, so the
   new design carries more weight than what it replaced. */

.compare {
    display: grid;
    grid-template-columns: 1fr 1.3fr;
    gap: 2rem;
    align-items: start;
    padding-block: 1.5rem;
}

.compare figure {
    margin: 0;
}

/* Both heading rows are button height, so the two images start level even
   though only one column carries a link. */

.compare h3,
.compare .head {
    align-items: center;
    min-height: var(--btn-h);
    margin-top: 0;
    margin-bottom: 1rem;
}

.compare .head h3 {
    min-height: 0;
    margin-bottom: 0;
}

.compare .shot {
    margin-bottom: 0;
}

@media (max-width: 52rem) {
    .compare {
        grid-template-columns: minmax(0, 1fr);
        gap: 2.5rem;
    }
}

/* ── Arrival ──────────────────────────────────────────────────────────────
   Cards assemble from the four edges on load: top, right, bottom, left, in
   four staggered waves. 0.45s each + 0.15s of stagger = 0.6s in total.

   fill-mode is `backwards`, not `both`, on purpose: `both` would pin the
   final transform forever and beat the :active press state underneath it. */

@keyframes deal-in {
    from {
        opacity: 0;
        transform: translate(var(--dx, 0), var(--dy, 0));
    }

    to {
        opacity: 1;
        transform: none;
    }
}

@media (prefers-reduced-motion: no-preference) {
    .grid .card,
    .flip,
    .stack,
    .hobbies {
        /* 30% slower than the original 0.45s / 0.15s */
        --dur: 0.585s;
        --spread: 0.195s;
        --step: calc(var(--spread) / 22); /* 23 cards, so 22 gaps */

        animation: deal-in var(--dur) cubic-bezier(0.22, 1, 0.36, 1) backwards;
        animation-delay: calc(var(--i, 0) * var(--step));
    }

    /* direction cycles with the reading position, set by grid.js */
    .card[data-dir="0"] { --dy: -2.5rem; }
    .card[data-dir="1"] { --dx: 2.5rem; }
    .card[data-dir="2"] { --dy: 2.5rem; }
    .card[data-dir="3"] { --dx: -2.5rem; }

    .flip { --dx: -2.5rem; --i: 0; }
    .stack { --dy: 2.5rem; --i: 1; }
    .hobbies { --dy: 2.5rem; --i: 2; }

    /* once settled, drop the animation so re-dealing on resize is silent */
    body[data-settled] .card,
    body[data-settled] .flip,
    body[data-settled] .stack,
    body[data-settled] .hobbies {
        animation: none;
    }
}

/* ── Press and focus ──────────────────────────────────────────────────────
   Every trigger needs a visible pressed state and a keyboard-visible focus
   ring, not just hover. Press feedback is deliberately fast (~80ms) so it
   reads as a direct response rather than an animation. */

:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}

/* the flip toggle is a 1px visually-hidden checkbox, so its focus has to be
   shown on the button that represents it */

.flip-in:focus-visible {
    outline: none;
}

.flip-in:focus-visible + .flip-zone .flip-btn {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}

.btn:active {
    transform: translateY(1px) scale(0.98);
    transition-duration: 0.08s;
}

.card:active {
    transform: scale(0.99);
    transition-duration: 0.08s;
}

.home-mark:active {
    transform: scale(0.95);
    transition-duration: 0.08s;
}

.reset:active {
    transform: rotate(-180deg) scale(0.88);
    transition-duration: 0.08s;
}

.flip-zone:active .flip-btn {
    transform: scale(0.92);
    transition-duration: 0.08s;
}

.social a:active,
.project-foot a:active {
    transform: translateY(1px);
}

/* A video with its own controls is a deliberate watch, not wallpaper */

.film {
    margin-block: 1.5rem;
    background-color: #000;
}

/* Hobby tiles: label sits on the image itself. No box around them -- a card
   inside a card is never right, and the panel is already the card.
   Greyscale at rest, full colour on hover, with the same pill-and-arrow
   language the project cards use. */

.panel .hobby-list {
    display: grid;
    gap: var(--gap);
    margin-top: 0.75rem;
}

.hobby {
    position: relative;
    display: block;
    overflow: hidden;
    border-radius: calc(var(--radius) - 0.5rem);
    transition: transform 0.08s;
}

.hobby:active {
    transform: scale(0.99);
}

.hobby img { /* the files are not 16:9, so crop to it rather than letterbox */
    width: 100%;
    height: auto;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    opacity: 0.5;
    filter: grayscale(1);
    transition:
        opacity 0.35s,
        filter 0.35s;
}

.hobby:hover img,
.hobby:focus-visible img {
    opacity: 1;
    filter: grayscale(0);
}

.hobby-name { /* chip at rest, white and tilted on hover */
    position: absolute;
    bottom: 0.6rem;
    left: 0.6rem;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-height: 1.9rem;
    padding-inline: 0.7rem;
    border-radius: 2rem;
    background-color: var(--chip);
    color: var(--fg);
    font-size: var(--text-xs);
    font-weight: 600;
    font-variation-settings: "wght" 600;
    transition:
        background-color 0.25s,
        color 0.25s,
        transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.hobby:hover .hobby-name,
.hobby:focus-visible .hobby-name {
    background-color: #fff;
    color: var(--bg);
    transform: rotate(-3deg);
}

.hobby::after { /* diagonal arrow, rides in from below-left like the cards */
    content: "";
    position: absolute;
    top: 0.6rem;
    right: 0.6rem;
    display: grid;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 50%;
    background-color: var(--chip);
    background-image: url("../assets/arrow.svg");
    background-position: center;
    background-size: 0.85rem;
    background-repeat: no-repeat;
    opacity: 0;
    transform: translate(-0.45rem, 0.55rem);
    transition:
        opacity 0.3s,
        transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.hobby:hover::after,
.hobby:focus-visible::after {
    opacity: 1;
    transform: translate(0, 0);
}

/* Tag row: the chip treatment from the side panel, without the drag affordance */

.tags {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    padding-block: 1.5rem 0;
}

.tags li {
    display: inline-flex;
    align-items: center;
    min-height: 1.9rem;
    padding-inline: 0.7rem;
    border-radius: 2rem;
    background-color: var(--chip);
    color: var(--fg);
    font-size: var(--text-xs);
}

/* Info-card titles carry a small icon */

.note h4 {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.note h4 .ico {
    color: var(--accent);
    font-size: 1.05rem;
}

/* Techboi Pixels specimen: a size ladder and the character set */

.project .sample {
    max-width: none;
    margin-top: 0;
    color: var(--fg);
    font-family: "Techboi Pixels", "Nohemi", sans-serif;
    line-height: 1.3;
    text-transform: uppercase;
    overflow-wrap: anywhere;
}

.ladder {
    display: grid;
    gap: 1.25rem;
    padding-block: 1.5rem;
}

.ladder li {
    display: grid;
    grid-template-columns: 3rem minmax(0, 1fr);
    gap: 1.5rem;
    align-items: baseline;
    padding-top: 1.25rem;
    border-top: 1px solid var(--rule);
}

.ladder .size {
    color: var(--muted);
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: var(--text-xs);
}

.ladder .sample {
    font-size: var(--s);
}

.ladder li:nth-child(1) { --s: clamp(3rem, 8vw, 6rem); }
.ladder li:nth-child(2) { --s: clamp(2.25rem, 5.5vw, 4rem); }
.ladder li:nth-child(3) { --s: clamp(1.75rem, 4vw, 3rem); }
.ladder li:nth-child(4) { --s: 2rem; }
.ladder li:nth-child(5) { --s: 1.5rem; }
.ladder li:nth-child(6) { --s: 1rem; }

.charset {
    display: grid;
    gap: 0.75rem;
    padding-block: 1.5rem;
}

.charset .sample {
    font-size: clamp(1.5rem, 4vw, 2.75rem);
    letter-spacing: 0.04em;
}

@media (max-width: 40rem) {
    .ladder li {
        grid-template-columns: minmax(0, 1fr);
        gap: 0.5rem;
    }
}

/* Side-panel labels carry a small icon, in the label's own muted tone */

.panel h2 {
    display: flex;
    align-items: center;
    gap: 0.45rem;
}

.panel h2 .ico {
    font-size: 0.95rem;
}

/* Techboi Pixels is a display face and falls apart at caption size. The small
   uppercase labels are headings only structurally, so they keep the text face. */

.panel h2,
.project h5 {
    font-family: "Nohemi", sans-serif;
}

/* ── Skeleton loader ──────────────────────────────────────────────────────
   A block that sweeps a highlight left to right while the media loads. It is
   a gradient, so it adds no bytes and paints with the stylesheet, long before
   the image or video it covers. media.js stamps [data-ready] when the content
   arrives; failing that, loaded media simply paints over its own background. */

@keyframes sweep {
    from {
        background-position: -150% 0;
    }

    to {
        background-position: 250% 0;
    }
}

.shot:not([data-ready]),
.card img:not([data-ready]),
.card video:not([data-ready]),
.hobby img:not([data-ready]),
.film:not([data-ready]) {
    background-color: var(--surface);
    background-image: linear-gradient(
        90deg,
        transparent 0%,
        color-mix(in oklab, var(--fg) 9%, transparent) 45%,
        color-mix(in oklab, var(--fg) 14%, transparent) 50%,
        color-mix(in oklab, var(--fg) 9%, transparent) 55%,
        transparent 100%
    );
    background-size: 60% 100%;
    background-repeat: no-repeat;
    animation: sweep 1.25s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
    .shot:not([data-ready]),
    .card img:not([data-ready]),
    .card video:not([data-ready]),
    .hobby img:not([data-ready]),
    .film:not([data-ready]) {
        animation: none;
        background-image: none;
        background-color: var(--surface-hi);
    }
}

/* Pixel-dispersion title: the canvas replaces the rendered heading, which
   stays in the DOM for assistive tech and search. Full width of the card. */

.pixel-title {
    position: relative;
    width: 100%;
    color: var(--fg);
}

.pixel-title.is-pixel {
    color: var(--fg);
    margin-bottom: 0.85rem; /* the canvas sits tight to its box, so add the air back */
    font-size: 0; /* hides the glyphs without hiding the text */
    line-height: 0;
}

.pixel-canvas {
    display: block;
    width: 100%;
    touch-action: none;
    cursor: crosshair;
}

/* masonry.js hands the galleries over from `columns` to real columns, so the
   distribution stops depending on the browser's balancer */

.shots.dealt {
    columns: auto;
    display: flex;
    gap: var(--gap);
    align-items: start;
}

.shot-col {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
}

.shot-col > .shot:last-child {
    margin-bottom: 0;
}

/* Feature row: the point on the left, the thing it describes on the right */

.project > .feature { /* outranks `.project > section`, which zeroes padding */
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 3rem;
    align-items: center;
    padding-block: 2.5rem; /* 40px above and below each row */
}

/* the run of features gets 80px clear at each end, and 80px between rows */

.project > .feature:not(.feature + .feature) {
    margin-top: 2.5rem;
}

.project > .feature:not(:has(+ .feature)) {
    margin-bottom: 2.5rem;
}

.feature .feature-text h3 {
    margin-top: 0;
}

.feature .feature-text p {
    margin-top: 1rem;
}

.feature > .shot { /* size to the artwork, not the column */
    width: auto;
    max-width: 100%;
    max-height: 28rem;
    margin: 0 auto;
}

@media (max-width: 52rem) {
    .feature {
        grid-template-columns: minmax(0, 1fr);
        gap: 1.5rem;
    }
}
