/* ============================================================================
   Enhancements — layered on top of the original theme (theme.css).
   ----------------------------------------------------------------------------
   Nothing here restyles the existing design. It adds what a static site had no
   way to express: signed-in navigation states, a scroll-reactive navbar, form
   feedback, and the accessibility basics the original CSS was missing.

   Every colour below is taken from the existing palette:
     accent  #EA9C22   surface  #141313 / #1A1919
     border  rgba(255,255,255,.08)      muted  #8A8585
   ========================================================================= */

/* ── Accessibility ──────────────────────────────────────────────────────── */

/* The original stylesheet suppressed focus outlines site-wide, which made
   every page unusable by keyboard. Restored, in brand amber. */
:focus-visible {
    outline: 2px solid #EA9C22;
    outline-offset: 3px;
    border-radius: 4px;
}

.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 2000;
}
.skip-link:focus {
    left: 16px;
    top: 16px;
    padding: 10px 18px;
    background: #EA9C22;
    color: #0F0E0E;
    font-weight: 700;
    border-radius: 10px;
}

[x-cloak] { display: none !important; }


/* ── Device tokens ─────────────────────────────────────────────────────────
   Two facts about the DEVICE that the whole site has to answer, declared once
   here because this is the only stylesheet both layouts load and because the
   answer is the same on a marketing page and inside the dashboard.

   ── 1. THE 16px FLOOR UNDER EVERY TEXT FIELD ───────────────────────────────
   iOS Safari zooms the entire page when a field whose computed font-size is
   BELOW 16px is focused, and it does not zoom back out afterwards. The
   customer is left on a page 1.4x too wide, scrolling sideways to read the
   form they were half way through, and nothing on the screen says what
   happened or how to undo it. It is invisible on Android and invisible on a
   desktop browser at 375px, which is why it survives review.

   Measured across every route in the dashboard before this existed, EVERY
   text-entry control on the member side was under the line: .dash-input,
   .dash-select and .dash-textarea at 14px (Profile, Security, Licences,
   Tickets, Top-up — 17 controls), .adm-inline-select and .adm-inline-input at
   13.5px (Orders, Wallet — 6), .adm-search-input at 15px (Orders), and the
   sign-in and register overlay's own fields at 14.5px. Not one field in the
   signed-in area, or on the way into it, could be focused on an iPhone without
   the page zooming.

   A FLOOR, NOT A SIZE, and that is the whole reason it is a token rather than
   a rule. `font-size: max(var(--field-font-floor, 0px), 14px)` is exactly 14px
   on a pointer device — the floor resolves to 0 and the declared size is what
   the designer wrote, to the pixel — and exactly 16px on a phone. Written the
   other way round, as a blanket `input { font-size: 16px }` somewhere late in
   the cascade, it would also have SHRUNK the two controls that are
   deliberately larger than the floor (.tfa-code-input at 20px, .adm-res-pct's
   figure at 18px) and it would have had to out-specify a dozen class rules in
   three stylesheets to land at all — the "restated at several rungs" failure
   this codebase has already been bitten by twice. max() at the declaration has
   no rung to lose: there is one number for the device and one for the design,
   and the larger wins.

   KEYED ON THE POINTER, NOT ON THE VIEWPORT. A 400px-wide desktop window does
   not zoom and does not need 16px type; an iPad at 1024px does and does. The
   sheet already uses `@media (hover: none)` for the copy affordances in
   dashboard.css, so this is the same distinction drawn with the same tool.

   ── 2. THE SAFE AREA ───────────────────────────────────────────────────────
   partials/head sets `viewport-fit=cover`, which is what PUTS the page under
   the notch and the home indicator — without it the browser letterboxes and
   there is no inset to respect. With it, anything full-bleed or fixed to an
   edge has to reserve for env(safe-area-inset-*) or it is drawn underneath the
   hardware. Only the BOTTOM inset was ever reserved (the footer and the
   sidebar foot), so a notched phone in LANDSCAPE — where the notch is on the
   long edge, 47px of it on an iPhone 14 Pro — put the sidebar's icons, the
   navbar's brand and the content pane's left gutter behind it.

   env() is read into custom properties rather than used directly at each site
   for one reason that matters more than tidiness: env() cannot be set, so a
   layout that consumes it directly cannot be TESTED anywhere except on the
   physical phone. Through a variable it can: set --safe-l on <html> in the
   console and every edge that honours the inset moves, and every edge that
   does not, does not. That is how the arithmetic below was checked. */
:root {
    --field-font-floor: 0px;

    --safe-l: env(safe-area-inset-left, 0px);
    --safe-r: env(safe-area-inset-right, 0px);
    --safe-b: env(safe-area-inset-bottom, 0px);
}

@media (pointer: coarse) {
    :root { --field-font-floor: 16px; }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ============================================================================
   Site-wide layout corrections
   ----------------------------------------------------------------------------
   These exist because the original was six separate HTML files that never sat
   next to each other. Now that the navbar and the footer are ONE partial
   rendered on every page, any width or padding a page stylesheet declares for
   a shared class becomes a visible jump when you navigate. Corrected here
   rather than in the page stylesheets, which stay as the original design.
   ========================================================================= */

/* ── Canonical chrome width ─────────────────────────────────────────────────
   THE RULE: .container is 1200px on every page, full stop. That is
   theme.css:40's value and what home, /store's hero, /status, /faq, /contact,
   /terms, /privacy, /email/verify and /reset-password all use.

   product.css:1608 redeclares `.container { max-width: 1300px }` at the same
   specificity and loads after theme.css, so on product pages it won this and
   dragged the NAVBAR and the FOOTER with it — both use .container. Measured at
   a 1425px viewport: nav container 1300px wide starting at x=62.5 on a product
   page vs 1200px at x=112.5 on every other page. Clicking into a product made
   the logo and the nav links jump 50px outward, which reads as the navbar
   moving. This one rule loads after product.css and pins chrome AND product
   content back to 1200.

   Why 1200 and not 1300: the product grid is fluid all the way down to its
   1024px breakpoint, so 1200 is well inside its tested range, whereas widening
   the chrome to 1300 everywhere would push the nav wider than the content it
   frames on the five other page types.

   DOCUMENTED EXCEPTION: store.css's .store-container stays at 1400px on /store
   and /status. That is a sidebar + card grid designed for the extra width and
   narrowing it costs a column. The rule is that the CHROME is the constant —
   nav and footer are 1200px on all eight page types — and a content region may
   opt out when its own layout needs to, not the other way round.

   Wrapped in @media screen so it does not outrank theme.css:5121's print
   block, which deliberately un-caps .container for the legal pages. */
@media screen {
    .container { max-width: 1200px; }
}

/* ── Scrollbar gutter ───────────────────────────────────────────────────────
   scrollbar-gutter was set nowhere, so the layout was 7.5px narrower on a page
   that scrolls than on one that does not, and the fixed navbar jogged
   sideways every time you navigated between a short page and a tall one.
   Reserving the gutter always makes every page lay out identically.

   (The related worry — the fixed navbar overhanging the scrollbar — is not
   real: `width: 100%` on a fixed element resolves against the initial
   containing block, which already excludes a classic scrollbar. Measured nav
   width == documentElement.clientWidth exactly.) */
html {
    scrollbar-gutter: stable;

    /* SAFARI'S TEXT AUTOSIZER, TURNED OFF — and 100%, never `none`.

       WebKit inflates the font-size of text blocks it judges too wide for the
       screen, and it does it hardest when an iPhone is turned to LANDSCAPE:
       body copy comes back larger than it was written, every measured height on
       the page is wrong by an amount nothing in the stylesheet can predict, and
       it happens on exactly one browser on exactly one orientation, which is
       why it survives every desktop check. Nothing in this codebase declared
       it — no reset here sets it, and theme.css is the original design and does
       not either — so every landscape phone has been reading a slightly
       different site.

       `100%` means "do not inflate", not "do not scale": pinch zoom and the
       reader's own text-size setting still work exactly as before. `none` would
       also disable those, which is an accessibility failure and is the reason
       this value is spelled out rather than copied from the first search
       result.

       Both spellings, because Safari only understands the prefixed one and
       Chrome only understands the standard one on desktop. */
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;

    /* nav.navbar is position:fixed and ~71px tall, so every in-page anchor,
       every :target and browser scroll restoration landed the target underneath
       it (measured getBoundingClientRect().top === 0.14 on /#faq-container-landing).
       71px of bar plus breathing room. */
    scroll-padding-top: 90px;
}

/* ── Navbar: scroll state ───────────────────────────────────────────────── */

/* The navbar is solid at rest (theme.css:53 — deliberately, it sits over the
   hero artwork) and gets *more* solid plus a soft edge once you scroll off the
   hero, so it reads as lifted above the content passing under it. The previous
   values here were 3% MORE transparent than the resting state, i.e. the effect
   was inverted and invisible; the comment that used to sit here claimed the
   bar was transparent over the hero, which it never was.

   The transition is narrowed to the properties that actually change: theme.css:58
   sets `transition: all 0.2s ease`, which meant the ~2px height change when the
   Outfit webfont swaps in ANIMATED, and the bar visibly wobbled on load. */
nav.navbar {
    transition: background-color .25s ease, border-color .25s ease,
                backdrop-filter .25s ease, box-shadow .25s ease;
}
nav.navbar.navbar-scrolled {
    background: rgba(15, 14, 14, .98);
    backdrop-filter: blur(16px);
    border-bottom-color: rgba(255, 255, 255, .10);
    box-shadow: 0 10px 30px rgba(0, 0, 0, .35);
}

/* ── Navbar: no layout shift while fonts load ───────────────────────────────
   Outfit and RemixIcon both load with a swap/block window. Measured: the nav
   was 69px tall on the fallback and 71px once Outfit landed, and the hamburger
   glyph went 15px → 20.84px wide. Because the bar is position:fixed nothing
   below it moves, so the change reads as the bar itself settling.

   min-height pins the row to its settled height so a shorter fallback metric
   cannot shrink it, and the icon <i> gets a reserved 1em box so a glyph that
   has not painted yet still occupies its final width. Together with the
   width/height now on the logo <img> (navbar.blade.php) the navbar geometry is
   final at first paint. */
nav.navbar > .container { min-height: 40px; }

nav.navbar .navbar-toggle i,
nav.navbar .nav-account-trigger i,
nav.navbar .nav-dashboard-btn i,
nav.navbar .nav-verify-badge i {
    display: inline-block;
    min-width: 1em;
    min-height: 1em;
    text-align: center;
    font-style: normal;
}

/* Active page marker. */
nav.navbar .navbar-menu > li > a.is-active {
    color: #fff;
}
nav.navbar .navbar-menu > li > a.is-active::after {
    content: '';
    display: block;
    height: 2px;
    margin-top: 5px;
    border-radius: 2px;
    background: #EA9C22;
}

/* ── Navbar: Dashboard button ───────────────────────────────────────────────
   An <a>, so theme.css:499's .btn-primary already gives it the right box —
   this only stops the label wrapping when the account username beside it is
   long. It is deliberately amber: it sits in the slot a guest sees the Login
   button in, so the primary action does not move when you sign in. */
.nav-dashboard-btn { white-space: nowrap; }

/* ── Navbar: unverified nudge ───────────────────────────────────────────── */

.nav-verify-badge {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 7px 13px;
    font-size: 12px;
    font-weight: 600;
    color: #F97316;
    border: 1px solid rgba(249, 115, 22, .35);
    background: rgba(249, 115, 22, .10);
    border-radius: 10px;
    text-decoration: none;
    white-space: nowrap;
    transition: background-color .2s ease;
}
.nav-verify-badge:hover { background: rgba(249, 115, 22, .18); }

.nav-verify-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #F97316;
}

/* ── Navbar: the cart ───────────────────────────────────────────────────────
   "cart should be top right at navbar."

   The first thing in .navbar-actions, on BOTH layouts — see the comment on the
   markup in partials/navbar for why that slot and not the far right.

   SELECTED THROUGH nav.navbar .navbar-actions, WHICH IS NOT DECORATION.
   theme.css:113 declares `nav.navbar .navbar-actions a { padding: 8px 20px;
   border-radius: 6px }`, which is specificity (0,2,2). A bare `.nav-cart` is
   (0,1,0) and LOSES to it regardless of file order, so the chip would silently
   inherit the pill geometry of a Login button — 40px wider than it needs to be,
   in the one part of the bar that has no room to spare. Written this way it is
   (0,3,1) and wins on its own merits, with no !important.

   A FIXED-WIDTH SQUARE, and that is the measurement that drove the design. The
   bar between 769px and 1023px is already at its limit: the wallet chip stands
   down entirely in that band because 133px of it pushed .navbar-actions 103px
   past the viewport at 780px (see the note on that chip below). A cart cannot
   stand down anywhere — it is the entry point to the basket and there is no
   sidebar entry to fall back on — so it is built to be the narrowest thing that
   can carry a count: one glyph, and the number OUTSIDE the box.

   The badge is absolutely positioned for the same reason. An inline count makes
   the control 18px wider the moment somebody adds an item, so the whole row
   shifts left as you shop; overlapping the corner, the chip is the same width
   with a basket of nothing and a basket of ninety-nine. */
nav.navbar .navbar-actions .nav-cart,
nav.navbar > .container > .nav-cart-bar {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 8px 11px;
    border: 1px solid rgba(255, 255, 255, .08);
    border-radius: 12px;
    background: transparent;
    color: #C9C5C5;
    font-size: 14px;
    font-weight: 500;
    line-height: 1;
    text-decoration: none;
    white-space: nowrap;
    transition: border-color .2s ease, color .2s ease, background-color .2s ease;
}

/* Borrowed from .nav-account-trigger exactly, so the cart, the wallet chip and
   the account button read as one family of controls rather than three widgets
   that happen to share a row. */
nav.navbar .navbar-actions .nav-cart:hover,
nav.navbar > .container > .nav-cart-bar:hover {
    color: #fff;
    border-color: rgba(234, 156, 34, .45);
    background: rgba(255, 255, 255, .03);
}

.nav-cart > i {
    font-size: 17px;
    /* The same reserved box every other glyph in this bar gets: RemixIcon loads
       with a block window, so without it the chip is narrower until the font
       lands and the bar visibly settles on first paint. */
    display: inline-block;
    min-width: 1em;
    min-height: 1em;
    text-align: center;
    font-style: normal;
}

/* The word "Cart", clipped on desktop and real in the mobile panel.

   ONE ELEMENT RATHER THAN A .sr-only SPAN PLUS A VISIBLE ONE. The chip needs an
   accessible name at every width and a visible label at exactly one of them; two
   elements would mean a screen reader hearing "Cart" twice on a phone. This is
   the standard clip, undone inside the 768px block at the bottom of this
   section. */
.nav-cart-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* AMBER, AND THE ONE AMBER THING THAT IS ALLOWED TO APPEAR AND DISAPPEAR.
   Everything else accented in this bar is permanent (the Dashboard button, the
   wallet glyph); this exists only when there is something in the basket, which
   is what makes it read as news rather than as chrome.

   The ring is the navbar's own background colour, so the badge reads as sitting
   ON the chip rather than being clipped by its corner — the bar is
   rgba(15,14,14,.95) over whatever the page is, and a solid #0F0E0E ring is
   indistinguishable from it at this size.

   NOT COLOUR ALONE: it is a number. There is nothing here a colour-blind
   customer has to infer from a hue. */
.nav-cart-count {
    position: absolute;
    top: -7px;
    right: -7px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 19px;
    height: 19px;
    padding: 0 5px;
    border-radius: 10px;
    background: #EA9C22;
    color: #0F0E0E;
    font-size: 11px;
    font-weight: 800;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    box-shadow: 0 0 0 2px #0F0E0E;
}

