/*
    The public site's own layer, on top of the shared tokens.

    tokens.css supplies the palette, the surface treatments and the spacing scale, so the site and
    the launcher stay one product. What it does NOT supply is a display type scale: its --font-title
    is 22px because it was written for a desktop panel where the largest thing on screen is a page
    header. A landing page needs a 68px headline, so that scale is defined here rather than pushed
    back into tokens.css where it would be dead weight for three hosts that will never use it.

    Everything else defers. If a colour appears here as a literal, it is a bug.

    One rule worth stating because it decides a dozen small choices: ACCENT MARKS THE PRICE YOU ARE
    ABOUT TO PAY, not every price on screen. A term you are selecting is accent, the same as in the
    launcher's checkout, because it is the same control seen twice. A "from" price in a listing is
    ordinary text - you are browsing, not committing.
*/

/*
    Space Grotesk, self-hosted rather than linked from a CDN so the page owns every request it makes.
    Latin subset only - the site has no other script - and one variable file spans 400 to 700, so a
    weight is an axis position rather than another download.

    Copyright 2020 The Space Grotesk Project Authors, SIL Open Font License 1.1.
    https://github.com/floriankarsten/space-grotesk
*/
@font-face {
    font-family: "Space Grotesk";
    src: url("../fonts/space-grotesk-latin.woff2") format("woff2");
    font-weight: 400 700;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
    --measure: 1200px;
    --nav-height: 76px;

    /*
        Display face, for the wordmark and every heading. Body text stays on the system stack: it is
        what the launcher renders in, and matching it there is worth more than matching the headings.
        The @font-face block below defines Space Grotesk; the rest of the stack is what shows while
        it loads and if it fails.
    */
    --font-display: "Space Grotesk", "Segoe UI Variable Display", "Segoe UI", system-ui, sans-serif;

    --display-1: clamp(2.75rem, 6.5vw, 4.25rem);
    --display-2: clamp(1.5rem, 2.6vw, 1.875rem);
    --lead: clamp(1rem, 1.4vw, 1.125rem);

    --label: 0.6875rem;
    --label-tracking: 0.09em;
}

*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; }

