
@import "font.css";

:root {
    /* ---- Brand palette (Allons-y Studio brand kit) ---- */
    --static-ink: #33394E;
    --static-slate: #414C75;
    --static-paper: #FBF9F3;
    --static-cream: #EFEBDF;
    --static-latte: #C2B1A6;

    /* Brights */
    --static-magenta: #C92C6B;
    --on-brand-magenta: var(--static-paper);
    --text-magenta: light-dark(hsl(336deg 68% 40%), hsl(336deg 72% 72%));

    --static-orange: #FF9A56;
    --text-orange: light-dark(hsl(22deg 82% 37%), hsl(28deg 100% 72%));

    --static-cyan: #7BD1E6;
    --text-cyan: light-dark(hsl(193deg 58% 31%), hsl(192deg 68% 72%));

    --static-yellow: light-dark(#FFD36C, #FFC233);
    --text-yellow: light-dark(hsl(38deg 88% 32%), hsl(45deg 100% 64%));

    /* ---- Feedback primitives ---- */
    --ui-success: light-dark(hsl(155deg 55% 35%), hsl(155deg 50% 55%));
    --on-success: var(--static-paper);

    --ui-error: light-dark(hsl(0deg 65% 45%), hsl(0deg 70% 60%));
    --on-error: var(--static-paper);

    --ui-warn: light-dark(hsl(40deg 86% 50%), hsl(42deg 100% 40%));
    --on-warn: var(--static-paper);

    --ui-light: light-dark(var(--static-latte), hsl(228deg 14% 32%));
    --on-light: var(--static-ink);

    /* ---- Gradients ----
       Warm-to-cool sweep (not the brand.js declaration order) so the stops
       read as one smooth hue travel instead of jumping straight from
       magenta to cyan. Used by .wordmark and the featured-badge chip. */
    --gradient-stack: var(--static-magenta), var(--static-orange), var(--static-yellow), var(--static-cyan);
    --gradient: linear-gradient(90deg, var(--gradient-stack));

    --bg: light-dark(var(--static-paper), hsl(228deg 20% 12%));
    --text: light-dark(var(--static-ink), var(--static-paper));

    /* was: subtle */
    --bg-light: light-dark(var(--static-cream), hsl(228deg 18% 17%));
    --text-light: light-dark(var(--static-slate), var(--static-latte));

	--accent-bg: light-dark(color-mix(in oklch, var(--static-cream) 50%, transparent), hsl(228deg 18% 17% / 50%));

    --bg-subtle: light-dark(color-mix(in oklch, var(--static-cream) 22%, transparent), hsl(228deg 18% 20% / 30%));

    /* @todo: should there be a separate disabled bg color? */
    --disabled: var(--bg-light);

    /* ---- The elevation ladder ---- */
    --bg-overlay: light-dark(var(--static-paper), hsl(228deg 15% 30%));

    /* ---- Typography ---- */
    --sans-font: "Instrument Sans", "Instrument Sans Fallback", system-ui, "Helvetica Neue", helvetica, arial, sans-serif;
    --dense-font: "Instrument Sans", "Instrument Sans Fallback", system-ui, sans-serif;
    --mono-font: "PT Mono", monospace;

    --base-font-size: 14px;
    --mono-font-size: max(var(--base-font-size), .8em);

    --font-size-5: max(var(--base-font-size), 1rem);
    --font-size-4: max(var(--base-font-size), calc(var(--font-size-5) * 1.25));
    --font-size-3: max(var(--base-font-size), calc(var(--font-size-4) * 1.25));
    --font-size-2: max(var(--base-font-size), calc(var(--font-size-3) * 1.25));
    --font-size-1: max(var(--base-font-size), calc(var(--font-size-2) * 1.25));
    --font-size-6: max(var(--base-font-size), calc(var(--font-size-5) / 1.25));
    --font-size-7: max(var(--base-font-size), calc(var(--font-size-6) / 1.25));

    /* ---- Font weights (Instrument Sans variable axis 400–700) ---- */
    --font-weight--thin: 100;
    --font-weight--extra-light: 200;
    --font-weight--light: 300;
    --font-weight: 400;

    /* Instrument Sans covers 400–700; Yellowtail + PT Mono are single-weight 400 */
    --font-weight--medium: 500;
    --font-weight--semi-bold: 600;
    --font-weight--bold: 700;

    --font-weight--extra-bold: 800;
    --font-weight--black: 900;

    /* ---- Line heights ---- */
    --font-height--dense: 1.1;
    --font-height: 1.5; /* body copy floor; below 1.5 fails WCAG 1.4.12 spacing */
    --font-height--spacious: 1.8;

    /* ---- Spacing ---- */
    --gutter: 4px;
    --content-space: 1em; /* ~16px at the base type size */
    --container-space: 16px;
    --section-space: 32px;

    /* ---- Shape ---- */
    --hairline-border-radius: 0.1em;
    --standard-border-radius: 0.2em;
    --squircle-border-radius: 40px;
    --pill-border-radius: 9999px;
    --circle-border-radius: 50%;

    --thin-border-width: 1px;
    --border-width: 2px;
    --thick-border-width: 4px;

    /* should never be smaller than 2px for a11y */
    --outline-width: max(var(--border-width), 2px);
    --outline-offset: calc(var(--outline-width) * 1.8);

    /* Composite shadows (color baked in) */
    --box-shadow: 0 10px 15px -10px light-dark(hsl(0deg 0% 60% / 60%), hsl(0deg 0% 30% / 60%));
    --box-shadow-light: 0 5px 5px -10px light-dark(hsl(0deg 0% 80% / 50%), hsl(0deg 0% 50% / 50%));

    /* ---- Sizing ---- */
    --container-max: 1200px;
    --content-max: 80ch;

    @media screen and (width <= 768px) {
        --container-space: 10px;
        --container-max: 100%;
    }

    /* Defaults mapped to the brand magenta */
    --ui: var(--static-magenta);
    --on-ui: var(--on-brand-magenta);
    --text-accent: var(--text-magenta);

    --code: var(--text);
    --preformatted: var(--text);

    /* @todo: mix this against state/should use --ui or --ui-light? */
    --ui--hover: color-mix(in oklch, var(--ui) 40%, transparent);
    --ui--active: color-mix(in oklch, var(--ui) 40%, transparent);

    /* Highlights (mapped to the brand yellow) */
    --marked: var(--static-yellow);

    /* ---- Section accent ---- */
    --section-ui: var(--ui);
    --section-text: var(--text-accent);

    --link: var(--text-accent);
    /* @todo: mix this against state/should use --ui or --ui-light? */
    --link--hover: color-mix(in oklch, var(--link) 90%, var(--text));
    --link--focus: color-mix(in oklch, var(--link) 90%, var(--text));
    --link--active: color-mix(in oklch, var(--link) 90%, var(--text));

    /* Element-default aliases — used below by the first-party reset
       (form controls, hr, kbd) in place of simpledotcss's own variables
       of the same name. */
    --border: var(--ui-light);
    --border-width: var(--thin-border-width);
    --accent-hover: color-mix(in oklch, var(--ui) 40%, transparent);

    --accent: var(--ui);

    /* prism.js only variables; not used in custom CSS */
    --prism-default-background: light-dark(#f6f2e8, #272b38);
}

/* ---- Element defaults ----
   A first-party reset in place of simpledotcss — this blog's markdown
   output leans on bare element selectors (h1-h6, blockquote, and table
   already had their own rules below; this covers the rest: forms, code,
   media, and the small text-level elements a classless base would
   otherwise supply). Written in the same idiom as allonsy-studio/website's
   core.css/site.css — logical properties, a WCAG 1.4.1 underline-in-prose
   rule for links, a shared focus-visible pattern — retargeted from that
   site's --theme--* namespace onto this one's own tokens above. (No
   scroll-behavior rule here: the html rule further down deliberately sets
   scroll-behavior: auto, so anchor jumps land instantly.) */
p {
    margin-block: 0 var(--content-space);

    &:first-child {
        margin-block-start: 0;
    }

    &:last-child {
        margin-block-end: 0;
    }
}

:is(img, video, embed, object, iframe) {
    max-inline-size: 100%;
    block-size: auto;
}

hr {
    border: none;
    border-block-start: var(--border-width) solid var(--border);
    inline-size: 100%;
    margin-block: var(--content-space);
}

figure {
    margin: 0;
    overflow-x: auto;

    > img {
        display: block;
        margin-inline: auto;
    }
}

figcaption {
    margin-block-start: var(--content-space);
    color: var(--text-light);
    font-size: var(--font-size-6);
    text-align: center;
}

abbr[title] {
    cursor: help;
    text-decoration: underline dotted;
}

mark {
    background-color: var(--marked);
    border-radius: var(--hairline-border-radius);
    color: var(--static-ink);
    padding: 0.1em 0.35em;
}

:is(sup, sub) {
    position: relative;
    vertical-align: baseline;
    font-size: 0.75em;
}

sup {
    inset-block-start: -0.5em;
}

sub {
    inset-block-start: 0.25em;
}

:is(code, pre, kbd, samp) {
    font-family: var(--mono-font);
    font-size: var(--mono-font-size);
}

code {
    color: var(--code);
}

:not(pre) > code {
    background-color: var(--bg-subtle);
    border-radius: var(--hairline-border-radius);
    padding: 0.15em 0.4em;
}

pre {
    color: var(--preformatted);
    overflow-x: auto;
    padding: 1em 1.25em;

    code {
        background: none;
        color: inherit;
        padding: 0;
    }
}

kbd {
    border: var(--border-width) solid var(--border);
    border-block-end-width: calc(var(--border-width) * 2);
    border-radius: var(--hairline-border-radius);
    color: var(--text);
    padding: 0.1em 0.4em;
}

:where(label) {
    display: block;
    color: var(--text);
    font-weight: var(--font-weight--medium);
    padding-block-start: var(--content-space);
}

:where(input:not([type="checkbox"], [type="radio"], [type="submit"], [type="reset"], [type="button"], [type="image"], [type="file"], [type="range"], [type="color"], [type="hidden"]), textarea, select) {
    box-sizing: border-box;
    inline-size: 100%;
    min-inline-size: 3em;
    block-size: 2.5em;
    margin: 0;
    padding: var(--content-space);
    appearance: none;
    vertical-align: top;

    background-color: color-mix(in sRGB, var(--bg) 80%, transparent);
    border: var(--border-width) solid var(--border);
    border-radius: var(--standard-border-radius);
    color: var(--text);
    font: inherit;
    text-overflow: ellipsis;

    &:is(textarea) {
        display: block;
        min-block-size: 7em;
        line-height: var(--font-height);
        resize: vertical;
    }

    &:hover {
        border-color: var(--ui--active);
    }

    &::placeholder {
        color: color-mix(in oklch, var(--text-light), transparent 45%);
        opacity: 1;
    }

    &:disabled {
        background-color: var(--disabled);
        color: var(--text-light);
        resize: none;
    }
}

:is(button, [type="submit"], [type="reset"], [type="button"]) {
    display: inline-block;
    border: var(--border-width) solid var(--ui);
    border-radius: var(--standard-border-radius);
    background-color: var(--ui);
    color: var(--on-ui);
    font: inherit;
    line-height: normal;
    padding: 0.5em 1.1em;
    cursor: pointer;

    &:hover {
        background-color: var(--accent-hover);
        border-color: var(--accent-hover);
    }

    &:disabled {
        background-color: var(--disabled);
        border-color: var(--disabled);
        color: var(--text-light);
        cursor: not-allowed;
    }
}

summary {
    cursor: pointer;
}

section {
    border-block: none;
    padding: 0;
    margin-block: 0;
}

article:where(.content-column) {
    border: none;
    padding-inline: 0;
    margin-block-end: 0;
}

article:last-child {
    margin-block-end: 0;
    padding-block-end: 0;
}

h1 {
    font-size: var(--font-size-1);
}

h2 {
    font-size: var(--font-size-2);
}

h3 {
    font-size: var(--font-size-3);
}

h4 {
    font-size: var(--font-size-4);
}

h5 {
    font-size: var(--font-size-5);
}

h6 {
    font-size: var(--font-size-6);
}

:is(p, li) {
    font-size: var(--font-size-5);
}

html {
    backface-visibility: hidden;

    /* Anchor jumps (TOC, header anchors) land instantly rather than
       smooth-scrolling — the element-defaults html rule above no longer
       sets scroll-behavior: smooth for this reason. The padding keeps
       anchor targets off the very top edge. */
    scroll-behavior: auto;
    scroll-padding-block-start: var(--container-space);
}

body {
    --page-column: min(var(--content-max), 90%);

    position: relative;
    display: grid;
    grid-template-columns: 1fr var(--page-column) 1fr;
    min-block-size: 100dvh;
    /* main, then the footer pinned to the bottom (no header row: there is
       no masthead — the home hero is the site's only chrome). */
    grid-template-rows: 1fr auto;
    font-family: var(--sans-font);
    font-size: var(--font-size-5);
    line-height: var(--font-height);

    font-optical-sizing: auto;
    overflow-x: hidden;
    text-wrap: stable;
    -webkit-font-smoothing: subpixel-antialiased;
    -moz-osx-font-smoothing: auto;
	scrollbar-gutter: stable;
	overscroll-behavior: contain;

    @media print {
        font-size: 16px;
        padding: 0;
    }
}

body:has(.content-column.wide) {
    --page-column: min(var(--container-max), 95%);
}

/* Every direct child sits in the centered column — there's no masthead
   needing a full-bleed grid-column exception anymore. A full-bleed
   background, like the hero's, breaks out on its own via
   inset-inline: calc(50% - 50vi) instead. */
body > * {
    grid-column: 2;
}

body > footer {
    margin-block-start: calc(var(--section-space) * 2);
    padding-block: var(--section-space) calc(var(--section-space) * 0.75);
    border-block-start: var(--border-width) solid var(--border);
    color: var(--text-light);
    font-size: var(--font-size-6);
}

[id] {
    scroll-margin-block-start: 1.5rlh;
}

::selection {
    background: color-mix(in hsl, var(--bg-light) 80%, transparent);
    border-radius: 0.25em;
    color: var(--text);
}

:focus-visible {
	outline: var(--outline-width) solid;
	outline-color: var(--ui--active);
	outline-offset: var(--outline-offset);
	border-color: var(--ui--active);
	border-radius: var(--hairline-border-radius);

	@supports (corner-shape: squircle) {
		corner-shape: squircle;
		border-radius: var(--squircle-border-radius);
	}
}

:where(ul).tags {
	list-style: none;
	padding-inline-start: 0;
	margin-block-start: 0;
	margin-block-end: 0;
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(100%, 12em), 1fr));
	gap: var(--gutter);

	li {
		display: inline-block;
		margin-inline-end: 0.5em;
		padding: 0.25em 0.5em;
		font-size: var(--font-size-6);
		font-weight: var(--font-weight--medium);
	}
}