/* ── Navbar: wallet balance ─────────────────────────────────────────────────
   The balance chip that sits immediately left of the account menu on panel
   pages. Rendered only when partials/navbar is given $walletChip, which
   DashboardNavComposer supplies and only for the dashboard layout — so this
   block is inert on marketing pages even though this stylesheet loads there.

   DECLARED HERE, beside .nav-account and .nav-verify-badge, rather than in
   dashboard.css: it is part of the signed-in account cluster in the bar, it
   has to obey the same 768px stacking rule as .nav-dashboard-btn (see the
   media query at the bottom of this section of the file), and splitting one
   visual group across two stylesheets is how the pieces come to disagree.

   NEUTRAL, NOT AMBER. The bar already has one accent object — the Dashboard
   button — and a second amber thing beside it makes neither read as the
   primary action. This borrows .nav-account-trigger's exact box instead: same
   12px radius, same 1px hairline, same muted text, same hover. The two then
   read as one control pair rather than as a badge stuck onto a button.

   PHONE WIDTHS ARE THE MEASURED RISK. Below 768px the whole of
   .navbar-actions collapses into the mobile menu panel as a full-width column
   (see the media query further down), so this chip never competes with the
   username for space in the bar itself — the bar there is a logo and a
   hamburger. The rule that matters is therefore the stacking one, not a
   truncation one. Measured at 430px and 360px: nothing overflows and the bar's
   own layout is unchanged with the chip present.

   The figure is tabular so it does not jitter in width as the balance changes
   — a chip that resizes on every page load reads as the layout settling. */
.nav-wallet {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 7px 12px;
    border: 1px solid rgba(255, 255, 255, .08);
    border-radius: 12px;
    background: transparent;
    color: #C9C5C5;
    font-size: 14px;
    font-weight: 500;
    line-height: 1;
    /* The bar's own `.navbar-actions a` rule (theme.css:113) sets 8px 20px and
       a 6px radius on every anchor in here. This is more specific by one class,
       so the values above win — but the shorthand there also sets
       text-decoration, which is restated rather than inherited. */
    text-decoration: none;
    white-space: nowrap;
    transition: border-color .2s ease, color .2s ease, background-color .2s ease;
}

.nav-wallet:hover {
    color: #fff;
    border-color: rgba(234, 156, 34, .45);
    background: rgba(255, 255, 255, .03);
}

/* Amber, and only the glyph. It is the one thing in the chip that says "money"
   at a glance, and colouring the figure itself would make a €0.00 balance look
   like a warning. */
.nav-wallet > i {
    font-size: 16px;
    color: #EA9C22;
    /* Same reserved box as the other navbar glyphs above: RemixIcon loads with
       a block window, so without this the chip is narrower until the font
       lands and the bar visibly settles. */
    display: inline-block;
    min-width: 1em;
    min-height: 1em;
    text-align: center;
    font-style: normal;
}

