/* theme.css — GENERATED by bin/build-theme-css.php from fonts.css, tokens.css, chrome.css.
 * Edit those files, then run the script; do not edit this one. */

/* ---- fonts.css ---- */
/* fonts.css — GENERATED by bin/selfhost-fonts (see docs/legal-compliance.md).
 *
 * Every face below is served from this site. Nothing here reaches
 * fonts.googleapis.com or fonts.gstatic.com, so rendering a page no
 * longer hands a visitor's IP address to a third party before they have
 * agreed to anything.
 *
 * The unicode-range on each face is Google's own subsetting, kept as-is:
 * a browser downloads only the ranges the page actually uses, so the
 * full set of files here is not the cost of a page view.
 *
 * Regenerate rather than hand-edit — bin/selfhost-fonts isn't checked into
 * this tree (it ran once, locally, for the commit that introduced this
 * file), so the DM Sans 500/600/700 faces added on 2026-09-23 were fetched
 * and appended by hand, matching its existing output: static per-weight
 * woff2 (fetched with an old-Chrome UA so Google returns static instances
 * instead of one variable-font file), same latin/latin-ext subset split,
 * filenames are sha1(source URL)[:10]. A changed face gets a new name, never
 * the old one: portal/.htaccess keeps these files for a year. Shop headings ask DM Sans for
 * font-weight 500/600/700 but only 400/300 existed, so every heading was
 * being browser-synthesized ("faux bold") instead of using a real bold cut.
 *
 * DM Sans 400–700 now comes from the two weight-variable files alone
 * (f71cbe2c46 latin, 7477c839bf latin-ext, wght 100–1000), so one request
 * covers every weight a page uses instead of one per weight. The range stops
 * at 700 so an 800 or bolder still renders at 700, as it did with the static
 * Bold cut. The 300 face, the italic and every other family are untouched.
 * Fraunces 500 was declared twice per subset; the later face always won, so
 * only the earlier, never-fetched declarations went. probe_portal_fonts.php
 * holds all of this.
 */

