/* Shared styles for Mills product pages (products/*.html) */

:root {
    --color-dark: #1c1917;
    --color-dark-alt: #292524;
    --color-text: #EBE6DD;
    --color-text-muted: #C5C0B6;
    --color-button-text: #3d3935;
    --accent: #EBE6DD;
    --accent-soft: rgba(235, 230, 221, 0.13);
    --accent-glow: rgba(235, 230, 221, 0.22);
    --border-faint: rgba(235, 230, 221, 0.1);
    --border-mid: rgba(235, 230, 221, 0.2);
    /* Product hue — the spot-market teal from the live Atlas map */
    --spot: #2dd4bf;
    --spot-dim: rgba(45, 212, 191, 0.55);
    --font-display: 'EB Garamond', serif;
    --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    --font-mono: 'JetBrains Mono', 'SF Mono', 'Consolas', monospace;
}

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

html {
    scroll-behavior: smooth;
}

body {
    font-family: var(--font-body);
    color: var(--color-text);
    background: var(--color-dark);
    overflow-x: hidden;
}

img {
    display: block;
}

a {
    color: inherit;
}

:focus-visible {
    outline: 2px solid var(--color-text);
    outline-offset: 3px;
    border-radius: 4px;
}

/* Gutters are longhand on purpose: .wrap shares an element with .pp-hero and the
   other section classes, which own the vertical axis. Equal specificity means
   whichever rule is written later wins the whole shorthand — a `padding` shorthand
   here (or in the 820px override) silently zeroes their padding-top/bottom. Each
   axis stays with one owner. */
.wrap {
    max-width: 1240px;
    margin: 0 auto;
    padding-left: 40px;
    padding-right: 40px;
}

/* ---- Eyebrow with tick ---- */
.eyebrow {
    display: inline-flex;
    align-items: flex-start;
    gap: 10px;
    font-family: var(--font-body);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    line-height: 1.2;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

/* Tick holds 18px and stays on the first line when a long category wraps.
   A two-part label wraps its whole string in .eyebrow-text for the same reason:
   one flex item wraps as one block, whereas bare halves would sit side by side and
   wrap internally. */
.eyebrow::before {
    content: '';
    flex: none;
    width: 18px;
    height: 2px;
    margin-top: 0.5em;
    background: var(--accent);
    opacity: 0.9;
}

/* One separator treatment site-wide — the mark recedes, the terms it divides don't.
   Contexts add their own spacing; see .pp-breadcrumb .sep. */
.sep {
    opacity: 0.5;
}

/* ---- Buttons ---- */
.btn {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    font-family: var(--font-body);
    font-size: 0.9375rem;
    font-weight: 500;
    text-decoration: none;
    padding: 15px 26px;
    border-radius: 8px;
    transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease, background 0.3s ease, border-color 0.3s ease;
}

.btn svg {
    width: 17px;
    height: 17px;
    transition: transform 0.25s ease;
}

.btn-primary {
    color: var(--color-button-text);
    background: var(--color-text);
}

.btn-primary:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3), 0 0 40px var(--accent-glow);
}

.btn-primary:hover svg {
    transform: translateX(3px);
}

.btn-ghost {
    color: var(--color-text);
    background: transparent;
    border: 1px solid var(--border-mid);
}

.btn-ghost:hover {
    border-color: rgba(235, 230, 221, 0.45);
    background: rgba(235, 230, 221, 0.06);
}

/* ---- Nav ---- */
/* These .wrap elements set vertical rhythm with longhand only — the other half of
   the one-owner-per-axis rule documented at .wrap. */
/* The wordmark rides .wrap so it starts the same vertical edge as the eyebrow, the
   title, the lede and everything below it. The legal pages' header mirrors this rule;
   see the note there before moving either. */
/* Centred rather than baselined. EB Garamond's low x-height sits the wordmark's mass
   well below its ascenders, so a baseline-matched breadcrumb reads as sagging even
   though the baselines agree to the pixel. Centring is what looks aligned here. */
.pp-nav {
    display: flex;
    align-items: center;
    gap: 20px;
    padding-top: 28px;
    padding-bottom: 28px;
}

.pp-logo {
    font-family: var(--font-display);
    font-size: 1.75rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-decoration: none;
}

.pp-breadcrumb {
    /* The one knob for how the breadcrumb sits against the wordmark: larger drops it,
       smaller lifts it. Neither end is right — 0 leaves it optically centred, which
       floats above the wordmark's mass (EB Garamond's x-height sits low under tall
       ascenders), and 5px puts it back on the shared baseline, which sags. The rule
       below subtracts this, so tuning it never moves the rule. */
    --breadcrumb-drop: 3px;
    position: relative;
    top: var(--breadcrumb-drop);
    /* 1px rule + 20px, matching .pp-nav's gap on the wordmark side */
    padding-left: 21px;
    font-size: 0.875rem;
    color: var(--color-text-muted);
}

/* Rule bracketing the wordmark, which the breadcrumb otherwise just floats beside.
   It clears the wordmark's ink at 28px, and is a step brighter than --border-mid,
   which disappears at 1px wide. Out of flow so its height can't drag the text. */