.nav-wallet-value {
    color: #FFFFFF;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* ── The band where the bar has no room for it ──────────────────────────────
   MEASURED, NOT GUESSED, and this rule exists because the first version of
   this chip broke the navbar without producing a scrollbar to notice it by.

   The bar is one flex row: brand, then .navbar-menu, which CONTAINS
   .navbar-actions. Between 769px (where theme.css stops collapsing the menu
   into the mobile panel) and roughly 890px, that row is already at its limit —
   measured on production before this chip existed, .navbar-actions cleared the
   container's right edge by 7px at a 780px viewport. Adding 133px of balance
   chip to a row with 7px of slack pushed .navbar-actions 103px PAST the
   viewport at 780px and 63px past it at 820px.

   And it did so SILENTLY. nav.navbar is position:fixed, and a fixed element's
   overflow does not extend the document's scrollable area — so
   documentElement.scrollWidth still equalled clientWidth, no scrollbar
   appeared, and the account menu simply sat off the right-hand edge of the
   screen where nobody could reach it. That is worse than a scrollbar: a
   scrollbar is visible.

   So the chip stands down for that band. 1023px rather than the ~890px where
   it actually starts fitting, because 1024 is the width the dashboard shell
   already uses to turn its sidebar into a drawer — reusing a breakpoint the
   layout has rather than inventing a third, and buying comfortable slack (at
   1024 the row clears the container by 24px with 117px spare) instead of the
   17px it has at 900.

   The chip is not lost in that band: below 1024 the sidebar is a drawer with a
   Wallet entry in it, and below 768 the chip itself reappears inside the mobile
   menu panel — which is why this query has a LOWER bound. Without min-width the
   rule would also hide it there, where it was measured to fit perfectly. */
@media (min-width: 769px) and (max-width: 1023px) {
    .nav-wallet { display: none; }
}

/* ── Navbar: account dropdown ───────────────────────────────────────────── */

.nav-account { position: relative; }

.nav-account-trigger {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 6px 10px 6px 6px;
    background: transparent;
    border: 1px solid rgba(255, 255, 255, .08);
    border-radius: 12px;
    color: #C9C5C5;
    font: inherit;
    font-size: 14px;
    font-weight: 500;
    cursor: pointer;
    transition: border-color .2s ease, color .2s ease, background-color .2s ease;
}
.nav-account-trigger:hover {
    color: #fff;
    border-color: rgba(234, 156, 34, .45);
    background: rgba(255, 255, 255, .03);
}
.nav-account-trigger i { font-size: 16px; transition: transform .2s ease; }
.nav-account-trigger i.is-flipped { transform: rotate(180deg); }

.nav-avatar {
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: #EA9C22;
    color: #0F0E0E;
    font-size: 12px;
    font-weight: 800;
}

.nav-account-name {
    max-width: 120px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── The band where the bar's width must not depend on who is signed in ─────
   THE WALLET CHIP WAS NOT THE ONLY VARIABLE IN THAT ROW. The block above
   stands the chip down between 769px and 1023px because the bar measured 7px
   over at 780px before it existed — but that figure, and the "24px of slack at
   1024" beside it, were both read off the operator's own account, and "Admin"
   is five characters. The username is rendered at its natural width up to the
   120px cap directly above, so the LENGTH OF A CUSTOMER'S NAME is a term in
   whether the navigation fits.

   Measured on /dashboard as the platform's own test member, whose name is
   twelve characters: the bar cleared the container by 23px at a 900px viewport
   and by 16px at 1024 — where the chip has just come back — and at the 120px
   cap, which any name of about eighteen characters reaches, by 68px and 61px.
   So this is not an edge case waiting to happen; it is the ordinary state for
   most of the customers on the platform, on every page of the panel.

   AND IT IS INVISIBLE FROM THE PAGE. nav.navbar is position:fixed, so its
   overflow never extends the document's scrollable area, and body is
   overflow-x:hidden besides — documentElement measures clean while the right
   edge of the account button, the control that reaches Profile, Security and
   Sign out, sits cut off past the screen edge. That is the same failure mode
   the chip's own comment above describes, one step further along.

   THE WORD IS WHAT LEAVES, NOT THE CONTROL. The avatar keeps the initial, the
   chevron keeps the affordance, the button keeps its box — and the dropdown it
   opens prints the full username and the email address as its first line, so
   nothing is actually lost. It is the move .nav-cart-label and
   .dash-bill-go-label already make on this platform when a row runs out of
   width, and it is the only one that makes the bar a FIXED width again rather
   than a width that happens to fit the person who measured it.

   1099px, measured rather than rounded to a breakpoint we already own: with
   the name gone the row clears the container by 24px from 769px up, and with a
   120px name it first clears at a 1100px viewport (15px spare) and is 61px
   over at 1024. Below 769 theme.css has already folded the whole row into the
   mobile panel, where the name is uncapped on purpose and there is a column to
   put it in — hence the lower bound, without which this would strip the name
   out of the one place it has room.

   WHAT THIS DOES NOT FIX, so nobody measures it and thinks the rule failed:
   between 769px and roughly 830px the bar is over the container by ~70px with
   no name at all. That is the pre-existing condition the chip's comment
   recorded at 780px and it is a property of the desktop row itself — five nav
   links at a 32px gap, the cart, the Dashboard button and the account cluster
   simply do not fit a 754px content box. Closing it means collapsing to the
   burger higher than 768px, which is theme.css's breakpoint and enhancements'
   whole mobile-panel block, and belongs to whoever owns that bar. */
@media (min-width: 769px) and (max-width: 1099px) {
    nav.navbar .nav-account-name { display: none; }
}

.nav-dropdown {
    position: absolute;
    right: 0;
    top: calc(100% + 10px);
    min-width: 232px;
    padding: 6px 0;
    background: #141313;
    border: 1px solid rgba(255, 255, 255, .08);
    border-radius: 14px;
    box-shadow: 0 24px 48px rgba(0, 0, 0, .55);
    /* THIS NUMBER ONLY RANKS THIS PANEL AGAINST ITS SIBLINGS INSIDE THE BAR.
       nav.navbar is fixed AND has a z-index, so it is a stacking context and
       nothing inside it can outrank anything outside it — against the rest of
       the document this menu sits at whatever level the BAR has, not at 1200.
       Reading it as "1200 clears the page" is what left the account menu under
       the flash toast. The level that actually governs it is nav.navbar's, in
       the STACKING ORDER block below. */
    z-index: 1200;
    overflow: hidden;
}

.nav-dropdown-head {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 12px 16px;
    border-bottom: 1px solid rgba(255, 255, 255, .07);
}
.nav-dropdown-head strong { font-size: 14px; color: #fff; }
.nav-dropdown-head span {
    font-size: 12px;
    color: #8A8585;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* min-height 44, not the 39px the padding alone produced. Measured at 430, 375
   and 360: every row in this menu — Dashboard, Subscriptions, Orders, Security,
   Sign out — came out 39px tall, a pixel under the 40px floor and five under
   the 44px target, and on a phone this menu IS the account menu because the
   whole thing stacks into the mobile panel. The height is added as a floor
   rather than as more padding so the text keeps sitting where it does on
   desktop, where the rows are already comfortable. */
.nav-dropdown a,
.nav-dropdown-signout {
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    min-height: 44px;
    padding: 10px 16px;
    font-size: 14px;
    color: #C9C5C5;
    text-decoration: none;
    background: transparent;
    border: 0;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
    transition: background-color .15s ease, color .15s ease;
}
.nav-dropdown a:hover,
.nav-dropdown-signout:hover { background: rgba(255, 255, 255, .05); color: #fff; }

.nav-dropdown-accent { color: #EA9C22 !important; font-weight: 600; }
.nav-dropdown form { border-top: 1px solid rgba(255, 255, 255, .07); }

/* ============================================================================
   STACKING ORDER — the one place the site's fixed layers are ranked
   ----------------------------------------------------------------------------
   WHAT WAS WRONG, AND WHY IT WAS INVISIBLE IN THE MARKUP

   nav.navbar is `position: fixed` with `z-index: 1000` (theme.css), and those
   two together make it a STACKING CONTEXT. Everything inside it is therefore
   ranked against its siblings only, and the whole bar is then ranked against
   the page as a single unit at 1000. .nav-dropdown declares `z-index: 1200`
   just above, which reads like it clears the page — and it does not clear
   anything. It cannot: a child cannot escape its parent's context, so the
   account menu's real level against the rest of the document is 1000, whatever
   number is written on it.

   .flash-toast sat at 1500, a sibling of the bar rather than a child of it, so
   it outranked the account menu by 500. MEASURED by hit-testing the centre of
   the open dropdown at 1440 and 1024 with a toast on screen: the element
   returned by elementFromPoint was div.flash-toast, not the menu. The toast
   covers the dropdown's head — the "signed in as" name and email — which is
   exactly the moment it is most needed, because a flash message and somebody
   opening the account menu are the SAME event: you sign in, "Signed in
   successfully" appears, and you go straight to the menu.

   WHY THE TOAST IS NOT SIMPLY LOWERED
   It has to stay above product.css's .breadcrumb, which is `position: fixed`,
   `top: 80px`, `z-index: 999` and lands under the toast on every product page.
   So the toast must be over 999 and the account menu must be over the toast —
   and the account menu IS the navbar. The navbar therefore has to move up, and
   everything that legitimately covers the navbar has to move with it.

   THE LADDER, low to high. Anything fixed on this site belongs on it.
     999   .breadcrumb            product.css — fixed under the bar
     1500  .flash-toast           over the page and the breadcrumb, under the bar
     1600  nav.navbar             and with it .nav-dropdown and the mobile panel
     1650  .dash-backdrop         dims the shell, so it is over the bar
     1700  .dash-sidebar          the drawer covers the bar on purpose; see the
                                  note in dashboard.css beside its own z-index
     1790  .lock-bar              platform state, over the drawer
     1800  .imp-bar               whose own note requires it to survive a modal
     2000  .auth-overlay          a real modal, over all site chrome
     10000 .video-modal           product.css

   WHY THE OVERRIDES LIVE HERE RATHER THAN IN EACH FILE
   Three of these values are declared in theme.css and dashboard.css. A ladder
   split across three files is a ladder nobody can read, and the last time these
   numbers were chosen independently is how the dropdown ended up under the
   toast. enhancements.css is the last stylesheet in the cascade, so one block
   here is both the statement of the order and the thing that enforces it.
   CHANGE A LEVEL HERE, NOT AT ITS ORIGINAL SITE, or the two will disagree.
   ========================================================================= */

nav.navbar { z-index: 1600; }

@media (max-width: 1024px) {
    /* The drawer and its backdrop are the two things that are meant to cover
       the bar — dashboard.css argues that case beside its own declarations.
       They move up with it so that stays true. Scoped to the same breakpoint
       dashboard.css uses, because above it .dash-sidebar is `position: sticky`
       and 100px down the page, where it neither needs a level nor should have
       one. */
    .dash-backdrop { z-index: 1650; }
    .dash-sidebar  { z-index: 1700; }
}

/* ── Flash toast ────────────────────────────────────────────────────────── */

/* Fixed, never in flow. The previous implementation was an in-flow div above
   the hero with `margin: 100px 0 -60px` hand-compensating for the fixed
   navbar, so any page that flashed a message rendered with different geometry
   from the same page without one — the symptom being "logging in moves the
   navbar". Position: fixed removes it from layout entirely, so no message can
   shift anything, on any page, at any breakpoint. */
.flash-toast {
    position: fixed;
    /* CLEARS THE TALLEST BAR ON THE SITE, WHICH IS NOT THE ONE THIS NUMBER WAS
       WRITTEN FOR. The old value said "clears the 68px navbar"; the bar has not
       been 68px for some time. Measured in a browser at six widths: the
       marketing bar is 73–74px everywhere, and the DASHBOARD bar is 75px above
       1024 and 83px at 768 and below. So the toast at 84px was already sitting
       5px inside the dashboard bar on a phone, and got away with it only
       because it used to paint on top of the bar.

       It no longer does — see the stacking block above, where the bar moved
       over the toast so the account menu could clear it — so the overlap that
       did not show before would now clip the toast's top corners behind the
       bar. 93px is the tallest measured bar plus a 10px gap, and it is one
       number for every width and both layouts rather than a value per
       breakpoint that drifts the next time a bar changes height. */
    top: 93px;
    right: 20px;
    z-index: 1500;

    display: flex;
    align-items: center;
    gap: 11px;

    max-width: min(420px, calc(100vw - 40px));
    padding: 13px 16px;
    border-radius: 13px;

    font-size: 14px;
    font-weight: 500;
    line-height: 1.45;

    background: #141313;
    box-shadow: 0 18px 40px rgba(0, 0, 0, .5);

    animation: flashToastIn .28s cubic-bezier(.16, 1, .3, 1);
}

@keyframes flashToastIn {
    from { opacity: 0; transform: translateX(16px); }
    to   { opacity: 1; transform: none; }
}

.flash-toast > i { font-size: 18px; flex-shrink: 0; }

.flash-toast > span { flex: 1; min-width: 0; }

/* 44x44, AND THE TOAST KEEPS ITS SHAPE.
   This is the only control on the toast, it is the only way to get a message
   off the screen before its six-second fuse burns down, and it measured 18x18
   at every width from 1440 to 360 — the smallest target anywhere in the site's
   chrome, on the one element that is always over the page rather than in it.

   THE NEGATIVE MARGIN IS THE WHOLE POINT, and it is on both axes. A bare 44px
   button would take the flex line from 28px to 44px and the toast from 54px to
   70px, and it would take the 26px it gains out of the flexible text span and
   drag the glyph 13px left with it — a fatter, rearranged bar of chrome on
   every sign-in, which is the opposite of what this pass is for. -8px top and
   bottom and -13px each side puts the button's MARGIN box back at 18px wide,
   which is exactly the space the old glyph occupied, so the toast's width is
   untouched and the glyph stays on the pixel it was on. The hit area grows
   outwards over the toast's own padding on the right and over the 11px gap on
   the left, both of which are dead space, and stays entirely inside the toast.

   MEASURED, same message, same 1440 viewport, before and after: toast 281x54
   with an 18x18 target, then 282x56 with a 44x44 one, glyph unmoved at
   x 1370-1388. One pixel of that is rounding on the side margins and two are
   the line growing to fit a 26px margin box against a 26.1px icon; that is the
   entire cost. elementFromPoint returns this button at all four corners and the
   centre of the 44x44 square, at 1440 and at 360, and the toast still clears
   the tallest bar on the site with its own top:93px. */
.flash-toast > button {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    margin: -8px -13px;
    padding: 0;
    background: none;
    border: 0;
    color: inherit;
    opacity: .55;
    font-size: 17px;
    line-height: 1;
    cursor: pointer;
    transition: opacity .15s ease;
}
.flash-toast > button:hover { opacity: 1; }

.flash-success {
    color: #22C55E;
    background: #101a13;
    border: 1px solid rgba(34, 197, 94, .35);
}
.flash-error {
    color: #EF4444;
    background: #1a1011;
    border: 1px solid rgba(239, 68, 68, .35);
}

/* Full width across the top on phones — a 420px card pinned right is most of
   a small screen anyway, and a partially off-screen toast reads as broken. */
@media (max-width: 520px) {
    .flash-toast {
        /* No `top` here any more. It used to drop the toast to 78px on a phone,
           which is 5px INSIDE the 83px dashboard bar — the collision described
           on the base rule, and the one width where it mattered most. The base
           93px already clears every bar at every width, so this block is left
           holding only the thing it is actually for: going full width. */
        left: 14px;
        right: 14px;
        max-width: none;
    }
}

/* ── Mobile ─────────────────────────────────────────────────────────────────
   768px, matching theme.css:140 and product.css:1770 exactly. This block used
   to open at 968px, which put a 200px band (769–968px) where the mobile
   account rules below were live against a navbar still in its full desktop row
   — a signed-in user at 900px got a full-width stretched account button and a
   dropdown rendered inline, growing the fixed bar from 72px to 264px tall.
   One breakpoint, used in all three places.
   ========================================================================= */

@media (max-width: 768px) {

    /* THE MOBILE PANEL. The original drove this from a `mobile-active` class
       that an inline onclick put on nav.navbar, and theme.css:151-162 hung the
       entire panel off it — display, fixed positioning, dark background,
       column direction, padding. Alpine sets `.is-open` on the <ul> instead,
       and only `display: flex` was ever restored, so theme.css:145's
       `nav.navbar .navbar-menu { display: none }` was defeated but NOTHING
       positioned the panel.

       Result measured at 375px: on /store the menu opened as a transparent,
       static, horizontal text row starting at x=227 and running 96px past the
       right edge of the screen (invisibly clipped by theme.css:37's
       body{overflow-x:hidden}). Product pages looked fine only by accident —
       product.css:1770-1783 declares .navbar-menu positioning unconditionally
       inside its own 768px block, and no other page loads product.css.

       So the whole panel is re-declared here rather than leaning on that leak.
       position:absolute + top:100% rather than the original's fixed 60px
       because nav.navbar is itself position:fixed and therefore the containing
       block for absolute descendants — that anchors the panel to the real
       bottom edge of the bar instead of a hardcoded height that is now wrong
       (the bar measures 74px at 375px, not 60px).

       Specificity 0,3,1 beats theme.css:145 (0,2,1) and product.css:1771
       (0,1,0) with no !important. */
    nav.navbar .navbar-menu.is-open {
        display: flex;
        position: absolute;
        top: 100%;
        left: 0;
        right: 0;
        flex-direction: column;
        align-items: stretch;
        gap: 0;
        padding: 20px;
        background: rgba(15, 14, 14, .98);
        backdrop-filter: blur(20px);
        border-bottom: 1px solid rgba(255, 255, 255, .1);

        /* A long menu on a short phone must scroll rather than run off the
           bottom of the screen with no way to reach Sign out.

           dvh, not vh, and on a phone that is the difference between the rule
           working and the rule being decorative. `100vh` on a mobile browser is
           the height with the URL bar HIDDEN — the largest the viewport ever
           gets — so a menu capped at `100vh - 100%` while the bar is on screen
           is capped to MORE room than it has, its last rows sit behind the
           browser chrome, and the internal scroll this block exists for never
           engages because as far as the box is concerned it fits. Measured on a
           667x375 landscape phone the menu is already 453px of content in a 294px
           well, so the rows behind the bar are the account block and Sign out.

           The vh line is kept underneath as the fallback for a browser without
           dvh (pre-Safari 15.4 / Chrome 108); it is declared first so dvh wins
           wherever it is understood. */
        max-height: calc(100vh - 100%);
        max-height: calc(100dvh - 100%);
        overflow-y: auto;
        overscroll-behavior: contain;
    }

    /* THE NAV LINKS WERE 36px AND 30px WIDE IN A 335px ROW.

       .navbar-menu is a column here and its <li>s stretch, so each row LOOKS
       like a full-width target. The <a> inside does not: an anchor is inline,
       so it is only as wide as the word in it. Measured at 375: "Store" was a
       36x44 hit area and "FAQ" was 30x44, both floating at the left end of a
       row the whole width of the phone. Everything either side of the text —
       most of the row — did nothing when tapped, which reads as an unresponsive
       menu rather than as a small button.

       The anchor becomes the row. 44px is the touch target the rest of this
       pass uses, and the text stays left-aligned where it already was. */
    nav.navbar .navbar-menu.is-open > li { width: 100%; }

    nav.navbar .navbar-menu.is-open > li > a {
        display: flex;
        align-items: center;
        width: 100%;
        min-height: 44px;
    }

    /* Register / Login — and, signed in, the whole account dropdown including
       Sign out — were unreachable on EVERY page at phone widths.
       theme.css:146 hides .navbar-actions below 768px and the original un-hid
       it from the same dead `mobile-active` selector. .navbar-actions is
       nested inside .navbar-menu (navbar.blade.php), so it is un-hidden as a
       child of the open panel rather than re-implementing the original's
       second fixed bar. Specificity 0,3,2 beats theme.css:146 (0,2,1). */
    nav.navbar .navbar-menu.is-open .navbar-actions {
        display: flex;
        flex-direction: column;
        width: 100%;
        gap: 10px;
        margin-top: 14px;
    }

    /* The mobile panel stacks .navbar-actions into a full-width column; an
       inline-flex anchor would otherwise sit at its content width, left
       aligned, out of line with the account button under it. */
    .nav-dashboard-btn { width: 100%; justify-content: center; }

    /* The wallet chip stacks with everything else in the panel. Without this
       it sits at its content width, left aligned, out of line with the
       full-width button above it and the account button below it — the exact
       fault .nav-dashboard-btn's rule above was written to fix. */
    .nav-wallet { width: 100%; justify-content: center; min-height: 44px; }

    /* THE CART BECOMES A LABELLED ROW. Everything in the open panel is a
       full-width control, so the chip stretches with the rest of them — and a
       stretched 39px square containing one glyph in the middle of a 335px row
       is a mystery button. The word appears, and the count comes back INTO the
       flow beside it: pinned to the corner it would float somewhere off to the
       right of the label with nothing under it, which is what the absolute
       positioning is for on the desktop bar and against it here. */
    /* min-height 44 for the same reason the wallet chip above has one: measured
       at 375 the stretched chip came out 35px tall and the wallet 34px, both
       under the 40px floor, in the one panel where every control is reached
       with a thumb. */
    nav.navbar .navbar-actions .nav-cart {
        width: 100%;
        justify-content: center;
        min-height: 44px;
    }

    .nav-cart-label {
        position: static;
        width: auto;
        height: auto;
        margin: 0;
        overflow: visible;
        clip: auto;
        clip-path: none;
    }

    .nav-cart-count {
        position: static;
        box-shadow: none;
    }

    .nav-account { width: 100%; }
    .nav-account-trigger { width: 100%; justify-content: center; }
    .nav-dropdown {
        position: static;
        width: 100%;
        margin-top: 8px;
        box-shadow: none;
    }
    .nav-account-name { max-width: none; }
}

/* ============================================================================
   Auth overlay
   ----------------------------------------------------------------------------
   Replaces the standalone /auth/login and /auth/register pages. Same visual
   language as the rest of the theme — near-black surfaces, amber accent,
   Outfit — so it reads as part of the site rather than a bolted-on widget.
   ========================================================================= */

.auth-overlay {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: grid;
    place-items: center;
    padding: 20px;
    overflow-y: auto;
}

.auth-overlay-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(5, 5, 5, .78);
    backdrop-filter: blur(6px);
}

.auth-modal {
    position: relative;
    width: 100%;
    max-width: 440px;
    padding: 32px;
    background: #121111;
    border: 1px solid rgba(255, 255, 255, .09);
    border-radius: 20px;
    box-shadow: 0 32px 80px rgba(0, 0, 0, .7);
    animation: authModalIn .22s ease;
}

@keyframes authModalIn {
    from { opacity: 0; transform: translateY(14px) scale(.985); }
    to   { opacity: 1; transform: none; }
}

/* 44px, not 34px. This is the only way out of the overlay for anybody who does
   not know Escape closes it, and at 375 it measured 34x34 — the smallest
   control in the whole modal and the one that has to be hit first. The box grew
   rather than the glyph: font-size is unchanged, so it reads exactly as before
   and simply has the target around it that it always implied. */
.auth-modal-close {
    position: absolute;
    top: 14px;
    right: 14px;
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    background: transparent;
    border: 1px solid rgba(255, 255, 255, .08);
    border-radius: 10px;
    color: #8A8585;
    font-size: 19px;
    cursor: pointer;
    transition: color .2s ease, border-color .2s ease;
}
.auth-modal-close:hover { color: #fff; border-color: rgba(255, 255, 255, .2); }

.auth-modal-head { text-align: center; margin-bottom: 22px; }
.auth-modal-logo { height: 34px; width: auto; margin-bottom: 16px; }
.auth-modal-head h2 { font-size: 21px; font-weight: 700; color: #fff; margin-bottom: 6px; }
.auth-modal-head p  { font-size: 13.5px; color: #8A8585; }

.auth-tabs {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 4px;
    padding: 4px;
    margin-bottom: 22px;
    background: rgba(255, 255, 255, .04);
    border-radius: 12px;
}
/* The tab strip is the first choice in the overlay — sign in or register — and
   at 375 each tab was 36px tall. A grid track cannot be short-changed on one
   axis and comfortable on the other, so the floor goes on the control itself
   and the padding stays where it was. */
.auth-tab {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding: 9px;
    background: transparent;
    border: 0;
    border-radius: 9px;
    color: #8A8585;
    font: inherit;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: background-color .2s ease, color .2s ease;
}
.auth-tab.is-active { background: #EA9C22; color: #0F0E0E; }
.auth-tab:not(.is-active):hover { color: #fff; }

.auth-form { display: flex; flex-direction: column; gap: 15px; }

.auth-field { display: flex; flex-direction: column; gap: 6px; }
/* .auth-field-name is the same text the wrapping <label><span> used to be. The
   field stopped being a wrapping label when the password boxes grew a reveal
   button: a <button> inside a <label> is a click the label also claims, so the
   caption became a real <label for> and the box it names got an id. The <span>
   selector stays because .auth-checkbox still wraps its own text. */
.auth-field > span,
.auth-field-name { font-size: 13px; font-weight: 600; color: #C9C5C5; }
/* The 16px floor reaches the FRONT DOOR too, and this is the one form where it
   matters most: every field in the sign-in and register overlay measured
   14.5px, so on an iPhone the page zoomed the moment a customer tapped the
   email box, stayed zoomed while they typed the password, and was still zoomed
   when the dashboard loaded behind it. dashboard.css says in as many words
   that .dash-input exists to be the same control as this one; a floor on one
   and not the other is two controls again. See --field-font-floor at the top
   of this file. */
.auth-field input {
    width: 100%;
    padding: 11px 13px;
    background: rgba(255, 255, 255, .04);
    border: 1px solid rgba(255, 255, 255, .09);
    border-radius: 11px;
    color: #fff;
    font: inherit;
    font-size: max(var(--field-font-floor, 0px), 14.5px);
    transition: border-color .2s ease, background-color .2s ease;
}
.auth-field input:focus {
    border-color: #EA9C22;
    background: rgba(255, 255, 255, .06);
    outline: none;
}
.auth-field input::placeholder { color: #6A6666; }
.auth-field small { font-size: 11.5px; color: #6A6666; }

/* ── Reveal ────────────────────────────────────────────────────────────────
   A password box you cannot read back is a box you retype, and on a phone
   keyboard with a 12-character minimum that is the commonest way a correct
   password gets entered wrong twice. The control is a real 44x44 target laid
   over the right-hand end of the field, and the field reserves the room for it
   so a long password never runs underneath. */
.auth-input-wrap { position: relative; display: flex; }
.auth-input-wrap input { padding-right: 50px; }
.auth-reveal {
    position: absolute;
    top: 50%;
    right: 0;
    transform: translateY(-50%);
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    background: none;
    border: 0;
    padding: 0;
    color: #8A8585;
    font-size: 17px;
    line-height: 1;
    cursor: pointer;
    transition: color .2s ease;
}
.auth-reveal:hover { color: #EA9C22; }
.auth-reveal[aria-pressed="true"] { color: #EA9C22; }

/* ── Per-field errors ──────────────────────────────────────────────────────
   The register panel used to print ONE message in a banner at the top no
   matter how many rules the submit broke: a form that failed on username,
   email, password and terms said "The username has already been taken." and
   nothing else, so fixing it cost four submits and every one of them arrived
   back with both password boxes emptied. Each message now sits under the box
   it is about, and the box is marked aria-invalid and points at it.

   BOTH SELECTORS CARRY TWO CLASSES ON PURPOSE. `.auth-field small` fourteen
   lines up is (0,1,1) and out-specifies a bare `.auth-field-error` at (0,1,0),
   so the first version of this rule lost every declaration in it and the
   errors painted in the hint's own muted grey at the hint's own 11.5px — "The
   username has already been taken." drawn as though it were the note
   explaining what a username may contain. Measured rgb(106, 102, 102) at
   11.5px before this line was raised to (0,2,0), rgb(248, 113, 113) at 12px
   after. Same trap as recurring defect (f), one level down: it is not that the
   media query added no specificity, it is that the class did not add enough. */
.auth-field > .auth-field-error,
.auth-terms-block > .auth-field-error {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    color: #F87171;
    font-size: 12px;
    line-height: 1.4;
}
.auth-field > .auth-field-error i,
.auth-terms-block > .auth-field-error i { font-size: 13px; flex-shrink: 0; margin-top: 1px; }
.auth-field input[aria-invalid="true"] { border-color: rgba(239, 68, 68, .6); }

/* The one thing the confirm box can answer without a round trip. Coloured AND
   worded — "is-ok" green next to "is-bad" red is the single most common way a
   status message says nothing at all to a red/green colour-blind reader. */
.auth-field > .auth-match { font-size: 12px; line-height: 1.4; }
.auth-match.is-ok  { color: #4ADE80; }
.auth-match.is-bad { color: #E0A050; }

.auth-field-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }

/* ── The "verify you are human" check ─────────────────────────────────────
   The slot <x-turnstile> renders into, and the geometry that keeps Turnstile's
   own box inside this modal. See config/turnstile.php and the component.

   COLLAPSED WHILE EMPTY, which is the common case by a distance: with no keys
   configured nothing renders at all, and even once keys exist the sign-in
   panel only carries a widget for an address that has recently failed. The
   slot reserves the POSITION in the markup without reserving any space on the
   page.

   MIN-HEIGHT ON THE FILLED SLOT, because Turnstile paints nothing until its
   script has loaded from Cloudflare and then inserts a 69px box. Without a
   reservation the submit button jumps 69px down the modal a second or so
   after the form appears — under the finger of somebody who was already
   reaching for it. 69px is the measured height of the widget at both 1280 and
   360, and it is stated for the slot rather than the widget so the space is
   held from first paint, before the widget exists to have a height. */
.auth-turnstile:empty { display: none; }
.auth-turnstile:not(:empty) { min-height: 69px; }

/* THE WIDGET DOES NOT FIT IN THIS MODAL ON A PHONE, AND IT PUSHES.

   Turnstile's `flexible` size fills its container but will not lay out below a
   300px floor of Cloudflare's own — measured 300.0 at both a 360 and a 390
   viewport, in slots 263 and 293 wide. A 300px box in a 263px slot is not
   merely clipped: the modal is a grid item, so its automatic minimum size is
   its min-content width and the widget makes the MODAL 342px. Measured at
   360x800 with a 300x69 stand-in of the real widget — modal 342 wide with its
   right edge at 362 against a 345px overlay, the whole thing 2px wider than
   the screen and the overlay scrolling 37px sideways to reach the border and
   the close button. That is the one form a new customer has to complete.

   Padding cannot buy it back: fitting 300px inside a 345px overlay leaves
   21.5px per side for the overlay's padding AND the modal's together, against
   20 and 20 today, and a dialog with 11px of padding is not a dialog.

   ZOOM, AND NOT transform: scale(). zoom participates in layout, so the box
   really is the smaller one and nothing has to be dragged back with a negative
   margin — and, more usefully here, the widget is laid out in its OWN
   coordinate space before being scaled, so a 263px slot at .87 offers it
   302.3px to fill. It therefore clears Cloudflare's 300px floor honestly
   rather than being squashed under it, and comes out at exactly the slot
   width: measured 263x60 at 360, 293x60 at 390, 333x69 at 430, 374x69 at 1280,
   with the modal back to 305 / 335 / 375 / 440 and no sideways scroll on the
   overlay or the document at any of them.

   THE RANGE THIS HOLDS OVER is down to a 358px viewport, which is where
   .87 stops offering the widget its 300px (300 x .87 + 97px of overlay
   padding, modal padding, border and scrollbar). Below that it clips instead
   of pushing, because overflow: hidden sets the slot's min-content width to
   zero — the guarantee rather than the fix, and the reason a future change to
   Cloudflare's floor, which is their number and not ours, can only ever cost
   a clipped widget and never a modal wider than the phone it is on.

   The 400px rung is where a slot stops reaching 300px once a 15px scrollbar is
   allowed for; a modal is viewport - 40, a slot is modal - 42.

   WHAT IT COSTS: the checkbox inside the widget is drawn at 87%, so it is
   under the 44px target floor the rest of this modal was measured against.
   That is Cloudflare's control and not one this stylesheet can grow, and every
   alternative measured worse — a modal 2px wider than the screen, a widget
   clipped by 37px, or 11px of dialog padding. */
@media (max-width: 400px) {
    .auth-turnstile .cf-turnstile { zoom: .87; }
    .auth-turnstile:not(:empty) { min-height: 60px; }
}
.auth-turnstile { min-width: 0; overflow: hidden; }

/* The LABEL is the target, not the 15px box inside it — clicking anywhere in
   here toggles the checkbox, which is why the input's own size is not the
   number that matters. What did matter is that the label was only as tall as
   one line of 13px text, so the comfortable target it implies did not exist.
   The floor gives it one without moving the tick or the words. */
.auth-checkbox {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    min-height: 44px;
    font-size: 13px;
    color: #C9C5C5;
    cursor: pointer;
}

/* .auth-terms is two or three lines of prose with a tick beside it and is
   already well past the floor; centring is only right for the one-line case. */
.auth-checkbox:not(.auth-terms) { align-items: center; }
.auth-checkbox input {
    width: 15px;
    height: 15px;
    margin-top: 1px;
    flex-shrink: 0;
    accent-color: #EA9C22;
    cursor: pointer;
}

/* ── The terms tick ────────────────────────────────────────────────────────
   This one is a legal record and not a preference, so it is the one control on
   the register form that is drawn as an object rather than as a line of grey
   text between two fields. It is boxed, it sits on its own surface, its tick is
   18px rather than 15px, and when the server rejects it the box goes red —
   "accepted" is the only rule on this form whose failure is invisible if the
   message is the only thing that changes, because there is no text in the
   control for a red border to sit around. */
.auth-terms-block {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 12px 14px;
    background: rgba(255, 255, 255, .03);
    border: 1px solid rgba(255, 255, 255, .09);
    border-radius: 11px;
}
.auth-terms-block[data-invalid] {
    background: rgba(239, 68, 68, .07);
    border-color: rgba(239, 68, 68, .45);
}
.auth-terms-block input[type="checkbox"] { width: 18px; height: 18px; margin-top: 0; }

/* 1.9, not 1.5: the two links inside this sentence are padded targets, and at
   1.5 a link on one line overlapped the line above it. */
.auth-terms { line-height: 1.9; }
.auth-terms a {
    display: inline-block;
    padding: 4px 3px;
    margin: -4px -3px;
    color: #EA9C22;
    text-decoration: underline;
    text-underline-offset: 2px;
}
.auth-terms a i { font-size: 12px; margin-left: 3px; vertical-align: -1px; }
.auth-terms a:hover { color: #FBB131; }

/* A <button> DRAWN AS A LINK STILL ARRIVES WITH THE BUTTON'S CHROME.
   theme.css's only reset for form controls (`button, input, select, textarea`
   at the top of the file) sets four font properties and nothing else, and the
   universal selector above it sets margin, padding and box-sizing — so a
   button's background-color and border are whatever the user agent says, and
   what Chrome says is ButtonFace and 2px outset.

   .auth-link was written for the <a> on the old standalone /auth/login page.
   The overlay renders it on a <button type="button">, because "Forgot
   password?" now swaps a panel rather than navigating, and nothing in either
   copy of the rule (theme.css:4503 is the other) turns the chrome off. It
   measured background-color rgb(240, 240, 240) with a 2px outset border
   against the modal's rgb(18, 17, 17): a white box round the one control on
   the sign-in panel, which is what the owner reported.

   STATED ONCE FOR BOTH text buttons in the overlay rather than per class.
   .auth-switch button carried its own private copy of this reset, which is
   exactly why "Create one" looked right and "Forgot password?" did not — the
   next text button added here would have had the same 50/50 chance. */
.auth-link,
.auth-switch button {
    appearance: none;
    -webkit-appearance: none;
    background: none;
    border: 0;
    padding: 0;
    color: #EA9C22;
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
}

/* "Forgot password?" is the ONLY way to the reset panel — there is no second
   route to it the way "Create one" has the Register tab — so it is the one
   inline text button in this modal that has to carry a real target. It measured
   110x20 at 375. inline-flex with a floor rather than padding, so it stays on
   the same line as the Remember-me checkbox and the row simply becomes a
   normal 44px form row. */
.auth-link {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    text-decoration: none;
    white-space: nowrap;
}
.auth-link:hover { text-decoration: underline; }

.auth-submit {
    justify-content: center;
    width: 100%;
    margin-top: 4px;
}

.auth-error {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    padding: 11px 13px;
    background: rgba(239, 68, 68, .10);
    border: 1px solid rgba(239, 68, 68, .35);
    border-radius: 11px;
    color: #EF4444;
    font-size: 13px;
    line-height: 1.45;
}
.auth-error i { font-size: 16px; flex-shrink: 0; margin-top: 1px; }

.auth-switch { text-align: center; font-size: 13px; color: #8A8585; }
.auth-switch button { text-decoration: underline; }

/* The lede on the forgot-password panel is a paragraph, not a switcher — it
   was borrowing .auth-switch for its type and then undoing the centring with
   an inline style. */
.auth-lede { text-align: left; margin-bottom: 4px; }

/* Navbar Register/Login are <button> now, not <a> — the theme styles the
   anchors, so normalise the button box model to match exactly.
   NOTE: font-FAMILY only. This used to be the `font` shorthand, which also
   reset font-size and font-weight — and because this selector (0,3,2) outranks
   theme.css:499's .btn-primary (0,1,0) it silently rendered the navbar buttons
   at the inherited 16px/400 instead of the theme's 14px/500, so they did not
   match the same buttons anywhere else on the site. Only the UA's refusal to
   inherit font-family into form controls needs correcting. */
nav.navbar .navbar-actions button.btn-primary,
nav.navbar .navbar-actions button.btn-secondary {
    font-family: inherit;
    cursor: pointer;
}

.sr-only {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

@media (max-width: 520px) {
    .auth-modal { padding: 26px 20px; }
    .auth-field-row { flex-direction: column; align-items: flex-start; gap: 8px; }
}

/* ── Standalone auth panels (verify email, reset password) ──────────────── */
/* Same visual language as the auth overlay, but as a page — these are reached
   from an email link, so they cannot be a modal. */

/* The section these panels live in. It used to be .store-content >
   .store-container, both of which are store.css classes on pages that only
   load info.css — so the wrapper had no max-width, no padding and no bottom
   spacing at all and the panel sat flush against the footer, full-bleed.
   The markup now uses .auth-page > .container (theme.css, always loaded), and
   this is its only rule.

   position/z-index: see the "decorative layers" section below — .footer-glow
   is 100vh tall and rises above the footer, and an unpositioned panel paints
   underneath it. min-height keeps the footer off the middle of the screen on
   what is a very short page. */
.auth-page {
    position: relative;
    z-index: 1;
    padding: 10px 0 120px;
    min-height: calc(100vh - 480px);

    /* Opaque, exactly as theme.css:4835 does for the legal pages. The panel
       sits on a plain field rather than having the footer's glow washing
       across it — the specific collision reported on /email/verify, where the
       "Check your inbox" card was 100% covered by .footer-glow. */
    background: #0f0e0e;
}

.auth-panel {
    max-width: 520px;
    margin: 0 auto;
    padding: 36px;
    background: #121111;
    border: 1px solid rgba(255, 255, 255, .09);
    border-radius: 20px;
    text-align: center;
}
.auth-panel h2 { font-size: 22px; font-weight: 700; color: #fff; margin-bottom: 10px; }
.auth-panel p  { font-size: 14.5px; color: #C9C5C5; line-height: 1.6; margin-bottom: 14px; }
.auth-panel strong { color: #EA9C22; }
.auth-panel .auth-form { text-align: left; margin: 22px 0; }
.auth-panel form { margin: 20px 0; }

.auth-panel-icon {
    display: grid;
    place-items: center;
    width: 62px;
    height: 62px;
    margin: 0 auto 20px;
    border-radius: 50%;
    background: rgba(234, 156, 34, .12);
    border: 1px solid rgba(234, 156, 34, .3);
    color: #EA9C22;
    font-size: 27px;
}

.auth-panel-muted { font-size: 13px !important; color: #8A8585 !important; }
.auth-panel-muted a { color: #EA9C22; text-decoration: underline; }

.auth-panel input[readonly] { opacity: .65; cursor: not-allowed; }

/* ==========================================================================
   Discord CTA
   ==========================================================================
   No rule needed. The original theme styled .btn-discord for a <button> that
   no script ever wired up — clicking it did nothing on every page that had
   one — and every ported page still renders a <button>, opening the invite
   from an @click handler. Rendering an <a> instead would need both the
   underline reset the theme never had to write AND width: fit-content, since
   .btn-discord (theme.css) sets no width and centres itself with
   margin-inline: auto, which is a no-op on a full-width block box. */

/* ==========================================================================
   Related products: rows that are not a multiple of three
   ==========================================================================
   product.css lays .products-grid out as three fixed 1fr tracks, because the
   original product pages hardcoded exactly three related cards on every page —
   including one for a product that was not in the catalog. This grid is driven
   by the catalog instead, so the row can hold two cards, or one.

   Three fixed tracks with two cards leaves a visibly empty cell on the right.
   These rules keep each card at exactly the width it has at three-up — the
   same track maths, (100% - 2 * 30px gap) / 3 — and centre the short row
   instead of left-aligning it against a hole. Card proportions are untouched;
   only the leftover space moves.

   Scoped to .related-products and bounded by min-width so product.css's own
   1024px (two-up) and 768px (single column) breakpoints still win below them.
   Browsers without :has() fall through to the current three-track behaviour. */

@media (min-width: 1025px) {
    .related-products .products-grid:has(> .related-product-card:last-child:nth-child(2)) {
        grid-template-columns: repeat(2, minmax(0, calc((100% - 60px) / 3)));
        justify-content: center;
    }
    .related-products .products-grid:has(> .related-product-card:only-child) {
        grid-template-columns: minmax(0, calc((100% - 60px) / 3));
        justify-content: center;
    }
}

@media (min-width: 769px) and (max-width: 1024px) {
    .related-products .products-grid:has(> .related-product-card:only-child) {
        grid-template-columns: minmax(0, calc((100% - 25px) / 2));
        justify-content: center;
    }
}

/* The original's related-card blurbs were one line each ("Kernel ESP + Aim
   with Vischeck"). These come from products.description_short, which is
   written for the store card and runs to a full sentence, so without a clamp
   one card in a row grows two lines taller than its neighbours. Two lines
   reserved, two lines shown — the row stays level whatever the catalog says.
   Nothing is truncated in the database, and the full text is still on the
   product's own page. */
.related-products .related-product-card .product-info p {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    min-height: calc(2 * 1.5 * 13px);   /* 2 lines at product.css's 13px/1.5 */
}

/* ============================================================================
   Footer: one component on every page
   ----------------------------------------------------------------------------
   product.css:1650-1768 redeclares the whole footer. It loads after theme.css
   at equal specificity, so on product pages it won every property and the
   footer was a visibly different component: shorter (80/60 instead of
   100/100), a dimmer glow (.5 vs .8), a narrower and differently-margined
   content column, and a gradient-filled brand wordmark instead of plain white.
   The original never noticed because it had no shared footer partial.

   Each selector is qualified with the element (footer.footer, 0,1,1) purely to
   outrank product.css's bare .footer (0,1,0) — no !important needed.
   These values ARE theme.css's; nothing new is invented here.
   ========================================================================= */

/* IMPORTANT, if you edit this block: because these selectors carry the extra
   element (0,1,1 / 0,2,1 / 0,2,2) they outrank theme.css's own rules at EVERY
   width — including the ones inside theme.css's 768/480/360 media queries,
   since a media query adds no specificity. So every property set at the base
   width has to be restated down the ladder, or the desktop value would leak
   onto phones. The ladder below is theme.css:4019-4085 (768),
   theme.css:4235-4297 (480) and theme.css:4311-4414 (360), copied verbatim.
   The reverse is equally true and was the bigger hole: product.css's BASE
   declarations (0,2,0, later file) beat theme.css's MEDIA-QUERY ones (0,2,0,
   earlier file), so on product pages theme's entire responsive footer ladder
   was dead at every breakpoint until each selector was neutralised here. */

footer.footer { padding: 100px 0 100px; }

footer.footer .footer-glow { opacity: .8; }

/* theme.css:1583-1593 declares `margin-bottom: 60px` and then `margin: auto`,
   which resets all four sides — the used value is auto on every side. Copied
   as it computes, not as it reads. */
footer.footer .footer-content {
    max-width: 300px;
    margin: auto;
    padding-bottom: 100px;
}

/* Clearing `background` alone is not enough — product.css:1683-1686 also sets
   -webkit-text-fill-color: transparent, which would leave the wordmark
   invisible once the gradient it was clipping to is gone. */
footer.footer .footer-brand h3 {
    font-size: 28px;
    font-weight: 600;
    line-height: 120%;
    background: none;
    -webkit-background-clip: border-box;
    background-clip: border-box;
    -webkit-text-fill-color: currentColor;
}

/* product.css:1700-1705 centres the link columns with a 50px gap and lets them
   wrap; theme.css:1619-1625 spreads them across the full width. */
footer.footer .footer-links {
    justify-content: space-between;
    align-items: flex-start;
    width: 100%;
    gap: 40px;
    flex-wrap: nowrap;
}

/* theme.css:1604. Kept out of the h3 rule above because that one exists to
   undo product.css's gradient wordmark; this is pure spacing, cut to 18px by
   product.css:1688. */
footer.footer .footer-brand h3 { margin-bottom: 30px; }

/* theme.css:1608-1616 vs product.css:1691-1698, which shrinks the blurb to
   15px/1.5, fades it to 70% white and drops the gap below to 25px. */
footer.footer .footer-brand p {
    font-size: 18px;
    line-height: 24px;
    color: #FFFFFF;
    margin-bottom: 30px;
}

/* theme.css:1630 vs product.css:1710's 10px. */
footer.footer .footer-column { gap: 15px; }

/* theme.css:1634-1639 vs product.css:1713-1719 (17px/600/1.3 + a 6px margin).
   theme declares no margin at all and relies on the `* { margin: 0 }` reset at
   theme.css:9-13, so 0 is the value being restored, not an invention. */
footer.footer .footer-column h4 {
    font-size: 18px;
    font-weight: 500;
    line-height: 24px;
    margin-bottom: 0;
}

/* theme.css:1641-1647 vs product.css:1721-1728's 14px/400/1.4. This was the
   most visible single difference: every footer link 4px smaller on product
   pages. Colour and text-decoration match in both files, so only the metrics
   are restated. */
footer.footer .footer-column a {
    font-size: 18px;
    font-weight: 500;
    line-height: 24px;
}

/* theme.css:1650-1658 vs product.css:1734-1744. The divider is the visible
   part: #cacaca49 is rgba(202,202,202,.286), product's rgba(255,255,255,.08)
   is ~3.5x dimmer. wrap/gap are product-only additions with no theme
   counterpart, so they go back to the flex defaults. */
footer.footer .footer-bottom {
    border-top-color: #cacaca49;
    flex-wrap: nowrap;
    gap: 0;
}

/* theme.css:1660-1665 vs product.css:1746-1751's 13px at 60% white. */
footer.footer .footer-bottom p {
    font-size: 18px;
    line-height: 24px;
    color: #FFFFFF;
}

/* theme.css:1667-1676 vs product.css:1753-1763: icons 6px smaller and
   permanently held at .7. product.css:1765's `a:hover img { opacity: 1 }`
   survives and is now simply a no-op, since the rest state is already 1. */
footer.footer .footer-social { gap: 15px; }

footer.footer .footer-social a img {
    width: 32px;
    opacity: 1;
}

@media (max-width: 768px) {
    footer.footer { padding: 80px 0 60px; }

    footer.footer .footer-content {
        max-width: 100%;
        padding: 0 20px 60px;
        gap: 40px;
    }

    /* theme.css:4034-4037 */
    footer.footer .footer-brand h3 {
        font-size: 24px;
        margin-bottom: 16px;
    }

    /* theme.css:4039-4042 */
    footer.footer .footer-brand p {
        font-size: 16px;
        margin-bottom: 0;
    }

    /* theme.css:4044-4049. flex-direction is the one property theme does NOT
       set here — it leaves the base `row` in force — but product.css:1896-1900
       stacks the columns, and a bare .footer-links in a media query still
       loses to nothing, so `row` has to be stated explicitly to beat it.
       Symptom: at 600px the product footer was 349px tall vs 224px elsewhere. */
    footer.footer .footer-links {
        flex-direction: row;
        justify-content: center;
        align-items: flex-start;
        flex-wrap: wrap;
        gap: 30px;
        text-align: center;
    }

    /* theme.css:4056-4059 */
    footer.footer .footer-column h4 {
        font-size: 16px;
        margin-bottom: 12px;
    }

    /* theme.css:4061-4065 */
    footer.footer .footer-column a {
        font-size: 14px;
        display: block;
        padding: 4px 0;
    }

    /* theme.css:4069 — the only property of theme's 768 .footer-bottom that
       the base rule above would otherwise clobber (gap: 0). */
    footer.footer .footer-bottom { gap: 20px; }

    /* theme.css:4074-4076 */
    footer.footer .footer-bottom p { font-size: 14px; }

    /* theme.css:4083-4085 */
    footer.footer .footer-social a img { width: 28px; }
}

@media (max-width: 480px) {
    footer.footer { padding: 60px 0 40px; }

    footer.footer .footer-content {
        padding: 0 16px 40px;
        gap: 30px;
    }

    /* theme.css:4294-4297 dims the glow on small screens. The base rule at the
       top of this block (.8, 0,2,1) was outranking it on EVERY page, not just
       product ones — same media-queries-add-no-specificity trap. */
    footer.footer .footer-glow { opacity: .6; }

    /* theme.css:4248-4251 */
    footer.footer .footer-brand h3 {
        font-size: 20px;
        margin-bottom: 12px;
    }

    /* theme.css:4253-4256 */
    footer.footer .footer-brand p {
        font-size: 14px;
        line-height: 1.4;
    }

    /* theme.css:4258-4262 — column here is theme's own choice, so product.css
       agrees at this width and this rule is belt-and-braces. */
    footer.footer .footer-links {
        flex-direction: column;
        align-items: center;
        gap: 24px;
    }

    /* theme.css:4269-4272 */
    footer.footer .footer-column h4 {
        font-size: 15px;
        margin-bottom: 10px;
    }

    /* theme.css:4274-4277 */
    footer.footer .footer-column a {
        font-size: 13px;
        padding: 3px 0;
    }

    /* theme.css:4279-4282 */
    footer.footer .footer-bottom {
        padding-top: 20px;
        gap: 16px;
    }

    /* theme.css:4284-4287 */
    footer.footer .footer-bottom p {
        font-size: 12px;
        line-height: 1.3;
    }

    /* theme.css:4289-4291 */
    footer.footer .footer-social a img { width: 24px; }
}

@media (max-width: 360px) {
    footer.footer { padding: 50px 0 30px; }
    footer.footer .footer-content { padding: 0 12px 30px; }
    footer.footer .footer-brand h3 { font-size: 18px; }

    /* theme.css:4323-4325 */
    footer.footer .footer-brand p { font-size: 12px; }

    /* theme.css:4380-4382 */
    footer.footer .footer-column h4 { font-size: 14px; }

    /* theme.css:4384-4391. This is the accessibility rung of the ladder: the
       32px minimum touch target for footer links on the smallest phones. It
       was dead on product pages, which held a bare 14px link with no padding
       and no min-height. display:flex deliberately supersedes the 768 rung's
       display:block — this block is later in the file at equal specificity,
       matching theme's own ordering. */
    footer.footer .footer-column a {
        font-size: 12px;
        padding: 6px 0;
        min-height: 32px;
        display: flex;
        align-items: center;
        justify-content: center;
    }

    /* theme.css:4393-4395 */
    footer.footer .footer-bottom p { font-size: 11px; }

    /* theme.css:4412-4414 */
    footer.footer .footer-social a img { width: 20px; }
}

/* ── The footer IS the site navigation on a phone ───────────────────────────
   Below 769px theme.css:178 takes .navbar-menu out of the document, so the
   hamburger and this footer are the only two ways to /store, /faq, /status,
   /terms, /privacy and /contact. The hamburger was sized for a finger three
   commits ago (theme.css:169-176); this column never was.

   MEASURED at 360x800 on /, /store, /store/rainbow-six-siege-script, /cart,
   /status, /faq, /contact and /changelog: every .footer-column a rendered
   200x36 and every .footer-social a rendered 40x40. Both are under the 44px
   guideline on their short axis, on the only navigation a phone has.

   The three rungs above each restate this selector — 768 sets display:block
   with 4px of padding, 480 cuts it to 3px, 360 adds min-height:32px and
   switches to flex — so a floor written into any one of them is overridden by
   the next one down. Media queries add no specificity, so the LAST matching
   rule wins and this block has to sit after all three. It restates the
   display mode for the same reason: at the 768 and 480 rungs the anchor is
   display:block, where a min-height would grow the box and leave the label at
   the top of it.

   44px is a floor, not a height. The font sizes, the colours and the centring
   are untouched, so nothing here changes what the footer looks like beyond the
   eight pixels of breathing room between one link and the next — which is the
   gap a thumb needs to hit the one it aimed at.

   The social row is icons only, so its anchors have no text to grow around and
   the 40px box theme.css:4222 gives them is the whole target. The images stay
   at their rung's width; only the box a finger aims at changes. */
@media (max-width: 768px) {
    footer.footer .footer-column a {
        display: flex;
        align-items: center;
        justify-content: center;
        min-height: 44px;
    }

    footer.footer .footer-social a {
        display: flex;
        align-items: center;
        justify-content: center;
        min-width: 44px;
        min-height: 44px;
    }
}

/* ============================================================================
   Decorative layers must never sit on top of readable content
   ----------------------------------------------------------------------------
   .footer .footer-glow (theme.css:1571-1581) is position:absolute, anchored to
   the footer's BOTTOM, height:100vh, z-index:0 — inside a .footer that is
   position:relative with no z-index of its own. So it is not contained by the
   footer at all: it lands in the ROOT stacking context and always rises
   (100vh − footerHeight) above the footer's top edge. Measured 635px at a
   1300px-tall viewport, 706px on product pages.

   Anything in that band that is position:static paints in the block-level
   layer, which is BELOW a positioned z-index:0 — so the glow painted OVER it.
   On /reset-password the panel was 100% covered; elementFromPoint over the
   panel, its inputs and its submit button all returned DIV.footer-glow.

   The upward bleed is the intended look, so it is not clipped. Two fixes:
   (1) the glow can never intercept a click anywhere, and (2) the page's
   content wrappers get a stacking context above it.
   ========================================================================= */

/* store.css:1339 already did this — but store.css is only loaded by home,
   /store and /status, so on /faq, /contact, /terms, /privacy, /email/verify,
   /reset-password and every product page the glow still swallowed clicks in a
   ~275px dead band above the footer. Verified live: the contact form's submit
   button and two product-page buttons were genuinely unclickable. Hoisted here
   so it is unconditional. The copy in store.css is now redundant but harmless
   and is left alone. */
.footer .footer-glow { pointer-events: none; }

/* z-index 1 is enough (the glow is 0) and is deliberately LOW: these sections
   contain product lightboxes and video modals that need to escape upward, so a
   high value here would trap them under the navbar's 1000.

   .related-products and .product-faq are the product page's share. Product
   pages were the one page type with no wrapper in this list — .related-products
   (product.css:1500-1503) and .related-product-card (product.css:1524) are both
   position:static/z-index:auto, so nothing between them and <body> made a
   stacking context and the glow painted over the related cards' headings,
   blurbs and "View Details" buttons. It is currently invisible: footer.png's
   alpha is ~0 across the top fifth, which is the part that reaches this high.
   That is luck, not design — the invariant is that decorative layers never sit
   over readable content, and here it was simply untrue. Neither section has
   anything that needs to escape upward, so z-index 1 is free. */
.info-section,
.store-content,
.auth-page,
.related-products,
.product-faq {
    position: relative;
    z-index: 1;
}

/* ============================================================================
   Page sections that lost their styling in the port
   ========================================================================= */

/* .info-section replaced the original's .terms-content on /contact, /terms and
   /privacy — and .info-section is defined in no stylesheet at all, so those
   pages silently lost theme.css:4833's `padding: 100px 0 120px`. Measured
   paddingTop/paddingBottom of 0px: the content butted straight into the hero
   above and the footer below, which no other page does. These are theme.css's
   own values, reproduced.

   min-height stops the footer floating in the upper third of a tall viewport
   on short pages: /terms is a 146px placeholder, and section.hero.store
   (theme.css:490) explicitly drops the theme's min-height:100vh, so total page
   height was hero + 146px + footer. 480px ≈ the measured height of the
   .hero.store block above it, so hero + content ≳ one viewport. */
.info-section {
    padding: 100px 0 120px;
    background: #0f0e0e;
    min-height: calc(100vh - 480px);
}

/* theme.css:5173 and :5130 carry the responsive and print variants of
   .terms-content. Renaming the class cost those too, so they are reproduced
   here against the new name. */
@media (max-width: 768px) {
    .info-section { padding: 80px 0 60px; }
}
@media print {
    .info-section {
        padding: 20px 0;
        background: #ffffff;
        min-height: 0;
    }
}

/* .store-content is likewise defined nowhere. It wraps the store grid on
   /store and the status board on /status, where .store-container already
   supplies the padding (store.css:55 and :1201), so only the bottom rhythm and
   the stacking context above are needed — no top padding, or it would double
   up with section.hero.store's 100px bottom padding. */
.store-content { padding-bottom: 40px; }

/* ============================================================================
   Product hero: background graphics were escaping the page
   ----------------------------------------------------------------------------
   theme.css:199-202 declares --bg-opacity / --bg-scale / --bg-blur on
   `section.hero`, and .hero-bg-graphic (theme.css:213) consumes them. The
   product page's hero is `.product-hero`, NOT `.hero`, but store/show.blade.php
   puts the same four .hero-bg-graphic divs inside it — so
   `transform: translateX(calc(-50% + var(--custom-left))) scale(var(--bg-scale)))`
   referenced undefined variables, was invalid at computed-value time, and
   computed to `transform: none`. Without the -50% translate,
   .hero-bg-top-mid sat at left:712.5px at full width and pushed
   documentElement.scrollWidth to 2138 against a 1425 clientWidth — 713px of
   horizontal document overflow on every product page, hidden only by
   theme.css:37's body{overflow-x:hidden}. The graphic that should be centred
   behind the hero was shoved off the right edge.

   Declaring the three properties is what re-centres it (it makes the transform
   valid again); overflow:hidden matches section.hero:195 so nothing else in
   this hero can escape either. The lightbox and video modal live in later
   sections, so clipping here does not affect them.

   padding-top matches every other hero's 180px. product.css:7 used 120px
   because it budgeted for .breadcrumb — a second fixed bar at top:80px
   (product.css:13-29) that neither the original product pages nor
   store/show.blade.php ever render. 120px against a 71px fixed navbar was
   already tight; 180px is what the rest of the site uses.
   ========================================================================= */
section.product-hero {
    overflow: hidden;
    padding-top: 180px;

    --bg-opacity: 1;
    --bg-scale: 1;
    --bg-blur: 0px;
}

/* The same clearance ladder theme.css:2401/2428/2541 gives section.hero, so
   the product hero tracks every other hero down the breakpoints instead of
   holding 180px on a phone. The 480px step also has to be restated because
   product.css:1920-1922 declares it at 0,1,0 and would otherwise lose to the
   0,1,1 rule above — which is outside any media query and would therefore
   apply at every width. */
@media (max-width: 900px) { section.product-hero { padding-top: 140px; } }
@media (max-width: 768px) { section.product-hero { padding-top: 120px; } }
@media (max-width: 480px) { section.product-hero { padding-top: 100px; } }

/* ============================================================================
   Buttons rendered as <button> where the theme styled an <a>
   ----------------------------------------------------------------------------
   Anything Alpine drives is a <button>, not an <a href="#">: before the
   deferred Alpine module binds, a click on an anchor followed the href and
   jumped the page to the top. The theme only ever styled the anchor form, so
   the button box model is normalised to match. (The navbar pair is handled
   above; this covers the hero CTA on the home page and any future one.)
   ========================================================================= */
button.btn-primary,
button.btn-secondary,
button.btn-discord {
    font-family: inherit;
    cursor: pointer;
}

/* ============================================================================
   Short-page background seams
   ----------------------------------------------------------------------------
   Two independent causes produced the hard horizontal line and the amber wash
   on /email/verify, and they show up on every SHORT page — verify-email,
   reset-password, legal, contact.
   ========================================================================= */

/* 1. THE SEAM.
   .hero is `overflow: hidden` and its artwork (.hero-bg-graphic) is absolutely
   positioned inside it. .hero.store overrides min-height to `fit-content`, so
   on a page whose hero is just a title and one line of subtitle the section is
   ~260px tall and the artwork is sliced off at that edge — a hard, straight
   cut against the flat page background.

   The original never hit this because every .hero.store page it shipped had
   enough content below the fold to disguise it.

   Fixed by fading the last stretch of the hero into the page colour, so the
   artwork ends in a gradient rather than an edge. `inset: auto 0 0 0` pins it
   to the bottom; pointer-events none so it never eats a click. */
.hero.store::after {
    content: '';
    position: absolute;
    inset: auto 0 0 0;
    height: 160px;
    pointer-events: none;
    z-index: 1;
    background: linear-gradient(to bottom, rgba(15, 14, 14, 0), #0F0E0E);
}

/* The hero's own content sits at z-index 5 (theme.css), so the fade above
   passes safely underneath it. Guard the graphics explicitly anyway — they
   default to z-index 0 and must stay below the fade. */
.hero.store .hero-bg-graphic { z-index: 0; }

/* Give a short hero enough room that the fade has somewhere to happen. Without
   this the gradient is taller than the section it is fading. */
.hero.store { min-height: 340px; }

@media (max-width: 768px) {
    .hero.store { min-height: 280px; }
    .hero.store::after { height: 120px; }
}

/* 2. THE AMBER WASH.
   .footer-glow is `height: 100vh` with footer.png at background-size: cover.
   On a long page it is a subtle glow behind a distant footer. On a short one
   the footer sits near the top of the viewport, so a full-viewport-height
   image blooms across most of the screen — which is the orange gradient
   swallowing the lower half of /email/verify.

   Capping it in px rather than vh makes it the same size on every page
   regardless of how tall the content is, which is what "a glow behind the
   footer" should have meant. */
.footer .footer-glow {
    height: 420px;
    max-height: 60vh;
    opacity: .38;
}

@media (max-width: 768px) {
    .footer .footer-glow { height: 300px; opacity: .3; }
}

/* ── Footer glow: kill the hard top edge ────────────────────────────────────
   .footer-glow paints footer.png at `background-size: cover` inside a box.
   The image fills the box, so wherever the box starts the image starts — at
   full brightness, in a straight horizontal line across the page. That line
   is visible on EVERY page, and capping the element's height (done earlier to
   stop the glow swallowing short pages) made it sharper rather than softer,
   because a shorter box means a more defined boundary.

   Height was never the problem: the missing piece is that the element has no
   soft edge at all. Masking the top 55% to transparent means the glow blooms
   out of the page background instead of switching on at a boundary, and it
   works at any height, so the two fixes stop fighting each other.

   mask-image rather than an overlay gradient: an overlay would have to match
   the page background colour exactly, and would break the moment anything
   sits behind the footer. A mask makes the element itself fade, whatever is
   underneath it. */
.footer .footer-glow {
    -webkit-mask-image: linear-gradient(to bottom, transparent 0%, rgba(0,0,0,.35) 30%, #000 55%);
            mask-image: linear-gradient(to bottom, transparent 0%, rgba(0,0,0,.35) 30%, #000 55%);
    -webkit-mask-size: 100% 100%;
            mask-size: 100% 100%;
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;

    /* Taller again now that the top fades — 420px was compensating for the
       hard edge by keeping it small. The fade does that job properly. */
    height: 620px;
    max-height: 72vh;
    opacity: .55;
}

@media (max-width: 768px) {
    .footer .footer-glow {
        height: 420px;
        opacity: .45;
    }
}

/* Safari and older Chromium need the -webkit- prefix; browsers with neither
   simply get the glow with its original hard edge rather than no glow, which
   is the right way round for a decorative layer. */
@supports not ((mask-image: linear-gradient(#000, #000)) or (-webkit-mask-image: linear-gradient(#000, #000))) {
    .footer .footer-glow { opacity: .28; }
}


/* ============================================================================
   IMPERSONATION BANNER
   ----------------------------------------------------------------------------
   Rendered by partials/impersonation-banner in BOTH layouts, so it lives here
   rather than in dashboard.css — an administrator viewing the storefront as a
   customer needs it just as much as one on /dashboard, and dashboard.css never
   reaches a marketing page.

   PINNED TO THE BOTTOM, NOT THE TOP. nav.navbar is position:fixed and every
   top offset on the site is measured against it (--dash-nav-h, the hero
   padding, scroll-padding-top in this file). A bar above the navbar would move
   all of them. Anchoring to the bottom means this element cannot shift a single
   pixel of the existing design, and .imp-spacer — a normal in-flow block at the
   very end of <body> — is what keeps it from covering the footer.

   z-index 1800 clears everything else on the site. It has to, because the one
   moment this bar must stay visible is when a modal or a slide-over is open.
   The number was 1200 and had the navbar at 1000, the dashboard backdrop at
   1050 and the slide-over at 1100 written beside it; all three moved when the
   account menu had to be lifted over the flash toast, so this moved with them.
   THE LADDER IS STATED IN ONE PLACE — see the STACKING ORDER block above — and
   this is one of its rungs rather than an independent choice.

   Colour is the site's danger red. It is deliberately the loudest surface
   anywhere on the site — the whole point of the control is that it cannot be
   mistaken for chrome.
   ========================================================================== */
.imp-bar {
    position: fixed;
    inset: auto 0 0 0;
    z-index: 1800;

    background: #1A0E0E;
    border-top: 1px solid rgba(239, 68, 68, .55);
    /* Upward shadow: the bar is at the bottom, so the lift has to read from
       below the content rather than above it. */
    box-shadow: 0 -10px 30px rgba(0, 0, 0, .45);
}

.imp-bar-inner {
    max-width: 1560px;              /* the dashboard shell's measure, which is
                                       the wider of the two layouts */
    margin: 0 auto;
    padding: 12px 32px;
    display: flex;
    align-items: center;
    gap: 16px;
}

.imp-bar-icon {
    width: 34px;
    height: 34px;
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    border-radius: 8px;
    background: rgba(239, 68, 68, .14);
    border: 1px solid rgba(239, 68, 68, .35);
    color: #EF4444;
    font-size: 18px;
}

.imp-bar-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;                   /* lets the sub-line ellipsis instead of
                                       forcing the flex row wider than the
                                       viewport */
    font-family: 'Outfit', sans-serif;
}

.imp-bar-text strong {
    font-size: 14.5px;
    font-weight: 600;
    color: #FFFFFF;
    letter-spacing: -.01em;
}

.imp-bar-sub {
    font-size: 12.5px;
    line-height: 1.45;
    color: #C9C5C5;
}

.imp-bar-form { margin-left: auto; flex: 0 0 auto; }

.imp-bar-exit {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 9px 18px;
    border-radius: 8px;
    border: 1px solid rgba(239, 68, 68, .45);
    background: rgba(239, 68, 68, .16);
    color: #FFFFFF;
    font-family: 'Outfit', sans-serif;
    font-size: 13.5px;
    font-weight: 500;
    cursor: pointer;
    transition: background .18s ease, border-color .18s ease;
}

.imp-bar-exit:hover {
    background: rgba(239, 68, 68, .3);
    border-color: rgba(239, 68, 68, .7);
}

.imp-bar-exit i { font-size: 16px; }

/* Height of .imp-bar at desktop widths. Kept as its own element rather than as
   padding on <body> because both layouts style body differently and one of them
   (.dash-body) is a flex column. */
.imp-spacer { height: 62px; }

@media (max-width: 720px) {
    .imp-bar-inner { padding: 10px 16px; gap: 12px; flex-wrap: wrap; }

    /* The explanatory second line is the first thing to go: the heading and the
       Exit button are what the bar is FOR, and on a phone they must both stay
       on one row rather than pushing Exit off screen. */
    .imp-bar-sub  { display: none; }
    .imp-spacer   { height: 58px; }
}


/* ============================================================================
   LOGIN LOCKDOWN BANNER
   ----------------------------------------------------------------------------
   Rendered by partials/lockdown-banner in BOTH layouts, so it lives here beside
   the impersonation bar rather than in dashboard.css — an administrator on the
   storefront needs the reminder just as much as one on /dashboard, and
   dashboard.css never reaches a marketing page.

   Same geometry as .imp-bar and for the same reasons: pinned to the BOTTOM
   because nav.navbar is fixed and every top offset on the site is measured
   against it, with an in-flow .lock-spacer keeping it off the footer.

   AMBER, NOT RED. Red is taken, and it means "you are acting as somebody else,
   stop and check" — the one state on the site that must never be mistaken for
   anything. This is the platform's own warning colour: serious, ongoing,
   deliberate. The two bars cannot appear together (an impersonating session
   holds the customer's permissions and never passes the can() on this one), so
   the distinction is about what the colour MEANS, not about telling two visible
   bars apart.

   z-index 1790 — one rung under the impersonation bar, over everything else on
   the site. It tracks that bar rather than standing on its own: when the whole
   ladder moved to lift the account menu over the flash toast, the gap between
   these two is the only thing that had to be preserved. See the STACKING ORDER
   block above for the full order and the reason it is stated in one place.
   ========================================================================== */
.lock-bar {
    position: fixed;
    inset: auto 0 0 0;
    z-index: 1790;

    background: #1C1408;
    border-top: 1px solid rgba(234, 156, 34, .55);
    /* Upward shadow: the bar sits at the bottom, so the lift reads from below
       the content rather than above it. */
    box-shadow: 0 -10px 30px rgba(0, 0, 0, .45);
}

.lock-bar-inner {
    max-width: 1560px;              /* the dashboard shell's measure, the wider
                                       of the two layouts */
    margin: 0 auto;
    padding: 12px 32px;
    display: flex;
    align-items: center;
    gap: 16px;
}

.lock-bar-icon {
    width: 34px;
    height: 34px;
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    border-radius: 8px;
    background: rgba(234, 156, 34, .14);
    border: 1px solid rgba(234, 156, 34, .35);
    color: #EA9C22;
    font-size: 18px;
}

.lock-bar-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;                   /* lets the sub-line wrap instead of forcing
                                       the flex row wider than the viewport */
    font-family: 'Outfit', sans-serif;
}

.lock-bar-text strong {
    font-size: 14.5px;
    font-weight: 600;
    color: #FFFFFF;
    letter-spacing: -.01em;
}

.lock-bar-sub {
    font-size: 12.5px;
    line-height: 1.45;
    color: #C9C5C5;
}

.lock-bar-sub code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11.5px;
    padding: 1px 5px;
    border-radius: 5px;
    background: rgba(255, 255, 255, .06);
    color: #EA9C22;
}

/* The operator's own note. Italic so it reads as a quotation from a person
   rather than as more of the system's copy. */
.lock-bar-sub em { color: #FFFFFF; font-style: italic; }

.lock-bar-action {
    margin-left: auto;
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 9px 18px;
    border-radius: 8px;
    border: 1px solid rgba(234, 156, 34, .45);
    background: rgba(234, 156, 34, .16);
    color: #FFFFFF;
    font-family: 'Outfit', sans-serif;
    font-size: 13.5px;
    font-weight: 500;
    text-decoration: none;
    transition: background .18s ease, border-color .18s ease;
}

.lock-bar-action:hover {
    background: rgba(234, 156, 34, .3);
    border-color: rgba(234, 156, 34, .7);
    color: #FFFFFF;
}

.lock-bar-action i { font-size: 16px; }

/* Height of .lock-bar at desktop widths. Its own element rather than padding on
   <body>, because the two layouts style body differently and one of them
   (.dash-body) is a flex column. */
.lock-spacer { height: 62px; }

@media (max-width: 720px) {
    .lock-bar-inner { padding: 10px 16px; gap: 12px; }

    /* The explanatory line goes first. The headline and the Manage button are
       what the bar is FOR, and on a phone both must stay on one row rather than
       pushing the button off screen. */
    .lock-bar-sub { display: none; }
    .lock-spacer  { height: 58px; }
}


/* ── THE MOBILE CART ───────────────────────────────────────────────────────
   The cart is rendered twice — once inside .navbar-actions and once as a direct
   child of .container — and exactly ONE is ever visible, because they are
   hidden on opposite sides of the same 768px line that theme.css:145 already
   uses to collapse .navbar-menu.

   This exists because everything inside .navbar-menu is display:none below
   769px, so the only cart on the page measured zero by zero on a phone and
   could be reached only by opening the hamburger. That is fine for a nav link
   and wrong for the control that shows what somebody is about to buy.

   Hidden by DEFAULT and revealed at the breakpoint, rather than the reverse: a
   stylesheet that fails to load leaves the desktop copy — the one in the normal
   place — rather than two carts side by side. */
nav.navbar > .container > .nav-cart-bar { display: none; }

@media (max-width: 768px) {
    nav.navbar > .container > .nav-cart-bar {
        display: inline-flex;
        /* Sits left of the hamburger, and pushed to the right of everything
           else, so the pair reads as one cluster in the corner rather than as a
           button drifting beside the logo. */
        margin-left: auto;
        margin-right: 10px;
        padding: 8px 10px;
    }

    /* The glyph alone. In the stacked panel the label earns its place because
       the chip is a full-width row; out here it is a 40px control in a corner
       and the word would double its width for nothing. */
    nav.navbar > .container > .nav-cart-bar .nav-cart-label {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        overflow: hidden;
        clip: rect(0 0 0 0);
        clip-path: inset(50%);
        white-space: nowrap;
        border: 0;
    }

    /* The toggle no longer needs to claim the right edge — the cart is doing
       it — so its auto margin is dropped to stop the two fighting for it. */
    nav.navbar > .container > .navbar-toggle { margin-left: 0; }

    /* ── AND THE ONE IN THE PANEL STANDS DOWN ─────────────────────────────
       The commit that introduced the bar copy claimed "exactly one of the two
       is ever visible, because they are hidden at opposite sides of the same
       768px breakpoint". That was wrong, and only wrong while the menu is OPEN.

       The desktop copy is hidden down here by .navbar-menu { display: none }
       from theme.css — but `.is-open` is what the hamburger adds, and it puts
       the panel back to display:flex. So the moment somebody opened the menu,
       the panel's full-width 320px cart row appeared underneath the 44px bar
       cart that was already in the corner. Measured on the live site at 360px:
       two visible .nav-cart elements, one at (246,16) and one at (20,286).

       Not a layout break — both were the right size and nothing overflowed —
       but two controls for one thing, three inches apart, on the narrowest
       screen there is. That is the crowding this round exists to remove.

       Scoped to .navbar-menu so it cannot touch the bar copy, which is a
       direct child of .container and is the one that should survive here. */
    nav.navbar .navbar-menu .nav-cart { display: none; }
}


/* ============================================================================
   THE CART DRAWER
   ----------------------------------------------------------------------------
   "Wouldn't it be smarter for the cart to be like a popup saying all added
    items with their product image on the left side what you have added in the
    cart and the quantity. Then the subtotal and then a review & checkout
    button."

   Markup: partials/cart-drawer (the dialog) and cart/drawer.blade.php (the
   contents, fetched from /cart/drawer). Behaviour: resources/js/cart-drawer.js.
   Each of those three explains its own half of the mechanism; this file owns
   where things sit.

   ── WHY IT IS IN THIS STYLESHEET AND NOT cart.css ───────────────────────────
   cart.css loads on exactly two pages. The drawer is CHROME: it renders in both
   layouts, on every page, beside the navbar chip that opens it. enhancements.css
   already loads last on every page of the site in both layouts and is already
   where that chip is styled — the panel belongs next to its trigger. A fourth
   link tag in partials/head would have been one more request on every page of
   the site for one component.

   ── THE PALETTE IS RESTATED, NOT INHERITED ──────────────────────────────────
   Same discipline cart.css states at the top of itself. The tokens below are
   declared on .cdr-panel, the outermost element the drawer owns, rather than on
   :root or on body — a var() with no declaration anywhere in the chain is
   invalid at computed-value time, the rule vanishes silently, and the only
   symptom is a panel that looks unfinished. Dashboard tokens in particular
   (--dash-*) are declared on .dash-body and DO NOT EXIST on a marketing page,
   so this component may not touch one.

     bg #0F0E0E · surface #141313 · raised #1A1919 · border #262424
     muted #8A8585 · secondary #C9C5C5 · accent #EA9C22 · danger #EF4444

   ── EVERY CLASS IS PREFIXED cdr- ────────────────────────────────────────────
   crt- is the cart page's, and it is loaded on the product page too; cdr- is
   the drawer's, and it is loaded everywhere. Keeping them apart is what stops a
   change to a basket line on /cart from restyling the panel on the home page.
   ========================================================================= */

/* ── The dialog itself ──────────────────────────────────────────────────────
   NOTHING SETS `display` ON THE BARE SELECTOR, and that is not a stylistic
   preference. The UA stylesheet hides a closed dialog with
   `dialog:not([open]) { display: none }`; any author rule declaring a display
   on `.cart-drawer` beats it and puts a permanently visible 420px panel over
   the right-hand third of every page on the site. The layout below is
   therefore expressed with insets and the panel's own flex column, and the one
   place a display IS needed is scoped to [open]. */
.cart-drawer {
    position: fixed;

    /* Pinned to the right edge, full height. `auto` on the left is what leaves
       the width declaration below as the thing that decides the size. */
    inset: 0 0 0 auto;

    /* min() rather than a media query for the desktop case: at any viewport
       narrower than 420px the panel is simply the viewport, with no breakpoint
       to maintain. The bottom-sheet form at the end of this block is a
       genuinely different layout and does get one. */
    width: min(420px, 100vw);

    /* HEIGHT IS DECLARED, and leaving it to the insets does not work. The UA
       stylesheet sets `dialog { height: fit-content }` — not `auto` — and a
       fit-content box is NOT stretched by `top: 0; bottom: 0`. The panel
       therefore sized itself to its own contents and simply grew past the
       bottom of the screen once the basket was long enough.

       Measured before this line existed: at 1425x900 with an eleven-unit
       basket the dialog came out 866px tall at a viewport of 900 and the
       footer sat below the fold, so the Review & checkout button was off
       screen — the precise failure the pinned footer exists to prevent, and
       invisible on any basket small enough to fit.

       `height: 100%` over-constrains the box against `bottom: 0`; CSS resolves
       that by ignoring the bottom inset, which is exactly the intended answer:
       one viewport tall, starting at the top.

       The UA also gives a modal dialog `max-width`/`max-height` of
       `calc(100% - 6px - 2em)`, `margin: auto`, 1em of padding and a solid
       border. All of them have to go, or the panel floats a few pixels off the
       edge inside a white frame. */
    height: 100%;
    max-width: none;
    max-height: none;
    margin: 0;
    padding: 0;
    border: 0;

    /* The panel paints its own surface. A background here shows as a hairline
       down the edge while the entry transform is still running. */
    background: transparent;

    /* .cdr-scroll takes the overflow. Left at the UA's `auto` the whole panel
       scrolls instead, and the subtotal and the checkout button scroll off the
       bottom of it — which is the exact complaint the drawer answers. */
    overflow: hidden;
}

.cart-drawer[open] {
    display: block;
}

/* The tint behind it. No blur: it is a full-height panel over an already dark
   site, so the separation is unambiguous without one, and backdrop-filter over
   a surface this large is the single thing here that would cost frames on a
   phone. */
.cart-drawer::backdrop {
    background: rgba(6, 6, 6, .66);
}

/* ── Entry, and deliberately no exit ────────────────────────────────────────
   IT SLIDES IN AND IT VANISHES. That asymmetry is a decision, not an oversight.

   @starting-style is the "from" state for an element ENTERING the top layer,
   and it is the only way to animate something that was display:none one frame
   ago without a class toggle driven from JavaScript. It is also ADDITIVE and
   cannot leave a stuck state: a browser that does not know the at-rule drops
   the block as unknown and the panel simply appears, fully placed.

   THE EXIT IS THE ONE THAT COULD FAIL, AND IT DID UNDER MEASUREMENT. Animating
   a dialog OUT requires transitioning `display` and `overlay` with
   `allow-discrete`, which holds the element at display:block for the duration
   of the transition and swaps it to none at the end. Measured in headless
   Chrome across 1440, 768 and 390 and in both layouts, `getComputedStyle
   (dialog).display` was still `block` four hundred milliseconds after close()
   on every single run — a 260ms transition that had not finished, or had not
   run at all.

   That may well be an artefact of the harness rather than of a real browser.
   It does not matter which. The failure mode if it is real is a 420px panel
   stuck over the right-hand third of every page on a live shop, and no exit
   animation is worth a risk with that shape when the alternative costs nothing
   but a quarter of a second of polish. Without a display transition the closed
   state is the UA's own `dialog:not([open]) { display: none }`, which applies
   on the frame the dialog closes and cannot be left half-applied.

   The global prefers-reduced-motion block near the top of this file already
   cuts the entry to 0.01ms. */
.cart-drawer {
    transform: translateX(100%);
    opacity: 0;
    transition:
        transform .26s cubic-bezier(.22, .61, .36, 1),
        opacity .2s ease;
}

.cart-drawer[open] {
    transform: translateX(0);
    opacity: 1;
}

@starting-style {
    .cart-drawer[open] {
        transform: translateX(100%);
        opacity: 0;
    }
}

.cart-drawer::backdrop {
    opacity: 0;
    transition: opacity .26s ease;
}

.cart-drawer[open]::backdrop { opacity: 1; }

@starting-style {
    .cart-drawer[open]::backdrop { opacity: 0; }
}

/* ── The panel ──────────────────────────────────────────────────────────────
   A flex COLUMN of three: a fixed head, a slot that takes everything left over,
   and whatever footer the fragment brought with it. Every one of them declares
   `flex: <grow> <shrink> auto` and never a pixel basis — in a column the basis
   is the HEIGHT, and a `flex: 1 1 320px` dropped into a column is how
   .adm-search became a 320px-tall box around a 51px input. */
.cdr-panel {
    --cdr-surface:   #141313;
    --cdr-raised:    #1A1919;
    --cdr-border:    #262424;
    --cdr-muted:     #8A8585;
    --cdr-secondary: #C9C5C5;
    --cdr-accent:    #EA9C22;
    --cdr-success:   #22C55E;
    --cdr-warning:   #F97316;
    --cdr-danger:    #EF4444;

    display: flex;
    flex-direction: column;
    height: 100%;
    background: var(--cdr-surface);
    border-left: 1px solid var(--cdr-border);
    box-shadow: -28px 0 64px rgba(0, 0, 0, .55);
    font-family: 'Outfit', sans-serif;
    color: #FFFFFF;

    /* This element is what showModal() focuses, and an amber ring around the
       whole drawer is noise — the customer knows where they are, the panel just
       slid in over everything else. Removed for the container only; every real
       control inside keeps the site's :focus-visible ring. */
    outline: none;
}

/* Pointer events are dropped on the list while a change is in flight. It is NOT
   a spinner and NOT an opacity fade: the request is one round trip to the same
   box, and a panel that dimmed for 30ms on every press of "+" would flicker.
   What it prevents is a second press landing while the first is unanswered,
   which the script would otherwise have to drop silently — leaving a quantity
   on screen that does not match what the customer did. */
.cdr-panel.is-busy { cursor: progress; }
.cdr-panel.is-busy .cdr-scroll { pointer-events: none; }

.cdr-head {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 17px 20px;
    border-bottom: 1px solid var(--cdr-border);
    background: var(--cdr-surface);
}

.cdr-head h2 {
    display: flex;
    align-items: center;
    gap: 9px;
    margin: 0;
    font-size: 16px;
    font-weight: 600;
    line-height: 1.3;
    color: #FFFFFF;
}

.cdr-head h2 > i {
    font-size: 18px;
    color: var(--cdr-accent);
}

.cdr-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    flex: 0 0 auto;
    padding: 0;
    border: 1px solid var(--cdr-border);
    border-radius: 8px;
    background: var(--cdr-raised);
    color: var(--cdr-secondary);
    font-size: 18px;
    cursor: pointer;
    transition: color .2s ease, border-color .2s ease;
}

.cdr-close:hover {
    color: #FFFFFF;
    border-color: #3A3535;
}

/* The slot the server's markup lands in, and the fragment's own root. Both are
   pass-through flex columns, so .cdr-scroll and .cdr-foot inside the fetched
   HTML behave as though they were children of the panel itself.

   min-height:0 on both is what actually lets the scroll area shrink. A flex
   item's automatic minimum size is its CONTENT, so without it a twelve-line
   basket makes the panel taller than the viewport and the footer — the
   subtotal and the checkout button — goes off the bottom of the screen. */
.cdr-slot,
.cdr-fill {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.cdr-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;

    /* Stops a flick at the end of the list from scrolling the store behind the
       panel. A modal dialog makes the page inert to FOCUS, not to the wheel. */
    overscroll-behavior: contain;
}

.cdr-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 40px 20px;
    font-size: 13.5px;
    color: var(--cdr-muted);
}

.cdr-loading > i {
    font-size: 18px;
    animation: cdr-spin 1s linear infinite;
}

@keyframes cdr-spin {
    to { transform: rotate(360deg); }
}

/* ── One line ───────────────────────────────────────────────────────────────
   Picture on the left at a fixed 56px, everything else in a column beside it.
   The operator asked for the image on the left and it is the reason the row is
   a GRID rather than a flex row: a 56px track is 56px whatever the product is
   called, where a flex item would be squeezed by a long name. */
.cdr-lines {
    list-style: none;
    margin: 0;
    padding: 0;
}

.cdr-line {
    display: grid;
    grid-template-columns: 56px minmax(0, 1fr);
    gap: 14px;
    align-items: start;
    padding: 15px 20px;
    border-bottom: 1px solid var(--cdr-border);
}

.cdr-line:last-child { border-bottom: 0; }

/* Stood down, not hidden. An unsellable line still renders because the customer
   has to be told WHICH item is the problem; the sentence inside it carries the
   reason and this only makes the row look like what the sentence says. */
.cdr-line.is-blocked { background: rgba(239, 68, 68, .05); }

/* THE PICTURE ITSELF IS NOT DEFINED HERE ANY MORE. It is .cart-art at the
   bottom of this file, shared with the cart page and the checkout — the drawer
   was the first surface to draw it and is now one of three. All this line does
   is hand the shared well the drawer's own border and surface tokens. */
.cdr-line > .cart-art {
    --cart-art-border: var(--cdr-border);
    --cart-art-bg:     var(--cdr-raised);
}

/* min-width:0 on the flexible track. A grid track's automatic minimum is
   min-content, so one long unbroken product name is all it takes to push the
   row wider than the panel — which inside a fixed-width drawer means a
   horizontal scrollbar on a basket, at every width. */
.cdr-line-main { min-width: 0; }

.cdr-line-name {
    margin: 0;
    font-size: 14px;
    font-weight: 600;
    line-height: 1.35;
    color: #FFFFFF;

    /* Wraps rather than truncates. The product name is the one thing here a
       customer must read in full to know what they are buying, and there is no
       hover on a phone to reveal an ellipsis. */
    overflow-wrap: anywhere;
}

.cdr-line-name a {
    color: inherit;
    text-decoration: none;
    transition: color .2s ease;
}

.cdr-line-name a:hover { color: var(--cdr-accent); }

.cdr-line-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px 8px;
    margin-top: 6px;
}

.cdr-plan-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 8px;
    border: 1px solid var(--cdr-border);
    border-radius: 999px;
    background: var(--cdr-raised);
    font-size: 11.5px;
    font-weight: 500;
    color: var(--cdr-secondary);
    white-space: nowrap;
}

.cdr-plan-pill > i { font-size: 12px; }

.cdr-line-grant {
    margin: 7px 0 0;
    font-size: 12px;
    line-height: 1.5;
    color: var(--cdr-muted);
}

/* Stepper, line total and remove on one row, with the total pushed across by an
   auto margin so the money in a four-line basket forms a column the eye can add
   up. */
.cdr-line-controls {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 11px;
}

.cdr-qty {
    display: inline-flex;
    align-items: center;
    border: 1px solid var(--cdr-border);
    border-radius: 8px;
    background: var(--cdr-raised);
    overflow: hidden;
}

.cdr-step {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--cdr-secondary);
    font-size: 15px;
    cursor: pointer;
    transition: background .2s ease, color .2s ease;
}

.cdr-step:hover:not(:disabled) {
    background: #232121;
    color: #FFFFFF;
}

/* Visible and inert rather than removed. "-" at a quantity of one and "+" at
   the plan's ceiling are both real edges of the rule, and a button that
   disappeared at the edge would make the control change shape under the
   finger. */
.cdr-step:disabled {
    color: #4A4646;
    cursor: not-allowed;
}

.cdr-qty-value {
    min-width: 26px;
    text-align: center;
    font-size: 13px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: #FFFFFF;
}

/* The lifetime case: one per account, so there is no step to take in either
   direction. Same height and the same box as the stepper it replaces, so a row
   does not change shape between a dated plan and a lifetime one. */
.cdr-qty-fixed {
    display: inline-flex;
    align-items: center;
    height: 32px;
    padding: 0 11px;
    border: 1px solid var(--cdr-border);
    border-radius: 8px;
    background: var(--cdr-raised);
    font-size: 12px;
    font-weight: 500;
    color: var(--cdr-muted);
    white-space: nowrap;
}

.cdr-line-total {
    margin-left: auto;
    font-size: 14px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    color: #FFFFFF;
}

.cdr-remove-form { display: flex; }

.cdr-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: 8px;
    background: transparent;
    color: var(--cdr-muted);
    font-size: 15px;
    cursor: pointer;
    transition: color .2s ease, background .2s ease, border-color .2s ease;
}

.cdr-remove:hover {
    color: var(--cdr-danger);
    border-color: rgba(239, 68, 68, .3);
    background: rgba(239, 68, 68, .08);
}

/* ── Sentences ──────────────────────────────────────────────────────────────
   Three tones, each with an ICON as well as a colour, because colour alone is
   not a status. The same three the cart page uses, and the same wording behind
   them — CartProblem owns the words, these own the picture. */
.cdr-note {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin-top: 9px;
    padding: 8px 10px;
    border: 1px solid var(--cdr-border);
    border-radius: 8px;
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--cdr-secondary);
}

.cdr-note > i {
    flex: 0 0 auto;
    margin-top: 1px;
    font-size: 14px;
}

.cdr-note a {
    color: var(--cdr-accent);
    text-decoration: underline;
}

.cdr-note-bad {
    border-color: rgba(239, 68, 68, .35);
    background: rgba(239, 68, 68, .08);
}
.cdr-note-bad > i { color: var(--cdr-danger); }

.cdr-note-warn {
    border-color: rgba(249, 115, 22, .3);
    background: rgba(249, 115, 22, .07);
}
.cdr-note-warn > i { color: var(--cdr-warning); }

.cdr-note-ok {
    border-color: rgba(34, 197, 94, .3);
    background: rgba(34, 197, 94, .07);
}
.cdr-note-ok > i { color: var(--cdr-success); }

/* A note that is the fragment's first child — the capped-quantity notice — sits
   in the scroll area above the list rather than beside a line, so it needs the
   horizontal padding the lines carry for themselves. */
.cdr-scroll > .cdr-note { margin: 14px 20px 0; }

/* ── Empty ──────────────────────────────────────────────────────────────────
   The COMMON case. Centred in the panel rather than pinned to the top, so it
   reads as a deliberate state and not as a list that failed to load. */
.cdr-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    min-height: 100%;
    padding: 40px 26px;
    text-align: center;
}

.cdr-empty-icon {
    font-size: 34px;
    color: #3A3535;
}

.cdr-empty h3 {
    margin: 2px 0 0;
    font-size: 16px;
    font-weight: 600;
    color: #FFFFFF;
}

.cdr-empty p {
    margin: 0;
    font-size: 13px;
    line-height: 1.6;
    color: var(--cdr-muted);
}

.cdr-empty-btn { margin-top: 8px; }

/* ── The footer ─────────────────────────────────────────────────────────────
   Pinned. The subtotal and the button are the two things a customer opened the
   panel to reach, and they stay on screen with twelve lines above them. */
.cdr-foot {
    flex: 0 0 auto;
    padding: 15px 20px 18px;
    border-top: 1px solid var(--cdr-border);
    background: #121111;
}

.cdr-sums {
    display: flex;
    flex-direction: column;
    gap: 7px;
}

.cdr-sum {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 14px;
    font-size: 13.5px;
    color: var(--cdr-secondary);
}

.cdr-sum-label { min-width: 0; }

.cdr-sum-value {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    color: #FFFFFF;
}

.cdr-sum-note {
    display: block;
    margin-top: 2px;
    font-size: 11.5px;
    color: var(--cdr-muted);
}

.cdr-code {
    display: inline-block;
    margin-left: 5px;
    padding: 1px 6px;
    border-radius: 5px;
    background: var(--cdr-raised);
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: .02em;
    color: var(--cdr-accent);
}

.cdr-sum-off .cdr-sum-value { color: var(--cdr-success); }

.cdr-sum-total {
    margin-top: 3px;
    padding-top: 9px;
    border-top: 1px solid var(--cdr-border);
    font-size: 15px;
    color: #FFFFFF;
}

.cdr-sum-total .cdr-sum-value { font-size: 17px; }

/* The one control the panel exists to reach. Full width and 46px tall: it is
   the primary action on a touch surface and the operator asked for it by name.
   Sized here rather than by editing theme.css because .btn-primary is an inline
   chip everywhere else on the site and has to stay one. */
.cdr-checkout {
    display: flex;
    width: 100%;
    justify-content: center;
    height: 46px;
    margin-top: 14px;
    font-size: 15px;
    font-weight: 600;
}

.cdr-checkout:disabled {
    background: #3A3535;
    color: #8A8585;
    cursor: not-allowed;
}

.cdr-checkout:disabled:hover { background: #3A3535; }

/* The way through to /cart, which is still the fallback, still linkable, and
   still where the promo field and "empty cart" live. Small and secondary: it is
   the exit for the minority who need one of those, not a second offer of the
   page the panel replaced. */
.cdr-foot-link {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    margin-top: 11px;
    font-size: 12.5px;
    color: var(--cdr-muted);
    text-decoration: none;
    transition: color .2s ease;
}

.cdr-foot-link:hover { color: var(--cdr-accent); }

/* ── Phone: a bottom sheet, not a narrow column ─────────────────────────────
   Below 560px a 420px panel is already most of the screen, so the side-anchored
   form gains nothing and costs two things: the checkout button ends up at the
   top of a tall thin column instead of under the thumb, and the strip of page
   left visible down the left-hand edge is too narrow to tell anybody where they
   still are.

   As a sheet it comes up from the bottom, stops short of the top so the page
   underneath is visibly still there, and puts the total and the button in the
   easiest place on a phone to reach.

   88dvh rather than 88vh, and that is not a detail. On iOS Safari `vh` is the
   height with the browser toolbars HIDDEN, so a sheet sized in vh is taller
   than the visible viewport and its footer sits underneath the browser chrome
   — which is the one control on this panel that must never be unreachable. */
@media (max-width: 560px) {
    .cart-drawer {
        inset: auto 0 0 0;
        width: 100%;

        /* `auto` UNDOES the base rule's `height: 100%`, and it has to. A sheet
           is as tall as its contents up to the cap below — a one-line basket
           should not be a full-screen panel with 600px of nothing under the
           button. Take this line out and every sheet is 88dvh regardless of
           what is in it. */
        height: auto;
        max-height: 88dvh;

        /* Entry is upward now, so the transform that was translateX has to be
           restated — an X translation on a sheet anchored to the bottom slides
           it in sideways past the corner. */
        transform: translateY(100%);
    }

    .cart-drawer[open] { transform: translateY(0); }

    @starting-style {
        .cart-drawer[open] { transform: translateY(100%); }
    }

    .cdr-panel {
        border-left: 0;
        border-top: 1px solid var(--cdr-border);
        border-radius: 16px 16px 0 0;
        box-shadow: 0 -20px 48px rgba(0, 0, 0, .55);

        /* max-height on the dialog is a maximum, not a height — a one-line
           basket makes a short sheet rather than a mostly empty tall one. */
        height: auto;
        max-height: 88dvh;
    }

    .cdr-head {
        position: relative;
        padding-top: 20px;
    }

    /* A grab handle, so the sheet reads as a sheet. Decoration only: it is not
       draggable, and suggesting a drag gesture nobody implemented would be
       worse than not suggesting one at all. */
    .cdr-head::before {
        content: '';
        position: absolute;
        top: 7px;
        left: 50%;
        width: 36px;
        height: 4px;
        margin-left: -18px;
        border-radius: 999px;
        background: #3A3535;
    }

    /* Room for a thumb at the bottom edge of a phone, and clear of the iOS home
       indicator. --safe-b is that env() read through the token block at the top
       of this file, so it can be exercised from the console like every other
       inset on the platform; it resolves to 0 everywhere it does not apply. */
    .cdr-foot {
        padding-bottom: calc(18px + var(--safe-b, 0px));
    }

    /* ── Touch targets ──────────────────────────────────────────────────────
       30px is a comfortable mouse target and a poor thumb one, and measurement
       said so: the smallest control in the open panel at 375px was 30 by 30.
       On the desktop drawer that is fine — it is the same size as the stepper
       on the cart page and it is being clicked with a pointer. On the sheet it
       is the control a customer uses most, one-handed, next to a REMOVE button
       that cannot be undone.

       The note that stood here stopped at 38 and gave a reason: "the row also
       has to hold a price and a delete, and measured at 375 the whole controls
       row comes to 213px inside a 265px track — 44px each would take it to 231
       and leave nine pixels of slack, which is the kind of margin that
       disappears the first time a currency prints four digits."

       RE-MEASURED AT 360, which is the width this business's customers are
       actually on. The track is 250px and the row uses 209.2 of it (a 108px
       stepper, an 8px gap, a 47.2px total, another 8px gap, a 38px delete), so
       there is 40.8px of slack, not nine. At 44px each the row uses 221.2 and
       still has 28.8 left. The four-digit case was then measured rather than
       imagined: with the line total forced to €1,000.00 the row comes to 247.7
       inside 250 and still does not overflow.

       It is the wrap below, not the arithmetic above, that makes that safe.
       Ten pixels of slack is not a margin anybody should ship on, and 320px
       phones exist where the same row has only 210px — so the row is told to
       WRAP and the two controls are told never to shrink. Without the second
       half the first is useless: a flex item's default is flex-shrink:1, and
       measured at 320 with the long price the stepper simply squeezed back to
       82px and put both buttons under 44 again, silently. With both, the
       delete drops onto a second line and every button keeps its size — a
       taller row instead of a smaller target.

       Nothing above 561px moves: the drawer is a side panel there, aimed at a
       pointer, and the 30px buttons stay. */
    .cdr-line-controls {
        flex-wrap: wrap;
        row-gap: 8px;
    }

    .cdr-qty,
    .cdr-remove-form { flex: 0 0 auto; }

    .cdr-step,
    .cdr-remove {
        width: 44px;
        height: 44px;
        font-size: 17px;
    }

    .cdr-qty-value { min-width: 30px; }

    .cdr-close {
        width: 44px;
        height: 44px;
    }

    /* The way out to /cart, and the only route to the promo field and to
       "empty cart". It is deliberately quiet — 12.5px, muted, no box — and it
       measured 320x16 at 360, which is quiet to the point of being unhittable:
       16px is under WCAG 2.2's 24px AA floor, never mind the 44 this panel
       holds everything else to. min-height rather than padding so the type
       size, the colour and the position under the checkout button are all
       unchanged; only the box grows, into margin that was already blank. */
    .cdr-foot-link { min-height: 44px; }
}


/* ============================================================================
   PRODUCT ART ON A BASKET LINE — ONE DEFINITION, THREE SURFACES
   ----------------------------------------------------------------------------
   The cart drawer has painted a picture beside every basket line since it was
   built. The cart page and the checkout did not, and the owner asked for both:
   "Make the checkout have product image", and the same for /cart. The drawer's
   treatment is what the other two now use, rather than each inventing its own —
   the markup comes from ONE Blade component (components/cart/art.blade.php) and
   the geometry from this ONE rule, so there is nothing for the three to drift
   apart about.

   IT LIVES IN enhancements.css BECAUSE THAT IS THE ONLY SHEET BOTH LAYOUTS
   LOAD. The cart page is layouts/app and pulls cart.css; the checkout is
   layouts/dashboard and pulls dashboard-billing.css; partials/head loads this
   file on both, last. A copy in each of those two sheets would be the second
   treatment this exists to prevent.

   A SQUARE WELL WITH THE IMAGE COVERING IT. Product art is 16:9 and the well is
   square: letting each image choose its own height gives a ragged left edge down
   a list of four, and stretching it to fit distorts a screenshot. The well is
   opaque and bordered so a placeholder, a dark screenshot and a bright one all
   occupy the same visible box.

   THE COLOURS ARE NOT WRITTEN HERE. Each of the three scopes binds
   --cart-art-border and --cart-art-bg to its own namespace's tokens (--cdr-*,
   --crt-*, --dash-*), which today are the same two greys in all three but are
   three separate palettes that may not stay that way. The literals below are the
   fallback for a scope that binds neither.

   TWO SIZES, AND THE SECOND IS ARITHMETIC RATHER THAN TASTE. 56px in the drawer
   and on the cart page, which are lists with a row to spend. 40px on the
   checkout, whose basket is a table inside a panel the owner requires to fit
   1440x900 without scrolling: its rows are 75px tall and hold a 20px title over
   a 17px sub-line, so a 40px tile costs the row nothing and a 56px one would add
   19px to every line of the basket.
   ============================================================================ */
.cart-art {
    --cart-art-size: 56px;

    display: block;
    flex: 0 0 auto;
    width: var(--cart-art-size);
    height: var(--cart-art-size);
    border-radius: 10px;
    overflow: hidden;
    border: 1px solid var(--cart-art-border, #262424);
    background: var(--cart-art-bg, #1A1919);
}

.cart-art.is-sm {
    --cart-art-size: 40px;
    border-radius: 8px;
}

.cart-art img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}


/* ============================================================================
   THE CART DRAWER ON A PHONE HELD SIDEWAYS
   ----------------------------------------------------------------------------
   Every touch floor this panel has is inside `@media (max-width: 560px)`, and
   that block ends by saying so in as many words: "Nothing above 561px moves:
   the drawer is a side panel there, aimed at a pointer, and the 30px buttons
   stay."

   The width is not what decides that. A phone turned on its side is 667, 740,
   852 or 915px wide and an iPad held upright is 768 — all of them above the
   rung, none of them holding a pointer. Opened by a real touch tap from the
   navbar on /dashboard at each of those, and at 568 and 1024, the panel is the
   side sheet and every control in it is the desktop size:

     .cdr-close      34 x 34     (44 x 44 below 561)
     .cdr-step       30 x 30     the quantity + and -
     .cdr-remove     30 x 30     the delete, which cannot be undone
     .cdr-foot-link  379 x 20.6  the only route to /cart, to the promo field
                                 and to "empty cart"

   THE ROOM IS NOT IN QUESTION. The 560 block's own arithmetic worried about a
   250px track at 360px and proved 44px each fits there with 28.8px to spare;
   the side sheet is 420px wide with a 379px track, so it has 129px more room
   than the case that already ships. Nothing here needs the flex-wrap that
   block adds, because nothing is close to running out.

   WHY THE SIZES ARE RESTATED RATHER THAN MOVED. The 560 rung does more than
   set sizes — it turns the drawer into a bottom sheet, adds the grab handle,
   pays the home-indicator inset and lets the controls row wrap. Only the four
   sizes belong to the device rather than to the layout, so only those four are
   lifted out. Below 561 both blocks now say 44 and agree to the pixel; this
   one is later in the file, so if they ever drift this is the one that wins,
   which is the correct way round for a floor.

   `hover: none` matches at every width, which is why this sits after the width
   rung and at the foot of the sheet — the same rule dashboard.css states three
   times and the reason recurring defect (f) has bitten this codebase before. */
@media (hover: none) {
    .cdr-close {
        width: 44px;
        height: 44px;
    }

    .cdr-step,
    .cdr-remove {
        width: 44px;
        height: 44px;
        font-size: 17px;
    }

    /* min-height rather than padding, so the type size, the muted colour and
       the position under the checkout button are all unchanged and only the
       box grows — into margin that was already blank. */
    .cdr-foot-link { min-height: 44px; }

    /* THE TWO CONTROLS IN THE SIGN-IN OVERLAY THAT NEVER GOT A FLOOR, and both
       for the same reason: they are words inside a sentence rather than boxes,
       so no rung of a width ladder was ever going to be where somebody looked.
       Measured on this build at 1280 and at 360, identically, because they are
       inline text: "Create one" and "Sign in" in the switch line were 38.6x16
       and 64.1x16, and the Terms and Privacy Policy links in the legal tick
       were 35.3x16 and 78.6x16. The switcher is how a customer who arrived on
       the wrong panel gets to the right one, and the two links are the
       documents they are being asked to agree to.

       inline-flex, not inline-block: the box is 44px tall and the text inside
       it must still sit on the line with the words either side of it, which is
       what centring the flex line and vertical-align: middle buy. The negative
       inline margin cancels the padding horizontally, so the sentence keeps its
       spacing and only the hit area grows. A line box that contains one of
       these becomes 44px tall, so nothing overlaps anything above it. */
    .auth-switch button,
    .auth-terms a {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
        padding: 0 6px;
        margin: 0 -6px;
        vertical-align: middle;
    }
}
