:root {
    /* colors */
    --paper: #ffffff;
    --bone: #f4f3f0;
    --bone-2: #eceae5;
    --ink: #1b1917;
    --black: #0d0c0b;
    --red: #d81e28;
    --red-deep: #a5141c;
    --muted: #6e6a64;
    --line: #e5e2dd;
    /* fonts */
    --font-display: "Archivo", "Segoe UI", system-ui, Arial, sans-serif;
    --font-body: "Archivo", "Segoe UI", system-ui, Arial, sans-serif;
    --font-mono: "Quantico", ui-monospace, "Cascadia Code", Consolas, monospace;
    /* font sizes */
    --accent-scale: 1.2;
    --accent-track: .75;
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    font-family: var(--font-body);
    color: var(--ink);
    background: var(--paper);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}

a { color: inherit; }

h1, h2, h3, h4 { font-family: var(--font-display); text-wrap: balance; margin: 0; letter-spacing: -.02em; }

.container { max-width: 1140px; margin: 0 auto; padding-inline: 1.5rem; }

main.container { padding-block: clamp(2rem, 5vw, 3.5rem); }

.eyebrow {
    font-family: var(--font-mono);
    font-size: calc(.72rem * var(--accent-scale));
    letter-spacing: calc(.18em * var(--accent-track));
    text-transform: uppercase;
    color: var(--red);
    margin: 0;
}

.muted { color: var(--muted); }