a {
    text-decoration: none;
    text-underline-offset: .2ex;
    font-weight: var(--font-weight);
    color: var(--link);

    :where(p, li, dd, blockquote, figcaption) &[href]:not(.cta-link, .header-anchor, .listing-item-more) {
        text-decoration: underline;
        text-decoration-color: color-mix(in oklch, currentColor 45%, transparent);
        text-decoration-thickness: 1px;
    }

    &:has(img, svg) {
        display: flex;
        aspect-ratio: var(--aspect-ratio, 1 / 1);
        inline-size: var(--image-size, 250px);
        line-height: 1;
    }

    &:hover {
        text-decoration: underline;
        text-decoration-color: var(--link--hover);
        color: var(--link--hover);
    }

    &:is(:focus, :focus-visible) {
        text-decoration: underline;
        text-decoration-color: var(--link--focus);
        color: var(--link--focus);
        outline: var(--outline-width) solid var(--ui--active);
        outline-offset: var(--outline-offset);
        border-radius: var(--standard-border-radius);

		@supports (corner-shape: squircle) {
			corner-shape: squircle;
			border-radius: var(--squircle-border-radius);
		}
    }

	&[name]:not([href]) {
		color: inherit;
	}

    &[aria-current="page"] {
        color: inherit;
        font-weight: var(--font-weight--semi-bold);
        text-decoration: underline;
        text-decoration-color: var(--link--active);
    }

    img {
        cursor: pointer;
    }

	@container (inline-size > 0) {
		text-decoration: dotted underline;
	}
}

:where(p, li, blockquote, .header-summary) a:not(.cta-button, .cta-link, .navigation-link, :has(img, svg)) {
    text-decoration: underline;
    text-decoration-color: color-mix(in sRGB, currentColor 45%, transparent);
    text-underline-offset: .2ex;
}

:where(iframe) {
	inline-size: min(var(--video--Width, 100%), 100%);
	block-size: auto;

	&:where([src*="youtube.com"]) {
		aspect-ratio: 16 / 9;
	}
}

:where(svg) {
	inline-size: 100%;
	block-size: auto;
	min-block-size: 2.5em;
	fill: currentColor;
	align-self: center;
}

:where(label) {
    text-wrap: stable;
    padding-block-start: var(--content-space);
    padding-inline: 0;
    font-weight: var(--font-weight--thin);
    line-height: var(--font-height--spacious);
    color: var(--text);

    :is(& + [disabled], [disabled] + &) {
        color: var(--text-light);
    }

    &:has(:where(input, textarea, select)) {
        position: relative;
        display: grid;

        &::after {
            position: absolute;
            content: "";
            pointer-events: none;
            inset-inline-end: 0.75em;
            inset-block-start: calc(1em * var(--font-height--spacious) + 0.2em);
            font-weight: var(--font-weight--bold);
        }

        &:has(:is(input, textarea, select):user-valid:not(:focus-visible, :disabled, :read-only))::after {
            content: "✓";
            content: "✓" / "";
            color: var(--ui-success);
        }

        &:has(:is(input, textarea, select):user-invalid:not(:focus-visible, :disabled, :read-only))::after {
            content: "!";
            content: "!" / "";
            color: var(--ui-error);
        }

        &:has(+ button) {
            margin-block-end: calc((var(--content-space) / 2) + 1cqi);
        }
    }
}

:where(input, textarea, select, button) {
    font: inherit;
    color: inherit;
}

:where(ul, ol) {
	margin: 0;

	&:not(:last-child) {
		margin-block-end: 1.5em;
	}
}

:where(li) {
	margin: 0;

	&:not([class]) {
		font-size: inherit;
		line-height: var(--font-height);
	}

	:is(b, strong) {
		font-weight: var(--font-weight--semi-bold);
	}

	&:has(b, strong):not(:last-child) {
		margin-block-end: 0.2em;
	}
}

:where(dl) {
	margin: 0;
}

:where(dt) {
	font-family: var(--sans-font);
	font-weight: var(--font-weight--bold);
	font-size: 1.2em;

	+ dd {
		margin-block-end: 1em;
	}
}

:where(dd) {
	margin-inline-start: 0;
}

:where(nav) {
	a {
        color: var(--text-light);
        white-space: nowrap;

        &:hover {
            color: var(--text);
        }

        span {
            text-decoration: dotted underline;
            text-decoration-color: var(--ui);
        }

        b {
            color: var(--text-accent);
            font-weight: var(--font-weight--bold);
            margin-inline-end: calc(var(--content-space) * 3);
        }
	}
}

:where(section:last-child) {
	margin-block-end: 80px;
}

:is([hidden]) {
    display: none;
}

.sr-only {
    position: absolute;
	inset-block-start: 1em;
	inset-inline-start: 1em;

    &:focus {
        z-index: 100;
        background: var(--bg-overlay);
        color: var(--text);
        padding: 0.5em 1em;
        border-radius: var(--standard-border-radius);
        box-shadow: var(--box-shadow);
    }

    &:not(:focus) {
        clip-path: inset(50%);
        overflow: hidden;
        white-space: nowrap;
        inline-size: 1px;
        block-size: 1px;
        border: 0;
        padding: 0;
        margin: -1px;
    }
}

