/* nine71 — micro1-inspired dark identity */

* { margin: 0; padding: 0; box-sizing: border-box; }

:root {
    --bg: #111212;
    /* Sampled off the reference rather than chosen: it builds surfaces from
       flat near-greys, not white alphas, and nothing on it is pure white. */
    --surface: #191919;          /* card fill */
    --surface-2: #202020;        /* card fill on hover — fill lifts, border does not */
    --border: #313131;
    --border-2: #3d3d3d;
    --nav-border: #2a2a2a;
    --brand: #7065f0;
    --brand-light: #9fa3fc;
    --brand-lighter: #caccfd;
    /* Every forward chevron is this gradient, never a flat colour — it is
       the only saturated moment in the whole UI. */
    --chev-1: #DDDCFA;
    --chev-2: #6F66E8;
    --light: #ebebf4;
    /* The gold Signal already uses for detected drops. Carried onto the landing
       page so the same colour means the same thing in the product and in the
       marketing: violet is the crowd, gold is the moment. */
    --gold: #d8a13a;
    --gold-soft: rgba(216, 161, 58, 0.14);
    --text: #E8E8E8;    /* primary. NOT #fff — only the hero H1 is white */
    --text-2: #A0A0A0;  /* muted body, subtitles, legal */
    --text-3: #767676;  /* column headings, smallest labels */
    --font: 'Outfit', -apple-system, BlinkMacSystemFont, sans-serif;
    /* The wordmark is an SVG (site/img/nine71-wordmark-white.svg), set in
       Coolvetica and converted to outlines. It is NOT live text: Coolvetica's
       licence permits "logo" and "web page (not embedded)" but forbids
       "web page (embedded)", so it cannot ship as a webfont. Outfit remains the
       body typeface. These tokens only affect the module label beside the mark. */
    --logo-tracking: -0.045em;
    --container: 1200px;
    --pad: 2rem;
}

html { scroll-behavior: smooth; }

body {
    font-family: var(--font);
    background: var(--bg);
    color: var(--text);
    line-height: 1.6;
    font-weight: 300;
    -webkit-font-smoothing: antialiased;
    overflow-x: hidden;
}

.container { max-width: var(--container); margin: 0 auto; padding: 0 var(--pad); }

/* Layout for the wordmark link/box. The mark itself is the SVG inside it, so
   there is nothing here to set a face or a weight on any more - which is also how
   the old 600/700/900 drift across four surfaces became impossible. */
.logo-wordmark {
    display: inline-block;
    line-height: 0;
    color: var(--text);
    user-select: none;
    text-decoration: none;
}

/* ---------- buttons ---------- */
.btn {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    border: none;
    border-radius: 6px;
    padding: 0.8rem 1.5rem;
    font: 500 0.95rem var(--font);
    cursor: pointer;
    text-decoration: none;
    transition: background 140ms ease, border-color 140ms ease, transform 140ms ease;
}
.btn-small { padding: 0.55rem 1.1rem; font-size: 0.88rem; }
.btn-light { background: #E8E8E8; color: #121212; }
.btn-light:hover { background: var(--brand-lighter); }
.btn-ghost {
    background: transparent;
    color: var(--text);
    border: 1px solid var(--border-2);
}
.btn-ghost:hover { border-color: var(--brand-light); color: var(--brand-lighter); }
.btn-arrow { display: inline-flex; align-items: center; }
.btn-light .btn-arrow { color: var(--brand); }

/* ---------- nav ---------- */
.nav {
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 100;
    transition: background 200ms ease, border-color 200ms ease;
    border-bottom: 1px solid transparent;
}
.nav.scrolled {
    background: rgba(17, 18, 18, 0.78);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    border-bottom-color: var(--border);
}
/* Three columns with the wordmark centred: links left, brand middle, one CTA
   right. Deliberately not a logo-left bar - the centred mark is what makes the
   header read as a masthead instead of an app chrome strip. */
.nav-inner {
    max-width: var(--container);
    margin: 0 auto;
    padding: 1.4rem var(--pad);
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 1.5rem;
}
.nav-logo {
    font-size: 1.2rem;
    line-height: 1;
    grid-column: 2;
    justify-self: center;
    color: #fff;
}
.nav-links {
    grid-column: 1;
    grid-row: 1;
    display: flex;
    gap: 1.9rem;
    justify-self: start;
}
.nav-cta { grid-column: 3; justify-self: end; }
.nav-links a {
    color: var(--text-2);
    text-decoration: none;
    font-size: 0.95rem;
    font-weight: 400;
    transition: color 140ms ease;
}
.nav-links a:hover { color: var(--text); }

/* ---------- hero ---------- */
/* The hero is atmosphere plus one sentence, and it stays that way. A generated
   energy curve was tried here and pulled back out: everything that competes
   with the headline goes, which is the same reason there is no second CTA and
   no subtitle. Depth comes from layered gradients and grain, which cannot look
   cheap the way a drawn crowd does at 2x. The charts below are where the data
   belongs. */
.hero {
    position: relative;
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    /* Asymmetric on purpose: the hero centres its content between these two
       edges, so the taller bottom pad is what lifts the headline above the
       optical middle and off the ridgeline in the photograph. */
    padding: 7.5rem var(--pad) 19vh;
    /* Four layers, because a single gradient on near-black reads as flat: a
       violet bloom low and off-centre, a cool counter-tint on the opposite
       side, a soft top light behind the headline, and the base ramp. Values are
       higher than they look like they should be - at these luminances anything
       subtler disappears into the black entirely, which is what the first pass
       got wrong. */
    /* Dull sunset, sitting BEHIND the hero PNG rather than beside it: a
       desaturated slate blue overhead falling through dusty peach at the
       horizon and out into the page background. Saturation is deliberately
       low — a vivid sunset fights the photograph on top of it. */
    background: var(--bg);
}
.hero-inner {
    position: relative;
    z-index: 5;
    text-align: center;
    max-width: 60rem;
}
.hero-heading {
    font-size: clamp(2.5rem, 5.9vw, 5.0rem);
    font-weight: 300;
    letter-spacing: -0.035em;
    line-height: 1.04;
    color: #fff;
}
.hero-sub {
    margin: 1.9rem auto 0;
    max-width: 34rem;
    color: var(--text-2);
    font-size: 1.05rem;
    font-weight: 300;
    line-height: 1.6;
}
.hero-ctas {
    /* There is no subtitle between the headline and the button, so this gap is
       the only thing separating them. It was 3.4rem against a 6.1rem headline;
       the headline is smaller now and the same gap left the CTA stranded. */
    margin-top: 2.5rem;
    display: flex;
    gap: 0.9rem;
    justify-content: center;
    flex-wrap: wrap;
}

/* Scroll cue: a hairline, not an animated chevron. */
.hero-cue {
    position: absolute;
    bottom: 2.6rem; left: 50%;
    transform: translateX(-50%);
    width: 1px; height: 3.4rem;
    background: linear-gradient(180deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.28) 100%);
    z-index: 5;
}
.hero-noise {
    position: absolute;
    inset: 0;
    z-index: 3;
    opacity: 0.4;
    pointer-events: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.05 0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- sections ---------- */
.section { padding: 7.5rem 0; position: relative; }

/* The nav is fixed, so an in-page anchor otherwise lands with the section's
   heading underneath it. Applied to the anchor targets rather than to .section,
   because the hero is not one and does not want the offset. */
#intelligence, #product, #charts, #research, #contact { scroll-margin-top: 6rem; }

.section-head { text-align: center; max-width: 46rem; margin: 0 auto 4rem; }

.heading-gradient {
    font-size: clamp(2rem, 3.6vw, 3rem);
    font-weight: 400;
    letter-spacing: -0.02em;
    line-height: 1.25;
    background-image: linear-gradient(180deg, #fff, #999);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
}
.section-sub {
    margin-top: 1.1rem;
    color: var(--text-2);
    font-size: 1.05rem;
}

/* cards */
.cards {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1.2rem;
}
.card {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0;
    padding: 1.9rem 1.8rem;
    text-decoration: none;
    color: inherit;
    transition: border-color 160ms ease, background 160ms ease, transform 160ms ease;
}
a.card:hover {
    border-color: var(--brand-light);
    background: var(--surface-2);
    transform: translateY(-3px);
}
.card-top { display: flex; }
.badge {
    font-size: 0.74rem;
    font-weight: 500;
    letter-spacing: 0.03em;
    border-radius: 100px;
    padding: 0.22rem 0.7rem;
    border: 1px solid var(--border);
    color: var(--text-2);
}
.badge-live { color: var(--brand-lighter); border-color: rgba(112, 101, 240, 0.55); background: rgba(112, 101, 240, 0.14); }
.badge-private { color: var(--text-2); background: rgba(255, 255, 255, 0.05); }
.badge-soon { color: var(--text-3); border-style: dashed; }
.card-title { font-size: 1.35rem; font-weight: 500; letter-spacing: -0.01em; }
.card-desc { color: var(--text-2); font-size: 0.95rem; flex: 1; }
.card-go {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--brand-light);
    font-size: 0.9rem;
    font-weight: 500;
}
a.card:hover .card-go { color: var(--brand-lighter); }
.card-go-muted { color: var(--text-3); }

/* light statement section */
.section-light {
    background: var(--light);
    color: #121212;
}
.statement { max-width: 52rem; margin: 0 auto; text-align: center; }
.statement-heading {
    font-size: clamp(2.2rem, 4.4vw, 3.6rem);
    font-weight: 400;
    letter-spacing: -0.02em;
    line-height: 1.2;
    color: #121212;
}
.statement-text {
    margin: 1.4rem auto 0;
    max-width: 42rem;
    color: rgba(18, 18, 18, 0.65);
    font-size: 1.12rem;
}

/* contact */
.section-contact { padding-bottom: 6rem; }
.contact-wrap { text-align: center; max-width: 44rem; margin: 0 auto; }
.contact-wrap .btn { margin-top: 2rem; }

/* ---------- footer ---------- */
.footer {
    border-top: 1px solid var(--border);
    padding-top: 4.5rem;
    overflow: hidden;
    position: relative;
}
.footer-content {
    display: flex;
    justify-content: space-between;
    gap: 3rem;
    flex-wrap: wrap;
    padding-bottom: 3rem;
}
.footer-logo { font-size: 1.4rem; margin-bottom: 0.6rem; }
.footer-tagline { color: var(--text-2); font-size: 0.92rem; max-width: 18rem; }
.footer-links { display: flex; gap: 4rem; }
.footer-column { display: flex; flex-direction: column; gap: 0.55rem; }
.footer-column h4 {
    font-size: 0.8rem;
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-3);
    margin-bottom: 0.35rem;
}
.footer-column a {
    color: var(--text-2);
    text-decoration: none;
    font-size: 0.94rem;
    transition: color 140ms ease;
}
.footer-column a:hover { color: var(--text); }
.footer-bottom {
    border-top: 1px solid var(--border);
    padding: 1.4rem 0;
    color: var(--text-3);
    font-size: 0.85rem;
}



/* ---------- nav dropdown ---------- */
/* Built on <details>, so it opens without JavaScript and closes on Escape.
   script.js only adds the two behaviours the element does not have: closing on
   an outside click, and closing after you follow a link inside it. */
.nav-dd { position: relative; }
.nav-dd summary {
    display: inline-flex;
    align-items: center;
    gap: 0.32rem;
    color: var(--text-2);
    font-size: 0.95rem;
    cursor: pointer;
    list-style: none;
    transition: color 140ms ease;
}
/* Both are needed: WebKit uses the pseudo-element, everyone else `list-style`. */
.nav-dd summary::-webkit-details-marker { display: none; }
.nav-dd summary:hover,
.nav-dd[open] summary { color: var(--text); }
.nav-dd-chev { transition: transform 160ms ease; }
.nav-dd[open] .nav-dd-chev { transform: rotate(180deg); }
.nav-dd-menu {
    position: absolute;
    top: calc(100% + 0.9rem);
    left: -0.9rem;
    width: 20rem;
    display: flex;
    flex-direction: column;
    padding: 0.5rem;
    border: 1px solid var(--border);
    border-radius: 16px;
    background: rgba(24, 24, 28, 0.94);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    box-shadow: 0 20px 50px rgba(0, 0, 0, 0.45);
}
.nav-dd-menu a {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 0.7rem 0.8rem;
    border-radius: 11px;
    text-decoration: none;
    transition: background 140ms ease;
}
.nav-dd-menu a:hover { background: var(--surface); }
.nav-dd-title { color: var(--text); font-size: 0.95rem; font-weight: 500; }
.nav-dd-desc { color: var(--text-3); font-size: 0.83rem; line-height: 1.45; }
.nav-dd-tag {
    font-size: 0.68rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-3);
    border: 1px dashed var(--border-2);
    border-radius: 100px;
    padding: 0.05rem 0.42rem;
    margin-left: 0.3rem;
}

/* ---------- corpus strip ---------- */
.strip {
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    background: rgba(255, 255, 255, 0.015);
}
.strip-row {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 1rem;
    padding: 2.1rem 0;
}
.strip-item {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    align-items: center;
    text-align: center;
}
.strip-num {
    font-size: clamp(1.5rem, 2.6vw, 2.1rem);
    font-weight: 400;
    letter-spacing: -0.02em;
    color: #fff;
    line-height: 1.1;
    /* Tabular so the five figures sit on a shared rhythm rather than jittering
       by digit width. */
    font-variant-numeric: tabular-nums;
}
.strip-cap {
    font-size: 0.78rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-3);
}

/* ---------- product card artwork ---------- */
/* Small generated graphics rather than screenshots: at card width a screenshot
   is an unreadable grey rectangle, while a mark of the right shape still says
   which tool it is. */
.card-art {
    height: 78px;
    margin-bottom: 0.4rem;
    border-radius: 12px;
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid var(--border);
    overflow: hidden;
    color: var(--brand-light);
}
.card-art svg { width: 100%; height: 100%; display: block; }
.card-art .art-dot { fill: var(--gold); }
.card-art-foundry {
    display: flex;
    align-items: flex-end;
    gap: 7px;
    padding: 12px 16px;
}
.art-bar {
    flex: 1;
    height: var(--h);
    border-radius: 3px;
    background: linear-gradient(180deg, var(--brand-light), rgba(112, 101, 240, 0.25));
}
.art-bar-hot { background: linear-gradient(180deg, var(--gold), rgba(216, 161, 58, 0.2)); }
.card-art-atlas { color: rgba(255, 255, 255, 0.28); }
a.card:hover .card-art { border-color: rgba(112, 101, 240, 0.4); }

