/* Copyright © 2026 Patrick Angle. All rights reserved. */

/* The header and footer, which no content file writes — the layout builds both from `site.json` and
 * `navigation.json`. */

@layer components {
    /* Registering the property makes it a real <number> in the cascade, so the calc() interpolations below resolve
       and `ShrinkingHeader` only ever has to write one value. */
    @property --header-scroll-progress {
        syntax: "<number>";
        inherits: true;
        initial-value: 0;
    }

    site-header {
        --header-scatter: calc(155 - 85 * var(--header-scroll-progress));
        --header-jumble: calc(245 - 70 * var(--header-scroll-progress));
        --header-cutout: calc(170 - 50 * var(--header-scroll-progress));
        --header-letter-tilt: calc(1.4deg - 1deg * var(--header-scroll-progress));

        align-items: center;
        anchor-name: --site-header;
        background-color: color-mix(in srgb, var(--ink-color) 82%, transparent);
        backdrop-filter: blur(28px) saturate(180%);
        border: 2px solid color-mix(in srgb, var(--header-accent-color, var(--page-accent-color)) 55%, transparent);
        border-radius: var(--chip-corner-radius);
        box-shadow: 0 1.25rem 3rem color-mix(in srgb, var(--ink-color) 45%, transparent);
        color: var(--paper-color);
        display: flex;
        gap: clamp(1rem, 3vw, 2.5rem);
        inline-size: calc(min(64rem, 100% - 2 * var(--gutter-inline-size)) - 8rem * var(--header-scroll-progress));
        inset-block-start: calc(1.25rem - 0.5rem * var(--header-scroll-progress));
        inset-inline: 0;
        justify-content: space-between;
        margin-inline: auto;
        padding: calc(0.85rem - 0.3rem * var(--header-scroll-progress)) clamp(1rem, 2.5vw, 1.75rem);
        position: fixed;
        transition: background-color 200ms ease, border-color 200ms ease;
        z-index: 100;
    }

    site-wordmark {
        display: block;
    }

    site-wordmark a {
        align-items: baseline;
        display: flex;
        font-size: calc(2.6rem - 0.9rem * var(--header-scroll-progress));
        line-height: 1;
        text-decoration: none;
    }

    /* Each letter interpolates from wherever the scroll has left the axes up to the top of their ranges. */
    site-wordmark a > * {
        --letter-tilt: calc(var(--header-letter-tilt) * (1 + 2 * var(--axis-boost)));

        display: block;
        font-variation-settings:
            "SCTR" calc(var(--header-scatter) + (300 - var(--header-scatter)) * var(--axis-boost)),
            "JMBL" calc(var(--header-jumble) + (300 - var(--header-jumble)) * var(--axis-boost)),
            "COUT" calc(var(--header-cutout) + (300 - var(--header-cutout)) * var(--axis-boost));
    }

    /* Staggering the delay by letter turns the hover into a wave that runs the length of the name and settles back
       out the same way when the pointer leaves. */
    @media (hover: hover) {
        site-wordmark a > * {
            transition: --axis-boost 300ms ease;
            transition-delay: calc(var(--letter-index) * 28ms);
        }

        site-wordmark a:hover > * {
            --axis-boost: 1;
        }
    }

    site-wordmark a > :nth-child(1) { --letter-index: 0; }
    site-wordmark a > :nth-child(2) { --letter-index: 1; }
    site-wordmark a > :nth-child(3) { --letter-index: 2; }
    site-wordmark a > :nth-child(4) { --letter-index: 3; }
    site-wordmark a > :nth-child(5) { --letter-index: 4; }
    site-wordmark a > :nth-child(6) { --letter-index: 5; }
    site-wordmark a > :nth-child(7) { --letter-index: 6; }
    site-wordmark a > :nth-child(8) { --letter-index: 7; }
    site-wordmark a > :nth-child(9) { --letter-index: 8; }
    site-wordmark a > :nth-child(10) { --letter-index: 9; }
    site-wordmark a > :nth-child(11) { --letter-index: 10; }
    site-wordmark a > :nth-child(12) { --letter-index: 11; }
    site-wordmark a > :nth-child(13) { --letter-index: 12; }

    /* Reduced motion gets no wave at all, rather than the instant jump to full scatter that a zeroed duration
       would produce. */
    @media (prefers-reduced-motion: reduce) {
        site-wordmark a:hover > * {
            --axis-boost: 0;
        }
    }

    wordmark-space {
        inline-size: 0.3em;
    }

    /* A ramp of its own rather than the page palette: those hues are tuned to carry ink-colored text as page grounds,
       and at wordmark size on the dark pill they go dull. These are pushed toward full chroma to hold up small. */
    site-wordmark a > :nth-child(7n + 1) { color: #FF3B47; }
    site-wordmark a > :nth-child(7n + 2) { color: #FF8A00; }
    site-wordmark a > :nth-child(7n + 3) { color: #FFD400; }
    site-wordmark a > :nth-child(7n + 4) { color: #2FD65C; }
    site-wordmark a > :nth-child(7n + 5) { color: #12E0DC; }
    site-wordmark a > :nth-child(7n + 6) { color: #C77DFF; }
    site-wordmark a > :nth-child(7n + 7) { color: #FF6FB1; }

    /* Each letter casts a halo in its own color and a tight contact shadow, which lifts the wordmark off the pill and
       reads as extra saturation without touching the fills. */
    site-wordmark a > * {
        filter:
            drop-shadow(0 1px 1px rgb(0 0 0 / 0.65))
            drop-shadow(0 0 14px color-mix(in srgb, currentColor 50%, transparent));
    }

    /* The axes scatter each glyph on their own; these nudges only break up the baseline so the wordmark reads as
       torn paper rather than as a single scattered word. */
    site-wordmark a > :nth-child(3n + 1) { rotate: calc(-1 * var(--letter-tilt)); }
    site-wordmark a > :nth-child(3n + 2) { rotate: calc(1.8 * var(--letter-tilt)); }
    site-wordmark a > :nth-child(3n + 3) { rotate: var(--letter-tilt); translate: 0 calc(-0.03em); }

    site-header nav ul {
        align-items: center;
        display: flex;
        gap: clamp(0.75rem, 2vw, 1.75rem);
    }

    site-header nav a,
    site-header nav button {
        /* A link is inline and a button is not, and an inline box holding a block rule lays that rule out a pixel or
           so off from where a block box does — so both entries are blocks and the rules across the header line up. */
        display: block;
        font-size: calc(0.85rem - 0.05rem * var(--header-scroll-progress));
        letter-spacing: 0.12em;
        padding-block: 0.35rem;
        position: relative;
        text-decoration: none;
        text-transform: uppercase;
        white-space: nowrap;
    }

    /* The rule hangs below the entry rather than sitting inside it, so the box the header centers holds the text and
       nothing else, and the type sits on the pill's middle whether or not a rule is under it. */
    site-header nav a::after,
    site-header nav button::after {
        background-color: var(--header-accent-color, var(--page-accent-color));
        block-size: 3px;
        content: "";
        inset-block-start: 100%;
        inset-inline: 0;
        position: absolute;
        scale: 0 1;
        transform-origin: 0 50%;
        transition: scale 180ms ease;
    }

    /* A group wears the rule for the page a reader is on the same way the entry that links to it would, so the header
       says where they are whether the menu is open or shut. */
    site-header nav a:hover::after,
    site-header nav a[aria-current="page"]::after,
    site-header nav button:hover::after,
    site-header nav-group:has([popover]:popover-open) button::after,
    site-header li:has(a[aria-current="page"]) button::after {
        scale: 1 1;
    }

    /* Second-level navigation: an entry that opens a panel of pages rather than naming one. The panel is a popover, so
       the browser gives it the top layer, a click anywhere else to dismiss it, and Escape, and anchor positioning ties
       it to the entry without either of them measuring the other. */

    site-header nav-group {
        anchor-scope: --nav-group-button;
        display: block;
    }

    site-header nav-group > button {
        anchor-name: --nav-group-button;
        background: none;
        border: none;
        color: inherit;
        cursor: pointer;
        /* Only the parts of the font a button does not already take from its ancestors: the `font` shorthand would
           reach further and undo the size the entry shares with the links beside it. */
        font-family: inherit;
        line-height: inherit;
        padding-inline: 0;
    }

    /* The affordance is drawn rather than written: a reader is being shown a state, not read a word. */
    nav-group-label::after {
        border-block-start: 0.32em solid currentColor;
        border-inline: 0.28em solid transparent;
        content: "";
        display: inline-block;
        margin-inline-start: 0.5em;
        transition: rotate 180ms ease;
        vertical-align: 0.1em;
    }

    site-header nav-group:has([popover]:popover-open) nav-group-label::after {
        rotate: 180deg;
    }

    site-header nav [popover] {
        align-items: start;
        backdrop-filter: blur(28px) saturate(180%);
        background-color: color-mix(in srgb, var(--ink-color) 92%, transparent);
        border: 2px solid color-mix(in srgb, var(--header-accent-color, var(--page-accent-color)) 55%, transparent);
        border-radius: 1.25rem;
        box-shadow: 0 1rem 2.5rem color-mix(in srgb, var(--ink-color) 55%, transparent);
        /* A popover is handed `CanvasText` by the browser itself, which outranks the header's color on its way down
           the tree, so the panel asks for what it would have inherited. */
        color: inherit;
        flex-direction: column;
        /* Wide enough to carry the rule that now hangs below each entry rather than counting into its height. */
        gap: 0.5rem;
        inset: auto;
        /* Down the block axis the panel hangs off the pill itself, so it sits the same however far the header has
           shrunk; across the inline axis it centers on the entry that opened it. */
        inset-block-start: anchor(--site-header bottom);
        justify-self: anchor-center;
        margin: 0;
        /* The panel rides up over the pill's lower edge, so the two read as one object rather than as a card that
           happens to be underneath. */
        margin-block-start: -0.6rem;
        padding: 0.6rem 1.2rem;
        position: absolute;
        position-anchor: --nav-group-button;
    }

    /* The header's list rule matches this panel too, and an author `display` outranks the rule the browser hides a
       closed popover with, so the panel puts that back. */
    site-header nav [popover]:not(:popover-open) {
        display: none;
    }

    @media (width < 52rem) {
        site-header {
            border-radius: 1.5rem;
            flex-direction: column;
            gap: 0.5rem;
            inline-size: min(30rem, 100% - 2rem);
        }

        site-header nav ul {
            flex-wrap: wrap;
            justify-content: center;
        }

        site-header nav a,
        site-header nav button {
            font-size: 0.75rem;
            letter-spacing: 0.08em;
        }
    }

    /* Footer */

    site-footer {
        background-color: var(--ink-color);
        /* The ink-on-ink pages would otherwise have no seam between content and footer. */
        border-block-start: 4px solid var(--page-accent-color);
        color: var(--paper-color);
        display: grid;
        grid-template-columns:
            [full-start] minmax(var(--gutter-inline-size), 1fr)
            [content-start] minmax(0, var(--content-inline-size))
            [content-end] minmax(var(--gutter-inline-size), 1fr)
            [full-end];
        padding-block: clamp(3rem, 6vw, 5rem);
    }

    site-footer > * {
        grid-column: content;
    }

    /* The heading is the contact call to action and is far wider than either link column, so it takes a row of its
       own rather than wrapping one of the navs onto a second line at an arbitrary width. */
    .site-footer-inner {
        display: flex;
        flex-wrap: wrap;
        gap: 2.5rem clamp(2.5rem, 8vw, 6rem);
    }

    .site-footer-heading {
        flex: 1 0 100%;
        font-size: clamp(2rem, 1.4rem + 3vw, 3.5rem);
        font-variation-settings:
            "SCTR" calc(110 + 190 * var(--axis-boost)),
            "JMBL" calc(220 + 80 * var(--axis-boost)),
            "COUT" calc(145 + 155 * var(--axis-boost));
        line-height: 1;
        /* The base `p` measure would otherwise cap this at 34rem and let a nav share its row. */
        max-inline-size: none;
        text-transform: uppercase;
    }

    /* The same axis push as the wordmark, but the whole phrase goes at once — no per-letter stagger. `:has()` scopes
       it to the link so the heading doesn't react to the empty width it spans, and so the contact page, where the
       heading is plain text, stays still. */
    @media (hover: hover) {
        .site-footer-heading {
            transition: --axis-boost 280ms ease;
        }

        .site-footer-heading:has(a:hover) {
            --axis-boost: 1;
        }
    }

    @media (prefers-reduced-motion: reduce) {
        .site-footer-heading:has(a:hover) {
            --axis-boost: 0;
        }
    }

    /* The heading is the contact link; at display size the inherited underline reads as a rule across the footer, so
       it only appears on hover. The arrow carries the affordance the rest of the time, and is drawn here so the
       navigation file holds a label rather than a label and a glyph. */
    .site-footer-heading a {
        text-decoration: none;
    }

    .site-footer-heading a::after {
        content: " →";
    }

    .site-footer-heading a:hover {
        text-decoration: underline;
    }

    site-footer nav ul {
        display: flex;
        flex-wrap: wrap;
        gap: 0.4rem 1.25rem;
    }

    site-footer a {
        text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
    }

    site-footer a:hover {
        color: var(--color-yellow);
        text-decoration-color: currentColor;
    }

    .site-footer-legal {
        color: color-mix(in srgb, var(--paper-color) 55%, transparent);
        font-size: 0.85rem;
        margin-block-start: 2.5rem;
        max-inline-size: none;
    }
}