.print-only {
	display: none;
}

@media print {
	.screen-only {
		display: none;
	}

	.print-only {
		display: revert;
	}
}

.field-group {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(100%, 12em), 1fr));
	gap: calc((var(--content-space) / 2) + 1cqi);

	&:has(> *:only-child) {
		grid-template-columns: 1fr;
	}
}

.shadow {
	box-shadow: var(--box-shadow), var(--box-shadow-light);
}

/* Paragraph margins come from the element-defaults `p` rule above; this
   is the typography layer (size, weight, and the .extra/.muted/.underline
   modifiers) shared with `cite`. */
:where(p, cite) {
	font-size: clamp(var(--font-size-5), 1rem + 0.3cqi, 1.125rem);
	font-weight: var(--font-weight);
	line-height: var(--font-height);

	&:is(.extra) {
		font-size: 1.2em;

		:is(b, strong) {
			font-weight: var(--font-weight--medium);
		}
	}

	&:is(.muted),
	:is(.muted) {
		color: var(--text-light);
	}

	&:is(.align-end) {
		text-align: end;
	}

	:is(b, strong) {
		font-weight: var(--font-weight--semi-bold);
	}

	&:is(.underline),
	:is(.underline) {
		font-style: normal;
		text-decoration: underline;
		text-decoration-color: var(--ui);
		text-decoration-thickness: 0.2ex;
	}

	&:is(.balance),
	:is(.balance) {
		text-wrap: balance;
	}
}

:where(h1, h2, h3, h4, h5, h6, .lede) {
	font-family: var(--sans-font);
	color: var(--text);
	line-height: var(--font-height--dense);
	margin-block: 0 0.4em;
	text-wrap: balance;

	&:where(:not(:has(+ *))) {
		margin-block-end: 0;
	}

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

@media screen {
	main :where(h2:not([class])) {
		margin-block-start: calc(var(--content-space) * 2.5);
	}

	main :where(h3:not([class])) {
		margin-block-start: calc(var(--content-space) * 1.75);
	}

	main :where(h4:not([class]), h5:not([class]), h6:not([class])) {
		margin-block-start: calc(var(--content-space) * 1.25);
	}
}

:is(h1, h2, h3, h4, h5, h6):has(> .header-anchor) {
	position: relative;
}

.header-anchor {
	position: absolute;
	inset-inline-start: -0.85em;
	opacity: 0;
	color: var(--text-light);
	text-decoration: none;
	transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);

	&:is(:hover, :focus-visible) {
		color: var(--link);
	}
}

:is(h1, h2, h3, h4, h5, h6):is(:hover, :focus-within) > .header-anchor,
.header-anchor:focus-visible {
	opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
	.header-anchor {
		transition: none;
	}
}

:where(h1:not([class])) {
	font-weight: var(--font-weight--semi-bold);
}

:where(h2:not([class])) {
	font-weight: var(--font-weight--semi-bold);
}

@media screen {
	main :where(h2:not([class])) {
		&::before {
			content: "";
			display: block;
			inline-size: 2.5rem;
			block-size: 3px;
			margin-block-end: 0.25em;

			background-color: var(--section-ui);
		}
	}
}

:where(h3:not([class])) {
	font-weight: var(--font-weight--medium);
}

:where(h4:not([class]), h5:not([class]), h6:not([class])) {
	font-weight: var(--font-weight--medium);
}

/* Don't allow line-breaking for the brand text */
.no-wrap {
	word-break: keep-all;
	white-space: nowrap;

	@media screen and (width <= 480px) {
		white-space: normal;
	}
}

:where(.lede) {
	text-wrap: balance;
	font-family: var(--sans-font);
	font-size: max(calc(var(--lede--FontSize, 1em) + .2cqi + .2cqb), var(--font-size-5));
	font-weight: var(--font-weight--light);
	line-height: var(--font-height);

	@media screen and (width <= 612px) {
		--lede--FontSize: .9em;
	}

	@media screen and (width <= 480px) {
		--lede--FontSize: .8em;
	}

	@container box (width <= 500px) {
		font-weight: var(--font-weight);
	}
}

eyebrow + :where(h1, h2, h3, h4, h5, h6) {
	margin-block-start: calc(var(--gutter) * 2);
}

/* ---- Reading column ---- */
.content-column {
	box-sizing: border-box;
	inline-size: 100%;
	padding-block: calc(var(--section-space) / 2) var(--section-space);

	> section + section {
		margin-block-start: var(--section-space);
	}

	> section > header {
		margin-block: calc(var(--content-space) * 1.5) calc(var(--content-space) * 1);
	}

	> header {
		margin-block-end: calc(var(--content-space) * 2);
	}
}

/* ---- Muted text ----
   Global counterpart to typography's paragraph-scoped `.muted`. */
.muted {
	color: var(--text-light);
}

/* ---- Wordmark ----
   The "[ Cassondra ]" hero title. Gradient text via background-clip,
   using the same --gradient token the featured-badge chip pulls its two
   stops from. `color: var(--text)` is the fallback for browsers without
   background-clip: text support — real, if rare — so the wordmark just
   reads as solid ink instead of vanishing. */
.wordmark {
	background-image: var(--gradient);
	background-clip: text;
	-webkit-background-clip: text;
	color: var(--text);

	@supports (background-clip: text) or (-webkit-background-clip: text) {
		color: transparent;
		-webkit-text-fill-color: transparent;
	}

	&:is(:hover, :focus-visible) {
		filter: brightness(1.12) saturate(1.1);
	}

	@media print {
		background-image: none;
		color: var(--text);
		-webkit-text-fill-color: currentColor;
	}
}

/* ---- Reading progress ----
   A fixed 3px bar above everything, so it
   stays visible the whole scroll. pages/scripts/reading-progress.js ties
   the fill's inline-size to how far through the <article> (not the whole
   document) the reader has gotten. */
.reading-progress-track {
	position: fixed;
	inset-block-start: 0;
	inset-inline: 0;
	z-index: 20;

	block-size: 3px;
	background: transparent;

	@media print {
		display: none;
	}
}

.reading-progress-fill {
	block-size: 100%;
	inline-size: 0%;
	background-image: var(--gradient);
	transition: inline-size 0.15s linear;

	@media (prefers-reduced-motion: reduce) {
		transition: none;
	}
}

/* ---- Blog index ---- */
.blog-index {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: calc(var(--content-space) * 1.5);

	li {
		border-block-start: var(--border-width) solid var(--border);
		padding-block-start: calc(var(--content-space) * 1.5);
	}

	/* `a[href]` so this outranks the generic prose-link underline rule
	   (`:where(li) a[href]:not(...)`), which otherwise underlines the whole
	   card; the title alone underlines on hover/focus instead. */
	a[href] {
		display: block;
		color: inherit;
		text-decoration: none;

		&:is(:hover, :focus-visible) h3 {
			text-decoration: underline;
			text-decoration-color: var(--ui);
			text-decoration-thickness: 0.12em;
			text-underline-offset: 0.15em;
		}
	}

	/* The eyebrow component renders as a bare <p> in some positions and as
	   <eyebrow><p> in others, so match both. */
	:is(eyebrow, .featured-badge) {
		display: inline-block;
		margin-inline-end: 0.75em;
	}

	h3 {
		margin-block: 0.4em;
	}

	p:not(.featured-badge) {
		margin-block-end: 0;
		color: var(--text-light);
	}
}

/* A subtle gradient chip, not a flat eyebrow: the one thing on a card
   actually worth calling out gets a small marker-highlight moment, while
   keeping the same uppercase/letter-spaced type treatment every other
   eyebrow uses. The gradient sits at 160% width so a background-position
   shift on the containing link's hover/focus reads as a slide, not a
   swap. `p.featured-badge` (not `.featured-badge` alone): eyebrow's own
   scoped color rule is a single class too and its bundle loads after
   base.css, so a same-specificity override would lose the cascade tie —
   the extra type selector wins regardless of load order. */
p.featured-badge {
	inline-size: max-content;
	padding: 0.15em 0.6em;
	border-radius: 999px;
	color: #fff;

	background-image: linear-gradient(120deg, var(--static-magenta), var(--static-orange));
	background-size: 160% 100%;
	background-position: 0% 0%;
	transition: background-position 0.35s ease-out;

	@media (prefers-reduced-motion: reduce) {
		transition: none;
	}
}

.blog-index a:is(:hover, :focus-visible) .featured-badge {
	background-position: 100% 0%;
}

/* ---- Tags ---- */
.post-tags {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5em;
	margin-block-start: 1em;
}

.tag-browse {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.5em;
	margin-block-start: calc(var(--content-space) * 2);
	padding-block-start: var(--content-space);
	border-block-start: var(--border-width) solid var(--border);
}

.tag-browse-label {
	font-size: max(var(--base-font-size), 0.85em);
	color: var(--text-light);
}

.tag-pill {
	display: inline-block;
	padding: 0.2em 0.7em;
	border: var(--border-width) solid var(--border);
	border-radius: 999px;
	color: var(--text);
	font-size: max(var(--base-font-size), 0.8em);
	text-decoration: none;

	&:is(:hover, :focus-visible) {
		border-color: var(--ui);
		color: var(--ui);
	}
}

/* Color as identity, not decoration: the same tag is always the same
   color everywhere it shows up, cycling through the four brights by
   position. .post-tags and .tag-browse each hold only tag-pill anchors,
   so :nth-of-type counts cleanly without a hash-based color function. */
.tag-pill:nth-of-type(4n + 1) {
	color: var(--text-magenta);
	border-color: color-mix(in sRGB, var(--static-magenta) 55%, transparent);

	&:is(:hover, :focus-visible) { border-color: var(--static-magenta); }
}

.tag-pill:nth-of-type(4n + 2) {
	color: var(--text-cyan);
	border-color: color-mix(in sRGB, var(--static-cyan) 55%, transparent);

	&:is(:hover, :focus-visible) { border-color: var(--static-cyan); }
}