/* ---------- product screenshots ---------- */
.section-product { background: linear-gradient(180deg, rgba(112, 101, 240, 0.05), transparent 60%); }

/* CSS-only tabs: the radios hold the state, so the panels work with JS off.
   Kept off-screen rather than display:none so they stay keyboard-focusable and
   the labels remain real controls. */
.shots input { position: absolute; opacity: 0; pointer-events: none; }
.shot-tabs, .chart-tabs {
    display: flex;
    gap: 0.4rem;
    justify-content: center;
    margin-bottom: 1.8rem;
    flex-wrap: wrap;
}
.shot-tabs label, .chart-tabs label {
    padding: 0.5rem 1.15rem;
    border-radius: 100px;
    border: 1px solid var(--border);
    color: var(--text-2);
    font-size: 0.9rem;
    cursor: pointer;
    transition: background 140ms ease, color 140ms ease, border-color 140ms ease;
}
.shot-tabs label:hover, .chart-tabs label:hover { color: var(--text); border-color: var(--border-2); }
/* One rule per tab, because CSS cannot say "the label whose `for` matches the
   checked input" - :has() could, but this ships to older Safari too. */
#shot-overview:checked ~ .shot-tabs label[for="shot-overview"],
#shot-moments:checked ~ .shot-tabs label[for="shot-moments"],
#shot-tracks:checked ~ .shot-tabs label[for="shot-tracks"] {
    background: #fff;
    border-color: #fff;
    color: #121212;
}
.shot-frame {
    border: 1px solid var(--border);
    border-radius: 18px;
    overflow: hidden;
    background: #0d0d0f;
    box-shadow: 0 40px 90px rgba(0, 0, 0, 0.5);
}
.shot-chrome {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--border);
    background: rgba(255, 255, 255, 0.03);
}
.shot-dot {
    width: 9px; height: 9px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.16);
}
.shot-stage { position: relative; }
.shot { display: none; margin: 0; }
#shot-overview:checked ~ .shot-frame .shot[data-shot="overview"],
#shot-moments:checked  ~ .shot-frame .shot[data-shot="moments"],
#shot-tracks:checked   ~ .shot-frame .shot[data-shot="tracks"] { display: block; }
.shot img {
    display: block;
    width: 100%;
    height: auto;
}
.shot figcaption {
    padding: 1rem 1.3rem 1.2rem;
    border-top: 1px solid var(--border);
    color: var(--text-2);
    font-size: 0.9rem;
}
.shot-note {
    margin-top: 1.4rem;
    text-align: center;
    color: var(--text-3);
    font-size: 0.88rem;
}
/* A button that has to read as a link, because it opens a mail client rather
   than navigating - so it must not be an <a href="#">. */
.linkish {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    color: var(--brand-light);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 3px;
}
.linkish:hover { color: var(--brand-lighter); }

/* ---------- charts ---------- */
.section-charts { border-top: 1px solid var(--border); }
.charts input { position: absolute; opacity: 0; pointer-events: none; }
#chart-moments:checked ~ .chart-tabs label[for="chart-moments"],
#chart-artists:checked ~ .chart-tabs label[for="chart-artists"],
#chart-crossover:checked ~ .chart-tabs label[for="chart-crossover"] {
    background: #fff;
    border-color: #fff;
    color: #121212;
}
.chart-panel { display: none; }
#chart-moments:checked ~ .chart-panel[data-chart="moments"],
#chart-artists:checked ~ .chart-panel[data-chart="artists"],
#chart-crossover:checked ~ .chart-panel[data-chart="crossover"] { display: block; }

.chart-lede {
    max-width: 46rem;
    margin: 0 auto 2.2rem;
    text-align: center;
    color: var(--text-2);
    font-size: 0.95rem;
}
.chart-list {
    list-style: none;
    border: 1px solid var(--border);
    border-radius: 18px;
    overflow: hidden;
    background: rgba(255, 255, 255, 0.02);
}
.chart-row {
    display: grid;
    /* rank · tile · text · meta · bar · value */
    grid-template-columns: 2.6rem 40px minmax(0, 1fr) 11rem 8rem 4rem;
    align-items: center;
    gap: 1rem;
    padding: 0.85rem 1.4rem;
    border-bottom: 1px solid var(--border);
    transition: background 140ms ease;
}
.chart-row:last-child { border-bottom: none; }
.chart-row:hover { background: rgba(255, 255, 255, 0.03); }
.chart-rank {
    font-size: 1.05rem;
    font-weight: 500;
    color: var(--text-3);
    font-variant-numeric: tabular-nums;
    text-align: right;
}
.chart-row:nth-child(-n+3) .chart-rank { color: var(--gold); }
.chart-tile {
    /* Sized by its grid track, not fixed: the track narrows on smaller screens
       and a hard 40px tile overflowed it into the title text. */
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
    border-radius: 9px;
    /* Fallback for rows with no curve: the tile is the row's own score, violet
       at the top of the chart cooling toward the bottom. */
    background:
        linear-gradient(160deg,
            rgba(112, 101, 240, calc(0.25 + 0.75 * var(--fill))),
            rgba(112, 101, 240, calc(0.06 + 0.2 * var(--fill))));
    border: 1px solid var(--border);
    overflow: hidden;
}
/* The sparkline version carries its own picture, so it drops the heat fill. */
.chart-tile-spark { background: rgba(255, 255, 255, 0.04); }
.chart-tile-spark svg { display: block; width: 100%; height: 100%; }
.spark-area { fill: rgba(112, 101, 240, 0.3); }
.spark-line {
    fill: none;
    stroke: var(--brand-light);
    stroke-width: 1.4;
    stroke-linejoin: round;
    /* Without this the horizontal stretch from preserveAspectRatio="none" also
       stretches the stroke, and the line reads thicker than it is. */
    vector-effect: non-scaling-stroke;
}
.chart-row:nth-child(-n+3) .spark-line { stroke: var(--gold); }
.chart-row:nth-child(-n+3) .spark-area { fill: rgba(216, 161, 58, 0.22); }
.chart-text { display: flex; flex-direction: column; gap: 0.12rem; min-width: 0; }
.chart-title {
    color: var(--text);
    font-size: 0.98rem;
    /* min-width:0 on the grid item plus this is what actually clips a long
       title. A max-width alone does not hold in a grid track. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chart-sub, .chart-meta {
    color: var(--text-3);
    font-size: 0.85rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chart-unnamed { color: var(--text-3); font-style: italic; }
.chart-bar {
    height: 5px;
    border-radius: 100px;
    background: rgba(255, 255, 255, 0.07);
    overflow: hidden;
}
.chart-bar span {
    display: block;
    height: 100%;
    border-radius: 100px;
    background: linear-gradient(90deg, rgba(112, 101, 240, 0.55), var(--brand-light));
}
.chart-value {
    text-align: right;
    color: var(--text);
    font-size: 0.95rem;
    font-variant-numeric: tabular-nums;
}
.chart-empty { padding: 2rem; text-align: center; color: var(--text-3); }
/* Crossover has two rows and long venue lists; it needs neither the bar nor a
   tile - there is no single moment behind a crossover to draw a curve for, and
   an empty square in that slot reads as a failed image load. */
.chart-list-wide .chart-row { grid-template-columns: 2.6rem minmax(0, 1fr) 18rem 5rem; }
.chart-list-wide .chart-bar { display: none; }

.tag {
    display: inline-block;
    margin-left: 0.5rem;
    padding: 0.08rem 0.5rem;
    border-radius: 100px;
    font-size: 0.7rem;
    letter-spacing: 0.03em;
    vertical-align: 1px;
}
.tag-own { color: var(--brand-lighter); background: rgba(112, 101, 240, 0.16); border: 1px solid rgba(112, 101, 240, 0.4); }
.tag-tent { color: var(--gold); background: var(--gold-soft); border: 1px solid rgba(216, 161, 58, 0.4); }

/* ---------- research ---------- */
.research-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1.2rem;
}
.research-card {
    display: flex;
    flex-direction: column;
    gap: 0.8rem;
    padding: 1.9rem 1.8rem;
    border: 1px solid var(--border);
    border-radius: 0;
    background: var(--surface);
}
.rs-kicker {
    align-self: flex-start;
    font-size: 0.72rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-3);
    border: 1px solid var(--border);
    border-radius: 100px;
    padding: 0.2rem 0.65rem;
}
.rs-title { font-size: 1.15rem; font-weight: 500; letter-spacing: -0.01em; line-height: 1.35; }
.rs-desc { color: var(--text-2); font-size: 0.92rem; flex: 1; }
.rs-stats {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    padding-top: 0.4rem;
    border-top: 1px solid var(--border);
}
.rs-stats li { display: flex; flex-direction: column; gap: 0.1rem; }
.rs-num {
    color: #fff;
    font-size: 1.25rem;
    font-weight: 400;
    letter-spacing: -0.01em;
    font-variant-numeric: tabular-nums;
}
.rs-cap { color: var(--text-3); font-size: 0.83rem; line-height: 1.45; }
.rs-note {
    color: var(--text-3);
    font-size: 0.83rem;
    font-style: italic;
    border-left: 2px solid rgba(216, 161, 58, 0.5);
    padding-left: 0.7rem;
}
.research-foot {
    margin-top: 2rem;
    text-align: center;
    color: var(--text-3);
    font-size: 0.85rem;
}
.research-foot code {
    font-size: 0.82rem;
    color: var(--text-2);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 0.1rem 0.4rem;
}