.pp-breadcrumb::before {
    content: '';
    position: absolute;
    left: 0;
    top: calc(50% - var(--breadcrumb-drop));
    transform: translateY(-50%);
    width: 1px;
    height: 28px;
    background: rgba(235, 230, 221, 0.3);
}

.pp-breadcrumb a {
    color: var(--color-text-muted);
    text-decoration: none;
    transition: color 0.25s ease;
}

.pp-breadcrumb a:hover {
    color: var(--color-text);
}

.pp-breadcrumb .sep {
    margin: 0 8px;
}

.pp-breadcrumb .here {
    color: var(--color-text);
}

/* ---- Hero ---- */
.pp-hero {
    padding-top: 64px;
    padding-bottom: 48px;
}

.pp-hero .eyebrow {
    margin-bottom: 20px;
}

.pp-title-row {
    display: flex;
    align-items: center;
    gap: 20px;
    flex-wrap: wrap;
    margin-bottom: 22px;
}

.pp-title {
    font-family: var(--font-display);
    font-size: 4.25rem;
    font-weight: 500;
    line-height: 1;
    letter-spacing: -0.02em;
}

/* For a product whose name is a description rather than a word */
.pp-title-long {
    font-size: 2.875rem;
    line-height: 1.1;
    max-width: 620px;
}

.pp-badge {
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    border: 1px solid var(--border-mid);
    padding: 6px 12px;
    border-radius: 999px;
    transform: translateY(4px);
}

.pp-lede {
    font-size: 1.375rem;
    line-height: 1.6;
    color: var(--color-text-muted);
    max-width: 620px;
    margin-bottom: 36px;
}

.pp-lede strong {
    color: var(--color-text);
    font-weight: 500;
}

.pp-actions {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 40px;
}

.pp-stats {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    font-size: 0.8125rem;
    color: var(--color-text-muted);
}

.pp-stats .dot {
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: rgba(235, 230, 221, 0.35);
}

/* Hero manifest — for a hero that inventories what's on the map rather than
   making three claims about it. Too many items to read as a dotted run-on. */
.pp-manifest {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    max-width: 960px;
}

.pp-manifest span {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: 0.8125rem;
    color: var(--color-text-muted);
    border: 1px solid var(--border-faint);
    border-radius: 999px;
    padding: 6px 12px 6px 10px;
}