/* ---------- Header ---------- */
.hdr {
    position: sticky; top: 0; z-index: 30;
    background: rgba(255, 255, 255, .9);
    backdrop-filter: saturate(180%) blur(8px);
    border-bottom: 1px solid var(--line);
}
.hdr-in { display: flex; align-items: center; justify-content: space-between; height: 68px; }
.brand { display: flex; align-items: center; gap: .6rem; text-decoration: none; }
.brand .mark { width: 26px; height: 26px; flex: none; }
.wordmark {
    font-family: var(--font-display); font-weight: 900; font-size: 1.12rem;
    letter-spacing: -.01em; text-transform: uppercase;
    /* baseline, not center: the red "3D" tab is a flex item, so centering it floats its letters off
       the wordmark's baseline. Baseline sits "3D" on the same line as "BIG MACKS" and lets the tab's
       padding hang below, which is what a tab should do. */
    display: inline-flex; align-items: baseline; gap: .35rem; color: var(--ink);
}
.wordmark .td { background: var(--red); color: #fff; font-size: .92rem; padding: .06em .34em; border-radius: 3px; letter-spacing: .02em; }
.nav { display: flex; align-items: center; gap: 1.6rem; }
.nav a { font-size: .92rem; text-decoration: none; color: var(--ink); opacity: .82; }
.nav a:hover { opacity: 1; color: var(--red); }
.nav .cart { font-family: var(--font-mono); font-size: calc(.8rem * var(--accent-scale)); opacity: 1; border: 1px solid var(--line); border-radius: 999px; padding: .3rem .7rem; }
.nav .cart:hover { border-color: var(--ink); color: var(--ink); }
/* Header pickers -- the printer and, beside it, My Colors. Same control, two axes of the same decision
   (what we cut to / what it's cut in), so they share every base rule; only the unset-printer call-out
   below is printer-specific. */
/* Stacked: icon + caption on top, the control itself underneath. Side by side, the caption competed with
   the value for the same line and the two pickers ran together into one strip of text. */
.hdr-pick { display: inline-flex; flex-direction: column; align-items: flex-start; gap: .22rem; font-size: .8rem; }
.hdr-pick .pp-top { display: inline-flex; align-items: center; gap: .35rem; }
.hdr-pick .pp-row { display: inline-flex; align-items: center; gap: .45rem; }
.hdr-pick .pp-ico { font-size: 1rem; opacity: .75; }
.hdr-pick .pp-lbl { font-family: var(--font-mono); font-size: calc(.68rem * var(--accent-scale)); text-transform: uppercase; letter-spacing: calc(.06em * var(--accent-track)); color: var(--muted); }
.hdr-pick select, .cs-trigger { font-family: var(--font-mono); font-size: calc(.78rem * var(--accent-scale)); color: var(--ink); background: transparent; border: 1px solid var(--line); border-radius: 999px; padding: .28rem .6rem; max-width: 15rem; cursor: pointer; }
.hdr-pick select:hover, .cs-trigger:hover { border-color: var(--ink); }
/* two pickers plus the account/cart links is a lot of nav; close the gap between the pair so they read as
   one pair of settings and leave the rest of the bar its breathing room. */
.nav .hdr-pick + .hdr-pick { margin-left: -.8rem; }
/* ---------- the colors listbox ----------
   A <select> can't show a scheme, only name it: an <option> holds text, never markup. So this is a
   listbox, styled to sit in the header as the printer's <select> does -- same pill, same type. */
.color-pick { position: relative; }
.cs-trigger { display: inline-flex; align-items: center; gap: .4rem; max-width: 13rem; }
.cs-cur { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cs-caret { font-size: .6rem; opacity: .6; }
.cs-trigger[aria-expanded="true"] { border-color: var(--ink); }

.cs-menu { position: absolute; z-index: 70; top: 100%; right: 0; margin-top: .3rem; width: 17rem; max-height: 22rem; overflow-y: auto; padding: .3rem; border: 1px solid var(--line); border-radius: 10px; background: #fff; box-shadow: 0 14px 40px rgba(20, 20, 24, .18); }
.cs-grp { position: sticky; top: 0; padding: .45rem .55rem .3rem; font-family: var(--font-mono); font-size: calc(.62rem * var(--accent-scale)); text-transform: uppercase; letter-spacing: calc(.08em * var(--accent-track)); color: var(--muted); background: #fff; }
.cs-opt { display: flex; align-items: center; gap: .55rem; padding: .34rem .55rem; border-radius: 7px; font-size: .84rem; cursor: pointer; }
.cs-opt.is-active { background: #f3f2ef; }
.cs-opt[aria-selected="true"] { font-weight: 700; }
.cs-dots { display: inline-flex; gap: .18rem; flex: none; }
.cs-dots i { width: .78rem; height: .78rem; border-radius: 50%; border: 1px solid rgba(20, 20, 24, .18); }
/* the mixer row before anything has been mixed -- three holes where a scheme would be */
.cs-dots i.is-empty { border-style: dashed; border-color: #b9b5ae; }
.cs-onm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* the chosen scheme's three colors, beside the select -- the only on-screen proof the pick did anything
   before you open a product page. Absent entirely when no scheme is set. */
.pp-sw { display: inline-flex; gap: .22rem; }
.pp-sw i { width: .82rem; height: .82rem; border-radius: 50%; border: 1px solid rgba(20, 20, 24, .18); }

/* The color mixer. It borrows the add-printer dialog's shell (.ap-dlg) and the product page's own
   color controls (.cfg-colors), so there is nothing new to style but the padding the shell normally
   gets from its <form> -- this dialog has no form to hang it on. */
.mc-body { padding: 1.4rem 1.5rem 1.2rem; }
.mc-colors { gap: 1.6rem; margin: 1.1rem 0 1.4rem; }
.mc-colors input[type=color] { width: 64px; height: 40px; }
/* The caption used to be hidden here -- it sat on the select's line and was the first thing to give up
   the width. Stacked it costs no width, so it stays and only the select narrows. */
@media (max-width: 640px) { .hdr-pick select, .cs-trigger { max-width: 9.5rem; } .cs-menu { width: 15rem; } }

/* ---------- "You haven't picked a printer yet" ----------
   The picker decides what every configurable model is cut to, but it sits in the header nav where the eye
   files it as chrome. Unset, on the home page, it stops being chrome: brand-red ring, red label, and a slow
   halo that draws the eye without flashing at it. It reverts to the quiet default the moment a printer is
   chosen (the pick reloads the page), so this can never become permanent wallpaper. */
.printer-pick.needs-printer .pp-lbl { color: var(--red); }
.printer-pick.needs-printer .pp-ico { opacity: 1; }
/* wider than the default 15rem: the bold "Not set - pick your printer" label is the whole point, and
   at the default width the select clips it to "pick your printe". */
.printer-pick.needs-printer select { border-color: var(--red); color: var(--red); font-weight: 600; max-width: 18rem; box-shadow: 0 0 0 3px rgba(216, 30, 40, .12); }
.printer-pick.needs-printer select:hover { border-color: var(--red-deep); }
.printer-pick.needs-printer select { animation: pp-halo 2.6s ease-in-out infinite; }
@keyframes pp-halo {
    0%, 100% { box-shadow: 0 0 0 3px rgba(216, 30, 40, .12); }
    50% { box-shadow: 0 0 0 6px rgba(216, 30, 40, .04); }
}
/* the ping fired when they click "Pick your printer" in the hero */
.printer-pick.is-pinged select { animation: pp-ping .55s ease-out 2; }
@keyframes pp-ping {
    0% { box-shadow: 0 0 0 0 rgba(216, 30, 40, .45); }
    100% { box-shadow: 0 0 0 10px rgba(216, 30, 40, 0); }
}
@media (prefers-reduced-motion: reduce) {
    .printer-pick.needs-printer select, .printer-pick.is-pinged select { animation: none; }
}

/* ---------- "Add your printer" dialog (header picker -> "My printer isn't listed...") ---------- */
.ap-dlg { width: min(46rem, calc(100vw - 2rem)); padding: 0; border: 1px solid var(--line); border-radius: 12px; box-shadow: 0 24px 60px rgba(20, 20, 24, .22); color: var(--ink); background: #fff; }
.ap-dlg::backdrop { background: rgba(20, 20, 24, .45); }
.ap-dlg form { padding: 1.4rem 1.5rem 1.2rem; }
.ap-dlg h2 { margin: 0; font-size: 1.3rem; }
.ap-dlg .ap-lede { margin: .45rem 0 1.1rem; color: var(--muted); font-size: .92rem; line-height: 1.5; max-width: 52ch; }
.ap-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: .7rem .9rem; margin-bottom: .8rem; }
.ap-grid label { display: flex; flex-direction: column; gap: .25rem; font-size: .82rem; color: var(--muted); }
.ap-grid input, .ap-grid select { font: inherit; font-size: .92rem; color: var(--ink); padding: .42rem .55rem; border: 1px solid var(--line); border-radius: 7px; background: #fff; }
.ap-grid input:focus, .ap-grid select:focus { outline: 2px solid var(--red); outline-offset: 1px; border-color: var(--red); }
.ap-more { margin: .3rem 0 1rem; border-top: 1px solid var(--line); padding-top: .8rem; }
.ap-more summary { cursor: pointer; font-size: .88rem; font-weight: 600; }
.ap-dlg .ap-note { margin: .6rem 0 .6rem; color: var(--muted); font-size: .84rem; line-height: 1.5; max-width: 60ch; }
/* The feet diagram, and the A/B/C/D badges that tie it to the four boxes. The badge hangs in the
   label's left margin (padding-left + negative text-indent) so a label that wraps to a second line
   lines up under its own text, not under the badge. */
/* The feet are four boxes, and auto-fit lands them 3 + 1 in a 46rem dialog. Pin them to a 2x2 so
   the two SIZES sit on one line and the two foot-center INSETS on the next. Modifier, not a change
   to .ap-grid: the printable w/d/h row above wants all three side by side. */
.ap-grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 620px) { .ap-grid-2 { grid-template-columns: minmax(0, 1fr); } }
.ap-fig { margin: .5rem 0 .9rem; }
.ap-fig svg { display: block; width: 100%; max-width: 24rem; height: auto; }
.ap-fig svg text { font-family: var(--font-body); }
.ap-fig figcaption { margin-top: .3rem; color: var(--muted); font-size: .78rem; }
.ap-lbl { display: block; padding-left: 1.5rem; text-indent: -1.5rem; }
.ap-key { display: inline-block; width: 1.15rem; height: 1.15rem; margin-right: .35rem; border-radius: 50%; background: var(--red); color: #fff; font-size: .72rem; font-weight: 700; line-height: 1.15rem; text-align: center; text-indent: 0; }
.ap-err { margin: 0 0 .8rem; padding: .55rem .7rem; border-radius: 7px; background: rgba(216, 30, 40, .08); border: 1px solid var(--red); color: var(--red-deep); font-size: .88rem; }
.ap-err[hidden] { display: none; }
.ap-actions { display: flex; justify-content: flex-end; gap: .6rem; }
.ap-actions button { cursor: pointer; }

/* Configurator sections -- "Drawer array size and feet" / "Drawer size" / "Printer". Grouping the
   controls by what they decide, rather than one undifferentiated grid of dropdowns. */
.cfg-sec { border: 1px solid var(--line); border-radius: 10px; padding: .9rem 1.1rem 1rem; margin: 0 0 1rem; }
.cfg-sec > legend { padding: 0 .5rem; margin-left: -.25rem; font-family: var(--font-mono); font-size: calc(.7rem * var(--accent-scale)); text-transform: uppercase; letter-spacing: calc(.09em * var(--accent-track)); color: var(--muted); }
.cfg-sec .cfg-note:last-child { margin-bottom: 0; }
.cfg-sec input:disabled { background: #f4f3f1; color: var(--muted); cursor: not-allowed; }

/* "Saved <printer>" -- survives the reload the save triggers, so the save isn't silent. */
.bm3d-toast { position: fixed; left: 50%; bottom: 1.6rem; transform: translateX(-50%); z-index: 60; padding: .7rem 1.1rem; border-radius: 999px; background: var(--ink); color: #fff; font-size: .9rem; box-shadow: 0 8px 26px rgba(20, 20, 24, .28); opacity: 1; transition: opacity .6s ease; }
.bm3d-toast.is-out { opacity: 0; }
@media (prefers-reduced-motion: reduce) { .bm3d-toast { transition: none; } }

/* The hero call-out that points at it. */
.printer-nudge { display: flex; align-items: center; flex-wrap: wrap; gap: .6rem .8rem; margin: 1.2rem 0 0; padding: .8rem 1rem; border: 1px solid var(--red); border-left-width: 3px; border-radius: 8px; background: rgba(216, 30, 40, .04); font-size: .92rem; line-height: 1.5; }
.printer-nudge .pn-ico { font-size: 1.15rem; }
.printer-nudge .pn-txt { flex: 1 1 16rem; }
.printer-nudge .pn-btn { flex: none; font: inherit; font-size: .85rem; font-weight: 600; color: #fff; background: var(--red); border: 0; border-radius: 999px; padding: .45rem .9rem; cursor: pointer; }
.printer-nudge .pn-btn:hover { background: var(--red-deep); }

/* ---------- Buttons ---------- */
.btn {
    font-family: var(--font-display); font-weight: 700; font-size: .98rem;
    text-decoration: none; border-radius: 10px; padding: .8rem 1.4rem;
    display: inline-flex; align-items: center; gap: .5rem;
    background: var(--red); color: #fff; border: 1.5px solid var(--red); cursor: pointer;
    transition: transform .12s ease, background .15s ease, border-color .15s ease;
}
.btn:hover { background: var(--red-deep); border-color: var(--red-deep); transform: translateY(-1px); color: #fff; }

.btn-outline {
    font-family: var(--font-display); font-weight: 700; font-size: .95rem;
    background: transparent; color: var(--ink); border: 1.5px solid var(--ink);
    border-radius: 10px; padding: .65rem 1.15rem; cursor: pointer; text-decoration: none;
    display: inline-flex; align-items: center; gap: .5rem; transition: background .15s ease, color .15s ease, transform .12s ease;
}
.btn-outline:hover { background: var(--ink); color: #fff; transform: translateY(-1px); }

.btn-link { background: none; border: none; color: var(--muted); cursor: pointer; text-decoration: underline; font-size: .9rem; padding: 0; font-family: var(--font-body); }
.btn-link:hover { color: var(--red); }

/* ---------- Hero (home) ---------- */
.hero { padding-block: clamp(1rem, 3vw, 2.5rem) clamp(2.5rem, 6vw, 4rem); }
.hero-grid { display: grid; grid-template-columns: 1.05fr .95fr; gap: clamp(2rem, 5vw, 4rem); align-items: center; }
@media (max-width: 900px) { .hero-grid { grid-template-columns: 1fr; } }
.hero h1 { font-weight: 800; font-size: clamp(2.3rem, 5vw, 3.8rem); line-height: 1.03; letter-spacing: -.025em; margin: 1rem 0 0; }
.hero h1 em { font-style: normal; color: var(--red); }
.hero .lede { color: var(--muted); font-size: 1.12rem; max-width: 46ch; margin: 1.25rem 0 0; }
.cta-row { display: flex; flex-wrap: wrap; gap: .8rem; margin-top: 1.9rem; }

.hero-actions { display: flex; justify-content: center; margin-top: .85rem; }
.hero-regen { font-size: .8rem; padding: .5rem 1rem; }
.hero-regen:disabled { opacity: .55; cursor: progress; }
.hero-stage { position: relative; border: 1px solid var(--line); border-radius: 16px; background: linear-gradient(180deg, #fbfaf8, var(--bone)); overflow: hidden; aspect-ratio: 5 / 4; }
.hero-stage canvas { display: block; width: 100%; height: 100%; }
.hero-stage .hero-viewer { position: absolute; inset: 0; z-index: 0; }
.hero-stage .hero-viewer canvas { cursor: grab; }
.hero-stage .hero-viewer canvas:active { cursor: grabbing; }
/* Still render shown until the live model loads. The render is baked on flat white, so the panel
   is white too (no seam around the letterboxed image); it fades out to reveal the 3D stage. */
.hero-stage .hero-poster { position: absolute; inset: 0; z-index: 1; pointer-events: none; background: #fff; transition: opacity .55s ease; }
.hero-stage .hero-poster.is-loaded { opacity: 0; }
.hero-stage .hero-poster img { width: 100%; height: 100%; object-fit: contain; }
.hero-viewer-hint { position: absolute; left: 0; right: 0; bottom: 14px; text-align: center; font-family: var(--font-mono); font-size: calc(.72rem * var(--accent-scale)); letter-spacing: calc(.1em * var(--accent-track)); text-transform: uppercase; color: var(--muted); }
.hero-stage .stage-tag { position: absolute; z-index: 2; pointer-events: none; left: 14px; top: 12px; font-family: var(--font-mono); font-size: calc(.68rem * var(--accent-scale)); letter-spacing: calc(.12em * var(--accent-track)); text-transform: uppercase; color: var(--muted); }
.hero-stage .price-chip { position: absolute; right: 14px; bottom: 14px; background: var(--black); color: #fff; border-radius: 12px; padding: .6rem .85rem; display: flex; align-items: center; gap: .6rem; box-shadow: 0 10px 30px rgba(13, 12, 11, .22); }
.hero-stage .price-chip .n { font-family: var(--font-display); font-weight: 800; font-size: 1.2rem; }
.hero-stage .price-chip .k { font-family: var(--font-mono); font-size: calc(.6rem * var(--accent-scale)); letter-spacing: calc(.1em * var(--accent-track)); text-transform: uppercase; color: #b9b6b0; line-height: 1.2; }

/* ---------- Spec strip ---------- */
.strip { border-block: 1px solid var(--line); background: var(--bone); }
.strip-in { display: flex; flex-wrap: wrap; gap: .6rem 1.4rem; padding-block: .9rem; font-family: var(--font-mono); font-size: calc(.74rem * var(--accent-scale)); letter-spacing: calc(.06em * var(--accent-track)); text-transform: uppercase; }
.strip-in span { display: inline-flex; align-items: center; gap: .5rem; }
.strip-in span::before { content: ""; width: 6px; height: 6px; background: var(--red); border-radius: 2px; transform: rotate(45deg); }

/* ---------- Section heads ---------- */
.section-head { display: flex; align-items: end; justify-content: space-between; gap: 1rem; margin: clamp(2.5rem, 5vw, 4rem) 0 1.75rem; }
.section-head h2 { font-weight: 800; font-size: clamp(1.6rem, 3vw, 2.15rem); }
.section-head p { margin: .4rem 0 0; color: var(--muted); }

/* ---------- Catalog ---------- */
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.25rem; }
@media (max-width: 860px) { .grid { grid-template-columns: 1fr; max-width: 420px; } }

/* The category page's card list. `.card-grid` carried no rule at all, so its cards stacked as
   full-width blocks with nothing between them -- hence a 16/9 image the width of the page, and one
   card butting straight onto the next.

   Now a grid the visitor sizes: 2, 3 or 4 across, remembered in a cookie and rendered server-side so
   it arrives in the chosen shape rather than snapping into it. The image no longer needs holding back
   to 80% -- a column narrower than the page was what the 80% was solving.

   Keyed on `.cat-list`, a class this page adds for the purpose, NOT on `.catalog` -- the home page
   wraps its shelves in a .catalog as well, so `.catalog .card-viz` reached in and shrank every
   shelf card to 80% too. The home page's promo band also uses a bare `.card-grid` and inherits the
   same missing gap; left alone here, but worth a look on its own. */
.cat-list { display: grid; gap: 1.25rem; align-items: start; }
.cat-list.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.cat-list.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.cat-list.cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
/* The choice is a ceiling, not a promise: four 300px cards do not fit a tablet. Step down rather than
   letting the cards squeeze to unreadable. */
@media (max-width: 1180px) { .cat-list.cols-4 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 900px)  { .cat-list.cols-3, .cat-list.cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px)  { .cat-list.cols-2, .cat-list.cols-3, .cat-list.cols-4 { grid-template-columns: minmax(0, 1fr); } }

/* The 2/3/4 control, above the grid and right-aligned so it sits out of the reading path. */
.cols-pick { display: flex; align-items: center; justify-content: flex-end; gap: .35rem; margin: 0 0 1rem; }
.cols-lbl { font-family: var(--font-mono); font-size: calc(.68rem * var(--accent-scale)); text-transform: uppercase; letter-spacing: calc(.06em * var(--accent-track)); color: var(--muted); margin-right: .15rem; }
.cols-btn {
    font-family: var(--font-mono); font-size: calc(.78rem * var(--accent-scale)); line-height: 1; cursor: pointer;
    color: var(--muted); background: transparent;
    border: 1px solid var(--line); border-radius: 7px; padding: .34rem .6rem;
}
.cols-btn:hover { color: var(--ink); border-color: var(--ink); }
.cols-btn.is-on { color: var(--ink); border-color: var(--ink); background: var(--bone-2); font-weight: 700; }
.cols-btn:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; }
/* Below the smallest breakpoint the grid is one column whatever is picked, so the control is a lie. */
@media (max-width: 620px) { .cols-pick { display: none; } }

/* Name first, above the picture: on a page you read top-to-bottom the name is what you are scanning
   for, and under the image it arrived after the thing it names. */
.cat-title { padding: 1.15rem 1.3rem .35rem; font-weight: 800; font-size: 1.3rem; }
/* The one-liner that says what the thing IS, before the picture shows it. */
.cat-tagline { margin: 0; padding: 0 1.3rem 1rem; color: var(--muted); font-size: 1rem; line-height: 1.5; }
.cat-card .card-viz { border-top: 1px solid var(--line); }
/* The product's real marketing copy, not the one-line tagline. Authored HTML, so style the paragraphs
   it actually contains rather than assuming a single block. */
.cat-copy { color: var(--muted); font-size: .95rem; line-height: 1.6; }
.cat-copy p { margin: 0 0 .7rem; }
.cat-copy p:last-child { margin-bottom: 0; }
.cat-copy strong { color: var(--ink); }

.card {
    display: flex; flex-direction: column; color: inherit; text-decoration: none;
    border: 1px solid var(--line); border-radius: 14px; overflow: hidden; background: var(--paper);
    transition: box-shadow .18s ease, transform .18s ease, border-color .18s ease;
}
.card:hover { transform: translateY(-3px); box-shadow: 0 16px 40px rgba(13, 12, 11, .09); border-color: #d9d5cf; }
.card-viz {
    position: relative; aspect-ratio: 16 / 9; border-bottom: 1px solid var(--line); display: grid; place-items: center;
    background:
        radial-gradient(circle at 1px 1px, rgba(27, 25, 23, .10) 1px, transparent 0) 0 0 / 16px 16px,
        linear-gradient(180deg, #fbfaf8, var(--bone-2));
}
.card-viz .glyph { width: 46%; height: 46%; }
.card-viz img { width: 100%; height: 100%; object-fit: contain; display: block; }
.card-body { padding: 1.1rem 1.2rem 1.3rem; display: flex; flex-direction: column; gap: .5rem; flex: 1; }
.card h3 { font-weight: 800; font-size: 1.16rem; }
.card .desc { margin: 0; color: var(--muted); font-size: .92rem; flex: 1; }
.card .meta { display: flex; align-items: center; justify-content: space-between; gap: .5rem; margin-top: .4rem; }
.card .price { font-family: var(--font-display); font-weight: 800; font-size: 1.1rem; font-variant-numeric: tabular-nums; }

.badge {
    font-family: var(--font-mono); font-size: calc(.64rem * var(--accent-scale)); text-transform: uppercase; letter-spacing: calc(.08em * var(--accent-track));
    color: var(--red); border: 1px solid var(--red); border-radius: 4px; padding: .18rem .45rem;
    /* On the catalog card this is a flex item in `.card .meta`, so it centers and vertical-align is
       ignored. On the product page it is an inline span trailing the price, where the default
       baseline alignment sits its TEXT on the baseline and lets its padding + border hang below the
       line -- which reads as the badge drooping. `middle` centers the box on the text instead.
       Note this must stay inline: the price there can wrap to two lines, and making the parent a flex
       row drops the badge onto a line of its own. */
    vertical-align: middle;
}

/* ---------- Product detail ---------- */
.back { margin: 0 0 1.25rem; }
.back a { color: var(--muted); text-decoration: none; font-family: var(--font-mono); font-size: calc(.82rem * var(--accent-scale)); }
.back a:hover { color: var(--red); }

.product { max-width: 940px; }
.product h1 { margin: 0 0 .5rem; font-weight: 800; font-size: clamp(1.9rem, 4vw, 2.6rem); }
.price-lg { font-family: var(--font-display); font-size: 1.3rem; font-weight: 800; margin: 0 0 1rem; }
.product .desc { color: var(--muted); margin: 0 0 1.5rem; font-size: 1.05rem; }

/* ---------- Configurator (product page) ---------- */
.configurator { border: 1px solid var(--line); border-radius: 14px; padding: 1.2rem 1.35rem; margin: 1.25rem 0 1.5rem; background: var(--bone); }
.configurator h3 { font-weight: 800; font-size: 1.05rem; margin: 0 0 1rem; }
.cfg-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: .75rem; }
.cfg-grid label { display: flex; flex-direction: column; gap: .3rem; font-family: var(--font-mono); font-size: calc(.74rem * var(--accent-scale)); text-transform: uppercase; letter-spacing: calc(.05em * var(--accent-track)); color: var(--muted); }
.cfg-grid select { padding: .5rem .55rem; border: 1px solid #cfccc6; border-radius: 8px; font-size: 1rem; font-family: var(--font-body); background: var(--paper); line-height: 1.35; }
/* Text inputs sitting in a .cfg-grid (e.g. Foot height, laptop Base depth/width) -- the .txt-sm box
   style is otherwise only defined as .cfg-plate .txt-sm, so it never reaches these. Match the selects.

   Both carry an explicit line-height, and that is what makes the two boxes the same HEIGHT. Matching
   padding and font-size is not enough: left at `normal`, an <input> and a <select> compute different
   content heights from the same font, so a select sat ~4px shorter than the number box beside it. */
.cfg-grid .txt-sm { width: 100%; padding: .5rem .55rem; border: 1px solid #cfccc6; border-radius: 8px; font-size: 1rem; font-family: var(--font-body); background: var(--paper); line-height: 1.35; }
/* The second line of a .cfg-grid label: the unit, and where to take the measurement. Lower case and
   unemphasised on purpose -- the label above it is the field, this is the instruction, and a stack of
   two shouting mono lines reads as two fields. */
.cfg-grid .cfg-hint { text-transform: none; letter-spacing: normal; font-weight: 400; opacity: .8; }
.cfg-plate { display: flex; gap: 1rem; margin: .9rem 0 0; flex-wrap: wrap; }
.cfg-plate label { display: flex; flex-direction: column; gap: .3rem; font-family: var(--font-mono); font-size: calc(.72rem * var(--accent-scale)); text-transform: uppercase; letter-spacing: calc(.05em * var(--accent-track)); color: var(--muted); }
/* Same explicit line-height as .cfg-grid select / .cfg-grid .txt-sm, and for the same reason: left
   at `normal`, Chrome ignores the page's inherited line-height inside a <select> but honors it in
   an <input>, so the printer select sat visibly shorter than the bed width/depth boxes beside it. */
.cfg-plate .txt-sm { width: 130px; padding: .5rem .55rem; border: 1px solid #cfccc6; border-radius: 8px; font-size: 1rem; font-family: var(--font-body); line-height: 1.35; }
.cfg-plate .cfg-printer select { min-width: 15rem; max-width: 100%; padding: .5rem .55rem; border: 1px solid #cfccc6; border-radius: 8px; font-size: 1rem; font-family: var(--font-body); background: var(--paper); line-height: 1.35; }
/* Combo boxes: drop the browser's native chrome and draw our own caret, so a <select> matches the
   flat text inputs beside it (same border, radius, background, padding) instead of the OS control. */
.cfg-grid select, .cfg-plate .cfg-printer select, .ap-grid select {
    -webkit-appearance: none; -moz-appearance: none; appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none' stroke='%236e6a64' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1.5 1.5 6 6l4.5-4.5'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right .6rem center; padding-right: 1.9rem;
}
.cfg-custom { display: inline-flex; gap: 1rem; flex-wrap: wrap; }
.cfg-note { font-family: var(--font-mono); font-size: calc(.74rem * var(--accent-scale)); color: var(--muted); line-height: 1.5; }
.cfg-colors { display: flex; gap: 1rem; margin: .9rem 0; flex-wrap: wrap; }
.cfg-colors label { display: flex; flex-direction: column; gap: .3rem; font-family: var(--font-mono); font-size: calc(.72rem * var(--accent-scale)); text-transform: uppercase; letter-spacing: calc(.05em * var(--accent-track)); color: var(--muted); align-items: flex-start; }
.cfg-colors input[type=color] { width: 46px; height: 32px; border: 1px solid #cfccc6; border-radius: 6px; padding: 0; background: none; cursor: pointer; }
/* The scheme row that replaced the per-product color pickers. Colors are chosen once in the header, so
   a product page shows what it will be built in and offers the one decision that IS per-product: which of
   the three lands on the biggest surface. */
.cfg-scheme { display: flex; align-items: flex-start; gap: .5rem; margin: .9rem 0; flex-wrap: wrap; }
.cs-sw { display: flex; flex-direction: column; align-items: center; gap: .32rem; }
.cs-sw i { width: 46px; height: 32px; border-radius: 6px; border: 1px solid #cfccc6; }
.cs-lbl { font-family: var(--font-mono); font-size: calc(.66rem * var(--accent-scale)); text-transform: uppercase; letter-spacing: calc(.05em * var(--accent-track)); color: var(--muted); }
/* nudged down so the arrow sits against the swatches, not the labels under them */
.cs-swap { margin-top: .35rem; padding: .1rem .3rem; font-size: 1.05rem; line-height: 1; color: var(--muted); background: none; border: 1px solid transparent; border-radius: 5px; cursor: pointer; }
.cs-swap:hover { color: var(--ink); border-color: var(--line); }
.cs-swap:focus-visible { outline: 2px solid var(--red); outline-offset: 1px; }
/* a slot pointed at another one has no color of its own on show to trade */
.cs-swap:disabled { opacity: .3; cursor: default; }
.cs-swap:disabled:hover { color: var(--muted); border-color: transparent; }
/* "Its own color" / "Same as primary": the one decision a slot past the first can make about itself.
   Sized off the label above it so the picker reads as part of the swatch rather than a form field, and
   left to size ITSELF widthwise -- a cap in rem clips "Same as secondary" at the larger --accent-scale. */
.cs-link { font-family: var(--font-mono); font-size: calc(.66rem * var(--accent-scale)); color: var(--muted);
    padding: .1rem .25rem; background: none; border: 1px solid var(--line); border-radius: 5px; cursor: pointer; }
.cs-link:hover { color: var(--ink); }
/* borrowed, not its own -- so two swatches showing one color still read as two roles */
.cs-sw.is-linked i { border-style: dashed; }

/* The assembled-size readout. Sized like the price, because for someone measuring a shelf it is the
   other number the decision turns on -- mm big, inches under it for the tape most US buyers own. */
.cfg-dims { display: flex; gap: 1.1rem 2.2rem; flex-wrap: wrap; }
.cfg-dims .dim { display: flex; flex-direction: column; gap: .15rem; }
.dim-lbl { font-family: var(--font-mono); font-size: calc(.7rem * var(--accent-scale)); text-transform: uppercase; letter-spacing: calc(.07em * var(--accent-track)); color: var(--muted); }
.dim-mm { font-family: var(--font-display); font-weight: 700; font-size: 1.25rem; line-height: 1.2; }
.dim-in { font-family: var(--font-mono); font-size: calc(.76rem * var(--accent-scale)); color: var(--muted); }

.cfg-price { margin: .6rem 0 1rem; font-family: var(--font-display); font-weight: 700; font-size: 1.1rem; }

.preview3d { position: relative; width: 100%; height: 400px; margin-top: 1rem; border: 1px solid var(--line); border-radius: 12px; background: radial-gradient(130% 100% at 50% 0%, #ffffff, #f3f2ef); overflow: hidden; }
.preview3d canvas { display: block; }
/* "Show my printer on the stand" -- the translucent printer ghost in the 3D preview. */
.ghost-toggle { display: flex; align-items: baseline; gap: .5rem; margin: .6rem 0 0; font-size: .86rem; line-height: 1.45; cursor: pointer; }
.ghost-toggle[hidden] { display: none; }
.ghost-toggle input { flex: none; }
.ghost-toggle .muted { font-size: .8rem; }
.preview-hint, .preview-status { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; text-align: center; color: var(--muted); padding: 1rem; font-family: var(--font-mono); font-size: calc(.82rem * var(--accent-scale)); }
.preview-status { background: rgba(255, 255, 255, .82); font-weight: 500; color: var(--ink); }
.build-stats { position: absolute; left: 0; right: 0; bottom: 0; display: flex; flex-wrap: wrap; gap: .35rem 1.4rem; justify-content: center; padding: .5rem 1rem; background: rgba(255, 255, 255, .88); border-top: 1px solid var(--line); font-family: var(--font-mono); font-size: calc(.76rem * var(--accent-scale)); color: var(--ink); backdrop-filter: blur(2px); }
.build-stats span { white-space: nowrap; }
.build-stats .build-price { font-weight: 600; color: var(--red); }
/* "won't fit your bed" warning + the gated Add-to-cart it disables */
.fit-warn { margin: .9rem 0 0; padding: .7rem .9rem; border: 1px solid #e6b7ba; border-left: 3px solid var(--red); border-radius: 8px; background: #fdf3f3; color: #7d1a20; font-size: .85rem; line-height: 1.45; }
.fit-warn[hidden] { display: none; }
/* The plate size the rack's slots are cut to. Stated on every rack build, not just the doubtful ones:
   the customer is the only person who can see whether the number matches the sheet in their hand. */
.plate-note { border-left: 3px solid var(--line); padding-left: .7rem; }
.fit-ok { margin: 0 0 1rem; padding: .7rem .9rem; border: 1px solid #bfe0c4; border-left: 3px solid #2f9e44; border-radius: 8px; background: #f2fbf4; color: #1c5a2b; font-size: .9rem; line-height: 1.45; }
.cfg-printer-line { margin: 0 0 1rem; font-size: .95rem; }
.meas-panel { background: #fbfaf8; }
.meas-panel .cfg-grid { margin-bottom: .9rem; }
.meas-panel input[type="text"], .meas-panel input[type="number"] { width: 100%; }
.btn.is-disabled { opacity: .5; cursor: not-allowed; pointer-events: none; }
.cfg-fulfillment { margin-top: 1.1rem; border: 1px solid var(--line); border-radius: 12px; padding: .6rem 1rem 1rem; }
.cfg-fulfillment legend { font-family: var(--font-mono); font-size: calc(.72rem * var(--accent-scale)); text-transform: uppercase; letter-spacing: calc(.08em * var(--accent-track)); color: var(--muted); padding: 0 .4rem; }
.cfg-fulfillment .ff-opt { display: flex; gap: .6rem; align-items: flex-start; padding: .55rem .2rem; cursor: pointer; line-height: 1.35; }
.cfg-fulfillment .ff-opt + .ff-opt { border-top: 1px solid var(--line); }
.cfg-fulfillment .ff-opt input { margin-top: .2rem; }
.cfg-fulfillment .ff-price { font-variant-numeric: tabular-nums; }
.cfg-fulfillment .ff-physical input:disabled ~ span { opacity: .55; }
.cart-fulfil { display: inline-block; font-family: var(--font-mono); font-size: calc(.68rem * var(--accent-scale)); text-transform: uppercase; letter-spacing: calc(.06em * var(--accent-track)); padding: .12rem .45rem; border-radius: 4px; margin-left: .4rem; vertical-align: middle; }
.cart-fulfil.physical { background: var(--red); color: #fff; }
.cart-fulfil.digital { border: 1px solid var(--line); color: var(--muted); }
.cart-total.sub { font-weight: 400; color: var(--muted); border-bottom: 0; padding-bottom: .15rem; }
.cart-total.sub .price { font-weight: 400; }
.ship-fields { margin: 1.2rem 0 .4rem; padding: 1rem 1.1rem 1.2rem; border: 1px solid var(--line); border-radius: 12px; display: flex; flex-direction: column; gap: .4rem; }
.ship-fields .ship-h { margin: 0 0 .4rem; font-size: 1rem; text-transform: none; letter-spacing: 0; color: var(--ink); }
.ship-fields .txt { display: block; width: 100%; box-sizing: border-box; }
.ship-row { display: flex; gap: .8rem; }
.ship-row > span { flex: 1; display: flex; flex-direction: column; gap: .4rem; }
.ship-to { margin: .4rem 0 1rem; padding: .8rem 1rem; border: 1px solid var(--line); border-radius: 10px; background: rgba(0,0,0,.02); }
.ship-to-h { font-family: var(--font-mono); font-size: calc(.72rem * var(--accent-scale)); text-transform: uppercase; letter-spacing: calc(.08em * var(--accent-track)); color: var(--muted); margin: 0 0 .3rem; }
.ship-to-addr { margin: 0 0 .4rem; line-height: 1.5; }
.calc-ship { display: flex; align-items: center; gap: .8rem; margin: .9rem 0 .2rem; flex-wrap: wrap; }
.ship-svc { font-family: var(--font-mono); font-size: calc(.66rem * var(--accent-scale)); color: var(--muted); }
button.btn[disabled] { opacity: .5; cursor: not-allowed; }

/* ---------- Cart / checkout / order ---------- */
.cart-table { width: 100%; border-collapse: collapse; margin-bottom: 1.5rem; }
.cart-table th, .cart-table td { text-align: left; padding: .8rem .5rem; border-bottom: 1px solid var(--line); }
.cart-table th { font-family: var(--font-mono); font-size: calc(.72rem * var(--accent-scale)); text-transform: uppercase; letter-spacing: calc(.08em * var(--accent-track)); color: var(--muted); font-weight: 500; }
.cart-table th.price, .cart-table td.price { text-align: right; font-variant-numeric: tabular-nums; }
/* The line picture. Fixed width so the item column starts in the same place whether or not a product
   has an image, and top-aligned because the line beside it can run to two rows of text. */
.cart-table th.thumb, .cart-table td.thumb { width: 64px; padding-right: 0; vertical-align: top; }
.cart-table a { color: var(--ink); font-weight: 600; text-decoration: none; }
.cart-table a:hover { color: var(--red); }
.cfg-desc { display: block; font-family: var(--font-mono); font-size: calc(.76rem * var(--accent-scale)); color: var(--muted); margin-top: .25rem; }

.summary { list-style: none; padding: 0; margin: 0 0 1.5rem; max-width: 680px; }
.summary li { display: flex; justify-content: space-between; align-items: center; padding: .7rem .2rem; border-bottom: 1px solid var(--line); gap: .5rem; }

.cart-total { display: flex; justify-content: space-between; align-items: baseline; max-width: 680px; font-family: var(--font-display); font-size: 1.2rem; font-weight: 800; padding: .8rem .2rem 1.1rem; }
.cart-total .price { font-variant-numeric: tabular-nums; }
.cart-actions { max-width: 680px; }

.checkout-form { max-width: 560px; margin-top: 1.5rem; display: flex; flex-direction: column; gap: .5rem; }
.checkout-form label { font-family: var(--font-mono); font-size: calc(.74rem * var(--accent-scale)); text-transform: uppercase; letter-spacing: calc(.05em * var(--accent-track)); color: var(--muted); margin-top: .7rem; }
.checkout-form .txt { padding: .7rem .75rem; border: 1px solid #cfccc6; border-radius: 8px; font-size: 1rem; font-family: var(--font-body); }
#card-container { min-height: 90px; padding: .2rem 0; }
.checkout-form .btn { margin-top: 1.1rem; align-self: flex-start; }
.field-error { color: var(--red); font-size: .9rem; margin: .3rem 0 0; }

.notice { max-width: 680px; margin-top: 1.5rem; padding: 1rem 1.2rem; background: var(--bone); border: 1px solid var(--line); border-radius: 10px; color: var(--muted); }

.order-box { max-width: 680px; border: 1px solid var(--line); border-radius: 14px; padding: 1.3rem 1.5rem; margin-top: 1rem; }
/* "Your makes" is the only order-box with a heading -- the rest lead with an order number in a <p>. */
.order-box h2 { font-size: 1.05rem; margin: 0 0 .9rem; }
.downloads li { flex-wrap: wrap; gap: .5rem; align-items: center; }
.dl-name { flex: 1 1 55%; font-weight: 600; }
/* Holds several buttons now (all / STLs / 3MF / guide), so it wraps rather than running off a narrow
   screen -- the line's name and price already claim most of the row. */
.dl-action { flex: 0 0 auto; display: flex; flex-wrap: wrap; gap: .4rem; justify-content: flex-end; }
.dl-action .btn, .dl-action .btn-outline { padding: .4rem .85rem; font-size: .9rem; }
.gen-status { display: inline-flex; align-items: center; gap: .45rem; color: var(--muted); font-family: var(--font-mono); font-size: calc(.82rem * var(--accent-scale)); }
/* A failed build isn't a dead end: it reads as a note with a way out, not an error shouting in red. */
.gen-failed { display: inline-flex; align-items: center; flex-wrap: wrap; gap: .5rem; color: var(--muted); font-size: .9rem; }
.order-box .btn-link { margin-left: .5rem; }
.spinner { width: 14px; height: 14px; border: 2px solid #cfccc6; border-top-color: var(--red); border-radius: 50%; display: inline-block; animation: bm-spin .8s linear infinite; }
@keyframes bm-spin { to { transform: rotate(360deg); } }

/* ---------- How it works ---------- */
.steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem; }
@media (max-width: 800px) { .steps { grid-template-columns: 1fr; max-width: 460px; } }
.step .bar { height: 3px; background: linear-gradient(90deg, var(--red), transparent); border-radius: 3px; margin-bottom: 1rem; }
.step .num { font-family: var(--font-mono); font-size: calc(.78rem * var(--accent-scale)); color: var(--red); letter-spacing: calc(.1em * var(--accent-track)); }
.step h3 { font-weight: 800; font-size: 1.2rem; margin: .5rem 0 .4rem; }
.step p { margin: 0; color: var(--muted); font-size: .95rem; }

/* ---------- Footer ---------- */
.foot { background: var(--black); color: #cfccc6; margin-top: clamp(3rem, 6vw, 5rem); }
.foot-in { display: grid; grid-template-columns: 1.6fr 1fr 1fr; gap: 2rem; padding-block: 3rem 2rem; }
@media (max-width: 640px) { .foot-in { grid-template-columns: 1fr 1fr; } }
.foot .wordmark { color: #fff; }
.foot h4 { font-family: var(--font-mono); font-size: calc(.72rem * var(--accent-scale)); letter-spacing: calc(.12em * var(--accent-track)); text-transform: uppercase; color: #86837d; margin: 0 0 .9rem; font-weight: 500; }
.foot ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.foot a { text-decoration: none; color: #cfccc6; font-size: .9rem; }
.foot a:hover { color: #fff; }
.foot-note { border-top: 1px solid rgba(255, 255, 255, .1); padding-block: 1.1rem 1.5rem; font-family: var(--font-mono); font-size: calc(.72rem * var(--accent-scale)); color: #86837d; display: flex; justify-content: space-between; flex-wrap: wrap; gap: .5rem; }
/* The build stamp is machinery, not brand: dimmer than the line it sits on so the eye passes over it
   until someone is looking for it. `help` cursor advertises the tooltip carrying the exact build time. */
.foot-ver { color: #6b6862; cursor: help; }

:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; border-radius: 4px; }

/* ---- product info / marketing page ---- */
html { scroll-behavior: smooth; }

.phero { display: grid; grid-template-columns: 1.05fr 1fr; gap: 2.5rem; align-items: center; margin: .5rem 0 2.75rem; }
.phero.no-media { grid-template-columns: 1fr; max-width: 640px; }
.phero-media { background: var(--paper); border: 1px solid var(--line); border-radius: 16px; padding: 1rem; box-shadow: 0 18px 40px -24px rgba(13, 12, 11, .35); }
.phero-media img { display: block; width: 100%; height: auto; border-radius: 8px; }
.phero-copy h1 { margin: 0 0 .5rem; }
.phero-copy .tagline { font-family: var(--font-display); font-size: clamp(1.1rem, 2vw, 1.3rem); font-weight: 600; line-height: 1.35; color: var(--ink); margin: 0 0 1rem; }
.phero-copy .price-lg { margin: 0 0 .35rem; }
.phero-copy .desc { margin: 0 0 1.5rem; font-size: 1rem; }
.btn-cta { display: inline-block; text-decoration: none; font-size: 1.02rem; padding: .7rem 1.4rem; }

.pmarketing { max-width: 660px; margin: 0 auto 2.75rem; font-size: 1.08rem; line-height: 1.72; color: var(--ink); }
.pmarketing p { margin: 0 0 1rem; }
.pmarketing p:last-child { margin-bottom: 0; }

.pfeatures, .pgallery { margin: 0 0 2.75rem; }
.pfeatures h2, .pgallery h2 { font-family: var(--font-display); font-weight: 800; font-size: 1.5rem; margin: 0 0 1.25rem; padding-bottom: .5rem; border-bottom: 2px solid var(--red); display: inline-block; }
.feature-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; }
.feature { border: 1px solid var(--line); border-radius: 14px; padding: 1.1rem 1.2rem; background: var(--bone); }
.feature h4 { font-family: var(--font-display); font-weight: 800; font-size: 1rem; margin: 0 0 .4rem; }
.feature p { margin: 0; color: var(--muted); font-size: .95rem; line-height: 1.55; }

.gallery-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.1rem; }
.shot { margin: 0; }
.shot img, .shot video { display: block; width: 100%; height: auto; border-radius: 12px; border: 1px solid var(--line); }
/* A video with no poster paints transparent until its first frame decodes, which on the bone page
   background reads as a hole in the grid. --paper is what the product media sits on elsewhere. */
.shot video { background: var(--paper); }
.shot figcaption { font-family: var(--font-mono); font-size: calc(.74rem * var(--accent-scale)); color: var(--muted); margin-top: .5rem; }

.buy-block { scroll-margin-top: 90px; border-top: 1px solid var(--line); padding-top: 2rem; }
.buy-block .buy-h { font-family: var(--font-display); font-weight: 800; font-size: 1.6rem; margin: 0 0 1.25rem; }

/* "In your download" -- what justifies paying over a free generator; sits under the buy heading. */
.deliverables { background: var(--bone); border: 1px solid var(--line); border-radius: 10px; padding: 1.25rem 1.4rem; margin: 0 0 1.75rem; }
.deliverables h3 { font-family: var(--font-mono); text-transform: uppercase; letter-spacing: calc(.08em * var(--accent-track)); font-size: calc(.72rem * var(--accent-scale)); color: var(--ink); margin: 0 0 1rem; }
.dl-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .85rem; }
.dl-list li { display: grid; grid-template-columns: 1.15rem 1fr; column-gap: .65rem; align-items: start; }
.dl-list li::before { content: "\2713"; color: var(--red); font-weight: 800; line-height: 1.4; }
.dl-t { grid-column: 2; font-weight: 700; color: var(--black); }
.dl-d { grid-column: 2; font-size: .9rem; color: var(--ink); opacity: .8; margin-top: .1rem; }

@media (max-width: 720px) {
    .phero { grid-template-columns: 1fr; gap: 1.5rem; }
}

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    .btn, .btn-outline, .card { transition: none; }
}

/* ---- Admin area (A6). Staff-only pages: dense, plain, no marketing styling. ---- */
.admin-nav { display: flex; gap: .25rem; border-bottom: 1px solid var(--line); margin: 0 0 1.5rem; }
.admin-tab { padding: .55rem .9rem; text-decoration: none; color: var(--muted);
             border-bottom: 2px solid transparent; font-size: .92rem; }
.admin-tab:hover { color: var(--ink); }
.admin-tab.is-current { color: var(--ink); border-bottom-color: var(--red); }

.admin-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
               gap: 1rem; margin: 0 0 2rem; }
.admin-card { border: 1px solid var(--line); border-radius: 12px; padding: 1rem 1.1rem; background: var(--paper); }
.admin-card h2 { font-size: .82rem; text-transform: uppercase; letter-spacing: .08em;
                 color: var(--muted); margin: 0 0 .4rem; font-weight: 500; }
.admin-card .admin-stat { font-family: var(--font-mono); font-size: calc(1.9rem * var(--accent-scale)); line-height: 1.1; margin: 0 0 .2rem; }
.admin-card .muted { font-size: .84rem; margin: 0; }

.admin-sec { margin: 0 0 2rem; }
.admin-sec h2 { font-size: 1.05rem; margin: 0 0 .75rem; }

/* A row of top-of-page actions on a record: the button, then what it will do, on one line. Wraps
   rather than shrinks on a narrow screen, so the explanation never gets squeezed to one word a column. */
.admin-actions { display: flex; align-items: center; flex-wrap: wrap; gap: .6rem .9rem; margin: 0 0 1.5rem; }
.admin-actions .muted { font-size: .84rem; }

/* Colour dots closing a configuration line, led by the scheme's name. Not admin-* any more: the cart
   closes its lines the same way, so a buyer and an operator read one picture of one configuration.
   The nudge is because .pp-sw is an inline-FLEX box, which otherwise sits its dots on the text
   baseline rather than through the middle of the words. */
.line-sw { margin-left: .35rem; vertical-align: -.12rem; }
.line-scheme { margin-left: .45rem; color: var(--muted); font-size: .82rem; white-space: nowrap; }
.admin-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.admin-table th { text-align: left; font-weight: 500; color: var(--muted); font-size: .8rem;
                  text-transform: uppercase; letter-spacing: .06em;
                  padding: .5rem .6rem; border-bottom: 1px solid var(--line); }
.admin-table td { padding: .55rem .6rem; border-bottom: 1px solid var(--line); }
.admin-table td.num, .admin-table th.num { text-align: right; font-family: var(--font-mono); }
.admin-table tbody tr:hover { background: var(--bone); }

/* Drag-to-reorder. The grip is the ONLY draggable part of a row -- see admin-reorder.js, which arms
   the row on mousedown here and disarms it on mouseup. */
.admin-table td.grip-cell, .admin-table th.grip-cell { width: 1.9rem; padding-right: 0; text-align: center; }
.grip { background: none; border: 0; padding: .1rem .25rem; cursor: grab; color: var(--muted);
        font-size: 1.05rem; line-height: 1; border-radius: 4px; }
.grip:hover { color: var(--ink); background: var(--bone-2); }
/* A visible focus ring matters more than usual here: the grip is the keyboard route to reordering
   (arrow keys), so it has to be obvious which row the arrows will move. */
.grip:focus-visible { outline: 2px solid var(--red); outline-offset: 1px; color: var(--ink); }
.grip:active { cursor: grabbing; }
.admin-table tbody tr.is-dragging { opacity: .45; background: var(--bone-2); }
.admin-table tbody tr.is-dragging td { border-bottom-color: var(--red); }

/* Order status. Color carries meaning here, so each state is distinct rather than decorative. */
.pill { display: inline-block; padding: .12rem .5rem; border-radius: 999px;
        font-size: .76rem; letter-spacing: .02em; border: 1px solid var(--line); }
.pill-paid { background: #e8f5ec; border-color: #b9dfc6; color: #1d6b36; }
.pill-pending { background: #fdf5e3; border-color: #ecd9a8; color: #8a6416; }
.pill-failed { background: #fdeceb; border-color: #f2c2be; color: #a5231a; }
.pill-refunded { background: var(--bone-2); color: var(--muted); }

.admin-filters { display: flex; gap: .75rem; align-items: flex-end; flex-wrap: wrap; margin: 0 0 1.25rem; }
.admin-filters label { display: flex; flex-direction: column; gap: .25rem; font-size: .82rem; color: var(--muted); }
/* A checkbox filter reads on ONE line with its box, unlike the stacked label-over-control the text and
   select filters use -- so it opts out of the column direction rather than inheriting it. */
.admin-filters label.admin-check { flex-direction: row; align-items: center; gap: .4rem; cursor: pointer; }
.admin-filters label.admin-check input { margin: 0; }
.admin-table th { white-space: nowrap; }
.admin-table tbody th { text-align: left; color: var(--muted); font-weight: 400; width: 12rem; }

/* The refund block is the one destructive control in the admin. Marked out so it never reads as
   just another form on the page. */
.admin-refund { border: 1px solid var(--red); border-radius: 12px; padding: 1rem 1.15rem; }
.admin-refund h2 { color: var(--red-deep); margin-top: 0; }

.notice { padding: .6rem .85rem; border-radius: 8px; margin: 0 0 1.25rem; font-size: .92rem; }
.notice-ok { background: #e8f5ec; border: 1px solid #b9dfc6; color: #1d6b36; }
.notice-bad { background: #fdeceb; border: 1px solid #f2c2be; color: #a5231a; }

.admin-form { display: grid; gap: .85rem; }
.admin-form label { display: flex; flex-direction: column; gap: .25rem; font-size: .85rem; color: var(--muted); }

/* ---------- Form controls: admin forms, admin filters, and the bare .txt-sm ----------
   One rule for every control on these pages, because the old one matched input[type=text] and
   textarea ONLY. Everything else fell through to the browser's default box: the <select> on
   Suggest, Promotions and the Orders filter; the email field on Suggest (TextMode="Email" renders
   type=email, which input[type=text] does not match); and every .txt-sm, whose box was only ever
   defined as .cfg-grid .txt-sm / .cfg-plate .txt-sm and so reached nothing outside a configurator.
   That is why one row could show a rounded Headline beside a square Promote and square dates.

   Listing the types rather than using a bare `input` on purpose -- a bare selector would also grab
   checkboxes, radios and buttons and give them padding and a border they should not have.

   font-size is set back UP off the label. These labels are small and muted by design; inheriting
   that into the control is what made the boxes feel cramped as much as the .45rem padding did. */
.admin-form input[type=text], .admin-form input[type=email], .admin-form input[type=url],
.admin-form input[type=number], .admin-form input[type=date], .admin-form input[type=search],
.admin-form input[type=password], .admin-form input[type=tel], .admin-form select, .admin-form textarea,
.admin-filters input[type=text], .admin-filters input[type=email], .admin-filters input[type=number],
.admin-filters input[type=date], .admin-filters input[type=search], .admin-filters select,
input.txt-sm {
    font: inherit; font-size: .95rem; color: var(--ink);
    padding: .5rem .6rem;
    border: 1px solid var(--line); border-radius: 8px;
    /* background-COLOR, not the shorthand: the caret rule below paints a background-image, and this
       rule is the later one -- the shorthand would silently reset it and leave a select with no
       arrow at all. Same reason the caret's padding-right is re-stated there rather than here. */
    background-color: var(--paper);
}
.admin-form textarea { line-height: 1.5; resize: vertical; }
/* The filter row is a toolbar, so its controls size to their content instead of filling a column --
   but a bare .txt-sm elsewhere is in a stacked label and should fill it. */
.admin-filters input.txt-sm, .admin-filters select { min-width: 11rem; }
.admin-form input.txt-sm, .admin-form select, .admin-form textarea { width: 100%; }
/* The muted default border is a whisper next to the ink text now sitting in the box; darken it on
   hover and hand focus to the site's red, the way the dialog fields already do. */
.admin-form input:hover, .admin-form select:hover, .admin-form textarea:hover,
.admin-filters input:hover, .admin-filters select:hover, input.txt-sm:hover { border-color: #cfccc6; }
.admin-form input:focus, .admin-form select:focus, .admin-form textarea:focus,
.admin-filters input:focus, .admin-filters select:focus, input.txt-sm:focus {
    outline: 2px solid var(--red); outline-offset: 1px; border-color: var(--red);
}
.admin-form input::placeholder, input.txt-sm::placeholder { color: #a9a59e; }
/* The checkbox opted out of the column layout above; keep it out of the box styling too. */
.admin-filters label.admin-check input { min-width: 0; padding: 0; border: 0; }

/* Drop the OS combo chrome and draw our own caret -- the same treatment .cfg-grid/.ap-grid selects
   already get, so a rounded select stops looking like a Windows control that wandered in. Must sit
   AFTER the padding rule above: it re-states padding-right for the caret's gutter, and a `padding`
   shorthand landing later would close that gutter and print the arrow over the text. */
.admin-form select, .admin-filters select {
    -webkit-appearance: none; -moz-appearance: none; appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none' stroke='%236e6a64' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1.5 1.5 6 6l4.5-4.5'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right .6rem center;
    padding-right: 1.9rem;
}

.admin-form-narrow { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
textarea.mono { width: 100%; font-family: var(--font-mono); font-size: calc(.82rem * var(--accent-scale)); line-height: 1.5;
    padding: .55rem; border: 1px solid var(--line); border-radius: 6px; }
.admin-thumb { width: 120px; height: auto; border: 1px solid var(--line); border-radius: 6px; }
video.admin-thumb { display: block; background: var(--paper); }
.admin-wide { width: 100%; font: inherit; padding: .4rem .5rem; border: 1px solid var(--line); border-radius: 6px; }
.btn-link { background: none; border: 0; padding: 0; color: var(--muted); cursor: pointer;
    font: inherit; text-decoration: underline; }
.btn-link:hover { color: var(--red); }

.admin-scroll { overflow-x: auto; margin: .5rem 0 1.25rem; }
.admin-sec h3 { margin: 1.1rem 0 .25rem; }
.admin-sec h3 + .muted { margin: 0 0 .5rem; font-size: .82rem; }

.hp-field { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
.admin-subnav { margin-top: -.4rem; font-size: .85rem; }
.sug-card { border: 1px solid var(--line); border-radius: 10px; padding: .9rem 1rem; margin: 0 0 .9rem; }
.sug-meta { margin: 0 0 .3rem; font-size: .82rem; color: var(--muted); }
.sug-body { margin: .5rem 0 .7rem; white-space: normal; }
.sug-actions { margin: .6rem 0 0; display: flex; gap: 1rem; flex-wrap: wrap; }

/* "Post a make", on an order line. The <li> it sits in is a flex row that already wraps, so this
   claims a whole row of its own rather than squeezing in beside the name and the download buttons. */
.make-post { flex: 1 1 100%; margin: .35rem 0 .1rem; }
.make-post > summary { cursor: pointer; font-size: .88rem; color: var(--muted); width: fit-content; }
.make-post > summary:hover { color: var(--red); }
.make-post[open] > summary { color: var(--ink); font-weight: 600; }
.make-post-body { margin: .75rem 0 .3rem; padding: .9rem 1rem 1rem; border: 1px solid var(--line); border-left: 3px solid var(--red); border-radius: 10px; background: var(--bone); }
.make-post-body .admin-form { gap: .7rem; }
.make-post-body textarea { width: 100%; max-width: 34rem; }
.make-post-body .cfg-note { margin: .8rem 0 .9rem; max-width: 62ch; }
.make-post-body p:last-child { margin-bottom: 0; }

.make-card { display: flex; gap: 1rem; align-items: flex-start; }
.make-thumb { width: 190px; height: auto; border: 1px solid var(--line); border-radius: 8px; display: block; }
.make-meta { flex: 1; min-width: 0; }

.cat-nav { display: flex; gap: .9rem; flex-wrap: wrap; margin: -.4rem 0 1.4rem; font-size: .88rem; }
.cat-nav a { color: var(--muted); text-decoration: none; border-bottom: 1px solid var(--line); }
.cat-nav a:hover { color: var(--red); border-color: var(--red); }
.cat-pick { display: flex; gap: .8rem 1.4rem; flex-wrap: wrap; }
.cat-pick label { display: flex; gap: .35rem; align-items: center; font-size: .9rem; }

/* ---------- Category shelves (home catalog) ----------
   One horizontally scrolling track per category. The track is a plain overflow container, so swipe,
   trackpad, shift-wheel and the scrollbar all work with no script; the arrows are layered on top. */
/* The catalog's table of contents: in-page links to each shelf. */
.shelf-jump { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0 0 2rem; }
.shelf-jump a {
    font-family: var(--font-mono); font-size: calc(.74rem * var(--accent-scale)); text-transform: uppercase; letter-spacing: calc(.05em * var(--accent-track));
    color: var(--muted); text-decoration: none;
    border: 1px solid var(--line); border-radius: 999px; padding: .38rem .85rem;
}
.shelf-jump a:hover { color: var(--ink); border-color: var(--ink); }
.shelf-jump a:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; }

/* scroll-margin, because the header is sticky: without it a jump puts the shelf heading UNDER the
   header and the visitor lands looking at cards with no idea which shelf they are on. */
.shelf { margin: 0 0 2.6rem; scroll-margin-top: 88px; }
.shelf-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; margin: 0 0 .85rem; }
.shelf-head h3 { font-weight: 800; font-size: 1.22rem; }
.shelf-all { font-size: .85rem; color: var(--muted); text-decoration: none; white-space: nowrap; border-bottom: 1px solid var(--line); }
.shelf-all:hover { color: var(--red); border-color: var(--red); }

.shelf-body { position: relative; }
.shelf-scroll {
    overflow-x: auto; overflow-y: hidden;
    scroll-snap-type: x proximity; scroll-behavior: smooth;
    scrollbar-width: thin;
    /* The cards lift on hover, and an overflow container clips that. Pad, then pull the padding back
       out with a matching negative margin, so the lift has room without the shelf gaining a gutter. */
    padding: 5px 5px 14px; margin: -5px -5px -14px;
}
/* The shelf card's width and gutter, hoisted out of the rule below because the home sidebar sizes
   ITSELF from them -- it takes whatever the row has left after three of these fit (see .home-body).
   Declared here, beside the rule that draws the card, so the two cannot drift apart. */
:root { --shelf-card: 270px; --shelf-gap: 1.25rem; }

.shelf-track {
    display: grid; grid-auto-flow: column; grid-auto-columns: var(--shelf-card); gap: var(--shelf-gap);
    /* One fixed width, not a minmax. A range lets each shelf size its cards to its own item count --
       250px on a shelf of seven, 290px on a shelf of two -- so nothing lines up down the page. Fixed
       also leaves a card part-visible at the edge on most widths, which says "this scrolls" better
       than any arrow does. justify-content keeps a short shelf left-aligned instead of stretched. */
    justify-content: start;
}
.shelf-track .card { scroll-snap-align: start; }
/* Clamp the blurb, so one long tagline does not set the height of every card on the shelf. The clamp
   goes on the inner span, not the <p>: see Services\Catalog.vb -- the <p> is a flex item and its
   display gets blockified out from under -webkit-box. min-height keeps short and long cards level. */
.shelf-track .card .desc { min-height: 3.9em; }
.shelf-track .card .desc > span {
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
/* Long names wrap to two lines on a narrow card; reserve both so the price rows line up shelf-wide. */
.shelf-track .card h3 { font-size: 1.08rem; min-height: 2.5em; }
.shelf-track .card .meta { flex-wrap: wrap; }

.shelf-nav {
    position: absolute; top: 50%; transform: translateY(-50%);
    width: 36px; height: 36px; padding: 0; border-radius: 50%;
    border: 1px solid var(--line); background: rgba(255, 255, 255, .94);
    color: var(--ink); font-size: 1.35rem; line-height: 1; cursor: pointer;
    display: grid; place-items: center;
    box-shadow: 0 4px 16px rgba(13, 12, 11, .12);
    transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.shelf-nav:hover { background: #fff; border-color: var(--red); color: var(--red); }
.shelf-nav[hidden] { display: none; }
.shelf-prev { left: -10px; }
.shelf-next { right: -10px; }
@media (max-width: 860px) {
    /* Touch scrolls directly; the arrows would only cover a card. */
    .shelf-nav { display: none; }
    /* One width, not a minmax: a max is an invitation to stretch, and a two-card shelf took it --
       the pair split the viewport and came out half again as wide as a full shelf's cards. */
    .shelf-track { grid-auto-columns: min(78vw, 300px); }
}

/* ---------- Home sidebar ----------
   Beside the shelves, not above or below them. minmax(0, 1fr) on the catalog column is load-bearing:
   the shelves are overflow-x scrollers, and an `auto`/`1fr` track measures their UNSCROLLED content --
   a shelf of nine cards would size the column to ~2500px and shove the sidebar off the page. */
.home-body {
    --home-gap: 2rem;
    /* Three shelf cards, flush, plus 4px so a sub-pixel container width cannot shave the third one.
       A fixed 20rem sidebar left the catalog at 737px -- two cards and a sliver of a third. */
    --catalog-3up: calc(3 * var(--shelf-card) + 2 * var(--shelf-gap) + 4px);

    display: grid;
    /* The sidebar is the REMAINDER, not a width of its own: whatever the 1092px row has left once
       three cards and the gutter are paid for, which comes to 206px. Sizing it this way is what
       keeps the third card whole -- a fixed sidebar width silently eats into the shelf instead.
       Both tracks are minmax(0, ...) so neither can be forced wider than the row by its contents. */
    grid-template-columns: minmax(0, 1fr) minmax(0, calc(100% - var(--catalog-3up) - var(--home-gap)));
    /* Fixed, not the old clamp(..., 3vw, ...): the container is capped at 1140px, so a
       viewport-relative gutter only made the catalog column breathe in and out by a few px --
       enough to put the third card's edge on and off the screen depending on the window. */
    gap: var(--home-gap);
    /* start, not stretch: the catalog runs ~2600px and the aside ~1000px, and a stretched aside would
       hand that whole height to its last card rather than letting the pair sit at the top of the row. */
    align-items: start;
}
/* Stack below the width at which .container stops growing. Above it the row is a known 1092px and
   the three-card arithmetic is exact; below it the row shrinks but the three cards do not, so the
   remainder the sidebar lives on would collapse to a useless 14px sliver before it ever stacked.
   The sidebar falls below the catalog, which is the right order -- the models are what the page
   is for -- and gets the full width back to read in. */
@media (max-width: 1139px) { .home-body { grid-template-columns: minmax(0, 1fr); } }

/* NOT sticky. It was, at top: 88px, and that only works while the aside is shorter than the viewport.
   At 206px the same copy sets ~1014px tall, and a stuck element taller than the window never scrolls
   up again -- the bottom 200px of the AI card became permanently unreachable on a 900px screen. The
   height moves every time the copy changes, so this cannot be fixed with a height threshold; scrolling
   with the page has no such failure mode. */
.home-side { display: grid; gap: 1.1rem; }

/* Tighter than a card elsewhere on the site, because at 206px the padding is the difference between
   a readable measure and four words a line. */
.side-card { border: 1px solid var(--line); border-radius: 14px; padding: 1rem 1.05rem 1.1rem; background: var(--bone); }
.side-card h3 { font-weight: 800; font-size: 1.02rem; line-height: 1.25; margin: .35rem 0 .5rem; }
.side-card p { margin: 0 0 .7rem; color: var(--muted); font-size: .88rem; line-height: 1.55; }
.side-card p:last-child { margin-bottom: 0; }
/* Full width and centered: at this measure the label wraps to two lines, and a wrapped button that
   still fills its column reads as deliberate where a shrink-wrapped one reads as broken. */
.side-card .side-btn { display: flex; justify-content: center; text-align: center; margin-top: .5rem; font-size: .85rem; padding: .5rem .8rem; }

/* Stacked, the sidebar has the whole page width -- so the two cards sit side by side rather than
   as one long ribbon, and the type goes back to its comfortable size. */
@media (max-width: 1139px) {
    .home-side { grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); gap: 1.25rem; margin-top: 2.5rem; }
    .side-card { padding: 1.15rem 1.25rem 1.3rem; }
    .side-card h3 { font-size: 1.12rem; }
    .side-card p { font-size: .93rem; line-height: 1.6; }
    .side-card .side-btn { display: inline-flex; }
}

.promo-band { margin: 0 0 2rem; }
.promo-card { border-color: var(--red); }
.promo-flag { margin: 0 0 .2rem; font-size: .68rem; letter-spacing: .1em; text-transform: uppercase; color: var(--red); font-weight: 500; }