/* ---------- contact ---------- */
.contact-heading { color: #fff; }
.contact-wrap .section-sub { margin-top: 1.3rem; }

/* Fade-in on scroll.
 *
 * Gated on .js, which script.js sets on <html> as its first act. Without that
 * gate, these elements start at opacity 0 and only JS can reveal them - so if
 * script.js fails to load or is blocked, the products, the statement and the
 * contact section are permanently invisible and the page looks broken. Content
 * must not depend on an animation to exist. */
.js .section-head,
.js .card,
.js .research-card,
.js .shots,
.js .charts,
.js .statement,
.js .contact-wrap {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity 600ms ease, transform 600ms ease;
}
.js .fade-in { opacity: 1 !important; transform: translateY(0) !important; }

/* Someone who asked for less motion gets the content, not the animation. */
@media (prefers-reduced-motion: reduce) {
    .js .section-head,
    .js .card,
    .js .statement,
    .js .contact-wrap {
        opacity: 1;
        transform: none;
        transition: none;
    }
}

/* ---------- responsive ---------- */
/* The chart row sheds columns before it sheds rows. Meta and the bar are
   context; the rank, the name and the number are the chart. */
@media (max-width: 1080px) {
    .chart-row { grid-template-columns: 2.2rem 34px minmax(0, 1fr) 7rem 3.6rem; }
    .chart-meta { display: none; }
    .chart-list-wide .chart-row { grid-template-columns: 2.2rem minmax(0, 1fr) 12rem 4.4rem; }
    .research-grid { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 900px) {
    .cards { grid-template-columns: 1fr; }
    .research-grid { grid-template-columns: 1fr; }
    .footer-links { gap: 2.5rem; }
    .section { padding: 5rem 0; }

    /* Five across becomes three-then-two rather than a scroll: the corpus is
       the page's credibility and should not need swiping to read. */
    .strip-row { grid-template-columns: repeat(3, 1fr); row-gap: 1.6rem; }
    .strip-item:nth-child(4) { grid-column: 1 / 2; }

    .chart-row { grid-template-columns: 1.9rem 30px minmax(0, 1fr) 3.4rem; gap: 0.7rem; padding: 0.8rem 1rem; }

    /* The title truncates on wide screens, where there is room for one line and
       a tag. On a phone that ellipsis ate the "own record" tag - which is the
       most interesting thing in the row - so here it wraps instead. */
    .chart-title { white-space: normal; }
    .chart-bar { display: none; }
    .chart-list-wide .chart-row { grid-template-columns: 1.9rem minmax(0, 1fr) 4.2rem; }
    .chart-list-wide .chart-meta { display: block; grid-column: 3 / -1; }

    /* Anchored to the nav row rather than to the link, which on a phone sits
       far enough left that a 20rem panel would hang off the screen. */
    .nav-dd-menu { left: 0; width: min(20rem, calc(100vw - 2 * var(--pad))); }

    /* The old 42vh bottom padding reserved room for the illustrated crowd that
       used to occupy the lower half of the hero. That image is gone, so the same
       padding just left a screen of dead space under the headline. */
    .hero { padding: 7rem var(--pad) 6rem; }
    .hero-heading { font-size: clamp(2.4rem, 10vw, 3.4rem); }
    .hero-sub { font-size: 1rem; }

    /* The headline carries a hard <br> that balances it into two lines on a
       wide screen. On a phone the first line wraps anyway, so the break left
       "layer" orphaned on a line of its own. Let it flow instead. */
    .hero-heading br { display: none; }

    /* Two rows instead of hiding the links: wordmark centred on top, links
       centred underneath. Previously .nav-links was display:none on mobile,
       which silently removed the only route to /tracks/ and /sets/ from the
       header - and I had just added a third link into it. */
    .nav-inner {
        grid-template-columns: 1fr auto;
        row-gap: 0.85rem;
        padding: 1rem var(--pad);
    }
    .nav-logo { grid-column: 1; grid-row: 1; justify-self: start; }
    .nav-cta { grid-column: 2; grid-row: 1; }
    .nav-links {
        grid-column: 1 / -1;
        grid-row: 2;
        justify-self: start;
        gap: 1.4rem;
        font-size: 0.9rem;
    }
}

@media (max-width: 420px) {
    .nav-links { gap: 1.05rem; font-size: 0.84rem; }
    .hero { padding-top: 8.5rem; }
}

/* The wordmark is an SVG, not text. Coolvetica's licence permits "logo" and
   "web page (not embedded)" but forbids "web page (embedded)", so it cannot ship
   as an @font-face webfont — outlines are the licensed route. Height in em so the
   existing font-size rules still control its scale; 0.78 matches the optical size
   the live text had, since a glyph's ink is shorter than its em box. */
.wordmark-img { display: block; height: 0.78em; width: auto; }

.nav-logo .wordmark-img, .footer-logo .wordmark-img { height: 0.72em; }


/* ==========================================================================
   Reference pass — appended as overrides.

   Everything below refines the base above toward the micro1 reference. It is
   appended rather than spliced into the rules it modifies, so a bad edit here
   can never take a working rule with it.
   ========================================================================== */

/* ---------- hero ----------
   Four layers, in this order, and nothing else:

     1. gradient   the sky, filling the hero
     2. image      the photograph, sitting on the bottom edge
     3. grain      one texture over both
     4. content    headline and CTA

   An earlier version stacked a white wash, a flat darkener, a brightness
   filter and a full-height mask on top of these. That is what washed the
   photograph out. If this needs adjusting, change the gradient or the image -
   do not add another layer.

   The supplied PNG carried a 29% band of solid white above the horizon, which
   landed on the dark sky as a hard edge. That band is cropped out of the file
   itself; the short fade below only softens the join. */

.hero-media {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
}

.hero-sky {
    position: absolute;
    inset: 0;
    /* Deep blue overhead, opening into warm light exactly where the photograph
       begins - the join only disappears if the gradient arrives at the image's
       own tone. */
    background: linear-gradient(180deg,
        #2E343C 0%,
        #363D46 10%,
        #3C444D 20%,
        #444B53 30%,
        #4A5057 40%,
        #4D5052 50%,
        #4F4D4D 60%,
        #4E4845 70%,
        #4A423B 80%,
        #443A33 90%,
        #372F29 100%);
}

.hero-img {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: auto;
    display: block;
    -webkit-mask-image: radial-gradient(125% 118% at 50% 100%,
        #000 56%, rgba(0, 0, 0, 0.6) 80%, transparent 100%);
    mask-image: radial-gradient(125% 118% at 50% 100%,
        #000 56%, rgba(0, 0, 0, 0.6) 80%, transparent 100%);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
}

.hero-wash {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(58% 44% at 86% 97%,
        rgba(255, 247, 236, 0.22) 0%,
        rgba(255, 245, 232, 0.11) 38%,
        rgba(255, 245, 232, 0.04) 60%,
        transparent 76%);
}

.hero-grain {
    position: absolute;
    inset: 0;
    opacity: 0.32;
    background-image: url("/img/grain.png");
    background-repeat: repeat;
    background-size: 200px;
    pointer-events: none;
}


/* ---------- nav ---------- */
.nav {
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 100;
    transition: background 200ms ease, border-color 200ms ease;
    border-bottom: 1px solid transparent;
}
.nav.scrolled {
    background: rgba(17, 18, 18, 0.78);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    border-bottom-color: var(--border);
}
/* Three columns with the wordmark centred: links left, brand middle, one CTA
   right. Deliberately not a logo-left bar - the centred mark is what makes the
   header read as a masthead instead of an app chrome strip. */
.nav-inner {
    max-width: var(--container);
    margin: 0 auto;
    padding: 1.4rem var(--pad);
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 1.5rem;
}
.nav-logo {
    font-size: 1.2rem;
    line-height: 1;
    grid-column: 2;
    justify-self: center;
    color: #fff;
}
.nav-links {
    grid-column: 1;
    grid-row: 1;
    display: flex;
    gap: 1.9rem;
    justify-self: start;
}
.nav-cta { grid-column: 3; justify-self: end; }
.nav-links a {
    color: var(--text-2);
    text-decoration: none;
    font-size: 0.95rem;
    font-weight: 400;
    transition: color 140ms ease;
}
.nav-links a:hover { color: var(--text); }

/* ---------- hero ---------- */
/* The hero is atmosphere plus one sentence, and it stays that way. A generated
   energy curve was tried here and pulled back out: everything that competes
   with the headline goes, which is the same reason there is no second CTA and
   no subtitle. Depth comes from layered gradients and grain, which cannot look
   cheap the way a drawn crowd does at 2x. The charts below are where the data
   belongs. */
.hero {
    position: relative;
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    /* Asymmetric on purpose: the hero centres its content between these two
       edges, so the taller bottom pad is what lifts the headline above the
       optical middle and off the ridgeline in the photograph. */
    padding: 7.5rem var(--pad) 19vh;
    /* Four layers, because a single gradient on near-black reads as flat: a
       violet bloom low and off-centre, a cool counter-tint on the opposite
       side, a soft top light behind the headline, and the base ramp. Values are
       higher than they look like they should be - at these luminances anything
       subtler disappears into the black entirely, which is what the first pass
       got wrong. */
    /* Dull sunset, sitting BEHIND the hero PNG rather than beside it: a
       desaturated slate blue overhead falling through dusty peach at the
       horizon and out into the page background. Saturation is deliberately
       low — a vivid sunset fights the photograph on top of it. */
    background: linear-gradient(180deg,
        #333B47 0%,
        #414855 15%,
        #5A585A 31%,
        #7C6959 46%,
        #8A6E56 56%,
        #6A5647 68%,
        #3E352F 80%,
        #1E1C1B 91%,
        var(--bg) 100%);
}
.hero-inner {
    position: relative;
    z-index: 5;
    text-align: center;
    max-width: 60rem;
}
.hero-heading {
    font-size: clamp(2.5rem, 5.9vw, 5.0rem);
    font-weight: 300;
    letter-spacing: -0.035em;
    line-height: 1.04;
    color: #fff;
}
.hero-sub {
    margin: 1.9rem auto 0;
    max-width: 34rem;
    color: var(--text-2);
    font-size: 1.05rem;
    font-weight: 300;
    line-height: 1.6;
}
.hero-ctas {
    /* There is no subtitle between the headline and the button, so this gap is
       the only thing separating them. It was 3.4rem against a 6.1rem headline;
       the headline is smaller now and the same gap left the CTA stranded. */
    margin-top: 2.5rem;
    display: flex;
    gap: 0.9rem;
    justify-content: center;
    flex-wrap: wrap;
}

/* Scroll cue: a hairline, not an animated chevron. */
.hero-cue {
    position: absolute;
    bottom: 2.6rem; left: 50%;
    transform: translateX(-50%);
    width: 1px; height: 3.4rem;
    background: linear-gradient(180deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.28) 100%);
    z-index: 5;
}
.hero-noise {
    position: absolute;
    inset: 0;
    z-index: 3;
    opacity: 0.4;
    pointer-events: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.05 0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- sections ---------- */
.section { padding: 7.5rem 0; position: relative; }

/* The nav is fixed, so an in-page anchor otherwise lands with the section's
   heading underneath it. Applied to the anchor targets rather than to .section,
   because the hero is not one and does not want the offset. */
#intelligence, #product, #charts, #research, #contact { scroll-margin-top: 6rem; }

.section-head { text-align: center; max-width: 46rem; margin: 0 auto 4rem; }

.heading-gradient {
    font-size: clamp(2rem, 3.6vw, 3rem);
    font-weight: 400;
    letter-spacing: -0.02em;
    line-height: 1.25;
    background-image: linear-gradient(180deg, #fff, #999);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
}
.section-sub {
    margin-top: 1.1rem;
    color: var(--text-2);
    font-size: 1.05rem;
}

/* cards */
.cards {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1.2rem;
}
.card {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0;
    padding: 1.9rem 1.8rem;
    text-decoration: none;
    color: inherit;
    transition: border-color 160ms ease, background 160ms ease, transform 160ms ease;
}
a.card:hover {
    border-color: var(--brand-light);
    background: var(--surface-2);
    transform: translateY(-3px);
}
.card-top { display: flex; }
.badge {
    font-size: 0.74rem;
    font-weight: 500;
    letter-spacing: 0.03em;
    border-radius: 100px;
    padding: 0.22rem 0.7rem;
    border: 1px solid var(--border);
    color: var(--text-2);
}
.badge-live { color: var(--brand-lighter); border-color: rgba(112, 101, 240, 0.55); background: rgba(112, 101, 240, 0.14); }
.badge-private { color: var(--text-2); background: rgba(255, 255, 255, 0.05); }
.badge-soon { color: var(--text-3); border-style: dashed; }
.card-title { font-size: 1.35rem; font-weight: 500; letter-spacing: -0.01em; }
.card-desc { color: var(--text-2); font-size: 0.95rem; flex: 1; }
.card-go {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--brand-light);
    font-size: 0.9rem;
    font-weight: 500;
}
a.card:hover .card-go { color: var(--brand-lighter); }
.card-go-muted { color: var(--text-3); }

/* light statement section */
.section-light {
    background: var(--light);
    color: #121212;
}
.statement { max-width: 52rem; margin: 0 auto; text-align: center; }
.statement-heading {
    font-size: clamp(2.2rem, 4.4vw, 3.6rem);
    font-weight: 400;
    letter-spacing: -0.02em;
    line-height: 1.2;
    color: #121212;
}
.statement-text {
    margin: 1.4rem auto 0;
    max-width: 42rem;
    color: rgba(18, 18, 18, 0.65);
    font-size: 1.12rem;
}

/* contact */
.section-contact { padding-bottom: 6rem; }
.contact-wrap { text-align: center; max-width: 44rem; margin: 0 auto; }
.contact-wrap .btn { margin-top: 2rem; }

/* ---------- footer ---------- */
.footer {
    border-top: 1px solid var(--border);
    padding-top: 4.5rem;
    overflow: hidden;
    position: relative;
}
.footer-content {
    display: flex;
    justify-content: space-between;
    gap: 3rem;
    flex-wrap: wrap;
    padding-bottom: 3rem;
}
.footer-logo { font-size: 1.4rem; margin-bottom: 0.6rem; }
.footer-tagline { color: var(--text-2); font-size: 0.92rem; max-width: 18rem; }
.footer-links { display: flex; gap: 4rem; }
.footer-column { display: flex; flex-direction: column; gap: 0.55rem; }
.footer-column h4 {
    font-size: 0.8rem;
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-3);
    margin-bottom: 0.35rem;
}
.footer-column a {
    color: var(--text-2);
    text-decoration: none;
    font-size: 0.94rem;
    transition: color 140ms ease;
}
.footer-column a:hover { color: var(--text); }
.footer-bottom {
    border-top: 1px solid var(--border);
    padding: 1.4rem 0;
    color: var(--text-3);
    font-size: 0.85rem;
}



/* ---------- nav dropdown ---------- */
/* Built on <details>, so it opens without JavaScript and closes on Escape.
   script.js only adds the two behaviours the element does not have: closing on
   an outside click, and closing after you follow a link inside it. */
.nav-dd { position: relative; }
.nav-dd summary {
    display: inline-flex;
    align-items: center;
    gap: 0.32rem;
    color: var(--text-2);
    font-size: 0.95rem;
    cursor: pointer;
    list-style: none;
    transition: color 140ms ease;
}
/* Both are needed: WebKit uses the pseudo-element, everyone else `list-style`. */
.nav-dd summary::-webkit-details-marker { display: none; }
.nav-dd summary:hover,
.nav-dd[open] summary { color: var(--text); }
.nav-dd-chev { transition: transform 160ms ease; }
.nav-dd[open] .nav-dd-chev { transform: rotate(180deg); }
.nav-dd-menu {
    position: absolute;
    top: calc(100% + 0.9rem);
    left: -0.9rem;
    width: 20rem;
    display: flex;
    flex-direction: column;
    padding: 0.5rem;
    border: 1px solid var(--border);
    border-radius: 16px;
    background: rgba(24, 24, 28, 0.94);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    box-shadow: 0 20px 50px rgba(0, 0, 0, 0.45);
}
.nav-dd-menu a {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 0.7rem 0.8rem;
    border-radius: 11px;
    text-decoration: none;
    transition: background 140ms ease;
}
.nav-dd-menu a:hover { background: var(--surface); }
.nav-dd-title { color: var(--text); font-size: 0.95rem; font-weight: 500; }
.nav-dd-desc { color: var(--text-3); font-size: 0.83rem; line-height: 1.45; }
.nav-dd-tag {
    font-size: 0.68rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-3);
    border: 1px dashed var(--border-2);
    border-radius: 100px;
    padding: 0.05rem 0.42rem;
    margin-left: 0.3rem;
}

/* ---------- corpus strip ---------- */
.strip {
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    background: rgba(255, 255, 255, 0.015);
}
.strip-row {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 1rem;
    padding: 2.1rem 0;
}
.strip-item {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    align-items: center;
    text-align: center;
}
.strip-num {
    font-size: clamp(1.5rem, 2.6vw, 2.1rem);
    font-weight: 400;
    letter-spacing: -0.02em;
    color: #fff;
    line-height: 1.1;
    /* Tabular so the five figures sit on a shared rhythm rather than jittering
       by digit width. */
    font-variant-numeric: tabular-nums;
}
.strip-cap {
    font-size: 0.78rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-3);
}

/* ---------- product card artwork ---------- */
/* Small generated graphics rather than screenshots: at card width a screenshot
   is an unreadable grey rectangle, while a mark of the right shape still says
   which tool it is. */
.card-art {
    height: 78px;
    margin-bottom: 0.4rem;
    border-radius: 12px;
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid var(--border);
    overflow: hidden;
    color: var(--brand-light);
}
.card-art svg { width: 100%; height: 100%; display: block; }
.card-art .art-dot { fill: var(--gold); }
.card-art-foundry {
    display: flex;
    align-items: flex-end;
    gap: 7px;
    padding: 12px 16px;
}
.art-bar {
    flex: 1;
    height: var(--h);
    border-radius: 3px;
    background: linear-gradient(180deg, var(--brand-light), rgba(112, 101, 240, 0.25));
}
.art-bar-hot { background: linear-gradient(180deg, var(--gold), rgba(216, 161, 58, 0.2)); }
.card-art-atlas { color: rgba(255, 255, 255, 0.28); }
a.card:hover .card-art { border-color: rgba(112, 101, 240, 0.4); }

/* ---------- product screenshots ---------- */
.section-product { background: linear-gradient(180deg, rgba(112, 101, 240, 0.05), transparent 60%); }

/* CSS-only tabs: the radios hold the state, so the panels work with JS off.
   Kept off-screen rather than display:none so they stay keyboard-focusable and
   the labels remain real controls. */
.shots input { position: absolute; opacity: 0; pointer-events: none; }
.shot-tabs, .chart-tabs {
    display: flex;
    gap: 0.4rem;
    justify-content: center;
    margin-bottom: 1.8rem;
    flex-wrap: wrap;
}
.shot-tabs label, .chart-tabs label {
    padding: 0.5rem 1.15rem;
    border-radius: 100px;
    border: 1px solid var(--border);
    color: var(--text-2);
    font-size: 0.9rem;
    cursor: pointer;
    transition: background 140ms ease, color 140ms ease, border-color 140ms ease;
}
.shot-tabs label:hover, .chart-tabs label:hover { color: var(--text); border-color: var(--border-2); }
/* One rule per tab, because CSS cannot say "the label whose `for` matches the
   checked input" - :has() could, but this ships to older Safari too. */
#shot-overview:checked ~ .shot-tabs label[for="shot-overview"],
#shot-moments:checked ~ .shot-tabs label[for="shot-moments"],
#shot-tracks:checked ~ .shot-tabs label[for="shot-tracks"] {
    background: #fff;
    border-color: #fff;
    color: #121212;
}
.shot-frame {
    border: 1px solid var(--border);
    border-radius: 18px;
    overflow: hidden;
    background: #0d0d0f;
    box-shadow: 0 40px 90px rgba(0, 0, 0, 0.5);
}
.shot-chrome {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--border);
    background: rgba(255, 255, 255, 0.03);
}
.shot-dot {
    width: 9px; height: 9px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.16);
}
.shot-stage { position: relative; }
.shot { display: none; margin: 0; }
#shot-overview:checked ~ .shot-frame .shot[data-shot="overview"],
#shot-moments:checked  ~ .shot-frame .shot[data-shot="moments"],
#shot-tracks:checked   ~ .shot-frame .shot[data-shot="tracks"] { display: block; }
.shot img {
    display: block;
    width: 100%;
    height: auto;
}
.shot figcaption {
    padding: 1rem 1.3rem 1.2rem;
    border-top: 1px solid var(--border);
    color: var(--text-2);
    font-size: 0.9rem;
}
.shot-note {
    margin-top: 1.4rem;
    text-align: center;
    color: var(--text-3);
    font-size: 0.88rem;
}
/* A button that has to read as a link, because it opens a mail client rather
   than navigating - so it must not be an <a href="#">. */
