/* =============================================================================
   AIConic Hustlers — theme
   =============================================================================
   Replaces the previous cream-and-forest "shelf" theme. Extracted from the
   marketplace mockups: a pink-to-violet-to-blue brand gradient over a very
   light lavender page, near-black navy type, white cards with soft lavender
   borders and low-contrast shadows, and pastel-tinted icon chips.

   THE TOKEN NAMES ARE UNCHANGED ON PURPOSE.

   --pine, --gold, --cream and the rest are referenced inline in about forty
   view files. Renaming them would mean editing every one of those to change a
   palette, which is exactly the kind of change that gets half-done. So the
   names survive and their VALUES are remapped, and a semantic alias is given
   beside each so new code can say what it means:

       --pine   is now the near-black navy, not a forest green
       --gold   is now the brand magenta, not a yellow
       --cream  is now white

   New work should prefer the semantic names (--ink, --surface, --brand-*).
   The legacy names are kept working, not recommended.
   ============================================================================= */


/* =============================================================================
   FONTS — self-hosted
   =============================================================================
   One family at several weights, which is what the mockup uses: the headlines
   are the same face as the body text at 800 rather than a separate display
   font. Plus Jakarta Sans has the geometric-humanist shapes and the tight,
   heavy top end the design depends on.

   DEPLOY STEP: drop plusjakartasans-variable.woff2 into assets/fonts/. Until
   it exists the stack falls back to the closest system faces and the site is
   plain-but-correct rather than broken — font-display:swap paints immediately
   either way.

   EVERY STACK ENDS IN A GENERIC KEYWORD. A font-family list without one has no
   fallback at all, and the browser drops to its default serif — which is how
   the previous theme ended up rendering its wordmark in Times New Roman.
   ============================================================================= */

@font-face{
  font-family:"Plus Jakarta Sans";
  src:url("../fonts/plusjakartasans-variable.woff2") format("woff2");
  font-weight:200 800; font-style:normal; font-display:swap;
}
@font-face{
  font-family:"DM Mono";
  src:url("../fonts/dmmono-regular.woff2") format("woff2");
  font-weight:400; font-style:normal; font-display:swap;
}
@font-face{
  font-family:"DM Mono";
  src:url("../fonts/dmmono-medium.woff2") format("woff2");
  font-weight:500; font-style:normal; font-display:swap;
}


/* =============================================================================
   TOKENS
   ============================================================================= */