/*
    Column, so the footer is pushed to the bottom of short pages rather than stopping wherever the
    content ran out. .aurora is fixed and #aurora-sksl is hidden, so neither takes part in the flex
    layout; the three flow children are the nav, main and the footer.
*/
body {
    margin: 0;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    background: var(--page-background);
    color: var(--text-primary);
    font-family: "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system, sans-serif;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}

main { flex: 1 0 auto; display: flex; flex-direction: column; }

h1, h2, h3 { margin: 0; font-family: var(--font-display); line-height: 1.1; letter-spacing: -0.025em; font-weight: 700; }
p { margin: 0; }
a { color: inherit; text-decoration: none; }
img, canvas { display: block; max-width: 100%; }

/*
    FocusOnNavigate hands focus to the page heading so a screen reader lands on it, which the browser
    then rings as though it had been clicked. The tabindex is -1, so nothing here is reachable by Tab
    and no keyboard user loses an indicator.
*/
[tabindex="-1"]:focus { outline: none; }

/* ---------------------------------------------------------------------------------------------
   Background. Fixed so the shader does not scroll with the content, and the gradient underneath is
   the fallback the renderer reveals by hiding the canvas when WebGL is missing or the context is
   lost - the same arrangement the panel uses, for the same reason: a failed background must never
   leave the page on a bare colour.
   --------------------------------------------------------------------------------------------- */

.aurora {
    position: fixed;
    inset: 0;
    z-index: -1;
    background:
        radial-gradient(58% 78% at 6% 42%, #1E6FA84D 0%, transparent 62%),
        radial-gradient(54% 74% at 94% 38%, #7B3FA84D 0%, transparent 62%),
        var(--page-background);
}

.aurora canvas { width: 100%; height: 100%; }

/*
    Backdrop scrim. Not decoration - the panel measured this and the number is theirs: a live Aurora
    frame peaks at rgb(149,208,255), and against that --text-secondary behind a translucent surface
    lands at 3.9:1, under the 4.5 floor. Raising the surface fills until text passes is what turns
    glass into flat dark cards; dimming the backdrop fixes the same number and leaves the shader
    visible and moving, which is also what frosted glass over a light source actually looks like.

    Shaped rather than uniform, which is the correction after two flat attempts both looked wrong.
    The panel damps hard and evenly because it is a dense table with secondary text sitting directly
    on the shader in hundreds of places. This page is the opposite: nearly all of its text lives in
    raised surfaces inside a 1200px measure, and the shader is the only thing on screen carrying any
    colour at all. Dimming it evenly turns the launcher's vivid cyan and magenta into uniform murk -
    which is exactly what happened.

    So the dim follows the content instead. The centre band, where the measure sits, is damped hard;
    the outer edges are left almost untouched, and that is where AuroraTheme puts its curtains
    anyway - its own remarks call the dark centre "where the content sits". The page is reinforcing
    the shader's design rather than fighting it. The vertical pass darkens the foot, where the
    footer's small muted text would otherwise sit on bare shader.
*/
.aurora::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;

    /*
        Alpha is baked into the colour stops rather than set with `opacity`. An opacity property
        applies to the whole layer including the flat fill underneath the gradient, so raising it to
        strengthen the vignette silently strengthens the uniform dim as well - which is how the
        first attempt at this ended up darker than the value it was meant to lighten.
    */
    background:
        linear-gradient(180deg, #0A0E1400 62%, #0A0E14B8 100%),
        linear-gradient(90deg, #0A0E1400 0%, #0A0E14BF 20%, #0A0E14BF 80%, #0A0E1400 100%),
        #0A0E1433;
}

/*
    The same scrim, inverted, where the surface style is light.

    Its job is the contrast of the text that sits DIRECTLY on the shader - the page headings, the
    footer - and under Flat's light palette that text is dark, so darkening the backdrop pushes it
    the wrong way. Shaped identically: hard through the centre band where the measure sits, barely
    there at the edges where the shader's curtains are.

    Carried further than the dark pass because the floor is higher: dark text needs the backdrop
    close to the panel white beside it, where light text only needed the shader's peaks damped.
*/
@media (prefers-color-scheme: light) {
    .theme-flat .aurora::after {
        background:
            linear-gradient(180deg, #F2F2F700 58%, #F2F2F7F2 100%),
            linear-gradient(90deg, #F2F2F700 0%, #F2F2F7F2 18%, #F2F2F7F2 82%, #F2F2F700 100%),
            #F2F2F766;
    }
}

/* ---------------------------------------------------------------------------------------------
   Layout
   --------------------------------------------------------------------------------------------- */

.wrap {
    width: 100%;
    max-width: var(--measure);
    margin-inline: auto;
    padding-inline: var(--space-5);
}

.section { padding-block: clamp(1.75rem, 3vw, 2.75rem); }

/* The first thing under the ribbon needs more room above it than two sections need between them. */
main > .section:first-child:not(.section-center) { padding-top: clamp(2.5rem, 4vw, 3.5rem); }

/*
    A page that is one card and nothing else. It takes the whole space between the ribbon and the
    footer and centres in it, rather than hanging from the top with a screen of empty background
    underneath. Column, so the wrap keeps the block-flow width it has everywhere else.

    Both offsets below are margins that centring would otherwise count as content. The card is the
    only thing on the page, so a trailing margin under it is space with nothing after it to separate,
    and the footer's own gap is already covered by main filling the height - left in, the two of them
    put the card off centre by half their sum.
*/
.section-center { flex: 1; display: flex; flex-direction: column; justify-content: center; }
.section-center > .wrap { margin-bottom: 0; }
main:has(> .section-center) + .footer { margin-top: 0; }

.section-head { text-align: center; margin-bottom: clamp(1.25rem, 2.2vw, 1.75rem); }
.section-head h2 { font-size: var(--display-2); }

.section-head p {
    margin-top: var(--space-2);
    color: var(--text-secondary);
    font-size: 0.9375rem;
}

.label {
    font-size: var(--label);
    font-weight: 700;
    letter-spacing: var(--label-tracking);
    text-transform: uppercase;
    color: var(--text-secondary);
}

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

/* ---------------------------------------------------------------------------------------------
   Navigation
   --------------------------------------------------------------------------------------------- */

.nav {
    position: sticky;
    top: 0;
    z-index: 50;
    height: var(--nav-height);
    display: flex;
    align-items: center;

    /* Only Glass raises this; the opaque surfaces have nothing to see through and use their panel. */
    background: var(--nav-bg, var(--panel-bg));
    border-bottom: 1px solid var(--divider);
}

.nav .wrap { display: flex; align-items: center; gap: var(--space-5); }

/*
    The wordmark, and the one place the display face is NOT used. Space Grotesk's wide geometric caps
    already carry their own air, so the tracking a locked-up wordmark needs reads as a gap between
    letters rather than as one mark; the UI face takes 0.16em cleanly at this size.
*/
.brand {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-size: 1.0625rem;
    font-weight: 700;
    letter-spacing: 0.16em;
}

.brand img { display: block; flex: none; width: 28px; height: 28px; object-fit: contain; }

/*
    Header wordmark hover: a shockwave passes through the letters. Each glyph lifts, brightens with
    an accent glow, dips slightly under, then settles. Each letter's animation-delay is scaled from
    its --i index (set in markup), so the wave travels left to right. Footer brand stays quiet.

    inline-block on the letter is required so translate/scale apply; the outer span keeps its
    default inline flow so the 0.16em tracking on .brand still sits between glyphs.
*/
.nav .brand .wm-letter {
    display: inline-block;
    transform-origin: 50% 100%;
    will-change: transform, color;
}
.nav .brand:hover .wm-letter,
.nav .brand:focus-visible .wm-letter {
    animation: brand-wave 700ms cubic-bezier(0.22, 0.68, 0.36, 1.6) both;
    animation-delay: calc(var(--i) * 55ms);
}
@keyframes brand-wave {
    0%   { transform: translateY(0)       scale(1);    color: currentColor;  text-shadow: none; }
    35%  { transform: translateY(-0.4em)  scale(1.18); color: var(--accent); text-shadow: 0 0 14px color-mix(in srgb, var(--accent) 70%, transparent); }
    65%  { transform: translateY(0.06em)  scale(0.96); color: currentColor;  text-shadow: none; }
    100% { transform: translateY(0)       scale(1);    color: currentColor;  text-shadow: none; }
}

.nav-links { display: flex; align-items: center; gap: var(--space-1); margin-inline: auto; }

/*
    Sized to the control scale the buttons beside them use, so the whole ribbon sits on one height
    and every item in it reads as something you can press.

    A quarter of the way from secondary toward primary, and no further: full contrast at the buttons'
    weight puts these level with the controls beside them and the row stops having a loudest item.
    This is 7.84:1 against the ribbon lifted to 9.75:1 - enough that they no longer recede next to
    the brand, little enough that they still read as navigation rather than as more buttons.

    Mixed rather than named because the two ends are the only text tokens there are, and mixing
    toward primary moves the right way in the light palette too, where primary is black.
*/
.nav-links a {
    display: flex;
    align-items: center;
    height: var(--control-height-lg);
    padding-inline: var(--space-4);
    border: 1px solid transparent;
    border-radius: var(--pill-radius);
    color: color-mix(in srgb, var(--text-primary) 25%, var(--text-secondary));
    font-size: 0.9375rem;
    font-weight: 500;
    white-space: nowrap;
    transition: color .15s, background-color .15s, border-color .15s;
}

.nav-links a:hover { background: var(--row-hover); color: var(--text-primary); }

.nav-links a[aria-current="page"] {
    background: var(--row-hover);
    border-color: var(--tile-stroke);
    color: var(--text-primary);
}

.nav-actions { display: flex; align-items: center; gap: var(--space-3); }

/*
   Signed in, this button carries a username, and an account named after an email address is long
   enough to run to twice the width of the button beside it. The cap keeps the pair looking like a
   pair; the ellipsis is on the label rather than the button, because .btn is a flex container and
   text-overflow needs a block of its own to act on - without it a long name is sliced mid-glyph.
*/
/*
   The standing call to action, and the only button on the site that carries emphasis of its own.
   It earns it through depth - a top-lit face and a short drop beneath - because the other way to
   make a fill louder is to make it brighter, and brighter is what makes it read taller than the
   quiet button beside it. Mixed from the accent rather than named, so the flat theme's blue lifts
   the same way.

   The height is that same correction: a light fill reads taller than an outline of identical
   height - two bare 40px divs, one filled and one outlined, show it with no button CSS at all -
   so this one loses 2px to sit level with its neighbour.
*/
.nav-actions .btn-primary {
    --cta-face: linear-gradient(180deg, color-mix(in srgb, #FFF 18%, var(--accent)), var(--accent-muted));

    height: 38px;
    background: var(--cta-face);
    box-shadow: 0 3px 10px -3px var(--accent);
    transition: box-shadow .15s, filter .15s, transform .1s;
}

/*
   Restates the face because the plain .btn-primary:hover flattens it to a single colour, and it
   carries the same specificity while sitting later in the file. Brightening lifts rather than
   dims: a call to action that darkens under the pointer reads as already pressed.
*/
.nav-actions .btn-primary:hover {
    background: var(--cta-face);
    filter: brightness(1.08);
    box-shadow: 0 5px 16px -3px var(--accent);
}

.nav-actions .signin { max-width: 12rem; }

.signin-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* ---------------------------------------------------------------------------------------------
   Buttons. Three weights only - filled for the one action a screen wants, outlined for its
   alternative, quiet for everything else. A fourth would need a reason.
   --------------------------------------------------------------------------------------------- */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    height: var(--control-height-lg);
    padding-inline: var(--space-5);
    border: 1px solid transparent;
    border-radius: var(--field-radius);
    font: inherit;
    font-size: 0.9375rem;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    transition: background-color .15s, border-color .15s, color .15s, transform .1s;
}

.btn:active { transform: translateY(1px); }

/*
   Buttons carried no focus style, so the browser drew its own ring - and a UA ring paints OUTSIDE
   the border box, which makes a focused button measure larger than the one beside it. Drawn inside
   with a negative offset, so focus never changes a button's footprint. The fields and the brand
   already style their own; this was the gap.
*/
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.btn-primary:focus-visible { outline-color: var(--primary-button-text); }

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

.btn-outline { border-color: var(--accent); color: var(--accent); }
.btn-outline:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }

.btn-quiet { border-color: var(--panel-stroke); color: var(--text-primary); background: var(--ghost-bg); }
.btn-quiet:hover { background: var(--row-hover); }

.btn-lg { height: 52px; padding-inline: var(--space-6); font-size: 1rem; }

/* Full width, for the one action a form has. display:flex overrides .btn's inline-flex. */
.btn-block { display: flex; width: 100%; margin-top: var(--space-5); }

/* A quiet alternative to the primary action - a link, not a second button competing with it. */
.link-action {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--accent);
    font-size: 0.9375rem;
    font-weight: 600;
}

/* The checkout's terms trigger is a <button> - inside a label an anchor's activation is unreliable
   and inside a form it would submit - so the element defaults have to be cleared to match. */
button.link-action { padding: 0; border: 0; background: none; font: inherit; font-weight: 600; cursor: pointer; }

.link-action svg { transition: transform .15s; }
.link-action:hover svg { transform: translateX(3px); }

/* ---------------------------------------------------------------------------------------------
   Surfaces
   --------------------------------------------------------------------------------------------- */

/*
    Glass's surface fills, raised - and raised HERE, over the token set, rather than written into
    the rules that use them. Every surface below reads its fill from the token, so Slate and Flat
    get theirs and this site follows a surface choice instead of painting one dark colour whatever
    the visitor picked.

    No backdrop-filter anywhere on this site, which is what these values compensate for - both they
    and the reason are copied from the panel rather than re-derived, because it already paid for
    the answer.

    The background here is ALWAYS animated. Blurring a surface whose backdrop is a full-viewport
    canvas re-snapshots and re-blurs it on every frame, which is the expensive part; the desktop
    hosts have no blur at all and tokens.css says why - "layered alpha rather than a platform blur,
    which is exactly why it ports".

    So opacity is the only thing carrying the glass read, and the values are graded - tile, then
    card, then panel, then nav - so every surface is one material at a different depth rather than
    four unrelated greys. The binding constraint is --text-secondary, not body text: it paints every
    label and rate line and sits far closer to the surface than white does.
*/
.theme-glass {
    /*
        Opaque, and the one member of this set that is. The ribbon is sticky, so it sits OVER content
        rather than beside it, and every other surface here only ever has the shader behind it. A
        sheer fill with no backdrop-filter to resolve it just shows the page through the header -
        display type ghosts through even at 95%, because the eye reads a large shape at a contrast
        it would never read text at. Blur is what makes glass work over moving content and the note
        above says why there is none, so the ribbon takes the other answer.
    */
    --nav-bg: #141B24;
    --panel-bg: #141B24A6;
    --card-bg: #10161FE8;
    --tile-bg: #141B248C;
    --tile-hover-bg: #16303FA6;
}

.panel {
    background: var(--panel-bg);
    border: 1px solid var(--panel-stroke);
    border-radius: var(--panel-radius);
    box-shadow: var(--panel-shadow);
}

/* ---------------------------------------------------------------------------------------------
   Hero
   --------------------------------------------------------------------------------------------- */

.hero { padding-block: clamp(2rem, 4.5vw, 3.5rem) clamp(1.25rem, 2.5vw, 2rem); }

.hero-grid {
    display: grid;
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.3fr);
    gap: clamp(1.5rem, 3.5vw, 3rem);
    align-items: center;
}

.hero h1 { font-size: var(--display-1); letter-spacing: -0.04em; }

.hero-lead {
    margin-top: var(--space-5);
    max-width: 27rem;
    color: var(--text-secondary);
    font-size: var(--lead);
}

.hero-actions { margin-top: clamp(1.75rem, 3vw, 2.5rem); display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-5); }

.badge {
    display: inline-flex;
    align-items: center;
    height: var(--control-height);
    padding-inline: var(--space-4);
    margin-bottom: var(--space-5);
    background: var(--tile-bg);
    border: 1px solid var(--tile-stroke);
    border-radius: var(--pill-radius);
    color: var(--accent);
    font-size: var(--label);
    font-weight: 700;
    letter-spacing: var(--label-tracking);
    text-transform: uppercase;
}

/*
    The hero image is the product's own window, not an illustration of it - the only honest answer to
    "what am I actually buying", and it stays current for free because tools/ViewPreview and
    tools/Capture-AppWindow.ps1 in ReflexFramework regenerate it from the shipping app.
*/
/*
    The back window sits in flow and sets the height; only the front one is positioned. A fixed
    min-height here instead meant the front window overflowed its container the moment the images got
    bigger, which is what made the whole shot read as texture rather than as a demonstration.
*/
.hero-shot { position: relative; padding-bottom: 24%; }

.shot {
    border-radius: var(--card-radius);
    border: 1px solid var(--panel-stroke);
    overflow: hidden;

    /* Dark in every surface style: it is the ground a screenshot sits on, not a surface of ours. */
    background: #0B1119;
    box-shadow: 0 28px 70px #000000B3;
}

.shot-back { position: relative; width: 72%; }
.shot-front { position: absolute; bottom: 0; right: 0; width: 78%; z-index: 1; }

.shot img { width: 100%; height: auto; }

/*
    A shot with its explanation beside it. The window keeps the width because it is the thing being
    explained; the prose only has to say which screen you are looking at.
*/
.shot-row {
    display: grid;
    grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
    gap: clamp(1.5rem, 3.5vw, 2.5rem);
    align-items: center;
}

.shot-row h3 { font-size: 1.125rem; }
.shot-row p { margin-top: var(--space-3); color: var(--text-secondary); font-size: 0.9375rem; }

/* ---------------------------------------------------------------------------------------------
   Numbered steps. Hairline-separated rows rather than cards: a step is one line of instruction, and
   a card around each would make eight of them look like eight products. An <ol>, so the order a
   reader cannot see in the layout is still in the markup - the drawn number is decorative. The track
   count comes from auto-fit, so three steps sit on one row and eight reflow instead of filling.

   The number is the only accent on the page outside a price, which is deliberate - it is what makes
   the list read as an ORDER rather than as bullets.
   --------------------------------------------------------------------------------------------- */

.steps {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(21rem, 1fr));
    column-gap: clamp(1.5rem, 4vw, 2.5rem);
}

.step {
    display: flex;
    gap: var(--space-4);
    padding-block: var(--space-4);
    border-top: 1px solid var(--divider);
}

/* Not .mono: that carries user-select:all, which turns a click near a step number into a selection
   of it, and --mono-color, which is the colour for values a reader copies. */
.step-n {
    flex: none;
    width: 1.75rem;
    font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
    font-size: 0.8125rem;
    /* The step text's own line box (0.9375rem x 1.6), so a smaller number still sits on its first
       line rather than riding above it. */
    line-height: 1.5rem;
    color: var(--accent);
}

.step p { font-size: 0.9375rem; line-height: 1.6; }

/* ---------------------------------------------------------------------------------------------
   Products
   --------------------------------------------------------------------------------------------- */

.product-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
    gap: var(--space-4);
}

/*
    Artwork fills the card and the chrome stays out of its way - the palette is deliberately
    desaturated so the product art is the only saturated thing on any screen.

    --tint is per product and stands in for the banner at api/apps/{id}/image?type=Banner. It is a
    shape to fill, not a style to migrate.
*/
.product {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--space-4);
    min-height: 9rem;
    padding: var(--space-4);
    border: 1px solid var(--card-stroke);
    border-radius: var(--card-radius);
    overflow: hidden;
    /*
        The tint fades to `transparent` rather than to the ground at zero alpha - which the featured
        panel below cannot do, and says why. A small card's tint core fills it, so the ramp through
        transparent black is never wide enough to read as a smudge.
    */
    background:
        radial-gradient(85% 140% at 16% 50%, var(--tint) 0%, transparent 72%),
        linear-gradient(120deg, color-mix(in srgb, var(--text-primary) 6%, transparent), transparent 55%),
        var(--card-bg);
    transition: border-color .15s, transform .15s, box-shadow .15s;
}