.linkish {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    color: var(--brand-light);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 3px;
}
.linkish:hover { color: var(--brand-lighter); }

/* ---------- charts ---------- */
.section-charts { border-top: 1px solid var(--border); }
.charts input { position: absolute; opacity: 0; pointer-events: none; }
#chart-moments:checked ~ .chart-tabs label[for="chart-moments"],
#chart-artists:checked ~ .chart-tabs label[for="chart-artists"],
#chart-crossover:checked ~ .chart-tabs label[for="chart-crossover"] {
    background: #fff;
    border-color: #fff;
    color: #121212;
}
.chart-panel { display: none; }
#chart-moments:checked ~ .chart-panel[data-chart="moments"],
#chart-artists:checked ~ .chart-panel[data-chart="artists"],
#chart-crossover:checked ~ .chart-panel[data-chart="crossover"] { display: block; }

.chart-lede {
    max-width: 46rem;
    margin: 0 auto 2.2rem;
    text-align: center;
    color: var(--text-2);
    font-size: 0.95rem;
}
.chart-list {
    list-style: none;
    border: 1px solid var(--border);
    border-radius: 18px;
    overflow: hidden;
    background: rgba(255, 255, 255, 0.02);
}
.chart-row {
    display: grid;
    /* rank · tile · text · meta · bar · value */
    grid-template-columns: 2.6rem 40px minmax(0, 1fr) 11rem 8rem 4rem;
    align-items: center;
    gap: 1rem;
    padding: 0.85rem 1.4rem;
    border-bottom: 1px solid var(--border);
    transition: background 140ms ease;
}
.chart-row:last-child { border-bottom: none; }
.chart-row:hover { background: rgba(255, 255, 255, 0.03); }
.chart-rank {
    font-size: 1.05rem;
    font-weight: 500;
    color: var(--text-3);
    font-variant-numeric: tabular-nums;
    text-align: right;
}
.chart-row:nth-child(-n+3) .chart-rank { color: var(--gold); }
.chart-tile {
    /* Sized by its grid track, not fixed: the track narrows on smaller screens
       and a hard 40px tile overflowed it into the title text. */
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
    border-radius: 9px;
    /* Fallback for rows with no curve: the tile is the row's own score, violet
       at the top of the chart cooling toward the bottom. */
    background:
        linear-gradient(160deg,
            rgba(112, 101, 240, calc(0.25 + 0.75 * var(--fill))),
            rgba(112, 101, 240, calc(0.06 + 0.2 * var(--fill))));
    border: 1px solid var(--border);
    overflow: hidden;
}
/* The sparkline version carries its own picture, so it drops the heat fill. */
.chart-tile-spark { background: rgba(255, 255, 255, 0.04); }
.chart-tile-spark svg { display: block; width: 100%; height: 100%; }
.spark-area { fill: rgba(112, 101, 240, 0.3); }
.spark-line {
    fill: none;
    stroke: var(--brand-light);
    stroke-width: 1.4;
    stroke-linejoin: round;
    /* Without this the horizontal stretch from preserveAspectRatio="none" also
       stretches the stroke, and the line reads thicker than it is. */
    vector-effect: non-scaling-stroke;
}
.chart-row:nth-child(-n+3) .spark-line { stroke: var(--gold); }
.chart-row:nth-child(-n+3) .spark-area { fill: rgba(216, 161, 58, 0.22); }
.chart-text { display: flex; flex-direction: column; gap: 0.12rem; min-width: 0; }
.chart-title {
    color: var(--text);
    font-size: 0.98rem;
    /* min-width:0 on the grid item plus this is what actually clips a long
       title. A max-width alone does not hold in a grid track. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chart-sub, .chart-meta {
    color: var(--text-3);
    font-size: 0.85rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chart-unnamed { color: var(--text-3); font-style: italic; }
.chart-bar {
    height: 5px;
    border-radius: 100px;
    background: rgba(255, 255, 255, 0.07);
    overflow: hidden;
}
.chart-bar span {
    display: block;
    height: 100%;
    border-radius: 100px;
    background: linear-gradient(90deg, rgba(112, 101, 240, 0.55), var(--brand-light));
}
.chart-value {
    text-align: right;
    color: var(--text);
    font-size: 0.95rem;
    font-variant-numeric: tabular-nums;
}
.chart-empty { padding: 2rem; text-align: center; color: var(--text-3); }
/* Crossover has two rows and long venue lists; it needs neither the bar nor a
   tile - there is no single moment behind a crossover to draw a curve for, and
   an empty square in that slot reads as a failed image load. */
.chart-list-wide .chart-row { grid-template-columns: 2.6rem minmax(0, 1fr) 18rem 5rem; }
.chart-list-wide .chart-bar { display: none; }

.tag {
    display: inline-block;
    margin-left: 0.5rem;
    padding: 0.08rem 0.5rem;
    border-radius: 100px;
    font-size: 0.7rem;
    letter-spacing: 0.03em;
    vertical-align: 1px;
}
.tag-own { color: var(--brand-lighter); background: rgba(112, 101, 240, 0.16); border: 1px solid rgba(112, 101, 240, 0.4); }
.tag-tent { color: var(--gold); background: var(--gold-soft); border: 1px solid rgba(216, 161, 58, 0.4); }

/* ---------- research ---------- */
.research-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1.2rem;
}
.research-card {
    display: flex;
    flex-direction: column;
    gap: 0.8rem;
    padding: 1.9rem 1.8rem;
    border: 1px solid var(--border);
    border-radius: 0;
    background: var(--surface);
}
.rs-kicker {
    align-self: flex-start;
    font-size: 0.72rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-3);
    border: 1px solid var(--border);
    border-radius: 100px;
    padding: 0.2rem 0.65rem;
}
.rs-title { font-size: 1.15rem; font-weight: 500; letter-spacing: -0.01em; line-height: 1.35; }
.rs-desc { color: var(--text-2); font-size: 0.92rem; flex: 1; }
.rs-stats {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    padding-top: 0.4rem;
    border-top: 1px solid var(--border);
}
.rs-stats li { display: flex; flex-direction: column; gap: 0.1rem; }
.rs-num {
    color: #fff;
    font-size: 1.25rem;
    font-weight: 400;
    letter-spacing: -0.01em;
    font-variant-numeric: tabular-nums;
}
.rs-cap { color: var(--text-3); font-size: 0.83rem; line-height: 1.45; }
.rs-note {
    color: var(--text-3);
    font-size: 0.83rem;
    font-style: italic;
    border-left: 2px solid rgba(216, 161, 58, 0.5);
    padding-left: 0.7rem;
}
.research-foot {
    margin-top: 2rem;
    text-align: center;
    color: var(--text-3);
    font-size: 0.85rem;
}
.research-foot code {
    font-size: 0.82rem;
    color: var(--text-2);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 0.1rem 0.4rem;
}

