/* ==========================================================================
   tsiwp.com : shared system
   Comic-book line design, ported from tomvsreality.com. No JavaScript.
   Self-hosted subsetted fonts. The ground is a mesh sampled from the cover.
   ========================================================================== */

/* ---- Fonts ------------------------------------------------------------- */
/* Comic Neue 400 is deliberately not shipped: body copy is 700 throughout and
   nothing on this site sets 400, so the file would be dead weight. */
@font-face {
    font-family: 'Bungee';
    src: url('../fonts/bungee-regular.woff2') format('woff2');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: 'Comic Neue';
    src: url('../fonts/comic-neue-700.woff2') format('woff2');
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}

/* ---- Tokens ------------------------------------------------------------ */
:root {
    /*
       Ground. A five-blob mesh over a vertical ramp, sampled from the cover
       itself rather than the spine: warm terracotta top right, magenta upper
       left, the violet of the stage through the middle, and the crowd's two
       blues along the bottom. Band averages taken off the cover art were
       #CA9470 sky, #625389 stage, #2E2C69 crowd.

       A single linear ramp was the first attempt and read as a flat wash,
       because one axis of change over a whole viewport has nothing for the eye
       to catch on. Overlapping radial blobs give the ground a centre and a
       direction without any of them being a visible edge.

       Contrast is measured, not assumed. Compositing this stack over a 101x101
       grid puts the lightest point at 78% 8%, the middle of the terracotta
       blob, at #694140. White on that is 8.67:1, well clear of the 4.5:1 body
       text needs, so the ink system holds everywhere on the page.
    */
    /* The blob colours are written as rgba() literals rather than tokens: each
       one needs its own alpha, and a hex custom property cannot carry one
       without color-mix, which buys nothing here. Source hexes, in stack order:
       ember #C4703A, magenta #964278, violet #6A3F8C, blue #2B4979,
       deep blue #18305C. */
    --ground-base: #171A3C;
    --ground:
        radial-gradient(60% 45% at 78% 8%,  rgba(196, 112, 58, 0.42) 0%, rgba(196, 112, 58, 0) 70%),
        radial-gradient(55% 40% at 18% 22%, rgba(150, 66, 120, 0.38) 0%, rgba(150, 66, 120, 0) 72%),
        radial-gradient(70% 50% at 50% 55%, rgba(106, 63, 140, 0.45) 0%, rgba(106, 63, 140, 0) 75%),
        radial-gradient(65% 55% at 12% 88%, rgba(43, 73, 121, 0.55) 0%, rgba(43, 73, 121, 0) 72%),
        radial-gradient(60% 50% at 88% 92%, rgba(24, 48, 92, 0.55) 0%, rgba(24, 48, 92, 0) 70%),
        linear-gradient(180deg, #2A1F45 0%, #171A3C 55%, #12213F 100%);

    /* The one brand accent: horizontal gradient, orange -> gold, left to right.
       This is the spine's warm half, unchanged from tomvsreality.com. */
    --accent: linear-gradient(90deg, #FF7700 0%, #FFC300 100%);

    /* Neutrals. Ink (outlines + text) is always pure at 100%.
       Buttons are full opaque black (they carry hover / gradient effects).
       Panels, cards, and headline letter-fills use --surface-dark, a TRANSLUCENT
       black so the ground reads through them. One dial. */
    --ink-white: #FFFFFF;
    --ink-black: #000000;
    --surface-opacity: 0.7;
    --surface-dark: rgba(0, 0, 0, var(--surface-opacity));

    /* Comic-book ink line: ONE weight everywhere, fixed px, never em-relative */
    --line: 3px;
    --radius: 12px;
    /*
       Title outline. The one stroke that cannot be a fixed px value: the title
       is sized in vw, and browser zoom shrinks the CSS viewport rather than the
       stroke, so a fixed stroke gains relative weight as the letters shrink and
       -webkit-text-stroke (centred, half of it painting inward) starts closing
       the counters. Tracks the title's own 6vw ramp instead.
    */
    --title-line: clamp(1.6px, calc(0.8px + 0.15vw), 2.4px);

    /* Bungee needs negative tracking to read like the logo (-3% to -5%) */
    --bungee-tracking: -0.04em;
}

/* ---- Reset ------------------------------------------------------------- */
* { margin: 0; padding: 0; box-sizing: border-box; }
/* min-height, not height. A fixed 100% locks the body to the viewport, so on a
   page taller than one screen the scrollable area stops at the last element and
   the bottom padding is never reachable. This bit tomvsreality.com once. */
html, body { min-height: 100%; }
img, svg { display: block; max-width: 100%; }
a { text-decoration: none; color: inherit; }

body {
    font-family: 'Comic Neue', system-ui, sans-serif;
    font-weight: 700;
    color: var(--ink-white);
    /* The fallback colour matters: it is what paints if the gradient is still
       resolving, and it is the mid-tone of the ramp rather than either end. */
    background-color: var(--ground-base);
    background-image: var(--ground);
    background-attachment: fixed;
    background-repeat: no-repeat;
    min-height: 100vh;
    -webkit-font-smoothing: antialiased;
}

/* ==========================================================================
   Comic-line primitives
   ========================================================================== */

/*
   Outlined button. The gradient-border trick: two stacked backgrounds, the
   dark surface clipped to padding-box and the outline colour clipped to
   border-box. Default outline is pure white. On hover the outline and the
   label both switch to the accent gradient.
*/
.btn {
    /* buttons are full opaque black, so their two-layer outline trick is fine */
    --surface-dark: var(--ink-black);
    --hue: var(--accent);
    display: inline-block;
    border: var(--line) solid transparent;
    border-radius: var(--radius);
    padding: 0.7em 1.15em;
    background:
        linear-gradient(var(--surface-dark), var(--surface-dark)) padding-box,
        linear-gradient(var(--ink-white), var(--ink-white)) border-box;
    transition: background 0.15s ease;
    cursor: pointer;
}
.btn__label {
    display: block;
    font-family: 'Comic Neue', system-ui, sans-serif;
    font-weight: 700;
    text-transform: uppercase;
    line-height: 1;
    text-align: center;
    color: var(--ink-white);
}
.btn:hover,
.btn:focus-visible {
    outline: none;
    background:
        linear-gradient(var(--surface-dark), var(--surface-dark)) padding-box,
        var(--hue) border-box;
}
.btn:hover .btn__label,
.btn:focus-visible .btn__label {
    background: var(--hue);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

/* Accent variant: the primary call to action. Sits in the accent gradient at
   rest and fills solid on hover, so it outranks a plain outlined button. */
.btn--accent {
    background:
        linear-gradient(var(--surface-dark), var(--surface-dark)) padding-box,
        var(--accent) border-box;
}
.btn--accent .btn__label {
    background: var(--accent);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}
.btn--accent:hover,
.btn--accent:focus-visible {
    background: var(--accent) padding-box,
        linear-gradient(var(--ink-black), var(--ink-black)) border-box;
}
.btn--accent:hover .btn__label,
.btn--accent:focus-visible .btn__label {
    background: none;
    color: var(--ink-black);
    -webkit-text-fill-color: var(--ink-black);
}

/* ==========================================================================
   Page primitives
   ========================================================================== */

.subpage {
    position: relative;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: clamp(1.5rem, 4vh, 2.5rem);
    padding: clamp(3.75rem, 9vh, 6.5rem) 6vw clamp(2.5rem, 6vh, 4rem);
}

/* Close button: returns to home. An X built from an SVG whose stroke stays at
   the uniform --line weight regardless of scale (non-scaling-stroke). */
.close-x {
    --surface-dark: var(--ink-black);
    position: absolute;
    top: clamp(1.1rem, 3vw, 2.2rem);
    right: clamp(1.1rem, 3vw, 2.2rem);
    width: clamp(2.6rem, 4vw, 3.4rem);
    aspect-ratio: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    border: var(--line) solid transparent;
    border-radius: var(--radius);
    background:
        linear-gradient(var(--surface-dark), var(--surface-dark)) padding-box,
        linear-gradient(var(--ink-white), var(--ink-white)) border-box;
    transition: background 0.15s ease;
}
.close-x svg { width: 55%; height: 55%; display: block; }
.close-x svg line {
    stroke: var(--ink-white);
    stroke-width: var(--line);
    stroke-linecap: round;
    vector-effect: non-scaling-stroke;
}
.close-x:hover,
.close-x:focus-visible {
    outline: none;
    background:
        linear-gradient(var(--surface-dark), var(--surface-dark)) padding-box,
        var(--accent) border-box;
}
.close-x:hover svg line,
.close-x:focus-visible svg line { stroke: #FF9500; }

/* Bungee outline title: dark fill, white outline at the uniform --line weight */
.page-title {
    font-family: 'Bungee', system-ui, sans-serif;
    font-weight: 400;
    text-transform: uppercase;
    letter-spacing: var(--bungee-tracking);
    line-height: 1.05;
    text-align: center;
    color: var(--surface-dark);
    -webkit-text-stroke: var(--title-line) var(--ink-white);
    font-size: clamp(2rem, 6vw, 4rem);
}

/* Content panel: ONE translucent layer + a real border, so the ground reads
   through it (no opaque backing). */
.panel {
    width: min(760px, 100%);
    border: var(--line) solid var(--ink-white);
    border-radius: calc(var(--radius) + 6px);
    background: var(--surface-dark);
    padding: clamp(1.6rem, 4vw, 2.9rem);
    color: var(--ink-white);
    font-family: 'Comic Neue', system-ui, sans-serif;
    font-weight: 700;
    font-size: clamp(1.1rem, 1.6vw, 1.4rem);
    line-height: 1.5;
    text-align: center;
}
.panel p { margin: 0 0 1em; }
.panel p:last-child { margin-bottom: 0; }
.panel a { color: var(--ink-white); text-decoration: underline; }
.panel--left { text-align: left; }
.panel--left h2 {
    font-size: 1.15em;
    margin: 1.4em 0 0.35em;
}
.panel--left h2:first-child { margin-top: 0; }
.panel--left h3 {
    font-size: 1em;
    margin: 1.1em 0 0.3em;
}

/* ---- Lists inside a panel ---------------------------------------------- */
/* The global reset strips list padding, so indentation, markers and item
   rhythm are all restored here. Always left-aligned: a centred list detaches
   its markers from its text. */
.panel ol,
.panel ul {
    margin: 0 0 1em;
    padding-left: 1.7em;
    text-align: left;
}
.panel ol:last-child,
.panel ul:last-child { margin-bottom: 0; }
.panel li { margin-bottom: 0.6em; }
.panel li:last-child { margin-bottom: 0; }
.panel li::marker { color: var(--ink-white); }

/*
   Comic Neue ships 400 and 700 only, and body copy is already 700, so the
   default `bolder` on <strong> computes to 900 and the browser fakes it by
   smearing the outline. Emphasis is carried by .accent-text instead.
*/
.panel strong { font-weight: inherit; }

/* Accent inline text (a phrase inside body copy set in the brand gradient) */
.accent-text {
    background: var(--accent);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

/*
   .panel underlines its links so prose links are visible. A button is not
   prose, and inheriting that rule draws a line under every label. Lives here
   rather than in home.css because the sources page puts buttons in a panel too.
*/
.panel .btn { text-decoration: none; }

/*
   Buttons inside a panel sit at least a full panel-padding away from the copy
   above them. Anything less and the gap to the text reads as tighter than the
   gap to the panel's own edge, which makes the buttons look like a footer that
   fell off the bottom of the paragraph rather than a separate block. Same
   clamp as .panel's padding, so the two track each other at every width.
*/
.panel .actions { margin-top: clamp(1.6rem, 4vw, 2.9rem); }

/* Row of action buttons under a panel */
.actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 1rem;
}

.subpage .btn {
    font-size: clamp(0.95rem, 1.4vw, 1.35rem);
}

/* ==========================================================================
   Overlay: a dialog with no JavaScript
   ==========================================================================
   Driven by :target. The trigger is an ordinary link to the overlay's id, so
   it works with scripting off, which is the whole point: this site ships no JS.

   Consequences of that choice, accepted deliberately:
   - Opening and closing push history entries, so Back steps through them.
   - Focus is not trapped inside the box, because trapping needs script. The
     close control is therefore the FIRST focusable thing in the box, and the
     backdrop is a real link, so Escape-by-keyboard is replaced by "Tab once,
     press Enter".
   The box is display:none until targeted, so its content stays in the markup
   for crawlers while being inert for readers.
*/
.overlay {
    position: fixed;
    inset: 0;
    z-index: 10;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 6vw;
}
.overlay:target { display: flex; }

/* The backdrop is an anchor covering the viewport, so a click anywhere outside
   the box dismisses. aria-hidden because the box already carries a close
   control: exposing two identical "close" targets to a screen reader is noise. */
.overlay__backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.72);
}

.overlay__box {
    position: relative;
    width: min(560px, 100%);
    max-height: 100%;
    overflow-y: auto;
    border: var(--line) solid var(--ink-white);
    border-radius: calc(var(--radius) + 6px);
    /* Opaque, unlike .panel. A translucent surface over a translucent backdrop
       stacks two washes of the ground and the copy stops holding its contrast. */
    background: #12121A;
    padding: clamp(1.8rem, 5vw, 2.6rem);
    text-align: center;
    display: flex;
    flex-direction: column;
    gap: 1.1rem;
}
/*
   Set in Comic Neue, not Bungee, unlike .page-title. An overlay is display:none
   until it is targeted, and browsers do not fetch a font for text they are not
   painting, so a Bungee title here would download on click and swap in front of
   the reader. Preloading Bungee on the landing page to avoid that would put a
   second font on the critical path of the one page whose LCP is the cover.
   Uppercase Comic Neue is already the site's other display voice (.btn__label).
*/
.overlay__title {
    font-family: 'Comic Neue', system-ui, sans-serif;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: var(--bungee-tracking);
    line-height: 1.1;
    font-size: clamp(1.35rem, 3.2vw, 1.9rem);
    background: var(--accent);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    /* Room for the close control, which is absolutely positioned top right. */
    padding-right: 2.6rem;
    text-align: left;
}
.overlay__box p {
    font-size: clamp(1rem, 1.5vw, 1.15rem);
    line-height: 1.5;
    text-align: left;
    margin: 0;
}
.overlay__box .close-x {
    /* Same control as the page close, re-anchored inside the box. */
    top: clamp(0.9rem, 2vw, 1.2rem);
    right: clamp(0.9rem, 2vw, 1.2rem);
    width: clamp(2.2rem, 3vw, 2.6rem);
}
.overlay__box .actions { margin-top: 0.3rem; }
.overlay__box .btn { font-size: clamp(0.95rem, 1.4vw, 1.15rem); }