@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/assets/fonts/dm-sans/7477c839bf.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(/assets/fonts/dm-sans/f71cbe2c46.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'DM Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/assets/fonts/dm-mono/b746c46dce.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'DM Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/assets/fonts/dm-mono/61f4626ada.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/assets/fonts/fraunces/e45fbfdae7.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/assets/fonts/fraunces/ed6f8e8580.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/assets/fonts/fraunces/9e5095030b.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'DM Mono';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(/assets/fonts/dm-mono/7aeae77ff6.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'DM Mono';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(/assets/fonts/dm-mono/23b8fe3343.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'DM Mono';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url(/assets/fonts/dm-mono/6896217e3d.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'DM Mono';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url(/assets/fonts/dm-mono/8a9cab61ef.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'DM Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/assets/fonts/dm-mono/d8a9ccf1ef.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'DM Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/assets/fonts/dm-mono/fa92f84062.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'DM Sans';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(/assets/fonts/dm-sans/d98229012a.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'DM Sans';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(/assets/fonts/dm-sans/918fdbd6c2.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url(/assets/fonts/dm-sans/a18ee4a68c.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url(/assets/fonts/dm-sans/fc88c23021.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Fraunces';
  font-style: italic;
  font-weight: 500;
  font-display: swap;
  src: url(/assets/fonts/fraunces/43a6679751.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Fraunces';
  font-style: italic;
  font-weight: 500;
  font-display: swap;
  src: url(/assets/fonts/fraunces/184a3626f0.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Fraunces';
  font-style: italic;
  font-weight: 500;
  font-display: swap;
  src: url(/assets/fonts/fraunces/d571553294.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---- tokens.css ---- */
/*
 * Every colour and radius the portal uses. The three stylesheets each had their
 * own :root and fifteen colours were written past them, so a palette change had
 * to be made in four places and agree in all of them.
 *
 * Load this first: the others only read.
 */
:root {
  /* The display face. Declared here rather than in shop/shop.css because the
     dashboard wants it too and never loads that sheet — read from there, it
     resolves to nothing and the text quietly falls back to the body face. */
  --display: 'Fraunces', ui-serif, Georgia, 'Times New Roman', serif;

  --bg: #fafafa;
  --bg2: #ffffff;
  --bg3: #f4f6f8;
  --bg4: #eaeef2;
  --topbar-bg: #1c1f23;  /* the dark strip — the one dark surface left */
  /* The strip's foot. A single flat fill reads as a block of colour at the 68px
     the bar now stands; two stops a few points apart read as a surface. Close
     enough to --topbar-bg that a screenshot diff barely moves — the point is the
     edge, not a visible gradient. */
  --topbar-bg2: #15181c;

  --border: #e0e0e0;
  --border2: #e8e8e8;

  /* Button labels run 13.5–15px/600, which doesn't qualify as large text, so
     the fill sits one step darker than the reference blue (#0091ff, only
     3.23:1 under white) to clear 4.5:1. The brighter blue survives as an
     accent — borders, icons, the hero gradient — never a labelled fill. */
  --base: #0074cc;
  /* unused today — reserved for a border, an icon or a gradient stop per
     the paragraph above, never a labelled fill. Leave it defined. */
  --base-l: #0091ff;
  --base-d: #005ca3;
  --base-bg: rgba(0,116,204,0.08);
  --section-bg: #f0f8ff;

  /* Channel triplets, because the sheets carry THIRTY distinct rgba() values
     — eight amber alphas alone. Naming each one is a token nobody remembers;
     composing them from a triplet means the light palette revalues six lines
     and every alpha follows. rgba(240,165,0,0.25) becomes
     rgba(var(--amber-rgb), 0.25), which is the same colour exactly — no
     colour-space conversion, so this stays a refactor. */
  --amber-rgb: 158,100,0;
  --teal-rgb: 13,130,95;
  --danger-rgb: 217,45,32;
  --on-dark-rgb: 255,255,255;
  --bg-rgb: 250,250,250;
  --shadow-rgb: 16,24,40;

  --amber: #9e6400;       /* darkened from #f0a500 — 4.7:1 on #fafafa */
  --amber-bg: rgba(var(--amber-rgb), 0.08);
  --amber-bg2: rgba(var(--amber-rgb), 0.15);
  --amber-l: #f0a500;
  /* THE WORDMARK'S GOLD, and deliberately not --amber-l even though it is the
     same value today. --amber is an ACCENT and body.shop legitimately repaints
     it (the storefront's accent is blue), so the wordmark riding on it turned
     "My" blue on the shop and gold on the dashboard — one brand, two colours,
     on the one element that appears on every page. Identity is not accent, so
     it gets its own token and body.shop does not override it. */
  --brand-mark: #f0a500;
  /* same problem as --teal-d: --amber on its own tint doesn't clear 4.5:1. */
  --amber-d: #7f5100;

  --teal: #0d825f;        /* darkened from #1abc8a — 4.6:1 on #fafafa */
  --teal-bg: rgba(var(--teal-rgb), 0.08);
  /* --teal on its own 8-12% tint only clears ~4.0-4.2:1, not 4.5 — a badge
     or alert reads its text against the tint, not the page ground behind it.
     Same relationship as --danger-d to --danger, one step darker. */
  --teal-d: #0a664b;

  /* Refusals and destructive actions. Hardcoded in four places before this. */
  --danger: #d92d20;
  /* also the error chip's text; was a pale salmon against a dark panel, now
     has to read dark against near-white, so it inverts instead of lightening */
  --danger-d: #a81c13;
  --danger-border: rgba(var(--danger-rgb), 0.25);
  --teal-border: rgba(var(--teal-rgb), 0.25);

  --text: #16191d;
  --text2: #5a626c;
  --text3: #8a929c;

  /* Labels ON a filled surface. These are not the page's text colour — the
     fills are dark enough now that white sits on every one of them
     (4.81 on --base, 4.91 on --amber, 4.83 on --danger), and that decision
     lives in one place rather than three. */
  --on-amber: #fff;
  --on-danger: #fff;
  --on-base: #fff;
  --on-teal: #fff;
  --on-dark: #fff;

  --shadow: 0 6px 20px rgba(var(--shadow-rgb), 0.08);
  /* The card resting/lifted pair. Here rather than in portal.css because a
     component sheet composing its own rgba() is a colour written outside the
     token layer, which is the thing this file exists to prevent. */
  --shadow-card: 0 4px 8px rgba(var(--shadow-rgb), 0.06);
  --shadow-card-hover: 0 6px 16px rgba(var(--shadow-rgb), 0.12);
  --scrim: rgba(var(--shadow-rgb), 0.55);
  /* The image lightbox: a near-opaque backdrop, so the photo is all there is,
     and a light close button on it. Fixed values, not the on-dark family:
     body.shop flips --on-dark, and the backdrop stays dark on every page. */
  --lightbox-scrim: rgba(0, 0, 0, 0.82);
  --lightbox-close-bg: rgba(255, 255, 255, 0.12);
  --lightbox-close-bg-hover: rgba(255, 255, 255, 0.22);
  --lightbox-close-ink: #fff;
  /* The resting shadow of a banner that sits flat on the page (the checkout's
     member-discount banner): a hairline, lighter than --shadow-card. */
  --shadow-banner: 0 1px 2px rgba(0, 0, 0, .05);
  /* The membership card's warm corner, where its gradient starts before it
     settles into the dark strip's --topbar-bg. */
  --member-card-from: #241a08;
  /* The account and category menus hang over the page rather than sitting on
     it, so they carry more spread than --shadow-card. The nav has two states:
     a hairline while the page is at the top, a real shadow once it has
     scrolled under — that transition is the whole reason the row reads as
     fixed rather than as part of the page. */
  --shadow-menu: 0 12px 32px rgba(var(--shadow-rgb), 0.12);
  --shadow-nav: 0 1px 0 rgba(var(--shadow-rgb), 0.04);
  --shadow-nav-stuck: 0 6px 20px rgba(var(--shadow-rgb), 0.08);

  /* Everything above is a colour a probe can find written straight into a
     stylesheet. It can't tell rgba(var(--x-rgb), α) apart from a literal one —
     both just look like "rgba(" — so a one-off alpha still needs a name here
     even though nothing else on the page shares it. Same triplets, same
     alphas, just called by name at the one spot each is used. */
  --amber-10: rgba(var(--amber-rgb), 0.10);
  --amber-14: rgba(var(--amber-rgb), 0.14);
  --amber-20: rgba(var(--amber-rgb), 0.2);
  --amber-25: rgba(var(--amber-rgb), 0.25);
  --amber-35: rgba(var(--amber-rgb), 0.35);
  --amber-40: rgba(var(--amber-rgb), 0.4);
  --amber-50: rgba(var(--amber-rgb), 0.5);

  --teal-12: rgba(var(--teal-rgb), 0.12);
  --teal-20: rgba(var(--teal-rgb), 0.2);
  --teal-30: rgba(var(--teal-rgb), 0.3);
  --teal-95: rgba(var(--teal-rgb), 0.95);

  /* alphas of --danger, same family as --danger-10 below — not of --danger-d,
     despite what these were named before (that named the wrong base). */
  --danger-08: rgba(var(--danger-rgb), 0.08);
  --danger-12: rgba(var(--danger-rgb), 0.12);
  --danger-20: rgba(var(--danger-rgb), 0.2);
  --danger-30: rgba(var(--danger-rgb), 0.3);
  --danger-10: rgba(var(--danger-rgb), 0.10);

  /* on-dark-rgb stays white — it's only ever used on --topbar-bg, the one
     surface in this palette that's still dark. */
  --on-dark-10: rgba(var(--on-dark-rgb), 0.1);
  --on-dark-35: rgba(var(--on-dark-rgb), 0.35);
  /* The strip's own hairline, the account button's resting/hover fill and the
     two text weights inside it. All four were going to be written straight into
     chrome.css, which is the literal probe_portal_theme exists to catch. */
  --on-dark-06: rgba(var(--on-dark-rgb), 0.06);
  --on-dark-14: rgba(var(--on-dark-rgb), 0.14);
  --on-dark-55: rgba(var(--on-dark-rgb), 0.55);
  --on-dark-70: rgba(var(--on-dark-rgb), 0.7);

  --bg-88: rgba(var(--bg-rgb), 0.88);
  --bg-94: rgba(var(--bg-rgb), 0.94);
  --bg-96: rgba(var(--bg-rgb), 0.96);

  --shadow-35: rgba(var(--shadow-rgb), 0.35);
  --shadow-50: rgba(var(--shadow-rgb), 0.5);

  --r: 6px;
  --rl: 12px;

  /* How far down the viewport a sticky element has to start, so it comes to
     rest UNDER the chrome rather than beneath it. Zero here because .pt-nav is
     only pinned above the pills breakpoint; chrome.css raises this to the
     nav's measured height there, and that is the only place the breakpoint is
     written. Anything sticky on a portal page reads this rather than pinning
     itself to 0 — pinning to 0 is how the product page's buy panel came to
     rest behind the nav with its price line hidden. */
  --pt-sticky-top: 0px;

  /* THE STORE LOOK (homepage v6): the shop front, and the store header and
     footer on every shop page (.hm, .pt-chrome--store, .pt-foot--store).
     The portal's own blue and gold, with the display face back, read here
     at :root so body.shop's black --base below cannot reach them. Light
     only, like the rest of the portal. */
  --store-accent: var(--base);             /* #0074cc */
  --store-accent-ink: var(--on-base);
  --store-accent-d: var(--base-d);         /* the accent's hover: the portal's darker base, read here at :root before body.shop repaints --base-d */
  --store-accent-soft: rgba(0,116,204,0.09);
  --store-gold: var(--amber);              /* #9e6400, text on light */
  --store-gold-l: var(--amber-l);          /* #f0a500, fills */
  --store-gold-soft: rgba(240,165,0,0.13);
  --store-gold-ink: #1a1300;
  --store-brand: #1fbf7c;                  /* the logo's green arrow */
  --store-brand-ink: #05261a;
  /* Membership plan cards (cmo-0455): the store's green for actions, and
     each tier's dark card with its metal (Starter, Growth, Elite by position). */
  --store-go: #0f7a4f;
  --store-go-d: #0b5c3b;
  --store-go-soft: rgba(15,122,79,0.08);
  --store-on-dark-muted: rgba(255,255,255,0.72);
  --store-tier-1: #d5d9e0;
  --store-tier-1-a: #2a2d33;
  --store-tier-1-b: #17191d;
  --store-tier-1-glow: rgba(213,217,224,0.22);
  --store-tier-2: #e9c46a;
  --store-tier-2-a: #2b2620;
  --store-tier-2-b: #17140f;
  --store-tier-2-glow: rgba(233,196,106,0.28);
  --store-tier-3: #b9a6ff;
  --store-tier-3-a: #251f3d;
  --store-tier-3-b: #121022;
  --store-tier-3-glow: rgba(185,166,255,0.32);
  --store-tier-clear: rgba(0,0,0,0);
  --store-ink: #16191d;
  --store-muted: #5a6270;
  --store-line: #e3e6ea;
  --store-bg: #fafafa;
  --store-bg2: #f4f6f8;
  --store-card: #ffffff;
  /* The ground every product picture's 590:300 frame sits on (cto-0947): the
     card itself, so a contained picture's spare space reads as card. */
  --store-surface: var(--store-card);
  --store-card-88: rgba(255,255,255,0.88);
  --store-card-0: rgba(255,255,255,0);      /* the account bar's edge fade ends here (BL-57) */
  --store-glow-a: rgba(0,145,255,0.15);
  --store-glow-b: rgba(35,213,139,0.16);
  --store-dot: rgba(22,25,29,0.08);
  --store-mask: #000;                      /* mask alpha only, never painted */
  --store-mask-55: rgba(0,0,0,0.55);
  --store-scrim: rgba(10,14,24,0.45);
  --store-menu-shadow: 0 18px 50px rgba(10,14,24,0.18);  /* the account menu card's lift (BL-57 mockup .acct-menu) */
  --store-dlg-shadow: 0 30px 80px rgba(10,14,24,0.4);    /* an account dialog's lift (BL-57 mockup .dlg) */
  --store-dlg-scrim: rgba(8,12,20,0.55);                 /* behind an account dialog (BL-57 mockup .dlg::backdrop) */
  --store-shadow: 0 1px 2px rgba(16,24,40,0.06), 0 8px 24px rgba(16,24,40,0.08);
  --store-cover-shadow: rgba(16,24,40,0.14);  /* what an add-on cover's drawings cast on its ground */
  --store-cover-green-soft: rgba(92,194,122,0.16);  /* --store-dot-green and --store-dot-red as a cover's backdrop wash */
  --store-cover-coral-soft: rgba(239,106,91,0.14);
  /* The purple of the live plugin artwork (logo.png of the tower game,
     lottery, achievements, coupons...), for the games' covers. */
  --store-cover-purple: #6e3be6;
  --store-cover-purple-soft: rgba(110,59,230,0.14);
  /* A payment gateway's add-on cover (shop/_addon_cover.php) is a card in the
     gateway's own colour: read off the wordmark in the plugin's logo.png
     (deepened where that is thin grey text), with the text colour of the
     two that reads better on it, and -away, the other of the two, which the
     card's corner under the name leans to (tl-1314: the name at 4.5:1 over
     everything drawn under it). One set per SHOP_COVER_GATEWAYS entry. */
  --gw-2checkout: #3b74e8; --gw-2checkout-ink: var(--on-base); --gw-2checkout-away: var(--store-ink);
  --gw-aamarpay: #f08c00; --gw-aamarpay-ink: var(--store-ink); --gw-aamarpay-away: var(--store-card);
  --gw-amazon: #ff9900; --gw-amazon-ink: var(--store-ink); --gw-amazon-away: var(--store-card);
  --gw-authorize: #1d3241; --gw-authorize-ink: var(--on-base); --gw-authorize-away: var(--store-ink);
  --gw-binance: #f0b90b; --gw-binance-ink: var(--store-ink); --gw-binance-away: var(--store-card);
  --gw-bkash: #d01e52; --gw-bkash-ink: var(--on-base); --gw-bkash-away: var(--store-ink);
  --gw-blockchain: #02ade5; --gw-blockchain-ink: var(--store-ink); --gw-blockchain-away: var(--store-card);
  --gw-bluesnap: #3f6fb5; --gw-bluesnap-ink: var(--on-base); --gw-bluesnap-away: var(--store-ink);
  --gw-cashfree: #49406f; --gw-cashfree-ink: var(--on-base); --gw-cashfree-away: var(--store-ink);
  --gw-cashmaal: #d6524e; --gw-cashmaal-ink: var(--store-ink); --gw-cashmaal-away: var(--store-card);
  --gw-chapa: #7bc300; --gw-chapa-ink: var(--store-ink); --gw-chapa-away: var(--store-card);
  --gw-checkout: #3c3f45; --gw-checkout-ink: var(--on-base); --gw-checkout-away: var(--store-ink);
  --gw-coinbase: #104ded; --gw-coinbase-ink: var(--on-base); --gw-coinbase-away: var(--store-ink);
  --gw-coingate: #012d5b; --gw-coingate-ink: var(--on-base); --gw-coingate-away: var(--store-ink);
  --gw-coinpayments: #0164af; --gw-coinpayments-ink: var(--on-base); --gw-coinpayments-away: var(--store-ink);
  --gw-flutterwave: #fcbc0f; --gw-flutterwave-ink: var(--store-ink); --gw-flutterwave-away: var(--store-card);
  --gw-gocardless: #201f1b; --gw-gocardless-ink: var(--on-base); --gw-gocardless-away: var(--store-ink);
  --gw-instamojo: #2e2d7c; --gw-instamojo-ink: var(--on-base); --gw-instamojo-away: var(--store-ink);
  --gw-intouch: #222f61; --gw-intouch-ink: var(--on-base); --gw-intouch-away: var(--store-ink);
  --gw-mercado: #00afe8; --gw-mercado-ink: var(--store-ink); --gw-mercado-away: var(--store-card);
  --gw-midtrans: #44607f; --gw-midtrans-ink: var(--on-base); --gw-midtrans-away: var(--store-ink);
  --gw-mollie: #111111; --gw-mollie-ink: var(--on-base); --gw-mollie-away: var(--store-ink);
  --gw-moonpay: #7d03fc; --gw-moonpay-ink: var(--on-base); --gw-moonpay-away: var(--store-ink);
  --gw-mpesa: #4c9a1a; --gw-mpesa-ink: var(--store-ink); --gw-mpesa-away: var(--store-card);
  --gw-nagad: #eb1921; --gw-nagad-ink: var(--on-base); --gw-nagad-away: var(--store-ink);
  --gw-nmi: #33185d; --gw-nmi-ink: var(--on-base); --gw-nmi-away: var(--store-ink);
  --gw-nowpayments: #3d86e0; --gw-nowpayments-ink: var(--store-ink); --gw-nowpayments-away: var(--store-card);
  --gw-openpix: #2fa092; --gw-openpix-ink: var(--store-ink); --gw-openpix-away: var(--store-card);
  --gw-payeer: #2f8fd0; --gw-payeer-ink: var(--store-ink); --gw-payeer-away: var(--store-card);
  --gw-paypal: #012e86; --gw-paypal-ink: var(--on-base); --gw-paypal-away: var(--store-ink);
  --gw-paystack: #041a31; --gw-paystack-ink: var(--on-base); --gw-paystack-away: var(--store-ink);
  --gw-paytm: #042c6e; --gw-paytm-ink: var(--on-base); --gw-paytm-away: var(--store-ink);
  --gw-paytr: #1c4dd7; --gw-paytr-ink: var(--on-base); --gw-paytr-away: var(--store-ink);
  --gw-payu: #a6c20a; --gw-payu-ink: var(--store-ink); --gw-payu-away: var(--store-card);
  --gw-razorpay: #052351; --gw-razorpay-ink: var(--on-base); --gw-razorpay-away: var(--store-ink);
  --gw-skrill: #581d5b; --gw-skrill-ink: var(--on-base); --gw-skrill-away: var(--store-ink);
  --gw-sslcommerz: #446ec1; --gw-sslcommerz-ink: var(--on-base); --gw-sslcommerz-away: var(--store-ink);
  --gw-stripe: #6671e3; --gw-stripe-ink: var(--store-ink); --gw-stripe-away: var(--store-card);
  --gw-uddoktapay: #0199f9; --gw-uddoktapay-ink: var(--store-ink); --gw-uddoktapay-away: var(--store-card);
  --gw-venmo: #2f88c2; --gw-venmo-ink: var(--store-ink); --gw-venmo-away: var(--store-card);
  --gw-wise: #193507; --gw-wise-ink: var(--on-base); --gw-wise-away: var(--store-ink);
  --store-dot-red: #ef6a5b;
  --store-dot-amber: #f2b84b;
  --store-dot-green: #5cc27a;
  --store-display: 'Fraunces', ui-serif, Georgia, 'Times New Roman', serif;
  --store-body: 'DM Sans', 'Segoe UI', system-ui, -apple-system, sans-serif;
  --store-mono: 'DM Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;
  --store-r: 14px;
  --store-r-btn: 10px;                     /* buttons and selects on the item page */
  --store-hdr: 64px;
}

/*
 * THE STOREFRONT SKIN.
 *
 * Krishna asked for the product page to match the reference marketplace's look,
 * not only its layout. Scoped to body.shop — browse, product and cart — so the
 * dashboard, account and auth pages keep the palette above unchanged. Written
 * here rather than in shop.css because probe_portal_theme holds every other
 * sheet to naming no colour of its own, and a re-skin that hid literals in a
 * component sheet is exactly the half-inverted theme that probe exists to catch.
 *
 * The contrast reasoning in the :root block still applies and is honoured:
 * every value below that carries a label clears 4.5:1 against the ground it
 * sits on. The reference's own accent (#2563eb, 4.83:1 on white) would pass,
 * but the step darker is used for text-weight accent so it also clears on the
 * --bg3 tint, where the lighter one lands at 4.1:1.
 */
body.shop {
  /* Sans throughout. Fraunces is the portal's display face; the reference sets
     headings in the same grotesque as its body text. DM Sans is already
     self-hosted by fonts.css, so this costs no extra request. */
  --display: 'DM Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;

  --bg: #f5f7fa;
  --bg2: #ffffff;
  --bg3: #eef2f7;
  --bg4: #e3e8ef;
  --bg-rgb: 245,247,250;

  /* The shop header is the SAME dark strip the dashboard wears, so a buyer
     crossing between the storefront and their account does not see the chrome
     change colour under them. This block used to invert --topbar-bg and
     --on-dark to a white bar with dark text, following the reference design;
     that made the one element common to every page the one element that did
     not look common. The dark values now come through from :root, which is
     also where --on-dark-rgb, .pt-top .brand-tld and .pt-top .topbar-avatar
     already expect to be — each of those carries a light-ground variant
     specifically for this strip, and they were all being applied against
     white. */

  --border: #e3e8ef;
  --border2: #eef2f7;

  --text: #111827;   /* 16.9:1 on white */
  --text2: #4b5563;  /*  7.6:1 */
  --text3: #6b7280;  /*  5.2:1 */

  /* The primary fill is near-black, as the reference's "Buy Product Now" is.
     White on it is 16.9:1. */
  --base: #111827;
  --base-l: #1f2937;
  --base-d: #000000;
  --base-bg: rgba(17,24,39,0.06);
  --section-bg: #f5f7fa;

  /* --amber is the storefront's accent: the price, the order total, the active
     tab's underline and the selected licence card all read it. Re-pointed at
     the reference's blue rather than renamed. 7.0:1 on white. */
  --amber-rgb: 29,78,216;
  --amber: #1d4ed8;
  --amber-l: #2563eb;
  --amber-d: #1e40af;

  /* EVERY DERIVED TOKEN HAS TO BE RE-DECLARED HERE, and this is not tidiness.
     A token like --on-dark-70, declared once as rgba(var(--on-dark-rgb), 0.7)
     on :root, is substituted AT :ROOT using :root's triplet; what inherits down
     is the finished colour string, not the recipe. Overriding the triplet in
     this block therefore reaches nothing that was composed above it.
     Re-declaring each derived token inside this block makes it compose again
     here, against the triplets above.
     Found the hard way: flipping --on-dark-rgb alone left the header's language
     switcher and "Create account" as white-on-white — present, focusable, and
     invisible, which is worse than missing. */
  --on-dark-06: rgba(var(--on-dark-rgb), 0.06);
  --on-dark-10: rgba(var(--on-dark-rgb), 0.1);
  --on-dark-14: rgba(var(--on-dark-rgb), 0.14);
  --on-dark-35: rgba(var(--on-dark-rgb), 0.35);
  --on-dark-55: rgba(var(--on-dark-rgb), 0.55);
  --on-dark-70: rgba(var(--on-dark-rgb), 0.7);

  --amber-bg:  rgba(var(--amber-rgb), 0.08);
  --amber-bg2: rgba(var(--amber-rgb), 0.15);
  --amber-10:  rgba(var(--amber-rgb), 0.10);
  --amber-14:  rgba(var(--amber-rgb), 0.14);
  --amber-20:  rgba(var(--amber-rgb), 0.2);
  --amber-25:  rgba(var(--amber-rgb), 0.25);
  --amber-35:  rgba(var(--amber-rgb), 0.35);
  --amber-40:  rgba(var(--amber-rgb), 0.4);
  --amber-50:  rgba(var(--amber-rgb), 0.5);

  --bg-88: rgba(var(--bg-rgb), 0.88);
  --bg-94: rgba(var(--bg-rgb), 0.94);
  --bg-96: rgba(var(--bg-rgb), 0.96);

  --r: 8px;
  --rl: 12px;
}

/* ---- chrome.css ---- */
/* The one dark surface in the portal, and the nav row that sticks under it.
   CodePlus puts brand and account on the dark strip and navigation on the pale
   row below, so the two never compete.

   Both rows are rendered by includes/portal_chrome.php — one partial, three
   call sites (the two shop pages and portal_header.php). The markup used to be
   pasted into each of the three and had already drifted; the selectors here
   assume the partial's structure, so a fourth hand-written copy will not pick
   up the menus or the sticky behaviour. */
.pt-top {
  /* Two stops rather than a flat fill: at 68px a single colour reads as a slab.
     The pair is close enough that it is felt as depth rather than seen as a
     gradient. */
  background: linear-gradient(180deg, var(--topbar-bg) 0%, var(--topbar-bg2) 100%);
  color: var(--on-dark);
  border-block-end: 1px solid var(--on-dark-06);
  position: relative;
  z-index: 120;
}
.pt-top__inner,
.pt-nav__inner {
  max-width: 78rem; margin: 0 auto; padding: 0 20px;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
}
.pt-top__inner { min-height: 68px; }
.pt-top__brand {
  display: inline-flex; align-items: center; gap: 10px;
  color: var(--on-dark); text-decoration: none;
  font-weight: 600; font-size: 15.5px; letter-spacing: -0.01em;
}
.pt-top__brand img { height: 24px; width: auto; }
/* The fused logo (icon + wordmark, one line) reads as a wordmark rather
   than an icon, so it earns more height than the bare icon mark did — the
   68px bar has the room. It is an inline <svg> (portal_logo_svg()), or the
   PNG if that file is missing; width follows the height either way. */
.pt-top__brand .pt-top__logo { height: 34px; width: auto; }
.pt-top__right { display: flex; align-items: center; gap: 10px; font-size: 13.5px; }

/* The cart icon, shown on every page that renders this chrome (including the
   dashboard, where it is fixed at zero — see portal_chrome()'s docblock). The
   badge reuses --danger/--on-danger rather than a colour of its own: the same
   pairing already renders on portal.css's danger stat tile and danger border,
   and probe_portal_theme.php fails on any literal written here regardless. */
.pt-cart { position: relative; display: inline-flex; align-items: center; color: var(--on-dark-70); padding: 6px; border-radius: var(--r); transition: color .18s ease; }
.pt-cart:hover { color: var(--on-dark); }
.pt-cart__qty {
  position: absolute; top: -2px; inset-inline-end: -2px;
  min-width: 17px; height: 17px; padding: 0 4px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--danger); color: var(--on-danger);
  border-radius: 999px; font-size: 10.5px; font-weight: 600; line-height: 1;
}

/* Guest: a quiet text link beside a bordered one. Two links of equal weight
   made the strip read as a choice between two unrelated things; the border
   says which one the page expects most visitors to want. */
.pt-top__link {
  color: var(--on-dark-70); text-decoration: none; font-weight: 500;
  padding: 8px 4px; transition: color .18s ease;
}
.pt-top__link:hover { color: var(--on-dark); }
.pt-top__signin {
  color: var(--on-dark); text-decoration: none; font-weight: 600;
  padding: 8px 16px; border-radius: 999px;
  border: 1px solid var(--on-dark-14);
  transition: background-color .18s ease, border-color .18s ease;
}
.pt-top__signin:hover { background: var(--on-dark-10); border-color: var(--on-dark-35); }

/* ── The store's sections beside the wordmark (store-look header) ────────
   Platforms, Themes and Add-ons inline from 1080px, the width at which the
   mockup's bar has room for them beside the switcher and the account; below
   it, one menu button in .pt-top__right opens the same three. */
.pt-primary { display: none; align-items: center; gap: 2px; margin-inline-end: auto; margin-inline-start: 10px; }
.pt-primary__link {
  color: var(--on-dark-70); text-decoration: none; font-weight: 500; font-size: 14.5px;
  padding: 8px 12px; border-radius: var(--r);
  transition: color .18s ease, background-color .18s ease;
}
.pt-primary__link:hover { color: var(--on-dark); background: var(--on-dark-10); }
.pt-primary-menu { position: relative; display: inline-flex; }
.pt-primary-menu__btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; padding: 0; cursor: pointer;
  background: transparent; color: var(--on-dark);
  border: 1px solid var(--on-dark-14); border-radius: var(--r);
  transition: background-color .18s ease;
}
.pt-primary-menu__btn:hover, .pt-primary-menu__btn[aria-expanded="true"] { background: var(--on-dark-10); }
.pt-primary-menu .pt-menu { width: 220px; }
/* Sign in lives in the bar from 641px and in this menu below it, where the
   bar has no room for both (the shop's bottom nav still has Account). */
.pt-primary-menu__signin { display: none; }
@media (max-width: 640px) {
  .pt-top__inner:has(.pt-primary-menu) .pt-top__signin { display: none; }
  .pt-primary-menu__signin { display: list-item; }
}
@media (min-width: 1080px) {
  .pt-primary { display: flex; }
  .pt-primary-menu { display: none; }
}

/* ── The account button and its menu ─────────────────────────────────── */
.pt-acct { position: relative; }
.pt-acct__btn {
  display: flex; align-items: center; gap: 9px;
  background: transparent; border: 1px solid transparent; cursor: pointer;
  color: var(--on-dark); font: inherit; font-size: 13.5px;
  padding: 5px 8px 5px 5px; border-radius: 999px;
  transition: background-color .18s ease, border-color .18s ease;
}
.pt-acct__btn:hover { background: var(--on-dark-10); border-color: var(--on-dark-14); }
.pt-acct__avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 50%; flex: none;
  background: var(--on-dark-14); color: var(--on-dark);
  font-size: 11.5px; font-weight: 600; letter-spacing: .02em;
}
/* The name is the widest thing on the strip and the least load-bearing — the
   avatar already identifies the account. It goes first when space runs out. */
.pt-acct__name {
  font-weight: 500; max-width: 15ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pt-acct__caret { opacity: .65; transition: transform .2s ease; flex: none; }
.pt-acct__btn[aria-expanded="true"] .pt-acct__caret { transform: rotate(180deg); }

/* One card shape for both menus. Hidden with visibility rather than display so
   the open/close transition has something to animate — visibility:hidden also
   takes the closed card out of hit-testing, so it cannot swallow a click meant
   for the page underneath the way an opacity:0 card would. */
.pt-menu, .pt-nav__panel {
  position: absolute; inset-inline-end: 0; top: calc(100% + 10px);
  background: var(--bg2); border: 1px solid var(--border2);
  border-radius: var(--rl); box-shadow: var(--shadow-menu);
  padding: 8px; z-index: 130;
  opacity: 0; visibility: hidden; transform: translateY(-6px) scale(.97);
  transform-origin: top var(--menu-origin, right);
  transition: opacity .16s ease, transform .16s ease, visibility .16s;
}
.pt-menu { width: 264px; }
/* Keyed on the two attributes chrome.js actually works in terms of, not on a
   list of component classes. It used to name .pt-acct and .pt-nav__item, and
   a menu whose class was not in that list simply never appeared: the script
   added .is-open and set aria-expanded="true" exactly as designed, and the
   panel stayed visibility:hidden. .pt-lang shipped that way and its menu has
   never opened — pressing it did nothing, with no error anywhere, because
   every moving part except this selector was working. A currency control added
   later was the second to inherit it, which is how it was found.

   So the rule now matches [data-chrome-menu] / [data-chrome-menu-panel], the
   same pair menus() and setOpen() use. A new menu that follows the script's
   contract is revealed by having followed it, rather than by someone also
   remembering to extend this list. */
[data-chrome-menu].is-open [data-chrome-menu-panel] {
  opacity: 1; visibility: visible; transform: translateY(0) scale(1);
}
.pt-menu__head {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 8px 12px; margin-block-end: 6px;
  border-block-end: 1px solid var(--border2);
}
.pt-acct__avatar--lg {
  width: 38px; height: 38px; font-size: 14px;
  background: var(--base-bg); color: var(--base-d);
}
.pt-menu__who {
  font-size: 13.5px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pt-menu__list { list-style: none; margin: 0; padding: 0; }
.pt-menu__link {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px; border-radius: var(--r);
  color: var(--text2); text-decoration: none; font-size: 13.5px; font-weight: 500;
  transition: background-color .15s ease, color .15s ease;
}
.pt-menu__link:hover { background: var(--bg3); color: var(--text); }
.pt-menu__link svg { opacity: .7; flex: none; }
.pt-menu__sep {
  margin-block-start: 6px; padding-block-start: 6px;
  border-block-start: 1px solid var(--border2);
}

/* ── The language switcher ───────────────────────────────────────────── */
/* Shares .pt-menu's card and the [data-chrome-menu] behaviour with the account
   menu — same shape, same script, no second implementation. */
.pt-lang { position: relative; }
.pt-lang__btn {
  display: flex; align-items: center; gap: 6px;
  background: transparent; border: 1px solid transparent; cursor: pointer;
  color: var(--on-dark-70); font: inherit; font-size: 13.5px; font-weight: 500;
  padding: 7px 10px; border-radius: 999px;
  transition: background-color .18s ease, border-color .18s ease, color .18s ease;
}
.pt-lang__btn:hover { background: var(--on-dark-10); border-color: var(--on-dark-14); color: var(--on-dark); }
.pt-lang__btn[aria-expanded="true"] .pt-acct__caret { transform: rotate(180deg); }
.pt-lang__name { white-space: nowrap; }
/* Eighteen locales is taller than a viewport on a phone, so the card scrolls
   rather than running off the screen with its last entries unreachable. */
/* A heading per section of the globe menu, which now carries language AND
   currency. Quiet enough that the choices stay the thing you read. */
.pt-menu__group {
  margin: 6px 0 2px; padding: 0 10px;
  font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--text3);
}
.pt-menu__list + .pt-menu__group { margin-top: 10px; border-block-start: 1px solid var(--border2); padding-block-start: 10px; }
.pt-menu__sym { display: inline-block; min-width: 14px; font-weight: 600; }

.pt-menu--lang { width: 208px; max-height: min(60vh, 420px); overflow-y: auto; }
.pt-menu--lang .pt-menu__link.is-current { background: var(--bg3); color: var(--text); font-weight: 600; }

/* The breadcrumb trail, where a page gives one. It sits where the nav links
   would be, and the categories keep their place after it: a trail says where
   you are, they are how you go elsewhere. Wraps rather than truncating —
   a product name is long and the last step is the one worth reading. */
.pt-crumbs-wrap { min-width: 0; }
.pt-crumbs {
  display: flex; flex-wrap: wrap; align-items: center; gap: 2px 6px;
  list-style: none; margin: 0; padding: 0;
  font-size: 13.5px; min-width: 0;
}
.pt-crumbs__item { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
/* The separator is generated, not markup: a "/" typed between items is read
   aloud as part of the label by a screen reader. */
.pt-crumbs__item + .pt-crumbs__item::before {
  content: "/"; color: var(--text3); font-size: 12px;
}
.pt-crumbs__link { color: var(--text2); text-decoration: none; white-space: nowrap; }
.pt-crumbs__link:hover { color: var(--text); text-decoration: underline; }
/* The current page carries the weight, and is the one allowed to wrap. */
.pt-crumbs__here {
  color: var(--text); font-weight: 600;
  overflow-wrap: anywhere; min-width: 0;
}

/* ── The nav row ─────────────────────────────────────────────────────── */
.pt-nav {
  background: var(--bg2);
  border-block-end: 1px solid var(--border);
  box-shadow: var(--shadow-nav);
  position: relative; z-index: 110;
  transition: box-shadow .2s ease;
}
.pt-nav__inner { min-height: 54px; }
.pt-nav__links { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }

/* The underline grows from the middle out. It is on a ::before rather than on
   border-bottom so the resting state occupies the same box as the hover state
   and the row does not shift by a pixel when a link is pointed at. */
.pt-nav__link {
  position: relative;
  color: var(--text2); text-decoration: none;
  font-size: 14px; font-weight: 500;
  padding: 16px 10px;
  transition: color .18s ease;
}
.pt-nav__link::before {
  content: ""; position: absolute;
  inset-inline-start: 50%; inset-block-end: 0;
  width: 0; height: 2px; background: var(--base);
  transform: translateX(-50%);
  transition: width .25s ease;
}
.pt-nav__link:hover { color: var(--text); }
.pt-nav__link:hover::before { width: calc(100% - 20px); }
.pt-nav__link.is-current { color: var(--text); font-weight: 600; }
.pt-nav__link.is-current::before { width: calc(100% - 20px); }

.pt-nav__item { position: relative; display: flex; align-items: center; }
.pt-nav__item .pt-nav__link { padding-inline-end: 2px; }
.pt-nav__caret {
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 0; cursor: pointer;
  color: var(--text3); padding: 6px 8px 6px 4px;
  transition: transform .2s ease, color .18s ease;
}
.pt-nav__item:hover .pt-nav__caret { color: var(--text); }
.pt-nav__item.is-open .pt-nav__caret { transform: rotate(180deg); color: var(--text); }
/* The panel is left-aligned to its category, not right-aligned to the strip. */
.pt-nav__panel {
  inset-inline-end: auto; inset-inline-start: 0;
  --menu-origin: left;
  min-width: 210px; padding: 10px;
}
.pt-nav__panel a {
  display: block; padding: 8px 10px; border-radius: var(--r);
  color: var(--text2); text-decoration: none; font-size: 13.5px; font-weight: 500;
  white-space: nowrap;
  transition: background-color .15s ease, color .15s ease;
}
.pt-nav__panel a:hover { background: var(--bg3); color: var(--text); }

.pt-nav__cta {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--base); color: var(--on-base); text-decoration: none;
  padding: 9px 17px; border-radius: 999px; font-size: 13.5px; font-weight: 600;
  white-space: nowrap; flex: none;
  box-shadow: var(--shadow-card);
  transition: background-color .18s ease, box-shadow .18s ease, transform .18s ease;
}
.pt-nav__cta:hover {
  background: var(--base-d);
  box-shadow: var(--shadow-card-hover);
  transform: translateY(-1px);
}
.pt-nav__cta svg { opacity: .9; }

/* Rendered only where there is more than one thing to collapse — the partial
   leaves the button out otherwise, so the dashboard's single-link nav does not
   grow a hamburger that opens one link above a pill strip holding eight. Shown
   only under .chrome-js: a hamburger is a promise that something will open. */
.pt-nav__toggle {
  display: none; align-items: center; gap: 8px;
  background: transparent; border: 1px solid var(--border);
  border-radius: var(--r); cursor: pointer;
  color: var(--text2); font: inherit; font-size: 13.5px; font-weight: 500;
  padding: 8px 14px;
}

:where(.pt-acct__btn, .pt-nav__caret, .pt-nav__toggle):focus-visible,
:where(.pt-top__link, .pt-top__signin, .pt-nav__link, .pt-menu__link, .pt-nav__panel a):focus-visible {
  outline: 2px solid var(--base-l);
  outline-offset: 2px;
  border-radius: var(--r);
}

/* Both rows stick above the pills breakpoint; below it only the dark strip
   does (the next block), since the nav row there is a breadcrumb trail or a
   category list that would eat a phone's height for nothing. */
@media (min-width: 769px) {
  /* Both rows stick, as one block: the dark strip carries the cart and the
     account menu, which are what a reader scrolling a long page reaches for.
     .pt-chrome is a direct child of <body>, so it stays pinned for the whole
     page rather than only within a short parent. */
  .pt-chrome { position: sticky; top: 0; z-index: 120; }
  .pt-chrome.is-stuck { box-shadow: var(--shadow-nav-stuck); }
  /* WHAT ANYTHING ELSE ON THE PAGE HAS TO STICK BELOW. Raised from the 0px
     tokens.css declares, because this is the only breakpoint at which the nav
     is pinned and therefore the only one at which its height is an offset.
     The raise stays HERE, beside the rule it is conditional on: moved to
     tokens.css it would be a second copy of this breakpoint, free to drift
     from the one that decides whether the nav sticks at all.
     The nav is the only thing pinned to the viewport top, so every other
     sticky element on a portal page has to start where the nav ends or it
     scrolls underneath and loses its first rows — which is exactly what the
     product page's buy panel did with its own top: 20px.
     Published as a token rather than repeated as a number: the nav's height
     is NOT a constant. Its min-height is 54px, but it carries the breadcrumb
     trail, which wraps on a long product name, so a page's nav can be two
     rows where another page's is one, and the shop front has no nav row at
     all. chrome.js keeps --pt-chrome-h measured (both rows together); the
     124px here is strip + one-row nav, so a page with no script still clears
     the header instead of sliding under it. */
  :root { --pt-sticky-top: var(--pt-chrome-h, 124px); }
  /* Opening on hover as well as on click is what makes a menu bar feel like
     one. Guarded on a fine pointer: on a touchscreen "hover" fires on the tap
     that is also following the category's own link, which opens a panel the
     visitor never sees on a page they are already leaving. */
  @media (hover: hover) and (pointer: fine) {
    .pt-nav__item:hover .pt-nav__panel {
      opacity: 1; visibility: visible; transform: translateY(0) scale(1);
    }
  }
}

@media (max-width: 768px) {
  /* The row becomes a stacked list. Note what is NOT conditional on
     .chrome-js: the layout. Only the two collapses are, so the no-script
     rendering is this same list, fully expanded, rather than an empty
     band with a dead hamburger in it. */
  .pt-nav__inner { flex-wrap: wrap; padding-block: 10px; }
  .pt-nav__links {
    width: 100%; order: 3;
    flex-direction: column; align-items: stretch; gap: 0;
    padding-block-end: 10px;
  }
  .pt-nav__link { padding: 12px 4px; }
  .pt-nav__link::before { display: none; }
  .pt-nav__item { flex-wrap: wrap; justify-content: space-between; }
  .pt-nav__item .pt-nav__link { flex: 1; }
  .pt-nav__caret { padding: 12px 8px; }
  /* The panel stops floating and becomes an accordion: a 264px card anchored
     to a full-width row would hang off the side of the screen. */
  .pt-nav__panel {
    position: static; width: 100%; order: 3;
    box-shadow: none; border: 0; border-radius: 0;
    padding: 0 0 8px 14px; margin: 0;
    transform: none; transition: none;
    opacity: 1; visibility: visible;
  }

  /* And where there was nothing to collapse, there is nothing to keep. The
     dashboard's row is one link to the shop plus the membership CTA, and at
     this width both destinations are already on the page — the shop in the
     footer, membership in the pill strip, the drawer and the account menu —
     so the row is a lone button on an otherwise empty band. Taken away
     whole, for the same reason the toggle is: hiding what is inside a row
     leaves its 54px behind. The shop's row has categories, hence a toggle,
     and keeps its place. */
  .pt-nav--bare { display: none; }
  /* Off in the flow, not merely invisible: a hidden row that still occupies
     54px is how the nav ends up as a blank band with nothing in it. */
  .chrome-js .pt-nav__toggle { display: inline-flex; }
  /* Scoped to --collapsible: a nav with no toggle must keep its links,
     or its only destination is unreachable on a phone. */
  .chrome-js .pt-nav--collapsible .pt-nav__links { display: none; }
  .chrome-js .pt-nav--collapsible .pt-nav__links.is-open { display: flex; }
  .chrome-js .pt-nav__panel { display: none; }
  .chrome-js .pt-nav__item.is-open .pt-nav__panel { display: block; }
}

/* On a phone the dark strip stays in view — it carries the cart and the
   account menu. .pt-chrome sticks as a unit above 768px, and a sticky child
   only sticks within its parent, so here the wrapper steps out of the box
   tree and the strip sticks against <body> instead. Everything else that
   sticks on a phone (the product tabs) starts below it via
   --pt-sticky-top; chrome.js measures the strip as --pt-top-h, and 60px is
   its height at this width without script. */
@media (max-width: 768px) {
  .pt-chrome { display: contents; }
  .pt-top { position: sticky; top: 0; }
  .pt-chrome.is-stuck .pt-top { box-shadow: var(--shadow-nav-stuck); }
  :root { --pt-sticky-top: var(--pt-top-h, 60px); }
}

/* Below 640px the two rows stack their own contents rather than truncating:
   the brand keeps its name and the account button drops to its avatar. */
@media (max-width: 640px) {
  .pt-top__inner, .pt-nav__inner { padding: 0 11px; gap: 8px; }
  .pt-nav__inner { padding-block: 10px; }
  .pt-top__inner { min-height: 60px; }
  .pt-acct__name { display: none; }
  .pt-top__link { display: none; }
  /* The third control on this strip, and the one that tipped it over: with the
     switcher's label showing, .pt-top__right measured 217px inside a 390px
     viewport and "Sign in" was cut off mid-word on every page. The globe and
     caret alone still read as a language control, the same way the avatar
     alone reads as an account. This bar has no room left at 390px. */
  .pt-lang__name { display: none; }
  /* Squeezed to a two-line "Sign / in" once the bar tightened. */
  .pt-top__signin { white-space: nowrap; padding: 8px 13px; }
  .pt-menu { width: min(264px, calc(100vw - 28px)); }
  /* Both cards hang from the end of the whole cluster, not from their own
     button. A 264px card anchored to a button partway along the strip ran
     past the screen edge: in Arabic the guest's closed language card made
     checkout scroll sideways (6px at 390, 44px at 320), and at 320 an open
     card started off-screen. The cluster's end sits on the strip's gutter,
     so the card always fits. The store bar does the same below 400px. */
  .pt-top__right { position: relative; }
  .pt-lang, .pt-acct { position: static; }

  /* THE SIGNED-IN BAR: a visitor also gets .mobile-menu-btn on the left, on
     top of brand + switcher + cart + account. An earlier version of this
     rule freed room by dropping the wordmark to a bare icon below 640px —
     it fit, but a lone mark with no name reads as "no logo" rather than as
     a simplified one. The wordmark stays; the room for it comes from
     trimming every other control on the bar by a few px instead (padding,
     gaps, the avatar and hamburger), none of which needed its full desktop
     size on a 60px-tall mobile strip anyway. */
  .mobile-menu-btn { width: 32px; height: 32px; }
  .pt-lang__btn { gap: 4px; padding: 6px 7px; }
  .pt-cart { padding: 4px; }
  .pt-acct__btn { gap: 7px; padding: 4px 6px 4px 4px; }
  .pt-acct__avatar { width: 27px; height: 27px; }
  .pt-top__right { gap: 4px; }
  /* The fused logo (icon + "startmy marketplace" spelled out on one line) is
     wide at its desktop size, so it scales with the viewport here: 22px at
     360px (a common Android width) up to 26px, which is what the shop bar's
     switcher/cart/account cluster leaves room for at each width.
     flex-shrink:0, on both the image and the link around it, is
     load-bearing: without it flexbox shrinks a replaced element by
     squashing its box rather than by scaling it, so a tight viewport
     would distort the logo (and run it into the switcher) instead of
     legibly holding its size. A viewport narrower than 360px scrolls this
     one strip horizontally, which is a visible seam but not a broken one —
     the alternative is the same distortion .pt-top__brand's min-width:0
     exists to avoid for a plain-text brand (see .brand-word below, still
     the shape a white-labelled deployment's wordmark-only brand needs). */
  .pt-top__brand .pt-top__logo { height: clamp(20px, 6.2vw, 26px); flex-shrink: 0; }
  /* The dashboard's bar also carries the menu button, so its logo keeps the
     20px that fits beside it at 360px. */
  .pt-top__inner:has(.mobile-menu-btn) .pt-top__logo { height: 20px; }
  .pt-top__brand:has(.pt-top__logo) { min-width: auto; flex-shrink: 0; }
  /* The dark bar's exception: a long "Sign in" (Arabic's is 110px against
     English's 71px) left a guest's strip 6px too wide at 390 and 21px at
     360, and checkout scrolled sideways. Here the logo gives up only the
     width the strip lacks, by scaling as a whole: the inline <svg> keeps
     its viewBox ratio inside a narrower box (preserveAspectRatio's default),
     and object-fit does the same for the PNG fallback, so it shrinks rather
     than squashes. Where the strip already fits, as in English, nothing
     changes. The 120px floor keeps the wordmark legible; below it, under
     360px, the strip scrolls as described above. The store bar has its own
     fit guard in chrome.js and is left alone. */
  .pt-chrome:not(.pt-chrome--store) .pt-top__brand:has(.pt-top__logo) { flex-shrink: 1; min-width: 120px; }
  .pt-chrome:not(.pt-chrome--store) .pt-top__brand .pt-top__logo { flex-shrink: 1; min-width: 0; max-width: 100%; object-fit: contain; }
  .pt-top__brand { min-width: 0; }
  .pt-top__brand .brand-word {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
  }
}

/* An animated underline and a menu that scales into place are decoration. The
   menus still open and close — only the movement goes. */
@media (prefers-reduced-motion: reduce) {
  .pt-nav__link::before,
  .pt-menu, .pt-nav__panel,
  .pt-acct__caret, .pt-nav__caret, .pt-nav__cta,
  .pt-foot__cols nav a, .pt-foot__col nav a {
    transition: none;
  }
  .pt-nav__cta:hover { transform: none; }
  .pt-foot__cols nav a:hover, .pt-foot__col nav a:hover { transform: none; }
}

.pt-foot { background: var(--bg2); border-block-start: 1px solid var(--border); margin-block-start: 64px; }
/* One line of colour across the seam between page and footer — the only
   accent on an otherwise flat, bordered footer, and enough on its own to read
   as a deliberate edge rather than the same hairline every card on the page
   already draws. --base-l is the accent that differs between the dashboard's
   and the shop's skins (tokens.css re-declares it under body.shop); --brand-mark
   is the one that deliberately never does, per its own comment there — so the
   line always carries one colour that matches the surface it sits on and one
   that stays the brand's regardless of which skin is loaded. */
.pt-foot::before {
  content: '';
  display: block;
  height: 3px;
  background: linear-gradient(90deg, var(--base-l) 0%, var(--brand-mark) 100%);
}
/* Same 78rem as .pt-top__inner/.pt-nav__inner above and the shop's own
   .shop-main, so the header, page content and footer share one left/right
   edge instead of each column picking its own. */
.pt-foot__inner { max-width: 78rem; margin: 0 auto; padding: 64px 20px 28px; }
/* The brand column runs wider than the three link columns beside it — a
   tagline wrapped to match a nav column's width read as cramped where the
   links had room to spare. */
.pt-foot__cols {
  display: grid;
  grid-template-columns: minmax(220px, 1.7fr) repeat(auto-fit, minmax(140px, 1fr));
  gap: 40px;
}
/* .pt-top__brand is styled for the dark strip; on the footer's pale
   background it needs its own colour rather than on-dark's white-on-white. */
.pt-foot .pt-top__brand { color: var(--text); font-size: 17px; }
.pt-foot .pt-top__brand .pt-top__logo { height: 30px; }
.pt-foot__cols p { color: var(--text2); font-size: 13.5px; line-height: 1.6; margin: 12px 0 0; max-width: 40ch; }
/* The reassurance pill under the brand tagline. Same convention checkout's
   own .co-trust-row__item already set — icon in --teal, chip fill in
   --teal-bg/--teal-border — so "secure" reads as the one colour it means
   everywhere it appears rather than the footer inventing a second one. */
.pt-foot__trust {
  display: inline-flex; align-items: center; gap: 6px;
  margin-block-start: 16px; padding: 6px 12px;
  background: var(--teal-bg); border: 1px solid var(--teal-border);
  border-radius: 999px;
  color: var(--teal-d); font-size: 12px; font-weight: 600;
}
.pt-foot__trust svg { flex: none; color: var(--teal); }
/* h3 only exists in the shop's richer footer (_footer.php); the dashboard's
   single nav (portal_footer.php) has no heading and is untouched by it. */
/* Set as the store-look mockup sets them: small, widely spaced capitals in
   the body face, so the links under them carry the column. */
.pt-foot__col h3 {
  font-weight: 600; font-size: 12px;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--text2); margin: 0 0 14px;
}
.pt-foot__cols nav, .pt-foot__col nav { display: flex; flex-direction: column; gap: 11px; }
.pt-foot__cols nav a, .pt-foot__col nav a {
  color: var(--text2); text-decoration: none; font-size: 13.5px;
  width: fit-content; transition: color .18s ease, transform .18s ease;
}
.pt-foot__cols nav a:hover, .pt-foot__col nav a:hover { color: var(--base); transform: translateX(2px); }
/* The shop footer's Company and Legal columns, wrapped together so the phone
   breakpoint below can give them one shared row instead of each stacking to
   the full width the single-column layout freed up — see _footer.php's own
   comment on the wrapper. display:contents keeps the wrapper out of the grid
   entirely above that breakpoint, so desktop's layout is exactly what it was
   with no wrapper at all. */
.pt-foot__col-pair { display: contents; }
.pt-foot__bottom {
  margin-block-start: 44px; padding-block-start: 20px;
  border-block-start: 1px solid var(--border);
  color: var(--text3); font-size: 12.5px;
  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px;
}
/* The official-site line: a full-width row of its own under the
   copyright, so it wraps as one sentence instead of squeezing the legal links. */
.pt-foot__official { flex: 0 0 100%; width: 100%; order: 3; margin: 0; line-height: 1.5; }
.pt-foot__official > span { display: block; max-width: 72ch; }
/* The bottom bar's own small nav — Terms/Privacy on the dashboard's simpler
   footer, which (unlike the shop's) has no dedicated Legal column to carry
   them. A middle dot between entries rather than a border: at 12.5px a
   1px divider reads as a stray line, the dot reads as punctuation. */
.pt-foot__legal { display: flex; align-items: center; gap: 14px; list-style: none; margin: 0; padding: 0; }
.pt-foot__legal a { color: var(--text3); text-decoration: none; transition: color .18s ease; }
.pt-foot__legal a:hover { color: var(--base); }
.pt-foot__legal a + a::before { content: '·'; margin-inline-end: 14px; color: var(--text3); }
@media (max-width: 640px) {
  .pt-foot__inner { padding: 44px 20px 24px; }
  .pt-foot__cols { grid-template-columns: 1fr; gap: 32px; }
  /* Company and Legal side by side, one row, rather than each stacking to
     the full column width a single-column grid gives everything else here —
     two short lists of links read as a pair, not as two unrelated sections. */
  .pt-foot__col-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 32px 20px; }
  .pt-foot__bottom { justify-content: center; text-align: center; }
  .pt-foot__official > span { margin-inline: auto; }
}

/* The wordmark is one inline box, so a flex parent's gap spaces it from the
   logo mark and never falls between its own words. See portal_wordmark(). */
.brand-word { display: inline; white-space: nowrap; }

/* The wordmark is one inline box, so a flex parent's gap spaces it from the
   logo mark and never falls between its own words. See portal_wordmark(). */
.brand-word { display: inline; white-space: nowrap; }

/* The wordmark's accent. Lives here because both the shop and the dashboard
   render the same wordmark and only this sheet is loaded by both. On .pt-top's
   dark strip --amber is the light-ground value and reads at 3.37:1, so that
   surface takes --amber-l. */
.brand-tld { color: var(--amber); }
.pt-top .brand-tld { color: var(--brand-mark); }

/* ── The store header and footer (homepage v6) ────────────────────────────
   Every shop page (portal_chrome() without sidebar_toggle) wears the light
   sticky bar from the store-look mockup: wordmark, the home's five sections,
   currency/language, Sign in, cart and "Find your store", with a drawer on a
   phone. The dashboard keeps the dark strip above. Colours are the --store-*
   tokens (tokens.css). */
.pt-chrome--store .pt-top {
  background: var(--store-card-88);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  color: var(--store-ink);
  border-block-end: 1px solid var(--store-line);
  font-family: var(--store-body);
}
/* The home's 1160px column; elsewhere the bar keeps the pages' own 78rem. */
body.shop--home .pt-chrome--store .pt-top__inner { max-width: 1160px; }
.pt-chrome--store .pt-top__inner { min-height: var(--store-hdr); padding: 0 max(16px, env(safe-area-inset-right, 0px)) 0 max(16px, env(safe-area-inset-left, 0px)); gap: 28px; }
.pt-chrome--store .pt-top__brand { color: var(--store-ink); }
.pt-chrome--store .pt-top__brand .pt-top__logo { height: 26px; }
.pt-chrome--store .pt-primary { margin-inline-start: 8px; gap: 4px; }
.pt-chrome--store .pt-primary__link { color: var(--store-ink); font-weight: 500; font-size: .95rem; padding: .5em .6em; border-radius: 8px; white-space: nowrap; }
.pt-chrome--store .pt-primary__link:hover { color: var(--store-ink); background: var(--store-bg2); }
.pt-chrome--store .pt-top__right { margin-inline-start: auto; gap: 12px; font-size: .9rem; }
.pt-chrome--store .pt-lang__btn,
.pt-chrome--store .pt-acct__btn,
.pt-chrome--store .pt-cart,
.pt-drawer-btn,
.pt-drawer__close {
  height: 40px; min-width: 40px; border-radius: 10px; border: 1px solid var(--store-line);
  background: var(--store-card); color: var(--store-ink);
  display: inline-flex; align-items: center; justify-content: center; gap: 6px; cursor: pointer;
}
.pt-chrome--store .pt-lang__btn { padding: 0 10px; font-weight: 600; font-size: .85rem; }
.pt-chrome--store .pt-acct__btn { padding: 0 8px 0 4px; }
.pt-chrome--store .pt-cart { padding: 0; }
.pt-chrome--store .pt-lang__btn:hover,
.pt-chrome--store .pt-acct__btn:hover,
.pt-chrome--store .pt-cart:hover,
.pt-drawer-btn:hover { background: var(--store-bg2); border-color: var(--store-line); color: var(--store-ink); }
.pt-chrome--store .pt-cart__qty { background: var(--store-gold-l); color: var(--store-gold-ink); top: -5px; inset-inline-end: -5px; }
/* The mockup's order: language/currency, Sign in (or the account), cart, Find your store, menu. */
.pt-chrome--store .pt-lang { order: 1; }
.pt-chrome--store .pt-top__signin, .pt-chrome--store .pt-acct { order: 2; }
.pt-chrome--store .pt-cart { order: 3; }
.pt-find { order: 4; }
.pt-drawer-btn { order: 5; }
.pt-chrome--store .pt-top__signin { display: none; color: var(--store-ink); background: none; border: 0; font-weight: 600; padding: 0 2px; }
.pt-chrome--store .pt-top__signin:hover { background: none; color: var(--store-accent); }
.pt-find {
  display: none; align-items: center; padding: .72em 1.15em; border-radius: 10px;
  background: var(--store-accent); color: var(--store-accent-ink); border: 1px solid var(--store-accent);
  font: 600 .95rem/1.2 var(--store-body); text-decoration: none; white-space: nowrap;
  transition: transform .15s;
}
.pt-find:hover { transform: translateY(-1px); color: var(--store-accent-ink); }
/* Neither button may wrap or be cut: at 1080 to 1440 'Sign in' broke onto two
   lines and 'Find your store' ran off the bar's end (cto-0418). */
.pt-chrome--store .pt-top__signin, .pt-chrome--store .pt-acct__btn { white-space: nowrap; flex: none; }
.pt-find { flex: none; }
/* Below 1440 the language/currency button is its globe alone: the menu it
   opens still names both, and its label holds them for a screen reader. */
@media (max-width: 1439.98px) {
  .pt-chrome--store .pt-lang__name { display: none; }
}
/* The account chip is its avatar on the store bar at every width: the row
   stops growing at 78rem (1160px on home), so a 15-character name never
   fits beside the sections in de or ru. The menu it opens names the
   account. */
.pt-chrome--store .pt-acct__name { display: none; }
/* The full row (five sections inline, Sign in or the account, cart, Find
   your store) from 1160px. QA's band scan (qa-20261101-0420) found it needs
   1134px (guest en) to 1276px (signed-in de) at 1080, where it used to come
   in, so 'Find your store' ran off the end and 'Sign in' wrapped. With the
   account chip at its avatar and the language button at its globe below,
   every page and context (guest and signed in, en/ar/de, and ru) fits by
   1148px, on home's 1160px row as on the others. Below 1160 the drawer
   button carries the sections. The dashboard's .pt-primary keeps 1080. */
@media (min-width: 1160px) {
  .pt-chrome--store .pt-top__signin, .pt-find { display: inline-flex; }
  .pt-drawer-btn { display: none; }
}
@media (max-width: 1159.98px) {
  .pt-chrome--store .pt-primary { display: none; }
}
/* chrome.js's fit guard, for a locale whose labels still overflow the row
   (Filipino, from 1160): first the language button goes to its globe at any
   width, then, if that is not enough, the bar takes its below-1160 state. */
.pt-chrome--store .pt-top--tight .pt-lang__name { display: none; }
.pt-chrome--store .pt-top--collapsed .pt-primary,
.pt-chrome--store .pt-top--collapsed .pt-top__signin,
.pt-chrome--store .pt-top--collapsed .pt-find { display: none; }
.pt-chrome--store .pt-top--collapsed .pt-drawer-btn { display: inline-flex; }
@media (max-width: 640px) {
  .pt-chrome--store .pt-top__inner { gap: 10px; }
  .pt-chrome--store .pt-top__right { gap: 8px; }
  .pt-chrome--store .pt-top__brand .pt-top__logo { height: 22px; }
}
@media (max-width: 430px) {
  .pt-chrome--store .pt-top__brand .pt-top__logo { height: 18px; }
}
/* Below 340 a guest's row (logo, language and currency, cart, menu) needs
   10px more than the bar has, so its end ran into the 16px gutter: one more
   logo step, still a legible wordmark. */
@media (max-width: 339.98px) {
  .pt-chrome--store .pt-top__brand .pt-top__logo { height: 16px; }
}
/* The account chip on the light store bar. Its avatar was styled for the
   dashboard's dark strip (--on-dark on --on-dark-14), which on the light
   button left the initials invisible; here it takes the store's own blue. */
.pt-chrome--store .pt-acct__avatar { background: var(--store-accent); color: var(--store-accent-ink); }
/* Signed in on a narrow phone the strip carries one control more than a
   guest's (the account chip), and the brand cannot shrink, so below 400px
   the chip drops to its avatar and, once the chip is there, the language and
   currency switch leaves the bar: the drawer carries both. A guest keeps it. */
@media (max-width: 399.98px) {
  .pt-chrome--store .pt-acct__btn { padding: 0; width: 40px; }
  .pt-chrome--store .pt-acct__btn .pt-acct__caret { display: none; }
  .pt-chrome--store .pt-top__right:has(.pt-acct) .pt-lang { display: none; }
  /* The account card (264px) hangs from its button's end edge; with the
     button down to 40px that edge sits too far in and the card ran 16px off
     the screen. Here it hangs from the end of the whole cluster instead,
     which sits on the bar's gutter. */
  .pt-chrome--store .pt-top__right { position: relative; }
  .pt-chrome--store .pt-acct { position: static; }
  /* The guest's language card (264px) had the same fault from its 54px
     button; it hangs from the cluster's end too. Signed in, .pt-lang is
     hidden here, so only guests see this. */
  .pt-chrome--store .pt-lang { position: static; }
}
:where(.pt-find, .pt-drawer-btn, .pt-drawer a, .pt-drawer button):focus-visible { outline: 3px solid var(--store-accent); outline-offset: 2px; }

/* Checkout's focused store bar (review #4, portal_chrome()'s 'checkout'): the
   wordmark, a lock and "Secure checkout", then the currency/language switch
   and Sign in or the account. No sections, cart, "Find your store" or drawer,
   so Sign in shows at every width (the store bar keeps it for 1160 up only,
   where its drawer carries it below). On a phone the lock stands alone and
   its words stay for a screen reader. */
.pt-chrome--checkout .pt-secure {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  color: var(--store-muted); font-size: .9rem; font-weight: 500; white-space: nowrap;
}
.pt-chrome--checkout .pt-top__signin,
.pt-chrome--checkout .pt-top--collapsed .pt-top__signin { display: inline-flex; }
@media (max-width: 519.98px) {
  .pt-chrome--checkout .pt-secure__text {
    position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
}

/* The phone drawer: a panel from the end edge over a scrim. */
.pt-drawer { position: fixed; inset: 0; z-index: 140; background: var(--store-scrim); display: flex; justify-content: flex-end; }
.pt-drawer[hidden] { display: none; }
.pt-drawer__panel {
  width: min(340px, 88vw); height: 100%; background: var(--store-card); color: var(--store-ink);
  padding: calc(16px + env(safe-area-inset-top, 0px)) max(16px, env(safe-area-inset-right, 0px)) 24px 20px;
  display: flex; flex-direction: column; gap: 6px; overflow: auto; font-family: var(--store-body);
}
.pt-drawer__close { align-self: flex-end; }
.pt-drawer__link { color: var(--store-ink); text-decoration: none; font-weight: 600; padding: .8em .2em; border-block-end: 1px solid var(--store-line); }
.pt-drawer__set { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 12px; color: var(--store-muted); font-size: .88rem; }
.pt-drawer__opt { color: var(--store-ink); text-decoration: none; border: 1px solid var(--store-line); border-radius: 8px; padding: .3em .7em; font-weight: 600; }
.pt-drawer__opt.is-current { border-color: var(--store-accent); color: var(--store-accent); }
.pt-drawer__find {
  margin-top: 14px; display: inline-flex; justify-content: center; padding: .72em 1.15em; border-radius: 10px;
  background: var(--store-accent); color: var(--store-accent-ink); font-weight: 600; text-decoration: none;
}
.pt-drawer-open { overflow: hidden; }

/* The store footer. */
.pt-foot--store { background: var(--store-bg2); border-block-start: 1px solid var(--store-line); font-family: var(--store-body); margin-block-start: 0; }
.pt-foot--store::before { display: none; }
.pt-foot--store .pt-foot__inner { max-width: 1160px; padding: 48px 16px 0; }
.pt-foot--store .pt-foot__cols { display: grid; gap: 28px; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.pt-foot--store .pt-foot__logo { display: inline-flex; color: var(--store-ink); }
.pt-foot--store .pt-foot__mark { height: 26px; width: auto; max-width: 100%; }
.pt-foot--store .pt-foot__cols p { color: var(--store-muted); font-size: .88rem; }
.pt-foot--store .pt-foot__col h3 { margin: 0 0 10px; font: 600 .78rem/1 var(--store-body); letter-spacing: .12em; text-transform: uppercase; color: var(--store-muted); }
.pt-foot--store .pt-foot__col nav { gap: 8px; }
.pt-foot--store .pt-foot__col nav a, .pt-foot__settings a { color: var(--store-ink); text-decoration: none; font-size: .95rem; }
.pt-foot--store .pt-foot__col nav a:hover, .pt-foot__settings a:hover { color: var(--store-accent); transform: none; }
.pt-foot__settings { list-style: none; padding: 0; margin: 0; display: grid; gap: 8px; color: var(--store-ink); font-size: .95rem; }
.pt-foot__settings a[aria-current] { font-weight: 600; }
.pt-foot--store .pt-foot__bottom { margin-top: 48px; padding-block: 18px; border-block-start: 1px solid var(--store-line); color: var(--store-muted); font-size: .85rem; display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px; }
.pt-foot__cc { display: inline-flex; gap: 4px; align-items: center; color: inherit; text-decoration: underline; text-underline-offset: 3px; }
@media (max-width: 599px) {
  .pt-foot--store .pt-foot__cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pt-foot--store .pt-foot__brand { grid-column: 1 / -1; }
}