.product:hover {
    border-color: var(--tile-stroke);
    transform: translateY(-2px);
    box-shadow: 0 16px 36px #00000066;
}

/*
    The icon-in-a-rounded-square convention the desktop hosts already use, rather than a bare letter
    floating in the card - a loose glyph reads as a missing asset, and a tile reads as a deliberate
    monogram. Swaps for the real banner without the layout moving.
*/
/* Real banner artwork, when the product has any. Replaces the monogram, not the layout around it. */
.product-art {
    flex: none;
    width: 6rem;
    height: 4.5rem;
    object-fit: cover;
    border-radius: var(--tile-radius);
    border: 1px solid var(--card-stroke);
}

/*
    A wash over whatever surface the card is, which is what --row-hover is in every theme - a light
    veil on a dark card, a dark one on a light card. The letter is held off full strength so the
    monogram reads as a placeholder rather than as a heading.
*/
.product-mark {
    flex: none;
    width: 3.5rem;
    height: 3.5rem;
    display: grid;
    place-items: center;
    background: var(--row-hover);
    border: 1px solid var(--card-stroke);
    border-radius: var(--tile-radius);
    font-family: var(--font-display);
    font-size: 1.375rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    color: color-mix(in srgb, var(--text-primary) 78%, transparent);
}

.product-body { display: flex; flex-direction: column; gap: var(--space-05); min-width: 0; }
.product-body h3 { font-size: 1.0625rem; }
.product-from { color: var(--text-secondary); font-size: 0.875rem; }
.product-body .link-action { margin-top: var(--space-2); font-size: 0.875rem; }

