/*
 * Model Simulations — one stylesheet, no build step, no dependencies.
 *
 * The site is four static pages, and this file is loaded directly. A framework
 * or a bundler would put a build between editing a sentence and seeing it, and
 * buy nothing back at this size.
 *
 * ## The look, and where it comes from
 *
 * Everything here is cut from the logo. The lockup is a chrome wordmark beside
 * a scanning device with a holographic blue figure inside it, and the two
 * palettes below were *sampled off the rendered pixels* of
 * `art/modelsims-lockup.png` rather than chosen — the figure's five blues and
 * the metal's six greys are literally the colours in the drawing. That is the
 * whole reason the page sits next to its own logo without arguing with it.
 *
 * Three rules follow from that, and they are the difference between this and
 * the dark-mode SaaS template it used to be:
 *
 *   1. **Corners are cut, not rounded.** A 20px radius on a dark card is the
 *      single most generic shape on the web. Panels here are nearly square and
 *      earn their edge from a bevel instead.
 *   2. **Edges are lit.** Every panel, button and rule is a 1px gradient that
 *      runs pale at the top and black at the bottom, which is what makes a flat
 *      fill read as milled metal. One recipe, `--edge`, used everywhere.
 *   3. **Ornament is structural.** Corner brackets, the diamond in a section
 *      rule, the bracket on an eyebrow — the neo-traditional borrowings. They
 *      are drawn with gradients on pseudo-elements, so they cost no markup and
 *      no requests.
 *
 * ## Two colour systems, which is not the same as two theming mechanisms
 *
 * `--accent` is still the only way to *theme* something. Each product page sets
 * it on <body> and buttons, rules, chips and link underlines follow; that has
 * not changed and there is still no second way to do it.
 *
 * What is new is a *material* palette — `--gun-*` and `--holo-*`. Those are not
 * themeable and are not per-product. Metal is metal on every page. Reach for
 * `--accent` when something should change identity between products, and for
 * the material variables when something should look like it is made of
 * something. Do not add a third.
 *
 * ## No JavaScript
 *
 * There is none on this site and the animation here does not introduce any.
 * Every moving part is a CSS transition or keyframe, every one of them is
 * decorative, and `prefers-reduced-motion` stops all of them — nothing becomes
 * invisible or unreachable when it does.
 */

/* Audiowide, self-hosted.
 *
 * The wordmark in the logo is Audiowide converted to outlines, so the headings
 * are now literally the same typeface as the company's name. This is the one
 * webfont on the site and it reverses an earlier decision not to have one; at
 * 20 KB for both subsets it costs about a fifth of one product screenshot.
 *
 * Self-hosted rather than linked to Google's CDN, because a third-party request
 * on every page view is the kind of thing this site has otherwise avoided, and
 * because a font that fails to load from somebody else's host is a page that
 * loses its headings.
 *
 * Licensing: Audiowide is under the SIL Open Font License 1.1. Clause 2 lets
 * the notice travel "in the appropriate machine-readable metadata fields within
 * text or binary files" — both .woff2 files carry the copyright and the licence
 * URL in their name table, so shipping them satisfies it. A human-readable copy
 * is in `brand/Audiowide-OFL.txt`, which is deliberately not served.
 *
 * Both subsets are Google's own cuts, with Google's unicode-ranges. The browser
 * fetches only the one a page actually needs, which for English is the first. */