.tag-pill:nth-of-type(4n + 3) {
	color: var(--text-orange);
	border-color: color-mix(in sRGB, var(--static-orange) 55%, transparent);

	&:is(:hover, :focus-visible) { border-color: var(--static-orange); }
}

.tag-pill:nth-of-type(4n + 4) {
	color: var(--text-yellow);
	border-color: color-mix(in sRGB, var(--static-yellow) 55%, transparent);

	&:is(:hover, :focus-visible) { border-color: var(--static-yellow); }
}

/* ---- Post table of contents ----
   Sticky within the article, not a two-column layout: placed inline at the
   top of .content-column > article, `position: sticky` pins it just under
   the viewport's top edge for as long as the (full-length) <article> is
   still in view, i.e. the whole read — without a wider grid to hold a real
   sidebar — but only above 612px. A phone is too short to give a pinned
   panel that much of the screen, so there it scrolls away with the rest of
   the article and the fixed reading-progress bar is the only chrome left.
   A <details> so a narrow viewport can collapse a long list back to its
   one-line summary; above 612px the summary is hidden and
   `::details-content` forces the list open, so wide screens keep the
   always-open list they had. Browsers without `::details-content` keep the
   toggle at every width, closed until asked for. */
.post-toc {
	position: sticky;
	inset-block-start: var(--content-space);
	z-index: 1;

	margin-block: calc(var(--content-space) * 1.5);
	padding: 1em 1.25em;

	background-color: var(--bg-light);
	border-radius: var(--standard-border-radius);
	max-block-size: 40vh;
	overflow-y: auto;

	/* Undo the element-default indent for a <details> body. */
	> :not(summary) {
		padding-inline-start: 0;
	}

	/* Phone: in flow, and no inner scroll area to fight a touch scroll. */
	@media screen and (width <= 612px) {
		position: static;
		max-block-size: none;
		overflow: visible;
	}

	@media print {
		display: none;
	}
}

.post-toc-label {
	margin-block: 0;
	font-size: max(var(--base-font-size), 0.8em);
	font-weight: var(--font-weight--semi-bold);
	color: var(--text-light);
}

.post-toc[open] > .post-toc-label {
	margin-block-end: 0.4em;
}

@supports selector(::details-content) {
	@media screen and (width > 612px) {
		.post-toc > summary {
			display: none;
		}

		.post-toc::details-content {
			content-visibility: visible;
		}
	}
}

.post-toc nav {
	ol {
		list-style: none;
		margin: 0;
		padding: 0;
		display: grid;
		gap: 0.3em;
	}

	/* `a[href]`, not `a`: the prose underline rule is (0,2,1). */
	a[href] {
		color: inherit;
		text-decoration: none;

		&:is(:hover, :focus-visible) {
			color: var(--link--hover);
		}
	}
}

/* ---- Checklist ---- */
.checklist {
	list-style: none;
	margin: 0;
	padding-inline-start: 0;

	li {
		padding-inline-start: 1.6em;
		text-indent: -1.6em;

		&::before {
			content: "☐" / "";
			display: inline-block;
			inline-size: 1.6em;
			text-indent: 0;
			color: var(--ui);
		}
	}

	li + li {
		margin-block-start: calc(var(--content-space) / 3);
	}
}

/* ---- Meta grid ---- */
.meta-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 14em), 1fr));
	gap: var(--content-space);

	dt {
		font-size: var(--font-size-6);
		text-transform: uppercase;
		letter-spacing: 0.05em;
	}

	dd {
		margin: 0;
	}

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

dialog::backdrop {
	background-color: rgb(0 0 0 / 0.5);
	backdrop-filter: blur(2px);
}

/* ---- Print ---- */
@media print {
	.content-column {
		max-inline-size: 100%;
		padding: 0;
	}

	details::details-content {
		content-visibility: visible;
	}
}

details {
	> *:not(summary) {
		padding-inline-start: 1em;
	}
}

tbody tr:nth-child(even) {
	background-color: var(--bg-subtle);
}

details {
	background-color: var(--bg-subtle);
}

/* ---- Lightweight table styles ---- */
.table-light {
	border-collapse: collapse;

	th,
	td {
		border-inline: none;
	}
}

table {
	:is(th, td).center {
		text-align: center;
	}
}

/* ---- Blockquote ---- */
blockquote {
	margin-block: calc(var(--content-space) * 2);
	margin-inline: 0;
	padding: 0;
	border: none;

	color: var(--text);
	font-size: 1.15em;
	font-style: normal;
	line-height: 1.45;
	text-wrap: pretty;

	&::before {
		content: "";
		display: block;
		inline-size: 2.5rem;
		block-size: 3px;
		margin-block-end: 0.9rem;
		background-color: var(--ui);
	}

	> :first-child {
		margin-block-start: 0;
	}

	> :last-child {
		margin-block-end: 0;
	}

	@media print {
		&::before {
			background-color: var(--static-ink);
		}
	}
}

blockquote:is(.quote, :has(cite)) {
	position: relative;
	padding-inline-start: 2.25rem;

	color: var(--text-light);
	font-size: 1em;
	line-height: var(--font-height--spacious);

	&::before {
		content: "\201C";
		content: "\201C" / "";
		position: absolute;
		inset-inline-start: 0;
		inset-block-start: -0.3em;
		display: block;
		inline-size: auto;
		block-size: auto;
		margin: 0;

		background: none;
		color: var(--ui);
		font-size: 2.75rem;
		line-height: 1;
	}

	cite {
		display: block;
		margin-block-start: 0.75em;

		color: var(--text-light);
		font-size: max(var(--base-font-size), 0.9em);
		font-style: normal;

		&::before {
			content: "— ";
		}
	}
}

/* ---- Callout ----
   The M3-flagged shape (see below) *is* the default now, not an opt-in:
   an asymmetric radius, not a uniform one. Both inline-start corners
   (where the accent bar sits) stay sharp at 2px — border-radius still
   applies under the bar, just barely, rather than a hard 0 — so the bar
   reads as a crisp edge instead of looking clipped by a heavy rounding;
   both inline-end corners open up to 24px circular (20px squircle where
   `corner-shape` is supported — see the @supports block below). One
   radius for the default and both variants below — neither overrides it,
   so this is the only place it's set. */
.callout {
	--callout-color: var(--ui);

	border-inline-start: var(--thick-border-width) solid var(--callout-color);
	background-color: color-mix(in sRGB, var(--callout-color) 10%, var(--bg));
	border-start-start-radius: 2px;
	border-end-start-radius: 2px;
	border-start-end-radius: 24px;
	border-end-end-radius: 24px;
	padding: 1em 1.25em;

	&:is(.success, [type="success"]) {
		--callout-color: var(--ui-success);
	}

	&:is(.warn, [type="warn"]) {
		--callout-color: var(--ui-warn);
	}

	&:is(.error, [type="error"]) {
		--callout-color: var(--ui-error);
	}

	> :first-child {
		margin-block-start: 0;
	}

	> :last-child {
		margin-block-end: 0;
	}

	@media print {
		background-color: transparent;
	}
}

/* ---- Callout: Material Design 3 variants ----
   Two optional treatments, opt in with a second class alongside .callout
   — the default above already carries the M3-flagged shape and its 24px
   open-end radius, so neither variant touches border-radius at all; they
   only override the surface treatment. Both inherit the default's sharp
   2px inline-start, --thick-border-width bar, and 24px open end
   unchanged — never thin the bar or diverge the radius per variant, or
   the family stops reading as one family. */
.callout.elevated {
	background-color: var(--bg-overlay);
	box-shadow: var(--box-shadow);

	@media print {
		box-shadow: none;
	}
}

.callout.expressive {
	--callout-color-2: var(--static-cyan);

	background-image: linear-gradient(
		135deg,
		color-mix(in sRGB, var(--callout-color) 16%, var(--bg)),
		color-mix(in sRGB, var(--callout-color-2) 16%, var(--bg))
	);
}

/* Preferred open-end treatment: a squircle superellipse curve reads softer
   than a circular arc at the same radius, so the default (and, by
   inheritance, both variants) converge on one 20px squircle instead of
   the 24px circular radius above. Progressive enhancement only —
   corner-shape isn't Baseline yet, so unsupporting browsers silently keep
   the 24px circular radius from above, which is the real (not a lesser)
   fallback. A bare `.callout` selector is sufficient here — unlike before
   this radius was unified, neither variant carries its own border-radius
   override outside this block anymore to out-specificity. */
@supports (corner-shape: squircle) {
	.callout {
		border-start-end-radius: 20px;
		border-end-end-radius: 20px;
		corner-shape: squircle;
	}
}

body:has(.error-page) {
	background-image: url("/images/topography.svg");
	background-position: center;
	background-repeat: repeat;
}

/* ---- Disclosure list ---- */
.disclosures {
	--disclosure-gutter: 1.6em;

	border-block-start: var(--thin-border-width) solid var(--border);

	details {
		margin-block: 0;
		padding: 0;

		background: none;
		border: none;
		border-block-end: var(--thin-border-width) solid var(--border);
		border-radius: 0;

		> :not(summary) {
			margin-block: 0 1.15em;
			padding-inline-start: var(--disclosure-gutter);
		}

		/* The answer belongs to the question above it, so it sits close. */
		> :not(summary):first-of-type {
			margin-block-start: 0;
		}
	}

	summary {
		display: flex;
		align-items: baseline;
		margin: 0;
		padding-block: 1.05em;
		padding-inline: 0;

		background: none;
		border: none;
		color: var(--text);
		font-weight: var(--font-weight--semi-bold);
		list-style: none;
		text-wrap: pretty;

		&::-webkit-details-marker {
			display: none;
		}

		&::before {
			content: "+";
			content: "+" / "";
			flex: none;
			inline-size: var(--disclosure-gutter);

			color: var(--ui);
			font-weight: var(--font-weight);
		}

		&:hover {
			color: var(--link--hover);
			cursor: pointer;
		}
	}

	/* Open: the question tightens onto its answer, and the marker flips. */
	details[open] > summary {
		padding-block-end: 0.4em;

		&::before {
			content: "\2212";
			content: "\2212" / "";
		}
	}
}