.pp-manifest svg {
    width: 13px;
    height: 13px;
    flex: none;
    color: var(--glyph);
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Glyph palette — the Atlas app's own icon assignments (entityDisplay.ts), so
   a teal truck here is a teal truck on the map. The app paints on bone, this
   page on near-black, so each hue is carried up into the 400 band; the 600/700
   steps it ships go muddy against the dark. Hue identity is what travels, not
   the exact step. Storage tanks and people share the app's neutral stone; the
   Truck repeats on terminals and trucking routes because the app uses it for
   both. Interconnects have no glyph in the app — they draw as bare junction
   circles stroked in their parent pipelines' colors — so they borrow the
   pipeline violet one step lighter, which is what they are. */
.m-terminals    { --glyph: #2DD4BF; }
.m-refineries   { --glyph: #D97706; }
.m-pipelines    { --glyph: #A78BFA; }
.m-interconnects { --glyph: #C4B5FD; }
.m-ethanol      { --glyph: #92BB81; }
.m-tanks        { --glyph: #A8A29E; }
.m-railways     { --glyph: #CA9A72; }
.m-marine       { --glyph: #38BDF8; }
.m-trucking     { --glyph: #D4A017; }
.m-companies    { --glyph: #FB923C; }
.m-people       { --glyph: #A8A29E; }
.m-specs        { --glyph: #2DD4BF; }
.m-zones        { --glyph: #60A5FA; }
.m-rack         { --glyph: #2DD4BF; }
.m-spot         { --glyph: #C084FC; }

/* ---- Demo media frame ---- */
.pp-demo {
    padding-bottom: 96px;
}

.pp-frame {
    border: 1px solid rgba(235, 230, 221, 0.14);
    border-radius: 16px;
    background: linear-gradient(165deg, rgba(41, 37, 36, 0.75) 0%, rgba(28, 25, 23, 0.95) 100%);
    box-shadow: 0 30px 70px rgba(0, 0, 0, 0.45), 0 0 90px rgba(235, 230, 221, 0.04);
    overflow: hidden;
}

.pp-frame-bar {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 16px;
    border-bottom: 1px solid rgba(235, 230, 221, 0.08);
}

.pp-frame-bar .dots {
    display: flex;
    gap: 6px;
}

.pp-frame-bar .dots span {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: rgba(235, 230, 221, 0.16);
}

.pp-frame-bar .url {
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    color: var(--color-text-muted);
    opacity: 0.7;
    margin-left: 10px;
}

.pp-frame-viewport {
    position: relative;
    aspect-ratio: 1512 / 855;
    overflow: hidden;
    background: var(--color-dark-alt);
}

.pp-frame-viewport img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: top center;
    transition: opacity 0.28s ease;
}

.pp-frame-viewport img.is-swapping {
    opacity: 0;
}

/* Awaiting a screenshot: show the frame with a caption rather than a broken
   image. Dropping the file at the img's src is all it takes to go live. */
.pp-frame-viewport.is-missing img {
    display: none;
}

.pp-frame-viewport.is-missing::after {
    content: attr(data-placeholder);
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 24px;
    text-align: center;
    font-size: 0.8125rem;
    color: var(--color-text-muted);
    opacity: 0.45;
}

/* ---- Data sheet ---- */
/* Where a product's demo is a data set rather than a screenshot. Sits inside
   .pp-frame in place of .pp-frame-viewport: rows set their own height, so the
   sheet is capped rather than held to an image's aspect ratio, and it scrolls
   inside the chrome on both axes the way a sheet does. Markup is generated —
   see scripts/gen_previews.py. */
.ss-scroll {
    position: relative;
    overflow: auto;
    max-height: 620px;
    background: var(--color-dark-alt);
    /* The sheet swallows the wheel, so without this the page jumps once the
       sheet hits its own end. */
    overscroll-behavior: contain;
    scrollbar-width: thin;
    scrollbar-color: rgba(235, 230, 221, 0.2) transparent;
}

.ss-scroll::-webkit-scrollbar {
    width: 6px;
    height: 6px;
}

.ss-scroll::-webkit-scrollbar-thumb {
    background: rgba(235, 230, 221, 0.2);
    border-radius: 999px;
}

/* separate, not collapse: a collapsed table drops the borders off sticky cells
   as they scroll, and every cell in the header rows and the gutter is sticky. */
.ss-table {
    border-collapse: separate;
    border-spacing: 0;
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.ss-table th,
.ss-table td {
    height: 30px;
    padding: 0 12px;
    text-align: left;
    font-weight: 400;
    border-right: 1px solid rgba(235, 230, 221, 0.05);
    border-bottom: 1px solid rgba(235, 230, 221, 0.05);
}

/* Column letters — the strip that says "spreadsheet" before a value is read */
.ss-cols th {
    position: sticky;
    top: 0;
    z-index: 3;
    height: 21px;
    padding: 0;
    text-align: center;
    font-size: 0.5625rem;
    font-weight: 500;
    letter-spacing: 0.08em;
    color: rgba(235, 230, 221, 0.3);
    background: #232020;
}

/* The field names are the CSV's own header line, so they stay lowercase
   snake_case rather than taking the uppercase table-head treatment the coverage
   matrix uses — this row is the file's first line, quoted. */
.ss-fields th {
    position: sticky;
    top: 21px;
    z-index: 3;
    color: var(--color-text-muted);
    font-weight: 500;
    background: #262322;
    border-bottom: 1px solid var(--border-mid);
}

/* Every cell paints from a per-row variable, so hover reaches the sticky gutter
   and key column too — they'd otherwise scroll their own opaque background over
   the highlight. */
.ss-table tbody tr {
    --cell: var(--color-dark-alt);
    --gutter: #232020;
}

.ss-table tbody tr:hover {
    --cell: #2f2b29;
    --gutter: #35302d;
}

.ss-table tbody th,
.ss-table tbody td {
    background: var(--cell);
    transition: background 0.15s ease;
}

.ss-rownum {
    position: sticky;
    left: 0;
    z-index: 2;
    width: 34px;
    min-width: 34px;
    padding: 0;
    text-align: center;
    font-size: 0.5625rem;
    color: rgba(235, 230, 221, 0.3);
    background: var(--gutter) !important;
}

/* The key column is frozen: scrolling right to read terms or a timestamp keeps
   the code that owns the row on screen. */
.ss-key {
    position: sticky;
    left: 34px;
    z-index: 2;
}

/* Corner cells outrank both sticky axes */
.ss-corner {
    position: sticky;
    left: 0;
    z-index: 4;
    width: 34px;
    min-width: 34px;
    padding: 0;
    background: #232020;
}

.ss-fields .ss-corner {
    background: #262322;
}

/* The app's price-code pill (PriceCodeBadge), carried onto the dark register */
.ss-code {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 0.625rem;
    letter-spacing: 0.01em;
    color: var(--color-text);
    background: rgba(235, 230, 221, 0.09);
}

.ss-num {
    text-align: right;
}

.ss-val {
    color: var(--color-text);
    font-weight: 500;
}

/* Signed deltas keep the app's price semantics: explicit sign, true minus,
   tabular figures. Both hues are lifted off the app's green-600/red-600, which
   go muddy on near-black; the green is the step the coverage matrix below
   already proved on this background. */
.ss-up { color: #6FBF8E; }
.ss-dn { color: #E28981; }

.ss-null {
    color: rgba(235, 230, 221, 0.28);
}

.ss-muted {
    color: var(--color-text-muted);
}

/* ---- Sample report reader ---- */
/* Preview image up front, live PDF only once asked for: the sample is several
   megabytes, too much to spend on visitors who scroll past. */
.pp-reader-cta {
    position: absolute;
    inset: auto 0 0 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    padding: 120px 24px 30px;
    background: linear-gradient(to bottom,
        rgba(28, 25, 23, 0) 0%,
        rgba(28, 25, 23, 0.55) 38%,
        rgba(28, 25, 23, 0.92) 72%,
        var(--color-dark) 100%);
}

.pp-reader-btn {
    border: 0;
    cursor: pointer;
}

.pp-reader-btn:hover svg {
    transform: translateY(2px);
}

.pp-reader-meta {
    font-size: 0.8125rem;
    color: var(--color-text-muted);
    opacity: 0.7;
}

/* The embedded viewer swallows wheel events, so it needs a way out that doesn't
   require scrolling all 17 pages to reach the rest of the page. */
.pp-reader-close {
    display: none;
    align-items: center;
    gap: 6px;
    margin-left: auto;
    padding: 4px 10px;
    font-size: 0.8125rem;
    color: var(--color-text-muted);
    background: transparent;
    border: 1px solid var(--border-faint);
    border-radius: 999px;
    cursor: pointer;
    transition: color 0.25s ease, border-color 0.25s ease;
}

.pp-reader-close:hover {
    color: var(--color-text);
    border-color: var(--border-mid);
}

.pp-reader-close svg {
    width: 12px;
    height: 12px;
}

.pp-frame.is-reading .pp-reader-close {
    display: inline-flex;
}

.pp-frame.is-reading .pp-reader {
    aspect-ratio: auto;
    height: min(86vh, 1080px);
}

.pp-frame.is-reading .pp-reader > img,
.pp-frame.is-reading .pp-reader-cta {
    display: none;
}

.pp-reader iframe {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
}

.pp-views {
    display: flex;
    justify-content: center;
    gap: 10px;
    margin-top: 22px;
    flex-wrap: wrap;
}

.pp-view-chip {
    font-family: var(--font-body);
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--color-text-muted);
    background: transparent;
    border: 1px solid var(--border-faint);
    border-radius: 999px;
    padding: 9px 18px;
    cursor: pointer;
    transition: color 0.25s ease, border-color 0.25s ease, background 0.25s ease;
}

.pp-view-chip:hover {
    color: var(--color-text);
    border-color: var(--border-mid);
}

.pp-view-chip.active {
    color: var(--color-text);
    background: var(--accent-soft);
    border-color: var(--border-mid);
}

/* ---- Sections ---- */
.pp-section {
    padding-bottom: 96px;
}

.pp-h2 {
    font-family: var(--font-display);
    font-size: 2.125rem;
    font-weight: 500;
    letter-spacing: -0.01em;
    margin-bottom: 8px;
    width: fit-content;
    max-width: 100%;
}

.pp-h2::after {
    content: '';
    display: block;
    width: 100%;
    height: 2px;
    background: linear-gradient(90deg, transparent, var(--accent), transparent);
    margin-top: 16px;
    opacity: 0.9;
}

/* ---- Overview ---- */
.pp-overview-grid {
    display: grid;
    grid-template-columns: 300px 1fr;
    gap: 64px;
    align-items: start;
}

.pp-overview-copy p {
    font-size: 1.0625rem;
    line-height: 1.85;
    color: var(--color-text-muted);
}

.pp-overview-copy p + p {
    margin-top: 22px;
}

.pp-overview-copy strong {
    color: var(--color-text);
    font-weight: 500;
}

/* ---- Key information ---- */
.pp-key-grid {
    display: grid;
    grid-template-columns: 1.55fr 1fr;
    gap: 72px;
    align-items: start;
    margin-top: 44px;
}

.pp-sub {
    font-family: var(--font-display);
    font-size: 1.375rem;
    font-weight: 500;
    margin-bottom: 20px;
}

.pp-block + .pp-block {
    margin-top: 48px;
}

/* Use-case list — markers echo the dashed spot-market rings on the Atlas map */
.pp-usecases {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 18px;
}

.pp-usecases li {
    position: relative;
    padding-left: 32px;
    font-size: 1rem;
    line-height: 1.7;
    color: var(--color-text-muted);
}

.pp-usecases li strong {
    color: var(--color-text);
    font-weight: 500;
}

/* --marker-top aligns the ring with the first line of whichever block wears it */
.pp-usecases li,
.pp-usecase-answer {
    --marker-top: 7px;
}

.pp-usecases li::before,
.pp-usecase-answer::before {
    content: '';
    position: absolute;
    left: 0;
    top: var(--marker-top);
    width: 11px;
    height: 11px;
    border-radius: 50%;
    border: 1.4px dashed var(--spot-dim);
}

.pp-usecases li::after,
.pp-usecase-answer::after {
    content: '';
    position: absolute;
    left: 4.2px;
    top: calc(var(--marker-top) + 4.2px);
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: var(--spot);
}

/* Use-case story card — a titled problem, then what Mills does about it */
.pp-usecase-list {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.pp-usecase {
    border: 1px solid var(--border-faint);
    border-radius: 14px;
    background: linear-gradient(165deg, rgba(41, 37, 36, 0.5) 0%, rgba(28, 25, 23, 0.75) 100%);
    padding: 24px 26px;
}

.pp-usecase-title {
    font-family: var(--font-display);
    font-size: 1.25rem;
    font-weight: 500;
    letter-spacing: -0.01em;
    margin-bottom: 14px;
}

.pp-usecase-problem {
    font-size: 0.9375rem;
    line-height: 1.75;
    color: var(--color-text-muted);
}

.pp-usecase-answer {
    position: relative;
    margin-top: 16px;
    padding: 16px 0 0 32px;
    border-top: 1px solid rgba(235, 230, 221, 0.07);
    font-size: 0.9375rem;
    line-height: 1.75;
    color: var(--color-text);
    --marker-top: 23px;
}

.pp-benefits {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.pp-benefits li {
    font-size: 1rem;
    line-height: 1.7;
    color: var(--color-text-muted);
    padding-left: 32px;
    position: relative;
}

.pp-benefits li::before {
    content: '';
    position: absolute;
    left: 2px;
    top: 13px;
    width: 14px;
    height: 2px;
    background: var(--accent);
    opacity: 0.7;
}

.pp-benefits strong {
    color: var(--color-text);
    font-weight: 600;
}

/* Heading sits outside the card so it lines up with the left column's heading,
   and travels with the card as one sticky unit */
.pp-details-col {
    position: sticky;
    top: 32px;
}

/* Details card — set like a line in the rack price report */
.pp-details {
    border: 1px solid var(--border-faint);
    border-radius: 14px;
    background: linear-gradient(165deg, rgba(41, 37, 36, 0.6) 0%, rgba(28, 25, 23, 0.9) 100%);
    padding: 14px 26px;
}

.pp-detail-row {
    display: flex;
    align-items: baseline;
    gap: 12px;
    padding: 13px 0;
}

.pp-detail-row + .pp-detail-row {
    border-top: 1px solid rgba(235, 230, 221, 0.07);
}

.pp-detail-key {
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--color-text-muted);
    white-space: nowrap;
}

.pp-detail-leader {
    flex: 1;
    border-bottom: 1px dotted rgba(235, 230, 221, 0.22);
    transform: translateY(-3px);
}

.pp-detail-val {
    font-size: 0.8125rem;
    color: var(--color-text);
    text-align: right;
    max-width: 55%;
    line-height: 1.55;
}

.pp-detail-val a {
    color: var(--spot);
    text-decoration: none;
    border-bottom: 1px dotted var(--spot-dim);
    transition: opacity 0.25s ease;
}

.pp-detail-val a:hover {
    opacity: 0.8;
}

/* Qualifies a value that would otherwise overclaim. Only Gauge uses it: its
   cadence and history describe the Mills benchmark data, not the customer's
   contract library. Emitted by gen_details.py, never hand-typed. */
.pp-detail-mark {
    color: var(--spot);
    padding-left: 3px;
}

.pp-details-note {
    font-size: 0.75rem;
    line-height: 1.6;
    color: var(--color-text-muted);
    margin-top: 14px;
    padding: 0 4px;
}

/* ---- Access and subscription pricing ---- */
.pp-pricing {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    margin-top: 44px;
}

/* Caps the card so a product with a single tier doesn't stretch to full width */
.pp-plan {
    position: relative;
    flex: 1 1 220px;
    max-width: 320px;
    border: 1px solid var(--border-faint);
    border-radius: 14px;
    background: linear-gradient(165deg, rgba(41, 37, 36, 0.6) 0%, rgba(28, 25, 23, 0.9) 100%);
    padding: 22px 24px;
}

.pp-plan-name {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    margin-bottom: 14px;
}

.pp-plan-price {
    font-family: var(--font-display);
    font-size: 1.625rem;
    font-weight: 500;
    line-height: 1.15;
    color: var(--color-text);
}

.pp-plan-term {
    font-size: 0.8125rem;
    color: var(--color-text-muted);
    margin-top: 8px;
}

.pp-pricing-note {
    font-size: 0.875rem;
    line-height: 1.7;
    color: var(--color-text-muted);
    margin-top: 20px;
    max-width: 640px;
}

/* Linked tiers — the standard tier opens a pricing email, the MMN tier links
   through to the network section. On MMN the tooltip is a pointer-only extra, so
   touch still gets the full story on tap-through. */
a.pp-plan {
    display: block;
    text-decoration: none;
    transition: border-color 0.3s ease, transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

a.pp-plan:hover {
    border-color: var(--border-mid);
    transform: translateY(-2px);
}

/* Sits inside .pp-plan-name, so it has to opt out of that label's uppercasing and
   letter-spacing: the first turns the info "i" into a capital, the second adds
   trailing space that pushes the lone glyph off-centre. */
.pp-plan-info {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    border: 1px solid var(--border-mid);
    font-size: 0.6875rem;
    font-style: normal;
    line-height: 1;
    letter-spacing: 0;
    text-transform: none;
    color: var(--color-text-muted);
}

.pp-tip {
    position: absolute;
    left: 24px;
    right: 24px;
    bottom: calc(100% - 8px);
    z-index: 3;
    border: 1px solid var(--border-mid);
    border-radius: 10px;
    background: var(--color-dark-alt);
    box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5);
    padding: 14px 16px;
    font-size: 0.8125rem;
    line-height: 1.65;
    color: var(--color-text-muted);
    /* visibility, not opacity alone — an opacity:0 span stays in the
       accessibility tree and gets concatenated into the link's name, so the
       whole tip would be announced as part of "MMN Member Subscription". */
    opacity: 0;
    visibility: hidden;
    transform: translateY(4px);
    pointer-events: none;
    transition: opacity 0.25s ease, transform 0.25s ease, visibility 0s linear 0.25s;
}

.pp-plan:hover .pp-tip,
.pp-plan:focus-visible .pp-tip {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
    transition-delay: 0s;
}

/* ---- Product components ---- */
/* The marks carry their own palette from the shared block below; the only colour
   this wrapper owns is the wash behind the focused column, which stays bone so
   it never competes with the level a cell is reporting. */
.pp-matrix {
    --spot-wash: rgba(235, 230, 221, 0.08);
    margin-top: 44px;
}

/* Column 2 is the page's own product: gen_components.py always emits the
   focused product first, which is also what keeps it beside the sticky
   component names instead of stranded off-screen at phone width. */
.pp-matrix .mx-table thead th:nth-child(2),
.pp-matrix .mx-table tbody td:nth-child(2) {
    background: var(--spot-wash);
}

.pp-matrix .mx-table thead th:nth-child(2) {
    color: var(--color-text);
}

/* BEGIN shared: component-matrix */
/* Everything below is mirrored verbatim into index.html's inline <style> by
   scripts/gen_components.py — edit it here and re-run that. The palette is
   deliberately excluded: each surface scopes --spot itself, because this file's
   :root teal and index.html's map verdigris are both wrong for the marks. */

/* Focusable: the table always overflows below ~960px, and the tbody holds
   no focusable content to pan it with */
.mx-scroll {
    overflow-x: auto;
    padding-bottom: 10px;
    scrollbar-width: thin;
    scrollbar-color: rgba(235, 230, 221, 0.2) transparent;
}

.mx-scroll:focus-visible {
    outline: 2px solid var(--color-text);
    outline-offset: 4px;
    border-radius: 4px;
}

.mx-scroll::-webkit-scrollbar {
    height: 5px;
}

.mx-scroll::-webkit-scrollbar-thumb {
    background: rgba(235, 230, 221, 0.2);
    border-radius: 999px;
}

.mx-table {
    width: 100%;
    min-width: 880px;
    border-collapse: collapse;
    font-family: var(--font-body);
    font-size: 0.8125rem;
}

.mx-table th,
.mx-table td {
    text-align: left;
    padding: 11px 14px;
    border-bottom: 1px solid rgba(235, 230, 221, 0.06);
}

.mx-table thead th {
    font-size: 0.625rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    border-bottom: 1px solid rgba(235, 230, 221, 0.2);
    white-space: nowrap;
    vertical-align: bottom;
}

.mx-table thead th a {
    color: inherit;
    text-decoration: none;
    transition: color 0.25s ease;
}

.mx-table thead th a:hover {
    color: var(--color-text);
}

/* Component names stay put while the product columns scroll under them */
.mx-table thead th:first-child,
.mx-table tbody th {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--color-dark);
}

.mx-table th[scope="row"] {
    font-weight: 400;
    color: var(--color-text);
    white-space: nowrap;
}

/* Unwrapped, the longest component name ("Fuel Regulation Zones (RFG, CARB,
   CBG, TxLED)") holds this column at ~327px. On a 390px screen that is the
   whole scroller, so the first product column — the page's own, and the one
   the wash is on — opens as a 19px sliver at the right edge. Letting the names
   wrap under that width puts it back on screen at rest. */
@media (max-width: 560px) {
    .mx-table th[scope="row"] {
        white-space: normal;
        max-width: 155px;
    }
}

/* .mx-table qualifier outweighs the sticky rule above, which would
   otherwise leave the section label's cell sharing the sticky background */
.mx-table .mx-group th {
    position: static;
    padding-top: 15px;
    padding-bottom: 15px;
    font-size: 0.625rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    white-space: nowrap;
    background: rgba(235, 230, 221, 0.04);
}

/* The cell spans all 7 columns so the band reads across the full table;
   the label itself sticks so panning right never scrolls it out of view */
.mx-group th span {
    position: sticky;
    left: 14px;
    display: inline-block;
}

/* Coverage palette, running full to unavailable. Each level carries a hue and
   its own word, so neither channel is load-bearing alone: the table still reads
   in greyscale, and green and amber converging under deuteranopia costs nothing
   because the label already said which is which. Dropping either one would put
   the whole matrix on a single channel. */
.mx-lv-in      { --c: #6FBF8E; }
.mx-lv-partial { --c: #E2A85C; }
.mx-lv-soon    { --c: #7E96B0; }
/* Lifted off the stone gray it started on to clear 4.5:1 against --color-dark at
   this size. It is the quietest level, but it is still text someone has to read. */
.mx-lv-none    { --c: #8A837A; }

.mx-cell {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    white-space: nowrap;
    font-size: 0.75rem;
    color: var(--c);
}

/* A dot means the product carries something and its colour says how much, so
   Not included has none at all — the gap is the signal and the word only names
   it. Nothing here is announced by aria: the cell is real text. */
.mx {
    flex: none;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--c);
}

/* The cells name their own level, so this glosses rather than decodes — the dot
   identifies the row and the muted text carries what the word actually means. */
.mx-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 10px 26px;
    margin-top: 24px;
    font-family: var(--font-body);
    font-size: 0.75rem;
    color: var(--color-text-muted);
}

.mx-legend > span {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}
/* END shared: component-matrix */

.pp-comp-note {
    font-size: 0.875rem;
    line-height: 1.7;
    color: var(--color-text-muted);
    margin-top: 26px;
    max-width: 640px;
}

.pp-comp-note a {
    color: var(--spot);
    text-decoration: none;
    border-bottom: 1px dotted var(--spot-dim);
}

/* Where a component maps across the line rather than within one product */
.pp-comp-table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 44px;
    font-size: 0.875rem;
}

.pp-comp-table th,
.pp-comp-table td {
    text-align: left;
    padding: 14px 16px;
    border-bottom: 1px solid rgba(235, 230, 221, 0.07);
}

.pp-comp-table thead th {
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    border-bottom-color: var(--border-mid);
}

.pp-comp-table tbody th {
    font-weight: 500;
    color: var(--color-text);
}

.pp-comp-table td {
    color: var(--color-text-muted);
}

.pp-comp-table td:empty::after {
    content: '—';
    opacity: 0.4;
}

/* ---- Related products (reuses the tile component) ---- */
/* Registered so the rail's edge fades can transition; an unregistered custom
   property inside a gradient snaps between values instead. */
@property --rail-fade-l {
    syntax: '<length>';
    inherits: false;
    initial-value: 0px;
}

@property --rail-fade-r {
    syntax: '<length>';
    inherits: false;
    initial-value: 0px;
}

/* Rail: three tiles at a time with the next one peeking, so the scroll reads.
   Flex over grid — auto-placed grid tracks size down to fit the container
   instead of overflowing it, which collapses the rail into a squeezed row. */
.pp-related .tiles-rail {
    display: flex;
    gap: 24px;
    margin-top: 34px;
    /* Top padding buys room for the tile's hover lift — a scroll container
       clips at its padding box, and overflow-x:auto forces the y axis too. */
    padding: 10px 0 16px;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    scrollbar-width: thin;
    scrollbar-color: rgba(235, 230, 221, 0.2) transparent;
    /* The peeking tile dissolves into the page rather than being cut off at the
       rail's edge. Masking the rail itself needs no markup, where an overlay
       would need a positioned wrapper on all seven product pages. A side only
       fades while it has somewhere left to scroll — data-overflow is set from
       product.js. */
    --rail-fade: 64px;
    --rail-fade-l: 0px;
    --rail-fade-r: 0px;
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 var(--rail-fade-l), #000 calc(100% - var(--rail-fade-r)), transparent 100%);
    mask-image: linear-gradient(90deg, transparent 0, #000 var(--rail-fade-l), #000 calc(100% - var(--rail-fade-r)), transparent 100%);
    transition: --rail-fade-l 200ms ease, --rail-fade-r 200ms ease;
}

.pp-related .tiles-rail[data-overflow="start"],
.pp-related .tiles-rail[data-overflow="both"] {
    --rail-fade-l: var(--rail-fade);
}

.pp-related .tiles-rail[data-overflow="end"],
.pp-related .tiles-rail[data-overflow="both"] {
    --rail-fade-r: var(--rail-fade);
}

.pp-related .tile {
    flex: 0 0 calc((100% - 3 * 24px) / 3.2);
    scroll-snap-align: start;
}

.pp-related .tiles-rail::-webkit-scrollbar {
    height: 5px;
}

.pp-related .tiles-rail::-webkit-scrollbar-thumb {
    background: rgba(235, 230, 221, 0.2);
    border-radius: 999px;
}

.tile {
    position: relative;
    display: flex;
    flex-direction: column;
    background: linear-gradient(165deg, rgba(41, 37, 36, 0.7) 0%, rgba(28, 25, 23, 0.9) 100%);
    border: 1px solid var(--border-faint);
    border-radius: 14px;
    overflow: hidden;
    text-decoration: none;
    transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), border-color 0.4s ease, box-shadow 0.4s ease;
}

.tile:hover {
    transform: translateY(-6px);
    border-color: var(--border-mid);
    box-shadow: 0 24px 48px rgba(0, 0, 0, 0.4), 0 0 80px rgba(235, 230, 221, 0.04);
}

.tile-media {
    position: relative;
    aspect-ratio: 16 / 11;
    overflow: hidden;
    background: var(--color-dark-alt);
}

.tile-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: top center;
    filter: brightness(0.82) saturate(0.92);
    transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1), filter 0.6s ease;
}

.tile:hover .tile-media img {
    transform: scale(1.04);
    filter: brightness(0.92) saturate(1);
}

.tile-media::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(28, 25, 23, 0.18) 0%, transparent 32%, transparent 48%, rgba(28, 25, 23, 0.94) 100%);
    pointer-events: none;
}

.tile-media.is-missing img {
    display: none;
}

/* Bottom padding clears the tile name overlaid at the foot of the media */
.tile-media.is-missing::before {
    content: attr(data-placeholder);
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 20px 44px;
    text-align: center;
    font-size: 0.8125rem;
    color: var(--color-text-muted);
    opacity: 0.4;
}

.tile-badge {
    position: absolute;
    top: 12px;
    left: 12px;
    z-index: 2;
    font-size: 0.625rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--color-text);
    background: rgba(28, 25, 23, 0.65);
    border: 1px solid rgba(235, 230, 221, 0.25);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    padding: 5px 10px;
    border-radius: 999px;
}

.tile-name {
    position: absolute;
    left: 20px;
    right: 20px;
    bottom: 14px;
    z-index: 2;
    font-family: var(--font-display);
    font-size: 1.75rem;
    font-weight: 500;
    letter-spacing: -0.01em;
    color: var(--color-text);
    line-height: 1.1;
}

.tile-body {
    display: flex;
    flex-direction: column;
    flex: 1;
    padding: 18px 20px 20px;
    border-top: 1px solid rgba(235, 230, 221, 0.07);
}

.tile .eyebrow {
    font-size: 0.6875rem;
    margin-bottom: 12px;
}

/* No flex:1 here — stretching a -webkit-box taller than its clamp makes it paint
   the lines past the ellipsis. The footer claims the slack instead. */
.tile-desc {
    font-size: 0.9rem;
    line-height: 1.6;
    color: var(--color-text-muted);
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-bottom: 18px;
}

.tile-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-top: auto;
}

.tile-learn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 0.875rem;
    font-weight: 500;
    color: var(--color-text);
}

.tile-learn svg {
    width: 16px;
    height: 16px;
    transition: transform 0.3s ease;
}

.tile:hover .tile-learn svg {
    transform: translateX(4px);
}

.tile-app {
    position: relative;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--color-text-muted);
    text-decoration: none;
    padding: 6px 10px;
    margin: -6px -10px;
    border-radius: 6px;
    transition: color 0.25s ease, background 0.25s ease;
}

.tile-app:hover {
    color: var(--color-text);
    background: var(--accent-soft);
}

.tile-app svg {
    width: 13px;
    height: 13px;
}

/* ---- Closing CTA band ---- */
.pp-cta-band {
    border-top: 1px solid rgba(235, 230, 221, 0.12);
    padding: 96px 0;
    text-align: center;
}

.pp-cta-band h2 {
    font-family: var(--font-display);
    font-size: 2.75rem;
    font-weight: 500;
    letter-spacing: -0.01em;
    margin-bottom: 14px;
}

.pp-cta-band p {
    font-size: 1.125rem;
    color: var(--color-text-muted);
    margin-bottom: 36px;
}

.pp-cta-band .pp-actions {
    justify-content: center;
    margin-bottom: 0;
}

/* ---- Footer ---- */
.pp-footer {
    border-top: 1px solid rgba(235, 230, 221, 0.12);
    padding: 40px 20px;
    text-align: center;
}

.pp-footer-links {
    margin-bottom: 20px;
}

.pp-footer-links a {
    color: var(--color-text-muted);
    text-decoration: none;
    margin: 0 16px;
    font-size: 0.875rem;
    transition: color 0.3s ease;
}

.pp-footer-links a:hover {
    color: var(--color-text);
}

.pp-footer p {
    font-family: var(--font-body);
    font-size: 0.875rem;
    color: var(--color-text-muted);
}

/* ---- Responsive ---- */
@media (max-width: 1024px) {
    .pp-key-grid {
        gap: 48px;
    }

    .pp-overview-grid {
        grid-template-columns: 1fr;
        gap: 28px;
    }

    .pp-related .tile {
        flex-basis: calc((100% - 2 * 24px) / 2.2);
    }
}

@media (max-width: 820px) {
    /* Longhand — see .wrap above. */
    .wrap {
        padding-left: 20px;
        padding-right: 20px;
    }

    /* The breadcrumb is hidden at this width (see .pp-breadcrumb below), which
       leaves the nav holding nothing but the wordmark. Desktop's 64px under a
       populated nav reads as composed; under a lone wordmark on a phone it reads
       as a void. The nav's own padding-bottom is the whole gap here — the hero
       adds nothing on top of it. */
    .pp-hero {
        padding-top: 0;
    }

    .pp-title {
        font-size: 3rem;
    }

    .pp-title-long {
        font-size: 2.125rem;
    }

    .pp-lede {
        font-size: 1.125rem;
    }

    .pp-key-grid {
        grid-template-columns: 1fr;
    }

    .pp-details-col {
        position: static;
    }

    /* One tile at a time here, so the peek is a much larger share of the rail —
       a desktop-width fade would swallow most of it. */
    .pp-related .tiles-rail {
        --rail-fade: 40px;
    }

    .pp-related .tile {
        flex-basis: 78%;
    }

    .pp-plan {
        max-width: none;
    }

    .pp-frame-viewport {
        aspect-ratio: 1512 / 855;
    }

    /* A landscape sliver of a letter page is unreadable this narrow, and leaves
       the call to action no room to sit. Give the preview page-like proportions. */
    .pp-reader {
        aspect-ratio: 4 / 5;
    }

    .pp-reader-cta {
        padding-top: 72px;
    }

    .pp-breadcrumb {
        display: none;
    }
}

@media (max-width: 640px) {
    .pp-cta-band h2 {
        font-size: 2rem;
    }

    .pp-footer-links a {
        margin: 0 10px;
    }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
    }
}
