/* ================================================================================================
   STORE.CSS — Direction C, "Clinical Utility"
   Design system for /store, /university and /courses.

   Loaded AFTER css/site.css on every storefront.layout page (see resources/views/storefront/
   layout.blade.php). It does not redeclare anything site.css already owns site-wide — it reads
   site.css's :root token ramp (--brand, --gold, --green, --bg, --surface, the --ink ramp, --line, --font, --ease,
   --shell/--z-*) and REUSES site.css's header/nav/mega-menu/drawer/footer components verbatim,
   unchanged, so the store is unmistakably the same site as the rest of dev.sesc.net. This file
   only adds what site.css does not already have: the commerce component vocabulary (product
   cards, listings, cart, checkout, University, Courses) plus a small number of additive refinements
   to shared components, each called out below.

   Ported from public/design/store-c.html (client-approved "Direction C" mockup), reorganised from
   one flat <style> block into the grouped sections below with the mockup's own class names kept
   verbatim wherever practical — see this stage's return value for the full published contract and
   the .c1-, .c2- and .prod- naming-collision note.
   ================================================================================================ */

/* ------------------------------------------------------------------------------------------------
   0. TOKENS — additive only. Everything else (brand, gold, green, bg, surface, the ink ramp, line, font, ease,
   shell/z-*) already exists in site.css's :root and is reused as-is, not redeclared here.
   ------------------------------------------------------------------------------------------------ */
:root {
  /* --danger is the one net-new colour Direction C needs (out-of-stock / sold-out / remove),
     built with the same color-mix formula as every other tint in site.css. Used only as small
     text and dot accents — never a large fill — per the "muted, not alarming" brief. */
  --danger: oklch(0.5 0.14 26);
  --danger-tint: color-mix(in oklch, var(--danger) 10%, white 90%);

  /* Card corner radius and section padding are store-specific: denser than the marketing site's
     section rhythm (site.css's bare `section` is 48-82px block padding; commerce sections don't
     need that much air — see .store-sec below, which opts in per-section rather than overriding
     `section` globally so marketing-style sections dropped into a store page are unaffected). */
  --radius-card: 14px;
  --section-pad: clamp(26px, 3.6vw, 46px);
}

/* ------------------------------------------------------------------------------------------------
   1. PAGE SHELL — section rhythm, heading rows, and the two link/label primitives almost every
   store section uses.
   ------------------------------------------------------------------------------------------------ */
section.store-sec { padding-block: var(--section-pad); }
.store-sec.no-pad-top { padding-top: 0; }

.sec-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 16px; flex-wrap: wrap; margin-bottom: 18px; }
.sec-head h2 { font-size: clamp(1.1rem, 1.8vw, 1.35rem); color: var(--brand); font-weight: 800; }
.sec-head p { margin-top: 4px; color: var(--ink-faint); font-size: 0.83rem; }

.link-more { font-size: 0.79rem; font-weight: 700; color: var(--brand); display: inline-flex; align-items: center; gap: 5px; }
.link-more svg { width: 13px; height: 13px; transform: rotate(-90deg); }
.link-more:hover { text-decoration: underline; }

/* Chip row — used for the listing's active-filter summary, University's track filter and any
   other single-select/multi-select pill row. */