/* ---------------------------------------------------------------------------------------------
   Terms. Deliberate parity with the launcher's checkout - the buyer meets this control twice and it
   has to be the same control both times, down to the accent on the price and the check on the right.
   --------------------------------------------------------------------------------------------- */

/*
    11rem, not 15: the tiles also render inside the checkout's narrower left column, where a 15rem
    minimum wrapped three terms onto two rows and broke the row-of-tiles the launcher shows.
*/
.terms { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: var(--space-3); }

a.term { display: block; color: inherit; }

.term {
    position: relative;
    padding: var(--space-5);
    background: var(--tile-bg);
    border: 1px solid var(--card-stroke);
    border-radius: var(--tile-radius);
    transition: border-color .15s, background-color .15s, box-shadow .2s, transform .1s;
}

/* Glass names its own; elsewhere the accent wash is mixed into whatever the tile already is. */
.term:hover {
    border-color: var(--accent);
    background: var(--tile-hover-bg, color-mix(in srgb, var(--accent) 12%, var(--tile-bg)));
    box-shadow: 0 0 0 1px var(--accent), 0 0 28px -4px var(--accent);
}

.term:active { transform: translateY(1px); }

/* The term being bought. Same treatment as the hover, held - it is the launcher's selected tile. */
.term.is-selected {
    border-color: var(--accent);
    background: var(--tile-hover-bg, color-mix(in srgb, var(--accent) 12%, var(--tile-bg)));
    box-shadow: 0 0 0 1px var(--accent);
}

.term-check { position: absolute; top: var(--space-3); right: var(--space-3); }

.term-name { font-size: 0.9375rem; color: var(--text-secondary); }
.term-price { font-family: var(--font-display); font-size: 2rem; font-weight: 700; letter-spacing: -0.035em; color: var(--accent); line-height: 1.2; }
.term-rate { font-size: 0.8125rem; color: var(--text-secondary); }