/* ---- Home hero ----
   The one place the wordmark appears, at display size, over a soft
   full-bleed wash of the four brights (three offset radial gradients, no
   hard edges). The wash is the page's opening visual instead of a rule
   under the hero; it runs edge to edge via the 50vi inset trick (body is
   overflow-x: hidden, so the scrollbar gutter doesn't leak a scrollbar).
   `isolation: isolate` keeps the z-index: -1 pseudo behind the hero's own
   content but inside the hero's stacking context, not behind <body>. */
.hero {
	position: relative;
	isolation: isolate;
	padding-block: calc(var(--section-space) * 1.5) calc(var(--section-space) * 1.25);

	&::before {
		content: "";
		position: absolute;
		z-index: -1;
		inset-block: calc(var(--section-space) / -2) 0;
		inset-inline: calc(50% - 50vi);
		pointer-events: none;

		background-image:
			radial-gradient(60% 75% at 92% 10%, color-mix(in oklch, var(--static-cyan) 34%, transparent), transparent 72%),
			radial-gradient(50% 65% at 2% 85%, color-mix(in oklch, var(--static-magenta) 20%, transparent), transparent 70%),
			radial-gradient(70% 60% at 65% 100%, color-mix(in oklch, var(--static-yellow) 24%, transparent), transparent 72%),
			radial-gradient(35% 50% at 28% 0%, color-mix(in oklch, var(--static-orange) 18%, transparent), transparent 70%);

		/* Feather the wash out toward the hero's bottom edge instead of
		   ending on a hard line — the whole point is fewer rules. */
		mask-image: linear-gradient(180deg, #000 45%, transparent 100%);

		@media print {
			display: none;
		}
	}

	/* <eyebrow> renders as a bare <p>, so target it by position. */
	> :first-child {
		margin-block-end: 0.5em;
	}

	> .lede {
		max-inline-size: 42ch;
		margin-block-end: calc(var(--content-space) * 1.25);
	}
}

.hero-title {
	font-size: clamp(2.75rem, 1.5rem + 7vi, 6rem);
	font-weight: var(--font-weight--bold);
	letter-spacing: -0.03em;
	line-height: 1;
	margin-block: 0.1em 0.3em;
	text-wrap: balance;

	/* The gradient needs an inline box to clip against; the h2 itself is
	   the balance/measure container. */
	.wordmark {
		display: inline-block;
	}
}

/* "The Musical" reads as struck through — a single static line by
   default; the motion block below animates it drawing left to right. */
.strikethrough {
	position: relative;
	display: inline-block;

	&::after {
		content: "";
		position: absolute;
		inset-inline: 0;
		inset-block-start: 50%;
		block-size: 0.06em;
		background: currentColor;
	}
}

/* "Blog" appears hand-written just after the strikethrough — an inline
   hand-drawn brush path (see _data/blogWordmark.js), not a downloaded
   webfont, so it needs no network request and renders identically
   everywhere. Sized off the hero-title's own font-size so it scales
   with the rest of the line; block-size (not inline-size) is set so the
   artwork's own aspect ratio decides its width.

   Overlapping paths share this box: each .wordmark-trace is one stroke
   of the artwork's extracted centerline (for a real pen-draw animation —
   see the motion block below) and .wordmark-fill is the original filled
   brush shape. Without motion they'd all just sit on top of each other
   rendering identically (same color, .wordmark-fill's shape fully
   covering the thinner centerline strokes down its middle), so there's
   no need to hide any of them by default. */
.handwriting {
	display: inline-block;
	inline-size: auto;
	block-size: 1.1em;
	min-block-size: 0;
	margin-inline-start: 0.15em;
	vertical-align: -0.32em;
	overflow: visible;

	path {
		fill: var(--text);
		stroke: none;
	}

	/* "Musical" alone already claims most of a narrow line, leaving "Blog"
	   squeezed beside it rather than read as its own word — drop it to a
	   line of its own on narrow viewports (.musical-group below handles
	   keeping it aligned under "Musical" rather than under wherever the
	   line itself starts, since "the Musical" now share a line). */
	@media screen and (width <= 612px) {
		margin-inline-start: 0;
		margin-block-start: 0.1em;
		block-size: 1.4em;
		/* .musical-group's align-items: flex-start (below) would otherwise
		   lose to the global :where(svg) reset's own align-self: center —
		   dormant everywhere else since nothing else puts an svg inside a
		   flex container, but this is now one. */
		align-self: flex-start;
	}
}

/* Wraps "Musical" and its handwritten "Blog" so the two can be stacked
   as a unit on narrow viewports without "Blog" falling back to the
   line's own start edge: plain inline by default (identical to today's
   layout), switching to a column flex only below 612px so "Blog" sits
   directly under wherever "Musical" itself starts — which is mid-line,
   after "the " — rather than under the line's start. */
.musical-group {
	@media screen and (width <= 612px) {
		display: inline-flex;
		flex-direction: column;
		align-items: flex-start;
	}
}

/* Affiliations as chips: the credential is the point of the hero, so it
   gets the same color-as-identity cycle the tag pills use, one bright per
   position. .credentials holds only <li>, so :nth-child counts cleanly. */
.credentials {
	list-style: none;
	margin: 0 0 calc(var(--content-space) * 1.5);
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: 0.5em;

	li {
		display: inline-flex;
		align-items: center;
		gap: 0.5em;
		padding: 0.25em 0.8em;
		border: var(--border-width) solid var(--border);
		border-radius: var(--pill-border-radius);
		font-size: max(var(--base-font-size), 0.85em);
		font-weight: var(--font-weight--medium);
		line-height: var(--font-height);

		&::before {
			content: "";
			inline-size: 0.5em;
			block-size: 0.5em;
			border-radius: var(--circle-border-radius);
			background-color: var(--credential-color, var(--static-magenta));
		}

		&:nth-child(4n + 1) { --credential-color: var(--static-magenta); }
		&:nth-child(4n + 2) { --credential-color: var(--static-cyan); }
		&:nth-child(4n + 3) { --credential-color: var(--static-orange); }
		&:nth-child(4n + 4) { --credential-color: var(--static-yellow); }
	}
}

/* Secondary places (GitHub, Mastodon, npm, the studio) — a quiet row of
   icon buttons under the CTAs; each link's aria-label is the accessible
   name since the icon alone carries no text. */
.hero-links {
	list-style: none;
	margin: calc(var(--content-space) * 1.25) 0 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: 0.5em;

	img {
		display: block;
	}
}

/* ---- Home section headers ----
   Title and its "see everything" link on one baseline, no accent bar (the
   classed h2 opts out of the `h2:not([class])::before` rule) — the cards
   below carry the visual weight instead of a rule above them. */
.section-header {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: 0.5em 1.5em;

	.lede {
		flex-basis: 100%;
		margin-block: 0.25em 0;
		color: var(--text-light);
	}
}

.section-title {
	margin: 0;
	font-size: var(--font-size-2);
	font-weight: var(--font-weight--semi-bold);
}

.section-link {
	font-weight: var(--font-weight--medium);
	white-space: nowrap;
}

/* ---- Home post cards ----
   The home page's take on .blog-index: surfaces instead of rules. The
   lead (first, featured) post spans the row on a two-color wash; the rest
   sit two-up on the quiet --bg-light surface and lift on hover. */
.blog-index.post-cards {
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--content-space);

	@media screen and (width <= 612px) {
		grid-template-columns: 1fr;
	}

	li {
		border-block-start: none;
		padding-block-start: 0;
	}

	li:first-child {
		grid-column: 1 / -1;
	}

	a[href] {
		box-sizing: border-box;
		block-size: 100%;
		padding: 1.4em 1.5em 1.5em;
		border-radius: calc(var(--standard-border-radius) * 3);
		background-color: var(--bg-light);
		transition: translate 150ms ease-out, box-shadow 150ms ease-out;

		&:is(:hover, :focus-visible) {
			translate: 0 -2px;
			box-shadow: var(--box-shadow), var(--box-shadow-light);
		}

		@media (prefers-reduced-motion: reduce) {
			transition: none;

			&:is(:hover, :focus-visible) {
				translate: none;
			}
		}
	}

	li:first-child a[href] {
		padding: 1.75em 1.75em 1.85em;
		background-image: linear-gradient(120deg, color-mix(in oklch, var(--static-magenta) 14%, var(--bg-light)), color-mix(in oklch, var(--static-orange) 10%, var(--bg-light)) 45%, color-mix(in oklch, var(--static-cyan) 16%, var(--bg-light)));

		h3 {
			font-size: var(--font-size-2);
		}
	}

	li:not(:first-child) p:not(.featured-badge) {
		display: -webkit-box;
		-webkit-box-orient: vertical;
		-webkit-line-clamp: 3;
		line-clamp: 3;
		overflow: hidden;
	}
}

/* ---- Call-to-action buttons ----
   `.cta-button` was already carved out of the prose-link underline rules
   above; this is its actual definition. Primary is the brand magenta fill,
   `.secondary` the same shape as an outline so the two can sit side by
   side without competing. */
.cta-row {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.75em;
	margin-block-end: 0;
}

.cta-button {
	display: inline-flex;
	align-items: center;
	gap: 0.4em;
	padding: 0.6em 1.4em;
	border: var(--border-width) solid var(--ui);
	border-radius: var(--pill-border-radius);

	background-color: var(--ui);
	color: var(--on-ui);
	font-weight: var(--font-weight--semi-bold);
	line-height: var(--font-height);
	text-decoration: none;
	transition: background-color 150ms ease-out, color 150ms ease-out, border-color 150ms ease-out;

	&:is(:hover, :focus-visible) {
		background-color: var(--link--hover);
		border-color: var(--link--hover);
		color: var(--on-ui);
	}

	&.secondary {
		background-color: transparent;
		color: var(--text-accent);

		&:is(:hover, :focus-visible) {
			background-color: color-mix(in oklch, var(--ui) 12%, transparent);
			border-color: var(--ui);
			color: var(--text-accent);
		}
	}

	@media (prefers-reduced-motion: reduce) {
		transition: none;
	}
}