.chiprow { display: flex; gap: 8px; flex-wrap: wrap; }
.chip { display: inline-flex; align-items: center; gap: 6px; padding: 8px 15px; min-height: 38px; border-radius: 999px; border: 1.5px solid var(--line); background: #fff; color: var(--ink-soft); font-size: 0.81rem; font-weight: 700; cursor: pointer; transition: border-color 160ms ease, color 160ms ease, background 160ms ease; }
.chip:hover { border-color: var(--brand); color: var(--brand); }
.chip:active { transform: scale(0.97); }
.chip.is-active { background: var(--brand); border-color: var(--brand); color: #fff; }
.chip .n { font-weight: 600; opacity: 0.75; }

/* ------------------------------------------------------------------------------------------------
   2. TITLEBAND, BADGE-RING, MONOGRAM, BREADCRUMB, TRUST STRIP
   .titleband / .badge-ring / .monogram / .crumb / .crumb-static are REUSED VERBATIM from site.css
   — not redeclared here. Every store page's title band (store home, category, product, checkout,
   University, course) uses that exact same navy band + circular badge + breadcrumb the marketing
   site already renders, so the two never drift into two different "title band" designs.
   Only .trust-strip is net new to the store: a compact fact bar directly under the title band.
   ------------------------------------------------------------------------------------------------ */
.trust-strip { background: var(--brand-tint); border-bottom: 1px solid var(--line); }
.trust-strip .shell { display: flex; flex-wrap: wrap; gap: 5px 26px; padding-block: 11px; }
.trust-item { display: flex; align-items: center; gap: 8px; font-size: 0.79rem; font-weight: 600; color: var(--ink-soft); }
.trust-item svg { width: 16px; height: 16px; color: var(--brand); flex: none; }
.trust-item a { color: var(--brand); font-weight: 700; }
.trust-item a:hover { text-decoration: underline; }

/* Refinement of site.css's shared .checks/.check/.tick trust-checklist component for the denser
   two-line "label + description" usage on the product detail page (site.css's own version is a
   single line per row with a bottom-border divider, sized for the marketing "why choose SESC"
   block). Same tokens, tighter spacing, no divider. Scoped to nothing extra deliberately: store
   pages should get the compact version wherever .checks appears, the same way store.css refining
   .btn below applies everywhere in the store, not just on one page. */
.checks { gap: 2px; margin-top: 22px; border-top: 1px solid var(--line); padding-top: 16px; }
.check { padding: 8px 2px; border-bottom: 0; }
.check b { display: block; font-size: 0.87rem; font-weight: 700; color: var(--ink); }
.check span.d { display: block; font-size: 0.81rem; color: var(--ink-faint); }

/* ------------------------------------------------------------------------------------------------
   3. BUTTONS — intentionally overrides site.css's .btn/.btn-primary/.btn-outline for the store
   context. site.css's versions are the marketing hero's CTA (gold-on-navy, no disabled/active
   states, no minimum touch target — it never needed them next to a static headline). Commerce
   buttons are pressed constantly, need a real :active tactile state, a :disabled state that isn't
   "just dimmer", and a guaranteed 44px target. store.css loads after site.css, so this table wins
   inside every storefront-rendered page without touching a single marketing template.
   ------------------------------------------------------------------------------------------------ */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 12px 22px; min-height: 44px; border-radius: 999px; font-weight: 800; font-size: 0.87rem; border: 2px solid transparent; cursor: pointer; transition: transform 160ms var(--ease), background 160ms ease, color 160ms ease, border-color 160ms ease, box-shadow 200ms ease; }
.btn:active { transform: scale(0.97); }
.btn:disabled { cursor: not-allowed; opacity: 0.55; transform: none !important; }
.btn svg { width: 16px; height: 16px; flex: none; }

.btn-primary { background: var(--green); color: #fff; box-shadow: 0 12px 22px -14px rgba(0,157,79,0.55); }
.btn-primary:hover:not(:disabled) { background: var(--green-deep); transform: translateY(-1px); }

.btn-navy { background: var(--brand); color: #fff; box-shadow: 0 12px 22px -14px rgba(25,79,144,0.5); }
.btn-navy:hover:not(:disabled) { background: var(--brand-deep); transform: translateY(-1px); }

.btn-outline { background: #fff; border-color: var(--brand); color: var(--brand); }
.btn-outline:hover:not(:disabled) { background: var(--brand); color: #fff; }

.btn-wa { background: var(--wa-green); color: #fff; box-shadow: 0 12px 22px -14px rgba(37,211,102,0.55); }
.btn-wa:hover:not(:disabled) { background: #1fb959; transform: translateY(-1px); }

.btn-block { width: 100%; }
.btn-sm { padding: 8px 14px; min-height: 38px; font-size: 0.78rem; }

/* ------------------------------------------------------------------------------------------------
   4. HEADER EXTRAS — the store's additions to site.css's header (cart tool + badge, and the
   account tool's logged-in dropdown). Everything else in the header/nav/mega-menu is site.css's
   own .site-header/.nav/.nav-link/.tool/.account/.drop/.mega/.mega-grid/.burger, reused as-is.
   ------------------------------------------------------------------------------------------------ */
.tool-cart { position: relative; }
.cart-badge { position: absolute; top: -6px; right: -6px; min-width: 18px; height: 18px; padding: 0 4px; border-radius: 999px; background: var(--gold); color: var(--brand-deeper); font-size: 0.65rem; font-weight: 800; display: grid; place-items: center; transition: transform 200ms var(--ease); font-variant-numeric: tabular-nums; }
.cart-badge.is-pulse { animation: cartBadgePulse 420ms var(--ease); }
@keyframes cartBadgePulse { 0% { transform: scale(1); } 40% { transform: scale(1.35); } 100% { transform: scale(1); } }

/* Small addition to site.css's .mega-edu-lane (the Education mega's two University/Courses
   lanes): a one-line description under each lane heading. site.css defines the lane's width but
   never a <p> inside it, because the coming-soon version it originally shipped had no copy. */
.mega-edu-lane p { font-size: 0.78rem; color: var(--ink-faint); padding: 0 6px; max-width: 22ch; }

/* Net-new dropdown for the Shop nav item (product categories) and the logged-in Account tool.
   Reuses site.css's own .drop panel/.drop a styling; only the badge and the hover-root are new. */
.drop-count { font-size: 0.72rem; font-weight: 700; color: var(--ink-faint); }
.drop-static { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; padding: 10px 12px; border-radius: 8px; font-size: 0.87rem; font-weight: 600; color: var(--ink-soft); background: none; border: 0; font-family: inherit; text-align: left; cursor: pointer; }
.drop-static:hover { background: var(--brand-tint); color: var(--brand); }

/* .account-wrap is the positioned root for the logged-in Account tool's .drop panel — site.css's
   hover-opens-.drop mechanism is `li:hover > .drop`/`li:focus-within > .drop`; the header's
   account tool sits in .header-tools, not an <li>, so this repeats that exact mechanism one level
   up rather than inventing a second one. */
.account-wrap { position: relative; }
.account-wrap:hover > .drop, .account-wrap:focus-within > .drop { opacity: 1; visibility: visible; transform: none; }
.account-wrap > .drop { right: 0; left: auto; min-width: 190px; }

/* ------------------------------------------------------------------------------------------------
   5. CATEGORY TILES — store home's "Shop by category" section.
   ------------------------------------------------------------------------------------------------ */
.cat-tiles { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; }
@media (max-width: 640px) { .cat-tiles { grid-template-columns: 1fr; } }
.cat-tile { display: flex; align-items: center; gap: 16px; padding: 18px 20px; background: #fff; border: 1px solid var(--line); border-radius: var(--radius-card); box-shadow: 0 10px 22px -20px rgba(25,79,144,0.4); transition: transform 160ms var(--ease), border-color 160ms ease, box-shadow 200ms var(--ease); }
.cat-tile:hover, .cat-tile:focus-visible { transform: translateY(-3px); border-color: var(--brand); box-shadow: 0 18px 32px -18px rgba(25,79,144,0.5); }
.cat-tile-ic { width: 48px; height: 48px; border-radius: 12px; background: var(--brand-tint); color: var(--brand); display: grid; place-items: center; flex: none; }
.cat-tile-ic svg { width: 24px; height: 24px; }
.cat-tile h3 { font-size: 1rem; color: var(--ink); font-weight: 800; }
.cat-tile p { margin-top: 3px; font-size: 0.8rem; color: var(--ink-faint); }
.cat-tile-arrow { margin-left: auto; color: var(--brand); flex: none; }

/* ------------------------------------------------------------------------------------------------
   6. PRODUCT CARD (pcard) — the single card used on store home, category listing, search results,
   cart recommendations and related products. Deliberately denser than the marketing .cat-card/
   .svc-card: less padding, more facts per card (price + stock + size availability all visible
   with no click, per the Direction C brief).

   NAMING: does NOT reuse site.css's existing .prod-card/.prod-grid/.prod-thumb — see this stage's
   return value for why those names are being retired, not extended.
   ------------------------------------------------------------------------------------------------ */
.pgrid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; }
@media (max-width: 1080px) { .pgrid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 720px) { .pgrid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 440px) { .pgrid { grid-template-columns: 1fr; } }
/* Fixed 4-up variant for contexts whose own container is narrower than the shell (e.g. a related-
   products rail) and should not fall back to the 3/2/1-up breakpoints meant for the full shell. */
.pgrid.pgrid-sm { grid-template-columns: repeat(4, 1fr); }

.pcard { display: flex; flex-direction: column; background: #fff; border: 1px solid var(--line); border-radius: var(--radius-card); overflow: hidden; transition: transform 160ms var(--ease), box-shadow 200ms var(--ease), border-color 160ms ease; }
.pcard:hover, .pcard:focus-visible { transform: translateY(-2px); border-color: var(--brand); box-shadow: 0 16px 28px -18px rgba(25,79,144,0.45); }
.pcard.is-out { opacity: 0.72; }
.pcard-link { display: block; }

/* Exactly one photo per product (never a gallery — see the project brief). object-fit:contain,
   not cover: a single clinic-supply photo on a flat backdrop should never be cropped to fill a
   square, or genuinely wide/tall items (a resistance band coil vs. a foam roller) get mangled. */
.pcard-thumb { position: relative; aspect-ratio: 1 / 1; background: #f3f1ee; display: block; }
.pcard-thumb img { width: 100%; height: 100%; object-fit: contain; padding: 10px; }
.pcard-cat { position: absolute; left: 8px; top: 8px; font-size: 0.63rem; font-weight: 800; letter-spacing: 0.05em; text-transform: uppercase; color: var(--green); background: rgba(255,255,255,0.92); padding: 3px 8px; border-radius: 999px; }
.pcard-stockflag { position: absolute; right: 8px; top: 8px; font-size: 0.63rem; font-weight: 800; padding: 3px 8px; border-radius: 999px; }
.pcard-stockflag.low { background: color-mix(in oklch, var(--gold) 22%, white); color: color-mix(in oklch, var(--gold) 60%, black 40%); }
.pcard-stockflag.out { background: var(--danger-tint); color: var(--danger); }

.pcard-body { padding: 11px 12px 12px; display: grid; gap: 5px; flex: 1 1 auto; }
.pcard-name { font-size: 0.87rem; font-weight: 700; color: var(--ink); line-height: 1.3; }
.pcard-facts { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 2px; }
.pcard-price { font-size: 0.95rem; font-weight: 800; color: var(--brand); font-variant-numeric: tabular-nums; }
.pcard-stock { font-size: 0.72rem; font-weight: 700; color: var(--green); }
.pcard-stock.low { color: color-mix(in oklch, var(--gold) 60%, black 40%); }
.pcard-stock.out { color: var(--danger); }

.pcard-sizes { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 1px; }
.pcard-size { font-size: 0.66rem; font-weight: 800; width: 22px; height: 22px; border-radius: 6px; display: grid; place-items: center; border: 1px solid var(--line); color: var(--ink-soft); }
.pcard-size.avail { border-color: var(--brand); color: var(--brand); }
.pcard-size.oos { color: var(--ink-faint); text-decoration: line-through; opacity: 0.55; }

.pcard-add { margin-top: 6px; display: flex; align-items: center; justify-content: center; gap: 6px; padding: 9px; min-height: 40px; border-radius: 9px; border: 1.5px solid var(--green); background: #fff; color: var(--green); font-weight: 800; font-size: 0.79rem; cursor: pointer; transition: background 150ms ease, color 150ms ease; }
.pcard-add:hover:not(:disabled) { background: var(--green); color: #fff; }
.pcard-add:active:not(:disabled) { transform: scale(0.97); }
.pcard-add:disabled { border-color: var(--line); color: var(--ink-faint); cursor: not-allowed; }

/* ------------------------------------------------------------------------------------------------
   7. LISTING — category/search page: sidebar filters + result grid.
   ------------------------------------------------------------------------------------------------ */
.listing { display: grid; grid-template-columns: 220px 1fr; gap: 30px; align-items: start; }
@media (max-width: 860px) { .listing { grid-template-columns: 1fr; } }

.filters { position: sticky; top: 12px; display: grid; gap: 22px; }
@media (max-width: 860px) { .filters { position: static; } }
.filter-group h4 { font-size: 0.7rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-faint); margin-bottom: 10px; }
.filter-row { display: flex; align-items: center; gap: 10px; padding: 7px 0; font-size: 0.85rem; color: var(--ink-soft); cursor: pointer; min-height: 30px; }
.filter-row input { width: 18px; height: 18px; accent-color: var(--brand); flex: none; }
.filter-row .cnt { margin-left: auto; font-size: 0.75rem; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.filters-note { font-size: 0.76rem; color: var(--ink-faint); border-top: 1px solid var(--line); padding-top: 12px; }

.listing-topbar { display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; margin-bottom: 16px; }
.result-count { font-size: 0.82rem; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.sort-select { border: 1.5px solid var(--line); border-radius: 9px; padding: 9px 12px; min-height: 40px; font-size: 0.82rem; font-weight: 600; color: var(--ink); background: #fff; cursor: pointer; }
.sort-select:focus-visible { border-color: var(--brand); }

/* ------------------------------------------------------------------------------------------------
   8. EMPTY STATES — one canonical component for every "nothing here" state in the store
   (empty cart, empty listing after filtering, no University videos yet, no upcoming courses).

   Consolidated from the mockup's three near-identical dashed-box variants (.cart-empty, .vempty,
   .empty-note) into a single .empty-state — see this stage's return value for why: three
   almost-identical primitives in a contract that twelve agents build against just invites three
   slightly different re-implementations of the same "nothing here" box.
   ------------------------------------------------------------------------------------------------ */
.empty-state { display: none; text-align: center; padding: 40px 18px; border: 1.5px dashed var(--line); border-radius: var(--radius-card); color: var(--ink-faint); font-size: 0.87rem; }
.empty-state.is-shown { display: block; }
.empty-state-ic { width: 56px; height: 56px; border-radius: 999px; background: var(--brand-tint); color: var(--brand); display: grid; place-items: center; margin: 0 auto 14px; }
.empty-state-ic svg { width: 26px; height: 26px; }
.empty-state h3 { font-size: 1rem; font-weight: 800; color: var(--ink); }
.empty-state p { margin-top: 6px; font-size: 0.85rem; color: var(--ink-faint); }
.empty-state .btn { margin-top: 16px; }

/* ------------------------------------------------------------------------------------------------
   9. PRODUCT DETAIL
   ------------------------------------------------------------------------------------------------ */
.pdetail { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(24px, 4vw, 50px); align-items: start; }
@media (max-width: 860px) { .pdetail { grid-template-columns: 1fr; } }
.pdetail-photo { position: relative; aspect-ratio: 1 / 1; background: #f3f1ee; border-radius: 16px; overflow: hidden; border: 1px solid var(--line); }
.pdetail-photo img { width: 100%; height: 100%; object-fit: contain; padding: 26px; }
/* Hover-reveal prev/next on the main photo itself -- client asked for arrows "hidden inside
   the box, on mouse over the arrow comes to show left and right". Deliberately NOT the
   shared .carousel-arrow (Team/Partners/Reviews) convention, which is always visible outside
   its own rail -- these sit INSIDE this box specifically hidden until hover, a different,
   client-specified behavior for this one gallery, not a second global arrow style. */
.pdetail-nav { position: absolute; top: 50%; translate: 0 -50%; z-index: 5; width: 40px; height: 40px;
  border-radius: 50%; border: 1px solid var(--line); background: color-mix(in oklch, white 92%, transparent);
  color: var(--brand); display: grid; place-items: center; cursor: pointer; opacity: 0; pointer-events: none;
  transition: opacity 200ms var(--ease), background 160ms var(--ease); }
.pdetail-nav svg { width: 16px; height: 16px; }
.pdetail-nav.prev svg { transform: rotate(180deg); } /* matches .carousel-arrow.prev's own rotation convention (site.css) -- #i-arrow points right by default */
.pdetail-photo:hover .pdetail-nav, .pdetail-nav:focus-visible { opacity: 1; pointer-events: auto; }
.pdetail-nav:hover { background: var(--brand); color: #fff; }
.pdetail-nav.prev { left: 14px; }
.pdetail-nav.next { right: 14px; }
@media (hover: none) {
  /* No hover on touch -- hiding these entirely would leave touch visitors with only the
     thumbnail rail (still fully functional), same graceful-degradation as every other
     hover-only affordance already on this page. */
  .pdetail-nav { display: none; }
}
.pdetail-trust { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 12px; }

.pdetail-eyebrow { font-size: 0.76rem; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase; color: var(--green); }
.pdetail h1 { margin-top: 6px; font-size: clamp(1.4rem, 2.6vw, 1.9rem); color: var(--ink); font-weight: 800; }
.pdetail .sku { margin-top: 4px; font-size: 0.78rem; color: var(--ink-faint); }
.pdetail-price { margin-top: 12px; font-size: 1.55rem; font-weight: 800; color: var(--brand); font-variant-numeric: tabular-nums; }
.pdetail-desc { margin-top: 10px; color: var(--ink-soft); font-size: 0.93rem; max-width: 52ch; }

.opt-head { display: flex; align-items: center; justify-content: space-between; margin: 18px 0 8px; }
.opt-head span.lbl { font-size: 0.76rem; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-faint); }
.opt-head span.sel { font-size: 0.79rem; font-weight: 700; color: var(--ink); }
.opt-row { display: flex; gap: 9px; flex-wrap: wrap; }
.opt-color { width: 40px; height: 40px; border-radius: 999px; border: 2px solid var(--line); cursor: pointer; display: grid; place-items: center; padding: 3px; background: none; }
.opt-color .sw { width: 100%; height: 100%; border-radius: 999px; }
.opt-color.is-sel { border-color: var(--brand); box-shadow: 0 0 0 2px #fff inset; }
.opt-color:focus-visible { outline-offset: 3px; }
/* .opt-size doubles as the guarded variant-picker button: `disabled` (server truly has no such
   combination) and `.oos` (combination exists but is out of stock) render identically on purpose
   — both mean "you cannot buy this right now" — so a page template can use either the native
   attribute or the class depending on how its own JS/Blade tracks state. */
.opt-size { min-width: 46px; height: 44px; padding: 0 10px; border-radius: 10px; border: 1.5px solid var(--line); background: #fff; font-weight: 700; font-size: 0.85rem; color: var(--ink); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: 5px; }
.opt-size .dot { width: 6px; height: 6px; border-radius: 999px; background: var(--gold); display: none; }
.opt-size.low .dot { display: block; }
.opt-size.is-sel { border-color: var(--brand); background: var(--brand-tint); color: var(--brand); }
.opt-size:disabled, .opt-size.oos { color: var(--ink-faint); border-color: var(--line); text-decoration: line-through; cursor: not-allowed; background: var(--surface); }

.avail-line { margin-top: 14px; font-size: 0.85rem; font-weight: 700; display: flex; align-items: center; gap: 7px; }
.avail-line svg { width: 15px; height: 15px; flex: none; }
.avail-line.ok { color: var(--green); }
.avail-line.warn { color: color-mix(in oklch, var(--gold) 60%, black 40%); }
.avail-line.bad { color: var(--danger); }

/* Quantity stepper — used on product detail (full size) and cart lines (.sm). Guards its own
   bounds visually via :disabled on the +/- buttons; the page template supplies the actual
   min/max/step logic server + client side, this only renders whatever state it is told. */
.qty-stepper { display: inline-flex; align-items: center; border: 1.5px solid var(--line); border-radius: 999px; overflow: hidden; }
.qty-stepper button { width: 40px; height: 40px; background: #fff; border: 0; display: grid; place-items: center; cursor: pointer; color: var(--brand); }
.qty-stepper button:hover:not(:disabled) { background: var(--brand-tint); }
.qty-stepper button:active:not(:disabled) { transform: scale(0.94); }
.qty-stepper button:disabled { color: var(--ink-faint); cursor: not-allowed; }
.qty-stepper input { width: 42px; text-align: center; border: 0; border-left: 1.5px solid var(--line); border-right: 1.5px solid var(--line); height: 40px; font-weight: 800; font-size: 0.9rem; }
.qty-stepper.sm button { width: 32px; height: 32px; }
.qty-stepper.sm input { width: 34px; height: 32px; font-size: 0.82rem; }

.qty-row { display: flex; align-items: center; gap: 16px; margin-top: 18px; flex-wrap: wrap; }
.pdetail-actions { display: flex; gap: 10px; margin-top: 20px; flex-wrap: wrap; }
.pdetail-actions .btn { flex: 1 1 180px; }

/* ------------------------------------------------------------------------------------------------
   10. CART
   ------------------------------------------------------------------------------------------------ */
.cart-wrap { display: grid; grid-template-columns: 1fr 340px; gap: 30px; align-items: start; }
@media (max-width: 900px) { .cart-wrap { grid-template-columns: 1fr; } }
.cart-lines { display: grid; gap: 12px; }
.cart-line { display: grid; grid-template-columns: 68px 1fr auto; gap: 14px; align-items: center; padding: 12px; border: 1px solid var(--line); border-radius: 12px; background: #fff; }
.cart-line.is-unavailable { opacity: 0.6; }
.cart-thumb { width: 68px; height: 68px; border-radius: 9px; background: #f3f1ee; overflow: hidden; }
.cart-thumb img { width: 100%; height: 100%; object-fit: contain; padding: 6px; }
.cart-info h4 { font-size: 0.9rem; font-weight: 700; color: var(--ink); }
.cart-info .variant { font-size: 0.78rem; color: var(--ink-faint); margin-top: 2px; }
.cart-info .unit { font-size: 0.78rem; color: var(--ink-soft); margin-top: 3px; font-variant-numeric: tabular-nums; }
.cart-line-right { display: grid; gap: 8px; justify-items: end; }
.cart-line-total { font-weight: 800; color: var(--brand); font-variant-numeric: tabular-nums; }
.cart-line-actions { display: flex; align-items: center; gap: 10px; }
.remove-btn { width: 32px; height: 32px; border-radius: 8px; border: 1px solid var(--line); background: #fff; color: var(--ink-faint); display: grid; place-items: center; cursor: pointer; }
.remove-btn:hover { border-color: var(--danger); color: var(--danger); }
.remove-btn:active { transform: scale(0.94); }

/* Order summary — reused as-is for the cart sidebar AND checkout sidebar, so the two totals
   blocks can never silently drift into different-looking numbers. */
.summary-card { border: 1px solid var(--line); border-radius: var(--radius-card); background: #fff; padding: 20px; position: sticky; top: 12px; }
.summary-card h3 { font-size: 0.98rem; font-weight: 800; color: var(--ink); margin-bottom: 14px; }
.summary-row { display: flex; justify-content: space-between; gap: 10px; padding: 7px 0; font-size: 0.87rem; color: var(--ink-soft); }
.summary-row.total { border-top: 1.5px solid var(--line); margin-top: 6px; padding-top: 12px; font-size: 1.05rem; font-weight: 800; color: var(--ink); }
.summary-row .v { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--ink); }
.summary-note { font-size: 0.76rem; color: var(--ink-faint); margin-top: 4px; }
.summary-actions { display: grid; gap: 9px; margin-top: 16px; }

/* ------------------------------------------------------------------------------------------------
   11. CHECKOUT
   ------------------------------------------------------------------------------------------------ */
.checkout-wrap { display: grid; grid-template-columns: 1fr 340px; gap: 30px; align-items: start; }
@media (max-width: 900px) { .checkout-wrap { grid-template-columns: 1fr; } }
.co-block { border: 1px solid var(--line); border-radius: var(--radius-card); background: #fff; padding: 20px; margin-bottom: 16px; }
.co-block h3 { font-size: 0.92rem; font-weight: 800; color: var(--ink); margin-bottom: 4px; }
.co-block .sub { font-size: 0.79rem; color: var(--ink-faint); margin-bottom: 14px; }

/* Form fields — net new; no shared form styling existed to reuse (site.css has no <input>/
   <select> rules of its own). This is now the store's one field system: filters above reuse
   .filter-row's own checkbox styling, everything else (checkout, future account forms) uses this. */
.field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media (max-width: 560px) { .field-grid { grid-template-columns: 1fr; } }
.field { display: grid; gap: 6px; }
.field.full { grid-column: 1 / -1; }
.field label { font-size: 0.78rem; font-weight: 700; color: var(--ink-soft); }
.field input, .field select, .field textarea { border: 1.5px solid var(--line); border-radius: 9px; padding: 11px 12px; min-height: 44px; font-size: 0.87rem; background: #fff; color: var(--ink); font-family: inherit; }
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible { border-color: var(--brand); }
.field .field-error { font-size: 0.76rem; font-weight: 700; color: var(--danger); }
.field-check { display: flex; align-items: center; gap: 9px; font-size: 0.82rem; color: var(--ink-soft); margin-top: 2px; }
.field-check input { width: 18px; height: 18px; accent-color: var(--brand); }

.method-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media (max-width: 560px) { .method-row { grid-template-columns: 1fr; } }
.method-opt { border: 1.5px solid var(--line); border-radius: 12px; padding: 14px; cursor: pointer; display: flex; gap: 10px; align-items: flex-start; }
.method-opt input { margin-top: 3px; width: 18px; height: 18px; accent-color: var(--brand); flex: none; }
.method-opt.is-sel { border-color: var(--brand); background: var(--brand-tint); }
.method-opt b { display: block; font-size: 0.87rem; color: var(--ink); }
.method-opt span { display: block; font-size: 0.78rem; color: var(--ink-faint); margin-top: 2px; }
.reveal-block { margin-top: 14px; }
.pickup-box { margin-top: 14px; background: var(--brand-tint); border-radius: 10px; padding: 14px 16px; font-size: 0.84rem; color: var(--ink-soft); display: flex; gap: 10px; }
.pickup-box svg { width: 17px; height: 17px; color: var(--brand); flex: none; margin-top: 2px; }

.coupon-row { display: flex; gap: 8px; }
.coupon-row input { flex: 1 1 auto; }
.coupon-msg { font-size: 0.78rem; margin-top: 6px; display: none; }
.coupon-msg.ok { color: var(--green); display: block; }
.coupon-msg.bad { color: var(--danger); display: block; }

.pay-chips { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.pay-chip { font-size: 0.73rem; font-weight: 800; color: var(--ink-soft); border: 1px solid var(--line); border-radius: 7px; padding: 5px 10px; }
.co-trust { text-align: center; font-size: 0.76rem; color: var(--ink-faint); margin-top: 12px; }

/* Generic alert (server-side validation summary, payment failure banner, etc.) — net new, no
   equivalent existed anywhere in the storefront's own styling. */
.alert { display: flex; gap: 10px; align-items: flex-start; padding: 13px 16px; border-radius: 10px; font-size: 0.86rem; margin-bottom: 16px; }
.alert svg { width: 18px; height: 18px; flex: none; margin-top: 1px; }
.alert-error { background: var(--danger-tint); color: color-mix(in oklch, var(--danger) 75%, black 25%); border: 1px solid color-mix(in oklch, var(--danger) 30%, transparent); }
.alert-ok { background: color-mix(in oklch, var(--green) 12%, white 88%); color: var(--green-deep); border: 1px solid color-mix(in oklch, var(--green) 30%, transparent); }
.alert ul { margin-top: 4px; padding-left: 1.1em; list-style: disc; }

/* ------------------------------------------------------------------------------------------------
   12. UNIVERSITY (video library)
   ------------------------------------------------------------------------------------------------ */
.uni-band { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; background: linear-gradient(135deg, var(--brand) 0%, var(--brand-deep) 100%); color: #fff; border-radius: var(--radius-card); padding: 18px 22px; margin-bottom: 22px; }
.uni-band .t { max-width: 60ch; }
.uni-band b { display: block; font-size: 1.02rem; font-weight: 800; }
.uni-band span { display: block; margin-top: 3px; font-size: 0.83rem; color: color-mix(in oklch, white 82%, transparent); }

.vgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
@media (max-width: 900px) { .vgrid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .vgrid { grid-template-columns: 1fr; } }
.vcard { background: #fff; border: 1px solid var(--line); border-radius: var(--radius-card); overflow: hidden; transition: transform 160ms var(--ease), box-shadow 200ms var(--ease); }
.vcard:hover, .vcard:focus-within { transform: translateY(-2px); box-shadow: 0 16px 28px -18px rgba(25,79,144,0.45); }
.vthumb { position: relative; aspect-ratio: 16/9; background: linear-gradient(155deg, var(--brand) 0%, var(--brand-deeper) 100%); display: grid; place-items: center; }
.vthumb img { width: 100%; height: 100%; object-fit: cover; }
.vthumb .playbtn { width: 46px; height: 46px; border-radius: 999px; background: rgba(255,255,255,0.16); border: 2px solid rgba(255,255,255,0.6); color: #fff; display: grid; place-items: center; }
.vthumb .playbtn svg { width: 18px; height: 18px; margin-left: 2px; }
.vthumb .dur { position: absolute; right: 8px; bottom: 8px; background: rgba(9,20,36,0.72); color: #fff; font-size: 0.7rem; font-weight: 700; padding: 3px 7px; border-radius: 6px; }
.vthumb .lockflag { position: absolute; left: 8px; top: 8px; background: rgba(255,255,255,0.92); color: var(--brand); font-size: 0.63rem; font-weight: 800; padding: 4px 8px; border-radius: 999px; display: flex; align-items: center; gap: 4px; }
.vthumb .lockflag svg { width: 11px; height: 11px; }
.vbody { padding: 12px 14px 14px; }
.vbody .track { font-size: 0.68rem; font-weight: 800; letter-spacing: 0.05em; text-transform: uppercase; color: var(--green); }
.vbody h4 { margin-top: 4px; font-size: 0.9rem; font-weight: 700; color: var(--ink); line-height: 1.3; }
.vbody .instr { margin-top: 3px; font-size: 0.79rem; color: var(--ink-faint); }
/* Trainer profiles (videos.instructor_id -> instructors table). When a video's instructor
   resolves to a public Instructor row, storefront/university/index.blade.php renders the byline
   as a link -- but NOT in place of `.vbody .instr` above (that stays a plain, unlinked div for the
   unmatched/hidden case exactly as before). It renders as a SIBLING of .pcard-link, the anchor
   that wraps the rest of the card, because nesting a second <a> inside .pcard-link is invalid
   HTML: the browser implicitly closes the outer anchor the instant it meets the inner one, which
   would silently break the card's own "click anywhere to open the video" target. Pulled back up
   with a negative margin so it still reads as part of the same card rather than a bolted-on row
   below it -- the -6px roughly cancels .vbody's own 14px bottom padding to land close to where
   `.vbody .instr`'s 3px top margin would have put it. */
.vcard > a.instr { display: block; padding: 0 14px 12px; margin-top: -6px; font-size: 0.79rem; color: var(--ink-faint); text-decoration: none; }
.vcard > a.instr:hover { color: var(--brand); text-decoration: underline; }
.vbody .vcta { margin-top: 10px; width: 100%; padding: 9px; min-height: 40px; border-radius: 9px; font-size: 0.79rem; font-weight: 800; cursor: pointer; border: 1.5px solid var(--brand); background: #fff; color: var(--brand); }
.vbody .vcta:hover { background: var(--brand); color: #fff; }
.vbody .vcta.locked { border-color: var(--line); color: var(--ink-faint); }
.vbody .vcta.locked:hover { background: var(--surface); color: var(--ink-faint); }

/* Titleband subtitle line. storefront/university/index.blade.php previously carried this exact
   rule in its own page-local <style> block (comment there: "the shipped store.css does not carry
   that one rule"). The new /university/trainer/{slug} page (Instructor model) needs the same
   subtitle treatment under its <h1>, and a second page-local <style> tag per page is exactly the
   "second design system" this file exists to prevent -- so it is promoted here instead, once,
   and both pages use the shared rule. index.blade.php's own local copy is left in place (harmless
   duplicate, out of scope for this change) rather than edited beyond its own trainer-link additions.  */
.titleband p.tb-sub { margin-top: 5px; color: color-mix(in oklch, white 82%, transparent); font-size: 0.86rem; max-width: 62ch; }

/* ------------------------------------------------------------------------------------------------
   13. COURSES
   ------------------------------------------------------------------------------------------------ */
.cgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
@media (max-width: 900px) { .cgrid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .cgrid { grid-template-columns: 1fr; } }
.ccard { background: #fff; border: 1px solid var(--line); border-radius: var(--radius-card); padding: 16px; display: grid; gap: 9px; transition: transform 160ms var(--ease), box-shadow 200ms var(--ease); }
.ccard:hover, .ccard:focus-within { transform: translateY(-2px); box-shadow: 0 16px 28px -18px rgba(25,79,144,0.45); }
.ccard.is-soldout, .ccard.is-unscheduled { opacity: 0.68; }
.ccard .track { font-size: 0.68rem; font-weight: 800; letter-spacing: 0.05em; text-transform: uppercase; color: var(--green); }
.ccard h3 { font-size: 0.98rem; font-weight: 800; color: var(--ink); line-height: 1.3; }
.ccard .meta { display: grid; gap: 4px; font-size: 0.79rem; color: var(--ink-soft); }
.ccard .meta li { display: flex; gap: 7px; align-items: center; }
.ccard .meta svg { width: 14px; height: 14px; color: var(--ink-faint); flex: none; }

.seat-wrap { margin-top: 2px; }
.seat-txt { display: flex; justify-content: space-between; font-size: 0.76rem; font-weight: 700; color: var(--ink-soft); margin-bottom: 4px; }
.seat-bar { height: 6px; border-radius: 999px; background: var(--line); overflow: hidden; }
.seat-bar span { display: block; height: 100%; background: var(--green); border-radius: 999px; }
.seat-bar.low span { background: var(--gold); }
.seat-bar.out span { background: var(--danger); }

.ccard-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 6px; }
.ccard-price { font-weight: 800; color: var(--brand); font-variant-numeric: tabular-nums; }

/* Unscheduled state — every course at launch. Not a generic .empty-state (that is for a whole
   grid with nothing in it); this is a per-card notice replacing the date/seats/price it cannot
   show yet, so it stays a small addition to .ccard rather than swapping the whole component. */
.ccard-unscheduled-note { font-size: 0.79rem; color: var(--ink-faint); font-style: italic; }

.cdetail { display: grid; grid-template-columns: 1fr 320px; gap: 30px; align-items: start; }
@media (max-width: 900px) { .cdetail { grid-template-columns: 1fr; } }
.curriculum { display: grid; gap: 2px; margin-top: 14px; }
.curriculum li { display: flex; gap: 11px; align-items: flex-start; padding: 10px 2px; border-bottom: 1px solid var(--line); font-size: 0.88rem; color: var(--ink-soft); }
.curriculum li:last-child { border-bottom: 0; }
.curriculum li b { color: var(--ink); }
.curriculum .num { width: 22px; height: 22px; border-radius: 999px; background: var(--brand-tint); color: var(--brand); font-size: 0.72rem; font-weight: 800; display: grid; place-items: center; flex: none; margin-top: 1px; }

.instr-card { display: flex; gap: 14px; align-items: center; margin-top: 24px; padding: 16px; border: 1px solid var(--line); border-radius: var(--radius-card); background: var(--surface); }
.instr-avatar { width: 52px; height: 52px; border-radius: 999px; background: var(--brand); color: #fff; display: grid; place-items: center; flex: none; }
.instr-avatar svg { width: 24px; height: 24px; }
.instr-card h4 { font-size: 0.9rem; font-weight: 800; color: var(--ink); }
.instr-card p { margin-top: 2px; font-size: 0.8rem; color: var(--ink-faint); }

/* Instructor photo / initials fallback (Video/Instructor::initials()). Added for the University
   trainer profiles: `.instr-avatar` previously only ever held the generic "person" svg glyph
   above (show.blade.php's Trainer tab, unmatched or no trainer). Now it holds an <img> when the
   trainer has a photo, or a `.initials` span when they don't -- a trainer with no photograph must
   still render a correct circle, never a broken <img> or, worse, a stranger's face. The radius on
   `img` itself (not `overflow:hidden` on `.instr-avatar`) is deliberate: it clips the photo
   without changing `.instr-avatar`'s own box behaviour for its existing svg-only callers. */
.instr-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 999px; }
.instr-avatar .initials { font-size: 0.95rem; font-weight: 800; }
/* The Trainer tab's "N videos by NAME" link to the trainer's own page reuses .link-more
   (already used for e.g. storefront/home.blade.php's "View all X" links) rather than a new
   component; it just needs breathing room above the bio paragraph it sits under here. */
.instr-card .link-more { margin-top: 8px; }

/* /university/trainer/{slug} profile hero -- same .instr-card/.instr-avatar vocabulary as the
   Trainer tab above, sized up: that one is a small panel inside a tab, this is the whole page's
   own subject. Purely additive modifiers, so the existing 52px Trainer-tab usage is unaffected. */
.instr-card.instr-hero { padding: 22px; gap: 18px; }
.instr-avatar.instr-avatar-lg { width: 88px; height: 88px; }
.instr-avatar.instr-avatar-lg .initials { font-size: 1.5rem; }
.instr-card.instr-hero h4 { font-size: 1.15rem; }
.instr-card.instr-hero p { margin-top: 6px; font-size: 0.88rem; line-height: 1.55; }
.instr-card.instr-hero .instr-count { display: block; margin-top: 8px; font-size: 0.78rem; font-weight: 700; color: var(--brand); }

.booking-card { border: 1px solid var(--line); border-radius: var(--radius-card); background: #fff; padding: 20px; position: sticky; top: 12px; }
.booking-card .price { font-size: 1.4rem; font-weight: 800; color: var(--brand); }
.booking-card .price span { font-size: 0.78rem; font-weight: 600; color: var(--ink-faint); }
.booking-meta { display: grid; gap: 9px; margin: 14px 0; }
.booking-meta li { display: flex; gap: 9px; align-items: flex-start; font-size: 0.84rem; color: var(--ink-soft); }
.booking-meta svg { width: 16px; height: 16px; color: var(--brand); flex: none; margin-top: 1px; }
.qty-line { display: flex; align-items: center; justify-content: space-between; margin: 14px 0; }
.qty-line .lbl { font-size: 0.84rem; font-weight: 700; color: var(--ink); }
.booking-total { display: flex; justify-content: space-between; font-size: 0.95rem; font-weight: 800; color: var(--ink); border-top: 1px solid var(--line); padding-top: 12px; margin-top: 4px; }
.map-mini { border-radius: 12px; overflow: hidden; aspect-ratio: 16/9; border: 1px solid var(--line); margin-top: 14px; }
.map-mini iframe { width: 100%; height: 100%; border: 0; }

/* Booking-not-open state: no date scheduled yet. Every one of the 25 courses is in this state at
   launch (see project brief) — this is not an edge case, it is the default, so it gets a real,
   deliberate treatment rather than an afterthought "TBA" label. No booking button renders at all
   in this state (page templates simply omit .btn-primary here, there is nothing to disable-and-
   grey per the "never a dead button" rule elsewhere in this system). */
.booking-unscheduled { text-align: center; padding: 8px 4px 4px; }
.booking-unscheduled svg { width: 30px; height: 30px; color: var(--ink-faint); margin: 0 auto 10px; }
.booking-unscheduled p { font-size: 0.87rem; color: var(--ink-soft); }
.booking-unscheduled .hint { margin-top: 6px; font-size: 0.78rem; color: var(--ink-faint); }

/* ------------------------------------------------------------------------------------------------
   14. MOTION
   ------------------------------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .cart-badge.is-pulse,
  .pcard, .cat-tile, .vcard, .ccard,
  .btn, .chip, .qty-stepper button, .remove-btn, .pcard-add,
  .tcard-name {
    transition: none !important;
    animation: none !important;
  }
}

/* ==========================================================================
   Services table
   Moved here out of storefront/partials/services-table.blade.php. It rendered
   inline behind an @once guard, which worked, but it put the string
   ".svc-table td.svc-price" into the page body — and StorefrontCatalogTest
   counts "svc-price" occurrences to prove a category rendered exactly one
   price cell per service. Shipping stylesheet text inside the document made
   that count read one too high on every category page.
   ========================================================================== */
.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }

.svc-table-scroll { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius-card); background: #fff; }
.svc-table { width: 100%; border-collapse: collapse; font-size: 0.87rem; min-width: 620px; }
.svc-table th {
    text-align: left;
    font-size: 0.7rem;
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-faint);
    padding: 12px 14px;
    border-bottom: 1.5px solid var(--line);
    white-space: nowrap;
}
.svc-table th.svc-table-num { text-align: right; }
.svc-table td {
    padding: 13px 14px;
    border-bottom: 1px solid var(--line);
    color: var(--ink-soft);
    vertical-align: middle;
}
.svc-table tbody tr:last-child td { border-bottom: 0; }
.svc-table tbody tr:hover { background: var(--brand-tint); }

.svc-table-name a {
    font-weight: 700;
    color: var(--ink);
    display: inline-block;
    padding: 4px 0;
}
.svc-table-name a:hover { color: var(--brand); text-decoration: underline; }

.svc-badge-course {
    display: inline-block;
    margin-left: 8px;
    font-size: 0.63rem;
    font-weight: 800;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--green);
    background: color-mix(in oklch, var(--green) 12%, white 88%);
    padding: 3px 8px;
    border-radius: 999px;
    vertical-align: middle;
}

.svc-table-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.svc-table td.svc-price { font-weight: 800; color: var(--brand); }

.svc-table-desc { color: var(--ink-faint); font-size: 0.83rem; max-width: 34ch; }

.svc-table-save { width: 44px; text-align: center; }
/* wishlist-heart.blade.php is a shared partial this unit does not own; its own button
   keeps whatever site-wide styling it already has — this only guarantees the table cell
   around it never collapses below the 44px minimum touch target the brief requires. */
.svc-table-save .wishlist-heart-form { display: inline-flex; }
.svc-table-save .wishlist-heart { min-width: 40px; min-height: 40px; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; background: none; border: 0; font-size: 1.1rem; }

@media (max-width: 560px) {
    .svc-table-desc { display: none; }
}

/* The WhatsApp enquiry is a full-width secondary, not a third competitor in the
   button row. .pdetail-actions .btn sets flex:1 1 180px, which overrode the
   .btn-block already on that link and squeezed three buttons onto one row until
   "Enquire via WhatsApp" wrapped onto two lines. The approved design shows two
   actions here; this keeps the mandatory WhatsApp route without crowding them. */
.pdetail-actions .btn-wa { flex: 1 0 100%; }


/* ==========================================================================
   Header: rich dropdown + inline search
   Added 2026-07-29. Both live here rather than in site.css because they are
   the store header's own additions; the shared chrome above is untouched.
   ========================================================================== */

/* A .drop whose entries need a line of explanation. Education uses it: two
   destinations where the label alone ("University") does not tell a patient
   what they are clicking into. Wider than a plain .drop, nowhere near a mega. */
.drop-rich { width: 290px; padding: 8px; }
.drop-rich a { display: block; padding: 11px 12px; border-radius: 9px; }
.drop-rich-title { display: block; font-weight: 700; font-size: 0.88rem; color: var(--ink); }
.drop-rich-sub { display: block; margin-top: 3px; font-size: 0.77rem; line-height: 1.45; color: var(--ink-faint); }
.drop-rich a:hover .drop-rich-title { color: var(--brand); }

/* --- Inline header search ------------------------------------------------ */

.hsearch { position: relative; display: flex; align-items: center; }
/* Above the field it overlays, so the icon stays clickable and visible. */
.hsearch-toggle { position: relative; z-index: 1; }

/* Collapsed to nothing rather than display:none, so opening is a width
   transition the eye can follow instead of a jump. */
/* Absolutely positioned, expanding leftwards from the header's right edge.
   In flow it pushed the nav sideways and wrapped "Our Services" onto two
   lines the moment it opened — a search box should not rearrange the
   navigation to make room for itself. */
.hsearch-field {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 0;
  padding: 0;
  border: 1.4px solid transparent;
  border-radius: 999px;
  background: var(--surface);
  font: inherit;
  font-size: 0.85rem;
  color: var(--ink);
  opacity: 0;
  pointer-events: none;
  transition: width 260ms var(--ease), opacity 180ms var(--ease), padding 260ms var(--ease), border-color 180ms ease;
}
.hsearch-field::placeholder { color: var(--ink-faint); }
.hsearch-field::-webkit-search-cancel-button { display: none; }

.hsearch.is-open .hsearch-field {
  /* Right padding clears the magnifier, which sits above the field. */
  width: clamp(190px, 24vw, 300px);
  padding: 9px 42px 9px 14px;
  border-color: var(--line);
  background: #fff;
  opacity: 1;
  pointer-events: auto;
}
.hsearch.is-open .hsearch-field:focus { outline: none; border-color: var(--brand); }
.hsearch.is-open .hsearch-toggle { color: var(--brand); }

.hsearch-panel {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  z-index: var(--z-dropdown, 60);
  width: clamp(280px, 30vw, 380px);
  max-height: min(60vh, 460px);
  overflow-y: auto;
  padding: 6px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  /* Tinted to the brand hue, never pure black — same formula as every other
     raised surface in this file. */
  box-shadow: 0 24px 48px -28px rgba(25, 79, 144, 0.45);
}

.hsearch-group + .hsearch-group { margin-top: 4px; border-top: 1px solid var(--line); padding-top: 4px; }
.hsearch-group-label {
  display: block;
  padding: 8px 12px 4px;
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.hsearch-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  padding: 9px 12px;
  border-radius: 9px;
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--ink);
}
.hsearch-item:hover,
.hsearch-item.is-active { background: var(--brand-tint); color: var(--brand); }
.hsearch-item-meta { flex: none; font-size: 0.78rem; font-weight: 700; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.hsearch-item.is-active .hsearch-item-meta { color: var(--brand); }

.hsearch-note { padding: 14px 12px; font-size: 0.83rem; color: var(--ink-faint); }
.hsearch-all {
  display: block;
  margin-top: 4px;
  padding: 10px 12px;
  border-top: 1px solid var(--line);
  font-size: 0.81rem;
  font-weight: 700;
  color: var(--brand);
}
.hsearch-all:hover { background: var(--brand-tint); }

/* Skeleton rows, sized like the results they stand in for, so the panel does
   not resize under the pointer when the answer arrives. */
.hsearch-skeleton { padding: 9px 12px; }
.hsearch-skeleton span {
  display: block;
  height: 11px;
  border-radius: 4px;
  background: linear-gradient(90deg, var(--surface) 25%, var(--line) 37%, var(--surface) 63%);
  background-size: 400% 100%;
  animation: hsearchShimmer 1.3s ease-in-out infinite;
}
.hsearch-skeleton span + span { margin-top: 8px; width: 62%; }
@keyframes hsearchShimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }

@media (max-width: 900px) {
  .hsearch.is-open .hsearch-field { width: clamp(150px, 40vw, 220px); }
  .hsearch-panel { width: min(84vw, 340px); }
}

@media (prefers-reduced-motion: reduce) {
  .hsearch-field { transition: opacity 120ms linear; }
  .hsearch-skeleton span { animation: none; }
}

/* Two actions per card. "Add to cart" stays the primary and keeps its full
   weight; "Buy now" sits beside it as an outline so the card offers a shortcut
   without turning into two competing green blocks. Stacks on narrow cards,
   where side-by-side would wrap the labels. */
.pcard-actions { display: flex; gap: 8px; }
.pcard-actions .pcard-add { flex: 1 1 auto; }
.pcard-buy {
  flex: 0 0 auto;
  padding: 10px 14px;
  border: 1.4px solid var(--brand);
  border-radius: 999px;
  background: #fff;
  color: var(--brand);
  font: inherit;
  font-size: 0.8rem;
  font-weight: 700;
  cursor: pointer;
  transition: background 160ms var(--ease), color 160ms var(--ease), transform 120ms var(--ease);
}
.pcard-buy:hover { background: var(--brand); color: #fff; }
.pcard-buy:active { transform: translateY(1px); }
.pcard-buy:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

@media (max-width: 520px) {
  .pcard-actions { flex-direction: column; }
  .pcard-buy { width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .pcard-buy { transition: none; }
}

/* Hierarchy once there are two buttons. The mockup's .pcard-add is an outline
   because it was the only control on the card; beside a second button it reads
   as an equal, and a card offering two equal choices makes the visitor decide
   which is the normal one. Filled green when paired, so "Add to cart" is
   obviously the default and "Buy now" is obviously the shortcut. Radius matched
   to .pcard-add rather than the pill used elsewhere, since these two sit
   shoulder to shoulder. */
.pcard-actions .pcard-add { background: var(--green); color: #fff; }
.pcard-actions .pcard-add:hover:not(:disabled) { background: var(--brand-deep, #16406f); border-color: var(--brand-deep, #16406f); }
.pcard-actions .pcard-add:disabled { background: #fff; }
.pcard-buy { border-radius: 9px; min-height: 40px; margin-top: 6px; font-size: 0.79rem; font-weight: 800; }

/* ==========================================================================
   University video detail
   Modelled on the client's own course pages: free preview first, tabbed body,
   purchase card alongside. Reuses .cdetail, .curriculum, .instr-card and
   .booking-card from the Courses section rather than growing a parallel set.
   ========================================================================== */

.uni-preview {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-card);
  overflow: hidden;
  background: #0b1c33;
  box-shadow: 0 30px 60px -40px rgba(25, 79, 144, 0.6);
}
.uni-preview iframe,
.uni-preview img { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.uni-preview.is-still img { object-fit: cover; }
.uni-preview-note { margin: 10px 2px 0; font-size: 0.79rem; color: var(--ink-faint); }

.uni-tabs { margin-top: 26px; }
.uni-tablist {
  display: flex;
  gap: 4px;
  overflow-x: auto;
  border-bottom: 1.5px solid var(--line);
  /* The strip scrolls on a phone rather than wrapping into two rows, which
     would push the panel below the fold on the narrowest screens. */
  scrollbar-width: none;
}
.uni-tablist::-webkit-scrollbar { display: none; }
.uni-tab {
  flex: none;
  padding: 11px 16px;
  border: 0;
  border-bottom: 2.5px solid transparent;
  margin-bottom: -1.5px;
  background: none;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--ink-faint);
  cursor: pointer;
  transition: color 160ms var(--ease), border-color 160ms var(--ease);
}
.uni-tab:hover { color: var(--ink); }
.uni-tab.is-active { color: var(--brand); border-bottom-color: var(--brand); }
.uni-tab:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }

.uni-panel { padding-top: 20px; }
.uni-lede { font-size: 0.92rem; line-height: 1.75; color: var(--ink-soft); margin-bottom: 20px; }
.uni-h { font-size: 1.05rem; color: var(--brand); font-weight: 800; margin-bottom: 12px; }

/* FAQ accordion, previously inline in the course detail template. Same
   vocabulary, one definition. */
.faq-list { display: grid; gap: 8px; }
.faq-item { background: #fff; border: 1px solid var(--line); border-radius: var(--radius-card); padding: 2px 16px; }
.faq-item summary {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 14px 0; cursor: pointer; font-weight: 700; font-size: 0.89rem;
  color: var(--ink); list-style: none;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::marker { content: ''; }
.faq-item summary:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.faq-item-chevron { flex: none; color: var(--ink-faint); transition: transform 200ms var(--ease); }
.faq-item[open] .faq-item-chevron { transform: rotate(180deg); }
.faq-item p { margin: 0 0 16px; font-size: 0.87rem; color: var(--ink-soft); line-height: 1.6; }

.uni-buy-single { margin-top: 10px; }
.uni-signin { margin-top: 12px; text-align: center; font-size: 0.8rem; }
.uni-signin a { color: var(--brand); font-weight: 700; }

.uni-meta { list-style: none; margin: 20px 0 0; padding: 18px 0 0; border-top: 1px solid var(--line); display: grid; gap: 11px; }
.uni-meta li { display: flex; align-items: flex-start; gap: 9px; font-size: 0.82rem; color: var(--ink-soft); line-height: 1.5; }
.uni-meta svg { flex: none; margin-top: 2px; color: var(--green); }

@media (prefers-reduced-motion: reduce) {
  .uni-tab, .faq-item-chevron { transition: none; }
}

/* Cover photo behind the play glyph on a University card. Absolutely
   positioned rather than replacing .vthumb's gradient so a video with no
   cover keeps the identical card shape, and so the badges and duration stay
   legible over a photograph: the scrim below is what guarantees that, since
   the imported covers are real photographs with unpredictable brightness. */
.vthumb-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.vthumb-img + * { position: relative; }
.vthumb::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(11,28,51,0.45) 0%, rgba(11,28,51,0.12) 45%, rgba(11,28,51,0.65) 100%);
  pointer-events: none;
}
.vthumb > *:not(.vthumb-img) { position: relative; z-index: 1; }

/* ==========================================================================
   Sticky offsets, corrected 2026-07-29
   Every sticky panel used top:12px, which was measured against the viewport
   and not against the sticky header sitting in front of it. The header is
   77px tall and also sticky, so a filter rail or a purchase card scrolled up
   and parked UNDERNEATH it, cropping its own first line. --header-h keeps the
   two numbers in one place instead of three copies drifting apart.
   ========================================================================== */
:root { --header-h: 77px; --sticky-gap: 14px; }

.filters,
.summary-card,
.booking-card { top: calc(var(--header-h) + var(--sticky-gap)); }

/* Below the header's breakpoint the bar is not sticky, so the offset should
   collapse back to a plain gap rather than reserving space for nothing. */
@media (max-width: 1080px) {
  .filters,
  .summary-card,
  .booking-card { top: var(--sticky-gap); }
}

/* ============================================================
   TRAINERS — the carousel on /university, the directory at
   /university/trainer, and the credentials list all share this
   comment block; only the first (.trainer-strip-head through
   .trainer-dot below) is this pass's concern.

   Rebuilt 2026-09-01: was ten small circular avatars in one
   scrolling row. Client asked to match the real reference,
   sesc.net/library/'s own Trainers carousel — read directly off
   that page's own Elementor media-carousel `data-settings`:
   slides_per_view 4, slides_to_scroll 1, space_between 7px
   (10px tablet/mobile — same widget's own space_between_tablet/
   space_between_mobile), speed 500, autoplay_speed 3000,
   pause_on_hover/interaction yes, bullets pagination (one dot per
   slide, confirmed against its live DOM: 8 trainers there, 8
   bullets). Rebuilt here as 4 large rectangular tiles, edge-to-
   edge, aspect-ratio 0.73 measured off that same reference's own
   active slide (269.75 x 370px). See public/js/university-
   trainers.js for the scroll-snap + dots logic driving it, and
   the inline filter script at the bottom of storefront.university
   .index for the click-to-filter behaviour these tiles keep from
   the old circles.

   .tcard-item clips to rounded corners (overflow: hidden); .tcard
   is the filter button, absolutely positioned inset:0 inside it,
   so the photo is the entire hit target. .tcard-name is a REAL
   link to the trainer's own page — see storefront.university
   .index's own comment on why it can't live inside the button.

   Corrected 2026-09-02, off the client's pasted live widget
   config: gap was 8px/6px, is now 7px/10px (see the two gap rules
   below). Also: the reference's own tiles show NOTHING over the
   photo by default — no caption, no badge — and only fade in a
   name/role caption on hover, confirmed live by hovering it
   myself (a flat rgba(25,79,144,0.23) scrim = var(--brand) at
   23%, centered white 700-weight text, opacity-only fade). This
   app's tiles previously showed .tcard-name always-on over a
   bottom gradient; it's now hover/focus-gated the same way,
   full-bleed and centered, to match. .tcard-n (the "N videos"
   pill) stays always-visible on purpose: it's at-a-glance browse
   information specific to this video-library context that the
   reference page doesn't have any equivalent of (it isn't a video
   context), not an identity caption the client asked to hide —
   only .tcard-name got the hover-gate the client actually
   demonstrated. .tcard-name is pointer-events:none while hidden
   so it never steals clicks from the filter button underneath;
   it only becomes clickable once revealed.
   ============================================================ */

.trainer-strip-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 14px;
    margin: 2px 0 12px;
}

.trainer-strip-head h2 {
    font-size: 1.05rem;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: var(--ink);
}

.trainer-strip-all {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 0.8rem;
    font-weight: 700;
    color: var(--brand);
    text-decoration: none;
}

.trainer-strip-all:hover { text-decoration: underline; }

.trainer-carousel { margin-bottom: 6px; }

/* Exactly 4 tiles visible: each item's flex-basis is a quarter of
   the track's width minus its share of the three 8px gaps between
   them. Scroll-snap is what keeps tiles aligned to that grid as it
   scrolls — university-trainers.js only decides *which* quarter is
   scrolled into view (dot clicks, autoplay); it never fights the
   snap points with its own layout math. */
.trainer-track {
    display: flex;
    gap: 7px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    -ms-overflow-style: none;
    padding-bottom: 2px;
}

.trainer-track::-webkit-scrollbar { display: none; }

/* 900px is this file's own tablet cutoff elsewhere (.cart-wrap,
   .vgrid, .cgrid all switch here) — reused rather than inventing a
   new one. Only one breakpoint needed even though the reference
   widget lists separate space_between_tablet and space_between_mobile:
   both are 10px, so tablet and phone share this single rule. */
@media (max-width: 900px) {
    .trainer-track { gap: 10px; }
    .tcard-item { flex-basis: calc((100% - 3 * 10px) / 4); }
}

.tcard-item {
    position: relative;
    flex: 0 0 calc((100% - 3 * 7px) / 4);
    aspect-ratio: 0.73;
    scroll-snap-align: start;
    border-radius: 10px;
    overflow: hidden;
    background: var(--brand-tint);
    box-shadow: 0 0 0 1px var(--line);
    transition: box-shadow 0.15s ease;
}

/* The filter-selected ring. Deliberately on the tile wrapper, not
   on .tcard (the button) — a box-shadow on the button would be
   clipped away by this element's own overflow: hidden. */
.tcard-item.is-active { box-shadow: 0 0 0 3px var(--brand); }

.tcard {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    padding: 0;
    margin: 0;
    border: 0;
    background: none;
    cursor: pointer;
}

.tcard img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center 16%;
    display: block;
    transition: transform 420ms var(--ease);
}

.tcard:hover img { transform: scale(1.045); }
.tcard:focus-visible { outline: 2.5px solid var(--gold); outline-offset: -3px; }

.tcard .initials {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    font-weight: 800;
    font-size: 1.8rem;
    color: var(--brand);
}

/* Video count, as a small pill over the photo — there's no room
   below it any more now the whole tile is the photograph. */
.tcard-n {
    position: absolute;
    top: 8px;
    left: 8px;
    z-index: 1;
    padding: 3px 9px;
    border-radius: 999px;
    font-size: 0.6rem;
    font-weight: 800;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: #fff;
    background: color-mix(in oklch, var(--brand) 78%, transparent);
}

/* Hidden by default, fading in full-bleed on hover/focus — matches the
   reference's own hover caption exactly: rgba(25,79,144,0.23), i.e.
   var(--brand) at 23% opacity (measured live off its computed style),
   centered white 700-weight text, opacity-only fade. pointer-events:none
   while hidden so this doesn't sit on top of .tcard (the filter button
   underneath it, same tile) stealing its clicks; only turns clickable
   once actually visible, so the name still reaches the trainer's own
   page same as before. */
.tcard-name {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 16px;
    background: color-mix(in oklch, var(--brand) 23%, transparent);
    color: #fff;
    font-size: 0.85rem;
    font-weight: 700;
    line-height: 1.35;
    text-decoration: none;
    opacity: 0;
    pointer-events: none;
    transition: opacity 300ms var(--ease);
}

.tcard-item:hover .tcard-name,
.tcard-item:focus-within .tcard-name {
    opacity: 1;
    pointer-events: auto;
}

.tcard-name:hover { text-decoration: underline; }

/* No hover on touch — unlike .pdetail-nav's own touch fallback (which
   hides its arrows entirely, fine there since the thumbnail rail still
   works without them), hiding a trainer's name entirely would leave
   touch visitors with no way to learn who they're looking at outside
   the small always-on video-count pill. Shown permanently instead,
   the same always-on behaviour this tile had before this pass. */
@media (hover: none) {
    .tcard-name { opacity: 1; pointer-events: auto; }
}

.trainer-dots {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    margin: 12px 0 20px;
}

.trainer-dot {
    width: 6px;
    height: 6px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: var(--line);
    cursor: pointer;
    transition: background 160ms ease, transform 160ms ease;
}

.trainer-dot:hover { background: color-mix(in oklch, var(--brand) 50%, var(--line)); }
.trainer-dot.is-active { background: var(--brand); transform: scale(1.35); }
.trainer-dot:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

.filter-note {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: -6px 0 16px;
    font-size: 0.82rem;
    color: var(--ink-soft);
}

.filter-clear {
    padding: 0;
    background: none;
    border: 0;
    font: inherit;
    font-weight: 700;
    color: var(--brand);
    cursor: pointer;
    text-decoration: underline;
}

/* --- the directory page --- */

.trainer-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
    gap: 16px;
}

.trainer-card {
    display: flex;
    gap: 14px;
    align-items: flex-start;
    padding: 16px;
    background: #fff;
    border: 1px solid var(--line);
    border-radius: 14px;
    text-decoration: none;
    transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

.trainer-card:hover {
    border-color: var(--brand);
    box-shadow: 0 6px 20px rgb(16 24 40 / 8%);
    transform: translateY(-2px);
}

.trainer-photo {
    flex: 0 0 auto;
    width: 68px;
    height: 68px;
    border-radius: 50%;
    overflow: hidden;
    background: var(--surface);
    box-shadow: 0 0 0 1px var(--line);
}

.trainer-photo img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center 20%;
    display: block;
}

.trainer-photo .initials {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    font-weight: 800;
    color: var(--brand);
}

.trainer-body { min-width: 0; }

.trainer-name {
    display: block;
    font-weight: 800;
    font-size: 0.96rem;
    color: var(--ink);
    line-height: 1.3;
}

.trainer-count {
    display: block;
    margin-top: 2px;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--green);
}

.trainer-sum {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-top: 7px;
    font-size: 0.8rem;
    line-height: 1.5;
    color: var(--ink-soft);
}

/* --- credentials list, on the trainer page and the video's Trainer tab --- */

.cred-list {
    margin: 10px 0 0;
    padding: 0;
    list-style: none;
    display: grid;
    gap: 7px;
}

.cred-list li {
    position: relative;
    padding-left: 20px;
    font-size: 0.85rem;
    line-height: 1.5;
    color: var(--ink-soft);
}

.cred-list li::before {
    content: "";
    position: absolute;
    left: 3px;
    top: 0.6em;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--green);
}

/* Two columns once there is room, because these run to twenty-eight
   entries and a single column turns one CV into a page of scrolling. */
@media (min-width: 820px) {
    .cred-list.is-long { grid-template-columns: 1fr 1fr; column-gap: 30px; }
}

/* Still exactly 4 tiles on a phone — the client's own ask ("show 4
   pictures") wasn't a desktop-only spec, and the reference itself
   carries no slides_per_view_tablet/mobile override in its
   data-settings, so slides_per_view stays 4 there too. The gap is
   already 10px from the shared max-width:900px rule above (same
   value as space_between_tablet and space_between_mobile in the
   reference's own config, so one rule covers both) — this block
   only tightens what's left: corner radius and the hover-caption/
   badge sizing for the smaller tile. */
@media (max-width: 560px) {
    .tcard-item { border-radius: 8px; }
    .tcard-name { padding: 10px; font-size: 0.68rem; }
    .tcard-n { top: 5px; left: 5px; padding: 2px 6px; font-size: 0.5rem; }
}

.cred-more {
    margin-top: 7px;
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--ink-faint);
}

/* display:flex above beats the [hidden] attribute's own display:none, so
   the note rendered its Clear button before any filter was on. */
.filter-note[hidden] { display: none; }

/* A credential list runs to 26 entries; centring the portrait against it
   parked the photograph two hundred pixels down the card, level with
   nothing. Top-aligned, so it sits beside the name it belongs to. */
.instr-hero { align-items: flex-start; }
.instr-hero .instr-avatar-lg { margin-top: 2px; }


/* ------------------------------------------------------------------------------------------------
   NAV UNIFICATION (Phase 19) -- partials/site-nav.blade.php and site-nav-drawer.blade.php render
   from $siteMenu (App\Services\SiteMenu) on BOTH site.layout and storefront.layout now, so
   site.layout loads this file too (see that layout's own head). Every rule below extends an
   existing, already token-driven selector to cover a case the two old hand-written copies of the
   nav never needed. Nothing above this point is touched.
   ------------------------------------------------------------------------------------------------ */

/* A dropdown child with no url yet (heading only -- a page still being built) renders as a
   button, never an anchor with an empty href. Same box as .drop-rich a; this only resets the
   native chrome a button carries that a span or anchor never did, and mutes the title since it
   goes nowhere yet. */
.drop-rich .drop-rich-item { display: block; width: 100%; padding: 11px 12px; border-radius: 9px; }
.drop-rich button.drop-rich-item { background: none; border: 0; font-family: inherit; text-align: left; cursor: default; }
.drop-rich button.drop-rich-item .drop-rich-title { color: var(--ink-faint); }
.drop-rich .drop-rich-item.is-active .drop-rich-title { color: var(--green); }

/* Same button-for-no-url rule for the two existing "coming soon" labels (nav-coming-soon-item,
   drawer-coming-soon-item -- both site.css, both already bare class selectors, not scoped to a
   span) and for a heading-only top-level drawer row (drawer-link). Only a native-chrome reset;
   the colour/size/spacing each already declares is unchanged. */
button.nav-coming-soon-item, button.drawer-coming-soon-item, button.drawer-link {
  width: 100%; text-align: left; background: none; border: 0; font: inherit;
}

/* is-active on the two nav shapes the old markup never needed it on: the drawer's split
   link+chevron row for a top-level item that both links somewhere and has children (the "Store"
   shape), a plain acc-inner child link, and drawer-link used on a top-level row with no children. */
.acc-cat-link.is-active, .drawer-link.is-active, .acc-inner a.is-active { color: var(--green); }

/* ==========================================================================
   PREVIEW PLAYER — self-hosted HLS in the .uni-preview frame.

   Two real bugs fixed here, both from one cause: a bare <video> was dropped
   into a frame whose fill rule only named iframe and img.

   1. The video stayed position:static and sized itself from the stream
      (853x480), rendering 717px tall inside a 675px frame. The frame clips,
      and the 42px it clipped was exactly the native control bar - so the
      seek bar, the duration and the fullscreen button were all cut off.

   2. .cdetail is `grid-template-columns: 1fr 320px`, and a 1fr track has
      min-width:auto, meaning it refuses to shrink below its content's
      intrinsic width. The video's 853px floor widened the track and shoved
      the 320px purchase card 242px past the right edge of the viewport.
      That is why the whole page went out of shape and scrolled sideways.

   Native controls are kept rather than replaced. They bring keyboard
   operation, fullscreen, captions and platform-correct touch targets for
   free, and a hand-built bar would be a second control system to keep
   accessible for no gain the viewer can see.
   ========================================================================== */

/* min-width:0 lets the track shrink to its share of the row regardless of
   what lands inside it. Without this, any wide child - a video, a table, a
   long unbroken string - silently pushes the sidebar off-screen again. */
.cdetail > * { min-width: 0; }

.uni-preview video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
  /* contain, not cover: these are lecture recordings, and cropping the
     edges of a slide removes the content being sold. A source that is not
     16:9 letterboxes onto the frame's own background, which is honest. */
  object-fit: contain;
  background: transparent;
}

.uni-preview {
  /* Tied to the brand ramp rather than a loose hex, so letterbox bars and
     the pre-play frame sit in the same family as the rest of the page. */
  background: color-mix(in oklch, var(--brand) 34%, black);
  border: 1px solid color-mix(in oklch, var(--brand) 18%, transparent);
}

/* The video is focusable, so it owes a visible focus ring like any other
   control. Offset outward: an inset ring would sit under the frame's own
   overflow clip and disappear. */
.uni-preview video:focus-visible {
  outline: 2px solid var(--green);
  outline-offset: 3px;
}

.uni-preview-note {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 11px 2px 0;
  font-size: 0.79rem;
  color: var(--ink-soft);
}

/* A small brand dot instead of an icon: the sprite has no "free" glyph, and
   inventing one for a single caption is a second icon vocabulary. */
.uni-preview-note::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--green);
}

/* An expired signed link is an expected state on a page that can sit open
   for longer than the fifteen-minute window, so it reads as an instruction
   rather than a failure. */
.uni-preview-note.is-expired { color: var(--ink); font-weight: 600; }
.uni-preview-note.is-expired::before { background: var(--danger); }

@media (max-width: 640px) {
  /* Native controls are around 40px tall and land inside the frame, so a
     very short frame leaves almost no picture. A 16:9 frame on a 360px
     screen is 202px tall, which is enough - this just stops it going below
     that on the narrowest devices in landscape. */
  .uni-preview { min-height: 190px; }
}

/* ==========================================================================
   TEAM CAROUSEL corrections -- REMOVED 2026-08-26.

   This block used to override site.css's .t3d-nav/.t3d-card here (site.css
   was treated as "closed", so fixes for it landed in this file instead,
   loaded after it). That pattern is exactly what caused the repeated,
   confusing bugs this cleanup pass was asked to find: a fix living in the
   file that DOESN'T describe the bug it's fixing, silently overriding the
   file that does. Every actual fix this block carried has been moved back
   to its root cause in site.css instead of staying here as a patch:

     - The arrow "shake" (translate:0 -50% + a hover `transform` that also
       set translateY, doubling the vertical shift for one frame) is fixed
       at the source in site.css's new shared .carousel-arrow rule, which
       .t3d-nav/.partners-nav/.rev-arrow all use now -- so Partners/Our
       Clients and Reviews get the same fix Team quietly had here, instead
       of only Team ever having it.
     - .t3d-nav's position (left/right, focus-visible outline) is restated
       directly on .t3d-nav in site.css, once.
     - .t3d-card's 420ms/var(--ease) transition was already correct in
       site.css by the time this ran -- this file's own comment says as
       much (the 520ms/opacity+filter version it was originally written to
       fix is what's actually gone).
     - [data-depth="2"/"3"] and the duplicate [data-hidden="true"] rule were
       already dead: this file's own comments called the former "dead
       weight...today's markup never sets data-depth" and the latter was a
       second copy of a rule two paragraphs above it, both here from the
       old 3D coverflow build. Dropped rather than restated anywhere.
   ========================================================================== */

/* ============================================================================================
   TEAM -- Phase 19, Site Takeover: /team index cards, an individual member's own page, and the
   prev-next pager between member pages. The card (.person, .team-people, .team-dept-head) and
   profile (.tm-name, .tm-role, .tm-bio, .tm-h, .tm-list, .tm-portrait, .tm-dept) vocabulary
   already lives in site.css -- this only adds what that vocabulary never covered: an initials
   fallback for a person with no photo (site.css only ever styled the img case), the client's
   uppercase department label, the two-column split for a member's own page, and the pager.
   ============================================================================================ */
.team-dept-head h2 { text-transform: uppercase; }

/* Same navy card background .person and .tm-portrait already paint behind a photograph, filled
   with a person's initials instead when photo_path is null. Shares the same 3 to 4 aspect ratio
   as .person img and .tm-portrait img so a photo and its fallback occupy identically sized slots
   and a card never resizes depending on whether the photo exists. */
.person-initials { display: flex; align-items: center; justify-content: center; width: 100%;
  aspect-ratio: 3 / 4; color: #fff; font-weight: 800; letter-spacing: 0.02em;
  font-size: clamp(1.6rem, 4vw, 2.4rem); }
@media (max-width: 760px) {
  /* Mirrors site.css's own .tm-portrait img rule at this breakpoint (a fixed 260px band, no
     forced aspect ratio) so a no-photo member's mobile layout matches a photographed one. */
  .team-detail-aside .tm-portrait .person-initials { height: 100%; aspect-ratio: auto; }
}

/* A member's own page: portrait plus the WhatsApp CTA in a sticky left column, the profile
   content on the right. */
.team-detail { display: grid; gap: clamp(28px, 5vw, 56px); align-items: start; }
@media (min-width: 860px) { .team-detail { grid-template-columns: 300px 1fr; } }
.team-detail-aside { display: grid; gap: 16px; }
@media (min-width: 860px) { .team-detail-aside { position: sticky; top: 24px; } }
.team-detail-aside .tm-portrait { border-radius: var(--radius-card); overflow: hidden; }
.team-detail-body .team-role { margin-bottom: 4px; }
.team-detail-body .tm-bio { max-width: none; }
.team-detail-body .tm-h:first-child { margin-top: 0; }

/* Previous and next between member pages, either side of a plain route back to the index -- so
   a page reached from a search engine or a shared link is never a dead end. */
.team-pagenav { display: flex; align-items: center; justify-content: space-between; gap: 16px;
  margin-top: clamp(36px, 5vw, 56px); padding-top: 22px; border-top: 1px solid var(--line); }
.team-pagenav-link { display: inline-flex; align-items: center; gap: 8px; min-width: 0;
  max-width: 38%; padding-block: 11px; font-weight: 700; font-size: 0.92rem; color: var(--ink);
  transition: color 160ms ease, transform 160ms var(--ease); }
.team-pagenav-link span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.team-pagenav-link svg { width: 16px; height: 16px; color: var(--green); flex: none; }
.team-pagenav-link.prev svg { transform: rotate(180deg); }
.team-pagenav-link:hover { color: var(--brand); }
.team-pagenav-link:active { transform: scale(0.97); }
.team-pagenav-all { flex: none; padding-block: 11px; font-size: 0.8rem; font-weight: 800;
  letter-spacing: 0.03em; text-transform: uppercase; color: var(--ink-faint);
  transition: color 160ms ease; }
.team-pagenav-all:hover { color: var(--brand); }
.person:active { transform: scale(0.97); }

/* About Us (site.about) -- the client's eleven real sections, read from
   site_settings by AboutController. Everything below reuses existing
   tokens and the same .split / .split.flip / .tone / .checks / .grid-2
   vocabulary already established for service pages (see
   site.partials.section). The two classes that are new -- .about-statement
   and .about-explainer -- are defined at the end of this file, in the
   second-pass block that explains why one treatment was not enough. */

/* Contact Us (site.contact) -- App\Http\Controllers\Site\ContactController. Reuses the split /
   media / btn / btn-wa / btn-outline vocabulary verbatim; only adds what nothing above already
   covers: a photo-style frame for the map iframe, the row of quick-action buttons, and the
   label/value detail rows below them. */

/* The map on a light page section. Deliberately not the footer's own .map (public/css/site.css)
   -- that class's dark invert filter is tuned for the navy footer background and would read as
   a wrong-coloured map here. Reuses .media's existing photograph treatment (rounded corners,
   drop shadow, 16/10 frame) for an iframe instead of an img, so the embed sits in the split
   layout exactly like every other section's photo does. */
.media iframe { width: 100%; aspect-ratio: 16 / 10; border: 0; display: block; border-radius: 16px; box-shadow: 0 24px 48px -30px rgba(25,79,144,0.6); background: var(--surface); }

.contact-quick { display: flex; flex-wrap: wrap; gap: 12px; margin: 22px 0 6px; }

.contact-detail-list { display: grid; gap: 0; margin-top: 26px; }
.contact-detail { padding-block: 14px; border-top: 1px solid var(--line); display: grid; gap: 4px; }
.contact-detail:first-child { border-top: 0; padding-top: 0; }
.contact-detail dt { font-size: 0.78rem; font-weight: 800; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-faint); }
.contact-detail dd { margin: 0; font-size: 0.98rem; font-weight: 600; color: var(--ink); }
.contact-detail dd a { color: var(--brand); font-weight: 700; display: inline-block; transition: color 150ms ease, transform 120ms var(--ease); }
.contact-detail dd a:hover { color: var(--brand-deep); text-decoration: underline; }
.contact-detail dd a:active { transform: scale(0.97); }
@media (prefers-reduced-motion: reduce) {
  .contact-detail dd a { transition: color 150ms ease; }
  .contact-detail dd a:active { transform: none; }
}

/* About Us, second pass. The first one gave every text-only section
   `.shell.split.single`, which caps the SHELL at 74ch -- so a 638px band
   sat between two 1280px photo bands and the page snapped
   narrow-wide-narrow-wide all the way down. Measured at 1425px: eight
   638px shells, two 1280px ones. Two treatments now instead of one, and
   both sit on the normal shell.

   .about-statement -- the three manifesto sections (opening statement,
   vision, mission). A statement is read once, whole, so a narrow centred
   measure is correct here; it just needs to look deliberate rather than
   cramped. Replaces .about-lead, which only changed the body copy. */
.about-statement { max-width: 62ch; margin-inline: auto; text-align: center; }
.about-statement h2 { margin: 0 0 18px; }
.about-statement .prose p { font-size: clamp(1.06rem, 1.8vw, 1.3rem); line-height: 1.65; color: var(--ink); font-weight: 500; }
.about-statement .prose p + p { margin-top: 14px; }

/* A short rule under the heading, so the three statements read as the
   same kind of thing wherever they land on the page. Gold, because that
   is what the brand already uses to mark something as its own claim. */
.about-statement h2::after {
  content: ""; display: block; width: 54px; height: 3px; border-radius: 2px;
  background: var(--gold); margin: 14px auto 0;
}

/* .about-explainer -- the five long explanatory sections. Heading in its
   own left rail against the full shell: it settles the page width and
   gives the eye a fixed landing point down a long read, which a stack of
   centred columns does not. The body keeps a real measure -- a line of
   text spanning 1280px is unreadable however much room there is. */
.about-explainer { display: grid; gap: clamp(14px, 3vw, 46px); align-items: start; }
.about-explainer .prose { max-width: 70ch; }
.about-explainer-head h2 { margin: 0; }

@media (min-width: 900px) {
  .about-explainer { grid-template-columns: minmax(210px, 0.8fr) minmax(0, 2fr); }
  /* Optical alignment: an h2's cap height starts below its line box, so
     without this nudge the heading reads as sitting lower than the first
     line of body text beside it. */
  .about-explainer-head { padding-top: 2px; }
}

/* Both treatments inherit the section's own vertical rhythm, so nothing
   here touches padding -- see site.css's own `section` rule. */

/* Payment-method buttons on the order summary (Phase 20). One per method
   the bound gateway can actually start for this order — the customer
   picks KNET or a card or Deema here, on our page, instead of being sent
   to a gateway-branded menu to pick.

   Deliberately not styled as .btn-primary repeated three times: three
   identical filled buttons in a column read as one decision made three
   times over. These are a list of choices, so they look like one — a
   quiet row each, the name carrying the weight, the hint underneath in
   the same muted ink the rest of the summary uses for secondary text. */
.pay-methods { display: grid; gap: 10px; text-align: left; }

.pay-method {
  width: 100%;
  display: grid;
  gap: 2px;
  padding: 14px 16px;
  min-height: 44px;
  border: 1.5px solid var(--line);
  border-radius: 12px;
  background: #fff;
  cursor: pointer;
  text-align: left;
  transition: border-color 140ms var(--ease), background 140ms var(--ease), transform 140ms var(--ease);
}

.pay-method:hover,
.pay-method:focus-visible { border-color: var(--brand); background: var(--brand-tint); }
.pay-method:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* The press. Every other button in this app has one; a payment button
   without tactile feedback feels broken at exactly the moment a customer
   most wants to know their tap registered. */
.pay-method:active { transform: scale(0.99); }

.pay-method-name { font-weight: 800; font-size: 0.95rem; color: var(--ink); }

/* Logo pass (2026-07-31). The name row now holds the method name plus its
   official mark(s) side by side; the hint keeps sitting on its own row
   below, unchanged. wrap allows the name to fall to a second line on a
   narrow phone rather than push the marks off the edge of the button --
   the name is the label a customer actually reads, the marks are just
   recognition. */
.pay-method-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

/* The name wraps its own text; the row never wraps. Letting the row wrap
   pushed Deema's mark onto a second line at phone width, because its
   label is the longest of the three, which made one button taller than
   its siblings and put its logo somewhere different from the other two.
   Three buttons offering the same kind of choice should not each be laid
   out differently. */
.pay-method-name { min-width: 0; }

.pay-method-logos { display: flex; align-items: center; gap: 8px; flex: none; }

.pay-method-logo-wrap { display: inline-flex; align-items: center; }

/* A mark that renders light-on-transparent (Deema's wordmark is white)
   would vanish on this button's white background, so it gets a small
   dark plate behind it instead of being recoloured -- the plate reuses
   the page's own --ink token rather than inventing a brand colour. */
.pay-method-logo-wrap.has-plate {
  background: var(--ink);
  border-radius: 6px;
  padding: 4px 8px;
}

.pay-method-logo { display: block; width: auto; }

/* Per-mark heights rather than one shared box: at an identical height a
   slim wordmark and a solid-filled circular mark do not read as the same
   size, so each gets its own value tuned by eye, not a single number
   applied to all three. */
.pay-method-logo--knet { height: 24px; }
.pay-method-logo--visa { height: 20px; }
.pay-method-logo--mastercard { height: 17px; }
.pay-method-logo--deema { height: 14px; }

@media (prefers-reduced-motion: reduce) {
  .pay-method { transition: none; }
  .pay-method:active { transform: none; }
}

/* ============================================================================================
   CHECKOUT: embedded card payment modal (Phase 21, Tap Card SDK)
   ============================================================================================
   Card only -- KNET and Deema keep redirecting exactly as before (see
   checkout-summary.blade.php and card-payment-modal.blade.php's own docblocks for why only Card
   can honestly avoid one). Same native <dialog> convention as .tm above (site.css) -- radius,
   backdrop and entrance motion reuse THAT file's tmIn/tmFade keyframes verbatim rather than
   inventing a second modal language for one page; both stylesheets share one document, so the
   keyframes defined there are available here with no redeclaration. Panels are shown/hidden
   purely by [data-state] on the dialog itself -- see the script driving it at the bottom of
   checkout-summary.blade.php. Trust-first on purpose: calm entrance, no motion on the payment
   states themselves, and the close button physically disappears (not just disables) while
   processing/3ds so there is never a visible way to wander off mid-charge. */
.card-pay-modal {
  width: min(440px, 94vw);
  padding: 0;
  border: 0;
  border-radius: 18px;
  background: #fff;
  color: var(--ink);
  overflow: hidden;
}
.card-pay-modal::backdrop {
  background: color-mix(in oklch, var(--brand-deeper) 62%, transparent);
  backdrop-filter: blur(4px);
}
.card-pay-modal[open] { animation: tmIn 260ms var(--ease); }
.card-pay-modal[open]::backdrop { animation: tmFade 260ms var(--ease); }
@media (prefers-reduced-motion: reduce) {
  .card-pay-modal[open], .card-pay-modal[open]::backdrop { animation: none; }
}

.cpm-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
  padding: 20px 20px 16px; border-bottom: 1px solid var(--line);
}
.cpm-head-text { display: flex; flex-direction: column; gap: 3px; }
.cpm-head-text strong { font-size: 1rem; font-weight: 800; }
.cpm-head-sub { font-size: 0.78rem; color: var(--ink-faint); }
.cpm-close {
  width: 34px; height: 34px; border-radius: 50%; border: 1px solid var(--line); background: #fff;
  color: var(--ink-soft); display: grid; place-items: center; cursor: pointer; flex: none;
  transition: border-color 140ms ease, color 140ms ease;
}
.cpm-close:hover { border-color: var(--brand); color: var(--brand); }
/* A customer must not be able to wander off mid-charge -- hidden, not merely disabled, so it is
   never even a visible false affordance while a payment is actually in flight. The keyboard path
   (Esc -> the dialog's native 'cancel' event) is blocked separately, in JS, for the same two
   states -- see checkout-summary.blade.php's modal script. */
.card-pay-modal[data-state="processing"] .cpm-close,
.card-pay-modal[data-state="3ds"] .cpm-close { visibility: hidden; }

.cpm-body { padding: 22px 20px 26px; min-height: 200px; }

/* One rule turns every panel off; each state below turns exactly one back on -- simpler than
   pairing an on/off rule per panel, and it means adding a future state is a single new line. */
.cpm-panel { display: none; }
.card-pay-modal[data-state="form"] .cpm-panel--form,
.card-pay-modal[data-state="3ds"] .cpm-panel--3ds { display: block; }
.card-pay-modal[data-state="loading"] .cpm-panel--loading,
.card-pay-modal[data-state="processing"] .cpm-panel--processing,
.card-pay-modal[data-state="success"] .cpm-panel--success,
.card-pay-modal[data-state="error"] .cpm-panel--error {
  display: flex; flex-direction: column; align-items: center; text-align: center; gap: 14px;
  padding: 24px 6px;
}

.cpm-panel p { color: var(--ink-soft); font-size: 0.9rem; margin: 0; }

.cpm-spinner {
  width: 30px; height: 30px; border-radius: 50%; border: 3px solid var(--brand-tint);
  border-top-color: var(--brand); animation: cpmSpin 800ms linear infinite;
}
@keyframes cpmSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .cpm-spinner { animation-duration: 1600ms; } }

#card-sdk-id { min-height: 130px; margin-bottom: 16px; }
.cpm-secure-note {
  margin-top: 14px; display: flex; align-items: center; gap: 7px; justify-content: center;
  font-size: 0.74rem; color: var(--ink-faint);
}
.cpm-secure-note svg { color: var(--green); flex: none; }

.cpm-3ds-note { text-align: center; font-size: 0.83rem; color: var(--ink-soft); margin: 0 0 12px; }
.cpm-3ds-frame { width: 100%; height: 420px; border: 1px solid var(--line); border-radius: 12px; }

.cpm-result-ic { width: 52px; height: 52px; border-radius: 50%; display: grid; place-items: center; flex: none; }
.cpm-result-ic--ok { background: color-mix(in oklch, var(--green) 14%, white 86%); color: var(--green); }
.cpm-result-ic--bad { background: var(--danger-tint); color: var(--danger); }

.cpm-error-actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }

@media (max-width: 480px) {
  .cpm-3ds-frame { height: 380px; }
}


/* ================================================================================================
   PUBLIC STOREFRONT SURFACES — 2026-09-08 redesign pass
   Scope: the catalog listing card, the category listing page, the product detail page and the
   store's own header dropdown. Nothing here touches cart, checkout, University, Courses, Team or
   any admin surface.

   Everything below is ADDITIVE and reads the same tokens the rest of this file does. No new
   colour is introduced: brand navy, brand-tint, green, green-deep, gold, the ink ramp, --line
   and --surface only. It loads after every rule it refines, so no !important is used anywhere.

   Why each block exists is stated above it. The five headline problems this pass fixes:
     1. the card action button rendered OUTSIDE the card box on every product grid
     2. a disabled primary button that read as broken rather than as waiting for a choice
     3. three equally loud stacked calls to action in the buy box, with no hierarchy
     4. a filter rail holding one checkbox beside 700px of empty column
     5. raw warehouse stock counts competing with the price for attention
   ================================================================================================ */

/* ------------------------------------------------------------------------------------------------
   S1. LISTING CARD — one card, actions included.

   The defect: a card's action is a form element (add to cart, buy now) or a second anchor (view
   options), and neither may legally sit inside the anchor that wraps the photo and body.
   storefront.partials.products-grid therefore renders them as SIBLINGS of .pcard inside
   .pcard-item — correct HTML, but .pcard carried the border, radius and background, so the card
   visibly ENDED above its own button and the button floated on the page background beneath it.

   The fix is to promote the card chrome one level, from .pcard to .pcard-item, and let .pcard
   become a transparent inner region. .pcard keeps its own chrome wherever it is used WITHOUT a
   .pcard-item wrapper (the product page's related rail, the account wishlist), so nothing else
   changes shape.
   ------------------------------------------------------------------------------------------------ */
.pcard-item {
  position: relative;
  display: flex;
  flex-direction: column;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  overflow: hidden;
  box-shadow: 0 10px 24px -20px rgba(25, 79, 144, 0.42);
  transition: transform 180ms var(--ease), border-color 160ms ease, box-shadow 220ms var(--ease);
}
.pcard-item:hover,
.pcard-item:focus-within {
  transform: translateY(-3px);
  border-color: color-mix(in oklch, var(--brand) 46%, transparent);
  box-shadow: 0 22px 38px -22px rgba(25, 79, 144, 0.5);
}
.pcard-item > .pcard-link { flex: 1 1 auto; }
.pcard-item > .pcard-link > .pcard { height: 100%; }
.pcard-item .pcard,
.pcard-item .pcard:hover,
.pcard-item .pcard:focus-visible {
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  transform: none;
}
/* The action sits inside the card box now, inset by the same gutter the body uses. */
.pcard-item > .pcard-add,
.pcard-item > form { margin: 0 13px 13px; }
.pcard-item > form { display: block; }

/* Photo tile. Every product shot in this catalog is a studio JPEG with an OPAQUE backdrop baked
   in, measured across all eight of them at roughly rgb(245 243 244) to rgb(250 244 244). The
   tile is set to the middle of that range so the photo's own backdrop and the tile it sits on
   read as one surface; the previous putty tone (and a white-to-tint wash tried first) both left
   a hard rectangle floating inside the card, which is most of what made these cards look
   unfinished. A hairline separates the photo from the card body instead. */
.pcard-thumb {
  background: #f6f3f4;
  border-bottom: 1px solid color-mix(in oklch, var(--line) 70%, transparent);
  overflow: hidden;
}
.pcard-thumb img { transition: transform 420ms var(--ease); }
.pcard-item:hover .pcard-thumb img { transform: scale(1.045); }

/* Corner flags read as labels rather than floating text: a real chip, aligned to the photo's
   inner gutter. */
.pcard-cat,
.pcard-stockflag {
  left: 10px;
  top: 10px;
  padding: 4px 9px;
  letter-spacing: 0.06em;
  box-shadow: 0 4px 10px -6px rgba(25, 79, 144, 0.4);
}
.pcard-stockflag { left: auto; right: 10px; }

/* Body: the name gets two reserved lines so a one-word and a four-word product name still line
   their prices up across a row, and the price becomes the loudest thing on the card. */
.pcard-body { padding: 13px 14px 14px; gap: 7px; }
.pcard-name {
  font-size: 0.94rem;
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: 2.7em;
}
.pcard-facts { margin-top: auto; padding-top: 2px; }
.pcard-price { font-size: 1.06rem; letter-spacing: -0.01em; }
/* Stock was a green number competing with the price. In stock is the expected case, so it is
   demoted to quiet meta; only genuinely scarce stock keeps a loud colour. */
.pcard-stock { font-size: 0.73rem; font-weight: 600; color: var(--ink-faint); }
.pcard-stock.low { font-weight: 800; color: color-mix(in oklch, var(--gold) 62%, black 38%); }

/* One action-bar height across the whole grid, whether the card offers "View options" or the
   add-to-cart pair.

   "View options" is now brand navy rather than brand green, for two reasons. Measured: the green
   label on white is 3.54:1, under the 4.5:1 floor this project holds itself to for text at this
   size; navy on white is 8.2:1. And it separates the two jobs a card button can have — navy
   takes you somewhere (pick a size on the product page), green commits something to the cart —
   instead of one colour meaning both. The filled green add-to-cart is left matching .btn-primary
   as it is used site-wide; that pairing is a brand-level question, not this component's to
   answer alone. */
.pcard-add { min-height: 42px; border-radius: 10px; font-size: 0.81rem; }
.pcard-item > .pcard-add { border-color: var(--brand); color: var(--brand); }
.pcard-item > .pcard-add:hover { background: var(--brand); color: #fff; }
.pcard-actions { gap: 8px; }
.pcard-buy { min-height: 42px; }

/* ------------------------------------------------------------------------------------------------
   S2. CATEGORY LISTING — the filter rail and the result toolbar.

   Before: a bare 220px column holding one checkbox, a paragraph and a pill button, followed by
   roughly 700px of empty page at 1440, and on a phone the same block pushed every product below
   the fold. Now the rail is a real card that sticks with the scroll on desktop, and folds into a
   single-row toolbar on anything narrower, so products start near the top of the page on a phone.
   ------------------------------------------------------------------------------------------------ */
/* Guarded by min-width, not stated bare: this file loads after the original
   `@media (max-width: 860px) { .listing { grid-template-columns: 1fr } }`, so a bare rule of the
   same specificity would win at every width and put a 250px rail beside the grid on a 360px
   phone. Same guard on .pdetail below, for the same reason. */
@media (min-width: 861px) {
  .listing { grid-template-columns: 250px minmax(0, 1fr); }
}
.listing { gap: clamp(20px, 2.6vw, 34px); }

.filters {
  top: 96px;
  gap: 16px;
  padding: 18px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: 0 12px 26px -22px rgba(25, 79, 144, 0.4);
}
.filter-group h4 { color: var(--brand); font-size: 0.72rem; }
.filter-row { min-height: 44px; font-weight: 600; color: var(--ink); }
.filter-row:hover { color: var(--brand); }
.filters-note { font-size: 0.75rem; line-height: 1.55; }

@media (max-width: 860px) {
  .filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 18px;
    padding: 12px 14px;
  }
  .filters .filter-group { display: flex; align-items: center; gap: 12px; }
  .filters .filter-group h4 { margin-bottom: 0; }
  .filters .filter-row { padding: 0; }
  .filters .btn-block { width: auto; flex: none; margin-inline-start: auto; }
  .filters-note { flex: 1 0 100%; border-top: 1px solid var(--line); padding-top: 10px; margin: 0; }
}

/* Result count and sort now sit on one quiet bar instead of floating at opposite ends of an
   invisible row, and the native select gets the same border, radius and 44px target as every
   other control in the store. */
.listing-topbar {
  padding: 9px 8px 9px 16px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 999px;
  margin-bottom: 18px;
}
.result-count { font-weight: 700; color: var(--ink-soft); }
.sort-select {
  min-height: 42px;
  padding-inline: 15px 38px;
  border-radius: 999px;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23194f90' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 9l7 7 7-7'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 12px 12px;
  transition: border-color 160ms ease;
}
.sort-select:hover { border-color: var(--brand); }

/* The listing's own section head duplicated the title band directly above it (the band says
   "Apparel", the head said "Products"). It stays in the markup as the section's accessible
   heading and is demoted visually to a quiet label, so the eye goes straight to the goods. */
.listing-sec > .sec-head { margin-bottom: 12px; }
.listing-sec > .sec-head h2 {
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* ------------------------------------------------------------------------------------------------
   S3. BUTTONS — a disabled state that reads as waiting, not as broken.

   .btn:disabled was opacity 0.55 over a filled green, which put white text on a pale green at
   roughly 1.9:1 — the loudest control on the product page looked like a rendering fault until a
   size was picked. It is now an inert chip with legible ink, which is also how a customer learns
   the control is waiting for them.
   ------------------------------------------------------------------------------------------------ */
.btn:disabled {
  opacity: 1;
  background: var(--surface);
  color: var(--ink-faint);
  border-color: var(--line);
  box-shadow: none;
}
.btn:focus-visible { outline: 2.5px solid var(--gold); outline-offset: 2px; }

/* ------------------------------------------------------------------------------------------------
   S4. PRODUCT DETAIL — gallery, buy-box hierarchy, sticky column.
   ------------------------------------------------------------------------------------------------ */
@media (min-width: 861px) {
  .pdetail { grid-template-columns: minmax(0, 1.02fr) minmax(0, 0.98fr); }
}

/* Same measured tile tone as the listing card above, so the hero photo and the related-product
   thumbnails under it sit on one surface rather than three slightly different greys. The frame
   gains the brand-tinted lift .media already gives photographs on the marketing pages. */
.pdetail-photo {
  background: #f6f3f4;
  border-radius: 18px;
  box-shadow: 0 26px 52px -36px rgba(25, 79, 144, 0.55);
}
.pdetail-photo img { padding: clamp(14px, 3vw, 34px); }

/* The gallery thumbnail rail is NOT restyled here. .pdetail-thumbs and .pdetail-thumb are
   declared in storefront.product's own page-level style block, which the browser reads after
   this file, so a rule here of the same specificity would silently lose. They were changed at
   source instead, in that block, where they already live. */

/* The buy column follows the gallery on a tall desktop photo instead of leaving the actions
   scrolled off screen. */
@media (min-width: 861px) {
  .pdetail-buy { position: sticky; top: 96px; }
}

.pdetail-eyebrow { letter-spacing: 0.08em; }
.pdetail h1 { line-height: 1.18; }
.pdetail-price {
  margin-top: 14px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
  font-size: clamp(1.5rem, 2.6vw, 1.8rem);
}
.pdetail-desc { margin-top: 14px; line-height: 1.65; }

/* "Select size to continue." was plain body text doing the job of a form hint. It now reads as
   one, with a dot marker in the same amber the low-stock size chips already use. */
.avail-line:not(.ok):not(.warn):not(.bad) { color: var(--ink-soft); font-weight: 600; }
.avail-line:not(.ok):not(.warn):not(.bad)::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--gold);
  flex: none;
}

.opt-head { margin-top: 22px; }
.opt-size { min-width: 50px; }
.opt-size:hover:not(:disabled):not(.oos) { border-color: var(--brand); color: var(--brand); }

/* THE ACTION BLOCK. Three full-width buttons of equal weight gave the loudest colour on the
   page (WhatsApp green) to the least committed action. Add to cart now leads and takes the
   wider cell; Buy now sits beside it as the alternate route to the same cart; the WhatsApp
   enquiry keeps its exact link, target, icon and label but moves below, where a secondary
   channel belongs. Nothing is removed. */
.pdetail-actions {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: 10px;
  margin-top: 22px;
}
.pdetail-actions .btn { flex: none; width: 100%; }
.pdetail-actions .btn-wa {
  grid-column: 1 / -1;
  margin-top: 8px;
}
.pdetail-actions .btn-primary { min-height: 50px; font-size: 0.92rem; }
@media (max-width: 480px) {
  .pdetail-actions { grid-template-columns: 1fr; }
}

/* ------------------------------------------------------------------------------------------------
   S5. RELATED RAIL — the product page's "You may also need" grid uses .pcard directly with no
   .pcard-item wrapper and no action button, so it keeps its own chrome; it only needed the same
   photo wash and hover as the listing card so the two never read as two different components.
   ------------------------------------------------------------------------------------------------ */
.pgrid-sm .pcard-thumb img { transition: transform 420ms var(--ease); }
.pgrid-sm .pcard:hover .pcard-thumb img { transform: scale(1.045); }
@media (max-width: 860px) {
  /* Four fixed columns at 360px gave 78px-wide cards with three-line names. */
  .pgrid.pgrid-sm { grid-template-columns: repeat(2, 1fr); }
}
/* On the narrowest phones a price and a stock note cannot share one line without both of them
   wrapping mid-word, so they stack. */
@media (max-width: 420px) {
  .pcard-facts { display: block; }
  .pcard-stock { display: block; margin-top: 3px; }
}

/* ------------------------------------------------------------------------------------------------
   S6. HEADER DROPDOWN — the store and overview menus.

   A .drop-rich is 290px wide and sized for two-line entries. The Overview and Store menus carry
   labels with no subtitle, so the panel rendered as a near-empty white slab with two bold words
   floating in it. When no entry has a subtitle the panel narrows to fit its own content.
   ------------------------------------------------------------------------------------------------ */
.drop-rich:not(:has(.drop-rich-sub)) { width: 218px; }
.drop-rich:not(:has(.drop-rich-sub)) a { padding: 10px 12px; }
.drop-rich a { transition: background 150ms ease, transform 200ms var(--ease); }
.drop-rich a:hover { transform: translateX(2px); }
.drop-rich a[aria-current] .drop-rich-title { color: var(--green); }

/* ------------------------------------------------------------------------------------------------
   S7. MOTION — entrance and feedback only, and every piece of it stands down when the visitor
   asks for less. There is no looping or decorative motion anywhere in this block.
   ------------------------------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .pcard-item,
  .pcard-thumb img,
  .drop-rich a { transition: none; }
  .pcard-item:hover,
  .pcard-item:focus-within { transform: none; }
  .pcard-item:hover .pcard-thumb img,
  .pgrid-sm .pcard:hover .pcard-thumb img,
  .drop-rich a:hover { transform: none; }
}