/* ---------------------------------------------------------------------------------------------
   Assurances. Not a row of adjectives - every one states a specific, checkable fact about what
   happens to the buyer, in the words the checkout already uses. "Premium quality" is a claim a
   competitor copies in a minute; "nothing renews" is a promise the product keeps.
   --------------------------------------------------------------------------------------------- */

.assurances {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    padding: clamp(1.25rem, 2.5vw, 1.75rem);
}

.assurance {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding-inline: clamp(0.5rem, 2vw, 1.5rem);
}

.assurance + .assurance { border-left: 1px solid var(--divider); }

.assurance .glyph {
    flex: none;
    width: 2.75rem;
    height: 2.75rem;
    display: grid;
    place-items: center;
    border: 1px solid var(--tile-stroke);
    border-radius: 50%;
    color: var(--accent);
}

.assurance p { font-size: 0.9375rem; line-height: 1.45; }

/* ---------------------------------------------------------------------------------------------
   Store
   --------------------------------------------------------------------------------------------- */

.featured { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); overflow: hidden; }

/*
    Fades to #10161F00, not to `transparent`. A gradient stop of `transparent` is rgba(0,0,0,0), so
    the browser interpolates the whole ramp through transparent BLACK - over a panel-sized radial
    that is most of the visible area, and it renders as a near-black smudge rather than a glow. The
    small product cards got away with it because their core fills the card; this one did not.
*/
/*
    Dark in every surface style, like .shot: this is the stage the product's artwork is mounted on,
    and a poster frame that changed colour with the chrome around it would be the odd one out. The
    monogram below is lit for this ground rather than for the panel.
*/
.featured-art {
    min-height: 20rem;
    display: grid;
    place-items: center;
    background:
        radial-gradient(62% 78% at 44% 74%, var(--tint) 0%, #10161F00 72%),
        radial-gradient(50% 62% at 80% 16%, #FFFFFF29 0%, #10161F00 70%),
        linear-gradient(200deg, #FFFFFF1A, #10161F00 55%),
        #10161F;
}

/*
    The no-artwork state, and it is a REQUIRED state rather than a placeholder: ProductInfo says
    BannerAssetId is "zero when the product has no artwork" and that the store "falls back rather
    than showing a broken image". So this panel has to look composed with nothing in it - a tinted
    void only reads as art-shaped hole. The monogram is the same rounded-square convention the cards
    use, scaled up; a real banner replaces the whole block without the layout moving.

    Lit for the stage above rather than for the surface style, for the same reason the stage is: it
    sits on the artwork ground in every theme, and a light chip there would vanish.
*/
.featured-mark {
    width: 6.5rem;
    height: 6.5rem;
    display: grid;
    place-items: center;
    background: #FFFFFF14;
    border: 1px solid #FFFFFF29;
    border-radius: var(--panel-radius);
    font-family: var(--font-display);
    font-size: 2.75rem;
    font-weight: 700;
    letter-spacing: -0.03em;
    color: #FFFFFFD6;
}

.featured-body {
    padding: clamp(1.5rem, 3vw, 2.5rem);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: flex-start;
    gap: var(--space-3);
}

.featured-body h2 { font-size: var(--display-2); }
.featured-body .btn { margin-top: var(--space-2); }

/* The left-aligned page head. Same distance to the content below as .section-head's centred one. */
.store-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    margin-bottom: clamp(1.25rem, 2.2vw, 1.75rem);
}

.store-head p { margin-top: var(--space-2); font-size: 0.9375rem; }

.store-head h2 { font-size: var(--display-2); }
.store-count { color: var(--text-secondary); font-size: 0.875rem; }

/* ---------------------------------------------------------------------------------------------
   Product detail
   --------------------------------------------------------------------------------------------- */

.features { list-style: none; margin: var(--space-2) 0 0; padding: 0; display: grid; gap: var(--space-2); }
.features li { display: flex; align-items: flex-start; gap: var(--space-3); font-size: 0.9375rem; }
.features svg { flex: none; margin-top: 0.15rem; color: var(--accent); }

/*
   The terms as a modal on the checkout, and as a disclosure on the product page. Different
   contexts: a shopper reading a product page has room to expand a panel, while a buyer mid-commit
   has a form to lose - so there the text arrives over the page and Esc puts them back.
*/
.terms-dialog {
    width: min(46rem, calc(100vw - 2 * var(--space-5)));
    max-height: min(38rem, calc(100vh - 2 * var(--space-6)));
    padding: 0;
    border: 1px solid var(--panel-stroke);
    border-radius: var(--panel-radius);
    background: var(--card-bg);
    color: var(--text-primary);
}

/* Only when open: a dialog is hidden by a UA display:none that any display here would override. */
.terms-dialog[open] { display: flex; flex-direction: column; opacity: 1; transform: none; }

/*
    The dialog and its backdrop fade rather than appear. Both properties that gate a modal are
    discrete - display flips none/flex, and overlay flips the top layer on and off - so both are
    named with allow-discrete or the fill would animate while the box itself popped, and on the way
    out the element would leave the top layer before it had faded.

    @starting-style is what the entry animates FROM. Without it the open state is the only style the
    element has ever had and there is nothing to move from, so it arrives at full opacity.
*/
.terms-dialog {
    opacity: 0;
    transform: translateY(6px) scale(0.99);
    transition:
        opacity .18s ease-out,
        transform .18s ease-out,
        overlay .18s ease-out allow-discrete,
        display .18s ease-out allow-discrete;
}

@starting-style {
    .terms-dialog[open] { opacity: 0; transform: translateY(6px) scale(0.99); }
}

.terms-dialog::backdrop {
    background: #00000000;
    transition:
        background-color .18s ease-out,
        overlay .18s ease-out allow-discrete,
        display .18s ease-out allow-discrete;
}

.terms-dialog[open]::backdrop { background: #000000A6; }

@starting-style {
    .terms-dialog[open]::backdrop { background: #00000000; }
}

.terms-dialog-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-4) var(--space-5);
    border-bottom: 1px solid var(--divider);
}

.terms-dialog-head h3 { font-size: 1.125rem; }

.terms-dialog pre {
    margin: 0;
    padding: var(--space-5);
    overflow: auto;
    color: var(--text-secondary);
    font-size: 0.8125rem;
    line-height: 1.6;
    white-space: pre-wrap;
}

/* Collapsed by default: the terms are the seller's full legal text, not page copy. */
.terms-disclosure { margin-top: var(--space-6); }
.terms-disclosure summary { cursor: pointer; color: var(--accent); font-size: 0.9375rem; font-weight: 600; }

.terms-disclosure pre {
    margin-top: var(--space-4);
    padding: var(--space-4);
    max-height: 22rem;
    overflow: auto;
    background: var(--field-bg);
    border: 1px solid var(--field-stroke);
    border-radius: var(--field-radius);
    color: var(--text-secondary);
    font-size: 0.8125rem;
    line-height: 1.6;
    white-space: pre-wrap;
}

/* ---------------------------------------------------------------------------------------------
   Checkout. Product left, order right, everything visible at once - the launcher's arrangement,
   because a buyer should never scroll to find what they are about to be charged.
   --------------------------------------------------------------------------------------------- */

/*
    The two columns are the same height because the grid makes them so, not because their contents
    happen to add up. Content here is conditional - a coupon error, a provider choice, a sign-in
    block that becomes a purchase form - so any balance struck by counting pixels is lost the next
    time one of those appears. Stretching both to the row and letting each absorb its own slack is
    the version that stays true.
*/
.checkout-grid { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); gap: var(--space-5); }

/*
    The buyer's side: what they are getting, then which term, then how they pay. When this column is
    the shorter one the slack goes to the gaps between its panels rather than into any one of them,
    which keeps every panel the size its content asked for and still lands the last edge on the line
    the order card ends on.
*/
.checkout-flow { display: flex; flex-direction: column; gap: var(--space-5); }


/*
    The order card distributes its slack exactly as .checkout-flow does, and for the same reason: it
    is reliably the shorter column, and collecting the whole difference in front of one child opens a
    dead band between the total and the acceptance it belongs to. Spread across the card's own gaps
    the same space reads as a roomier summary, and the rules already dividing the sections keep the
    grouping legible.

    No auto margin anywhere below, on either child. An auto margin takes all the free space before
    justify-content can divide any of it, so one would put the hole straight back.
*/
.checkout-summary { justify-content: space-between; }

/* The commit: the acceptance and the control that acts on it, under the total it commits to. */
.checkout-commit { display: flex; flex-direction: column; gap: var(--space-3); }

.checkout-product { display: flex; align-items: flex-start; gap: var(--space-4); }
.checkout-product-body { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.checkout-product-body h3 { font-size: 1.25rem; }
.checkout-note { font-size: 0.8125rem; }

/*
    The icon rides ON the monogram rather than beside it, and arrives as --icon on the tile itself.

    A background rather than an image element, because the request may legitimately 404 - nothing on
    the contract says whether an app has an icon. A background that fails paints nothing and the
    letter underneath stands; a failed <img> leaves the browser's broken-image glyph in the tile,
    empty alt or not. With --icon unset the background-image declaration has nothing to resolve and
    drops out entirely, which is the same outcome by the same path.
*/
.checkout-mark { position: relative; width: 4.5rem; height: 4.5rem; font-size: 1.75rem; overflow: hidden; }
.checkout-mark-sm { width: 2.5rem; height: 2.5rem; font-size: 1rem; }

.checkout-mark::after {
    content: "";
    position: absolute;
    inset: 0;
    background-image: var(--icon);
    background-size: cover;
    background-position: center;
}

.order-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--divider);
    font-size: 0.9375rem;
}