/* ---- Open source project cards ----
   A grid of small cards, each carrying one of the four brights along its
   top edge — the same positional color cycle as the tag pills and the
   hero credentials, so the palette reads as one system down the page.
   .project-grid holds only <li>, so :nth-child is safe here too. */
.project-grid {
	list-style: none;
	margin: 0 0 var(--content-space);
	padding: 0;
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--content-space);

	@media screen and (width <= 612px) {
		grid-template-columns: 1fr;
	}

	> li:nth-child(4n + 1) { --project-color: var(--static-magenta); }
	> li:nth-child(4n + 2) { --project-color: var(--static-cyan); }
	> li:nth-child(4n + 3) { --project-color: var(--static-orange); }
	> li:nth-child(4n + 4) { --project-color: var(--static-yellow); }
}

.project-card {
	box-sizing: border-box;
	block-size: 100%;
	margin: 0;
	padding: 1.25em 1.25em 1em;
	display: flex;
	flex-direction: column;
	gap: 0.5em;

	border: var(--thin-border-width) solid var(--border);
	border-block-start: var(--thick-border-width) solid var(--project-color, var(--ui));
	border-radius: var(--standard-border-radius);
	background-color: var(--bg-subtle);
	transition: border-color 150ms ease-out, box-shadow 150ms ease-out;

	&:has(a:is(:hover, :focus-visible)) {
		border-color: var(--project-color, var(--ui));
		box-shadow: var(--box-shadow-light);
	}

	h3 {
		margin-block: 0;
		font-size: var(--font-size-4);
		font-weight: var(--font-weight--semi-bold);

		/* `a[href]`: outranks the generic prose-link underline (see .blog-index). */
		a[href] {
			color: inherit;
			text-decoration: none;

			&:is(:hover, :focus-visible) {
				text-decoration: underline;
				text-decoration-color: var(--project-color, var(--ui));
				text-decoration-thickness: 0.15em;
			}
		}
	}

	p {
		margin-block: 0;
	}

	.project-package {
		font-size: max(var(--base-font-size), 0.8em);
		color: var(--text-light);

		code {
			padding: 0;
			background: none;
			color: inherit;
			font-size: inherit;
		}
	}

	> p:not(.project-package) {
		flex-grow: 1;
	}

	@media (prefers-reduced-motion: reduce) {
		transition: none;
	}
}

.project-links {
	list-style: none;
	margin: 0.75em 0 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.5em;

	/* The one text link (the post) sits at the far end of the icon row. */
	.project-links-text {
		margin-inline-start: auto;
		font-size: max(var(--base-font-size), 0.85em);

		a {
			font-weight: var(--font-weight--medium);
		}
	}
}

/* Round icon buttons for the repo and package links; the accessible name
   is the link's aria-label, the SVG is decorative. Tinted with the card's
   own bright on hover/focus so the icons belong to the card, not the
   page. */
.icon-link {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	inline-size: 2.25rem;
	block-size: 2.25rem;
	border-radius: var(--circle-border-radius);
	background-color: var(--bg-light);
	color: var(--text-light);
	transition: background-color 150ms ease-out, color 150ms ease-out;

	&:is(:hover, :focus-visible) {
		background-color: color-mix(in oklch, var(--project-color, var(--ui)) 18%, var(--bg-light));
		color: var(--text);
	}

	svg {
		inline-size: 1.25rem;
		block-size: 1.25rem;
	}

	@media (prefers-reduced-motion: reduce) {
		transition: none;
	}
}

/* ---- Allons-y Studio banner ----
   The consultancy pitch, kept deliberately smaller than the writing and
   the projects above it: a tinted band with the full brand gradient
   running down its leading edge (the third of the "gradient moments" —
   wordmark, reading progress, this). */
.studio-banner {
	position: relative;
	overflow: hidden;
	margin-block-start: var(--section-space);
	padding: calc(var(--content-space) * 1.75) calc(var(--content-space) * 1.75) calc(var(--content-space) * 1.75) calc(var(--content-space) * 2);

	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	align-items: center;
	gap: calc(var(--content-space) * 1.5);

	/* With the brand mark present it takes its own leading column. */
	&:has(> .studio-banner-mark) {
		grid-template-columns: auto minmax(0, 1fr) auto;
	}

	background-color: var(--bg-light);
	border: none;
	border-radius: var(--standard-border-radius);

	/* simple.css floats <aside> at 30% as a sidebar; this one is a band. */
	float: none;
	inline-size: auto;
	margin-inline: 0;
	margin-block-end: 0;
	font-size: inherit;

	&::before {
		content: "";
		position: absolute;
		inset-block: 0;
		inset-inline-start: 0;
		inline-size: var(--thick-border-width);
		background-image: linear-gradient(180deg, var(--gradient-stack));
	}

	> div > :first-child {
		margin-block-end: 0.4em;
	}

	p:last-child {
		margin-block-end: 0;
	}

	@media screen and (width <= 612px) {
		grid-template-columns: 1fr;

		&:has(> .studio-banner-mark) {
			grid-template-columns: 1fr;
		}
	}

	@media print {
		display: none;
	}
}

/* Brand rule: clear space of one cup-width around the mark, never below
   20px. Cream fill + the arc keep it legible on the dark scheme too. */
.studio-banner-mark {
	inline-size: 4.5rem;
	block-size: 4.5rem;
	align-self: start;
	flex-shrink: 0;

	@media screen and (width <= 612px) {
		inline-size: 3rem;
		block-size: 3rem;
	}
}

.studio-banner-title {
	font-size: var(--font-size-3);
	font-weight: var(--font-weight--semi-bold);
	margin-block: 0 0.4em;
}

/* ---- Post outro ----
   Two endings for a post, one shown at a time. The reader one is the HTML
   default — a coffee, a sponsor link, a one-line studio mention — so no-JS
   and stripped-referrer visits get the modest version. When
   pages/scripts/visitor-origin.js decides the visit started at Allons-y
   Studio, it sets data-visitor="studio" on <html> and the studio banner
   (the same .studio-banner as the home page) takes its place. */
.post-outro {
	margin-block: calc(var(--section-space) * 1.25) var(--section-space);
	padding-block-start: calc(var(--content-space) * 1.5);
	border-block-start: var(--border-width) solid var(--border);

	@media print {
		display: none;
	}
}

.post-outro-reader {
	display: flex;
	align-items: start;
	gap: var(--content-space);

	p {
		margin-block: 0 0.75em;
		color: var(--text-light);
	}

	p:last-child {
		margin-block-end: 0;
	}

	strong {
		color: var(--text);
	}
}

.post-outro-mark {
	inline-size: 3rem;
	block-size: 3rem;
	flex-shrink: 0;
}

.cta-row.compact .cta-button {
	padding: 0.4em 1.1em;
	font-size: max(var(--base-font-size), 0.9em);
}

.post-outro-studio {
	display: none;
	margin-block-start: 0;
}

:root[data-visitor="studio"] {
	.post-outro-reader {
		display: none;
	}

	.post-outro-studio {
		display: grid;
	}
}

/* ---- Home page motion ----
   Scroll-driven, CSS-only, and decorative: everything here is gated on
   prefers-reduced-motion: no-preference and on real support for
   animation-timeline + animation-range (the range check filters out
   partial implementations). Unsupporting browsers — Firefox, at the time
   of writing, which is also what `yarn start` opens — get the static page,
   which is exactly what shipped before. No JS fallback on purpose. Only
   compositor-friendly properties move (translate, scale, opacity), and
   the animation-timeline declarations come *after* the `animation`
   shorthand so the shorthand can't reset them. */
@media screen and (prefers-reduced-motion: no-preference) {
	/* One-time sheen across the wordmark on arrival: a second, narrow
	   highlight layer sweeps over the gradient inside the text and parks
	   off-text (fill-mode forwards) so it never repeats. Not scroll-driven. */
	@keyframes wordmark-sheen {
		to {
			background-position: -60% 0, 0 0;
		}
	}

	.hero-title .wordmark {
		background-image:
			linear-gradient(105deg, transparent 42%, rgb(255 255 255 / 65%) 50%, transparent 58%),
			var(--gradient);
		background-size: 260% 100%, 100% 100%;
		background-position: 160% 0, 0 0;
		animation: wordmark-sheen 1.3s cubic-bezier(0.4, 0, 0.2, 1) 0.4s forwards;
	}

	/* The strikethrough draws itself left to right once, just after the
	   wordmark sheen settles. "Blog" then draws itself the same way a pen
	   would: each .wordmark-trace path — one of the artwork's extracted
	   centerline strokes, see _data/blogWordmark.js — is its own element
	   with its own stroke-dasharray (its exact length) and a slice of
	   the draw timeline (--stroke-delay/--stroke-duration, sized to that
	   length's share of the total) so the pen moves at one constant
	   speed across every stroke rather than snapping through short ones.
	   A single shared dasharray across one multi-subpath path can't do
	   this: stroke-dashoffset accounting restarts at every "M", so a
	   dasharray covering the *combined* length just leaves each shorter
	   subpath rendering almost fully from the start — hence separate
	   elements. Once every stroke finishes, .wordmark-fill (the original
	   filled brush shape, hidden until now) fades in on top, settling
	   into the same solid artwork the no-motion default shows outright.
	   Both fill-mode forwards so neither replays. */
	@keyframes strike-through {
		from {
			transform: scaleX(0);
		}
	}

	@keyframes handwriting-draw {
		from {
			stroke-dashoffset: var(--stroke-length);
		}
	}

	@keyframes handwriting-settle {
		from {
			opacity: 0;
		}
	}

	.strikethrough::after {
		transform-origin: 0% 50%;
		animation: strike-through 0.5s ease-out 1.2s both;
	}

	.handwriting .wordmark-trace {
		fill: none;
		stroke: var(--text);
		stroke-width: 26;
		stroke-linecap: round;
		stroke-linejoin: round;
		stroke-dasharray: var(--stroke-length);
		animation: handwriting-draw var(--stroke-duration) linear var(--stroke-delay) both;
	}

	.handwriting .wordmark-fill {
		animation: handwriting-settle 0.3s ease-in var(--handwriting-settle-delay) both;
	}

	@supports ((animation-timeline: view()) and (animation-range: entry)) {
		/* 1. The hero wash drifts up and thins as the reader leaves the hero,
		   on the document scroll timeline over roughly the hero's height. */
		@keyframes hero-wash-drift {
			to {
				translate: 0 -12%;
				opacity: 0.35;
			}
		}

		.hero::before {
			animation: hero-wash-drift linear both;
			animation-timeline: scroll(root block);
			animation-range: 0 60vb;
		}

		/* 2. Cards settle in over the first 40% of their entry — position-
		   driven, so nothing plays unseen and scrolling back reverses it.
		   The second column of a row starts a beat after the first. */
		@keyframes card-settle {
			from {
				opacity: 0;
				translate: 0 12px;
			}
		}

		.blog-index.post-cards > li,
		.project-grid > li {
			animation: card-settle linear both;
			animation-timeline: view(block);
			animation-range: entry 0% entry 40%;

			&:nth-child(even) {
				animation-range: entry 10% entry 50%;
			}
		}

		/* 3. The studio banner's gradient edge fills from the top as the
		   banner enters. Named timeline on the banner, since the animated
		   box is its pseudo-element, not the tracked subject. */
		@keyframes edge-fill {
			from {
				scale: 1 0;
			}
		}

		.studio-banner {
			view-timeline: --studio-banner block;

			&::before {
				transform-origin: top;
				animation: edge-fill linear both;
				animation-timeline: --studio-banner;
				animation-range: entry 0% entry 60%;
			}
		}
	}
}