@font-face {
    font-family: "Audiowide";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("font/Audiowide-latin.woff2") format("woff2");
    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;
}
@font-face {
    font-family: "Audiowide";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("font/Audiowide-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
        U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
        U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F,
        U+A720-A7FF;
}

:root {
    /* The scanned figure, sampled from the logo at five luminance bands. These
       are the glow; nothing else on the page is allowed to be this saturated. */
    --holo-0: #002C4E;
    --holo-1: #0771B9;
    --holo-2: #21ADE8;
    --holo-3: #5FCEF4;
    --holo-4: #9CEDFC;

    /* The metal, sampled the same way. Faintly green rather than blue — real
       gunmetal, not the blue-grey that gets used as a stand-in for it. Keep
       them neutral; warming them is what makes chrome look like plastic. */
    --gun-0: #161515;
    --gun-1: #262A2B;
    --gun-2: #414443;
    --gun-3: #5F625F;
    --gun-4: #878883;
    --gun-5: #B3B4AE;
    --gun-6: #D8D9D3;

    --bg: #06090F;
    --surface: #0D121B;
    --surface-2: #131A26;
    --line: #232B36;
    --ink: #EDF1F7;
    --muted: #93A0B4;
    --dim: #637087;

    /* The three products, and the parent that spans them. */
    --vrecord: #3B82F6;
    --questjam: #2BB3A3;
    --cubemarch: #8B5CF6;
    --accent: var(--vrecord);

    /* ---- materials ---------------------------------------------------- */

    /* A machined face. Four stops with the break slightly above centre, which
       is where a cylindrical highlight actually falls. */
    --plate: linear-gradient(176deg, #19202C 0%, #121824 44%, #0C111A 56%, #141B27 100%);
    --plate-2: linear-gradient(176deg, #222A38 0%, #19202E 44%, #121823 56%, #1B2331 100%);

    /* The lit edge. Used as a border-box background against a padding-box fill,
       which is how you get a gradient border without a second element. */
    --edge: linear-gradient(176deg, var(--gun-4) 0%, var(--gun-2) 38%, #090C12 62%, var(--gun-3) 100%);
    --edge-hot: linear-gradient(176deg, var(--holo-4) 0%, var(--holo-2) 40%, var(--holo-0) 70%, var(--holo-1) 100%);

    /* Milled relief: a highlight along the top inside, a hard shade along the
       bottom inside, and the shadow the whole thing casts. */
    --bevel: inset 0 1px 0 rgba(216, 217, 211, 0.14),
             inset 0 -1px 0 rgba(0, 0, 0, 0.72),
             0 1px 2px rgba(0, 0, 0, 0.6),
             0 12px 28px -14px rgba(0, 0, 0, 0.9);
    --bevel-lift: inset 0 1px 0 rgba(216, 217, 211, 0.24),
                  inset 0 -1px 0 rgba(0, 0, 0, 0.72),
                  0 2px 4px rgba(0, 0, 0, 0.6),
                  0 20px 40px -16px rgba(0, 0, 0, 0.95);

    /* The figure's light, for anything that should look powered. */
    --glow: 0 0 22px -4px rgba(33, 173, 232, 0.55);

    --measure: 36rem;
    --page: 74rem;

    /* Nearly square. See rule 1 in the header. */
    --radius: 3px;
    --radius-lg: 5px;

    --display: "Audiowide", var(--sans);
    --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI",
        Helvetica, Arial, sans-serif;
    --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

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

html {
    scroll-behavior: smooth;
    /* The nav is sticky, so an anchor link scrolls its target to y=0 and the
       nav then sits on top of it — "How we build" landed with its eyebrow and
       half its heading covered. Clears the tallest the bar ever gets, which is
       the wrapped two-row state on a phone. */
    scroll-padding-top: 7.5rem;
}

/* Everything that moves here is decoration. Switching it off must cost a reader
   nothing, so this kills duration rather than hiding anything, and the one
   looping animation is stopped outright rather than run instantly forever. */
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
    .hero::after { display: none; }
}

body {
    margin: 0;
    background:
        /* A faint field of the figure's light, so the page is lit from the same
           source as the logo rather than being flat black. */
        radial-gradient(120% 70% at 50% -8%, rgba(33, 173, 232, 0.07), transparent 62%),
        var(--bg);
    background-attachment: fixed;
    color: var(--ink);
    font: 16px/1.65 var(--sans);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

img, svg { max-width: 100%; height: auto; display: block; }

a { color: var(--ink); text-decoration-color: color-mix(in srgb, var(--accent) 60%, transparent); text-underline-offset: 3px; }
a:hover { text-decoration-color: var(--accent); }

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

/* ----------------------------------------------------------- typography -- */

/*
 * Audiowide has one weight and no italic. Asking for 700 gets a synthesised
 * bold — the browser smearing the outline sideways — which on a wide geometric
 * face looks like a rendering fault. So every display heading is 400 and
 * synthesis is off, and hierarchy comes from size and colour instead.
 *
 * It is also a *wide* face: the same string is around 30% longer than in the
 * system sans. The sizes below are smaller than the ones they replace for that
 * reason, and `h1` is capped well under where a neutral face could sit.
 */
h1, h2, h3, .display {
    font-family: var(--display);
    font-weight: 400;
    font-synthesis: none;
    line-height: 1.22;
    letter-spacing: -0.005em;
    margin: 0;
    text-wrap: balance;
}

h1 { font-size: clamp(1.85rem, 4vw, 2.9rem); }
h2 { font-size: clamp(1.3rem, 2.4vw, 1.75rem); }
h3 { font-size: 1rem; letter-spacing: 0.005em; }

/*
 * The headings glow, and the gradient is the figure.
 *
 * Light at the top and saturated at the bottom, which is the direction the
 * figure in the logo runs. It deliberately does *not* reach `--holo-0`: the
 * darkest band of the figure is nearly black, and a heading whose descenders
 * fall into the background is a heading nobody can read.
 *
 * The last stop is mixed with `--accent`, so a product page's headings carry
 * that product's colour without a second mechanism — VRecord's run blue,
 * QuestJam's turn teal, CubeMarch's go violet, all off the one property.
 *
 * `drop-shadow` rather than `text-shadow`: with `background-clip: text` the
 * text has no colour of its own for `text-shadow` to pick up, and a filter
 * blooms the actual gradient instead of a flat approximation of it.
 */
h1, h2 {
    background: linear-gradient(174deg,
        #F4FCFF 0%,
        var(--holo-4) 26%,
        var(--holo-3) 52%,
        color-mix(in srgb, var(--accent) 72%, var(--holo-2)) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    filter: drop-shadow(0 0 20px color-mix(in srgb, var(--accent) 28%, transparent))
            drop-shadow(0 1px 0 rgba(0, 0, 0, 0.55));
}

/* h3 is the other material. Twenty-six of them sit inside cards, and if they
   glowed too the page would have no quiet level left — so these are polished
   steel: the chrome ramp, no bloom. */
h3 {
    background: linear-gradient(174deg, var(--gun-6) 0%, #F2F3EF 40%, var(--gun-5) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

p { margin: 0 0 1rem; text-wrap: pretty; }

.wrap { max-width: var(--page); margin: 0 auto; padding: 0 1.5rem; }
.lede { font-size: clamp(1.02rem, 1.7vw, 1.22rem); color: var(--muted); max-width: var(--measure); }
.muted { color: var(--muted); }
.small { font-size: 0.88rem; }
strong { color: var(--gun-6); font-weight: 650; }

/* ------------------------------------------------------------------ nav -- */

.nav {
    position: sticky;
    top: 0;
    z-index: 20;
    background: linear-gradient(180deg, rgba(10, 14, 21, 0.93), rgba(6, 9, 15, 0.88));
    backdrop-filter: blur(14px) saturate(130%);
    /* A rail, not a hairline: bright metal, then the figure's light under it. */
    border-bottom: 1px solid transparent;
    border-image: linear-gradient(90deg, transparent, var(--gun-2) 18%, var(--gun-4) 50%, var(--gun-2) 82%, transparent) 1;
    box-shadow: 0 1px 0 rgba(33, 173, 232, 0.12), 0 10px 30px -18px #000;
}
.nav-in {
    max-width: var(--page);
    margin: 0 auto;
    padding: 0.7rem 1.5rem;
    display: flex;
    align-items: center;
    gap: 1.5rem;
}

/* The logo is a lockup: the emblem and the company name are one image, so there
   is no text node here to style and nothing that can wrap. The name is in the
   alt text, which is what anything not drawing the image will read. Width is
   set and height left to the global `height: auto`, so the ratio survives
   swapping the file for one cut at a different size.

   Scaled against the viewport rather than fixed, because a width that reads
   right on a laptop is a postage stamp on a 27-inch monitor — which is how this
   was first shipped. The floor keeps the wordmark legible on a phone; the
   ceiling stops it from becoming the page. */
.brand { display: flex; align-items: center; text-decoration: none; flex: none; }
.brand img {
    width: clamp(170px, 14vw, 240px);
    flex: none;
    transition: filter 0.3s ease;
}
.brand:hover img { filter: drop-shadow(0 0 14px rgba(33, 173, 232, 0.4)); }

.nav-links { margin-left: auto; display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; }

/*
 * The menu items are machined plates.
 *
 * Four things move, and they are layered deliberately rather than all being
 * "hover makes it brighter": the plate lifts a pixel, its edge lights to the
 * figure's blue, a specular highlight sweeps across the face, and a bar grows
 * from the centre of the bottom edge. The sweep is the one that makes it read
 * as metal — a flat fill that brightens reads as a flat fill that brightened.
 *
 * `:focus-visible` gets the same treatment as `:hover`, so this is a keyboard
 * affordance and not just a pointer one. `overflow: hidden` keeps the sweep
 * inside the plate, and `isolation` keeps it under the label.
 */
.nav-links a {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    display: inline-flex;
    align-items: center;
    padding: 0.44rem 0.85rem;
    font-size: 0.86rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--muted);
    text-decoration: none;
    border-radius: var(--radius);
    border: 1px solid transparent;
    background: linear-gradient(176deg, #161D29, #0C1119) padding-box,
                linear-gradient(176deg, var(--gun-2), #0A0E14) border-box;
    box-shadow: inset 0 1px 0 rgba(216, 217, 211, 0.07);
    transition: color 0.2s, transform 0.2s, box-shadow 0.25s, background 0.25s;
}

/* The travelling highlight. Parked off the left edge and sent across on hover;
   it is skewed because a specular on a curved face is not a vertical band. */
.nav-links a::before {
    content: "";
    position: absolute;
    inset: -1px;
    z-index: -1;
    background: linear-gradient(104deg,
        transparent 34%,
        rgba(216, 217, 211, 0.22) 47%,
        rgba(156, 237, 252, 0.3) 53%,
        transparent 66%);
    transform: translateX(-130%);
    transition: transform 0.62s cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* The bar under the plate, grown from the centre rather than wiped in from one
   side, so it reads as something switching on. */
.nav-links a::after {
    content: "";
    position: absolute;
    left: 50%;
    bottom: 0;
    height: 2px;
    width: 0;
    transform: translateX(-50%);
    background: linear-gradient(90deg, transparent, var(--holo-2), var(--holo-4), var(--holo-2), transparent);
    box-shadow: 0 0 10px rgba(33, 173, 232, 0.9);
    transition: width 0.3s ease;
}

.nav-links a:hover,
.nav-links a:focus-visible {
    color: var(--ink);
    transform: translateY(-1px);
    background: linear-gradient(176deg, #1E2735, #10161F) padding-box,
                linear-gradient(176deg, var(--holo-3), var(--holo-0) 70%, var(--gun-2)) border-box;
    box-shadow: inset 0 1px 0 rgba(216, 217, 211, 0.16), var(--glow);
}
.nav-links a:hover::before,
.nav-links a:focus-visible::before { transform: translateX(130%); }
.nav-links a:hover::after,
.nav-links a:focus-visible::after { width: 72%; }

.nav-links a:active { transform: translateY(0); }

/* The page you are on is lit without being hovered, and stays lit. */
.nav-links a[aria-current="page"] {
    color: var(--ink);
    background: linear-gradient(176deg, #1C2634, #0E141D) padding-box,
                linear-gradient(176deg, var(--holo-2), var(--holo-0) 72%, var(--gun-2)) border-box;
}
.nav-links a[aria-current="page"]::after { width: 72%; }

/* Below this the logo and four links will not share a line. Stack them rather
   than let them fight: the brand keeps the first row, the links take the
   second in full. */
/* The plates cost height that the plain text links did not, and on a phone the
   nav is sticky — it was taking a fifth of the viewport and keeping it. Small
   enough here that the four fit on the one wrapped row rather than two, which
   is where most of that height was going. */
@media (max-width: 40rem) {
    .nav-in { flex-wrap: wrap; gap: 0.5rem 1.5rem; padding: 0.55rem 1.25rem; }
    .nav-links { margin-left: 0; width: 100%; gap: 0.3rem; }
    .nav-links a {
        padding: 0.34rem 0.52rem;
        font-size: 0.74rem;
        letter-spacing: 0.005em;
    }
    .brand img { width: 150px; }
}

/* ----------------------------------------------------------- structure -- */

section { padding: clamp(3.25rem, 7vw, 5.5rem) 0; position: relative; }

/*
 * The join between two sections, as an ornament rather than a hairline.
 *
 * A chrome rule that fades out at both ends, with a diamond set into the middle
 * of it. This is the most openly neo-traditional thing on the page and it is
 * doing real work — it gives the page a cadence, so the sections read as plates
 * bolted together rather than as one scroll of text with gaps in it.
 */
section + section::before {
    content: "";
    position: absolute;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    width: min(var(--page), calc(100% - 3rem));
    height: 1px;
    background: linear-gradient(90deg, transparent, var(--gun-2) 12%, var(--gun-4) 50%, var(--gun-2) 88%, transparent);
}
section + section::after {
    content: "";
    position: absolute;
    top: -3.5px;
    left: 50%;
    width: 7px;
    height: 7px;
    transform: translateX(-50%) rotate(45deg);
    background: linear-gradient(135deg, var(--gun-6), var(--gun-3) 55%, var(--gun-0));
    box-shadow: 0 0 0 3px var(--bg), 0 0 12px rgba(33, 173, 232, 0.4);
}

/*
 * The eyebrow, bracketed.
 *
 * It keeps `--accent`, so it is still the fastest read of which product you are
 * looking at, but it now sits behind a short chrome rule and a lit pip. Small
 * ornament, used nineteen times — it is most of what makes the section heads
 * feel built rather than typed.
 */
.eyebrow {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    font-family: var(--display);
    font-size: 0.68rem;
    font-weight: 400;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--accent);
    margin: 0 0 1rem;
}
.eyebrow::before {
    content: "";
    width: 1.6rem;
    height: 1px;
    flex: none;
    background: linear-gradient(90deg, transparent, var(--accent));
}
.eyebrow::after {
    content: "";
    width: 4px;
    height: 4px;
    flex: none;
    transform: rotate(45deg);
    background: var(--accent);
    box-shadow: 0 0 8px color-mix(in srgb, var(--accent) 80%, transparent);
}

.split {
    display: grid;
    gap: clamp(2rem, 5vw, 4rem);
    align-items: center;
    grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 62rem) {
    /* minmax(0, 1fr), not 1fr. A bare `1fr` is `minmax(auto, 1fr)`, so one
       column with wide min-content — a long unbroken string, or an image that
       failed to load and is showing its alt text — silently steals width from
       the other and the two stop being halves. */
    .split { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
    .split.reverse > :first-child { order: 2; }
}

/*
 * Counted, not fitted.
 *
 * Both tracks used to be `repeat(auto-fit, minmax(Nrem, 1fr))`, which decides
 * the column count from arithmetic against the container — and that arithmetic
 * silently changed the first time `--page` moved. Widening the page by 4rem was
 * enough to let a third 22rem column fit, which put the four-card track back to
 * three-and-one with an orphan: the exact layout the minimum had been widened
 * to prevent, reintroduced by a change nowhere near it.
 *
 * So the counts are stated instead of derived. The rule both tracks obey is
 * that a row never ends in an orphan, which given the counts actually used
 * here — three, four and six cards — means:
 *
 *   .grid       3 or 1 columns   (3 children fill one row; 6 fill two)
 *   .grid.quad  2 or 1 columns   (4 children fill two rows)
 *
 * No intermediate two-column state for `.grid`, for the same reason the
 * products row has none.
 */
.grid { display: grid; gap: 1.1rem; grid-template-columns: minmax(0, 1fr); }
@media (min-width: 50rem) { .grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.grid.quad { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 46rem) { .grid.quad { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* --------------------------------------------------------------- hero -- */

/*
 * Three layers, none of them in the markup: the product blooms, a scanline
 * field, and a band that travels down through it.
 *
 * The scan is the logo's own idea — the emblem is a *scanning* device, and the
 * page it sits on may as well be doing the same thing. It is slow (eleven
 * seconds), it is faint, and `prefers-reduced-motion` removes it entirely.
 */
.hero { padding-top: clamp(3.5rem, 8vw, 6rem); position: relative; overflow: hidden; }
.hero > .wrap { position: relative; z-index: 2; }

.hero::before {
    content: "";
    position: absolute;
    inset: -40% 10% auto -10%;
    height: 44rem;
    z-index: 0;
    pointer-events: none;
    background:
        radial-gradient(45% 50% at 26% 40%, color-mix(in srgb, var(--vrecord) 20%, transparent), transparent 70%),
        radial-gradient(40% 45% at 58% 28%, color-mix(in srgb, var(--questjam) 16%, transparent), transparent 70%),
        radial-gradient(38% 42% at 86% 44%, color-mix(in srgb, var(--cubemarch) 16%, transparent), transparent 70%);
}

.hero::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background:
        /* The travelling band. */
        linear-gradient(180deg, transparent, rgba(33, 173, 232, 0.07) 45%, rgba(156, 237, 252, 0.11) 50%, rgba(33, 173, 232, 0.07) 55%, transparent),
        /* The field it travels through. */
        repeating-linear-gradient(180deg, transparent 0 3px, rgba(33, 173, 232, 0.035) 3px 4px);
    background-size: 100% 14rem, 100% 100%;
    background-repeat: no-repeat, repeat;
    /* Fades out before it reaches the copy, so nothing is read through it. */
    -webkit-mask-image: linear-gradient(180deg, #000 0%, transparent 78%);
    mask-image: linear-gradient(180deg, #000 0%, transparent 78%);
    animation: scan 11s linear infinite;
}

@keyframes scan {
    from { background-position: 0 -14rem, 0 0; }
    to   { background-position: 0 100%, 0 0; }
}

/* The one place three product colours appear in one string. Left as the span
   across the company rather than pushed through `--accent`, because that is
   exactly what the sentence means. */
.hero h1 .fade {
    background: linear-gradient(100deg, var(--holo-4), var(--vrecord) 22%, var(--questjam) 58%, var(--cubemarch));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

/* ------------------------------------------------------------ buttons -- */

.row { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 1.75rem; }

/*
 * The same plate as the menu items, at a size you can hit with a thumb, with
 * the same specular sweep. Square-ish rather than the pill it was — a 999px
 * radius is the other half of the generic dark-mode kit.
 */
.btn {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.72rem 1.3rem;
    border-radius: var(--radius);
    border: 1px solid transparent;
    font-family: var(--display);
    font-size: 0.8rem;
    font-weight: 400;
    letter-spacing: 0.04em;
    text-decoration: none;
    color: var(--gun-6);
    background: var(--plate-2) padding-box, var(--edge) border-box;
    box-shadow: var(--bevel);
    transition: transform 0.2s, box-shadow 0.25s, color 0.2s, background 0.25s;
}
.btn::before {
    content: "";
    position: absolute;
    inset: -1px;
    z-index: -1;
    background: linear-gradient(104deg, transparent 34%, rgba(216, 217, 211, 0.26) 48%, rgba(156, 237, 252, 0.34) 54%, transparent 68%);
    transform: translateX(-130%);
    transition: transform 0.66s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.btn:hover::before, .btn:focus-visible::before { transform: translateX(130%); }

.btn:hover, .btn:focus-visible {
    color: #fff;
    transform: translateY(-2px);
    background: var(--plate-2) padding-box, var(--edge-hot) border-box;
    box-shadow: var(--bevel-lift), var(--glow);
}
.btn:active { transform: translateY(0); box-shadow: var(--bevel); }

/* The primary is the figure's own light rather than a flat accent fill: a lit
   panel with the accent underneath it, so it belongs to the same object as
   everything else instead of being a coloured rectangle laid on top. */
.btn.primary {
    color: #04090F;
    font-weight: 400;
    background:
        linear-gradient(176deg,
            color-mix(in srgb, var(--accent) 24%, var(--holo-4)) 0%,
            color-mix(in srgb, var(--accent) 62%, var(--holo-3)) 44%,
            color-mix(in srgb, var(--accent) 88%, var(--holo-1)) 56%,
            color-mix(in srgb, var(--accent) 70%, var(--holo-2)) 100%) padding-box,
        linear-gradient(176deg, #FFFFFF, color-mix(in srgb, var(--accent) 60%, var(--holo-2)) 45%, var(--holo-0)) border-box;
    box-shadow: var(--bevel), 0 0 26px -6px color-mix(in srgb, var(--accent) 70%, transparent);
    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.28);
}
.btn.primary:hover, .btn.primary:focus-visible {
    color: #04090F;
    box-shadow: var(--bevel-lift), 0 0 38px -6px color-mix(in srgb, var(--accent) 85%, transparent);
}

/* ---------------------------------------------------------------- card -- */

/*
 * A plate with a lit top edge.
 *
 * The hairline across the top is `--accent` at a third strength, brightening on
 * hover — one detail, twenty-three times, which is the right budget at this
 * level. The corner brackets below are saved for `.product`, because putting
 * them on everything would turn ornament into wallpaper.
 */
.card {
    position: relative;
    isolation: isolate;
    background: var(--plate) padding-box, var(--edge) border-box;
    border: 1px solid transparent;
    border-radius: var(--radius-lg);
    padding: 1.4rem;
    box-shadow: var(--bevel);
    transition: box-shadow 0.25s, transform 0.25s;
}
.card::before {
    content: "";
    position: absolute;
    top: 0;
    left: 12%;
    right: 12%;
    height: 1px;
    background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--accent) 55%, transparent), transparent);
    transition: opacity 0.25s, left 0.3s, right 0.3s;
}
.card:hover { transform: translateY(-2px); box-shadow: var(--bevel-lift); }
.card:hover::before {
    left: 0;
    right: 0;
    background: linear-gradient(90deg, transparent, var(--accent), var(--holo-4), var(--accent), transparent);
    box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 60%, transparent);
}
.card h3 { margin-bottom: 0.4rem; }
.card p:last-child { margin-bottom: 0; }
.card .ico { width: 30px; height: 30px; color: var(--accent); margin-bottom: 0.85rem; }

/*
 * A whole product, presented as one clickable panel.
 *
 * A column with a footer, not a stack of blocks. The three products do not
 * write paragraphs of the same length and their chip rows do not wrap the same
 * way, so in plain document order the three "About →" links landed at three
 * different heights and the slack pooled as dead space under the two shorter
 * cards. The paragraph takes the slack instead — everything below it is pinned
 * to the bottom of whatever height the grid settled on.
 */
.product {
    position: relative;
    isolation: isolate;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto 1fr auto auto;
    text-decoration: none;
    background: var(--plate) padding-box, var(--edge) border-box;
    border: 1px solid transparent;
    border-radius: var(--radius-lg);
    padding: 1.6rem;
    box-shadow: var(--bevel);
    transition: box-shadow 0.25s, transform 0.25s, background 0.25s;
}

/*
 * Corner brackets, all four from one pseudo-element.
 *
 * Eight 1px gradients — two per corner — positioned into the corners of an
 * inset box. Doing it this way rather than with four elements is what keeps the
 * ornament free: no markup, no extra requests, and it inherits `--accent` so
 * each product's brackets are its own colour.
 *
 * Absolutely positioned, so it is not a grid item. A static pseudo-element here
 * would become the first cell of the grid and shove the picture down a row.
 */
.product::before {
    content: "";
    position: absolute;
    inset: 6px;
    pointer-events: none;
    --b: color-mix(in srgb, var(--accent) 55%, transparent);
    background:
        linear-gradient(var(--b), var(--b)) left top / 1px 13px no-repeat,
        linear-gradient(var(--b), var(--b)) left top / 13px 1px no-repeat,
        linear-gradient(var(--b), var(--b)) right top / 1px 13px no-repeat,
        linear-gradient(var(--b), var(--b)) right top / 13px 1px no-repeat,
        linear-gradient(var(--b), var(--b)) left bottom / 1px 13px no-repeat,
        linear-gradient(var(--b), var(--b)) left bottom / 13px 1px no-repeat,
        linear-gradient(var(--b), var(--b)) right bottom / 1px 13px no-repeat,
        linear-gradient(var(--b), var(--b)) right bottom / 13px 1px no-repeat;
    transition: inset 0.3s cubic-bezier(0.22, 0.61, 0.36, 1), opacity 0.3s;
    opacity: 0.75;
}

.product:hover {
    transform: translateY(-3px);
    background: var(--plate-2) padding-box, var(--edge-hot) border-box;
    box-shadow: var(--bevel-lift), 0 0 30px -10px color-mix(in srgb, var(--accent) 65%, transparent);
}
/* The brackets open outwards on hover — the panel being framed, rather than a
   border simply changing colour. */
.product:hover::before { inset: 3px; opacity: 1; --b: var(--accent); }

.product[data-product="vrecord"] { --accent: var(--vrecord); }
.product[data-product="questjam"] { --accent: var(--questjam); }
.product[data-product="cubemarch"] { --accent: var(--cubemarch); }

.product img, .product svg {
    border-radius: 2px;
    margin-bottom: 1.2rem;
    border: 1px solid #0A0E14;
    box-shadow: 0 1px 0 rgba(216, 217, 211, 0.08), 0 8px 20px -12px #000;
}
.product h3 { font-size: 1.18rem; margin-bottom: 0.5rem; }
.product p.muted { margin-bottom: 0; font-size: 0.95rem; }

/* The rule turns the alignment above into something visible. All three sit on
   the same line across the row, which is the whole point of pinning. */
.product .more {
    position: relative;
    font-family: var(--display);
    color: var(--accent);
    font-weight: 400;
    font-size: 0.78rem;
    letter-spacing: 0.04em;
    margin: 1.3rem 0 0;
    padding-top: 1.1rem;
    border-top: 1px solid transparent;
    border-image: linear-gradient(90deg, var(--gun-2), color-mix(in srgb, var(--accent) 40%, var(--gun-2)) 50%, transparent) 1;
    transition: text-shadow 0.25s;
}
.product:hover .more { text-shadow: 0 0 14px color-mix(in srgb, var(--accent) 70%, transparent); }

/*
 * Three products, which `.split` cannot hold — it is a pair.
 *
 * There is deliberately no two-column state. An odd count in two columns always
 * strands the last card on a row of its own beside an empty half, which is the
 * single thing that made this section look unfinished, and no amount of spacing
 * fixes it. So the row is three-up or it is one-up, and the band in between —
 * tablets, and iPad portrait sits right in it — turns the card on its side
 * instead: picture left, text right, full width. Same markup, same reading
 * order, no hole.
 */
.products { display: grid; gap: 1.5rem; grid-template-columns: minmax(0, 1fr); }
@media (min-width: 64rem) { .products { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

@media (min-width: 38rem) and (max-width: 63.999rem) {
    .product {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
        grid-template-rows: auto 1fr auto auto;
        column-gap: 1.6rem;
    }
    /* Spans every row so it can centre against the whole card, but keeps its
       own ratio rather than filling the column. Two of the three pictures are
       drawn diagrams with labels in them, and stretching a 1.9:1 drawing into a
       taller box hands `object-fit: cover` the sides to cut off — which it did,
       taking QuestJam's title with it. Let it letterbox. */
    .product img, .product svg { grid-area: 1 / 1 / -1 / 2; margin-bottom: 0; align-self: center; }
    .product h3 { grid-area: 1 / 2; }
    .product p.muted { grid-area: 2 / 2; }
    .product .chips { grid-area: 3 / 2; }
    .product .more { grid-area: 4 / 2; }
}

/* The card images are three different kinds of picture — two drawn diagrams and
   one render off the product itself. Fixing the box and cropping to it keeps
   the row level; without this the cards below start at three different
   heights. */
.product img { aspect-ratio: 760 / 400; object-fit: cover; width: 100%; }

/* ------------------------------------------------------------- imagery -- */

/*
 * A content picture, set into the page rather than floating on it.
 *
 * These used to carry `style="border-radius:16px"` one at a time, which is how
 * the generic look got in — a soft-cornered image on a soft-cornered card. The
 * frame is the same recipe as every other surface: a lit top edge, a black
 * bottom one, and the shadow it casts. Near-square, like the panels.
 */
.shot {
    border: 1px solid transparent;
    border-radius: var(--radius);
    background: #0A0E14 padding-box, var(--edge) border-box;
    box-shadow: var(--bevel);
}

/* The one picture that is genuinely an icon and should keep an icon's shape.
   No frame — an app icon with a border around it reads as a screenshot of an
   app icon. It gets the figure's light instead. */
.app-icon {
    border-radius: 22%;
    filter: drop-shadow(0 18px 34px rgba(0, 0, 0, 0.75))
            drop-shadow(0 0 28px rgba(33, 173, 232, 0.28));
}

/* A dark-on-light logo needs its light background, so this one plate stays
   pale. It gets the chrome edge so it is still part of the same object. */
.plate-light {
    background: linear-gradient(176deg, #FFFFFF, #DFE2E4) padding-box,
                linear-gradient(176deg, var(--gun-6), var(--gun-3) 55%, var(--gun-0)) border-box;
    border: 1px solid transparent;
    border-radius: var(--radius);
    box-shadow: var(--bevel);
}

/* ---------------------------------------------------------------- chip -- */

.chips { display: flex; flex-wrap: wrap; gap: 0.35rem; padding: 0; margin: 1.1rem 0 0; list-style: none; }

/* Upper case suits the panel, but it is also what made these wrap: tracked-out
   caps run wide, and "Stereo side-by-side" went from one line to two inside a
   card a third of the page across. Small and tight enough that the longest
   label still sits beside its neighbour. */
.chip {
    font-size: 0.67rem;
    font-weight: 600;
    letter-spacing: 0.025em;
    text-transform: uppercase;
    white-space: nowrap;
    padding: 0.26rem 0.5rem;
    border-radius: 2px;
    border: 1px solid transparent;
    color: var(--gun-5);
    background: linear-gradient(176deg, #171E2A, #0D1219) padding-box,
                linear-gradient(176deg, var(--gun-2), #0A0E14) border-box;
    box-shadow: inset 0 1px 0 rgba(216, 217, 211, 0.06);
}
/* The lit one. A chip that is on should look powered, not merely tinted. */
.chip.on {
    color: color-mix(in srgb, var(--accent) 70%, var(--holo-4));
    background: linear-gradient(176deg, #19222F, #0D131C) padding-box,
                linear-gradient(176deg, color-mix(in srgb, var(--accent) 80%, var(--holo-3)), var(--holo-0) 75%, var(--gun-2)) border-box;
    box-shadow: inset 0 1px 0 rgba(216, 217, 211, 0.1),
                0 0 14px -4px color-mix(in srgb, var(--accent) 60%, transparent);
}

/* --------------------------------------------------------------- specs -- */

.specs { width: 100%; border-collapse: collapse; margin-top: 1.5rem; font-size: 0.92rem; }
.specs th, .specs td {
    text-align: left;
    padding: 0.8rem 1rem 0.8rem 0;
    border-bottom: 1px solid transparent;
    border-image: linear-gradient(90deg, var(--gun-2), #151A22 85%, transparent) 1;
    vertical-align: top;
}
.specs th {
    font-family: var(--display);
    font-weight: 400;
    font-size: 0.8rem;
    letter-spacing: 0.02em;
    color: var(--gun-5);
    white-space: nowrap;
    width: 1%;
    padding-right: 2rem;
}
.specs td { color: var(--muted); }
.specs code { font: 0.9em var(--mono); color: color-mix(in srgb, var(--accent) 60%, var(--holo-3)); }

/* On a phone the label column takes whatever `nowrap` demands and cannot give
   any of it back, so a long label leaves the text beside it reading as a column
   of two-word lines. Below this a row stops being a row: the label gets its own
   line and the text underneath gets the full width. */
@media (max-width: 34rem) {
    .specs, .specs tbody, .specs tr, .specs th, .specs td { display: block; }
    .specs tr {
        border-bottom: 1px solid transparent;
        border-image: linear-gradient(90deg, var(--gun-2), transparent) 1;
        padding: 0.9rem 0;
    }
    .specs th, .specs td { border: 0; padding: 0; width: auto; white-space: normal; }
    .specs th { color: var(--gun-6); margin-bottom: 0.3rem; }
    .specs td { color: var(--muted); }
}

/* A claim the site deliberately does not overstate. Set into the page like a
   plate rather than hung off a coloured bar, so it reads as a caveat with
   weight instead of a callout box. */
.note {
    position: relative;
    border: 1px solid transparent;
    border-radius: var(--radius);
    background: linear-gradient(176deg, #121825, #0B1017) padding-box,
                linear-gradient(176deg, var(--gun-2), #0A0E14) border-box;
    padding: 0.9rem 1.1rem 0.9rem 1.5rem;
    margin: 1.75rem 0;
    color: var(--muted);
    font-size: 0.92rem;
}
.note::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 3px;
    background: linear-gradient(180deg, var(--accent), color-mix(in srgb, var(--accent) 30%, transparent));
    box-shadow: 0 0 12px -2px color-mix(in srgb, var(--accent) 70%, transparent);
}
.note p:last-child { margin-bottom: 0; }

/* -------------------------------------------------------------- footer -- */

.foot {
    position: relative;
    padding: 3rem 0;
    color: var(--dim);
    font-size: 0.88rem;
    background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.45));
}
.foot::before {
    content: "";
    position: absolute;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    width: min(var(--page), calc(100% - 3rem));
    height: 1px;
    background: linear-gradient(90deg, transparent, var(--gun-2) 12%, var(--gun-4) 50%, var(--gun-2) 88%, transparent);
}
.foot-in { max-width: var(--page); margin: 0 auto; padding: 0 1.5rem; display: flex; flex-wrap: wrap; gap: 1.5rem 2.5rem; align-items: baseline; }
.foot a {
    color: var(--muted);
    text-decoration: none;
    transition: color 0.2s, text-shadow 0.2s;
}
.foot a:hover { color: var(--holo-3); text-shadow: 0 0 12px rgba(33, 173, 232, 0.5); }
.foot nav { display: flex; gap: 1.4rem; flex-wrap: wrap; }
.foot .copy { margin-left: auto; letter-spacing: 0.02em; }