.order-item-body { display: flex; flex-direction: column; min-width: 0; margin-right: auto; }
.order-item-body .muted { font-size: 0.875rem; }

.order-lines { display: grid; gap: var(--space-2); }
.order-lines > div { display: flex; justify-content: space-between; gap: var(--space-4); font-size: 0.9375rem; }

/* Both order options, stacked; the Apply that commits them sits on the row of the last one. */
.options-form { display: grid; gap: var(--space-3); }
.options-form .field { margin-bottom: 0; }
.options-row { display: flex; align-items: flex-end; gap: var(--space-2); }
.options-row .field { flex: 1; min-width: 0; }
.options-row .btn { flex: none; height: var(--control-height-lg); }

.order-total {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-4);
    margin-block: var(--space-4);
    padding-top: var(--space-4);
    border-top: 1px solid var(--divider);
}

/*
    Accepted methods, in the buyer's column rather than under the total. It is the width that decides
    this: a mark small enough to fit beside a price is a mark nobody can read, and the question the
    strip answers - can I actually pay for this - belongs with the purchase either way.

    Light tiles, which is the one place on this site that is not glass: these are other people's
    marks, drawn for a light ground, and a Visa wordmark knocked out in white is not a Visa wordmark.
    The hairline is dark rather than white so a tile still has an edge on a light surface style.
*/
.pay-panel h3 { font-size: 1.125rem; }
.pay-panel > p { font-size: 0.9375rem; }
/*
    One track per mark, from a count the component works out - see PaymentMarks. The tracks share the
    width, so the row is flush on both edges whatever the seller accepts, and the cap keeps a
    one-method seller from being handed a tile the width of the panel.

    A single row, deliberately: this panel sits beside the order card and the two have to end near
    enough together to read as a pair, and a second row of marks is 50px the left column cannot
    spare. It is the width of the column that sets the mark size here, not a chosen height.
*/
.pay-marks {
    display: grid;
    grid-template-columns: repeat(var(--pay-cols, 6), minmax(0, 4.5rem));
    gap: var(--space-2);
    margin-top: var(--space-2);
}

.pay-mark {
    display: grid;
    place-items: center;
    height: 2.25rem;
    padding: 0.1875rem 0.25rem;
    background: #F5F6F8;
    border: 1px solid #0000001F;
    border-radius: 6px;
    box-shadow: 0 1px 2px #0000004D;
}

/* Every mark shares one viewBox, so they scale together and sit on one optical line. */
.pay-mark svg { width: 100%; height: 100%; }

/* The acknowledgement. Never pre-ticked - it is evidence, and a default would make it worthless. */
.consent { display: flex; align-items: flex-start; gap: var(--space-3); margin-block: var(--space-4); font-size: 0.8125rem; line-height: 1.5; }
.consent input { flex: none; margin-top: 0.2rem; width: 1.1rem; height: 1.1rem; accent-color: var(--accent); }

/* A step on the card, not an aside inside it: its own rule, its own label, its own full-width row. */
.signin-gate { padding-top: var(--space-4); border-top: 1px solid var(--divider); }
.signin-gate p { font-size: 0.875rem; }
.signin-gate > .label + p { margin-top: var(--space-2); }
.signin-gate .auth-methods { flex-direction: column; }