:root{
  /* ── Brand ─────────────────────────────────────────────────────────────── */
  --brand-pink:   #EC4899;
  --brand-rose:   #F0409B;
  --brand-violet: #8B5CF6;
  --brand-purple: #7C3AED;
  --brand-blue:   #3B82F6;
  --brand-indigo: #4F46E5;
  --brand-cyan:   #22D3EE;

  /* The signature. Used on the logo, the headline accent, primary buttons and
     every promotional surface. Angle is consistent so two gradients side by
     side read as one system. */
  --grad-brand:  linear-gradient(100deg, #F0409B 0%, #A855F7 52%, #3B82F6 100%);
  --grad-violet: linear-gradient(135deg, #7C3AED 0%, #4C1D95 100%);
  --grad-night:  linear-gradient(135deg, #1A0F33 0%, #2E1065 55%, #3B0764 100%);
  --grad-soft:   linear-gradient(120deg, #EEF0FC 0%, #F3EEFB 48%, #FBF0F7 100%);

  /* ── Neutrals ──────────────────────────────────────────────────────────── */
  --night:   #0B0B18;   /* dark header and announcement bar */
  --night-2: #14142B;   /* raised dark surface, nav row     */
  --ink:     #0F172A;   /* headline navy                    */
  --ink-2:   #64748B;   /* body and muted                   */
  --ink-3:   #94A3B8;   /* faint, meta, placeholders        */
  --line:    #E9EAF4;   /* soft lavender hairline           */
  --line-2:  #F1F2F9;
  --page:    #F7F8FD;   /* page background                  */
  --surface: #FFFFFF;   /* cards                            */
  --surface-2:#F1F2FB;  /* inset / alt surface              */

  /* ── Status ────────────────────────────────────────────────────────────── */
  --danger:  #EF4444;
  --warn:    #D97706;
  --ok:      #059669;
  --star:    #F59E0B;

  /* ── Legacy aliases — see the file header ──────────────────────────────── */
  --pine:    var(--ink);
  --forest:  #1E293B;
  --fern:    #475569;
  --meadow:  var(--brand-purple);
  --moss:    var(--brand-violet);
  --sage:    #DDD6FE;
  --mist:    #EDE9FE;
  --shell:   var(--surface-2);
  --cream:   var(--surface);
  --gold:    var(--brand-pink);
  --sun:     #F472B6;
  --coral:   var(--danger);
  --sky:     var(--brand-blue);
  --plank:   var(--surface-2);
  --plank-d: var(--line);

  /* ── Shape ─────────────────────────────────────────────────────────────── */
  --r-sm: 12px; --r-md: 16px; --r-lg: 22px; --r-xl: 30px;

  /* Shadows are lavender-tinted rather than grey — a neutral drop shadow on a
     lavender page reads as dirt. */
  --shadow-rest: 0 2px 8px rgba(15,23,42,.05);
  --shadow-lift: 0 14px 32px -12px rgba(76,29,149,.20);
  --shadow-glow: 0 12px 26px -10px rgba(236,72,153,.45);

  --maxw: 1320px;
  --ease: cubic-bezier(.22,.9,.3,1);

  /* ── Type stacks — every one ends in a generic ─────────────────────────── */
  --font-display: "Plus Jakarta Sans", "Poppins", "Segoe UI Variable Display",
                  "Segoe UI", system-ui, -apple-system, Roboto,
                  "Helvetica Neue", Arial, sans-serif;
  --font-body:    "Plus Jakarta Sans", "Segoe UI Variable Text", "Segoe UI",
                  system-ui, -apple-system, Roboto, "Helvetica Neue", Arial,
                  sans-serif;
  --font-mono:    "DM Mono", ui-monospace, "SFMono-Regular", "Cascadia Mono",
                  "Segoe UI Mono", Consolas, monospace;
}


/* =============================================================================
   DARK THEME
   =============================================================================
   Extracted from the dark dashboard mockup. ONLY THE NEUTRALS CHANGE — the
   brand gradient, the pinks and the violets are identical in both themes,
   which is what makes the two read as one product rather than two skins.

   This works at all because every rule in this file goes through a token. That
   indirection was kept when the palette was replaced, and this is the payoff:
   a whole second theme is thirty declarations, not a second stylesheet.

   THREE STATES, SET BY THE OPERATOR IN ADMIN → APPEARANCE:

     data-theme="light"   forced light, device preference ignored
     data-theme="dark"    forced dark, device preference ignored
     no attribute         "follow the device" — the media query below decides

   A visitor's own toggle overrides all of it by writing the attribute from
   localStorage before first paint. See views/partials/theme_boot.php for why
   that has to happen in the browser and not on the server.
   ============================================================================= */

:root[data-theme="dark"]{
  --night:    #06060E;
  --night-2:  #101024;
  --ink:      #F8FAFC;
  --ink-2:    #A5B0C2;
  --ink-3:    #6B7A90;
  --line:     #262643;
  --line-2:   #1B1B32;
  --page:     #0A0A16;
  --surface:  #16162B;
  --surface-2:#1E1E38;

  /* Legacy aliases that resolved to literals rather than to other tokens have
     to be restated, or they keep their light values. */
  --forest: #CBD5E1;
  --fern:   #94A3B8;
  --sage:   #3B2F63;
  --mist:   #241C46;

  /* The hero and auth backdrop become a deep violet wash rather than a pale
     lavender one. Same angle, same hue family, inverted luminance. */
  --grad-soft: linear-gradient(120deg, #150F2E 0%, #1D1440 48%, #2A1140 100%);

  /* Black-based shadows. A violet-tinted shadow on a near-black surface is
     invisible, so the elevation would simply disappear. */
  --shadow-rest: 0 2px 10px rgba(0,0,0,.45);
  --shadow-lift: 0 18px 38px -14px rgba(0,0,0,.7);
  /* Softer than the light value. The same pink glow that reads as a lift on a
     white page reads as a blown-out halo on a near-black card. */
  --shadow-glow: 0 8px 20px -12px rgba(236,72,153,.55);
}

/* "Follow the device" is the ABSENCE of the attribute, so this only applies
   when the operator has not forced one. The :not() guard matters: without it
   a visitor who chose light on a dark device would be overridden by their own
   operating system. */
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]):not([data-theme="dark"]){
    --night:    #06060E;
    --night-2:  #101024;
    --ink:      #F8FAFC;
    --ink-2:    #A5B0C2;
    --ink-3:    #6B7A90;
    --line:     #262643;
    --line-2:   #1B1B32;
    --page:     #0A0A16;
    --surface:  #16162B;
    --surface-2:#1E1E38;
    --forest: #CBD5E1;
    --fern:   #94A3B8;
    --sage:   #3B2F63;
    --mist:   #241C46;
    --grad-soft: linear-gradient(120deg, #150F2E 0%, #1D1440 48%, #2A1140 100%);
    --shadow-rest: 0 2px 10px rgba(0,0,0,.45);
    --shadow-lift: 0 18px 38px -14px rgba(0,0,0,.7);
    --shadow-glow: 0 12px 28px -10px rgba(236,72,153,.5);
  }
}

/* Surfaces that carry their own colour rather than a token. Grouped so both
   dark paths — forced and device-following — share one definition. */
:root[data-theme="dark"] .panel-bar,
:root:not([data-theme="light"]) .panel-bar{ background:rgba(22,22,43,.86) }

/* Bar is dark here, so the logo plate comes off. Same selector pair as its
   neighbours so it follows the theme exactly as they do. */
:root[data-theme="dark"] .brand-logo,
:root:not([data-theme="light"]) .brand-logo{ background:none;padding:0 }

:root[data-theme="dark"] .search-hero,
:root:not([data-theme="light"]) .search-hero{ background:var(--surface-2) }

:root[data-theme="dark"] .search-hero input,
:root:not([data-theme="light"]) .search-hero input{ color:var(--ink) }

:root[data-theme="dark"] .bottom-nav,
:root:not([data-theme="light"]) .bottom-nav{ background:rgba(22,22,43,.96) }

/* Pastel tints go from solid pastel to translucent-on-dark, keeping the same
   hue so a category keeps its identity across themes. */
:root[data-theme="dark"] .tint-1{background:rgba(236,72,153,.16);color:#F9A8D4}
:root[data-theme="dark"] .tint-2{background:rgba(139,92,246,.18);color:#C4B5FD}
:root[data-theme="dark"] .tint-3{background:rgba(59,130,246,.18);color:#93C5FD}
:root[data-theme="dark"] .tint-4{background:rgba(34,211,238,.16);color:#67E8F9}
:root[data-theme="dark"] .tint-5{background:rgba(5,150,105,.20);color:#6EE7B7}
:root[data-theme="dark"] .tint-6{background:rgba(217,119,6,.20);color:#FCD34D}
:root[data-theme="dark"] .tint-7{background:rgba(225,29,72,.18);color:#FDA4AF}
:root[data-theme="dark"] .tint-8{background:rgba(79,70,229,.20);color:#A5B4FC}

/* Alerts and status pills are colour-on-tint, so both halves need inverting or
   the text disappears into its own background. */
:root[data-theme="dark"] .alert-notice{background:rgba(5,150,105,.13);color:#6EE7B7;border-color:rgba(5,150,105,.32)}
:root[data-theme="dark"] .alert-warn{background:rgba(217,119,6,.13);color:#FCD34D;border-color:rgba(217,119,6,.32)}
:root[data-theme="dark"] .alert-error{background:rgba(239,68,68,.13);color:#FCA5A5;border-color:rgba(239,68,68,.32)}
:root[data-theme="dark"] .status-active{background:rgba(5,150,105,.20);color:#6EE7B7}
:root[data-theme="dark"] .status-pending{background:rgba(217,119,6,.20);color:#FCD34D}
:root[data-theme="dark"] .status-suspended{background:rgba(239,68,68,.20);color:#FCA5A5}
:root[data-theme="dark"] .p-badge{background:rgba(236,72,153,.18);color:#F9A8D4}
:root[data-theme="dark"] .p-badge.is-popular{background:rgba(139,92,246,.20);color:#C4B5FD}
:root[data-theme="dark"] .p-badge.is-trending{background:rgba(217,119,6,.20);color:#FCD34D}
:root[data-theme="dark"] .p-badge.is-new{background:rgba(5,150,105,.20);color:#6EE7B7}
:root[data-theme="dark"] .disclaimer{background:rgba(217,119,6,.12);color:#FCD34D;border-color:rgba(217,119,6,.3)}
:root[data-theme="dark"] .fill{background:#FCD34D;color:#3B2F00}
:root[data-theme="dark"] .btn-ghost{background:var(--surface-2)}
:root[data-theme="dark"] .trust-ico{background:rgba(255,255,255,.07)}
:root[data-theme="dark"] .trust-strip,
:root[data-theme="dark"] .trust-item + .trust-item{border-color:rgba(255,255,255,.09)}
:root[data-theme="dark"] .toast{background:var(--surface-2);border:1px solid var(--line)}

/* The theme control itself. */
.theme-toggle{
  width:40px;height:40px;border-radius:12px;flex:0 0 auto;
  display:grid;place-items:center;font-size:16px;
  color:var(--ink-2);background:transparent;
  transition:background .2s,color .2s,transform .16s var(--ease);
}
.theme-toggle:hover{background:var(--surface-2);color:var(--brand-pink);transform:rotate(-15deg)}
.site-head .theme-toggle{color:#CBD5E1}
.site-head .theme-toggle:hover{background:rgba(255,255,255,.08);color:#fff}


/* =============================================================================
   BASE
   ============================================================================= */
*,*::before,*::after{box-sizing:border-box}

/* THE hidden ATTRIBUTE HAS TO BE RESTORED, AND IT IS NOT PEDANTRY.
   ===========================================================================
   Browsers implement hidden with [hidden]{display:none} in their USER-AGENT
   stylesheet, and any author rule that sets display beats it — author styles
   outrank UA styles regardless of specificity. So the moment a class like
   .alert says display:flex, every <div class="alert" hidden> in the codebase
   is permanently visible and every element.hidden = true in JavaScript does
   nothing.

   That is not theoretical. The checkout's "this shop does not accept cash on
   delivery for orders this size" warning was displayed on every single
   checkout, next to a working Place order button, contradicting it — because
   .alert carried display:flex and the hidden attribute it was rendered with had
   no effect at all. Nothing errored. The markup and the JavaScript were both
   correct.

   !important is warranted here and almost nowhere else: the whole point is to
   outrank a component's own display rule, which is the thing that broke it. */
[hidden]{display:none !important}
/* overflow-x:hidden IS ON html, NOT ON body, AND THE DIFFERENCE IS THE WHOLE
   BOTTOM NAVIGATION.
   ===========================================================================
   It was on body. CSS overflow has a rule that catches almost everybody: if one
   axis is not `visible` and the other is, the visible one COMPUTES TO `auto`.
   So `body{overflow-x:hidden}` silently gives body `overflow-y:auto` as well,
   which makes <body> a scrolling box rather than a plain block.

   A scrolling box is not supposed to affect fixed positioning, but on Android
   Chrome and iOS Safari it does: position:fixed children of a scrolling body
   are laid out against that box instead of the viewport, so a nav pinned to
   bottom:0 lands at the bottom of the DOCUMENT and scrolls out of sight. It
   shows up first on whichever page is tall enough or wide enough to make the
   body actually scroll — here the products table, which is the only panel page
   carrying a 560px-wide table.

   On html the clip still happens, because overflow on the root element applies
   to the viewport itself. But html is the root, so nothing propagates and body
   stays a normal block — and position:fixed keeps meaning "relative to the
   viewport", which is the only reason a fixed bar works at all.

   DO NOT MOVE THIS BACK TO body to "keep the resets together". */
html{scroll-behavior:smooth;overflow-x:hidden}
body{
  margin:0;
  background:var(--page);
  color:var(--ink);
  font-family:var(--font-body);
  font-size:15.5px; line-height:1.6;
  -webkit-font-smoothing:antialiased;
}
h1,h2,h3,.display{
  font-family:var(--font-display);
  font-weight:800;
  line-height:1.14; letter-spacing:-.022em; margin:0;
  text-wrap:balance;
  font-optical-sizing:auto;
}
.mono,.price,.line-price,.qty span{
  font-family:var(--font-mono);
  font-variant-numeric:tabular-nums;
}
img,svg{display:block;max-width:100%}
button{font:inherit;color:inherit;border:0;background:none;cursor:pointer}
a{color:inherit;text-decoration:none}
.wrap{max-width:var(--maxw);margin-inline:auto;padding-inline:24px}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.muted{color:var(--ink-2)}
.hint{font-size:13px;color:var(--ink-3);margin:6px 0 0}
.num{text-align:right;font-variant-numeric:tabular-nums}

/* Focus is never removed, only restyled. */
:focus-visible{outline:3px solid var(--brand-purple);outline-offset:3px;border-radius:8px}

.eyebrow{
  font-size:11.5px;font-weight:800;letter-spacing:.14em;
  text-transform:uppercase;color:var(--brand-purple);
}

/* Gradient text. -webkit- prefixes are load-bearing, not legacy noise: this is
   still the only way to clip a background to glyphs in every current engine. */
.grad-text{
  background:var(--grad-brand);
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
}

.fa-solid,.fa-regular,.fa-brands{font-size:1.1em;vertical-align:-.13em;flex:0 0 auto;line-height:1}


/* =============================================================================
   BUTTONS
   ============================================================================= */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:9px;
  padding:14px 26px;border-radius:999px;
  font-family:var(--font-display);font-weight:700;font-size:15px;
  letter-spacing:-.01em;
  white-space:nowrap;
  transition:transform .18s var(--ease),background .2s,box-shadow .2s,opacity .2s;
}
.btn-primary{background:var(--grad-brand);color:#fff;box-shadow:var(--shadow-glow)}
.btn-primary:hover{transform:translateY(-2px);box-shadow:0 16px 30px -10px rgba(236,72,153,.55)}
.btn-ghost{background:var(--surface);color:var(--ink);box-shadow:inset 0 0 0 1.5px var(--line)}
.btn-ghost:hover{box-shadow:inset 0 0 0 1.5px var(--brand-violet);transform:translateY(-2px)}
.btn-gold{background:var(--brand-pink);color:#fff}
.btn-gold:hover{background:var(--sun);transform:translateY(-2px)}

/* ── The AIConnect join button ───────────────────────────────────────────────
   Built from the supplied artwork rather than shipping the artwork itself, and
   that is a deliberate trade rather than a shortcut.

   The reference is a 1536x1024 raster of a pill on a black field. As an <img>
   it would need the black cropping to transparency or it draws a black slab on
   a white card; it would be a fixed width on a layout that reflows from 360px
   to desktop; it would be soft wherever it did not land at its native size; and
   it would cost tens of kilobytes on the connections section 2 is built around
   — to draw a shape the stylesheet can already make for nothing.

   Every colour in it is already a token. --grad-brand IS the reference's
   gradient: #F0409B through #A855F7 to #3B82F6. What the picture adds on top is
   three lighting cues, and each maps to one shadow below:

     the thin inner outline        inset ring at 28% white
     the lit top edge              inset highlight, 2px, at the top
     the shaded bottom lip         inset dark, blurred, from below
     the coloured halo             two outer glows, pink from the left end and
                                   blue from the right, matching where those
                                   stops actually sit in the gradient

   Text is uppercase with positive tracking because that is what the artwork
   shows, and because at this weight the negative tracking .btn sets globally
   makes capitals collide. */
.btn-join{
  background:var(--grad-brand);
  color:#fff;
  font-weight:800;
  font-size:15.5px;
  text-transform:uppercase;
  letter-spacing:.045em;
  text-shadow:0 1px 2px rgba(0,0,0,.20);
  box-shadow:
    inset 0 0 0 1.5px rgba(255,255,255,.30),
    inset 0 2px 0 rgba(255,255,255,.38),
    inset 0 -4px 10px rgba(0,0,0,.16),
    0 10px 26px -8px rgba(240,64,155,.55),
    0 10px 26px -8px rgba(59,130,246,.45);
}
.btn-join:hover{
  transform:translateY(-2px);
  box-shadow:
    inset 0 0 0 1.5px rgba(255,255,255,.34),
    inset 0 2px 0 rgba(255,255,255,.44),
    inset 0 -4px 10px rgba(0,0,0,.16),
    0 16px 34px -10px rgba(240,64,155,.62),
    0 16px 34px -10px rgba(59,130,246,.52);
}
/* Pressed: the lit edge and the halo both drop, so it reads as going down into
   the page rather than merely changing colour. */
.btn-join:active{
  transform:translateY(0);
  box-shadow:
    inset 0 0 0 1.5px rgba(255,255,255,.26),
    inset 0 3px 8px rgba(0,0,0,.22),
    0 4px 12px -6px rgba(240,64,155,.45);
}
.btn-danger{background:var(--danger);color:#fff}
.btn-danger:hover{background:#DC2626}
.btn-block{width:100%}
.btn-sm{padding:10px 18px;font-size:13.5px}
.btn:disabled,.btn[aria-disabled="true"]{opacity:.5;cursor:not-allowed;transform:none;box-shadow:none}

.icon-btn{
  width:38px;height:38px;border-radius:12px;background:var(--surface-2);
  display:grid;place-items:center;color:var(--ink-2);transition:background .2s,color .2s;
}
.icon-btn:hover{background:var(--mist);color:var(--brand-purple)}

.chip{
  padding:9px 16px;border-radius:999px;font-weight:700;font-size:13px;
  background:var(--surface);box-shadow:inset 0 0 0 1.5px var(--line);color:var(--ink-2);
  transition:background .2s,color .2s,box-shadow .2s,transform .16s var(--ease);
}
.chip:hover{transform:translateY(-2px);color:var(--brand-purple);box-shadow:inset 0 0 0 1.5px var(--brand-violet)}
.chip[aria-pressed="true"]{background:var(--grad-brand);color:#fff;box-shadow:none}


/* =============================================================================
   FORMS
   =============================================================================
   Inputs stay 16px: iOS Safari zooms the viewport on focus for anything
   smaller, which on a checkout form reads as the page jumping away.
   ============================================================================= */
.field{margin-bottom:18px}
.field label{display:block;font-weight:700;font-size:13.5px;margin-bottom:7px;color:var(--ink)}
.input,.select,.textarea{
  width:100%;padding:13px 16px;border-radius:var(--r-sm);
  background:var(--surface);color:var(--ink);
  border:1.5px solid var(--line);
  font:inherit;font-size:16px;
  transition:border-color .2s,box-shadow .2s;
}
.input::placeholder,.textarea::placeholder{color:var(--ink-3)}
.input:focus,.select:focus,.textarea:focus{
  outline:none;border-color:var(--brand-violet);
  box-shadow:0 0 0 4px rgba(139,92,246,.15);
}
.textarea{min-height:130px;resize:vertical;line-height:1.6}
.select{appearance:none;background-image:none;cursor:pointer}
.input-money{font-family:var(--font-mono);font-variant-numeric:tabular-nums}

/* ── Share this page / QR ────────────────────────────────────────────────────
   partials/share_qr.php. ALWAYS VISIBLE — see the note in that file for why the
   disclosure it replaced was the wrong call. No script: a share control that
   needs JavaScript fails on exactly the connections this platform is built for. */
.share-qr{
  display:flex;gap:16px;align-items:center;
  border:1.5px solid var(--line);border-radius:var(--r-md);
  background:var(--surface);margin:0 0 18px;padding:14px 16px;
}
/* The SVG carries its own white background across the whole viewport, quiet zone
   included, so the code stays scannable on a dark page with no help from here. */
.share-qr-code{flex:0 0 auto;line-height:0;border-radius:var(--r-sm);overflow:hidden}
.share-qr-code svg{display:block;width:120px;height:120px}
.share-qr-text{flex:1 1 200px;min-width:0}
.share-qr-text h2{
  display:flex;align-items:center;gap:8px;
  font-size:15px;font-weight:800;margin:0 0 4px;color:var(--ink);
}
.share-qr-text h2 i{color:var(--brand-violet);font-size:14px}
.share-qr-text p{margin:0 0 10px;font-size:13.5px;color:var(--ink-2);line-height:1.5}
.share-qr-text .input{font-size:12.5px;font-family:var(--font-mono);padding:9px 12px}

/* Below the narrow breakpoint the code sits above the text rather than beside
   it — at 120px square plus a URL field, side by side stops fitting. */
@media (max-width:480px){
  .share-qr{flex-direction:column;align-items:flex-start;gap:12px}
  /* FLEX-BASIS HAS TO BE RESET, and this is the whole reason the block used to
     have a slab of dead space under the link on a phone.

     .share-qr-text carries `flex:1 1 200px` for the side-by-side layout, where
     the main axis is horizontal and 200px is a minimum WIDTH. The moment
     flex-direction becomes column the main axis is vertical, so that same 200px
     becomes a minimum HEIGHT — and flex-grow:1 then stretches it further. The
     text was being given 200px of vertical space to put three short lines in.

     `flex:0 0 auto` puts it back to being sized by its contents. */
  .share-qr-text{flex:0 0 auto;width:100%}
}

/* ── Password show/hide ──────────────────────────────────────────────────────
   Injected by assets/js/password-toggle.js, never written into a view — see
   that file for why the control is built by behaviour rather than by markup.

   .pw-wrap is display:block, so wrapping an input changes nothing about the
   layout of .field around it; the input keeps its own width:100%. */
.pw-wrap{position:relative;display:block}

/* Room for the button, so the dots never run underneath it. 16px of padding
   plus a 44px target. */
.pw-wrap .input{padding-right:52px}

.pw-toggle{
  position:absolute;top:0;right:0;
  /* Full height of the field rather than a centred square: the hit area then
     matches what the eye reads as "the end of the box", and it stays correct
     if the input's padding ever changes. 44px wide is the minimum comfortable
     thumb target. */
  height:100%;width:44px;
  display:grid;place-items:center;
  background:none;border:0;padding:0;cursor:pointer;
  color:var(--ink-3);font-size:15px;
  border-radius:var(--r-sm);
  transition:color .2s;
}
.pw-toggle:hover{color:var(--ink)}

/* Pressed = the password is currently visible. Worth a colour of its own: it is
   the state somebody needs to notice before they walk away from the screen. */
.pw-toggle[aria-pressed="true"]{color:var(--brand-violet)}

/* NEVER REMOVED. Inset so the ring sits inside the field's own border rather
   than doubling it. */
.pw-toggle:focus-visible{
  outline:2.5px solid var(--brand-violet);outline-offset:-3px;color:var(--ink);
}
@media (prefers-reduced-motion:reduce){
  .pw-toggle{transition:none}
}
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:0 18px}
.form-actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:8px}
.check{display:flex;gap:11px;align-items:flex-start;font-size:14px;color:var(--ink-2);margin-bottom:14px}
.check input{width:19px;height:19px;accent-color:var(--brand-purple);flex:0 0 auto;margin-top:1px}


/* =============================================================================
   PUBLIC SITE HEADER — dark
   =============================================================================
   Three stacked strips: announcement, the logo/search/actions row, then the
   section nav. Dark so the light hero card below it reads as lifted off the
   page rather than continuous with it.
   ============================================================================= */
/* Metrics taken off the mockup crop and scaled to full size (the crop is a
   0.63 reduction of a 1536px render, so every figure below is the measured
   value divided by that). They are written as literals rather than tokens
   because they describe one specific composition, not a reusable scale.

   THREE STRIPS, ONE BACKGROUND. Announcement, the logo row and the section
   nav are all the same near-black; only hairlines separate them. Giving each
   its own shade — which is the obvious way to build it — makes the header look
   like three stacked bars instead of one object. */

.announce{
  background:var(--night);color:#94A3B8;
  font-size:12.5px;line-height:1.4;font-weight:500;
  border-bottom:1px solid rgba(255,255,255,.06);
}
.announce .wrap{
  display:flex;align-items:center;justify-content:center;
  min-height:38px;padding-block:8px;text-align:center;
}
.announce strong{color:#fff;font-weight:700}
.announce .fa-solid{font-size:12px;margin-right:7px;color:var(--brand-pink)}
/* Absolute so the welcome line stays optically centred in the viewport rather
   than centred in the space the aside leaves over. */
.announce .announce-aside{
  position:absolute;right:24px;top:50%;transform:translateY(-50%);
  color:#64748B;white-space:nowrap;font-size:12px;
}
.announce .wrap{position:relative}

.site-head{background:var(--night);position:sticky;top:0;z-index:50}

/* Logo row — 72px, everything vertically centred. */
.site-head-top{display:flex;align-items:center;gap:26px;min-height:72px}

.site-head-nav{border-top:1px solid rgba(255,255,255,.06)}
.site-head-nav .wrap{
  display:flex;align-items:center;justify-content:center;min-height:46px;
  overflow-x:auto;scrollbar-width:none;
}
.site-head-nav .wrap::-webkit-scrollbar{display:none}
/* Centred, so the nav has to fill the row and centre its own children rather
   than sit as a left-aligned lump. justify-content on the wrap alone would not
   do it once the row scrolls horizontally on a narrow screen. */
.site-head-nav .mega-nav{flex:1;justify-content:center}

/* ── Logo ───────────────────────────────────────────────────────────────── */
/* One image now. The .logo-mark / .logo-word / .logo-name / .logo-sub rules
   that used to build the lockup out of a gradient circle and two lines of type
   are deleted rather than left behind: nothing renders those classes any more,
   and a dead rule that still matches something later is how .admin-bar .brand-sub
   ended up styling an element nobody meant it to.

   height drives the size and width:auto keeps the 4.37:1 ratio. display:block
   removes the inline baseline gap that would otherwise add a few pixels to the
   72px header row for no reason. */
.logo{display:flex;align-items:center;flex:0 0 auto;min-width:0}
.logo img{display:block;height:42px;width:auto;max-width:100%}

/* THE SAME LOCKUP IN THE OTHER PLATFORM HEADERS — auth, docs, legal, admin.
   .site-head above is var(--night) in both themes so the image needs nothing
   there. .panel-bar is not: it is white in the light theme, and this lockup is
   drawn for a dark background — "Conic" is white and "Hustlers" is pale blue,
   both of which disappear on white.

   So in light it sits on a --night plate. That is the ordinary way a
   dark-drawn logo goes onto a light bar, not a workaround, but it does exist
   only because there is one file. A variant with dark lettering would replace
   all four of these rules with nothing. */
.brand-logo{display:block;padding:5px 10px;border-radius:12px;background:var(--night)}
.brand-logo img{display:block;height:32px;width:auto;max-width:100%}
/* Bar already dark: no plate. */
.admin-bar .brand-logo{background:none;padding:0}

/* ── Search ─────────────────────────────────────────────────────────────── */
/* A ROUNDED RECTANGLE, NOT A PILL. Measured at ~10px against a 44px height —
   a 999px radius reads as a chat input and loses the "field" affordance the
   mockup is going for. Sits immediately after the logo rather than centred in
   the viewport, with the actions pushed right by margin-left:auto. */
.search-hero{
  flex:1 1 auto;max-width:560px;height:44px;
  display:flex;align-items:center;
  background:rgba(255,255,255,.055);
  border:1px solid rgba(255,255,255,.10);
  border-radius:10px;
  padding:0 6px 0 15px;
  transition:border-color .2s,background .2s;
}
.search-hero:focus-within{
  border-color:rgba(236,72,153,.55);
  background:rgba(255,255,255,.08);
}
.search-hero .search-ico{color:#64748B;font-size:14px;flex:0 0 auto}
.search-hero input{
  flex:1;border:0;background:none;font:inherit;font-size:13.5px;
  padding:0 12px;color:#fff;min-width:0;height:100%;
}
.search-hero input:focus{outline:none}
.search-hero input::placeholder{color:#64748B}
/* Plain icon, not a filled circle. The mockup's submit is the same weight as
   the leading magnifier — a gradient button here would out-shout the primary
   call to action two elements to its right. */
.search-hero button{
  width:32px;height:32px;border-radius:8px;flex:0 0 auto;
  background:none;color:#94A3B8;
  display:grid;place-items:center;font-size:14px;
  transition:color .2s,background .2s;
}
.search-hero button:hover{color:#fff;background:rgba(255,255,255,.08)}

/* ── Right-hand actions ─────────────────────────────────────────────────── */
.head-actions{display:flex;align-items:center;gap:26px;margin-left:auto;flex:0 0 auto}
.head-action{
  display:flex;flex-direction:column;align-items:center;gap:5px;
  color:#CBD5E1;font-size:11px;font-weight:500;line-height:1;
  position:relative;transition:color .2s;
}
.head-action:hover{color:#fff}
.head-action .fa-solid,.head-action .fa-regular{font-size:19px;line-height:1}
/* Count bubble, top-right of the glyph. */
.head-action .head-badge{
  position:absolute;top:-5px;right:-8px;
  min-width:16px;height:16px;padding:0 4px;border-radius:999px;
  background:var(--brand-pink);color:#fff;
  font-size:9.5px;font-weight:800;line-height:16px;text-align:center;
}

/* Gradient-outlined pill. Two stacked backgrounds: the flat fill clipped to
   the padding box, the gradient clipped to the border box, so the border
   itself carries the gradient. border-image cannot do this and keep a radius. */
.head-cta{
  display:inline-flex;align-items:center;justify-content:center;
  height:42px;padding:0 24px;border-radius:999px;
  font-family:var(--font-display);font-weight:700;font-size:13.5px;color:#fff;
  border:1.5px solid transparent;
  background-image:linear-gradient(var(--night),var(--night)),var(--grad-brand);
  background-origin:border-box;
  background-clip:padding-box,border-box;
  white-space:nowrap;transition:box-shadow .2s,transform .18s var(--ease);
}
.head-cta:hover{transform:translateY(-1px);box-shadow:0 8px 20px -8px rgba(236,72,153,.7)}

/* ── Section nav ────────────────────────────────────────────────────────── */
.mega-nav{display:flex;align-items:stretch;height:46px}
.mega-nav a{
  display:inline-flex;align-items:center;
  padding:0 16px;font-size:13.5px;font-weight:500;color:#CBD5E1;
  position:relative;white-space:nowrap;transition:color .2s;
}
.mega-nav a:hover{color:#fff}
.mega-nav a[aria-current="page"]{color:#fff;font-weight:600}
/* The underline hugs the TEXT, not the padding box. Insetting it by the
   horizontal padding is the difference between the mockup's tick and a bar
   that looks like a tab.
   Every item keeps its padding now that the row is centred — zeroing the first
   one, which was right for a left-aligned row, would push the whole group 16px
   off centre and make the tick sit wrong under the first label. */
.mega-nav a[aria-current="page"]::after{
  content:"";position:absolute;left:16px;right:16px;bottom:0;height:2.5px;
  border-radius:2px 2px 0 0;background:var(--brand-pink);
}
.nav-flag{
  margin-left:7px;padding:2px 7px;border-radius:999px;
  background:var(--brand-pink);color:#fff;
  font-size:9px;font-weight:800;letter-spacing:.03em;text-transform:uppercase;
  line-height:1.5;
}


/* =============================================================================
   PANEL CHROME — merchant and admin
   ============================================================================= */
.panel-bar{
  position:sticky;top:0;z-index:40;
  background:rgba(255,255,255,.86);backdrop-filter:blur(14px);
  border-bottom:1px solid var(--line);
}
.panel-bar .wrap{display:flex;align-items:center;gap:20px;min-height:70px}
.brand{display:flex;align-items:center;gap:11px;margin-right:auto}
.brand-mark{
  width:42px;height:42px;border-radius:14px;background:var(--grad-brand);
  display:grid;place-items:center;flex:0 0 auto;color:#fff;font-size:19px;
  box-shadow:var(--shadow-glow);
}
/* Carrying a real logo rather than a glyph. Keeps the 42px box so the header
   metrics do not move, and drops every decoration the tile added: the mark is
   drawn on transparency with its own gradient, and stacking it on the brand
   gradient would be two gradients fighting inside 42 pixels.
   object-fit:contain rather than cover — a square mark in a square box needs
   neither, but cover would silently crop a future logo that is not square, and
   a cropped logo is the kind of thing nobody notices until a merchant does. */
.brand-mark.is-image{background:none;box-shadow:none;border-radius:0}
.brand-mark.is-image img{width:100%;height:100%;object-fit:contain;display:block}
.brand-name{font-family:var(--font-display);font-weight:800;font-size:20px;letter-spacing:-.03em}
.brand-sub{font-size:11px;color:var(--ink-3);margin-top:-3px;font-weight:600}

.panel-nav{display:flex;gap:6px;font-weight:600;font-size:14.5px;color:var(--ink-2)}
.panel-nav a{padding:9px 14px;border-radius:999px;transition:color .2s,background .2s}
.panel-nav a:hover{color:var(--brand-purple);background:var(--mist)}
.panel-nav a[aria-current="page"]{color:#fff;background:var(--grad-brand)}

/* ── Platform admin bar ─────────────────────────────────────────────────────
   Visually distinct from the merchant panel on purpose: an admin acting on the
   wrong screen has platform-wide reach, so the two must not be mistakable at a
   glance.

   EVERY COLOUR HERE IS FIXED, NOT A TOKEN. This bar was built with inline
   styles using the legacy aliases — background:var(--pine), colour:var(--cream)
   — which was fine while there was one theme. Those aliases INVERT: --pine is
   --ink, so in dark mode the deliberately-dark bar turned white, --cream text
   turned dark navy, and the hover state put --cream (dark) on --mist (dark
   purple), which is invisible.

   A surface that must stay dark in both themes cannot be described in tokens
   that mean "foreground" in one of them. --night is the one token that is dark
   either way; the rest are literals. */
.admin-bar{background:var(--night);border-bottom:1px solid rgba(255,255,255,.08)}
.admin-bar .brand-mark{background:var(--brand-pink);color:#fff;box-shadow:0 6px 16px -6px rgba(236,72,153,.65)}
.admin-bar .brand-name{color:#fff}
.admin-bar .panel-nav a{color:#CBD5E1}
.admin-bar .panel-nav a:hover{color:#fff;background:rgba(255,255,255,.10)}
.admin-bar .panel-nav a[aria-current="page"]{color:#fff;background:var(--grad-brand)}
.admin-bar .theme-toggle{color:#CBD5E1}
.admin-bar .theme-toggle:hover{background:rgba(255,255,255,.10);color:#fff}

/* The admin has no bottom bar to fall back on, so its nav must not vanish at
   the breakpoint that hides .panel-nav everywhere else — that would leave the
   whole admin area with no navigation on a tablet. It scrolls instead. */
/* THE ICONS ARE FOR THE PHONE AND MUST NOT RENDER ON A WIDE SCREEN.
   They ship in the markup at every width so the breakpoint is pure CSS, but
   showing them alongside the words added roughly 24px to each of twelve items —
   about 290px — which pushed .panel-nav past the row. It does not wrap, so flex
   shrank every pill below its content width and the word broke onto a second
   line under the icon. The desktop bar is text-only, exactly as it was. */
.admin-bar .panel-nav a i{display:none}

@media (max-width:1000px){
  .admin-bar .panel-nav{
    display:flex;order:3;flex-basis:100%;
    overflow-x:auto;scrollbar-width:none;padding-bottom:2px;
  }
  .admin-bar .panel-nav::-webkit-scrollbar{display:none}
  .admin-bar .wrap{flex-wrap:wrap;padding-block:10px}

  /* ICONS ONLY FROM HERE DOWN.
     Twelve destinations written out is a scroll strip nobody reaches the end
     of, and "Sign out" spelled in full was wide enough that the button could
     not share the first row with the brand and the theme toggle — so it wrapped
     to a line of its own and the bar stood three rows tall on a phone.

     .brand-name is deliberately NOT hidden. Every other word here is a
     destination somebody can recognise by its icon; that one says which of two
     near-identical surfaces you are standing on, and the admin one reaches
     every shop on the platform. It is the last label that should go. */
  .admin-bar .label{display:none}
  .admin-bar .panel-nav a{flex:0 0 auto;padding:9px 13px;font-size:16px;line-height:1}
  .admin-bar .panel-nav a i{display:inline-block}

  /* The icon is the whole button now, so the pill should read as a square
     rather than a stretched capsule with nothing in it. */
  .admin-bar .btn.btn-sm{padding-inline:12px}
}

@media (max-width:400px){
  /* Last resort on the narrowest phones: the wordmark goes, the mark stays.
     Losing the icon as well would leave no way home from a sub-page. */
  .admin-bar .brand-name{display:none}
}

.panel-main{padding:34px 0 70px}
.page-head{display:flex;flex-wrap:wrap;align-items:flex-end;gap:18px;margin-bottom:26px}
.page-head h1{font-size:clamp(26px,3.2vw,36px)}
.page-head p{margin:8px 0 0;color:var(--ink-2);max-width:56ch;font-size:15px}
.page-head .actions{margin-left:auto;display:flex;gap:10px;flex-wrap:wrap}


/* =============================================================================
   APP SHELL — sidebar layout
   =============================================================================
   From the signed-in dashboard mockup: a fixed left rail of sections with the
   working area beside it.

   APPLIED TO THE MERCHANT PANEL, NOT THE PUBLIC SITE, and that is a decision
   rather than an oversight. The mockup shows this shell around a signed-in
   BUYER — but buyers here never sign in, by design, so there is no such
   session to hang it on. The panel is the platform's only authenticated
   surface with enough sections to need a rail, and it maps almost one to one:
   Dashboard, Products, Orders, Payments, Shop details, Account, Help.

   Below 1000px the rail is hidden entirely and the fixed bottom bar takes
   over — the same breakpoint pair as .panel-nav and .mega-nav. A drawer
   sidebar would be a third navigation pattern to maintain for no gain.
   ============================================================================= */
.app-shell{display:grid;grid-template-columns:264px minmax(0,1fr);min-height:100vh}

.side-nav{
  background:var(--surface);border-right:1px solid var(--line);
  padding:22px 18px;display:flex;flex-direction:column;gap:22px;
  position:sticky;top:0;height:100vh;overflow-y:auto;
}
.side-brand{display:flex;align-items:center;gap:11px;padding:0 6px 4px}
/* A 264px rail leaves about 200px for the wordmark. Without this a shop called
   anything longer than two short words breaks mid-word onto a second line and
   reads as a rendering fault rather than a name. Truncating is honest — the
   full name is on Shop details, and the tooltip carries it. */
.side-brand,.side-brand > span{min-width:0}
.side-brand .brand-name{
  display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}

.side-links{display:flex;flex-direction:column;gap:3px}
/* BUTTONS TOO, NOT ONLY LINKS. An entry that changes server state has to be a
   form — the shop chooser on /account is a POST per shop — and a button that
   looks nothing like the links around it reads as a different kind of thing.
   The reset is explicit because a <button> inherits none of the page's type. */
.side-links a,.side-links button{
  display:flex;align-items:center;gap:13px;width:100%;
  padding:11px 14px;border:0;border-radius:14px;
  background:none;font-family:inherit;
  font-size:14.5px;font-weight:600;color:var(--ink-2);text-align:left;
  transition:background .2s,color .2s;
}
.side-links button{cursor:pointer}
.side-links a .fa-solid,.side-links a .fa-regular,
.side-links button .fa-solid,.side-links button .fa-regular{width:20px;text-align:center;font-size:16px}
.side-links a:hover,.side-links button:hover{background:var(--surface-2);color:var(--ink)}
.side-links a[aria-current="page"]{background:var(--grad-brand);color:#fff;box-shadow:var(--shadow-glow)}
.side-links a[aria-current="page"] .fa-solid{color:#fff}
/* Trailing pill, for a section that needs flagging. */
.side-flag{
  margin-left:auto;padding:2px 8px;border-radius:999px;
  background:var(--mist);color:var(--brand-purple);
  font-size:10px;font-weight:800;letter-spacing:.03em;text-transform:uppercase;
}
.side-flag.is-warn{background:#FEF3C7;color:#92400E}
:root[data-theme="dark"] .side-flag.is-warn{background:rgba(217,119,6,.2);color:#FCD34D}

/* ── Shop switcher ─────────────────────────────────────────────────────────
   Only rendered for a merchant who owns more than one shop. A native <details>
   so the menu needs no JavaScript — see the note in layouts/panel.php. */
.shop-switch{margin:0 0 14px;position:relative}
.shop-switch > summary{
  display:flex;align-items:center;gap:10px;
  padding:10px 12px;border-radius:14px;
  border:1px solid var(--line);background:var(--surface-2);
  font-size:13.5px;font-weight:700;color:var(--ink);
  cursor:pointer;list-style:none;
}
/* The default disclosure triangle, removed in both engines. The caret in the
   markup replaces it, because the native one cannot be positioned. */
.shop-switch > summary::-webkit-details-marker{display:none}
.shop-switch > summary::marker{content:""}
.shop-switch > summary:hover{border-color:var(--brand-purple)}
.shop-switch > summary:focus-visible{outline:2px solid var(--brand-purple);outline-offset:2px}
.shop-switch-name{
  flex:1 1 auto;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.shop-switch-caret{flex:0 0 auto;font-size:11px;color:var(--ink-2);transition:transform .2s}
.shop-switch[open] > summary .shop-switch-caret{transform:rotate(180deg)}

.shop-switch-menu{
  display:flex;flex-direction:column;gap:2px;
  margin-top:6px;padding:6px;
  border:1px solid var(--line);border-radius:14px;background:var(--surface);
  box-shadow:var(--shadow-lift);
}
.shop-switch-form{margin:0}
/* One rule for the button, the link and the current-shop label, so three
   different elements cannot drift apart visually. */
.shop-switch-item{
  display:flex;align-items:center;gap:10px;width:100%;
  padding:9px 10px;border:0;border-radius:11px;
  background:none;font-family:inherit;font-size:13.5px;font-weight:600;
  color:var(--ink-2);text-align:left;cursor:pointer;
}
.shop-switch-item > span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.shop-switch-item .fa-solid{width:16px;text-align:center;font-size:13px}
button.shop-switch-item:hover,a.shop-switch-item:hover{background:var(--surface-2);color:var(--ink)}
.shop-switch-item.is-current{color:var(--ink);cursor:default}
.shop-switch-item.is-current .fa-check{color:var(--forest)}
.shop-switch-item.is-add{border-top:1px solid var(--line);border-radius:0 0 11px 11px;margin-top:4px;padding-top:12px}

/* The dashboard's copy, for widths where .side-nav — which carries the rail
   copy — is display:none. Hidden here so the two are never on screen at once;
   revealed in the 1000px block below, beside the rule that hides the rail, so
   the pair cannot fall out of step. */
.shop-switch-onmobile{display:none}

.side-block{
  border-radius:var(--r-lg);padding:18px;
  background:var(--surface-2);
}
.side-block h4{
  font-family:var(--font-display);font-weight:700;font-size:14px;
  margin:0 0 10px;color:var(--ink);
}
.side-block .side-links a{padding:8px 10px;font-size:13.5px;border-radius:11px}

/* The promotional card at the foot of the rail. */
.side-promo{
  border-radius:var(--r-lg);padding:20px;color:#fff;
  background:var(--grad-night);position:relative;overflow:hidden;
}
.side-promo h4{
  font-family:var(--font-display);font-weight:800;font-size:16px;
  margin:0 0 6px;color:#fff;letter-spacing:-.02em;
}
.side-promo p{font-size:12.5px;line-height:1.55;color:rgba(255,255,255,.78);margin:0 0 14px}
.side-promo::after{
  content:"";position:absolute;right:-50px;bottom:-50px;width:150px;height:150px;
  border-radius:50%;background:radial-gradient(closest-side,rgba(236,72,153,.35),transparent 70%);
  pointer-events:none;
}

.side-foot{margin-top:auto}
.side-user{
  display:flex;align-items:center;gap:11px;padding:12px;
  border-radius:14px;background:var(--surface-2);
}
.side-user .brand-mark{width:36px;height:36px;border-radius:11px;font-size:15px}
.side-user b{display:block;font-size:13.5px;font-weight:700;color:var(--ink);line-height:1.3}
/* Fourth instance — .brand-mark is a <span> in here, and this rule was flattening
   the merchant's avatar chip in the panel rail the same way. */
.side-user span:not(.brand-mark){display:block;font-size:11.5px;color:var(--ink-3)}

/* The working area beside the rail. */
.app-main{min-width:0;display:flex;flex-direction:column}
.app-bar{
  position:sticky;top:0;z-index:40;
  background:rgba(255,255,255,.88);backdrop-filter:blur(14px);
  border-bottom:1px solid var(--line);
}
:root[data-theme="dark"] .app-bar{background:rgba(22,22,43,.88)}
.app-bar-inner{display:flex;align-items:center;gap:16px;padding:14px 30px;min-height:70px;justify-content:flex-end}
.app-body{padding:28px 30px 70px;flex:1}

/* The bar's brand is a MOBILE-ONLY duplicate of the one in the rail. Above the
   breakpoint the rail is on screen carrying it already, so showing it twice
   would put the shop name beside itself. justify-content on .app-bar-inner is
   what keeps the buttons right-aligned once it is gone — there is no other flex
   child with margin-right:auto to hold them there. */
.app-bar .brand{display:none}
.app-bar .brand-name{font-size:17px}

@media (max-width:1000px){
  .app-shell{grid-template-columns:1fr}
  .side-nav{display:none}
  /* The rail just went away, so the dashboard's copy of the shop switcher
     takes over. These two rules belong together — separating them is how one
     gets changed and the other does not, leaving a merchant with two shops and
     no way to switch between them. */
  .shop-switch-onmobile{display:block;margin-bottom:18px}
  .app-bar-inner{padding-inline:16px}
  .app-body{padding:22px 16px 70px}

  /* min-width:0 so a long shop name ellipses instead of shoving Sign out off
     the right edge — a flex item will not shrink below its content otherwise. */
  .app-bar .brand{display:flex;margin-right:auto;min-width:0}
  .app-bar .brand-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
}

/* THE BAR IS FULL AT 360px. Measured: mark 42 + View shop 105 + toggle 36 +
   Sign out 95 + three 16px gaps = 326 of the 328 available inside the padding.
   That is why the shop name had nowhere to go, and why adding the brand without
   this rule would only have pushed Sign out off the edge.

   So the two buttons drop their text and become icons. Both carry an aria-label
   in panel.php, which is load-bearing rather than decorative: with the icon
   aria-hidden and the text display:none, an unlabelled control would have no
   accessible name at all — a screen reader announces "link", and that is worse
   than the layout problem this solves. */
/* =============================================================================
   RESPONSIVE TABLES
   =============================================================================
   Below 620px a six-column table stops being a table. Every row becomes a small
   stack, each cell labelled with the column heading it lost, and the heading row
   is dropped because it now has nothing to head.

   THIS IS WHY .table-scroll EXISTS AND WHY IT IS TURNED OFF HERE. Horizontal
   scrolling inside a card is a poor consolation prize: the merchant cannot see
   Price and Actions at the same time as the product name, and on a touch screen
   the inner scroller competes with the page's own vertical swipe. Restacking
   removes the overflow rather than making it swipeable.

   The label comes from data-label on each <td>. Cells that carry no label —
   the photo and the buttons — get no ::before at all, because "Photo: [image]"
   reads as a defect rather than a label.
   ============================================================================= */
@media (max-width:620px){
  .table-scroll{overflow-x:visible}

  table.data{min-width:0;display:block}
  table.data thead{display:none}
  table.data tbody,table.data tr,table.data td{display:block;width:100%}

  table.data tr{padding:14px 0;border-bottom:1px solid var(--line)}
  table.data tr:last-child{border-bottom:0}

  table.data td{
    padding:5px 0;border:0;
    display:flex;align-items:center;gap:12px;
    /* min-width:0 so a long product name ellipses inside the row instead of
       widening it and reintroducing the overflow this block exists to remove. */
    min-width:0;
  }
  table.data td::before{
    content:attr(data-label);
    flex:0 0 88px;
    font-size:10.5px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
    color:var(--ink-3);
  }
  /* No attribute, no label row. :not() rather than an empty string so a cell
     that simply was not annotated degrades to no label instead of an
     88px blank gutter. */
  table.data td:not([data-label])::before{content:none}
  table.data td.num{text-align:left}
}

@media (max-width:620px){
  .app-bar .btn .label{display:none}
  .app-bar .brand-name{font-size:16px}

  /* The storefront header carries the cart button, which cannot shrink — it is
     the reason somebody is on the page. So the marketplace link drops to its
     icon here rather than competing for the width. Scoped to .to-market and not
     to .panel-bar .btn, because that bar is shared with the auth, docs, legal
     and admin layouts whose buttons have no aria-label to fall back on: hiding
     their text would leave them announcing as nothing at all. */
  .to-market .label{display:none}

  /* ── The storefront masthead on a phone ───────────────────────────────────
     A shop called "NEW STORE" was wrapping onto two lines and doubling the
     height of the bar. The cause is not the font size: a flex item will not
     shrink below the width of its own content unless min-width is cleared, so
     .brand held its full natural width, the row overflowed, and the only give
     left was the text wrapping.

     min-width:0 lets it shrink; nowrap + ellipsis then decides what happens
     when it must. Truncating a long shop name is the right failure — the mark
     beside it and the page itself already say which shop this is, and a
     two-line masthead pushes the products below the fold on every load. */
  .panel-bar .wrap{gap:12px}
  .panel-bar .brand{min-width:0}
  .panel-bar .brand > span{min-width:0;overflow:hidden}
  .panel-bar .brand-name{
    display:block;font-size:16px;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  }
  /* 42 -> 36 buys six pixels back for the name at no legibility cost. */
  .panel-bar .brand-mark{width:36px;height:36px}
}

@media (max-width:440px){
  /* THE CART KEEPS ITS ICON AND ITS COUNT, and loses only the word — at this
     width the wordmark, three controls and a badge do not fit, and the cart is
     the one control that cannot be dropped.

     Scoped to .cart-btn, NOT to .panel-bar .btn: that bar is shared with the
     auth, docs, legal and admin layouts whose buttons have no aria-label to
     fall back on, so hiding their text would leave them announcing as nothing.
     The cart button carries an explicit aria-label in the storefront layout for
     exactly this reason. */
  .panel-bar .cart-btn .label{display:none}
}


/* =============================================================================
   CARDS, ALERTS, STATUS
   ============================================================================= */
.card{
  background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-lg);padding:24px;margin-bottom:20px;
  box-shadow:var(--shadow-rest);
}
.card h2{font-size:19px;margin-bottom:4px}
.card-sub{color:var(--ink-2);font-size:14px;margin:0 0 18px}

.alert{
  display:flex;gap:12px;align-items:flex-start;
  padding:15px 18px;border-radius:var(--r-md);margin-bottom:20px;font-size:14.5px;
}
.alert p{margin:0}
.alert-notice{background:#ECFDF5;color:#065F46;border:1px solid #A7F3D0}
.alert-warn{background:#FFFBEB;color:#92400E;border:1px solid #FDE68A}
.alert-error{background:#FEF2F2;color:#991B1B;border:1px solid #FECACA}

.status{
  display:inline-block;padding:4px 11px;border-radius:999px;
  font-size:11.5px;font-weight:800;letter-spacing:.02em;text-transform:capitalize;
}
.status-active{background:#D1FAE5;color:#047857}
.status-draft{background:var(--surface-2);color:var(--ink-2)}
.status-pending{background:#FEF3C7;color:#92400E}
.status-suspended{background:#FEE2E2;color:#991B1B}

/* ORDER STATUSES, BOTH AXES.
   None of these existed — every order badge rendered as bare text with no
   background, on the panel list, the detail page and the buyer's receipt. There
   are twice as many now that payment and fulfilment are separate, so they are
   grouped by meaning rather than invented one at a time.

   Money is amber while owed, green when settled, rose when reversed. Goods are
   grey while nothing has happened, violet once the merchant is working, blue in
   transit, green delivered, rose on anything that came back or never went. */
.status-unpaid{background:#FEF3C7;color:#92400E}
.status-awaiting{background:#FEF3C7;color:#92400E}
.status-paid{background:#D1FAE5;color:#047857}
.status-refunded{background:#FFE4E6;color:#9F1239}
.status-partially_refunded{background:#FFE4E6;color:#9F1239}

.status-placed{background:var(--surface-2);color:var(--ink-2)}
.status-preparing{background:#EDE9FE;color:#6D28D9}
.status-shipped{background:#DBEAFE;color:#1D4ED8}
.status-delivered{background:#D1FAE5;color:#047857}
.status-cancelled{background:#FEE2E2;color:#991B1B}
.status-rts{background:#FFE4E6;color:#9F1239}
.status-returned{background:#FFE4E6;color:#9F1239}

:root[data-theme="dark"] .status-unpaid,
:root[data-theme="dark"] .status-awaiting{background:rgba(217,119,6,.20);color:#FCD34D}
:root[data-theme="dark"] .status-paid,
:root[data-theme="dark"] .status-delivered{background:rgba(5,150,105,.20);color:#6EE7B7}
:root[data-theme="dark"] .status-refunded,
:root[data-theme="dark"] .status-partially_refunded,
:root[data-theme="dark"] .status-cancelled,
:root[data-theme="dark"] .status-rts,
:root[data-theme="dark"] .status-returned{background:rgba(225,29,72,.18);color:#FDA4AF}
:root[data-theme="dark"] .status-preparing{background:rgba(139,92,246,.20);color:#C4B5FD}
:root[data-theme="dark"] .status-shipped{background:rgba(59,130,246,.20);color:#93C5FD}

.stat-row{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:26px}
.stat{
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:20px;box-shadow:var(--shadow-rest);transition:transform .18s var(--ease),box-shadow .2s;
}
a.stat:hover{transform:translateY(-3px);box-shadow:var(--shadow-lift)}
.stat b{
  display:block;font-family:var(--font-display);font-weight:800;font-size:30px;
  letter-spacing:-.03em;color:var(--ink);line-height:1.1;
}
.stat span{display:block;font-size:13px;color:var(--ink-2);margin-top:4px}

.disclaimer{
  background:#FFFBEB;border:1px solid #FDE68A;color:#92400E;
  border-radius:var(--r-md);padding:15px 18px;font-size:13.5px;line-height:1.6;
}
.fill{background:#FEF08A;color:#713F12;font-weight:800;padding:1px 7px;border-radius:5px}


/* =============================================================================
   HERO — public home
   ============================================================================= */
.hero{
  position:relative;overflow:hidden;
  background:var(--grad-soft);
  border:1px solid var(--line);
  border-radius:var(--r-xl);
  padding:52px 56px 0;
  margin:26px 0 22px;
}
.hero-inner{display:flex;align-items:center;gap:40px}
.hero-copy{flex:1 1 0;min-width:0;position:relative;z-index:1}
.hero-title{
  font-size:clamp(30px,4.6vw,52px);
  line-height:1.08;letter-spacing:-.035em;margin:0 0 18px;
}
.hero-sub{
  font-size:16.5px;line-height:1.62;color:var(--ink-2);
  max-width:46ch;margin:0 0 28px;
}
.hero-cta{display:flex;gap:14px;flex-wrap:wrap;margin-bottom:44px}

.hero-art{
  flex:0 0 auto;width:min(46%,470px);position:relative;z-index:1;
  display:grid;place-items:center;
  /* THE ONLY THING KEEPING THE ORB OFF THE TRUST STRIP. .hero has
     padding-bottom:0 by design — the strip is meant to sit flush against the
     bottom edge of the card, which is what makes it read as part of the hero
     rather than a band underneath it. So the gap has to come from the art,
     and without this the circle rests directly on the strip's top border.
     The narrow breakpoints set their own smaller values further down. */
  margin-bottom:44px;
}
/* Placeholder mark until artwork exists. Deliberately not an <img> pointing at
   a file that is not there — a broken image icon is worse than a shape. */
.hero-orb{
  width:100%;aspect-ratio:1;max-width:400px;border-radius:50%;
  background:
    radial-gradient(closest-side, rgba(255,255,255,.9), rgba(255,255,255,0) 72%),
    var(--grad-brand);
  display:grid;place-items:center;color:#fff;font-size:clamp(60px,9vw,120px);
  filter:drop-shadow(0 30px 50px rgba(124,58,237,.35));
}

/* Trust strip, inside the hero card and flush to its bottom edge. */
.trust-strip{
  position:relative;z-index:1;
  display:grid;grid-template-columns:repeat(4,1fr);
  border-top:1px solid rgba(15,23,42,.08);
  margin:0 -56px;padding:0 56px;
}
.trust-item{
  display:flex;align-items:center;gap:13px;padding:20px 22px 20px 0;
}
.trust-item + .trust-item{border-left:1px solid rgba(15,23,42,.08);padding-left:26px}
.trust-ico{
  width:42px;height:42px;border-radius:12px;flex:0 0 auto;
  display:flex;align-items:center;justify-content:center;font-size:21px;
  background:rgba(255,255,255,.75);color:var(--brand-purple);
}
.trust-item b{display:block;font-size:14px;font-weight:700;color:var(--ink);line-height:1.3}

/* :not(.trust-ico) IS THE WHOLE FIX FOR THE ICON CHIP.
   ===========================================================================
   .trust-ico is a <span> inside .trust-item, so this selector matched it. At
   (0,1,1) it outranks .trust-ico at (0,1,0), and it is later in the file as
   well — so the chip was display:block at 12.5px no matter what .trust-ico
   said. Every centring and sizing change made up there was overridden before
   it ever reached a browser, which is why the glyph sat in the top-left corner
   of the square at the size of the caption beside it.

   Measured on the deployed page: the <i> reported display:block, font-size
   13.75px, and a box 42px wide by one line tall pinned to the chip's top-left
   corner — the chip's own 42x42 with none of its own type or layout.

   The lesson is the selector, not this component: `.parent span` reaches every
   span in the subtree, including ones that are structure rather than text. */
.trust-item span:not(.trust-ico){display:block;font-size:12.5px;color:var(--ink-2)}

/* ICON CHIPS — give the glyph a square em box of its own.
   ===========================================================================
   The chips centre with flex, and flex centres whatever box the <i> presents.
   Left to itself that box is neither square nor predictable: Font Awesome
   renders through ::before on an inline-block <i>, so the width is the glyph's
   own advance — different for a shield and a banknote — and the height is a
   line box built from the font's ascent and descent, which is taller than the
   em and hangs off a baseline. Centring that put a narrow glyph left of centre
   and every glyph above it.

   width/height 1em with line-height 1 replaces it with a square the size of the
   type, so what flex centres is a box that means something. text-align handles
   the advance-width difference inside it.

   What is left is the ink's placement inside its own em square, which belongs
   to the font and no stylesheet can reach. Do not chase that with margin
   nudges: a value tuned to fa-shield-halved is wrong for every other icon on
   the page, and the category glyphs come from ms_categories.icon, so the set is
   not fixed at build time. */
.trust-ico > .fa-solid, .trust-ico > .fa-regular,
.cat-ico   > .fa-solid, .cat-ico   > .fa-regular{
  display:block;height:1em;line-height:1;
}

/* NO width HERE, DELIBERATELY. It was width:1em, which is wrong for most of the
   set: Font Awesome draws on a 512-unit em but a large share of its icons are
   640 units wide — fa-link and fa-couch among them — so their advance is 1.25em.
   Constraining the box to 1em made those glyphs overflow their own line box,
   and text-align:center does not centre a line that overflows; the excess goes
   one way. The square icons looked right and the wide ones sat off to the side,
   which is exactly the set that was still reported crooked.

   Letting the box size to the glyph means flex centres the advance itself, and
   the advance is what the ink is drawn inside. Correct at every width, with no
   list of exceptions to maintain. */


/* =============================================================================
   STOREFRONT PROMO BAND
   =============================================================================
   The shop-page equivalent of .hero — smaller, because a storefront's job is to
   show stock, not to sell the platform. Kept because shop/store.php renders it;
   dropping the class in the retheme would have left that page with an unstyled
   block and nothing pointing at why.
   ============================================================================= */
.hero-promo{
  position:relative;overflow:hidden;
  background:var(--grad-soft);
  border:1px solid var(--line);
  border-radius:var(--r-lg);
  padding:26px 28px;
  margin:20px 0 24px;
  display:flex;align-items:center;gap:22px;
}
.hero-promo-text{flex:1;min-width:0;position:relative;z-index:1}
.hero-promo h2{font-size:clamp(20px,3.4vw,27px);letter-spacing:-.03em;margin:0}
.hero-promo p{margin:7px 0 16px;font-size:14.5px;color:var(--ink-2);max-width:44ch}
.hero-promo .btn{padding:12px 22px;font-size:14px}
.hero-promo-art{
  position:relative;z-index:1;flex:0 0 auto;
  width:104px;height:104px;border-radius:26px;overflow:hidden;
  background:var(--grad-brand);
  display:grid;place-items:center;color:#fff;font-size:38px;
  box-shadow:var(--shadow-glow);
}
.hero-promo-art img{width:100%;height:100%;object-fit:cover}


/* =============================================================================
   STOREFRONT CART CONTROL
   =============================================================================
   These three were referenced by layouts/storefront.php and never defined by
   the old stylesheet either — the count rendered as bare text beside the
   button. Added here rather than left as a second inherited gap.
   ============================================================================= */
.cart-btn{position:relative}
.cart-count{
  min-width:21px;height:21px;padding:0 6px;border-radius:999px;
  background:#fff;color:var(--brand-pink);
  font-family:var(--font-mono);font-size:11.5px;font-weight:800;
  display:inline-flex;align-items:center;justify-content:center;
  margin-left:2px;
}
.ship-note{font-size:12.5px;color:var(--ink-3);line-height:1.5}


/* =============================================================================
   CATEGORY GRID
   ============================================================================= */
/* AUTO-FILL, NOT A FIXED COLUMN COUNT.
   ===========================================================================
   This was repeat(9,1fr), which assumes nine categories have stock. M_discovery
   only returns categories that have something in them, and on a young
   marketplace that is one or two — so the row divided the full page width by
   nine anyway, and the two real cards came out as a pair of slivers hard
   against the left gutter with seven columns of nothing beside them. That is
   the "margin" problem: not a margin at all, but a grid sized for a catalogue
   that does not exist yet.

   auto-fill sizes each track from the card, not from the page, so two
   categories and twenty both lay out correctly. minmax keeps a floor, and 1fr
   lets the last row's tracks share what is left rather than leaving a ragged
   gap. */
.cat-grid{
  display:grid;grid-template-columns:repeat(auto-fill,minmax(124px,1fr));
  gap:14px;margin-bottom:34px;
}
/* A GRID OF TWO OR THREE, FILLING THE ROW.
   .cat-grid uses auto-FILL, which lays down as many 124px tracks as the row
   holds and leaves the surplus empty — right for a category list of unknown
   length, wrong for a fixed pair, which sat as two narrow cards against a lot
   of nothing. auto-FIT collapses the empty tracks, so the items stretch to take
   the whole container, and the wider floor keeps them from becoming letterbox
   slabs on a desktop. Below ~460px they stack, each full width. */
.cat-grid.is-pair{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}
.cat-grid.is-pair .cat-card{padding:24px 18px}
.cat-card{
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:20px 12px;text-align:center;
  box-shadow:var(--shadow-rest);
  transition:transform .18s var(--ease),box-shadow .2s,border-color .2s;
}
.cat-card:hover{transform:translateY(-4px);box-shadow:var(--shadow-lift);border-color:var(--sage)}
.cat-ico{
  width:54px;height:54px;border-radius:16px;margin:0 auto 12px;
  display:flex;align-items:center;justify-content:center;font-size:26px;
}
.cat-card b{display:block;font-size:13.5px;font-weight:700;color:var(--ink);line-height:1.3}
/* Same swallow as .trust-item span above, same exclusion. .cat-ico is a <span>
   inside .cat-card, so this rule was setting it to display:block at 12px and
   winning on both specificity and order. */
.cat-card span:not(.cat-ico){display:block;font-size:12px;color:var(--ink-3);margin-top:3px}

/* Pastel tints, cycled across the row so no two neighbours match. */
.tint-1{background:#FCE7F3;color:#DB2777}
.tint-2{background:#EDE9FE;color:#7C3AED}
.tint-3{background:#DBEAFE;color:#2563EB}
.tint-4{background:#CFFAFE;color:#0891B2}
.tint-5{background:#D1FAE5;color:#059669}
.tint-6{background:#FEF3C7;color:#D97706}
.tint-7{background:#FFE4E6;color:#E11D48}
.tint-8{background:#E0E7FF;color:#4F46E5}

/* Two tracks wide. This card carries a heading, a line of description and a
   40px arrow side by side, which needs roughly twice the width of a card
   holding one centred word — in a single track "All categories" broke across
   three lines and "See everything" was clipped mid-word by the arrow. */
.cat-all{
  grid-column:span 2;
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  text-align:left;padding:20px;
}
.cat-all .fa-solid{
  width:40px;height:40px;border-radius:999px;flex:0 0 auto;
  background:var(--grad-brand);color:#fff;display:grid;place-items:center;
}


/* =============================================================================
   SECTION HEADINGS AND RAILS
   ============================================================================= */
.section-head{
  display:flex;align-items:center;gap:12px;margin:0 0 16px;
}
.section-head h2{font-size:21px;letter-spacing:-.025em}
.section-head a{
  margin-left:auto;font-size:13.5px;font-weight:700;color:var(--brand-purple);
  display:inline-flex;align-items:center;gap:7px;
}
.section-head a:hover{color:var(--brand-pink)}

/* Home layout: a product rail with a promotional panel beside it. */
.rail-split{display:grid;grid-template-columns:minmax(0,1fr) 332px;gap:22px;margin-bottom:34px;align-items:start}
/* No rail to sit beside — the sell panel takes the full width instead of a
   332px column with an empty 1fr to its left. */
.rail-split.rail-solo{grid-template-columns:1fr}


/* =============================================================================
   PRODUCT CARDS
   ============================================================================= */
/* auto-fill, for the same reason .cat-grid uses it: repeat(6,1fr) divides the
   row by six whether or not there are six products, so a marketplace with one
   listing rendered one card a sixth of the page wide with five empty columns
   beside it — and next to the 332px sell panel that reads as a stray banner
   rather than a sidebar. Tracks now size from the card. */
.p-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:16px}
.p-card{
  position:relative;
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:16px;box-shadow:var(--shadow-rest);
  display:flex;flex-direction:column;
  transition:transform .18s var(--ease),box-shadow .2s,border-color .2s;
}
.p-card:hover{transform:translateY(-5px);box-shadow:var(--shadow-lift);border-color:var(--sage)}
/* Full-width square, not the mockup's 60px chip.
   The mockup's product images are software logos, which read fine at postage
   stamp size. These are physical goods — a sack of rice at 60px is a smudge,
   and the photograph is most of what decides whether somebody clicks. */
.p-art{
  width:100%;aspect-ratio:1;border-radius:16px;overflow:hidden;margin-bottom:14px;
  background:var(--surface-2);display:grid;place-items:center;
  color:var(--ink-3);font-size:30px;
}
.p-art img{width:100%;height:100%;object-fit:cover}
/* The chip form survives for cards that carry a mark rather than a photo —
   shop tiles, where the thing being shown is an identity, not a product. */
.p-art.is-chip{width:56px;height:56px;aspect-ratio:auto;font-size:21px}
.p-badge{
  position:absolute;top:14px;right:14px;
  padding:4px 10px;border-radius:999px;
  font-size:10.5px;font-weight:800;letter-spacing:.02em;
  background:#FCE7F3;color:#DB2777;
}
.p-badge.is-popular{background:#EDE9FE;color:#7C3AED}
.p-badge.is-trending{background:#FEF3C7;color:#B45309}
.p-badge.is-new{background:#D1FAE5;color:#047857}
.p-name{
  font-family:var(--font-display);font-weight:700;font-size:15px;
  color:var(--ink);line-height:1.3;letter-spacing:-.015em;margin:0 0 3px;
}
.p-meta{font-size:12.5px;color:var(--ink-3);margin:0 0 10px}
.p-foot{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  margin-top:auto;padding-top:12px;
}
.p-price{
  font-family:var(--font-display);font-weight:800;font-size:17px;
  letter-spacing:-.025em;color:var(--ink);font-variant-numeric:tabular-nums;
}
.p-was{font-size:12.5px;color:var(--ink-3);text-decoration:line-through;margin-left:6px;font-weight:600}
.p-add{
  width:38px;height:38px;border-radius:12px;flex:0 0 auto;
  background:var(--surface-2);color:var(--ink-2);
  display:grid;place-items:center;transition:background .2s,color .2s,transform .16s var(--ease);
}
.p-add:hover{background:var(--grad-brand);color:#fff;transform:scale(1.06)}
.p-fav{
  position:absolute;top:14px;left:14px;color:var(--ink-3);
  transition:color .2s;font-size:15px;
}
.p-fav:hover,.p-fav[aria-pressed="true"]{color:var(--brand-pink)}
.p-empty{grid-column:1/-1;text-align:center;padding:40px;color:var(--ink-2)}

/* Rating line. */
.stars{display:flex;align-items:center;gap:5px;font-size:12.5px;color:var(--ink-2);margin-bottom:10px}
.stars .fa-solid{color:var(--star);font-size:11.5px}


/* =============================================================================
   PROMOTIONAL PANELS
   ============================================================================= */
.promo-panel{
  border-radius:var(--r-lg);padding:26px;color:#fff;position:relative;overflow:hidden;
  background:var(--grad-violet);
}
.promo-panel.is-night{background:var(--grad-night)}
.promo-panel h3{
  font-family:var(--font-display);font-weight:800;font-size:22px;
  letter-spacing:-.03em;line-height:1.2;margin:0 0 10px;color:#fff;
}
.promo-panel p{font-size:14px;line-height:1.6;color:rgba(255,255,255,.82);margin:0 0 20px}
.promo-panel .btn{box-shadow:none}
.promo-panel::after{
  content:"";position:absolute;right:-70px;bottom:-70px;
  width:210px;height:210px;border-radius:50%;
  background:radial-gradient(closest-side, rgba(255,255,255,.20), transparent 70%);
  pointer-events:none;
}
.promo-tag{
  display:inline-flex;align-items:center;gap:7px;
  font-size:11.5px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;
  color:var(--brand-cyan);margin-bottom:10px;
}


/* =============================================================================
   FEATURE STRIP
   ============================================================================= */
.feature-strip{
  display:grid;grid-template-columns:repeat(4,1fr);gap:0;
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  box-shadow:var(--shadow-rest);overflow:hidden;margin-bottom:34px;
}
.feature-item{display:flex;align-items:center;gap:14px;padding:22px 24px}
.feature-item + .feature-item{border-left:1px solid var(--line)}
.feature-ico{
  width:44px;height:44px;border-radius:13px;flex:0 0 auto;
  display:grid;place-items:center;font-size:17px;
}
.feature-item b{display:block;font-size:14.5px;font-weight:700;color:var(--ink);line-height:1.3}
/* Third instance of the same swallow — .feature-ico is a <span> in here. */
.feature-item span:not(.feature-ico){display:block;font-size:12.5px;color:var(--ink-2);margin-top:2px}


/* =============================================================================
   DISCOVERY — search bar, chips, legacy category strip
   ============================================================================= */
.search-bar{
  position:relative;display:flex;align-items:center;
  background:var(--surface);border:1.5px solid var(--line);
  border-radius:999px;padding:4px 6px 4px 18px;margin-bottom:20px;
  transition:border-color .2s,box-shadow .2s;
}
.search-bar:focus-within{border-color:var(--brand-violet);box-shadow:0 0 0 4px rgba(139,92,246,.15)}
.search-ico{color:var(--ink-3);flex:0 0 auto}
.search-bar input{
  flex:1;border:0;background:none;font:inherit;font-size:16px;
  padding:12px;color:var(--ink);min-width:0;
}
.search-bar input:focus{outline:none}
.search-clear{color:var(--ink-3);padding:8px}

.cat-strip{display:flex;gap:12px;overflow-x:auto;padding-bottom:6px;scrollbar-width:none}
.cat-strip::-webkit-scrollbar{display:none}
.cat-chip{
  flex:0 0 auto;width:100px;text-align:center;
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r-md);
  padding:14px 8px;transition:transform .18s var(--ease),border-color .2s;
}
.cat-chip:hover{transform:translateY(-3px);border-color:var(--sage)}
.cat-ico,.cat-chip .cat-ico{margin-inline:auto}
.cat-name{display:block;font-size:12.5px;font-weight:700;color:var(--ink);margin-top:8px;line-height:1.25}

.empty-state{text-align:center;padding:54px 20px;color:var(--ink-2)}

/* DIRECT CHILD, NOT DESCENDANT.
   This was `.empty-state .fa-solid`, which also matched the icon inside the
   call-to-action button sitting in the same block — so a 15px plus sign in a
   pill rendered at 42px, display:block, with 14px of margin under it, in the
   decorative grey. It stretched the button into a lozenge and turned its icon
   the wrong colour, on every empty state in the application.
   The `>` confines it to the illustration, which is the only thing it was ever
   describing. */
.empty-state > .fa-solid,
.empty-state > .fa-regular{
  display:grid;place-items:center;
  width:64px;height:64px;margin:0 auto 16px;
  border-radius:50%;
  background:var(--mist);
  color:var(--brand-purple);
  font-size:26px;
}
/* --brand-purple on the dark chip is too close in value to read as an icon. */
:root[data-theme="dark"] .empty-state > .fa-solid,
:root[data-theme="dark"] .empty-state > .fa-regular{color:#C4B5FD}

.empty-state h3{font-size:20px;color:var(--ink);margin-bottom:6px}
.empty-state p{font-size:14.5px;margin:0 0 20px;max-width:44ch;margin-inline:auto}


/* =============================================================================
   SHELF — retained for the storefront, restyled
   =============================================================================
   The wooden plank of the previous theme is gone with it. These classes stay
   because storefront views reference them; they are now a plain surface.
   ============================================================================= */
.shelf{margin-bottom:30px}
.shelf-label{display:flex;align-items:center;gap:11px;margin-bottom:14px}
.shelf-title{font-family:var(--font-display);font-weight:800;font-size:21px;letter-spacing:-.025em;color:var(--ink)}
.shelf-theme{font-family:var(--font-display);font-weight:600;font-size:18px;color:var(--ink-2)}
.shelf-meta{margin-left:auto;font-family:var(--font-mono);font-size:12px;color:var(--ink-3)}
.shelf-age{font-size:12px;color:var(--ink-3)}
.shelf-row{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}
.plank{height:10px;border-radius:0 0 var(--r-md) var(--r-md);background:var(--line-2)}


/* =============================================================================
   PRODUCT PAGE
   ============================================================================= */
.product{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:34px;margin-bottom:30px}
.product-art{
  border-radius:var(--r-lg);overflow:hidden;background:var(--surface-2);
  aspect-ratio:1;display:grid;place-items:center;color:var(--ink-3);font-size:46px;
}
/* Which photo you are looking at, on the product thumbnail strip. gallery.js
   sets aria-current on the selected button — the attribute IS the state, so
   there is no class to fall out of step with it, and a screen reader gets the
   same answer the ring gives everyone else.

   OUTLINE, NOT BORDER. A border would change the button's box and shift the
   whole strip by 2px every time a different thumbnail was chosen. The offset
   sits it just inside the rounded corner. */
button.product-art{border:0;background:var(--surface-2)}
button.product-art[aria-current="true"]{
  outline:2.5px solid var(--brand-purple);outline-offset:-2.5px;
}
/* Never removed — the strip is keyboard-operable and these are real buttons. */
button.product-art:focus-visible{outline:2.5px solid var(--brand-purple);outline-offset:2px}
.price-row{display:flex;align-items:baseline;gap:12px;margin:14px 0}
.price{font-family:var(--font-display);font-weight:800;font-size:30px;letter-spacing:-.03em;color:var(--ink)}
.tag{
  display:inline-block;padding:4px 11px;border-radius:999px;
  font-size:11.5px;font-weight:800;background:var(--mist);color:var(--brand-purple);
}
.tag-new{background:#D1FAE5;color:#047857}
.tag-out{background:#FEE2E2;color:#991B1B}
.qty{display:inline-flex;align-items:center;gap:0;border:1.5px solid var(--line);border-radius:999px;overflow:hidden}
.qty button{width:42px;height:42px;display:grid;place-items:center;color:var(--ink-2);transition:background .2s}
.qty button:hover{background:var(--surface-2)}
.qty span{min-width:44px;text-align:center;font-weight:700}
.add{flex:1}


/* =============================================================================
   CART DRAWER
   ============================================================================= */
.scrim{
  position:fixed;inset:0;background:rgba(15,23,42,.45);backdrop-filter:blur(3px);
  opacity:0;pointer-events:none;transition:opacity .25s;z-index:60;
}
/* .open, NOT .on. THIS IS THE NAME cart.js TOGGLES.
   ===========================================================================
   The retheme brought these rules over from the mockup, which uses .on as its
   universal "this thing is showing" class. cart.js does not: it has always
   added "open" to the drawer and the scrim and "show" to the toast. Nothing
   errored — the class went on, no rule matched it, and the drawer stayed
   parked at translateX(102%) with the scrim at opacity 0. Clicking Cart did
   exactly what it was told and produced no visible effect.

   The JavaScript is the older side and wishlist.js shares its vocabulary, so
   the stylesheet moves. The one genuine .on in this codebase is the bottom-nav
   cart badge, which cart.js really does toggle with that name — see .bn-badge.
   Do not "tidy" these three back to .on. */
.scrim.open{opacity:1;pointer-events:auto}
.drawer{
  position:fixed;top:0;right:0;bottom:0;width:min(420px,92vw);z-index:70;
  background:var(--surface);display:flex;flex-direction:column;
  transform:translateX(102%);transition:transform .3s var(--ease);
  box-shadow:-20px 0 50px -20px rgba(15,23,42,.3);
}
.drawer.open{transform:none}
.drawer-head{display:flex;align-items:center;gap:12px;padding:22px;border-bottom:1px solid var(--line)}
.drawer-head h2{font-size:19px;margin-right:auto}
.drawer-body{flex:1;overflow-y:auto;padding:18px 22px}
.drawer-foot{padding:20px 22px;border-top:1px solid var(--line);background:var(--surface-2)}
.sum{display:flex;justify-content:space-between;font-weight:800;font-size:16px;margin-bottom:14px}
.line{display:flex;gap:13px;padding:13px 0;border-bottom:1px solid var(--line-2)}
.line-art{width:56px;height:56px;border-radius:13px;background:var(--surface-2);flex:0 0 auto;overflow:hidden}
.line-main{flex:1;min-width:0}
.line-price{font-weight:700;font-size:14px}
.line-remove{font-size:12.5px;color:var(--danger);font-weight:700;margin-top:5px}
/* Sibling of .line-remove for a non-destructive inline action — "Make main" on
   a product photo. Same weight and size so the two read as a pair, brand colour
   rather than danger red so the destructive one stays the one that looks
   destructive. */
.line-action{font-size:12.5px;color:var(--brand-purple);font-weight:700;margin-top:5px}

/* ── The grand total ───────────────────────────────────────────────────────
   .sum.grand was used in five templates — checkout, the confirmation, the
   buyer's order page, the seller's order page and the affiliate panel — and
   had NO RULE AT ALL, so the figure a person is actually being asked to pay
   rendered identically to the delivery charge above it. */
.sum.grand{
  margin-top:14px;padding-top:14px;border-top:1px solid var(--line);
  font-size:19px;
}
.sum.grand .mono{font-size:20px}

/* ── What was ordered ──────────────────────────────────────────────────────
   Rows, not a table. See partials/order_items.php for why: the restacked
   mobile <td> is display:flex, which turns a cell's <br>-separated lines into
   side-by-side columns and clips them. Rows have nothing to restack. */
.order-lines .line:first-child{padding-top:0}
.order-lines .line:last-child{border-bottom:0}
.order-lines .line-art{
  display:flex;align-items:center;justify-content:center;
  color:var(--ink-3);font-size:20px;
}
.order-lines .line-art img{width:100%;height:100%;object-fit:cover;display:block}

/* The name has to be able to wrap. .line-main carries min-width:0 already,
   which is what lets a long product name break instead of widening the row
   and pushing the price off the edge. */
.order-lines .line-main{display:flex;flex-direction:column;gap:3px}
.order-lines .line-name{font-size:15px;line-height:1.35;color:var(--ink)}
.order-lines .line-option{font-size:13px;color:var(--ink-2);line-height:1.4}
.order-lines .line-sku{font-size:11.5px;color:var(--ink-3)}
.order-lines .line-qty{font-size:13px;color:var(--ink-2);margin-top:2px}
.order-lines .line-price{flex:0 0 auto;align-self:flex-start;font-size:15px;white-space:nowrap}

/* ONE ITEM GETS A BIGGER PICTURE. A single-product order is the common case,
   and the photo is the fastest way for a buyer to confirm they bought the
   right thing. Five items are a list to scan, and five large images turn that
   into scrolling. */
.order-lines.is-solo .line{align-items:center;gap:16px}
.order-lines.is-solo .line-art{width:104px;height:104px;border-radius:16px;font-size:30px}
.order-lines.is-solo .line-name{font-size:16px}

.order-sums{margin-top:18px}

@media (max-width:420px){
  /* The price drops under the text rather than fighting it for width. At this
     size a nowrap peso figure beside a wrapping product name is what pushes a
     row wider than the screen. */
  .order-lines .line{flex-wrap:wrap}
  .order-lines .line-price{width:100%;padding-left:69px;margin-top:2px}
  .order-lines.is-solo .line-price{padding-left:120px}
}

.line-action:hover,.line-remove:hover{text-decoration:underline}
/* Never removed — these are real buttons in a form. */
.line-action:focus-visible,.line-remove:focus-visible{
  outline:2.5px solid var(--brand-violet);outline-offset:2px;border-radius:4px;
}


/* =============================================================================
   TOAST
   ============================================================================= */
.toast{
  position:fixed;left:50%;bottom:26px;transform:translate(-50%,26px);
  display:flex;align-items:center;gap:10px;
  background:var(--ink);color:#fff;
  padding:13px 22px;border-radius:999px;font-size:14px;font-weight:600;
  opacity:0;pointer-events:none;transition:opacity .25s,transform .25s var(--ease);
  z-index:80;box-shadow:var(--shadow-lift);
}
.toast.show{opacity:1;transform:translate(-50%,0)}


/* =============================================================================
   TABLES
   ============================================================================= */
.table-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}
/* min-width is what forces the sideways scroll. It is correct on a desktop —
   six columns need the room — and it is the wrong shape entirely on a phone,
   where it puts a 560px table in a 430px card and slices the last two columns
   off. The narrow-screen override at the bottom of this file drops it and
   restacks the rows; see "RESPONSIVE TABLES". */
table.data{width:100%;border-collapse:collapse;font-size:14.5px;min-width:560px}
table.data th{
  text-align:left;font-size:11.5px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--ink-3);font-weight:800;padding:0 14px 11px;border-bottom:1px solid var(--line);
  white-space:nowrap;
}
table.data td{padding:14px;border-bottom:1px solid var(--line-2);vertical-align:middle}
table.data tr:last-child td{border-bottom:0}


/* =============================================================================
   AUTH
   ============================================================================= */
.auth-shell{
  min-height:calc(100vh - 70px);display:grid;place-items:center;padding:34px 20px;
  background:var(--grad-soft);
}
.auth-card{
  width:100%;max-width:460px;
  background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-xl);padding:38px 34px;
  box-shadow:0 30px 60px -34px rgba(76,29,149,.35);
}
.auth-card h1{font-size:30px;margin-bottom:6px}
.auth-card .lede{color:var(--ink-2);font-size:15px;margin:0 0 26px}
.auth-foot{
  margin-top:24px;padding-top:20px;border-top:1px solid var(--line);
  font-size:14px;color:var(--ink-2);text-align:center;
}
.auth-foot a{color:var(--brand-purple);font-weight:700}


/* =============================================================================
   FOOTER
   ============================================================================= */
footer.site{background:var(--night);color:#94A3B8;padding:52px 0 30px;margin-top:50px}
/* Five columns, with the first wider because it carries the blurb paragraph
   while the other four carry short link lists. Was repeat(4,1fr); the AIConnect
   column made a fifth, and an equal split cramped the blurb to about 210px.
   The 2-column and 1-column overrides further down still apply unchanged. */
.foot-grid{display:grid;grid-template-columns:1.4fr repeat(4,1fr);gap:34px;margin-bottom:34px}
.foot-grid h4{
  font-family:var(--font-display);font-weight:700;font-size:15px;
  margin:0 0 14px;color:#fff;letter-spacing:-.015em;
}
.foot-grid ul{list-style:none;margin:0;padding:0}
.foot-grid li{margin-bottom:9px;font-size:14px}
.foot-grid a:hover{color:#fff}
.foot-blurb{font-size:14px;line-height:1.65;color:#94A3B8}
.colophon{
  display:flex;flex-wrap:wrap;gap:18px;
  padding-top:22px;border-top:1px solid rgba(255,255,255,.09);
  font-size:13px;color:#64748B;
}
.colophon a:hover{color:#fff}


/* =============================================================================
   MOBILE BOTTOM NAVIGATION
   =============================================================================
   Labels are always present. Icon-only tab bars test badly for recognition on
   everything except Home and Cart.
   ============================================================================= */
:root{ --bottom-nav-h: 60px; }

/* ── Install prompt ─────────────────────────────────────────────────────────
   The bar that offers "Add to Home Screen". Created by JavaScript, never
   rendered by PHP: whether an install is possible is a fact about the visitor's
   browser and its history with this site, and these pages are stored whole in
   ms_cache_entries and served byte-identical to everybody. Anything
   per-visitor here would be baked in and handed to the next stranger — the same
   rule the signed-in hint follows.

   Positioned like .toast, above the bottom bar where there is one, because a
   fixed element at bottom:0 would sit underneath it. */
.install-bar{
  position:fixed;left:14px;right:14px;bottom:18px;z-index:60;
  display:flex;align-items:center;gap:12px;
  max-width:520px;margin-inline:auto;
  padding:12px 14px;border-radius:var(--r-lg);
  background:var(--surface);border:1px solid var(--line);
  box-shadow:var(--shadow-lift);
}
.install-bar img{width:40px;height:40px;border-radius:11px;flex:0 0 auto}
.install-bar .ib-text{flex:1;min-width:0;font-size:13.5px;line-height:1.35}
.install-bar .ib-text b{display:block;font-size:14.5px;color:var(--ink)}
.install-bar .ib-text span{color:var(--ink-2)}
.install-bar .ib-close{
  flex:0 0 auto;width:30px;height:30px;border-radius:50%;
  display:grid;place-items:center;color:var(--ink-3);font-size:13px;
}
.install-bar .ib-close:hover{background:var(--mist);color:var(--ink)}

.bottom-nav{
  position:fixed;left:0;right:0;bottom:0;z-index:45;
  display:none;
  background:rgba(255,255,255,.96);backdrop-filter:blur(14px);
  border-top:1px solid var(--line);
  padding-bottom:env(safe-area-inset-bottom, 0px);
}
.bottom-nav ul{
  list-style:none;margin:0;padding:0;
  display:grid;grid-auto-flow:column;grid-auto-columns:1fr;
}
.bottom-nav li{min-width:0}
.bottom-nav a,
.bottom-nav button{
  position:relative;width:100%;min-height:var(--bottom-nav-h);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;
  padding:8px 2px 7px;color:var(--ink-3);
  font-size:10.5px;font-weight:700;line-height:1.1;text-align:center;
  transition:color .18s;
}
.bottom-nav .fa-solid,.bottom-nav .fa-regular{font-size:20px;line-height:1}
.bottom-nav .bn-label{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.bottom-nav a:hover,.bottom-nav button:hover{color:var(--brand-purple)}
.bottom-nav a[aria-current="page"]{color:var(--brand-purple)}
.bottom-nav a[aria-current="page"]::before{
  content:"";position:absolute;top:0;left:50%;transform:translateX(-50%);
  width:30px;height:3px;border-radius:0 0 4px 4px;background:var(--grad-brand);
}
.bottom-nav :focus-visible{outline:3px solid var(--brand-purple);outline-offset:-3px;border-radius:8px}

.bn-badge{
  position:absolute;top:6px;left:calc(50% + 7px);
  min-width:17px;height:17px;padding:0 5px;border-radius:999px;
  background:var(--danger);color:#fff;
  font-family:var(--font-mono);font-size:10.5px;font-weight:800;
  display:none;align-items:center;justify-content:center;
  box-shadow:0 0 0 2px var(--surface);
}
.bn-badge.on{display:flex}


/* =============================================================================
   RESPONSIVE
   =============================================================================
   1000px is a PAIR: .panel-nav and .mega-nav hide, .bottom-nav appears. If one
   moves they all move, or the site has a width band with no navigation at all.
   ============================================================================= */
@media (max-width:1180px){
  /* .cat-grid needs nothing here — auto-fill already drops a track when the row
     runs out of room. Only the floor changes, further down. */
  /* .p-grid needs no override — auto-fill drops a track when the row runs out. */
  .rail-split{grid-template-columns:1fr}
  .hero{padding:44px 40px 0}
  .trust-strip{margin:0 -40px;padding:0 40px}
}

@media (max-width:1000px){
  .bottom-nav{display:block}
  .panel-nav,.mega-nav,.site-head-nav{display:none}
  .head-actions .head-action{display:none}

  body.has-bottom-nav{padding-bottom:calc(var(--bottom-nav-h) + env(safe-area-inset-bottom, 0px))}
  body.has-bottom-nav .drawer-foot{padding-bottom:calc(24px + var(--bottom-nav-h) + env(safe-area-inset-bottom, 0px))}
  body.has-bottom-nav .toast{bottom:calc(var(--bottom-nav-h) + env(safe-area-inset-bottom, 0px) + 14px)}
  body.has-bottom-nav .install-bar{bottom:calc(var(--bottom-nav-h) + env(safe-area-inset-bottom, 0px) + 14px)}

  .auth-shell{min-height:calc(100vh - 64px - var(--bottom-nav-h))}
  .stat-row{grid-template-columns:repeat(2,1fr)}
  .foot-grid{grid-template-columns:1fr 1fr}
  .shelf-row{grid-template-columns:repeat(2,1fr)}
  .product{grid-template-columns:1fr;gap:24px}

  /* The hero stacks and the illustration steps back. */
  .hero-inner{flex-direction:column;align-items:flex-start;gap:26px}
  .hero-art{width:100%;max-width:320px;margin-inline:auto}
  .trust-strip{grid-template-columns:repeat(2,1fr)}
  .trust-item + .trust-item{border-left:0}
  .trust-item:nth-child(even){border-left:1px solid rgba(15,23,42,.08);padding-left:26px}
  .trust-item:nth-child(n+3){border-top:1px solid rgba(15,23,42,.08)}

  .feature-strip{grid-template-columns:1fr 1fr}
  .feature-item:nth-child(odd){border-left:0}
  .feature-item:nth-child(n+3){border-top:1px solid var(--line)}
}

@media (max-width:760px){
  .cat-grid{grid-template-columns:repeat(auto-fill,minmax(112px,1fr))}
  .p-grid{grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}

  /* HEADER ON A PHONE.
     min-height alone gave the announcement no room to breathe once its text
     wrapped to two lines — the line box filled the bar edge to edge. Height
     comes from padding here, not from a floor, so it grows with the content. */
  .announce{font-size:12px}
  .announce .wrap{min-height:0;padding-block:11px;line-height:1.5}
  /* Absolute positioning is meaningless once the aside is hidden, and leaving
     it set makes the centred text look off if it ever comes back. */
  .announce .announce-aside{display:none}

  .site-head-top{gap:12px;min-height:0;padding-block:12px;flex-wrap:wrap}
  /* Full width on its own line below the logo and the pill. */
  .search-hero{order:3;flex-basis:100%;max-width:none;height:42px}

  .head-actions{gap:14px}
  .head-cta{height:38px;padding:0 18px;font-size:13px}

  .logo img{height:38px}
}

@media (max-width:620px){
  .wrap{padding-inline:16px}
  .grid-2{grid-template-columns:1fr}
  .page-head{flex-direction:column;align-items:flex-start}
  .page-head .actions{margin-left:0}
  .foot-grid{grid-template-columns:1fr}
  .brand-sub{display:none}
  .auth-card{padding:30px 22px}
  .card{padding:20px}

  .panel-bar .wrap{gap:12px;min-height:64px}
  .brand{gap:9px}
  .brand-mark{width:36px;height:36px;border-radius:11px;font-size:16px}
  .brand-name{font-size:18px}
  .logo img{height:34px}

  .hero{padding:32px 22px 0;border-radius:var(--r-lg);margin:18px 0}

  /* THE ORB NEEDS ITS OWN BOTTOM MARGIN, and this is the only place it can
     come from. .hero has padding-bottom:0 by design — the trust strip is
     supposed to sit flush against the bottom edge of the card, which is what
     makes it read as part of the hero rather than a band under it. So the
     orb was resting directly on the strip's top border.

     It is also capped far smaller here. At max-width:320px inside a 304px
     content column it was simply "as wide as the card", a 300px circle on a
     380px screen, and .hero{overflow:hidden} clipped its top. It is a
     placeholder mark, not the content. */
  .hero-art{max-width:210px;margin-bottom:30px}

  .hero-title{font-size:clamp(27px,8vw,36px)}
  .hero-sub{font-size:15px;margin-bottom:22px}
  .hero-cta{margin-bottom:30px}
  .hero-cta .btn{flex:1 1 auto}
  /* ONE COLUMN, NOT TWO.
     Two columns leave 105px for the text beside a 42px chip on a 380px phone,
     which is where "Nothing added at checkout" became three lines and "You
     always see who ships it" became four — with the chip floating halfway down
     a block it was supposed to label. Four short rows read in a glance; two
     columns of ragged four-line paragraphs do not.
     The separator moves to the top edge and uses + .trust-item, which is the
     selector the dark-theme border-colour rule already covers. The nth-child
     borders from the 760px block are cancelled rather than left to bleed
     through as dark-on-dark lines. */
  .trust-strip{margin:0 -22px;padding:0 22px;grid-template-columns:1fr}
  .trust-item{padding:14px 0;gap:14px}
  /* ONE rule for every item after the first, because the indent it has to undo
     is set by exactly that selector. The base .trust-item + .trust-item carries
     padding-left:26px for the vertical divider, and the 760px block only clears
     its border — not the padding. Resetting it per nth-child left item 3 sitting
     26px right of the other three: it is the only odd-numbered item with a
     preceding sibling, so an :nth-child(even) reset never reached it.
     This matches items 2, 3 and 4 and is later in the file than both, so it
     wins outright and there is no per-item bookkeeping to get wrong. */
  .trust-item + .trust-item{
    border-left:0;padding-left:0;
    border-top:1px solid rgba(15,23,42,.08);
  }

  .empty-state{padding:40px 16px}
  .empty-state .fa-solid,.empty-state .fa-regular{font-size:38px}
  .empty-state h3{font-size:18.5px}

  .feature-strip{grid-template-columns:1fr}
  .feature-item{border-left:0!important}
  .feature-item + .feature-item{border-top:1px solid var(--line)}
}

@media (max-width:400px){
  .cat-grid{grid-template-columns:repeat(auto-fill,minmax(96px,1fr))}
  .hero-art{max-width:170px;margin-bottom:24px}
  .shelf-row,.stat-row{grid-template-columns:1fr}
  .panel-bar .wrap{gap:9px}
  .brand-name{font-size:16.5px}
  .btn-sm{padding:9px 14px;font-size:13px;gap:7px}
  .empty-state{padding:34px 12px}

  /* Logo and pill share one line at 360px and it is genuinely tight. The
     logo gives way before the button does — the button is the thing
     somebody came to press. */
  .logo img{height:30px}
  .head-cta{height:36px;padding:0 14px;font-size:12.5px}
}

@media (max-width:340px){
  .bottom-nav a,.bottom-nav button{font-size:9.5px}
  .bottom-nav .fa-solid,.bottom-nav .fa-regular{font-size:18px}
}

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