/* ============================================================
   Guides (/guides/)
   ============================================================
   Shared styles for the guides section, ported from
   allonsy-studio/website's pages/css/guides.css: the 6-stage color
   system, SVG diagrams, the sticky stage nav, comparison tables, code
   annotations, and prose tweaks. Its <a-band>/<a-note> chrome rules
   aren't ported (no such components here; guide content sits in the
   same .content-column prose column as blog posts, and the guide
   files' own <a-band>/<a-note> tags were converted to plain
   section/div[role="note"] at migration time), and every
   masthead-offset reference collapses to 0 (this site has no masthead).
   Lives here rather than in a per-layout css bundle shortcode because
   _layouts/guide.webc is WebC, which doesn't process Nunjucks
   shortcodes — this file is this repo's one stylesheet regardless. */

:root {
	--panel2: #e3e7ee;
	--line2: #d3d8e2;

	/* pipeline stage code — each hue is a fixed meaning, used only on that stage */
	--s1: #2f6fc7; /* parse / DOM        */
	--s2: #6536cc; /* custom el reactions */
	--s3: #0d857a; /* shadow / flatten   */
	--s4: #a86a00; /* style / stylo      */
	--s5: #cf3252; /* layout / frames    */
	--s6: #9c368a; /* paint / webrender  */
	--ok: #0f8f5f;
	--warn: light-dark(#8a5300, #e8a13a);

	/* Text-safe variants of the stage hues: the raw --sN are tuned as vivid
	   fills (paired with white/surface text) and fail AA when used directly as
	   text. These darken on light backgrounds and lighten on dark ones. */
	--s1-text: light-dark(#1f5fb0, #7fb0ff);
	--s2-text: light-dark(#5a2bb0, #b699ff);
	--s3-text: light-dark(#0a6b62, #45d0c0);
	--s4-text: light-dark(#8a5300, #e8a13a);
	--s5-text: light-dark(#b3264a, #f27d95);
	--s6-text: light-dark(#8a2c7c, #e08cd0);
	--ok-text: light-dark(#0b7a4f, #46d08a);
}

/* Anchor jumps from the stage nav must clear the pinned stage nav itself,
   so deepen the scroll padding on guide pages. This block only matters on
   guides (nothing else in the site uses .stage-nav), so it's harmless
   elsewhere even though it's not selector-scoped. */
:root {
	scroll-padding-block-start: 3.5rem;
}

/* ---------- sticky stage nav ---------- */
header.sticky {
	box-sizing: border-box;
	position: sticky;

	/* No masthead here to sit underneath — pin straight to the viewport top. */
	inset-block-start: 0px;
	inset-inline: 0;
	z-index: 5;

	font-size: 1.2rem;

	background: var(--bg);
	border-block-end: var(--thin-border-width) solid var(--border);

	.center {
		box-sizing: border-box;
		inline-size: min(var(--container-max), 100%);
		margin-inline: auto;
		padding-inline: var(--container-space);
	}

	nav.flex {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		justify-content: center;
		column-gap: calc(var(--container-space)*3);
		row-gap: var(--content-space);

		padding-block: var(--content-space);
	}

	nav > a {
		display: inline-flex;
		align-items: baseline;
		gap: 0.4em;

		color: var(--text-light);
		text-decoration: none;
		font-size: 0.75em;
	}

	nav > a:hover {
		color: var(--text);
	}

	/* Active module (set by the scrollspy script); colour-only so the row
	   baseline doesn't shift. */
	nav > a[aria-current="true"] {
		color: var(--base--text, var(--text-accent));
	}

	/* Below ~960px the centered nav wraps into 2–3 sticky rows that both eat
	   the viewport and outgrow the 3.5rem scroll-padding allowance above.
	   Collapse it to a single row that scrolls horizontally once the links
	   no longer fit, keeping its height deterministic at every width. */
	@media (width <= 980px) {
		nav.flex {
			flex-wrap: nowrap;
			justify-content: flex-start;
			overflow-x: auto;
			column-gap: calc(var(--container-space) * 2);
		}

		nav > a {
			flex-shrink: 0;
			padding-block: 6px;
			white-space: nowrap;
		}
	}

	nav > a b {
		font-family: var(--mono-font);

		/* The AA-tuned text variant of the stage hue (darkens on light, lightens
		   on dark) — the raw --base fill fails 4.5:1 as small nav text. Links
		   without a stage class (References) fall back to the accent text token. */
		color: var(--base--text, var(--text-accent));
	}
}

/* ---------- hero ---------- */
.pipewrap {
	inline-size: 100%;
	overflow-x: auto;
}

.pipewrap > svg {
	inline-size: 100%;
	block-size: auto;
	display: block;

	/* Below this the pipeline labels shrink past legibility — hold the
	   diagram at a readable size and let it pan instead. */
	min-inline-size: 880px;
}

.pipe-svg a {
	cursor: pointer;
}

.pipe-cap {
	font-family: var(--mono-font);
	font-size: 11.5px;
	color: var(--text-light);
	margin-block-start: 9px;
}

.hero .headline em {
	color: var(--text-light);
	font-style: italic;
}

.metadata {
	font-family: var(--mono-font);
	font-size: 12px;
	color: var(--text-light);

	/* Source paths are long unbreakable tokens — let them break rather than
	   spill past a narrow viewport. */
	overflow-wrap: break-word;

	p + & {
		margin-block-start: 1em;
	}
}

/* ---------- eyebrow labels ---------- */
.eyebrow {
	display: inline-flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--content-space);
	margin-block: 0 var(--container-space);

	span:not(.tag) {
		color: var(--text-light);
		text-transform: uppercase;
	}
}

.eyebrow .tag {
	text-transform: uppercase;
	color: var(--base--text, var(--text-accent));
	font-weight: var(--font-weight--bold);
}

.title {
	color: var(--text-light);
	letter-spacing: 0.08em;
	margin-block: 34px 12px;
}

.lead-in {
	font-weight: var(--font-weight--bold);
	color: var(--text);
	margin-block-start: 1.5em;
}

/* ---------- figures & diagrams ---------- */
figure {
	margin-block: 26px;
	margin-inline: 0;
}

/* Scroll container for diagrams & tables that are denser than a narrow
   viewport: the content keeps a legible size and pans, while captions and
   surrounding prose stay put. Focusable (tabindex="0" in markup) so keyboard
   users can scroll it. */
.scrollable {
	overflow-x: auto;
}

figure svg {
	box-sizing: border-box;
	inline-size: 100%;
	block-size: auto;
	display: block;
	background: hsl(0deg 0% 100%);
	border: var(--thin-border-width) solid var(--border);
	border-radius: 12px;

	/* Same trade as the hero pipeline: hold diagrams at a readable size on
	   small screens and pan, rather than scaling their labels away. */
	min-inline-size: 880px;
}

figcaption {
	font-family: var(--mono-font);
	font-size: 11.5px;
	color: var(--text-light);
	margin-block-start: 9px;
	padding-inline-start: 2px;
}

svg text {
	font-family: var(--mono-font);
}

/* ---------- inline code ---------- */
code.inl {
	background: var(--panel2);
	color: #2c3242;
	padding-block: 1px;
	padding-inline: 6px;
	border-radius: 5px;
	font-size: 0.9em;
	border: var(--thin-border-width) solid var(--line2);
	font-family: var(--mono-font);

	/* Identifiers like mWhenDefinedPromiseMap are longer than a phone is
	   wide; break them rather than overflow the prose column. */
	overflow-wrap: break-word;
}

/* ---------- code copy button (added by the guide's inline script) ---------- */
.code-wrap {
	position: relative;
}

.code-wrap .code-copy {
	position: absolute;
	inset-block-start: 6px;
	inset-inline-end: 6px;

	padding-block: 2px;
	padding-inline: 8px;
	font-family: var(--sans-font);
	font-size: 12px;
	color: var(--text-light);
	background: var(--bg);
	border: var(--thin-border-width) solid var(--border);
	border-radius: var(--standard-border-radius);
	cursor: pointer;

	/* De-emphasized via the subtle text color only — never whole-button
	   opacity, which pushes effective contrast below the 4.5:1 AA floor. */
	transition: color 0.15s ease, border-color 0.15s ease;
}

.code-wrap:hover .code-copy,
.code-wrap .code-copy:is(:hover, :focus-visible) {
	color: var(--text);
	border-color: var(--text-light);
}

@media (prefers-reduced-motion: reduce) {
	.code-wrap .code-copy {
		transition: none;
	}
}

/* ---------- deep-dive disclosure ---------- */
details.deep {
	border: var(--thin-border-width) solid var(--line2);
	background: var(--bg-overlay);
	margin-block-start: var(--content-space);
	transition: all 0.15s;

	summary {
		font-family: var(--mono-font);

		.lbl {
			color: var(--text-light);
			font-size: 0.8em;
			text-transform: uppercase;

			&::before {
				content: "|";
				margin-inline-end: 0.5em;
				padding-inline: 0.5em;
			}
		}
	}

	.body {
		padding-block: 8px;
		padding-inline: 16px;

		p {
			max-inline-size: unset;

			&:not(:last-child) {
				margin-block-end: 1em;
			}
		}
	}
}

/* ---------- lifecycle scenario switcher ---------- */
.switch {
	display: flex;
	gap: 8px;
	flex-wrap: wrap;
	margin-block: 8px 22px;
}

.switch button {
	font-family: var(--mono-font);
	font-size: 12.5px;
	background: var(--bg-overlay);
	color: var(--text-light);
	border: var(--thin-border-width) solid var(--line2);
	border-radius: 8px;
	padding-block: 9px;
	padding-inline: 13px;
	cursor: pointer;
	transition: all 0.15s;
}

@media (prefers-reduced-motion: reduce) {
	.switch button {
		transition: none;
	}
}

.switch button:hover {
	color: var(--text);
}

.switch button[aria-selected="true"] {
	/* Darker teal than the raw --s3 fill so white text clears AA in both modes
	   (the stage teal is too light to carry small text at 4.5:1). */
	background: #0a6b62;
	color: hsl(0deg 0% 100%);
	border-color: #0a6b62;
	font-weight: 700;

	/* In forced-colors the stage fill and surface text both map toward the
	   canvas color (white-on-white); pin the selected state to a guaranteed
	   system pairing instead. */
	@media (forced-colors: active) {
		background: Highlight;
		color: HighlightText;
		border-color: Highlight;
	}
}

.scene {
	display: none;
}

.scene.on {
	display: block;
}

.seq {
	list-style: none;
	margin: 0;
	padding: 0;
	counter-reset: step;
}

.seq li {
	position: relative;
	padding-block: 12px;
	padding-inline: 54px 14px;
	border: var(--thin-border-width) solid var(--border);
	border-radius: 10px;
	margin-block-end: 10px;
	background: var(--bg-overlay);
	counter-increment: step;
}

.seq li::before {
	content: counter(step);
	position: absolute;
	inset-inline-start: 12px;
	inset-block-start: 11px;
	inline-size: 28px;
	block-size: 28px;
	display: grid;
	place-items: center;
	border-radius: 7px;
	font-family: var(--mono-font);
	font-weight: 700;
	font-size: 13px;
	background: var(--panel2);

	/* --panel2 is a fixed light chip even in dark mode, so keep the dark stage
	   hue here rather than the mode-adaptive --s3-text (which would go light). */
	color: var(--s3);
	border: var(--thin-border-width) solid var(--line2);
}

.seq li .cb {
	font-family: var(--mono-font);
	font-weight: 700;
	color: var(--text);
	font-size: 14px;
}

.seq li .cb.ctor {
	color: var(--s2-text);
}

.seq li .cb.attr {
	color: var(--s4-text);
}

.seq li .cb.conn {
	color: var(--ok-text);
}

.seq li .cb.disc {
	color: var(--s5-text);
}

.seq li .cb.move {
	color: var(--s3-text);
}

.seq li .cb.adopt {
	color: var(--s6-text);
}

/* Non-event placeholder row in a lifecycle sequence ("— created, no callbacks —"). */
.seq li .cb.pending {
	color: var(--text-light);
}

/* "Mechanism:" footnote under each lifecycle scenario. */
.mechanism {
	font-size: 14px;
	color: var(--text-light);
}

.seq li .why {
	font-size: 13.5px;
	color: var(--text-light);
	display: block;
	margin-block-start: 2px;
}

.seq li .why code {
	font-size: 12.5px;
}

/* ---------- reaction-stack stepper ---------- */
.stepper {
	background: var(--bg-overlay);
	border: var(--thin-border-width) solid var(--border);
	border-radius: 14px;
	padding: 20px;
	margin-block: 22px;
}

.stepper .stage {
	font-family: var(--mono-font);
	font-size: 12.5px;
	color: var(--s2-text);
	letter-spacing: 0.05em;
	min-block-size: 20px;
}

.stepper .desc {
	color: var(--text-light);
	font-size: 14px;
	margin-block: 6px 18px;
	min-block-size: 42px;
}

.qzone {
	display: grid;
	gap: 16px;
}

@media (width >= 640px) {
	.qzone {
		grid-template-columns: 1.4fr 1fr;
	}
}

.q {
	border: var(--thin-border-width) solid var(--line2);
	border-radius: 10px;
	padding: 12px;
	background: var(--bg-overlay);
}

.q h4 {
	font-family: var(--mono-font);
	font-size: 11.5px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	margin-block: 0 10px;
	color: var(--text-light);
	font-weight: 500;
}

.q.backup h4 {
	color: var(--warn);
}

.qstack {
	display: flex;
	flex-direction: column;
	gap: 7px;
	min-block-size: 120px;
}

.frame {
	border: var(--thin-border-width) dashed var(--line2);
	border-radius: 8px;
	padding-block: 8px;
	padding-inline: 9px;
}

.frame .flabel {
	font-family: var(--mono-font);
	font-size: 10px;
	color: var(--text-light);
	text-transform: uppercase;
	letter-spacing: 0.06em;
	margin-block-end: 6px;
}

.frame.active {
	border-style: solid;
	border-color: var(--s2);
	background: rgb(101 54 204 / 10%);
}

.rx {
	font-family: var(--mono-font);
	font-size: 12px;
	padding-block: 5px;
	padding-inline: 8px;
	border-radius: 6px;
	margin-block: 4px;
	background: var(--panel2);
	border: var(--thin-border-width) solid var(--line2);
	color: var(--text);
	display: flex;
	justify-content: space-between;
	gap: 8px;
	align-items: center;
}

.rx .tp {
	font-size: 9.5px;
	padding-block: 1px;
	padding-inline: 5px;
	border-radius: 4px;
	font-weight: 700;
	letter-spacing: 0.03em;
}

.rx.up .tp {
	background: var(--s2);
	color: var(--bg);
}

.rx.cb .tp {
	background: var(--s4);
	color: var(--bg);
}

.rx.fired {
	opacity: 0.42;
	text-decoration: line-through;
}

.empty {
	color: var(--text-light);
	font-family: var(--mono-font);
	font-size: 11.5px;
	font-style: italic;
}

.controls {
	display: flex;
	gap: 10px;
	margin-block-start: 18px;
	flex-wrap: wrap;
	align-items: center;
}

.controls button {
	font-family: var(--mono-font);
	font-size: 13px;
	padding-block: 9px;
	padding-inline: 15px;
	border-radius: 8px;
	cursor: pointer;
	border: var(--thin-border-width) solid var(--line2);
	background: var(--bg-overlay);
	color: var(--text);
}

.controls button:hover {
	border-color: var(--s2);
}

.controls button.primary {
	background: var(--s2);
	border-color: var(--s2);
	color: var(--base--on, hsl(0deg 0% 100%));
	font-weight: 700;
}

.controls .prog {
	font-family: var(--mono-font);
	font-size: 11.5px;
	color: var(--text-light);
	margin-inline-start: auto;
}

/* ---------- comparison / reference tables ---------- */
.grid {
	display: grid;
	gap: 16px;
	grid-template-columns: repeat(var(--columns, auto-fit), minmax(min(260px, 100%), 1fr));
	margin-block: 18px;

	/* A fixed --columns count (e.g. 2) can't fit side by side on a phone —
	   stack the cards instead. */
	@media (width <= 640px) {
		grid-template-columns: 1fr;
	}
}

/* Plain replacement for website's <a-card bordered> (no such component
   here) — a stage-colored card sets --base/--base--text via its own
   .stageN class (see the stage color system above); .accent inside it
   picks that up, falling back to the ordinary link color on stage-less
   cards. */
.card {
	border: var(--thin-border-width) solid var(--border);
	border-radius: var(--standard-border-radius);
	background: var(--bg-overlay);
	padding: calc(var(--content-space) * 2) calc(var(--content-space) * 1.5);
}

.grid .card .content .accent,
.grid .card .content h3.accent {
	color: var(--base--text, var(--link));
}

.reftable {
	inline-size: 100%;
	border-collapse: collapse;
	margin-block: 18px;
	font-size: 13.5px;

	/* Inside the narrow scroll container the auto layout would crush the
	   prose column to one word per line; keep the columns roomy and pan. */
	min-inline-size: 640px;
}

.reftable th,
.reftable td {
	text-align: start;
	vertical-align: top;
	padding-block: 8px;
	padding-inline: 10px;
	border-block-end: var(--thin-border-width) solid var(--border);
}

.reftable th {
	font-family: var(--mono-font);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-light);
}

.reftable td code {
	font-family: var(--mono-font);
}

.reftable td.what {
	color: var(--text-light);
}

.tt {
	font-family: var(--mono-font);
	font-size: 0.7em;

	/* Was a fixed slate that disappeared on the dark page background. */
	color: var(--text);
}

/* ---------- reference definition list ---------- */
dl dt {
	margin-block-start: 1em;
	font-weight: var(--font-weight--bold);
}

dl dd.d {
	color: var(--text-light);
	margin-inline-start: 0;
	font-size: 0.95em;
}