/*
    One select control for the whole site; the two that use it differ only in size.

    appearance:none takes it out of native rendering, so it is a box this stylesheet controls rather
    than a platform widget dropped into the page. That also removes the UA dropmarker, so the chevron
    is drawn here.
*/
.field select,
.theme-choice select {
    appearance: none;
    -webkit-appearance: none;
    background-color: var(--field-bg);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23AEAEB2' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-size: 10px 6px;
    border: 1px solid var(--field-stroke);
    border-radius: var(--field-radius);
    color: var(--text-primary);
    font: inherit;
}

/*
    appearance:none does not reach the OS-drawn popup, so the option surface needs a colour of its
    own or Glass renders white text on white.

    It must be an OPAQUE one. --panel-bg is deliberately translucent, and the option list is painted
    by the platform outside the page's compositing tree - it has no backdrop to blend with, so a
    translucent colour collapses to whatever flat approximation the platform picks. That grey wash is
    not a colour choice; it is translucency used where translucency does not exist.
*/
.field select option,
.theme-choice select option { background: var(--surface-opaque); color: var(--text-primary); }

.field select {
    width: 100%;
    height: var(--control-height-lg);
    padding-inline: var(--space-3) 34px;
    background-position: right var(--space-3) center;
}

/* ---------------------------------------------------------------------------------------------
   Account and sign-in
   --------------------------------------------------------------------------------------------- */

.auth-wrap { max-width: 27rem; }

/* Wider than the sign-in column it shares a shape with. A licence key is 36 monospace characters
   and its Copy button sits beside it, which together do not fit a column sized for a password. */
.order-wrap { max-width: 34rem; }

.auth-methods { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-4); }

/* The provider mark rides in the button's own gap and never scales with its label. */
.auth-methods .btn svg { flex: none; width: 18px; height: 18px; }

/* Stacked methods space on the flex gap; .btn-block's margin belongs to a form's submit. */
.auth-methods .btn-block { margin-top: 0; }
.auth-divider + .auth-methods { margin-top: 0; }

/* A rule with the alternative written into it, rather than a bare line and a floating label. */
.auth-divider {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-block: var(--space-5);
    color: var(--text-secondary);
    font-size: 0.8125rem;
}

.auth-divider::before,
.auth-divider::after { content: ""; flex: 1; height: 1px; background: var(--divider); }

.method-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.method-list li { display: flex; gap: var(--space-3); font-size: 0.9375rem; text-transform: capitalize; }
.method-list .muted { text-transform: none; }

.auth-panel { padding: clamp(1.5rem, 3vw, 2.25rem); }
.auth-note { margin-top: var(--space-5); text-align: center; font-size: 0.875rem; }

.field { display: block; margin-bottom: var(--space-4); }
.field > span { display: block; margin-bottom: var(--space-2); font-size: 0.875rem; color: var(--text-secondary); }

.field input {
    width: 100%;
    height: var(--control-height-lg);
    padding-inline: var(--space-3);
    background: var(--field-bg);
    border: 1px solid var(--field-stroke);
    border-radius: var(--field-radius);
    color: var(--text-primary);
    font: inherit;
    font-size: 0.9375rem;
}

.field input:focus-visible { outline: none; border-color: var(--accent); }

/* The line that sits between the last field and the form's action. */
.field-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); font-size: 0.875rem; }
.field-row a { color: var(--accent); }
.field-row a:hover { color: var(--accent-muted); }

/* A box and its label as one target. .consent is the taller sibling, for text that wraps. */
.checkbox { display: inline-flex; align-items: center; gap: var(--space-2); color: var(--text-secondary); cursor: pointer; }
.checkbox input { flex: none; width: 1rem; height: 1rem; margin: 0; accent-color: var(--accent); }
.checkbox:hover { color: var(--text-primary); }

/*
    One error line, above the form. A refusal names what to do next and never which half was wrong -
    telling the person in front of the screen that the username exists is a support incident.
*/
.form-error {
    margin-bottom: var(--space-4);
    padding: var(--space-3);
    background: color-mix(in srgb, var(--danger) 12%, transparent);
    border: 1px solid color-mix(in srgb, var(--danger) 35%, transparent);
    border-radius: var(--field-radius);
    color: var(--danger);
    font-size: 0.875rem;
}

/*
    In the auth card only. Everything else in it is centred, so a message that is not reads as
    misaligned - while the same box on the checkout spans the full measure beside left-aligned order
    lines, where centring one short sentence is what would look wrong.
*/
.auth-panel .form-error,
.auth-panel #auth-status { text-align: center; }

.validation-message { display: block; margin-top: var(--space-2); color: var(--danger); font-size: 0.8125rem; }

/*
   Two columns on a wide screen, one on a narrow. align-items is the point of it: the settings
   panels are short and fixed, and stretching them to the height of an orders list that grows with
   every purchase is what leaves most of this page empty.
*/
.account-grid {
    display: grid;
    /* min() so the track can go under its own floor on a phone, where 22rem is wider than the page. */
    grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
    align-items: start;
    gap: var(--space-4);
}

.account-settings { display: grid; gap: var(--space-4); }
.account-panel { padding: clamp(1.25rem, 2.5vw, 1.75rem); display: flex; flex-direction: column; gap: var(--space-3); }
.account-panel p { font-size: 0.9375rem; }

/* A hardware id is a support handle: selectable, monospace, never a label. */
.mono { font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; font-size: 0.875rem; color: var(--mono-color); user-select: all; }

.order-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }

/*
   Flex rather than fixed columns, so the row folds as a unit. Four grid tracks in a panel this
   narrow squeeze the product name until it wraps mid-title and the date breaks across lines; here
   the name keeps a floor and everything after it drops to a second line together.
*/
.order-list li {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-1) var(--space-4);
    padding-bottom: var(--space-3);
    border-bottom: 1px solid var(--divider);
    font-size: 0.9375rem;
}

.order-list li > :first-child { flex: 1 1 10rem; }

.order-list li:last-child { border-bottom: 0; padding-bottom: 0; }
.order-list .link-action { font-size: 0.875rem; }
.order-term { margin-left: var(--space-2); color: var(--text-secondary); font-size: 0.875rem; white-space: nowrap; }

/*
   A labelled field with an action beside it, the same shape as .field. The border belongs to the
   key alone: it is the thing being copied, and a box drawn around the button as well reads as one
   control that happens to contain text.
*/
.license-key {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin: var(--space-5) 0;
}

.license-key .label { flex: 1 0 100%; }