/* ---------- contact ---------- */
.contact-heading { color: #fff; }
.contact-wrap .section-sub { margin-top: 1.3rem; }

/* Fade-in on scroll.
 *
 * Gated on .js, which script.js sets on <html> as its first act. Without that
 * gate, these elements start at opacity 0 and only JS can reveal them - so if
 * script.js fails to load or is blocked, the products, the statement and the
 * contact section are permanently invisible and the page looks broken. Content
 * must not depend on an animation to exist. */
.js .section-head,
.js .card,
.js .research-card,
.js .shots,
.js .charts,
.js .statement,
.js .contact-wrap {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity 600ms ease, transform 600ms ease;
}
.js .fade-in { opacity: 1 !important; transform: translateY(0) !important; }

/* Someone who asked for less motion gets the content, not the animation. */
@media (prefers-reduced-motion: reduce) {
    .js .section-head,
    .js .card,
    .js .statement,
    .js .contact-wrap {
        opacity: 1;
        transform: none;
        transition: none;
    }
}

/* ---------- responsive ---------- */
/* The chart row sheds columns before it sheds rows. Meta and the bar are
   context; the rank, the name and the number are the chart. */
@media (max-width: 1080px) {
    .chart-row { grid-template-columns: 2.2rem 34px minmax(0, 1fr) 7rem 3.6rem; }
    .chart-meta { display: none; }
    .chart-list-wide .chart-row { grid-template-columns: 2.2rem minmax(0, 1fr) 12rem 4.4rem; }
    .research-grid { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 900px) {
    .cards { grid-template-columns: 1fr; }
    .research-grid { grid-template-columns: 1fr; }
    .footer-links { gap: 2.5rem; }
    .section { padding: 5rem 0; }

    /* Five across becomes three-then-two rather than a scroll: the corpus is
       the page's credibility and should not need swiping to read. */
    .strip-row { grid-template-columns: repeat(3, 1fr); row-gap: 1.6rem; }
    .strip-item:nth-child(4) { grid-column: 1 / 2; }

    .chart-row { grid-template-columns: 1.9rem 30px minmax(0, 1fr) 3.4rem; gap: 0.7rem; padding: 0.8rem 1rem; }

    /* The title truncates on wide screens, where there is room for one line and
       a tag. On a phone that ellipsis ate the "own record" tag - which is the
       most interesting thing in the row - so here it wraps instead. */
    .chart-title { white-space: normal; }
    .chart-bar { display: none; }
    .chart-list-wide .chart-row { grid-template-columns: 1.9rem minmax(0, 1fr) 4.2rem; }
    .chart-list-wide .chart-meta { display: block; grid-column: 3 / -1; }

    /* Anchored to the nav row rather than to the link, which on a phone sits
       far enough left that a 20rem panel would hang off the screen. */
    .nav-dd-menu { left: 0; width: min(20rem, calc(100vw - 2 * var(--pad))); }

    /* The old 42vh bottom padding reserved room for the illustrated crowd that
       used to occupy the lower half of the hero. That image is gone, so the same
       padding just left a screen of dead space under the headline. */
    .hero { padding: 7rem var(--pad) 6rem; }
    .hero-heading { font-size: clamp(2.4rem, 10vw, 3.4rem); }
    .hero-sub { font-size: 1rem; }

    /* The headline carries a hard <br> that balances it into two lines on a
       wide screen. On a phone the first line wraps anyway, so the break left
       "layer" orphaned on a line of its own. Let it flow instead. */
    .hero-heading br { display: none; }

    /* Two rows instead of hiding the links: wordmark centred on top, links
       centred underneath. Previously .nav-links was display:none on mobile,
       which silently removed the only route to /tracks/ and /sets/ from the
       header - and I had just added a third link into it. */
    .nav-inner {
        grid-template-columns: 1fr auto;
        row-gap: 0.85rem;
        padding: 1rem var(--pad);
    }
    .nav-logo { grid-column: 1; grid-row: 1; justify-self: start; }
    .nav-cta { grid-column: 2; grid-row: 1; }
    .nav-links {
        grid-column: 1 / -1;
        grid-row: 2;
        justify-self: start;
        gap: 1.4rem;
        font-size: 0.9rem;
    }
}

@media (max-width: 420px) {
    .nav-links { gap: 1.05rem; font-size: 0.84rem; }
    .hero { padding-top: 8.5rem; }
}

/* The wordmark is an SVG, not text. Coolvetica's licence permits "logo" and
   "web page (not embedded)" but forbids "web page (embedded)", so it cannot ship
   as an @font-face webfont — outlines are the licensed route. Height in em so the
   existing font-size rules still control its scale; 0.78 matches the optical size
   the live text had, since a glyph's ink is shorter than its em box. */
.wordmark-img { display: block; height: 0.78em; width: auto; }

.nav-logo .wordmark-img, .footer-logo .wordmark-img { height: 0.72em; }


/* ==========================================================================
   Reference pass — appended as overrides.

   Everything below refines the base above toward the micro1 reference. It is
   appended rather than spliced into the rules it modifies, so a bad edit here
   can never take a working rule with it.
   ========================================================================== */

/* The old .hero-photo / .hero-photo-blur implementation lived here. It was a
   competing version of the same hero and is gone; the four layers above are
   the only ones. */
.hero-noise { display: none; }
.hero-inner { z-index: 6; }
.hero-cue   { z-index: 6; }

/* ---------- nav ----------
   Two components sharing one piece of markup. At the top of the page: links
   stacked at the left, wordmark centred, CTA at the right, sitting directly on
   the hero with no container. Once scrolled: a narrow centred floating panel.

   The panel used to hide on scroll-down and return on scroll-up. It does not any
   more - the header is present at every scroll position, deliberately. The
   .nav-hidden rule is kept as a no-op guard: the class is never set, and leaving
   the declaration here means a stray `nav-hidden` from a cached script.js cannot
   translate the header off-screen with nothing left to bring it back. */

.nav { transition: transform 320ms ease; border-bottom: 0; }
.nav.nav-hidden { transform: none; }

.nav-inner {
    align-items: start;
    padding: 1.75rem var(--pad);
    border: 1px solid transparent;
    transition: max-width 260ms ease, padding 260ms ease,
                background 260ms ease, border-color 260ms ease;
}

.nav-links {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.42rem;          /* 26px line-to-line on the reference */
}

.nav-links a,
.nav-dd > summary { font-size: 0.875rem; line-height: 1.4; }

.nav-logo { padding-top: 0.15rem; }
.nav-logo .wordmark-img { height: 16px; }

/* --- scrolled: the floating panel --- */
.nav.scrolled { background: none; backdrop-filter: none; -webkit-backdrop-filter: none; }

.nav.scrolled .nav-inner {
    max-width: 38rem;   /* 608px — the reference panel is 609 */
    margin-top: 0.6rem;
    padding: 0.4rem 0.4rem 0.4rem 1.1rem;
    /* Translucent, not opaque — it lifts visibly over anything but flat black. */
    background: rgba(17, 18, 18, 0.55);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    border-color: var(--nav-border);
    border-radius: 10px;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 1.1rem;
}

.nav.scrolled .nav-logo { grid-column: 1; justify-self: start; padding-top: 0; }
.nav.scrolled .nav-logo .wordmark-img { height: 14px; }

.nav.scrolled .nav-links {
    grid-column: 2;
    justify-self: start;
    flex-direction: row;
    align-items: center;
    gap: 1.3rem;
}

.nav.scrolled .nav-cta { grid-column: 3; }
.nav.scrolled .nav-cta .btn { padding: 0.5rem 1rem; }
/* The chevron belongs to the hero chip only; inside the panel the button is
   plain, exactly as on the reference. */
.nav.scrolled .nav-cta .btn-arrow { display: none; }

/* ---------- gradient chevrons ----------
   Light-to-indigo, never flat - the one saturated moment in the UI.
   Done with an SVG paint server (the <defs> block at the top of each page)
   rather than a CSS mask: `stroke` accepts a url() reference, and masking a
   background through a glyph cannot colour a stroked path. */
.card-go svg path,
.stack-item .card-go svg path,
.btn .btn-arrow svg path { stroke: url(#chevGrad); }

/* Links in this family are not underlined on the reference. */
.card-go, .card-go:hover { text-decoration: none; }

/* ---------- aurora band ----------
   The reference's colour banner: a full-bleed grainy multicolour gradient with
   an inner panel inset 12px on all sides, so a 12px ring of raw gradient shows
   as a frame. Square corners — the frame is the whole effect. */
.aurora,
.ui-frame {
    position: relative;
    padding: 12px;
    background:
        linear-gradient(105deg,
            #8F8D93 0%,
            #8A7050 18%,
            #874342 38%,
            #614A90 62%,
            #413C89 80%,
            #7C7A83 100%);
    overflow: hidden;
}

.aurora::before,
.ui-frame::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0.55;
    mix-blend-mode: overlay;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
}

.aurora-inner {
    position: relative;
    z-index: 2;
    background: #292829;
    padding: clamp(2.5rem, 6vw, 4.5rem) var(--pad);
    text-align: center;
}

.aurora-inner .statement-heading { max-width: 40rem; margin: 0 auto; }

/* ---------- footer legal row ---------- */
.footer-bottom {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem 1.5rem;
}

.footer-legal { display: flex; flex-wrap: wrap; gap: 1.5rem; }
.footer-legal a { color: var(--text-3); transition: color 200ms ease; }
.footer-legal a:hover { color: var(--text); }

/* ---------- sub-page header ----------
   The reference pattern: a centred page title and subtitle over the dark
   ground, with the section headings beneath it LEFT-aligned. Concept titles
   are centred; content-index titles are not. */
.page-hero {
    padding: 11rem var(--pad) 4rem;
    text-align: center;
    position: relative;
    background: linear-gradient(180deg, #23252A 0%, #191A1D 42%, var(--bg) 100%);
}

.page-title {
    font-size: clamp(2.3rem, 4.4vw, 3.15rem);
    font-weight: 400;
    letter-spacing: -0.02em;
    line-height: 1.22;
    color: var(--text);
}

.page-sub {
    margin: 1.2rem auto 0;
    max-width: 42rem;
    color: var(--text-2);
    font-size: 1.125rem;
    line-height: 1.6;
}

/* ---------- the stacking product section ----------
   Copy scrolls on the left; the visual on the right is sticky and swaps to
   whichever product is level with the middle of the viewport. Blocks are sized
   in vh so the swap tracks scroll distance rather than text length.

   Below 900px the mechanism is dropped and each visual renders inline above
   its own copy. */

.stack {
    display: grid;
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
    gap: 4.5rem;
    align-items: start;
    text-align: left;
}

.stack-copy { display: grid; }

.stack-item {
    display: grid;
    gap: 0.9rem;
    justify-items: start;
    align-content: center;
    min-height: 72vh;
    padding: 2rem 0;
    opacity: 0.4;
    transition: opacity 450ms ease;
}

.stack-item.is-active { opacity: 1; }

.stack-title { font-size: 1.75rem; font-weight: 500; letter-spacing: -0.02em; }
.stack-desc  { margin: 0; max-width: 30rem; color: var(--text-2); font-size: 1.02rem; line-height: 1.6; }

.stack-media {
    position: sticky;
    top: 8.5rem;
    /* A definite height is load-bearing: the panels are absolutely positioned
       inside it, so without one the container collapses and each SVG lays out
       at its intrinsic size - which is what turned this section into two
       page-tall line drawings. */
    height: min(27rem, 58vh);
}

.stack-panel {
    position: absolute;
    inset: 0;
    opacity: 0;
    transform: scale(0.99) translateY(8px);
    transition: opacity 500ms ease, transform 500ms ease;
    pointer-events: none;
}

.stack-panel.is-active { opacity: 1; transform: none; }

/* Same treatment as .card-art, at panel scale, with the reference's 3D lay-back:
   tilted away from the viewer and rotated slightly counter-clockwise, lit from
   above-left, with a large diffuse shadow down and to the left. */
.stack-visual {
    height: 100%;
    display: block;
    overflow: hidden;
    background: #141414;
    border: 1px solid var(--border);
    color: var(--brand-light);
    transform: perspective(1400px) rotateX(6deg) rotateZ(-1.4deg);
    transform-origin: 50% 60%;
    box-shadow:
        0 42px 70px -28px rgba(0, 0, 0, 0.85),
        0 2px 0 0 rgba(255, 255, 255, 0.05) inset;
}

/* The SVG must be sized in BOTH axes. It carries a viewBox and no width/height,
   and preserveAspectRatio="none" means an unconstrained one stretches without
   limit rather than settling at an intrinsic size. */
.stack-visual > svg { width: 100%; height: 100%; display: block; }
.stack-visual > svg [stroke] { vector-effect: non-scaling-stroke; }

.stack-visual.card-art-foundry {
    display: flex;
    align-items: flex-end;
    gap: 2%;
    padding: 12% 6%;
}

.stack-inline { display: none; }

@media (max-width: 900px) {
    .stack { grid-template-columns: 1fr; gap: 0; }
    .stack-media { display: none; }
    .stack-item {
        min-height: 0;
        padding: 2.4rem 0;
        opacity: 1;
        justify-items: stretch;
        border-top: 1px solid var(--border);
    }
    .stack-item:first-child { border-top: 0; }
    .stack-item > .badge,
    .stack-item > .card-go { justify-self: start; }
    .stack-inline { display: block; }
    .stack-inline .card-art { height: 150px; margin-bottom: 0.7rem; }
}

/* In step with the reveal list in script.js. */
.js .stack { opacity: 0; transform: translateY(18px); transition: opacity 600ms ease, transform 600ms ease; }
.js .stack.fade-in { opacity: 1; transform: none; }

/* ---------- aurora band text ----------
   .statement-heading / .statement-text are authored for the light section, so
   they inherit dark ink. Inside the aurora panel the ground is dark again. */
.aurora-inner .statement-heading { color: var(--text); }
.aurora-inner .statement-text { color: var(--text-2); margin: 1.2rem auto 0; max-width: 44rem; }

/* ---------- APPENDED: long-form legal copy ----------
   Added for /terms/ and /privacy/, which are the only pages on the site that
   are a wall of running text. Nothing else in this stylesheet sets type for
   consecutive <p>/<h2>/<ul>, so those pages had no readable measure, no
   paragraph rhythm and no list styling at all. Scoped entirely under .prose so
   it cannot reach any existing page. Keep additions BELOW this line. */
.prose {
    max-width: 46rem;
    margin: 0 auto;
    color: var(--text-2);
    font-size: 1rem;
    line-height: 1.75;
}
.prose > *:first-child { margin-top: 0; }
.prose h2 {
    margin: 2.6rem 0 0.8rem;
    color: var(--text);
    font-size: 1.2rem;
    font-weight: 500;
    letter-spacing: -0.01em;
    line-height: 1.35;
}
.prose p { margin: 0 0 1.1rem; }
.prose ul {
    margin: 0 0 1.1rem;
    padding-left: 1.15rem;
    list-style: disc;
}
.prose li { margin-bottom: 0.5rem; }
.prose strong { color: var(--text); font-weight: 500; }
.prose a {
    color: var(--brand-light);
    text-decoration: underline;
    text-underline-offset: 0.18em;
}
.prose a:hover { color: var(--brand-lighter); }

/* ---------- APPENDED: sub-page header trim ----------
   Two placements .page-hero never had to handle before the product pages used
   it: a status badge above the title, and a CTA row below the subtitle.
   .badge is inline and the global reset zeroes every margin, so the badge sat
   flush on the title; .hero-ctas carries a 3.4rem top margin sized for the
   landing hero, which has no subtitle above the button. */
.page-hero .badge { display: inline-block; margin-bottom: 1.1rem; }
.page-hero .hero-ctas { margin-top: 2.2rem; }

/* ---------- pinned stack header + larger type ----------
   The title and subtitle pin at the top of the viewport while the three
   products scroll past underneath. It has to be a child of the grid to span
   both tracks, and the grid's own `align-items: start` is what lets a sticky
   child work at all - a stretched item has no room to move within. */
.stack-head {
    grid-column: 1 / -1;
    position: sticky;
    /* Pinned at the viewport top with the offset taken as padding instead of a
       `top` value. The box IS the scrim the products vanish into, so any gap
       between the viewport edge and the box is a band where a product that has
       already faded out reappears above the heading on its way up. */
    top: 0;
    z-index: 5;
    text-align: center;
    /* Full grid width, not 46rem: the copy column starts well to the left of a
       centred 46rem box, so a scrim that narrow covered the heading's own
       column and left the product titles sliding past uncovered beside it.
       The type stays centred and measured by the max-widths below. */
    max-width: none;
    /* Not `margin-inline: auto`. An auto inline margin on a grid item overrides
       the default `stretch`, so the box shrank to its widest child (the 42rem
       subtitle) and the scrim was 672px wide in the middle of a 1136px grid —
       which is exactly the strip the product titles slid past, uncovered. */
    margin: 0 0 1rem;
    padding-top: 6.5rem;
    padding-bottom: 2rem;
    /* Opaque past the subtitle, then a short fade that finishes at the bottom
       edge of the box — a product is gone before it reaches the underside of
       the heading rather than showing through it. */
    background: linear-gradient(180deg, var(--bg) 0, var(--bg) 80%, rgba(17, 18, 18, 0) 100%);
}

.stack-head .heading-gradient { font-size: clamp(2.4rem, 4.4vw, 3.5rem); }
.stack-head .section-sub { font-size: 1.2rem; max-width: 42rem; margin-inline: auto; }

/* Bigger across the board — the reference runs noticeably larger than we were. */
.heading-gradient { font-size: clamp(2.4rem, 4.2vw, 3.5rem); }
.section-sub { font-size: 1.2rem; line-height: 1.6; }
.page-title { font-size: clamp(2.7rem, 5vw, 3.75rem); }
.page-sub { font-size: 1.25rem; max-width: 44rem; }
.statement-heading { font-size: clamp(2.2rem, 4vw, 3.25rem); }
.statement-text { font-size: 1.18rem; }
.stack-title { font-size: 2.1rem; }
.stack-desc { font-size: 1.12rem; max-width: 32rem; }
.card-title { font-size: 1.5rem; }
.card-desc { font-size: 1.02rem; line-height: 1.6; }

@media (max-width: 900px) {
    .stack-head {
        position: static;
        background: none;
        /* Nothing pins here, so the scrim's top offset would just be dead space. */
        padding-top: 0;
        padding-bottom: 0;
        margin-bottom: 2.5rem;
    }
}

/* The stacked links sit on the brightest part of the sky, so they need a bit
   more weight than they do once the nav becomes the dark floating panel. */
.nav:not(.scrolled) .nav-links a,
.nav:not(.scrolled) .nav-dd > summary {
    color: #fff;
    text-shadow: 0 1px 12px rgba(0, 0, 0, 0.45);
}

/* ---------- mobile ----------
   The desktop hero nav stacks four links down the left, which costs 208px of a
   844px screen. On a phone the reference uses a compact bar with a hamburger
   and promotes the CTA into a full-screen menu, so that is what this does. */

.nav-toggle { display: none; }

@media (max-width: 900px) {
    .nav-inner {
        grid-template-columns: auto 1fr auto;
        align-items: center;
        padding: 0.85rem var(--pad);
    }

    .nav-logo { grid-column: 1; grid-row: 1; justify-self: start; padding-top: 0; }
    .nav-logo .wordmark-img { height: 15px; }

    .nav-toggle {
        grid-column: 3;
        grid-row: 1;
        justify-self: end;
        display: grid;
        gap: 4px;
        width: 34px;
        padding: 8px 5px;
        background: none;
        border: 0;
        cursor: pointer;
        z-index: 2;
    }

    .nav-toggle span {
        display: block;
        height: 1.5px;
        background: #fff;
        transition: transform 240ms ease, opacity 200ms ease;
    }

    .nav.menu-open .nav-toggle span:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
    .nav.menu-open .nav-toggle span:nth-child(2) { opacity: 0; }
    .nav.menu-open .nav-toggle span:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }

    /* The menu is an opaque full-screen panel, not a drawer.
     *
     * justify-self MUST be reset here and it is not redundant. An earlier mobile
     * block lays .nav-links out as a real grid item in a two-row nav, with
     * `grid-column: 1 / -1; justify-self: start`. Going position:fixed does not
     * shed that: Chrome keeps applying justify-self to a fixed-positioned grid
     * child, so the panel shrank to the intrinsic width of its longest link —
     * 199.75px of a 500px viewport — even though left and right were both 0 and
     * max-width was none. It read as a drawer covering the left ~40%, with the
     * page showing through beside it. */
    .nav-links {
        position: fixed;
        inset: 0;
        justify-self: stretch;
        z-index: 1;
        display: flex;
        flex-direction: column;
        align-items: flex-start;
        justify-content: flex-start;
        gap: 1.5rem;
        padding: 6rem var(--pad) 8rem;
        background: var(--bg);
        opacity: 0;
        visibility: hidden;
        transition: opacity 240ms ease, visibility 240ms ease;
    }

    .nav.menu-open .nav-links { opacity: 1; visibility: visible; }

    .nav-links a,
    .nav-dd > summary { font-size: 1.5rem; line-height: 1.3; }
    .nav:not(.scrolled) .nav-links a,
    .nav:not(.scrolled) .nav-dd > summary { text-shadow: none; }

    .nav-dd-menu {
        position: static;
        width: 100%;
        margin-top: 0.9rem;
        box-shadow: none;
    }

    /* Promoted out of the bar and pinned full-width at the bottom of the menu. */
    .nav-cta {
        position: fixed;
        left: var(--pad);
        right: var(--pad);
        bottom: 1.6rem;
        z-index: 2;
        opacity: 0;
        visibility: hidden;
        transition: opacity 240ms ease, visibility 240ms ease;
    }

    .nav.menu-open .nav-cta { opacity: 1; visibility: visible; }
    .nav-cta .btn { width: 100%; justify-content: center; padding: 0.95rem 1.2rem; }

    /* The floating panel is a desktop idea; on a phone the bar just stays put. */
    .nav.scrolled .nav-inner {
        max-width: none;
        margin-top: 0;
        border-radius: 0;
        border-color: transparent;
        border-bottom-color: rgba(255, 255, 255, 0.12);
        padding: 0.85rem var(--pad);
        background: rgba(17, 18, 18, 0.86);
    }
    .nav.scrolled .nav-logo .wordmark-img { height: 15px; }
    .nav.nav-hidden { transform: none; }

    /* The scrolled/floating-panel rules are written for desktop OUTSIDE any media
     * query (.nav.scrolled .nav-links / .nav-cta / .nav-inner, ~L1876). At
     * (0,3,0) they out-specify the (0,1,0) mobile panel rules above, so once the
     * page is scrolled past 24px on a phone the .scrolled class re-applies
     * `justify-self:start; flex-direction:row` and the full-screen menu collapses
     * back to a 543px-wide row that runs off a 390px screen — Enterprise and
     * Research land off-canvas and unreachable. And .nav.scrolled .nav-inner
     * keeps its `backdrop-filter`, which makes it a containing block for the
     * position:fixed panel and CTA, so they anchor to the bar (panel at x=104,
     * CTA a sliver above the top) instead of the viewport. Reassert the panel for
     * the scrolled state here, where the media query gives these the last word.
     * Validated at 390px scrolled+open: panel {0,0,390,844}, CTA pinned, no link
     * off-screen. */
    .nav.scrolled .nav-inner { backdrop-filter: none; -webkit-backdrop-filter: none; }
    .nav.scrolled .nav-links {
        grid-column: auto;
        justify-self: stretch;
        flex-direction: column;
        align-items: flex-start;
        justify-content: flex-start;
        gap: 1.5rem;
    }
    .nav.scrolled .nav-cta { grid-column: auto; }

    /* The image keeps its own aspect on mobile too - a fixed height plus
       cover was cropping the ridgeline. */

    .hero { padding-top: 6rem; }
}

/* The sticky panel was starting 34px above the pinned header's bottom edge, so
   the two overlapped and the panel read as sliding up behind the title. It now
   parks just below it and only cross-fades. */
.stack-media {
    /* Parked below the pinned heading's scrim. The scrim now runs from the
       viewport top to just under the subtitle, so anything less than this and
       the top edge of the mockup fades out under it. */
    top: 20.5rem;
    height: min(24rem, 48vh);
}

/* At the top of the page the nav is part of the hero, not of the content
   column: links hard left, CTA hard right, wordmark centred on the viewport. */
.nav:not(.scrolled) .nav-inner {
    max-width: none;
    padding-inline: 1.75rem;
}

@media (max-width: 900px) {
    .stack-media { top: auto; height: auto; }
    .nav:not(.scrolled) .nav-inner { padding-inline: var(--pad); }
}

@media (max-width: 900px) {
    /* An abspos grid child still honours justify-self within its grid area, so
       the base `justify-self: end` was shrinking the pinned CTA to its content
       width despite left/right both being set. */
    .nav-cta { justify-self: stretch; width: auto; }
}

@media (max-width: 900px) {
    /* At phone width a 3:1 panorama is only ~130px tall, which reads as a
       sliver of nothing. Widening it past the viewport scales it up while
       keeping the whole scene - `cover` at a fixed height would crop the ends
       of the bay off instead. */
    .hero-img {
        width: 200%;
        left: 50%;
        right: auto;
        transform: translateX(-50%);
    }
}

@media (max-width: 560px) {
    .hero-img { width: 290%; }
}

/* Hero CTA: plain white text with a chevron, underlined on hover only. Not a
   filled button - the hero already has one strong shape in the headline, and a
   solid pill under it competes with the photograph. */
.hero-link {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    color: #fff;
    font-size: 1.22rem;
    font-weight: 400;
    letter-spacing: -0.01em;
    text-decoration: none;
    padding-bottom: 3px;
    /* An underline drawn as a border, so it can animate from nothing rather
       than blinking on - text-decoration cannot be transitioned reliably. */
    border-bottom: 1px solid transparent;
    transition: border-color 200ms ease;
}

.hero-link:hover { border-bottom-color: #fff; }
.hero-link svg { display: block; }
.hero-link:hover .btn-arrow { transform: translateX(3px); }
.hero-link .btn-arrow { transition: transform 200ms ease; }

/* The chevron stays white here rather than taking the indigo gradient - it is
   part of the word, not a separate accent. */
.hero-link .btn-arrow svg path { stroke: #fff; }

/* ---------- footer wordmark ----------
   The name at the scale of the page rather than of a logo: sitting just above
   the ground it is printed on, and cropped by the footer's existing
   `overflow: hidden` so it reads as continuing past the bottom edge.

   It lives INSIDE the footer's .container, so its left and right edges line up
   with the small wordmark and the copyright line above it. Full-bleed was tried
   first and the glyphs sliced the viewport edges, which reads as an image that
   overflowed rather than as a deliberate crop. Aligning it to the same column
   as everything else in the footer is what makes the bottom crop look intended.

   Opacity, not a hex value. The mark is a white SVG and this is the only way to
   keep it exactly N% above whatever --bg is; hard-coding #1E1F1F here would
   silently stop matching the moment the background token moves, which it did
   once already this session (#0E0B0A -> #111212).

   It is decoration and must never behave like a link — no href, no hover, no
   pointer events. The real wordmark is at the top of the footer. */
.footer-giant {
    margin-top: 3.5rem;
    /* Pulled down so the baseline sits below the page edge. Without this the
       glyphs float above a strip of empty ground and read as an image someone
       forgot to position, rather than as the page being cut off. The value is
       in vw because the mark scales with the container — a fixed px crop eats
       the whole glyph on a phone and barely nicks it on a wide monitor. */
    margin-bottom: -2.6vw;
    line-height: 0;
    pointer-events: none;
    user-select: none;
}

.footer-giant-mark {
    display: block;
    width: 100%;
    height: auto;
    /* 5.5% of white on #111212 lands around #1E1F1F — legible as a shape when
       you look for it, invisible when you are reading the links above it. */
    opacity: 0.055;
}

@media (max-width: 900px) {
    /* The crop is proportional, so on a phone it eats a third of the glyph
       height. Less overhang, and a touch more presence to survive the smaller
       rendered size. */
    .footer-giant { margin-top: 2.5rem; margin-bottom: -2.5vw; }
    .footer-giant-mark { opacity: 0.07; }
}

/* ---------- hero fireworks ----------
   Over the club on the far shore. The launch point was given in the SOURCE
   image's pixels — 877 in from the left, 225 up from the bottom of a 2172x724
   photograph — so every number here is a fraction of that file rather than a
   viewport length:

     left   877 / 2172 = 40.38%
     bottom 225 /  724 = 31.08%
     rise   125 /  724 = 17.27%   (the .fw box IS the rise)

   .hero-fx carries the photograph's aspect-ratio and is laid over it exactly,
   including the mobile overscale below, which is what keeps the origin welded
   to the sign at every width. A vw-based origin drifts off it as soon as the
   window changes shape.

   Two axes, kept separate so they can be tuned independently:
     .fw-p1/2/3   the VOLLEY — its colour and when it goes up
     .fw-a/.fw-b  the two shells within a volley — where and 0.15s apart

   Three volleys on one 13s cycle means a pair roughly every 4.3s, and because
   the colour rides on the volley rather than on the shell, two consecutive
   bursts are never the same colour. */
.hero-fx {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    aspect-ratio: 2172 / 724;
    pointer-events: none;
    /* Burst radius. In vw because a 0x0 anchor has no box to take a % of; it is
       re-stated wherever .hero-img is overscaled, so it stays in proportion to
       the photograph rather than to the window. */
    --fw-r: 2.2vw;
}

.fw {
    position: absolute;
    left: 40.38%;
    bottom: 31.08%;
    width: 0;
    height: 17.27%;   /* the rise: the shell travels this box, bottom to top */
}

/* --- the two shells of a volley ---------------------------------------- */
/* .fw-a sits on the stated point. .fw-b is nudged along the shore and stops a
   little lower, so the pair reads as two fireworks at slightly different
   distances rather than as one mirrored across an axis. */
.fw-a { --fw-o: 0s; }
.fw-b {
    --fw-o: 0.15s;
    left: 43.4%;
    bottom: 29.6%;
    height: 14.2%;
}

/* --- the volleys: colour, and when ------------------------------------- */
/* An "R, G, B" triplet rather than a colour, so one value drives both the solid
   particle and the softer shell without declaring the hue twice. */
.fw-p1 { --fw-c: 255, 226, 190; --fw-t: 0s; }      /* amber */
.fw-p2 { --fw-c: 188, 214, 255; --fw-t: 4.33s; }   /* cold blue */
.fw-p3 { --fw-c: 255, 192, 208; --fw-t: 8.66s; }   /* rose */

.fw-shell {
    position: absolute;
    left: -1px;
    bottom: 0;
    width: 2px;
    height: 2px;
    border-radius: 50%;
    background: rgba(var(--fw-c), 0.9);
    opacity: 0;
    animation: fw-rise 13s ease-out infinite;
    animation-delay: calc(var(--fw-t) + var(--fw-o));
}

@keyframes fw-rise {
    0%          { bottom: 0;    opacity: 0; }
    2%          { opacity: 0.55; }
    11%         { bottom: 100%; opacity: 0.55; }
    12%, 100%   { bottom: 100%; opacity: 0; }
}

.fw-burst {
    position: absolute;
    left: 0;
    bottom: 100%;   /* the top of the rise, where the shell arrives */
    width: 0;
    height: 0;
}

.fw-burst i {
    position: absolute;
    left: -1px;
    bottom: -1px;
    width: 2px;
    height: 2px;
    border-radius: 50%;
    background: rgb(var(--fw-c));
    opacity: 0;
    animation: fw-pop 13s ease-out infinite;
    animation-delay: calc(var(--fw-t) + var(--fw-o));
}

/* Eight arms. rotate-then-translate, so one keyframe block serves all of them
   and only the angle differs. */
.fw-burst i:nth-child(1) { --fw-a:   0deg; }
.fw-burst i:nth-child(2) { --fw-a:  45deg; }
.fw-burst i:nth-child(3) { --fw-a:  90deg; }
.fw-burst i:nth-child(4) { --fw-a: 135deg; }
.fw-burst i:nth-child(5) { --fw-a: 180deg; }
.fw-burst i:nth-child(6) { --fw-a: 225deg; }
.fw-burst i:nth-child(7) { --fw-a: 270deg; }
.fw-burst i:nth-child(8) { --fw-a: 315deg; }

@keyframes fw-pop {
    0%, 10.5%   { transform: rotate(var(--fw-a)) translateX(0); opacity: 0; }
    12%         { transform: rotate(var(--fw-a)) translateX(calc(var(--fw-r) * 0.25)); opacity: 0.5; }
    19%         { transform: rotate(var(--fw-a)) translateX(var(--fw-r)); opacity: 0; }
    100%        { transform: rotate(var(--fw-a)) translateX(var(--fw-r)); opacity: 0; }
}

/* An ambient loop with no control to stop it is exactly what this media query
   exists for. Nothing else in the hero moves, so there is nothing to keep. */
@media (prefers-reduced-motion: reduce) {
    .hero-fx { display: none; }
}

/* Must mirror .hero-img exactly — same width, same offset — or the launch point
   stops sitting on the sign. These two pairs of rules are one decision; change
   them together. */
@media (max-width: 900px) {
    .hero-fx {
        width: 200%;
        left: 50%;
        right: auto;
        transform: translateX(-50%);
        --fw-r: 4.4vw;   /* 2.2vw x the 2x overscale */
    }
}

@media (max-width: 560px) {
    .hero-fx { width: 290%; --fw-r: 6.38vw; }
}

/* ---------- APPENDED: stack items — title as the link, screenshot as the art ----------
   The separate "Explore Signal" CTA under each product is gone. The product
   name IS the link now, with a chevron that is part of the word rather than a
   coloured accent — same treatment as .hero-link, so the two read as one
   family. Underline on hover only, drawn as a border so it can fade in.

   Type came down a step at the same time: at 2.1rem/1.12rem the three products
   were competing with the pinned section heading above them. */
.stack-title { font-size: 1.6rem; }
.stack-desc  { font-size: 1.02rem; max-width: 30rem; }

.stack-link {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    color: var(--text);
    text-decoration: none;
    padding-bottom: 2px;
    border-bottom: 1px solid transparent;
    transition: border-color 200ms ease;
}

.stack-link:hover { border-bottom-color: var(--text); }
.stack-link .stack-chev { display: block; transition: transform 200ms ease; }
.stack-link:hover .stack-chev { transform: translateX(3px); }
/* The chevron takes the text colour, not the indigo gradient the .card-go
   family uses — see the note on .hero-link. The rule has to be explicit
   because #chevGrad is applied by selector elsewhere in this file. */
.stack-link .stack-chev path { stroke: currentColor; }

/* A mockup on a transparent ground: no frame, no border, no lay-back — the
   image already has all three baked in. */
.stack-shot {
    display: block;
    width: 100%;
    height: auto;
}

/* In the sticky panel it deliberately breaks its column. Fitted inside the
   panel box it landed at ~560px wide, which reads as a thumbnail of a
   dashboard rather than as the dashboard; at 122% it overhangs the grid
   track by about half the page gutter and still clears the viewport edge.
   Pushed below centre by the same move, so it sits under the pinned heading
   rather than crowding it. */
.stack-panel .stack-shot {
    position: absolute;
    left: 50%;
    top: 60%;
    width: 122%;
    max-width: none;
    height: auto;
    /* The three mockups are not one shape — Signal is 1.48:1, Foundry and
       Atlas about 1.11:1 because each carries a second panel floating above
       the slab. Sized on width alone the tall pair stood a third higher than
       Signal and ran up under the pinned heading, so height is what is held
       equal between them and width follows the aspect. */
    max-height: min(34rem, 62vh);
    transform: translate(-50%, -50%);
}

/* Once the container stops having page gutters to overhang into, the same 122%
   runs off the right edge of the viewport and takes a horizontal scrollbar with
   it. Below the container's own width the overhang has to shrink with it. */
@media (max-width: 1300px) {
    .stack-panel .stack-shot { width: 106%; }
}

@media (max-width: 900px) {
    /* The rules between products were the only thing making this read as a
       settings list on a phone. Spacing separates them now. */
    .stack-item { border-top: 0; padding: 1.9rem 0; }
    .stack-inline .stack-shot { height: auto; margin-bottom: 0.9rem; }
}

/* ---------- APPENDED: framed product screenshot ----------
   The interface, shown flat, inside the same grainy multicolour ring the
   aurora band is made of — so the frame is an existing part of the language
   rather than a second decorative idea. It shares .aurora's gradient, grain
   and 12px inset (see the selector lists above); everything below is only
   what a picture needs and a full-bleed band does not.

   The ring is the whole effect, so it stays 12px at every width: a frame that
   scales with the viewport reads as a border, and a border on a screenshot is
   the thing this is meant to avoid. */
.ui-frame {
    display: block;
    line-height: 0;
    overflow: hidden;
}

/* Above the grain, exactly as .aurora-inner is — the noise belongs to the ring
   and washes out the UI underneath it otherwise. */
.ui-shot {
    position: relative;
    z-index: 2;
    display: block;
    width: 100%;
    height: auto;
}

/* Tight to the hero above it: this is the hero's evidence, not a new section. */
.section-ui-shot { padding-top: 1rem; }

.ui-note {
    margin-top: 0.9rem;
    text-align: center;
    color: var(--text-3);
    font-size: 0.82rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

/* ---------- APPENDED: compliance mark in the footer ----------
   A hairline chip in the footer's bottom row, between the copyright and the
   legal links. Square corners and --text-3 on purpose: it is a fact stated
   quietly, and a bright badge in a footer reads as a trust seal bought from a
   widget rather than as an audit that happened. */
.footer-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.35rem 0.7rem;
    border: 1px solid var(--border);
    color: var(--text-3);
    font-size: 0.7rem;
    font-weight: 500;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    white-space: nowrap;
}

.footer-badge svg { display: block; flex: none; }

/* ---------- APPENDED: light theme, scoped to /charts/ ----------
   The charts are the one public, user-facing surface here (they get their own
   domain), and a ranked list of records reads as a document rather than as a
   dashboard — so this page runs the palette the other way round. It is a token
   swap first: nearly everything on the page already draws from --bg/--text/
   --border, and only the values below are hard-coded darks that had to be
   answered one by one.

   Scope is `body.theme-light`, so nothing here can reach another page. To take
   a second page light, add the class — then check it against this list, because
   a rule that hard-codes #fff will not have been caught by it. */
.theme-light {
    --bg: #FFFFFF;
    --surface: #F4F4F2;
    --surface-2: #ECECE9;
    --border: #E4E4DF;
    --border-2: #CBCBC4;
    --nav-border: #E4E4DF;
    /* The indigo and the gold both have to go DARKER, not lighter: on the dark
       page the "-light"/"-lighter" steps are the legible end of the ramp, and
       on white they are the illegible one. The names keep their meaning
       (further from the ground); only the direction of the ramp flips. */
    --brand: #5A4FE0;
    --brand-light: #4C41CE;
    --brand-lighter: #3B31B4;
    --gold: #96690F;
    --gold-soft: rgba(150, 105, 15, 0.12);
    --light: #16181A;
    --text: #16181A;
    --text-2: #55595C;
    --text-3: #797D80;
}

/* --- chrome --- */
.theme-light .nav.scrolled { background: rgba(255, 255, 255, 0.78); }
.theme-light .nav.scrolled .nav-inner {
    /* Denser than the dark panel's 0.55, and with a border of its own: a
       translucent white panel on a white page has nothing to lift off, so at
       the dark page's opacity it reads as no panel at all. */
    background: rgba(255, 255, 255, 0.86);
    border-color: #DEDED8;
}
/* These carry a white fill and a drop shadow for the landing hero photograph.
   There is no photograph here and no dark ground under them. */
.theme-light .nav:not(.scrolled) .nav-links a,
.theme-light .nav:not(.scrolled) .nav-dd > summary { color: var(--text-2); text-shadow: none; }
.theme-light .nav-logo { color: var(--text); }
.theme-light .nav-dd-menu { background: rgba(255, 255, 255, 0.97); }
.theme-light .nav-toggle span { background: var(--text); }

/* The sub-page header's ground, flipped end for end. */
.theme-light .page-hero {
    background: linear-gradient(180deg, #F1F1EE 0%, #F8F8F6 42%, var(--bg) 100%);
}

/* Clipped to the text, so it cannot inherit a colour — it has to be restated. */
.theme-light .heading-gradient { background-image: linear-gradient(180deg, #16181A, #6E7275); }
.theme-light .statement-heading,
.theme-light .contact-heading,
.theme-light .strip-num { color: var(--text); }

.theme-light .strip { background: rgba(0, 0, 0, 0.018); }

/* The primary button is defined as "pale chip on a dark page". Here it is the
   only dark shape on a white one, which is the same job. */
.theme-light .btn-light { background: #16181A; color: #FFFFFF; }
.theme-light .btn-light:hover { background: #33373B; }

/* --- the chart itself --- */
.theme-light #chart-moments:checked ~ .chart-tabs label[for="chart-moments"],
.theme-light #chart-artists:checked ~ .chart-tabs label[for="chart-artists"],
.theme-light #chart-crossover:checked ~ .chart-tabs label[for="chart-crossover"] {
    background: #16181A;
    border-color: #16181A;
    color: #FFFFFF;
}
/* Every one of these is a white alpha over the dark page: on white it is the
   same value as the ground, so the list loses its fill, its hover and its
   unfilled bar track all at once. */
.theme-light .chart-list { background: rgba(0, 0, 0, 0.018); }
.theme-light .chart-row:hover { background: rgba(0, 0, 0, 0.035); }
.theme-light .chart-tile-spark { background: rgba(0, 0, 0, 0.035); }
.theme-light .chart-bar { background: rgba(0, 0, 0, 0.09); }
.theme-light .chart-bar span {
    background: linear-gradient(90deg, rgba(90, 79, 224, 0.45), var(--brand));
}
.theme-light .spark-area { fill: rgba(90, 79, 224, 0.2); }
.theme-light .chart-row:nth-child(-n+3) .spark-area { fill: rgba(150, 105, 15, 0.18); }
.theme-light .tag-own {
    color: var(--brand-lighter);
    background: rgba(90, 79, 224, 0.1);
    border-color: rgba(90, 79, 224, 0.32);
}
.theme-light .tag-tent {
    color: var(--gold);
    background: var(--gold-soft);
    border-color: rgba(150, 105, 15, 0.32);
}

@media (max-width: 900px) {
    .theme-light .nav-inner {
        background: rgba(255, 255, 255, 0.9);
        border-bottom-color: rgba(0, 0, 0, 0.1);
    }
}

/* ---------- nine71 charts (the public tracklist site) ----------
   Reuses the .shots component from the Signal section. Those selectors are
   id-specific by necessity (see the note there), so a second instance on the
   same page needs its own rules and its own radio `name` - otherwise the two
   tab groups share one state and clicking a tab here would blank the other. */
/* ---------- one light section on a dark page ----------
   The tracklists product is the public, free, no-account half of nine71, and it
   reads as a different thing when it sits in a different light. /charts/ already
   does this at page scope with body.theme-light; this is the same token swap
   scoped to a single <section>, so it reuses a palette that has already been
   reasoned about rather than inventing a second light ramp.

   CUSTOM PROPERTIES ARE WHY THIS IS SHORT. Nearly everything inside the section
   draws from --bg/--text/--border, so redeclaring the tokens on the section is
   most of the work; only the handful of rules that hard-code a dark value have
   to be answered one by one, and they are the ones listed below.

   The .shot-frame stays DARK on purpose. The screenshots inside it are of a dark
   app, and a white frame around a dark screenshot reads as a mistake — the dark
   frame reads as a screen. */
.section-charts-site {
    --bg: #FFFFFF;
    --surface: #F4F4F2;
    --surface-2: #ECECE9;
    --border: #E4E4DF;
    --border-2: #CBCBC4;
    --brand: #5A4FE0;
    --brand-light: #4C41CE;
    --brand-lighter: #3B31B4;
    --gold: #96690F;
    --gold-soft: rgba(150, 105, 15, 0.12);
    --light: #16181A;
    --text: #16181A;
    --text-2: #55595C;
    --text-3: #797D80;

    background: var(--bg);
    color: var(--text);
    border-top: 1px solid var(--border);
}

/* Clipped to the text, so it cannot inherit a colour — it has to be restated or
   it renders as the dark page's pale ramp on white, i.e. invisible. */
.section-charts-site .heading-gradient {
    background-image: linear-gradient(180deg, #16181A, #6E7275);
}
.section-charts-site .section-sub,
.section-charts-site .shot-note { color: var(--text-2); }
.section-charts-site .shot figcaption { color: var(--text-3); }

/* The selected tab is "pale chip on a dark page" everywhere else. Here it is the
   only dark shape on a white one, which is the same job done the other way up. */
#ct-index:checked ~ .shot-tabs label[for="ct-index"],
#ct-set:checked ~ .shot-tabs label[for="ct-set"],
#ct-charts:checked ~ .shot-tabs label[for="ct-charts"] {
    background: #16181A;
    border-color: #16181A;
    color: #FFFFFF;
}

/* Lighter than the dark page's, because a 0.5-alpha black shadow under a frame
   on white is a smudge rather than a lift. */
.section-charts-site .shot-frame { box-shadow: 0 30px 70px rgba(22, 24, 26, 0.18); }


#ct-index:checked ~ .shot-frame .shot[data-shot="ct-index"],
#ct-set:checked   ~ .shot-frame .shot[data-shot="ct-set"],
#ct-charts:checked ~ .shot-frame .shot[data-shot="ct-charts"] { display: block; }

/* The one outbound CTA on the page. An anchor, not a .linkish button: it really
   does navigate, and to another origin. */
.charts-cta {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    margin-left: 0.5rem;
    color: var(--brand-light);
    text-decoration: none;
    font-weight: 400;
    transition: color 140ms ease;
}
.charts-cta:hover { color: var(--brand-lighter); }
.charts-cta .btn-arrow { transition: transform 140ms ease; }
.charts-cta:hover .btn-arrow { transform: translateX(2px); }

/* ===========================================================================
   /tracklists/ — the free, public half
   Runs on body.theme-light like /charts/, so the tokens are already flipped by
   the block above; everything here is layout for the one thing this page has
   that no other page does — a photographic hero.
   =========================================================================== */

/* FULL SCREEN. The picture is the page's opening argument, so it gets the
   viewport rather than a band of it, and the copy sits low-left on top of it.
   `100svh` not `100vh`: on mobile Safari the large viewport unit puts the button
   under the browser chrome on first paint. */
.tl-hero {
    position: relative;
    isolation: isolate;
    display: flex;
    align-items: center;
    min-height: 100svh;
    /* CENTRED, and left. The copy used to sit low, which put it inside the wash
       at the foot — white type on a whitening ground. Vertically centred it
       clears the fade by construction rather than by a padding value that has to
       be re-tuned whenever the fade height changes. */
    padding: 0;
    overflow: hidden;
}
.tl-hero-media { position: absolute; inset: 0; z-index: -2; }
.tl-hero-media img {
    width: 100%; height: 100%; object-fit: cover; object-position: 50% 45%;
    display: block;
}
/* Weight where the type is, and at the very top where the fixed nav sits — the
   rest of the frame stays open, because the picture is the point. */
/* THE NUMBERS BELOW WERE TUNED TWICE WHILE NOTHING WAS RENDERING, so they ran
   away: at 0.82 on the left the scrim stopped being atmosphere and became a
   filter over the painting. These are the values chosen with it actually
   visible — present on all four edges, and still a photograph underneath.

   z-index 1, NOT -1. These two are children of .tl-hero-media, which is
   positioned with z-index:-2 and so opens its own stacking context — inside it a
   negative z-index paints behind the element's own content, which is the <img>.
   So the scrim and the grain were both rendering underneath the photograph and
   neither had ever been visible. Positive values put them where they belong,
   above the image and still behind the copy. */
.tl-hero-scrim {
    position: absolute; inset: 0; z-index: 1;
    background:
        /* A veil, not a band. The old stop was 0.34 hard against the top edge and
           gone by 26%, which reads as a dark strip laid on the picture; this is
           lighter and carried further down, so it registers as the sky being a
           shade deeper up there and never as an edge. It is only there to keep
           the nav off bright cloud. */
        linear-gradient(180deg, rgba(12,16,22,0.20) 0%, rgba(12,16,22,0.13) 10%, rgba(12,16,22,0.05) 22%, rgba(12,16,22,0) 34%),
        linear-gradient(180deg, rgba(12,16,22,0) 42%, rgba(12,16,22,0.16) 70%, rgba(12,16,22,0.30) 92%),
        /* The type is centred and left, so the weight goes there rather than
           along the bottom edge where it used to sit. */
        linear-gradient(90deg, rgba(12,16,22,0.58) 0%, rgba(12,16,22,0.44) 26%, rgba(12,16,22,0.24) 48%, rgba(12,16,22,0.08) 68%, rgba(12,16,22,0) 84%),
        /* And the right edge. Lighter than the left, because nothing is read over
           there — it is there so the frame closes on both sides instead of the
           picture running brightly off one of them. */
        linear-gradient(270deg, rgba(12,16,22,0.40) 0%, rgba(12,16,22,0.22) 16%, rgba(12,16,22,0.08) 32%, rgba(12,16,22,0) 52%);
}
/* THE JOIN. The wash ends on --bg exactly, so the picture dissolves into the
   page's own ground instead of stopping at an edge. Tall and soft — a short hard
   fade reads as a gradient laid over a seam, which is the thing it is hiding. */
.tl-hero-fade {
    position: absolute; left: 0; right: 0; bottom: -1px; height: 30vh;
    z-index: 1; pointer-events: none;
    /* Taller, and it commits to the page colour earlier — the last stretch is
       flat --bg rather than a gradient still resolving, so the join has no
       visible end. Reaching solid only at the final pixel left a faint band
       where the wash and the section below it disagreed by a percent or two. */
    background: linear-gradient(180deg,
        rgba(255,255,255,0) 0%,
        rgba(252,251,255,0.22) 30%,
        rgba(252,251,255,0.62) 58%,
        rgba(252,251,255,0.92) 80%,
        var(--bg) 94%,
        var(--bg) 100%);
}
/* Identical to .hero-grain on the landing page — same tile, same 200px repeat,
   same opacity. Two heroes, one texture. */
.tl-hero-grain {
    position: absolute; inset: 0; z-index: 2;
    opacity: 0.32;
    background-image: url("/img/grain.png");
    background-repeat: repeat;
    background-size: 200px;
    pointer-events: none;
}
/* width:100% is load-bearing. .container carries `margin: 0 auto`, and as a flex
   ITEM of the centred hero that auto margin centres the whole block and shrinks
   it to its content — so a left-aligned headline rendered as an indented one,
   nowhere near the gutter the nav sits on. Filling the row hands the centring
   back to .container's own max-width, which is the same track the nav uses. */
.tl-hero-inner { position: relative; z-index: 2; width: 100%; }
/* Between the two. Flush to the viewport gutter (32px) read as hard against the
   edge of a full-bleed picture; .container's own track (152px) read as indented
   under it. This sets its own inset, so the headline sits clearly left without
   touching the edge — a hero over a photograph is not obliged to share the
   column the prose below it uses. */
.tl-hero .tl-hero-inner {
    max-width: none;
    padding-left: clamp(2rem, 5.5vw, 5.5rem);
    padding-right: var(--pad);
}
/* LONG AND LIGHT. A short heavy line is the shape of a slogan; this page is
   describing what the thing does, so it gets a sentence at 300 with the line
   length to breathe. Outfit is a variable font over 300..900, so the light stop
   is real rather than a synthesised thin. */
.tl-title {
    color: #fff;
    font-size: clamp(1.9rem, 3.6vw, 3.1rem);
    line-height: 1.22; letter-spacing: -0.015em; font-weight: 500;
    margin-bottom: 1.9rem; max-width: 26ch;
    text-shadow: 0 2px 26px rgba(10,14,20,0.5);
}
.tl-ctas { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
/* On the photograph the pale chip is the legible one, so .btn-light keeps its
   dark-page definition here and is NOT flipped by the theme-light rule. */
.theme-light .tl-hero .btn-light { background: #FFFFFF; color: #16181A; }
.theme-light .tl-hero .btn-light:hover { background: #F1EFF8; }

.tl-aside {
    max-width: 62ch; color: var(--text-2); line-height: 1.7;
    border-left: 2px solid var(--border-2); padding-left: 1.1rem;
}
.tl-aside a { color: var(--brand-light); text-decoration: underline; text-underline-offset: 2px; }
.tl-aside a:hover { color: var(--brand-lighter); }

/* The tab chips on this page are the light-page pair: dark chip on white. */
#tl-index:checked ~ .shot-tabs label[for="tl-index"],
#tl-set:checked ~ .shot-tabs label[for="tl-set"],
#tl-charts:checked ~ .shot-tabs label[for="tl-charts"] {
    background: #16181A; border-color: #16181A; color: #FFFFFF;
}
#tl-index:checked ~ .shot-frame .shot[data-shot="tl-index"],
#tl-set:checked   ~ .shot-frame .shot[data-shot="tl-set"],
#tl-charts:checked ~ .shot-frame .shot[data-shot="tl-charts"] { display: block; }

@media (max-width: 760px) {
    /* FULL SCREEN ON THE PHONE TOO. This block is left over from the first
       version of the hero, when it was a band rather than the page's opening:
       min-height:62vh cut it to 523px of an 844px viewport, so "What it is"
       started half a screen up and the picture never got a screen of its own.
       100svh — the SMALL viewport unit — so the button is not under Safari's
       chrome on first paint, before the bar collapses.
       .tl-strip is gone from this page; its rule went with it. */
    .tl-hero { min-height: 100svh; align-items: center; }
    /* Portrait crop: the DJ and the crowd sit right of centre in the frame, and
       at 390px a centred crop lands on empty sea. */
    .tl-hero-media img { object-position: 64% 46%; }
    .tl-hero .tl-hero-inner { padding-left: var(--pad); padding-right: var(--pad); }
    /* The wash is a proportion of a much taller box here, so it needs pulling in
       or it climbs over the headline. */
    .tl-hero-fade { height: 22vh; }
}

/* THIS PAGE IS THE EXCEPTION THE THEME-LIGHT NAV RULE CALLS OUT. That rule drops
   the white fill and the drop shadow from the at-rest nav links because "there is
   no photograph here and no dark ground under them" — true of /charts/, and not
   true here: /tracklists/ opens on a bright painting, and dark-on-pale links land
   on sunlit cloud. So the at-rest nav takes the landing page's treatment back.
   Only at rest: once .scrolled applies, the links sit inside the panel and the
   normal rules are right again. */
/* A WHITE PAGE WITH A LITTLE VIOLET IN IT, not a lilac page. The brand's indigo
   is already the accent here, so the ground is pulled a couple of points toward
   it — enough that the white is not the same white as every other site, and
   little enough that nobody would call it purple. Text is untouched: only the
   surfaces move. */
.theme-light.tl-page {
    --bg: #FCFBFF;
    --surface: #F5F3FB;
    --surface-2: #EEEBF7;
    --border: #E6E2F2;
    --border-2: #CBC5DF;
}
.theme-light.tl-page .strip { background: rgba(90, 79, 224, 0.035); }

.theme-light.tl-page .nav:not(.scrolled) .nav-links a,
.theme-light.tl-page .nav:not(.scrolled) .nav-dd > summary {
    color: #FFFFFF;
    text-shadow: 0 1px 14px rgba(10, 14, 20, 0.55);
}
.theme-light.tl-page .nav:not(.scrolled) .nav-dd-chev { color: #FFFFFF; }
.theme-light.tl-page .nav:not(.scrolled) .nav-logo { color: #FFFFFF; }
/* The wordmark is an SVG file, so it cannot take `color`. Invert it instead, and
   only while it is over the picture. */
.theme-light.tl-page .nav:not(.scrolled) .nav-logo .wordmark-img {
    filter: invert(1) brightness(1.6) drop-shadow(0 1px 10px rgba(10, 14, 20, 0.6));
}
/* Pale chip on a photograph, like the landing page — the theme-light flip to a
   dark chip is for a white ground. */
.theme-light.tl-page .nav:not(.scrolled) .nav-cta .btn-light { background: #FFFFFF; color: #16181A; }

/* THE MOBILE NAV CARRIES ITS OWN NEAR-WHITE PANEL — .nav-inner is
   rgba(255,255,255,0.9) at this width — so the white-on-photograph treatment
   this page applies at rest is exactly wrong there: a white wordmark and white
   links on a white bar.

   AT THE END OF THE FILE ON PURPOSE. This lost the first time because it sat in
   the @media block ABOVE the .tl-page rules it has to beat, and a media query
   adds no specificity — same weight, so source order decides. Anything that
   overrides those rules has to come after them. */
@media (max-width: 760px) {
    .theme-light.tl-page .nav:not(.scrolled) .nav-links a,
    .theme-light.tl-page .nav:not(.scrolled) .nav-dd > summary {
        color: var(--text-2); text-shadow: none;
    }
    .theme-light.tl-page .nav:not(.scrolled) .nav-dd-chev { color: var(--text-2); }
    /* NO FILTER. This page ships the DARK wordmark (nine71-wordmark.svg, not the
       -white one the dark pages use), so it is already correct on a white bar —
       invert(1) here was turning a dark mark white and putting it straight back
       on the white panel it was supposed to be readable against. The desktop
       rule inverts precisely because up there it sits on the photograph. */
    .theme-light.tl-page .nav:not(.scrolled) .nav-logo .wordmark-img { filter: none; }
    .theme-light.tl-page .nav:not(.scrolled) .nav-cta .btn-light {
        background: #16181A; color: #FFFFFF;
    }
}

/* ---------- the contact page (/contact/, relaid 2026-08-28) ----------
   Two columns under the centred hero: who this reaches on the left, the form
   on a card on the right — the site's own square-cornered surface, not the
   app's rounded fields, which is what this block used to borrow. The first
   cut was the bare form against the left gutter with half the viewport empty.
   The honeypot is removed from every real medium (visual, tab order, a11y
   tree) without display:none, which some form-fillers skip. */
.contact-section { padding-top: 1.5rem; }   /* the hero already carries the air */
.contact-grid {
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: 4.5rem;
    align-items: start;
    max-width: 62rem;
    margin: 0 auto;
}
.contact-aside h2 {
    font-size: 1.35rem; font-weight: 500; letter-spacing: -0.01em;
    margin-bottom: 0.9rem;
}
.contact-aside > p { color: var(--text-2); font-size: 0.98rem; line-height: 1.6; }
.contact-routes { margin-top: 2rem; display: flex; flex-direction: column; gap: 1.15rem; }
.contact-routes p { color: var(--text-2); font-size: 0.92rem; line-height: 1.55; }
.contact-routes strong { color: var(--text); font-weight: 500; display: block; margin-bottom: 2px; }
.contact-routes a { color: var(--text); }
.contact-mail { margin-top: 2.2rem; font-size: 0.88rem; color: var(--text-3); }
.contact-mail a { color: var(--text-2); }

.contact-form {
    background: var(--surface);
    border: 1px solid var(--border);
    padding: 2rem 1.9rem;
}
.cf-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.cf-field { display: flex; flex-direction: column; gap: 7px; margin-bottom: 16px; }
.cf-label { font-size: 13px; color: var(--text-2); }
.contact-form input[type="text"],
.contact-form input[type="email"],
.contact-form select,
.contact-form textarea {
    background: var(--bg); border: 1px solid var(--border);
    color: var(--text); border-radius: 0; padding: 12px 14px;
    font-size: 15px; font-family: inherit; width: 100%; outline: none;
}
.contact-form textarea { resize: vertical; min-height: 130px; line-height: 1.55; }
.contact-form input:focus, .contact-form select:focus, .contact-form textarea:focus {
    border-color: var(--border-2);
}
.contact-form ::placeholder { color: var(--text-3); }
.cf-trap {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
}
.cf-actions { display: flex; align-items: center; gap: 16px; margin-top: 6px; }
.cf-status { font-size: 14px; color: var(--text-2); margin: 0; min-height: 1.4em; }

@media (max-width: 860px) {
    .contact-grid { grid-template-columns: 1fr; gap: 3rem; }
    .contact-form { padding: 1.5rem 1.2rem; }
    .cf-row { grid-template-columns: 1fr; }
}

/* ---------- /charts/ : nine71charts.com's chart, rendered here ----------
   The rows arrive from /api/charts/trending, so this is the only styling on the
   site that dresses content it did not ship with. Kept deliberately close to the
   charts site's own presentation — rank, movement, record, sets, DJs — because
   the two are supposed to read as one chart in two places.

   EVERY COLOUR IS A TOKEN, and the first draft of this block was not. It used
   #fff and rgba(255,255,255,…) throughout, which is invisible on /charts/
   because that page is `body.theme-light` — white rows on a white ground, fifty
   of them, with the assertions all passing because the rows were in the DOM.
   The warning was already written at the top of the .theme-light block: "a rule
   that hard-codes #fff will not have been caught by it." */
.chart-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 22px; }
.chart-tab {
  font: inherit; font-size: 13px; cursor: pointer;
  padding: 7px 13px; border-radius: 999px;
  border: 1px solid var(--border-2);
  background: transparent; color: var(--text-2);
}
.chart-tab:hover { color: var(--text); border-color: var(--text-3); }
.chart-tab.current { background: var(--text); color: var(--bg); border-color: var(--text); }

.chart-status { color: var(--text-2); font-size: 14px; margin: 0 0 14px; }
.chart-rows { list-style: none; margin: 0; padding: 0; }
.ct-row {
  display: grid; align-items: center; gap: 14px;
  grid-template-columns: 34px 46px 1fr 62px 62px;
  padding: 13px 0; border-top: 1px solid var(--border);
}
.ct-row:first-child { border-top: none; }
.ct-rank { font-variant-numeric: tabular-nums; color: var(--text-3); font-size: 14px; }
.ct-id { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ct-title { color: var(--text); font-size: 15.5px; font-weight: 500; }
.ct-mix { color: var(--text-3); font-weight: 400; }
.ct-artist {
  color: var(--text-2); font-size: 13px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ct-n {
  text-align: right; font-variant-numeric: tabular-nums;
  color: var(--text); font-size: 14px;
}
.ct-cap { display: block; font-size: 10.5px; color: var(--text-3); letter-spacing: .04em; }
.mv { font-size: 12px; font-variant-numeric: tabular-nums; }
/* The movement pair is stated in both themes rather than left to a token: green
   and red have to stay green and red, and both of these hold on white and on
   #111 (checked against the same contrast bar the data-series pair was). */
.mv-up { color: #2E9E5B; }
.mv-down { color: #C0453F; }
.theme-light .mv-up { color: #1F7A45; }
.theme-light .mv-down { color: #A8332E; }
.mv-same, .mv-new { color: var(--text-3); }
.chart-note { margin: 20px 0 0; font-size: 12.5px; line-height: 1.6; color: var(--text-2); }
.chart-note a { color: var(--text); }

@media (max-width: 620px) {
  .ct-row { grid-template-columns: 28px 40px 1fr 50px; }
  .ct-n:last-child { display: none; }   /* the DJ count folds first; sets is the ranking */
}