/* Takes the row and gives the button up when the two no longer fit, which is a phone. break-all
   because a GUID offers the browser no break opportunity of its own. */
.license-key .mono {
    flex: 1 1 14rem;
    min-width: 0;
    padding: var(--space-3);
    border: 1px solid var(--panel-stroke);
    border-radius: var(--field-radius);
    background: var(--field-bg);
    word-break: break-all;
}

/* ---------------------------------------------------------------------------------------------
   Footer
   --------------------------------------------------------------------------------------------- */

.footer { border-top: 1px solid var(--divider); margin-top: clamp(2.5rem, 5vw, 4rem); }

.footer .wrap {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-4) var(--space-5);
    padding-block: var(--space-6);
}

/* Pushed to the far edge, so the row reads brand-left, links-right rather than trailing off centre. */
.footer-links { display: flex; flex-wrap: wrap; gap: var(--space-5); margin-left: auto; }
.footer a { color: var(--text-secondary); font-size: 0.875rem; }
.footer a:hover { color: var(--text-primary); }
.footer small { color: var(--text-secondary); font-size: 0.875rem; }
.footer .brand { font-size: 0.875rem; color: var(--text-secondary); }
.footer .brand img { width: 24px; height: 24px; }

/*
    Two independent choices, so two controls: the background is a shader, the surface is a token set,
    and neither implies the other. Same split the admin panel and the desktop hosts present.

    Captions use the site's .label rather than the footer's link typography: at link size and colour
    they read as two more footer links. Controls take their fill and stroke from the field tokens,
    which is what makes the picker restyle itself the moment it is used.
*/
.theme-picker { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-4); }

.theme-choice { display: inline-flex; align-items: center; gap: var(--space-2); }

.theme-choice select {
    height: var(--control-height-sm);
    padding-inline: var(--space-2) 26px;
    background-position: right var(--space-2) center;
    font-size: 0.875rem;
    line-height: normal;
}

/* ---------------------------------------------------------------------------------------------
   Responsive
   --------------------------------------------------------------------------------------------- */

@media (max-width: 980px) {
    .hero-grid, .featured { grid-template-columns: minmax(0, 1fr); }

    /*
        Tightened, never hidden. Display:none here left Pricing and Support reachable only from the
        footer, which is a scroll away and not where anyone looks for navigation - and there is no
        menu button to replace them with.
    */
    .nav-links { gap: 0; }
    .nav-links a { padding-inline: var(--space-2); font-size: 0.875rem; }
    .nav .wrap { gap: var(--space-3); }
    .featured-art { min-height: 13rem; }
    .shot-row { grid-template-columns: minmax(0, 1fr); }
    .hero-shot { min-height: 18rem; }
    .assurance + .assurance { border-left: 0; border-top: 1px solid var(--divider); padding-top: var(--space-4); margin-top: var(--space-4); }
}

/*
    The checkout stacks earlier than the rest of the page.

    Two columns are only worth it while the left one can hold the three plan tiles on one row and the
    payment marks on another. Below this both wrap, the column grows well past the order card beside
    it, and the marks are squeezed narrower than the wordmarks inside them - one column gives all
    three the full measure instead.

    The line is --measure, because that is exactly what it depends on: at 1200px the wrap stops
    growing and the two columns are the width they were designed against. A pixel narrower and every
    row inside them is a pixel narrower too, which is where the wrapping starts.
*/
@media (max-width: 1199px) {
    .checkout-grid { grid-template-columns: minmax(0, 1fr); }
}

/*
    The wordmark drops to the mark alone, and only in the ribbon - the footer has room to wrap. Below
    this the three nav links plus two buttons no longer fit beside the name, and the ribbon is the
    one row on the site where nothing may be hidden: there is no menu button to hide it behind.
*/
@media (max-width: 720px) {
    .nav .brand span { display: none; }
}

/*
    Below this the mark, three links and two buttons measure wider than the viewport, so the ribbon
    wraps rather than overflowing: the mark and the two controls on the first row, the links spread
    across the second. Nothing may be hidden here - there is no menu button to hide it behind - and
    shrinking type and padding far enough to hold one row lands the links under the 24px touch floor.

    The height becomes the content's, so it is padding that sets the ribbon's depth from here down.

    640 rather than the ~530 the row actually measures on this machine: the threshold is a width and
    the thing being avoided is a text measurement, so it is set with room for a platform whose UI
    face runs wider. Wrapping a little early costs a row of white space; wrapping late costs a
    ribbon that runs off the side of the screen.
*/
@media (max-width: 640px) {
    .nav { height: auto; padding-block: var(--space-2); }
    .nav .wrap { flex-wrap: wrap; row-gap: var(--space-2); }

    /* Last in the flex order, so it takes the second row on its own. */
    .nav-links { order: 1; width: 100%; margin-inline: 0; justify-content: space-between; }

    /* The margin the links gave up, so the controls still sit against the right edge. */
    .nav-actions { margin-inline-start: auto; }

    /*
        Three across, whatever the count. The column count the component picks balances the ROWS,
        which is the right answer on a column 600px wide and the wrong one on a phone: five tracks
        there are narrower than the mark inside them, and the strip shrinks to illegible rather than
        taking another row it has plenty of room for.
    */
    .pay-marks { grid-template-columns: repeat(3, minmax(0, 7rem)); }

    /*
        The footer is one item per row by this width, and the links' margin-left:auto - which sets
        the brand-left/links-right reading on a single row - strands that one row against the right
        edge while every row around it sits left. Stacked explicitly and flush left instead.
    */
    .footer .wrap { flex-direction: column; align-items: flex-start; }
    .footer-links { margin-left: 0; }

    /*
        Both choices on one row, each at its content width. The pair measures 4px wider than a 390px
        phone leaves it, which is the whole reason it was wrapping, and the chevron gutter is where
        that comes from: the chevron is 10px wide and sits 8px in, so 22px still clears it.
    */
    .theme-choice select { padding-inline-end: 22px; }
}

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    .btn, .product, .term, .nav-links a, .link-action svg { transition: none; }

    /* The modal still has to appear and leave, so only the movement goes - not the display flip. */
    .terms-dialog { transform: none; transition: opacity .01s, overlay .01s allow-discrete, display .01s allow-discrete; }
    .terms-dialog::backdrop { transition: background-color .01s, overlay .01s allow-discrete, display .01s allow-discrete; }
    .nav .brand:hover .wm-letter,
    .nav .brand:focus-visible .wm-letter { animation: none; }
}
