/* WSK Commerce — shared styles for the admin panel and storefront (plain CSS, no build step) */

/* Used only by the tenant admin panel's Shopify-style redesign further down
   this file (everything scoped under `body.admin-page`) — safe to load
   globally since nothing outside that scope references the Inter family,
   so the storefront and platform-admin panel (its own separate stylesheet)
   are visually unaffected either way. */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;650;700;800&display=swap');

:root {
  /* Admin panel palette — deliberately the SAME family as the storefront's
     default "Classic Navy" theme (--store-primary etc., further down this
     file) rather than its own separate identity: navy #1E3A5F as the one
     primary brand color everywhere (buttons, active nav, focus rings,
     sidebar), and coral #E85D4E reserved specifically for --accent, which
     is used ONLY for "needs attention / urgent" signals (nav notification
     badges, the Needs Attention dashboard section, overdue/low-stock
     emphasis) — never as a general decorative color. This used to be a
     teal/amber pair unrelated to the storefront; recoloring here is the
     only actual color change in this design pass, every other token below
     (--danger, --warn, --success) already carried a distinct, correct
     semantic meaning and is left alone. */
  --brand: #1E3A5F;
  --brand-dark: #14293F;
  --brand-darker: #0D1D2C;
  --brand-light: #E8EDF3;
  --accent: #E85D4E;
  --accent-dark: #D1483A;
  --accent-light: #FDECEA;
  --bg: #f3f5f7;
  --surface: #ffffff;
  --border: #e4e7ec;
  --border-strong: #d3d8e0;
  --text: #1a2027;
  --text-muted: #67707c;
  --danger: #dc2626;
  --danger-light: #fdecec;
  --success: #16a34a;
  --success-light: #e6f7ec;
  --warn: #d97706;
  --warn-light: #fef3e2;
  --radius: 10px;
  --radius-lg: 12px;
  --shadow-sm: 0 1px 2px rgba(20, 30, 40, 0.05);
  --shadow: 0 1px 3px rgba(20, 30, 40, 0.07), 0 1px 2px rgba(20, 30, 40, 0.05);
  --shadow-md: 0 4px 14px rgba(20, 30, 40, 0.09);
  --shadow-lg: 0 16px 40px rgba(14, 25, 35, 0.16);
  --ease: cubic-bezier(0.2, 0.6, 0.3, 1);
  /* Spacing scale — the four values used consistently for every gap,
     margin, and padding introduced or touched in this design pass (stat
     grid, dashboard sections, card spacing). Existing pages keep their
     original spacing untouched to avoid an unrelated visual shift
     everywhere at once; new/rebuilt dashboard markup uses these. */
  --space-1: 8px;
  --space-2: 16px;
  --space-3: 24px;
  --space-4: 32px;
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
a { color: var(--brand); text-decoration: none; transition: color .15s var(--ease); }
/* :not(...) is the actual bug fix here — a LOT of link-styled elements in
   this app are <a> tags standing in for buttons, sidebar nav items, and
   card components (Download/Print/View links styled as buttons; the
   sidebar's Dashboard/POS/Orders/... nav links; dashboard stat cards). A
   bare "a:hover" selector is MORE specific than a single class like
   ".btn-primary" or ".sidebar a:hover" would assume it beats (element +
   pseudo-class can outrank one or two chained classes on the specificity
   type-count), so without excluding those components this rule silently
   overrides their own hover text color.
   The exclusions are wrapped in :where(...), which ALWAYS has zero
   specificity no matter how many selectors are listed inside it — every
   previous fix for this bug (see git history / earlier fix notes) added
   another :not(.foo) here, and each one increased this rule's own
   specificity a little more, until it became MORE specific than
   ".sidebar a:hover" and started winning again — painting sidebar link
   text var(--brand-dark) (near-navy) on the navy sidebar background,
   i.e. invisible on hover. :where() ends that escalation for good: this
   rule's specificity is permanently pinned to plain "a:hover" (one type +
   one pseudo-class) regardless of how long the exclusion list grows, so a
   component-specific hover rule with its own class only ever needs to beat
   that fixed, low bar — it can never lose a specificity race to this rule
   again. Every .btn variant below still also declares its own hover color
   explicitly too (defense in depth: correct even if this exclusion list is
   ever emptied by mistake). */
a:hover:not(:where(.btn, .stat-card, .sidebar a, .attention-row, .getting-started-item)) {
  text-decoration: underline;
  color: var(--brand-dark);
}
h1, h2, h3, h4 { margin: 0 0 8px; font-weight: 650; letter-spacing: -0.01em; color: #10151b; }
h1 { font-size: 22px; }
h2 { font-size: 17px; }
h3 { font-size: 14.5px; }
p { margin: 0 0 12px; }
code {
  background: #eef0f3; padding: 1px 6px; border-radius: 5px; font-size: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* ---- App shell: sidebar + topbar ---- */
.app-shell { display: flex; min-height: 100vh; }
.sidebar {
  width: 224px;
  flex-shrink: 0;
  background: linear-gradient(180deg, var(--brand-dark), var(--brand-darker));
  color: #fff;
  display: flex;
  flex-direction: column;
  padding: 20px 0;
}
.sidebar .brand {
  padding: 0 20px 18px;
  font-weight: 700;
  font-size: 16px;
  letter-spacing: -0.01em;
  border-bottom: 1px solid rgba(255,255,255,0.12);
  margin-bottom: 12px;
}
.sidebar .brand small { display: block; font-weight: 400; opacity: 0.65; font-size: 11px; margin-top: 3px; letter-spacing: .02em; }
.sidebar .brand .brand-logo { display: block; max-width: 100%; max-height: 40px; width: auto; background: #fff; border-radius: 8px; padding: 4px 10px; object-fit: contain; }
.sidebar nav { flex: 1; padding: 0 8px; }
.sidebar a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 9px 12px;
  margin-bottom: 2px;
  border-radius: 7px;
  color: rgba(255,255,255,0.78);
  font-size: 13.5px;
  font-weight: 500;
  transition: background .15s var(--ease), color .15s var(--ease);
}
.sidebar a:hover { background: rgba(255,255,255,0.08); color: #fff; text-decoration: none; }
/* The active-item stripe deliberately does NOT use --accent (coral) —
   coral is reserved for alerts/urgent items only (see the :root comment
   above), and "this is the page you're on" isn't an alert. Plain white
   reads as a clean active-state indicator against the navy sidebar. */
.sidebar a.active { background: rgba(255,255,255,0.16); color: #fff; font-weight: 650; box-shadow: inset 2px 0 0 rgba(255,255,255,0.85); }
.nav-badge {
  display: inline-block; min-width: 18px; padding: 1px 6px; margin-left: 6px; border-radius: 99px;
  background: var(--accent); color: #fff; font-size: 10.5px; font-weight: 700; text-align: center; line-height: 1.5;
}

/* Small stroke icon in front of Dashboard/POS/Orders and each group
   header's label (see NAV_ICONS in shell.js) — dimmed to match the resting
   link color and brightening along with the rest of the link on hover/
   active, purely via currentColor, no icon-specific color rules needed. */
.nav-icon { width: 16px; height: 16px; flex-shrink: 0; opacity: 0.85; }
.nav-link-main { display: flex; align-items: center; gap: 10px; min-width: 0; }

/* ---- Sidebar: Dashboard sits ungrouped at top; everything else lives in
   a section with its own small icon + label. Sections are collapsible (so
   anyone who wants a shorter list can still tuck ones they rarely use
   away) but open BY DEFAULT — see the isOpen comment in shell.js — so a
   brand-new, non-technical owner sees every real page name up front
   instead of having to guess which abstract header hides "Reports". ---- */
.sidebar a.nav-top-link { margin-bottom: 10px; }
.nav-group { margin-bottom: 2px; }
.nav-group-header {
  display: flex; align-items: center; justify-content: space-between; width: 100%;
  padding: 8px 12px; border: none; background: none; cursor: pointer; font: inherit;
  color: rgba(255,255,255,0.62); font-size: 11.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; border-radius: 7px; transition: background .15s var(--ease), color .15s var(--ease);
}
.nav-group-label { display: flex; align-items: center; gap: 9px; min-width: 0; }
.nav-group-header:hover { background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.85); }
.nav-group-chev { flex-shrink: 0; transition: transform .18s var(--ease); }
.nav-group.open .nav-group-chev { transform: rotate(180deg); }
.nav-group-body {
  max-height: 0; overflow: hidden; opacity: 0;
  transition: max-height .22s var(--ease), opacity .18s var(--ease);
}
.nav-group.open .nav-group-body { max-height: 400px; opacity: 1; }
.nav-group-body a { margin-left: 6px; }
.main-area { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.topbar {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: 12px 26px;
  display: flex; align-items: center; gap: 16px;
  position: sticky; top: 0; z-index: 40;
}

/* ---- Mobile nav drawer — hidden on desktop (the sidebar already sits
   permanently visible there); below the breakpoint the sidebar becomes an
   off-canvas panel toggled by this button, so a phone gets its full width
   back for actual page content instead of a permanent 224px sidebar eating
   most of the screen. See initAdminShell in shell.js for the JS wiring. ---- */
.mobile-nav-toggle {
  display: none; align-items: center; justify-content: center;
  width: 38px; height: 38px; flex-shrink: 0; border-radius: 9px; border: 1px solid var(--border);
  background: var(--surface); color: var(--text); cursor: pointer;
}
.mobile-nav-toggle:hover { background: #f3f5f7; }
.sidebar-backdrop {
  position: fixed; inset: 0; background: rgba(16, 24, 40, 0.5); z-index: 199;
  opacity: 0; pointer-events: none; transition: opacity .18s var(--ease);
}
.sidebar-backdrop.open { opacity: 1; pointer-events: auto; }

@media (max-width: 900px) {
  .mobile-nav-toggle { display: inline-flex; }
  .sidebar {
    /* !important on position + z-index only: body.admin-page .sidebar (the
       "premium redesign" pass, further down this file) sets
       `position: relative; z-index: 10;` at higher specificity
       (0,2,0 vs this rule's 0,1,0) — CSS specificity beats media-query
       nesting AND source order, so without !important here that rule
       silently won on every real admin page (which always carries the
       admin-page body class) and the sidebar never actually left normal
       flow: it just sat in its usual flex column with a no-op transform,
       which is exactly the "sidebar and content both visible side by side,
       hamburger showing but doing nothing useful" bug reported from a live
       screenshot. transform/height/etc. below aren't contested by that
       rule, so they don't need !important. */
    position: fixed !important; z-index: 200 !important;
    top: 0; left: 0; height: 100vh;
    transform: translateX(-100%); transition: transform .22s var(--ease);
    box-shadow: var(--shadow-lg);
    /* Nav groups default to open (every group's .nav-group-body reserves a
       flat max-height:400px each for the CSS expand/collapse transition —
       see that rule further up this file), so the sidebar's real content
       height comfortably exceeds one phone screen once several groups are
       expanded. On desktop that was harmless (the sidebar sits in normal
       flow and the whole page just scrolls); fixed-position + height:100vh
       here needs its OWN scroll instead, or everything past the first
       screenful becomes invisible/unreachable. */
    overflow-y: auto; -webkit-overflow-scrolling: touch;
  }
  .sidebar.open { transform: translateX(0); }
  body.nav-drawer-open { overflow: hidden; }
  .topbar { padding: 10px 14px; gap: 10px; }
}
@media (max-width: 480px) {
  .topbar-logo { width: 40px; height: 40px; font-size: 13px; }
  .account-name { display: none; }
  .account-trigger { padding: 6px; }
}
.topbar h1 {
  font-size: 20px; font-weight: 750; letter-spacing: -0.01em; margin: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
.page-content { padding: var(--space-3); flex: 1; }

/* ---- Topbar: shop identity (left) + account menu (right) ----
   Logout and account controls used to live at the bottom of the sidebar,
   easy to miss and inconsistent with where most dashboards put them. They
   now live here instead — one place, every page, top-right. */
.topbar-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
.topbar-logo {
  width: 56px; height: 56px; border-radius: 12px; flex-shrink: 0;
  background: linear-gradient(135deg, var(--brand), var(--brand-dark)); color: #fff;
  display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 16px;
}
/* Kept as a compact square badge (matching the wskadmin topbar's own
   treatment) rather than a large banner logo — this sits inline with the
   page title in a thin topbar row, so it stays a badge here; the big,
   prominent version of a shop's logo belongs on the storefront header
   below, which is a real nav bar with room for it. */
.topbar-logo.has-image { background: #fff; border: 1px solid var(--border); }
.topbar-logo.has-image img { width: 100%; height: 100%; object-fit: contain; border-radius: 9px; padding: 4px; }
.topbar-titles { min-width: 0; overflow: hidden; }
.topbar-shopname {
  font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); line-height: 1.4;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.account-menu { position: relative; margin-left: auto; flex-shrink: 0; }
.account-trigger {
  display: flex; align-items: center; gap: 9px; background: none; border: none; cursor: pointer;
  padding: 6px 10px 6px 6px; border-radius: 99px; transition: background .15s var(--ease);
}
.account-trigger:hover { background: #f3f5f7; }
.account-avatar {
  width: 30px; height: 30px; border-radius: 50%; flex-shrink: 0;
  background: var(--brand-light); color: var(--brand-dark);
  display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 13px;
}
.account-name { font-size: 13.5px; font-weight: 650; color: var(--text); }
.account-trigger .chev { color: var(--text-muted); transition: transform .15s var(--ease); flex-shrink: 0; }
.account-menu.open .chev { transform: rotate(180deg); }
.account-dropdown {
  position: absolute; top: calc(100% + 8px); right: 0; background: #fff; border: 1px solid var(--border);
  border-radius: 12px; box-shadow: var(--shadow-lg); min-width: 190px; padding: 6px; z-index: 60;
}
.account-dropdown-header { padding: 8px 10px 10px; border-bottom: 1px solid var(--border); margin-bottom: 6px; font-size: 13px; font-weight: 650; line-height: 1.5; }
.account-dropdown-header .text-muted { font-weight: 500; font-size: 11.5px; }
.account-dropdown a, .account-dropdown button {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; padding: 8px 10px;
  border-radius: 7px; border: none; background: none; cursor: pointer; font-size: 13px; font-weight: 600; color: var(--text);
}
.account-dropdown a:hover, .account-dropdown button:hover { background: #f3f5f7; text-decoration: none; }

/* Announcement bell — tenant admin panel's #topbar-extra slot (see
   public/admin/shell.js initAnnouncementBell()). Scoped under
   body.admin-page throughout so this can never affect the storefront or
   the separately-styled platform-admin panel (its own bell, in
   public/platform-admin/style.css, is untouched by any of this). Visual
   language deliberately mirrors .account-dropdown just above (same
   border/radius/shadow/z-index) so it reads as part of the same topbar,
   not a bolted-on widget. */
body.admin-page .announce-wrap { position: relative; }
body.admin-page .announce-bell {
  position: relative; background: transparent; border: 1px solid transparent; color: var(--text-muted);
  border-radius: 50%; width: 36px; height: 36px; cursor: pointer; display: flex;
  align-items: center; justify-content: center; transition: background .15s, color .15s, box-shadow .15s;
  flex-shrink: 0;
}
body.admin-page .announce-bell svg { width: 18px; height: 18px; }
body.admin-page .announce-bell:hover, body.admin-page .announce-wrap.open .announce-bell {
  background: var(--brand-light); color: var(--brand-dark);
}
body.admin-page .announce-wrap.open .announce-bell { box-shadow: 0 0 0 3px rgba(30,58,95,.12); }
body.admin-page .announce-badge {
  position: absolute; top: 1px; right: 1px; background: var(--accent); color: #fff; font-size: 10px;
  font-weight: 700; min-width: 16px; height: 16px; border-radius: 99px; display: flex; align-items: center;
  justify-content: center; padding: 0 4px; border: 2px solid var(--surface);
}
body.admin-page .announce-badge.hidden { display: none; }
body.admin-page .announce-dropdown {
  position: absolute; top: calc(100% + 8px); right: 0; background: #fff; border: 1px solid var(--border);
  border-radius: 12px; box-shadow: var(--shadow-lg); width: 340px; max-width: calc(100vw - 24px);
  max-height: 420px; overflow-y: auto; z-index: 60;
}
body.admin-page .announce-dropdown.hidden { display: none; }
body.admin-page .announce-dropdown-header {
  display: flex; align-items: baseline; gap: 6px; padding: 12px 14px; border-bottom: 1px solid var(--border);
  font-size: 13.5px; position: sticky; top: 0; background: #fff; border-radius: 12px 12px 0 0;
}
body.admin-page .announce-dropdown-header .text-muted { font-weight: 500; font-size: 11.5px; }
body.admin-page .announce-item {
  display: flex; gap: 10px; padding: 11px 14px; border-bottom: 1px solid var(--border);
}
body.admin-page .announce-item:last-child { border-bottom: none; }
body.admin-page .announce-item.unread { background: var(--brand-light); }
body.admin-page .announce-item-icon { font-size: 15px; line-height: 1.5; flex-shrink: 0; }
body.admin-page .announce-item-title { font-size: 13px; font-weight: 650; color: var(--text); margin-bottom: 2px; }
body.admin-page .announce-item-text {
  font-size: 12.5px; color: var(--text); line-height: 1.5; white-space: pre-wrap;
  overflow-wrap: anywhere; margin-bottom: 4px;
}
body.admin-page .announce-item-meta { font-size: 11px; color: var(--text-muted); font-weight: 600; }
body.admin-page .announce-empty { padding: 24px 14px; text-align: center; font-size: 12.5px; color: var(--text-muted); }

@media (max-width: 520px) {
  body.admin-page .announce-dropdown { position: fixed; top: 62px; right: 12px; left: 12px; width: auto; max-width: none; }
}
.account-dropdown button.logout-item { color: var(--danger); }
.account-dropdown button.logout-item:hover { background: var(--danger-light); }

/* ---- Cards / tables / forms ---- */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-3);
  box-shadow: var(--shadow-sm);
  margin-bottom: var(--space-3);
}
.card h2, .card h3 { margin-top: 0; }
.card h2 { padding-bottom: 12px; margin-bottom: var(--space-2); border-bottom: 1px solid var(--border); }
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--space-2); margin-bottom: var(--space-3); }
.stat-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: var(--space-2); box-shadow: var(--shadow-sm); transition: box-shadow .15s var(--ease), transform .15s var(--ease);
}
.stat-card .label { font-size: 11.5px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .06em; font-weight: 600; }
.stat-card .value { font-size: 28px; font-weight: 800; margin-top: 6px; letter-spacing: -0.02em; color: #10151b; }
/* Stat cards double as nav shortcuts (dashboard) — click one, land on the
   page it summarizes. display:block + color/text-decoration overrides are
   needed because a .stat-card is an <a> tag: without them it would pick up
   the generic link's blue-ish color and underline-on-hover instead of
   looking like a card. (.stat-card is excluded from the base a:hover rule
   above for the same "don't let a generic link rule reach into a
   component that manages its own hover state" reason as .btn.) */
.stat-card { display: block; color: inherit; text-decoration: none; }
.stat-card:hover { box-shadow: var(--shadow-md); border-color: var(--border-strong); transform: translateY(-1px); text-decoration: none; color: inherit; }
.stat-card-icon {
  width: 34px; height: 34px; border-radius: 9px; margin-bottom: var(--space-1);
  display: flex; align-items: center; justify-content: center;
  background: var(--brand-light); color: var(--brand-dark);
}
.stat-card-icon svg { width: 18px; height: 18px; }
/* .urgent is the one place a stat card's number/icon uses --accent (coral)
   instead of navy — reserved for cards that represent something needing
   action (Low Stock, Outstanding Udhar, Pending Returns), never a plain
   informational count. */
.stat-card.urgent .stat-card-icon { background: var(--accent-light); color: var(--accent-dark); }
.stat-card.urgent .value { color: var(--accent-dark); }

/* ---- Dashboard-specific layout (public/admin/index.html) ---- */
.dashboard-grid { display: grid; grid-template-columns: 1.3fr 1fr; gap: var(--space-3); align-items: start; }
@media (max-width: 900px) { .dashboard-grid { grid-template-columns: 1fr; } }
.section-label {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em;
  color: var(--text-muted); margin: 0 0 var(--space-2);
}
.attention-list { display: flex; flex-direction: column; gap: 2px; }
.attention-row {
  display: flex; align-items: center; gap: 12px; padding: 11px 4px; border-bottom: 1px solid var(--border);
  color: inherit; text-decoration: none;
}
.attention-row:last-child { border-bottom: none; }
.attention-row:hover { background: #f8fafb; text-decoration: none; color: inherit; }
.attention-row .dot {
  width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; background: var(--accent);
}
.attention-row .body { flex: 1; min-width: 0; }
.attention-row .title { font-size: 13.5px; font-weight: 650; color: var(--text); }
.attention-row .sub { font-size: 12px; color: var(--text-muted); margin-top: 1px; }
.attention-row .amount { font-size: 13px; font-weight: 700; color: var(--accent-dark); flex-shrink: 0; }

/* ---- Getting Started checklist (first-time / empty-shop dashboard) ---- */
.getting-started-list { display: flex; flex-direction: column; gap: 4px; }
.getting-started-item {
  display: flex; align-items: center; gap: 12px; padding: 12px; border-radius: var(--radius);
  border: 1px solid var(--border); color: inherit; text-decoration: none;
  transition: border-color .15s var(--ease), background .15s var(--ease);
}
.getting-started-item:hover { border-color: var(--border-strong); background: #f8fafb; text-decoration: none; color: inherit; }
.getting-started-item .check {
  width: 24px; height: 24px; border-radius: 50%; flex-shrink: 0; display: flex; align-items: center; justify-content: center;
  border: 1.5px solid var(--border-strong); color: transparent; font-size: 13px; font-weight: 700;
}
.getting-started-item.done { opacity: .6; }
.getting-started-item.done .check { background: var(--success); border-color: var(--success); color: #fff; }
.getting-started-item .label { font-size: 13.5px; font-weight: 650; }
.getting-started-item.done .label { text-decoration: line-through; }
.getting-started-item .arrow { margin-left: auto; color: var(--text-muted); flex-shrink: 0; }

/* ---- Chart components (public/js/charts.js — WskCharts) ----
   Colors reference these tokens only, never a hardcoded hex, so the exact
   same charts.js renders correctly here (light theme) and in the platform
   admin's dark theme (which defines the same variable names with its own
   dark-appropriate values in platform-admin/style.css) without any
   theme-detection logic in the JS itself. */
:root {
  --chart-line: var(--brand);
  --chart-c1: #1e6f5c;
  --chart-c2: #2f8f77;
  --chart-c3: #c8863a;
  --chart-c4: #5b7fd1;
  --chart-c5: #a25ddb;
  --chart-c6: #d1495b;
  --chart-c7: #3aa6a0;
  --chart-c8: #8a8f3f;
}
.wsk-chart { position: relative; }
.wsk-chart-svg { width: 100%; height: auto; display: block; overflow: visible; }
.wsk-chart-empty { padding: 40px 0; text-align: center; color: var(--text-muted); font-size: 13px; }
.wsk-chart-grid { stroke: var(--border); stroke-width: 1; }
.wsk-chart-axis-label { fill: var(--text-muted); font-size: 10.5px; }
.wsk-chart-grad-start { stop-color: var(--chart-line); stop-opacity: 0.28; }
.wsk-chart-grad-end { stop-color: var(--chart-line); stop-opacity: 0.02; }
.wsk-chart-line-stroke { stroke: var(--chart-line); stroke-width: 2.5px; stroke-linejoin: round; stroke-linecap: round; }
.wsk-chart-dot { fill: var(--chart-line); opacity: 0; transition: opacity .12s var(--ease); }
.wsk-chart-dot.active { opacity: 1; }
.wsk-chart-hit { cursor: pointer; }
.wsk-chart-tooltip {
  position: absolute; z-index: 5; pointer-events: none; background: #1a1a1a; color: #fff;
  font-size: 12px; line-height: 1.4; padding: 6px 10px; border-radius: 6px;
  box-shadow: 0 4px 14px rgba(0,0,0,.18); white-space: nowrap; max-width: 220px;
}
.wsk-chart-tooltip.wsk-hidden-tip { display: none; }
.wsk-donut-layout { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.wsk-donut-svg-wrap { position: relative; width: 176px; height: 176px; flex-shrink: 0; }
.wsk-donut-svg { width: 100%; height: 100%; }
.wsk-donut-track { stroke: var(--border); }
.wsk-donut-seg { transition: opacity .12s var(--ease); cursor: pointer; }
.wsk-donut-seg.active { opacity: .82; }
.wsk-donut-center { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; pointer-events: none; }
.wsk-donut-total { font-size: 15px; font-weight: 700; }
.wsk-donut-total-label { font-size: 10.5px; color: var(--text-muted); margin-top: 2px; }
.wsk-chart-legend { display: flex; flex-direction: column; gap: 7px; flex: 1; min-width: 140px; }
.wsk-legend-row { display: flex; align-items: center; gap: 8px; font-size: 12.5px; }
.wsk-legend-swatch { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; }
.wsk-legend-label { flex: 1; color: var(--text); }
.wsk-legend-value { font-weight: 600; color: var(--text-muted); }
.wsk-chart-bar-label { fill: var(--text); font-size: 12px; }
.wsk-chart-bar-track { fill: var(--border); opacity: .5; }
.wsk-chart-bar-fill { transition: opacity .12s var(--ease); }
.wsk-chart-bar-fill.active { opacity: .82; }
.wsk-chart-bar-value { fill: var(--text-muted); font-size: 11.5px; }
.wsk-chart-c1 { stroke: var(--chart-c1); fill: var(--chart-c1); }
.wsk-chart-c2 { stroke: var(--chart-c2); fill: var(--chart-c2); }
.wsk-chart-c3 { stroke: var(--chart-c3); fill: var(--chart-c3); }
.wsk-chart-c4 { stroke: var(--chart-c4); fill: var(--chart-c4); }
.wsk-chart-c5 { stroke: var(--chart-c5); fill: var(--chart-c5); }
.wsk-chart-c6 { stroke: var(--chart-c6); fill: var(--chart-c6); }
.wsk-chart-c7 { stroke: var(--chart-c7); fill: var(--chart-c7); }
.wsk-chart-c8 { stroke: var(--chart-c8); fill: var(--chart-c8); }
.wsk-chart-c1-bg { background: var(--chart-c1); }
.wsk-chart-c2-bg { background: var(--chart-c2); }
.wsk-chart-c3-bg { background: var(--chart-c3); }
.wsk-chart-c4-bg { background: var(--chart-c4); }
.wsk-chart-c5-bg { background: var(--chart-c5); }
.wsk-chart-c6-bg { background: var(--chart-c6); }
.wsk-chart-c7-bg { background: var(--chart-c7); }
.wsk-chart-c8-bg { background: var(--chart-c8); }

/* ---- Store style picker (Settings -> Store Style) ----
   Each option is a clickable card with a tiny live preview built from that
   theme's own two colors (a "header" bar + a "hero" block + an accent
   chip) — a rough thumbnail of the real storefront, not a generic swatch,
   so a non-technical owner can tell the three apart at a glance without
   needing to visit the storefront first. */
.theme-option {
  display: block; text-align: left; background: var(--surface); border: 2px solid var(--border);
  border-radius: var(--radius-lg); padding: 12px; cursor: pointer; font: inherit; color: inherit;
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
}
.theme-option:hover { border-color: var(--border-strong); }
.theme-option.selected { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-light); }
.theme-option-preview { border-radius: 8px; overflow: hidden; border: 1px solid var(--border); margin-bottom: 10px; }
.theme-preview-header { height: 14px; }
.theme-preview-body { padding: 8px; background: #f3f4f6; }
.theme-preview-hero { height: 28px; border-radius: 4px; margin-bottom: 6px; }
.theme-preview-chip { width: 34px; height: 8px; border-radius: 99px; }
.theme-option-label { font-weight: 700; font-size: 13.5px; margin-bottom: 3px; display: flex; align-items: center; gap: 6px; }
.theme-option.selected .theme-option-label::after { content: '✓ Active'; font-size: 10.5px; font-weight: 700; color: var(--brand); background: var(--brand-light); padding: 1px 7px; border-radius: 99px; }
.theme-option-desc { font-size: 12px; color: var(--text-muted); line-height: 1.4; }

table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
th, td { padding: 10px 12px; text-align: left; border-bottom: 1px solid var(--border); }
th { color: var(--text-muted); font-weight: 650; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em; background: #fafbfc; }
th:first-child { border-top-left-radius: 8px; }
th:last-child { border-top-right-radius: 8px; }
tbody tr { transition: background .1s var(--ease); }
tbody tr:hover { background: #f8fafb; }
tbody tr:last-child td { border-bottom: none; }
.table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: 10px; }
.table-wrap table { border: none; }
.table-wrap th, .table-wrap td { border-left: none; border-right: none; }

/* On narrow screens the `table { width: 100% }` rule above was forcing
   every .table-wrap table to squeeze into the container instead of
   actually scrolling — .table-wrap already has overflow-x:auto, but it
   can never kick in if the table itself refuses to grow past 100%. The
   visible symptom: short cells like dates get crushed to ~40px and word-
   wrap character-by-character ("13-\nAug-\n2026,\n10:44\nam"), which is
   exactly the "Recent Orders / Orders / Products table" mobile bug
   reported live. min-width here restores enough room per column that the
   wrap scrolls horizontally like it was always meant to, on every page
   that uses .table-wrap (Dashboard, Orders, Products, Customers, etc. —
   this is one shared rule, not a per-page fix). */
@media (max-width: 900px) {
  .table-wrap table { min-width: 620px; }
}

label { display: block; font-size: 12.5px; font-weight: 650; margin-bottom: 5px; color: var(--text-muted); }
input, select, textarea {
  width: 100%; padding: 8px 11px; border: 1px solid var(--border-strong); border-radius: 7px;
  font-size: 13.5px; font-family: inherit; background: #fff; color: var(--text);
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
}
input::placeholder, textarea::placeholder { color: #a3a9b3; }
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px rgba(30, 58, 95, 0.14);
}
input:disabled, select:disabled { background: #f5f6f8; color: var(--text-muted); }
.field { margin-bottom: 14px; }
.field-row { display: flex; gap: 12px; flex-wrap: wrap; }
.field-row .field { flex: 1; min-width: 140px; }

.btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 16px; border-radius: 7px; border: 1px solid var(--border-strong);
  background: #fff; color: var(--text); cursor: pointer; font-size: 13.5px; font-weight: 600;
  transition: background .15s var(--ease), border-color .15s var(--ease), transform .05s var(--ease), box-shadow .15s var(--ease), color .15s var(--ease);
}
/* Every hover rule below declares `color` explicitly, even when it's the
   same as the base state — this is the actual fix for the "invisible
   button text on hover" bug (see the a:not(.btn):hover comment above for
   the root cause). Relying on inheritance/no-op for hover text color is
   exactly what let a global link-hover rule silently win before; an
   explicit declaration here always has priority over that generic rule
   regardless of source order or any future changes to it. Applies to
   every variant so this can't quietly reappear on a button style added
   later — copy an existing rule below rather than omitting `color`. */
.btn:hover { background: #f6f7f9; border-color: #c7cdd6; color: var(--text); }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.btn-primary { background: var(--brand); border-color: var(--brand); color: #fff; box-shadow: 0 1px 2px rgba(30,58,95,.25); }
.btn-primary:hover { background: var(--brand-dark); border-color: var(--brand-dark); color: #fff; }
.btn-danger { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn-danger:hover { background: #c11f1f; border-color: #c11f1f; color: #fff; }
.btn-sm { padding: 5px 11px; font-size: 12px; }
.btn-outline { background: transparent; color: var(--text); }
.btn-outline:hover { color: var(--text); }
.btn[disabled], .btn[disabled]:hover { opacity: .5; cursor: not-allowed; transform: none; }

.badge { display: inline-block; padding: 2.5px 10px; border-radius: 99px; font-size: 11px; font-weight: 700; letter-spacing: .01em; }
.badge-success { background: var(--success-light); color: var(--success); }
.badge-warn { background: var(--warn-light); color: var(--warn); }
.badge-danger { background: var(--danger-light); color: var(--danger); }
.badge-muted { background: #eef0f3; color: var(--text-muted); }
/* NEW-order marker (Sales & Orders table) — reuses the danger palette so it
   reads as "needs your attention" at a glance, distinct from the
   payment-status badges (success/warn) already on the same row. Paired
   with .order-row-new below so a new online order is obvious without
   having to read the payment-status column at all — see orders.html. */
.badge-new { background: var(--danger-light); color: var(--danger); margin-left: 4px; }
.order-row-new { background: var(--brand-light); }
.order-row-new:hover { background: var(--brand-light); filter: brightness(0.97); }

.toolbar { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-3); flex-wrap: wrap; }
.toolbar .spacer { flex: 1; }
.search-input { max-width: 260px; }

.empty-state { text-align: center; padding: 44px 20px; color: var(--text-muted); font-size: 13.5px; }

/* ---- Redesign pass: Stripe/Linear-inspired polish (Dashboard, Products,
   Orders first; same patterns apply everywhere else after). Four ideas,
   all below: more whitespace than the original defaults, fewer competing
   borders, one clear focal point per page instead of everything at the
   same visual weight, and real motion/empty states instead of instant
   snaps and blank tables. Existing component classes above (.card,
   .stat-card, table, .btn, etc.) are intentionally left alone — these are
   additive, opt-in classes layered on top, so nothing already using the
   old patterns anywhere else in the app shifts until it's deliberately
   moved over to them. ---- */
:root {
  --space-5: 40px;
  --space-6: 56px;
}

/* More breathing room around every page's content than the original
   --space-3 default, and more still on wide screens — err toward
   "spacious" per the brief, especially since this is a dashboard people
   sit in front of all day, not a dense data-entry form. .page-content is
   shell.js's own shared wrapper (every admin page renders inside it), so
   this one change alone gives every page more room immediately —
   deliberately not gated behind a modifier class the way the more
   opinionated card/table changes below are. */
.page-content { padding: var(--space-4); }
@media (min-width: 1100px) { .page-content { padding: var(--space-5) var(--space-6); } }

/* Cards: a hairline border barely darker than the page background (not
   the original --border, which read as a hard outline once spacing grew)
   plus taller internal padding and more room between stacked cards —
   separation comes from whitespace first, the border is just a faint
   assist for anyone on a low-contrast screen. */
.card.quiet { border-color: #eef0f3; padding: var(--space-4); margin-bottom: var(--space-4); box-shadow: none; }
.card.quiet h2 { padding-bottom: 14px; margin-bottom: var(--space-3); }

/* ---- Hero stat: the ONE number a page wants your eye to land on first
   (e.g. Dashboard's Today's Sales). Previously bare text with no card
   chrome at all, floating directly on the page background — this read as
   unfinished/plain rather than "deliberately quiet", so it's now a proper
   elevated card (a navy gradient, matching the marketing site's own hero
   treatment, since this genuinely is the one moment on this page that
   should look premium) with an icon chip, while the secondary stats below
   stay comparatively flat — that contrast is still the point, it just
   needed an actual card to contrast against instead of empty space. ---- */
.hero-stat-label { font-size: 12px; font-weight: 650; text-transform: uppercase; letter-spacing: .1em; color: var(--text-muted); margin-bottom: 8px; }
.hero-stat-value { font-size: 46px; font-weight: 800; letter-spacing: -0.02em; color: #10151b; line-height: 1.1; }
.hero-stat-sub { font-size: 13px; color: var(--text-muted); margin-top: 8px; }

.dash-hero-card {
  background: linear-gradient(135deg, var(--brand) 0%, var(--brand-dark) 100%);
  border-radius: var(--radius-lg); padding: var(--space-4) var(--space-4);
  margin-bottom: var(--space-3); box-shadow: var(--shadow-md); position: relative; overflow: hidden;
}
/* A faint decorative glow, not a real UI element — purely so the gradient
   card doesn't read as a single flat color block. Clipped by the card's
   own overflow:hidden, never affects layout or hit-testing. */
.dash-hero-card::after {
  content: ''; position: absolute; top: -60px; right: -60px; width: 220px; height: 220px;
  background: radial-gradient(circle, rgba(255,255,255,.10) 0%, transparent 70%); pointer-events: none;
}
.dash-hero-card .hero-stat-label { color: rgba(255,255,255,.75); }
.dash-hero-card .hero-stat-value { color: #fff; }
.dash-hero-card .hero-stat-value:hover { opacity: .9; }
.dash-hero-card .hero-stat-sub { color: rgba(255,255,255,.7); }
.dash-hero-icon {
  width: 40px; height: 40px; border-radius: 10px; margin-bottom: var(--space-2);
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,.14); color: var(--accent-light);
}
.dash-hero-icon svg { width: 20px; height: 20px; }

/* ---- Secondary stat row: supporting numbers, deliberately flatter and
   smaller than a .stat-card — no border or shadow of their own, just
   figures in a row separated by whitespace (plus a hairline divider on
   wide screens where they'd otherwise float ambiguously), because
   they're context for the hero number above, not headlines themselves. */
.stat-row { display: flex; flex-wrap: wrap; gap: var(--space-4); margin-bottom: var(--space-4); }
.stat-row-item { flex: 1; min-width: 128px; padding-left: var(--space-3); border-left: 1px solid var(--border); transition: opacity .15s var(--ease); }
.stat-row-item:first-child { padding-left: 0; border-left: none; }
.stat-row-item .label { font-size: 11px; font-weight: 650; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); }
.stat-row-item .value { font-size: 21px; font-weight: 750; margin-top: 5px; letter-spacing: -0.01em; color: var(--text); }
.stat-row-item.urgent .value { color: var(--accent-dark); }
.stat-row-item a { display: block; color: inherit; text-decoration: none; }
.stat-row-item a:hover { opacity: .68; text-decoration: none; color: inherit; }
@media (max-width: 700px) { .stat-row-item { border-left: none; padding-left: 0; flex: 1 1 42%; } }

/* ---- Flat tables: an alternative to .table-wrap's boxed border+radius —
   a single rule under the header and taller row padding reads as "a
   clean list", closer to Linear's tables than "a grid of cells". Opt-in
   (.flat modifier) so existing table-wrap usages elsewhere are untouched
   until they're deliberately moved over. */
.table-wrap.flat { border: none; border-radius: 0; }
.table-wrap.flat table th { background: none; border-bottom: 1.5px solid var(--text); padding: 0 12px 12px; font-size: 10.5px; }
.table-wrap.flat table td { padding: 16px 12px; }
.table-wrap.flat table tr:last-child td { border-bottom: none; }

/* ---- Skeleton loaders: a shape that hints at the real content's layout
   while a request is in flight, replacing the old "Loading…" text flash.
   A gentle shimmer rather than a spinner — a spinner asks for more
   attention than a sub-second local-network load deserves. */
@keyframes skeletonShimmer { 0% { background-position: -120px 0; } 100% { background-position: 120px 0; } }
.skeleton {
  display: inline-block; background-color: #eceef1;
  background-image: linear-gradient(90deg, #eceef1 0px, #f4f5f7 40px, #eceef1 80px);
  background-size: 240px 100%; animation: skeletonShimmer 1.3s ease-in-out infinite;
  border-radius: 5px; vertical-align: middle;
}
.skeleton-text { height: 12px; width: 70%; }
.skeleton-block { height: 40px; width: 100%; border-radius: var(--radius); }
.skeleton-circle { width: 40px; height: 40px; border-radius: 50%; }

/* ---- Empty states: icon + one clear line + a next action, instead of a
   blank table or a lonely "0 results" string. ---- */
.empty-state-block { text-align: center; padding: 60px 20px; }
.empty-state-icon {
  width: 52px; height: 52px; margin: 0 auto 16px; border-radius: 14px;
  background: var(--brand-light); color: var(--brand-dark);
  display: flex; align-items: center; justify-content: center;
}
.empty-state-icon svg { width: 24px; height: 24px; }
.empty-state-title { font-size: 14.5px; font-weight: 650; color: var(--text); margin-bottom: 5px; }
.empty-state-sub { font-size: 13px; color: var(--text-muted); max-width: 320px; margin: 0 auto 20px; line-height: 1.5; }

#toast-host { position: fixed; top: 18px; right: 18px; z-index: 999; display: flex; flex-direction: column; gap: 8px; }
.toast {
  background: #1a2027; color: #fff; padding: 11px 18px; border-radius: 9px;
  font-size: 13px; font-weight: 500; box-shadow: var(--shadow-lg); opacity: 0; transform: translateY(-8px);
  transition: all .25s var(--ease); max-width: 320px;
}
.toast.show { opacity: 1; transform: translateY(0); }
.toast-error { background: var(--danger); }
.toast-success { background: var(--success); }

.modal-backdrop {
  position: fixed; inset: 0; background: rgba(14, 20, 27, 0.5); backdrop-filter: blur(1px); display: flex; align-items: center;
  justify-content: center; z-index: 900; padding: 20px;
}
.modal {
  background: #fff; border-radius: var(--radius-lg); padding: 24px; width: 100%; max-width: 480px;
  max-height: 90vh; overflow-y: auto; box-shadow: var(--shadow-lg);
}
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }

.text-muted { color: var(--text-muted); }
.text-danger { color: var(--danger); }
.text-right { text-align: right; }
.mt-0 { margin-top: 0; }
.hidden { display: none !important; }

.kv { display: grid; grid-template-columns: 120px 1fr; gap: 6px 12px; font-size: 13px; margin: 0 0 16px; }
.kv dt { color: var(--text-muted); font-weight: 600; }
.kv dd { margin: 0; }

/* ---- Login page ---- */
.login-wrap {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  background: radial-gradient(circle at top left, var(--brand) 0%, var(--brand-dark) 45%, var(--brand-darker) 100%);
  padding: 20px;
}
.login-card {
  background: #fff; border-radius: var(--radius-lg); padding: 36px; width: 100%; max-width: 380px;
  box-shadow: var(--shadow-lg);
}
.login-card h1 { font-size: 21px; margin-bottom: 2px; }
.login-card .sub { color: var(--text-muted); margin-bottom: 24px; font-size: 13px; }

/* ---- Barcode scan box (POS) — deliberately its own high-contrast card so
   it reads as a distinct "scan here" target, not just another text field. ---- */
.barcode-scan-card { border: 1px solid var(--brand); background: #eef7f4; padding: 10px 14px; margin-bottom: 14px; }
.barcode-scan-row { display: flex; align-items: center; gap: 10px; }
.barcode-icon { width: 20px; height: 20px; flex-shrink: 0; color: var(--brand); }
.barcode-scan-field { flex: 1; min-width: 0; }
.barcode-scan-field label {
  display: block; font-size: 11px; font-weight: 700; color: var(--brand-dark, var(--brand));
  text-transform: uppercase; letter-spacing: .04em; margin-bottom: 3px;
}
.barcode-input {
  width: 100%; font-size: 13.5px; padding: 8px 11px; border: 1px solid var(--brand); border-radius: 7px;
  background: #fff; transition: background-color .2s var(--ease), border-color .2s var(--ease);
}
.barcode-input:focus { outline: none; box-shadow: 0 0 0 3px rgba(30,58,95,.18); }
.barcode-input.flash-success { background: #d5f3e3; border-color: var(--success); }
.barcode-input.flash-error { background: #fbdada; border-color: var(--danger); }
.barcode-scan-status { font-size: 12.5px; font-weight: 650; min-width: 150px; max-width: 240px; text-align: right; }
.barcode-scan-status.success { color: var(--success); }
.barcode-scan-status.error { color: var(--danger); }
.cart-row.flash-added { animation: cartRowFlash .9s ease; border-radius: 6px; }
@keyframes cartRowFlash {
  0% { background: #d5f3e3; }
  100% { background: transparent; }
}

/* ---- POS layout ---- */
.pos-layout { display: grid; grid-template-columns: 1.5fr 1fr; gap: 18px; align-items: start; }
@media (max-width: 900px) { .pos-layout { grid-template-columns: 1fr; } }
.pos-results { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; max-height: 62vh; overflow-y: auto; padding: 2px; }
.pos-item-card {
  border: 1px solid var(--border); border-radius: 10px; padding: 11px; background: #fff; cursor: pointer; text-align: left;
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease), transform .1s var(--ease);
}
.pos-item-card:hover { border-color: var(--brand); box-shadow: var(--shadow-md); transform: translateY(-1px); }
.pos-item-card:active { transform: translateY(0); }
.pos-item-card .pname { font-weight: 650; font-size: 13px; margin-bottom: 3px; }
.pos-item-card .pmeta { font-size: 11.5px; color: var(--text-muted); }
.pos-item-card .pprice { font-weight: 700; color: var(--brand-dark); margin-top: 7px; }
.pos-item-card .pstock-low { color: var(--danger); font-weight: 600; }
.cart-row { display: flex; align-items: center; gap: 8px; padding: 9px 0; border-bottom: 1px solid var(--border); }
.cart-row .name { flex: 1; font-size: 13px; }
.cart-row .qty-controls { display: flex; align-items: center; gap: 6px; }
.cart-row .qty-controls button {
  width: 25px; height: 25px; border-radius: 6px; border: 1px solid var(--border-strong); background: #fff; cursor: pointer;
  font-weight: 700; color: var(--text); transition: background .15s var(--ease);
}
.cart-row .qty-controls button:hover { background: #f0f2f4; }
.cart-total-row { display: flex; justify-content: space-between; padding: 6px 0; font-size: 13.5px; }
.cart-total-row.grand { font-size: 19px; font-weight: 700; border-top: 2px solid var(--text); padding-top: 12px; margin-top: 8px; }

/* ---- Receipt / print ----
   Kept intentionally simple (monospace, dashed rules, no color) since the
   real target is a 58mm/80mm thermal printer, not a screen — anything
   fancier (shadows, colored badges, web fonts) either doesn't render on
   thermal hardware or wastes paper-roll width. All alignment below uses
   flex + padding, not manual spaces/&nbsp;, so it stays lined up
   correctly no matter how wide the actual paper roll prints at. */
.receipt {
  width: 320px; margin: 0 auto; background: #fff; padding: 18px; font-family: 'Courier New', monospace; font-size: 12.5px;
  border: 1px dashed var(--border-strong); border-radius: 4px; line-height: 1.5;
}
.receipt .center { text-align: center; }
.receipt hr { border: none; border-top: 1px dashed #333; margin: 8px 0; }
.receipt hr.dbl { border-top: 2px double #333; margin: 9px 0; }
.receipt .row { display: flex; justify-content: space-between; gap: 8px; }
.receipt .shop-name { font-weight: 700; font-size: 16px; letter-spacing: .01em; }
.receipt .receipt-logo { max-height: 56px; max-width: 100%; width: auto; object-fit: contain; margin-bottom: 4px; }
.receipt .branch-line { font-size: 12px; }
.receipt .meta-label { color: #444; }
.receipt .item-line { margin-bottom: 3px; }
.receipt .item-name { font-weight: 700; }
.receipt .item-sub { display: flex; justify-content: space-between; padding-left: 10px; color: #333; }
.receipt .total { font-weight: 700; font-size: 17px; }
.receipt .thankyou { margin-top: 6px; font-weight: 700; }
.receipt .platform-credit { margin-top: 8px; font-size: 9.5px; color: #999; letter-spacing: 0.2px; }
@media print {
  body * { visibility: hidden; }
  .receipt, .receipt * { visibility: visible; }
  .receipt { position: absolute; left: 0; top: 0; border: none; width: 100%; padding: 6px 4px; }
  @page { margin: 4mm; }
}

/* =========================================================================
   STOREFRONT DESIGN SYSTEM — customer-facing site only (.store-page, set
   on <body> by initStoreShell in shell.js). Replaces every earlier design
   pass on this file (the old orange accent + warm-cream theme is gone
   entirely) with one deliberate, cohesive palette and one flat set of
   component rules — no more layered overrides re-declaring the same
   selector across several "design pass" blocks.

   Palette (defined once below, referenced everywhere on this page —
   nothing else on the storefront uses a color outside this list):
     --store-primary        #1E3A5F  deep navy — headers, primary buttons,
                                      active nav/tab states, icons
     --store-primary-dark   #14293F  hover/pressed state for the above
     --store-primary-light  #E8EDF3  light navy tint — selected-state
                                      backgrounds, category icon circles
     --store-coral          #E85D4E  sale badges/discount call-outs, the
                                      "you saved X" moment at checkout, AND
                                      (per the "lean into coral for energy"
                                      pass) every shopping-action CTA that's
                                      specifically about urgency/conversion:
                                      the header search button, the per-card
                                      Add to Cart icon, the low-stock line,
                                      hero CTA buttons, the Flash Sale nav
                                      link/section. Navy remains the site's
                                      structural/identity color (header bg,
                                      nav links, pagination, form focus) —
                                      coral is reserved for "act now"
                                      moments specifically, never decorative
     --store-coral-dark     #D1483A  hover state for coral elements
     --store-coral-light    #FDECEA  light coral tint — sale badge/callout
                                      backgrounds
     --store-bg             #F8F9FA  page background (soft off-white)
     --store-surface        #FFFFFF  card/panel background (pure white),
                                      so every card visibly lifts off the
                                      page background
     --store-ink             #1A1A1A  headings
     --store-text             #5A5A5A  body / secondary text
     --store-border          #E7E9EC  hairline borders on cards/tiles
     --store-placeholder      #F1F2F4  flat light-gray fill behind any
                                       product image slot with no photo yet
   Because .store-page is set on <body>, every element on a store page is
   a descendant of it — component rules below reference these variables
   directly (no fallback chains) since they only ever render inside a
   store page. Spacing is the same 8px-based scale as before, plus one
   larger step (--sp-7, 80px) used specifically for the gap between major
   homepage sections (hero -> categories -> deals -> featured), per the
   "generous vertical rhythm" requirement. */
body.store-page {
  --store-primary: #1E3A5F;
  --store-primary-dark: #14293F;
  --store-primary-light: #E8EDF3;
  --store-coral: #E85D4E;
  --store-coral-dark: #D1483A;
  --store-coral-light: #FDECEA;
  --store-bg: #F8F9FA;
  --store-surface: #FFFFFF;
  --store-ink: #1A1A1A;
  --store-text: #5A5A5A;
  --store-border: #E7E9EC;
  --store-placeholder: #F1F2F4;
  --sp-1: 8px;
  --sp-2: 16px;
  --sp-3: 24px;
  --sp-4: 32px;
  --sp-5: 48px;
  --sp-6: 64px;
  --sp-7: 80px;
  /* Content max-width for every full-bleed section (header, footer, page
     container) — much wider than a typical centered-column site (1280px)
     so the store reads as edge-to-edge on ordinary laptop/desktop widths
     (nothing narrower than ~1660px ever sees a visible side gap beyond the
     small fixed padding below), while still capping on ultra-wide monitors
     so rows of cards don't stretch into unreadably long lines. Matches the
     reference site's own "wide, not infinite" container approach. */
  --store-max: 1600px;
  background: var(--store-bg);
  color: var(--store-ink);
  font-size: 16px;
  line-height: 1.6;
}

/* ---- Admin panel style presets ("Admin Panel Theme" in Settings) ----
   A shop owner (or, centrally, the platform admin) picks one of these;
   public/admin/shell.js sets data-admin-theme on <body class="admin-page">
   to whatever's chosen (falling back to "navy" — the original look — for
   anything unset/unrecognized). Each preset only re-points the SAME
   --brand/--brand-dark/--brand-darker/--brand-light variables already used
   everywhere in this admin stylesheet, plus the chart line color those
   charts key off of — no new one-off colors, no component touched
   directly, so a theme switch stays 100% inside the existing design
   system by construction, same approach as the store presets below.
   --accent (coral) is deliberately NEVER touched by any admin theme: it's
   reserved platform-wide for "needs attention" signals, and a theme picker
   changing what counts as an alert color would be a real usability
   regression, not a style choice. "navy" needs no override block since its
   values are exactly the :root defaults above. */
body.admin-page[data-admin-theme="forest"] {
  --brand: #1F5C4A;
  --brand-dark: #153F33;
  --brand-darker: #0F2B24;
  --brand-light: #E6F0EC;
  --chart-line: #1F5C4A;
}
body.admin-page[data-admin-theme="sunset"] {
  --brand: #5C1F3A;
  --brand-dark: #3F1428;
  --brand-darker: #2A0D1B;
  --brand-light: #F0E6EA;
  --chart-line: #5C1F3A;
}
body.admin-page[data-admin-theme="indigo"] {
  --brand: #4338CA;
  --brand-dark: #362DA3;
  --brand-darker: #241E6E;
  --brand-light: #EEECFB;
  --chart-line: #4338CA;
}

/* ---- Store style presets ("Choose your store style" in Settings) ----
   A shop owner picks one of these from Settings; public/store/shell.js sets
   data-theme on <body> to whatever they picked (falling back to "classic"
   for anything unset/unrecognized). Each preset only ever re-points the
   SAME --store-primary/--store-coral variables already used everywhere
   above and below this block — no new one-off colors, no component gets
   touched directly — so a theme switch stays 100% inside the existing
   design system by construction. "classic" needs no override block since
   its values are exactly the body.store-page defaults above.
   Header-style and homepage-layout differences (the other two axes the
   picker varies) are handled further down, near .store-header and
   .home-layout respectively — same data-theme attribute, different rules. */
body.store-page[data-theme="forest"] {
  --store-primary: #1F5C4A;
  --store-primary-dark: #153F33;
  --store-primary-light: #E6F0EC;
  --store-coral: #D98A2B;
  --store-coral-dark: #B86F1B;
  --store-coral-light: #FBEEDD;
}
body.store-page[data-theme="sunset"] {
  --store-primary: #5C1F3A;
  --store-primary-dark: #3F1428;
  --store-primary-light: #F0E6EA;
  --store-coral: #D9A62B;
  --store-coral-dark: #B8871B;
  --store-coral-light: #FBF3DD;
}

.store-page a { color: var(--store-primary); }
/* Same :not(.btn) fix as the base a:hover rule above — without it, this
   was forcing hover text to var(--store-primary-dark) on any storefront
   button that's an <a> tag (Continue Shopping, Shop Now, etc.), which for
   .btn-primary exactly matched that button's own hover background,
   making the text disappear. Wrapped in :where(...) for the same reason
   as the base rule above: keeps this selector's specificity pinned to
   plain ".store-page a:hover" even if more storefront components ever
   need excluding here later, so it can never creep past a component's own
   hover rule the way the un-:where()'d version of the base rule just did. */
.store-page a:hover:not(:where(.btn)) { color: var(--store-primary-dark); }
/* Buttons/inputs inherit the base .btn / input,select,textarea rules
   above by default, which were sized for the admin panel's denser 13.5px
   UI — on the storefront's own larger 16px type scale (see body.store-page
   above) that reads as small and slightly cramped next to the surrounding
   copy, most noticeably on high-stakes CTAs like checkout's "Place Order".
   This block bumps both up to match the storefront's own scale instead,
   and swaps the admin's border/text tokens for the storefront's own
   (--store-border/--store-ink/--store-text) so a themed shop (Forest,
   Sunset, ...) doesn't get a stray admin-navy edge on its form fields. */
.store-page .btn {
  padding: 10px 20px; border-radius: 10px; border-color: var(--store-border);
  font-size: 14.5px; color: var(--store-ink);
}
.store-page .btn-sm { padding: 6px 13px; font-size: 12.5px; }
/* Plain/outline buttons (Continue Shopping, Apply coupon, ...) used to
   fall through to the base .btn:hover rule, which turns the background a
   near-white grey (#f6f7f9) — almost the exact same shade as the
   storefront's own page background (--store-bg, #F8F9FA), so hovering one
   of these barely looked like anything happened. Swapping to the light
   navy tint already used for "selected" states elsewhere (size chips,
   payment method picker) makes the hover unmistakable and keeps it on the
   same brand color as everything else, instead of an unrelated grey.
   Declared BEFORE .btn-primary below on purpose: both selectors tie at
   the same specificity (.store-page + one class + :hover), so whichever
   is declared LAST wins any overlapping property — .btn-primary's own
   hover needs to be that last, more-specific-in-intent declaration, or a
   button with both classes (the normal case) would end up styled by
   this generic rule instead of its own. Same landmine the a:hover
   :where() fix above exists to avoid; here the fix is just ordering. */
.store-page .btn:hover { background: var(--store-primary-light); border-color: var(--store-primary); color: var(--store-primary-dark); transform: translateY(-1px); }
.store-page .btn:active { transform: translateY(0); }
.store-page .btn:focus-visible { outline-color: var(--store-primary); }
.store-page .btn-primary { background: var(--store-primary); border-color: var(--store-primary); color: #fff; box-shadow: 0 2px 6px rgba(30,58,95,.22); }
/* Primary CTA gets a small lift + deeper shadow on hover, same tactile
   "this responded to you" language as .product-card's hover lift just
   above — not just a flat color swap, so the storefront's biggest, most
   important buttons (Add to Cart, Place Order) feel like they react. */
.store-page .btn-primary:hover { background: var(--store-primary-dark); border-color: var(--store-primary-dark); color: #fff; transform: translateY(-1px); box-shadow: 0 6px 16px rgba(30,58,95,.28); }
.store-page .btn-primary:active { transform: translateY(0); box-shadow: 0 2px 6px rgba(30,58,95,.22); }
/* Same equal-specificity ordering issue as .btn-primary above, for the one
   destructive action the storefront has (order-detail.html's Cancel
   Order): without its own hover declared after the generic .btn:hover
   rule, cancelling an order would hover to the same friendly navy tint as
   every other button instead of staying red — the one place on this site
   a color SHOULD read as a warning. */
.store-page .btn-danger:hover { background: #c11f1f; border-color: #c11f1f; color: #fff; transform: translateY(-1px); }
.store-page .text-coral { color: var(--store-coral-dark); font-weight: 650; }

/* Same "admin-density leaking into the customer-facing page" fix as the
   buttons above, applied to every text input/select/textarea/label — this
   is what login, signup, and checkout's address/coupon fields all use.
   Focus ring border-color now follows the shop's actual chosen theme
   color (--store-primary) instead of always showing navy regardless of
   theme, which previously meant a Forest or Sunset themed shop's whole
   site was green/maroon EXCEPT its own form fields on hover/focus. */
.store-page label { color: var(--store-text); font-size: 13px; }
.store-page input, .store-page select, .store-page textarea {
  padding: 11px 14px; border-radius: 10px; border-color: var(--store-border);
  font-size: 15px; color: var(--store-ink);
}
.store-page input::placeholder, .store-page textarea::placeholder { color: #9aa0a8; }
.store-page input:focus, .store-page select:focus, .store-page textarea:focus {
  border-color: var(--store-primary); box-shadow: 0 0 0 3px rgba(30,58,95,.14);
}

/* ---- Trust bar — a slim always-on strip above the header (see the render
   comment in store/shell.js for why these four lines specifically). Dark
   navy rather than the coral/primary-light tints used elsewhere on the
   page: this needs to read as a fixed, structural "about this store" line
   sitting above everything else, not a themed/decorative element, so it
   stays legible and consistent across every store theme (Classic/Forest/
   Sunset) without needing its own per-theme override block. ---- */
.store-trustbar { background: var(--store-primary-dark); color: rgba(255,255,255,0.92); }
.store-trustbar > .inner {
  max-width: var(--store-max); margin: 0 auto; padding: 0 20px; height: 34px;
  display: flex; align-items: center; justify-content: center; gap: 32px;
  flex-wrap: wrap; overflow: hidden;
}
.store-trustbar span { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; font-weight: 650; letter-spacing: .01em; white-space: nowrap; }
.store-trustbar svg { width: 14px; height: 14px; flex-shrink: 0; opacity: .9; }
@media (max-width: 640px) { .store-trustbar { display: none; } }

/* ---- Header: two rows, reference-site style. Row 1 (.store-header > .inner)
   is logo, a dominant full-width-ish search bar, then cart/account —
   sticky so it stays visible while scrolling, same as the reference site.
   Row 2 (.store-subnav) is a slim tinted strip carrying the actual page
   links + category mega-menu trigger, kept as a separate row specifically
   so the search bar has real room to be the visually dominant element up
   top instead of splitting space with nav links. White/sticky/hairline
   border on row 1 is unchanged from the original single-row header. ---- */
.store-header { background: rgba(255,255,255,0.94); backdrop-filter: saturate(180%) blur(10px); border-bottom: 1px solid var(--store-border, var(--border)); box-shadow: 0 1px 3px rgba(26,26,26,0.03); position: sticky; top: 0; z-index: 50; }
.store-header > .inner { max-width: var(--store-max); margin: 0 auto; padding: 16px 20px; display: flex; align-items: center; gap: 22px; position: relative; }
.store-header .logo { font-weight: 800; font-size: 20px; color: var(--store-ink); letter-spacing: -0.02em; display: flex; align-items: center; flex-shrink: 0; }
.store-header .logo.has-image img { max-height: 52px; max-width: 220px; width: auto; object-fit: contain; display: block; }

/* Search — the dominant element of the top row (grows to fill the space
   between logo and account icons, capped so it doesn't stretch absurdly
   wide on a very large screen), coral submit button for visual energy
   consistent with the rest of the "urgency" color pass. Submits through to
   products.html's existing `q` filter — see the submit handler in
   initStoreShell (store/shell.js) — so there's no second search backend. */
.store-search { flex: 1; display: flex; max-width: 640px; }
.store-search input {
  flex: 1; min-width: 0; border: 1.5px solid var(--store-border); border-right: none; border-radius: 8px 0 0 8px;
  padding: 10px 14px; font-size: 14px; background: var(--store-bg); color: var(--store-ink);
}
.store-search input:focus { outline: none; border-color: var(--store-coral); background: #fff; }
.store-search button {
  display: flex; align-items: center; justify-content: center; width: 46px; flex-shrink: 0;
  background: var(--store-coral); color: #fff; border: none; border-radius: 0 8px 8px 0; cursor: pointer;
  transition: background .15s var(--ease);
}
.store-search button:hover { background: var(--store-coral-dark); }
.store-search button svg { width: 18px; height: 18px; }

.store-header .actions { display: flex; gap: 20px; align-items: center; font-size: 14px; font-weight: 600; flex-shrink: 0; }
.store-header .actions a { color: var(--store-text); }
.store-header .actions a:hover { color: var(--store-ink); }
.store-header .cart-link { display: inline-flex; align-items: center; position: relative; color: var(--store-text); }
.store-header .cart-link:hover { color: var(--store-ink); }
.store-header .cart-link svg { width: 23px; height: 23px; stroke: currentColor; }
/* Badge overlaps the icon's top-right corner (the conventional cart-count
   affordance) instead of sitting inline beside it — same markup/JS as
   before (updateCartBadge() just sets its textContent, unchanged), purely
   a positioning change. The border "cuts" the badge out of the icon so it
   reads as a distinct chip rather than overlapping pixels. */
.store-header .cart-badge {
  position: absolute; top: -7px; right: -9px;
  display: inline-flex; align-items: center; justify-content: center; min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: 99px; background: var(--store-coral); color: #fff; font-size: 10px; font-weight: 700;
  border: 2px solid var(--store-surface, #fff);
}

/* Sub-nav strip — faint navy tint so it visibly reads as a secondary row
   under the white search row rather than competing with it. Same nav
   links/mega-menu markup as before, just relocated out of the top row. */
.store-subnav { background: var(--store-primary-light); border-top: 1px solid var(--store-border); }
.store-subnav > .inner { max-width: var(--store-max); margin: 0 auto; padding: 0 20px; display: flex; align-items: center; gap: 24px; height: 42px; }
.store-subnav a { color: var(--store-primary-dark); font-size: 13.5px; font-weight: 650; padding: 4px 1px; border-bottom: 2px solid transparent; }
.store-subnav a:hover { text-decoration: none; color: var(--store-ink); }
.store-subnav a.active { color: var(--store-coral-dark); border-color: var(--store-coral); }
/* Flash Sale nav shortcut — pinned to the right end of the strip, coral
   throughout (not just on hover) since this link IS an urgency signal by
   itself, same reasoning as the discount badge/low-stock line. */
.store-subnav .subnav-flash { color: var(--store-coral-dark); margin-left: auto; font-weight: 800; }

@media (max-width: 640px) {
  .store-header > .inner { flex-wrap: wrap; padding: 10px 16px; gap: 10px; }
  .store-header .logo { order: 1; }
  .store-header .actions { order: 2; margin-left: auto; }
  .store-search { order: 3; flex: 1 1 100%; max-width: none; }
  /* Sub-nav row (Home/Shop All/Shop by Category/Combos/Flash Sale) — the
     desktop rule above gives this row a fixed 42px height, which is too
     short to also give each link a real touch target once padding is
     added below; switching to auto height (content + this padding) here
     only, so the horizontal-scroll strip itself is untouched, just taller
     and easier to tap on a phone. */
  .store-subnav > .inner { height: auto; padding: 8px 16px; overflow-x: auto; gap: 18px; scrollbar-width: none; }
  .store-subnav > .inner::-webkit-scrollbar { display: none; }
  /* Real touch-target fix: these links/trigger had only 4px vertical
     padding (~22px tall tap area) — comfortable for a mouse pointer, too
     small for a thumb. Desktop is untouched; this only applies inside the
     same ≤640px block already governing the mobile subnav layout above. */
  .store-subnav a, .nav-dropdown-trigger { padding: 10px 2px; }
}

/* ---- "Shop by Category" mega-menu — the nav shows one trigger instead of
   trying to fit every category as its own top-level link, so this scales
   to a client with 5 categories or 25 without the header ever overflowing
   or wrapping onto a second line. The panel itself is a wrapping multi-
   column grid with a max-height + scroll safety net, so even an unusually
   long category list stays contained instead of growing the page. Opens
   on hover for mouse users and on click/tap (with outside-click + Escape
   to close) so it also works with touch and keyboard. ---- */
.nav-dropdown { position: relative; }
.nav-dropdown-trigger {
  display: inline-flex; align-items: center; gap: 5px; background: none; border: none; cursor: pointer;
  font: inherit; color: var(--store-text); font-size: 14px; font-weight: 600; padding: 4px 1px;
  border-bottom: 2px solid transparent;
}
.nav-dropdown-trigger:hover { color: var(--store-ink); }
.nav-dropdown .nav-chev { margin-left: 1px; transition: transform .15s var(--ease); position: relative; top: 1px; }
.nav-dropdown.open .nav-chev, .nav-dropdown:hover .nav-chev { transform: rotate(180deg); }
/* Real bug fix, all screen sizes: this used to be `position: absolute`,
   centered via `left: 50%; transform: translateX(-50%)` relative to the
   trigger button's OWN containing block (.nav-dropdown) — not the
   viewport. The trigger sits near the left side of the subnav row (right
   after "Home"/"Shop All"), so on anything narrower than roughly 1600px
   (i.e. almost every real browser window, not just phones — this was
   never actually a "mobile-only" bug, it was just most visible on a
   narrower window), a 640px-wide menu centered under that left-anchored
   trigger has nowhere to go but off the left edge of the page, which is
   exactly the overflow/clipping this caused. `position: fixed` makes its
   containing block the viewport itself (nothing here has a transform/
   filter creating a new one), so `left: 50%; transform: translateX(-50%)`
   now centers it on the actual browser window at every width, and
   `width: min(640px, 92vw)` keeps it comfortably inside that window even
   on the narrowest phones. `top` can't be a plain CSS percentage under
   fixed positioning (there's no ancestor to be "100%" of any more), so
   it's computed and set inline by shell.js right when the menu opens —
   from the header's real rendered height, which varies shop-to-shop
   depending on whether the free-shipping promo strip is showing above
   the header; the value below is only a same-first-paint fallback. */
.mega-menu {
  position: fixed; top: 108px; left: 50%; transform: translateX(-50%) translateY(6px);
  background: var(--store-surface); border: 1px solid var(--store-border); border-radius: 14px;
  box-shadow: var(--shadow-lg); padding: 18px; width: min(640px, 92vw); z-index: 70;
  opacity: 0; visibility: hidden; transition: opacity .15s var(--ease), transform .15s var(--ease), visibility .15s;
}
.nav-dropdown:hover .mega-menu, .nav-dropdown.open .mega-menu { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
.mega-menu-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 2px 14px; max-height: 360px; overflow-y: auto; padding-right: 4px; }
.mega-menu-link { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px; border-radius: 8px; color: var(--store-ink); font-size: 13.5px; font-weight: 600; }
.mega-menu-link:hover { background: var(--store-bg); color: var(--store-primary); text-decoration: none; }
.mega-menu-count { font-size: 11px; font-weight: 600; color: var(--store-text); flex-shrink: 0; }
.mega-menu-viewall { display: block; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--store-border); font-size: 13px; font-weight: 700; color: var(--store-primary); text-align: center; }

.promo-strip { background: var(--store-primary-dark); color: #fff; text-align: center; font-size: 12.5px; font-weight: 650; padding: 7px 16px; letter-spacing: .01em; }

/* ---- Page container + vertical rhythm ---- */
.store-container { max-width: var(--store-max); margin: 0 auto; padding: var(--sp-5, 48px) 20px var(--sp-6, 64px); }
.store-container h2 { font-size: 22px; font-weight: 700; letter-spacing: -0.015em; margin: 0 0 var(--sp-3, 24px); }
.store-container p { color: var(--store-text); }
.store-page h1 { font-size: 28px; font-weight: 800; letter-spacing: -0.02em; margin-bottom: var(--sp-2, 16px); color: var(--store-ink); }
.store-page h2 { font-weight: 700; color: var(--store-ink); }
.store-page h3 { font-weight: 700; color: var(--store-ink); }

.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: var(--sp-3, 24px); }
.section-head h2 { margin-bottom: 0; display: flex; align-items: center; gap: 10px; }
.section-head h2::before { content: ''; width: 5px; height: 22px; border-radius: 3px; background: var(--store-primary); display: inline-block; }
.section-head a { font-size: 13.5px; font-weight: 650; }

/* ---- Product rail — the reference site's row-you-scroll-through pattern
   for the named homepage sections (Flash Sale, New Arrivals, Recommended
   For You, Top in [Category]), as opposed to the wrapping CSS-grid used on
   Shop All/category pages. Cards keep a fixed width instead of stretching
   to fill a grid cell, native horizontal scroll + snap (no JS scroller
   needed), scrollbar hidden since the peeking-next-card edge is the scroll
   affordance. Works identically on desktop (trackpad/shift-scroll/drag) and
   mobile (touch swipe) — same markup, same CSS, no separate mobile-only
   behavior needed. */
.product-rail {
  display: flex; gap: 12px; overflow-x: auto; scroll-snap-type: x proximity;
  padding-bottom: 6px; scrollbar-width: none; margin-bottom: 0;
}
.product-rail::-webkit-scrollbar { display: none; }
.product-rail .product-card { flex: 0 0 152px; scroll-snap-align: start; }
@media (min-width: 780px) { .product-rail .product-card { flex-basis: 190px; } }

/* Flash Sale gets its own warm coral-tinted zone (background + generous
   padding pulling it out to a full-bleed band) rather than sitting flush
   with the plain page background like every other rail — the one visually
   "hottest" section on the homepage, matching how the reference site marks
   its own flash-sale zone as a distinct colored area, not just another grid
   with a heading. */
.home-flashsale {
  background: linear-gradient(135deg, var(--store-coral-light) 0%, #fff 60%);
  border: 1px solid var(--store-coral-light); border-radius: 16px;
  padding: 22px 22px 26px;
}
.home-flashsale .section-head h2::before { background: var(--store-coral); }
.home-flashsale .section-head a { color: var(--store-coral-dark); font-weight: 800; }

/* ---- Hero carousel ----
   Built as a real slider (track + slides + dots + arrows) even though the
   homepage only ever feeds it one slide today — the JS (initHeroCarousel in
   store/shell.js) loops over an array of slide objects, so a future "Banner"
   admin feature can hand it 2, 3, or 10 slides without this markup/CSS
   changing at all. Arrows/dots/auto-advance simply don't render or start
   when there's only one slide (see the JS), so a single-slide shop sees
   exactly what the old static hero looked like, just in the new shell. */
.hero-carousel { position: relative; border-radius: 16px; overflow: hidden; margin-bottom: 0; box-shadow: var(--shadow-lg); }
.hero-track { display: flex; transition: transform .45s var(--ease); }
/* Bolder, more colorful hero than a flat navy box: the same navy base
   (brand consistency) plus a warm coral glow bleeding in from the corner
   and a faint dot-grid texture, both purely decorative (::before/::after,
   clipped by overflow:hidden on .hero-carousel above) so they never affect
   layout or collide with the real slide content sitting above them via
   z-index/position. */
.hero-slide {
  min-width: 100%; flex-shrink: 0;
  background: linear-gradient(135deg, var(--store-primary) 0%, var(--store-primary-dark) 75%);
  color: #fff; padding: clamp(32px, 6vw, 64px) clamp(24px, 5vw, 48px); position: relative; overflow: hidden;
}
.hero-slide::before {
  content: ''; position: absolute; top: -35%; right: -12%; width: 60%; padding-bottom: 60%; border-radius: 50%;
  background: radial-gradient(circle, rgba(232,93,78,.55) 0%, rgba(232,93,78,0) 70%); pointer-events: none;
}
.hero-slide::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background-image: radial-gradient(rgba(255,255,255,.09) 1.5px, transparent 1.5px);
  background-size: 24px 24px;
}
/* A shop-uploaded Hero Banner photo (Settings -> Store Appearance) replaces
   the gradient itself via an inline background-image (see renderHeroCarousel
   in store/shell.js) — the decorative coral glow/dot-grid above only make
   sense against the plain gradient, so both are swapped for one legibility
   overlay instead, keeping the same white heading/paragraph text readable
   over an arbitrary photo without needing per-shop color tuning. */
.hero-slide.has-image { background-size: cover; background-position: center; }
.hero-slide.has-image::before {
  top: 0; right: 0; width: 100%; padding-bottom: 0; height: 100%; border-radius: 0;
  background: linear-gradient(90deg, rgba(13,29,44,.78) 0%, rgba(13,29,44,.48) 55%, rgba(13,29,44,.15) 100%);
}
.hero-slide.has-image::after { display: none; }

/* Promotional Banner (Settings -> Store Appearance, optional) — a plain
   full-width clickable image sitting just under the hero, on its own no
   different from any other storefront image. Hidden entirely (via the
   shared .hidden utility class, toggled in store/index.html) rather than
   rendered empty when a shop hasn't uploaded one, same rule every other
   optional home-page section on that page already follows. */
.home-promo-banner {
  display: block; border-radius: 16px; overflow: hidden; margin: 0;
  box-shadow: var(--shadow-sm); transition: box-shadow .15s var(--ease), transform .15s var(--ease);
}
.home-promo-banner:hover { box-shadow: var(--shadow-md); transform: translateY(-1px); }
.home-promo-banner img { display: block; width: 100%; height: auto; }
/* .home-layout lays its sections out via explicit flex `order` per theme
   (see the "Store style presets" block further down) rather than relying
   on source order — the banner needs the SAME order value as
   .hero-carousel in every theme (default/classic, forest, sunset) so it
   always lands immediately after the hero in the flex order (ties resolve
   to DOM order, and this element is already placed right after
   .hero-carousel in store/index.html's markup), keeping "appears below the
   hero" true no matter which theme a shop has picked. */
.home-layout > .home-promo-banner { order: 1; }
.hero-slide > * { position: relative; z-index: 1; }
.hero-slide .eyebrow { display: inline-flex; align-items: center; gap: 7px; text-transform: uppercase; letter-spacing: .12em; font-size: 11.5px; font-weight: 700; color: rgba(255,255,255,0.9); margin-bottom: var(--sp-2, 16px); }
.hero-slide .eyebrow::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: var(--store-coral); display: inline-block; }
.hero-slide h1 { font-size: clamp(28px, 3.8vw, 42px); margin-bottom: 12px; color: #fff; letter-spacing: -0.02em; line-height: 1.15; max-width: 32ch; font-weight: 800; }
.hero-slide p { opacity: .92; margin-bottom: 0; font-size: 16px; color: #fff; max-width: 48ch; line-height: 1.6; }
.hero-slide .hero-cta {
  display: inline-flex; align-items: center; gap: 8px; margin-top: var(--sp-3, 24px); padding: 13px 26px;
  background: var(--store-coral); color: #fff; border-radius: 9px; font-weight: 700; font-size: 14px;
  box-shadow: 0 6px 18px rgba(232,93,78,.35); transition: transform .15s var(--ease), box-shadow .15s var(--ease);
}
.hero-slide .hero-cta:hover { color: #fff; background: var(--store-coral-dark); transform: translateY(-2px); box-shadow: 0 10px 24px rgba(232,93,78,.45); text-decoration: none; }
.hero-arrow {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2; width: 38px; height: 38px; border-radius: 50%;
  background: rgba(255,255,255,0.22); border: none; color: #fff; display: flex; align-items: center; justify-content: center;
  cursor: pointer; backdrop-filter: blur(4px); transition: background .15s var(--ease);
}
.hero-arrow:hover { background: rgba(255,255,255,0.38); }
.hero-arrow.prev { left: 16px; }
.hero-arrow.next { right: 16px; }
.hero-arrow svg { width: 18px; height: 18px; }
.hero-dots { position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%); z-index: 2; display: flex; gap: 7px; }
.hero-dot { width: 7px; height: 7px; border-radius: 50%; background: rgba(255,255,255,0.45); border: none; cursor: pointer; padding: 0; transition: background .15s var(--ease), width .15s var(--ease); }
.hero-dot.active { background: #fff; width: 20px; border-radius: 4px; }

/* ---- Category strip — Daraz-style: a horizontally-scrollable rail of
   icon-forward tiles on mobile (no room for a multi-column grid at that
   width), switching to a proper wrapping grid from tablet width up. Wraps
   to however many categories a shop has via auto-fill; nothing here
   assumes a fixed count. Real per-category icons (see getCategoryIcon in
   store/shell.js) replace the old plain letter-in-a-circle — matched by
   keyword against the category's own name, so this works for any shop's
   own category names with zero configuration, and falls back to a generic
   tag icon for anything unrecognized. */
.category-grid {
  display: flex; gap: var(--sp-2, 16px); margin-bottom: var(--sp-6, 64px);
  overflow-x: auto; scroll-snap-type: x proximity; padding-bottom: 4px;
  scrollbar-width: none;
}
.category-grid::-webkit-scrollbar { display: none; }
@media (min-width: 780px) {
  .category-grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
    overflow-x: visible; padding-bottom: 0;
  }
}
.category-tile {
  display: flex; flex-direction: column; align-items: center; text-align: center; gap: 8px;
  flex-shrink: 0; width: 84px; scroll-snap-align: start;
  padding: 4px 2px; border-radius: 12px; transition: transform .2s var(--ease);
}
@media (min-width: 780px) { .category-tile { width: auto; } }
.category-tile:hover { transform: translateY(-3px); text-decoration: none; }
.category-tile .cat-icon {
  width: 60px; height: 60px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  flex-shrink: 0; box-shadow: 0 1px 3px rgba(26,26,26,0.06);
}
.category-tile .cat-icon svg { width: 26px; height: 26px; }
/* Three tints cycling by position, built ONLY from the existing theme
   tokens (never a new hardcoded color) so this stays correct under every
   store theme (Classic/Forest/Sunset), not just the default navy one. */
.category-tile:nth-child(3n+1) .cat-icon { background: var(--store-primary-light); color: var(--store-primary-dark); }
.category-tile:nth-child(3n+2) .cat-icon { background: var(--store-coral-light); color: var(--store-coral-dark); }
.category-tile:nth-child(3n) .cat-icon { background: var(--store-bg); color: var(--store-ink); border: 1px solid var(--store-border); }
.category-tile .cat-name {
  font-weight: 650; font-size: 12.5px; color: var(--store-ink); line-height: 1.25;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
.category-tile .cat-count { display: none; font-size: 11px; color: var(--store-text); }
@media (min-width: 780px) { .category-tile .cat-count { display: block; } }
#school-section, #deals-section, #newarrivals-section, #recommended-section, #topcategory-section { margin-bottom: 0; }

/* ---- Homepage section order (the "different homepage layout" axis of the
   store style picker) — a plain flex column with `gap` handling the
   spacing between sections uniformly, so reordering them per theme via
   `order` never has to fight each section's own margin-bottom. The
   sections' own margin-bottom rules above still apply (harmless — flex
   items are their own block-formatting-context root, so that margin stays
   contained inside each section and never adds on top of the gap), so
   nothing needed to change there. Only `order` differs between themes;
   the HTML is identical for every theme. */
/* 40px (was 80px) — denser, reference-site-style rhythm between homepage
   sections instead of the old generous magazine-style spacing. Hero sits
   directly above the category row with zero extra gap of its own (see
   .hero-carousel's margin-bottom: 0 above), so the category icons read as
   immediately following the banner, matching the reference layout order. */
.home-layout { display: flex; flex-direction: column; gap: 40px; }
.home-layout > .hero-carousel { order: 1; margin-bottom: 0; }
.home-layout > .home-categories { order: 2; }
.home-layout > #school-section { order: 3; }
.home-layout > #deals-section { order: 4; }
.home-layout > #newarrivals-section { order: 5; }
.home-layout > #recommended-section { order: 6; }
.home-layout > #topcategory-section { order: 7; }
.home-layout > .home-featured { order: 8; }
.home-categories .category-grid, .home-featured .product-grid { margin-bottom: 0; }

body.store-page[data-theme="forest"] .home-layout > .home-categories { order: 1; }
body.store-page[data-theme="forest"] .home-layout > #school-section { order: 2; }
body.store-page[data-theme="forest"] .home-layout > #deals-section { order: 3; }
body.store-page[data-theme="forest"] .home-layout > #newarrivals-section { order: 4; }
body.store-page[data-theme="forest"] .home-layout > #recommended-section { order: 5; }
body.store-page[data-theme="forest"] .home-layout > #topcategory-section { order: 6; }
body.store-page[data-theme="forest"] .home-layout > .hero-carousel { order: 7; }
body.store-page[data-theme="forest"] .home-layout > .home-featured { order: 8; }
/* Promo banner tracks the hero's order in every theme (see the comment on
   .home-layout > .home-promo-banner above) — forest moves the hero itself
   to order 7, so the banner has to move with it here to stay adjacent. */
body.store-page[data-theme="forest"] .home-layout > .home-promo-banner { order: 7; }

body.store-page[data-theme="sunset"] .home-layout > .hero-carousel { order: 1; }
body.store-page[data-theme="sunset"] .home-layout > .home-featured { order: 2; }
body.store-page[data-theme="sunset"] .home-layout > .home-categories { order: 3; }
body.store-page[data-theme="sunset"] .home-layout > #school-section { order: 4; }
body.store-page[data-theme="sunset"] .home-layout > #deals-section { order: 5; }
body.store-page[data-theme="sunset"] .home-layout > #newarrivals-section { order: 6; }
body.store-page[data-theme="sunset"] .home-layout > #recommended-section { order: 7; }
body.store-page[data-theme="sunset"] .home-layout > #topcategory-section { order: 8; }

/* ---- Product cards ----
   Denser Daraz/Priceoye-style grid: more, smaller cards per row than the
   old 4-column layout, tighter internal spacing, image-forward. White
   card, 10px corners, subtle shadow that deepens on hover along with a
   -3px lift. The image keeps a fixed 1:1 aspect ratio with a flat
   light-gray fill behind any product with no photo yet, so the grid stays
   perfectly aligned either way. Name is bold and clamped to 2 lines with
   an ellipsis so a long product name can never stretch one card taller
   than its neighbors.
   Three overlays live directly on top of the thumbnail image now, each in
   its own corner so they never collide: a coral "-20%" discount ribbon
   (top-left, only when actually on sale), a small circular "Quick View"
   eye-icon button (top-right, hover-reveal on mouse/trackpad, matching the
   old center-pill behavior just relocated), and the actual add-to-cart
   control (bottom-right) — now a small round icon button instead of a
   full-width bar, permanently visible (not hover-gated) since that's the
   one primary action and touch devices have no hover state to reveal it
   with anyway. A star-rating placeholder row sits under the name: this
   shop has no real review data yet, so it renders 5 unfilled/muted stars
   (never a fabricated rating number) purely to reserve that familiar
   layout slot for whenever real reviews are built. */
.product-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--sp-2, 16px); margin-bottom: var(--sp-2, 16px); }
@media (min-width: 1400px)                        { .product-grid { grid-template-columns: repeat(6, 1fr); } }
/* Container is now up to 1600px wide (see --store-max) — one more column
   at that width keeps card size in the same dense, reference-site range
   instead of just stretching the existing 6 columns wider. */
@media (min-width: 1560px)                        { .product-grid { grid-template-columns: repeat(7, 1fr); } }
@media (max-width: 1200px)                        { .product-grid { grid-template-columns: repeat(4, 1fr); } }
@media (max-width: 900px)                         { .product-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 560px)                         { .product-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; } }
.product-card {
  background: var(--store-surface); border: 1px solid var(--store-border); border-radius: 10px; overflow: hidden;
  display: flex; flex-direction: column; box-shadow: 0 1px 2px rgba(26,26,26,0.04);
  transition: transform .2s var(--ease), box-shadow .2s var(--ease);
}
.product-card:hover { transform: translateY(-3px); box-shadow: 0 12px 28px rgba(26,26,26,0.1); text-decoration: none; }
.product-card .thumb-link { display: block; position: relative; }
.product-card .thumb {
  aspect-ratio: 1 / 1; background: var(--store-placeholder); position: relative;
  display: flex; align-items: center; justify-content: center; color: var(--store-text); font-size: 12px; overflow: hidden;
}
.product-card .thumb img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .4s var(--ease); }
.product-card:hover .thumb img { transform: scale(1.05); }
.product-card.out-of-stock .thumb img { filter: grayscale(.5); opacity: .55; }
.product-card .oos-overlay {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  background: rgba(26,26,26,0.28); pointer-events: none;
}
.product-card .oos-overlay span {
  background: rgba(26,26,26,0.78); color: #fff; font-size: 11px; font-weight: 700; letter-spacing: .03em;
  text-transform: uppercase; padding: 5px 12px; border-radius: 5px;
}
.product-card .discount-badge {
  position: absolute; top: 8px; left: 8px; z-index: 1;
  background: var(--store-coral); color: #fff; font-size: 11px; font-weight: 800; letter-spacing: .01em;
  border-radius: 5px; padding: 3px 7px; line-height: 1.2; box-shadow: 0 2px 5px rgba(0,0,0,0.12);
}
.product-card .body { padding: 10px 10px 0; flex: 1; display: flex; flex-direction: column; }
.product-card .vendor-line { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--store-text); margin-bottom: 2px; }
.product-card .pname {
  font-weight: 650; font-size: 12.5px; color: var(--store-ink); line-height: 1.35; margin-bottom: 4px;
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.product-card .pcat {
  display: inline-flex; align-self: flex-start; background: var(--store-bg); color: var(--store-text);
  border: 1px solid var(--store-border); border-radius: 999px; padding: 1px 8px; margin-bottom: 5px;
  font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
}
/* Rating placeholder — 5 outline stars, muted gray, no invented number.
   title="" gives a screen reader / hover-tooltip an honest explanation
   rather than silently implying a rating exists. */
.product-card .rating-placeholder { display: flex; align-items: center; gap: 1px; margin-bottom: 5px; color: var(--store-border); }
.product-card .rating-placeholder svg { width: 12px; height: 12px; }
.product-card .price-row { display: flex; align-items: baseline; gap: 6px; margin-bottom: 3px; flex-wrap: wrap; }
.product-card .pprice { font-weight: 800; color: var(--store-ink); font-size: 14.5px; letter-spacing: -0.01em; margin-bottom: 0; }
.product-card.on-sale .pprice { color: var(--store-coral-dark); }
.product-card .price-was { font-size: 11px; color: var(--store-text); text-decoration: line-through; font-weight: 600; }
.product-card .price-badge { display: none; } /* superseded by .discount-badge on the thumb; class kept so old markup never errors */
.stock-badge { font-size: 10.5px; margin-top: 1px; margin-bottom: 3px; }
/* Plain products render their add-to-cart control INSIDE .thumb (see the
   icon-button rules below) and never fill .card-actions at all anymore.
   Combo/bundle cards (renderComboCard — a fixed-price kit, not a single
   product with a photo-corner layout) still use the classic full-width
   button in .card-actions, so that treatment is kept working here rather
   than removed. */
.product-card .card-actions:not(:empty) { padding: 8px 10px 10px; }
.product-card .card-actions .add-cart-btn {
  position: static; width: 100%; height: auto; border-radius: 8px; padding: 9px 12px;
  display: flex; align-items: center; justify-content: center; gap: 6px; font-size: 13px; font-weight: 700; box-shadow: none;
}
.product-card .card-actions .add-cart-btn svg { display: none; }

/* Quick View — small circular eye icon, top-right corner of the image,
   hover/focus reveal only (unchanged behavior from the old center pill,
   just relocated so it never competes with the cart button below). */
.product-card .quick-view-btn {
  position: absolute; top: 8px; right: 8px; z-index: 1; opacity: 0; transform: translateY(-4px);
  width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,0.95); color: var(--store-ink); border: none; cursor: pointer; box-shadow: var(--shadow-md);
  transition: opacity .15s var(--ease), transform .15s var(--ease);
}
.product-card .quick-view-btn svg { width: 15px; height: 15px; }
@media (hover: hover) and (pointer: fine) {
  .product-card:hover .quick-view-btn, .product-card:focus-within .quick-view-btn { opacity: 1; transform: translateY(0); }
}
@media (hover: none) { .product-card .quick-view-btn { display: none; } } /* no hover on touch — the card tap already opens the product page */
.product-card .quick-view-btn:hover { background: #fff; }

/* Add to Cart — small round icon button pinned to the image's bottom-right
   corner (lives inside .thumb, alongside the discount badge/quick-view
   button above), always visible (this is the one primary action, and
   touch devices have no hover state to reveal a hidden button with). Only
   applies inside .thumb — combo cards' full-width .card-actions button
   above is a completely different element in a different place, styled by
   its own more-specific rule instead. */
/* Coral, not navy — this is the single most-clicked control on the entire
   storefront (every product card's own Add to Cart), and per the "lean
   harder into coral for urgency/CTA elements" color pass it's now treated
   as one, alongside the discount badge and low-stock line below. Navy stays
   the header/nav/pagination color; coral is now specifically the "take a
   shopping action" color. */
.product-card .thumb .add-cart-btn {
  position: absolute; bottom: 8px; right: 8px; z-index: 1;
  display: flex; align-items: center; justify-content: center; width: 34px; height: 34px; padding: 0;
  background: var(--store-coral); color: #fff; border: none; border-radius: 50%; cursor: pointer;
  box-shadow: 0 3px 8px rgba(232,93,78,.35);
  transition: background .15s var(--ease), transform .15s var(--ease);
}
.product-card .thumb .add-cart-btn svg { width: 17px; height: 17px; }
.product-card .thumb .add-cart-btn:hover { background: var(--store-coral-dark); transform: scale(1.07); }
.product-card .thumb .add-cart-btn.outline { background: #fff; color: var(--store-coral-dark); border: 1.5px solid var(--store-coral); }
.product-card .thumb .add-cart-btn.outline:hover { background: var(--store-coral-light); }
.product-card .thumb .add-cart-btn[disabled] { background: rgba(255,255,255,0.85); color: var(--store-text); cursor: not-allowed; box-shadow: none; }
.product-card .thumb .add-cart-btn[disabled]:hover { transform: none; }

/* "Only X left" — real stock data (see the low-stock comment in
   renderProductCard, store/shell.js), styled as an urgency signal like the
   discount badge: small, bold, coral. */
.low-stock-line { font-size: 10.5px; font-weight: 800; color: var(--store-coral-dark); margin-top: 2px; }

.pd-was-price { font-size: 16px; color: var(--store-text); text-decoration: line-through; font-weight: 600; margin-right: 8px; }

/* ============================================================
   STOREFRONT INITIAL-LOAD SKELETON
   ============================================================
   Covers the ~1-2s gap between a store page's first paint and its real
   header/categories/products actually arriving from the API — before this,
   that gap just showed a bare, unstyled page (no header, empty grids),
   which reads as "slow or broken" rather than "loading". This block is
   generic on purpose: every shape below is sized/positioned to match the
   REAL element it stands in for (reusing the exact same .category-grid/
   .category-tile/.product-grid/.product-card/.thumb/.cat-icon layout
   classes those real elements already use — see index.html), so there's a
   single already-correct source of truth for spacing/columns/breakpoints
   instead of a second, separately-maintained set of skeleton dimensions
   that could quietly drift out of sync and cause a layout jump on swap.
   Colors come entirely from this shop's own --store-* theme variables
   (never a hardcoded color), so the skeleton is automatically in the
   shop's own current theme (Classic/Forest/Sunset) with zero per-tenant
   configuration — same shimmer motion as the existing admin-panel
   .skeleton (skeletonShimmer keyframe, reused rather than duplicated) for
   one consistent "this is loading" feel across the whole app, just
   restyled here for the storefront's own light theme surface instead of
   the admin panel's flat gray.
   Driven by a body.store-loading class (see initStoreShell callers, e.g.
   store/index.html's own script) — present by default in each store
   page's markup so the skeleton is what a visitor sees on first paint,
   before any JS has even run; removed once that page's real data has
   actually finished loading (success) or replaced by the error state
   below (failure). No fixed/artificial delay anywhere in this file — how
   long the skeleton stays visible is entirely up to the real load. */
.store-skel-shape {
  background-color: var(--store-placeholder);
  background-image: linear-gradient(90deg, var(--store-placeholder) 0px, var(--store-surface) 40px, var(--store-placeholder) 80px);
  background-size: 240px 100%; animation: skeletonShimmer 1.5s ease-in-out infinite;
}
.store-skel-line { display: block; border-radius: 5px; height: 11px; }

/* Hidden by default; only shown while body carries .store-loading. Kept as
   a normal in-flow block (not position:fixed) so it can never cover a
   later-appended element like common.js's own fatal-error banner — it just
   sits above it in the page, same as any other content. */
#store-skeleton { display: none; }
body.store-loading #store-skeleton { display: block; }
/* The real header/trust-bar/footer/promo-strip/WhatsApp button are
   inserted straight onto <body> by initStoreShell() (see store/shell.js) —
   this is the one thing standing in for a "header skeleton": rather than
   duplicating shell.js's own header-building logic a second time just for
   the loading state, the real header is simply not shown until it's ready,
   while the skeleton's own header-shaped placeholder (below) fills that
   same visual space in the meantime. Real page content (each page's own
   .store-container) is hidden the same way via body.store-loading in each
   page's own stylesheet-free markup — see index.html. */
body.store-loading .store-header,
body.store-loading .store-trustbar,
body.store-loading .store-footer,
body.store-loading .store-footer-bottom,
body.store-loading .promo-strip,
body.store-loading .store-whatsapp-fab { display: none !important; }
/* Real content fades in rather than just popping into place the instant
   body.store-loading is removed — a deliberately short, subtle transition
   (not an artificial delay: it only starts once the real content is
   already ready to show), matching "smoothly replace the skeleton" without
   drawing more attention to itself than a sub-second local load deserves. */
#store-real-content { opacity: 0; transition: opacity .25s ease; }
/* Only revealed once loading finished successfully — kept hidden (not
   half-rendered underneath the error card) in the failure state too, so a
   fetch that fails partway through never shows a broken-looking mix of
   "some real content" + "error message" at the same time. */
body:not(.store-loading):not(.store-load-error) #store-real-content { opacity: 1; }

.skel-header { background: rgba(255,255,255,0.94); border-bottom: 1px solid var(--store-border); }
.skel-header-inner {
  max-width: var(--store-max); margin: 0 auto; padding: 14px 20px;
  display: flex; align-items: center; gap: 20px;
}
.skel-header-inner .store-skel-shape.logo { width: 120px; height: 22px; border-radius: 5px; flex-shrink: 0; }
.skel-header-inner .store-skel-shape.search { flex: 1; height: 40px; border-radius: 8px; min-width: 0; }
.skel-header-inner .store-skel-shape.icon { width: 24px; height: 24px; border-radius: 6px; flex-shrink: 0; }
@media (max-width: 700px) { .skel-header-inner .store-skel-shape.search { display: none; } }
.skel-subnav { background: var(--store-primary-light); border-top: 1px solid var(--store-border); height: 42px; display: flex; align-items: center; }
.skel-subnav-inner { max-width: var(--store-max); margin: 0 auto; padding: 0 20px; display: flex; gap: 24px; width: 100%; }
.skel-subnav .store-skel-shape.navlink { width: 64px; height: 10px; }

.store-skel-body { max-width: var(--store-max); margin: 0 auto; padding: var(--sp-5, 48px) 20px var(--sp-6, 64px); }
.store-skel-hero { border-radius: 16px; height: clamp(160px, 26vw, 260px); margin-bottom: 40px; }
.store-skel-heading { width: 180px; height: 20px; margin-bottom: 24px; }
/* Reuses .category-grid/.category-tile/.cat-icon exactly as the real
   category rail does (see store/shell.js's getCategoryIcon usage in
   index.html) — same flex/grid columns, same 60px icon circle, so this
   collapses into the identical layout at every breakpoint the real one
   already has, no separate skeleton-only responsive rules needed. */
.skel-category-grid.category-grid { margin-bottom: 40px; }
.skel-category-grid .category-tile { cursor: default; }
.skel-category-grid .cat-icon.store-skel-shape { border-radius: 50%; }
.skel-category-grid .cat-name.store-skel-shape { width: 70%; height: 10px; margin: 0 auto; }
/* Same reuse for the product grid — .product-grid/.product-card/.thumb
   already carry the real 1:1 thumbnail aspect ratio and the exact column
   counts per breakpoint (see the .product-grid media queries above), so a
   skeleton card lines up pixel-for-pixel with the real card that replaces
   it. */
.skel-product-grid .product-card { cursor: default; }
.skel-product-grid .thumb.store-skel-shape { border-radius: 0; }
.skel-product-grid .product-card .body { padding: 10px 10px 12px; gap: 6px; display: flex; flex-direction: column; }

/* ---- Initial-load error state ----
   Shown instead of the skeleton if the page's own data fetch fails (never
   left as a blank/half-rendered page). Retry re-runs the same load
   function client-side — no full page reload — so a transient network
   blip doesn't cost a visitor a full reload just to try again. */
#store-load-error { display: none; }
body.store-load-error #store-load-error { display: flex; }
body.store-load-error #store-skeleton { display: none; }
#store-load-error {
  flex-direction: column; align-items: center; text-align: center; gap: 14px;
  max-width: 420px; margin: 100px auto; padding: 36px 28px;
}
#store-load-error .sle-icon {
  width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: var(--store-coral-light); color: var(--store-coral-dark);
}
#store-load-error .sle-icon svg { width: 24px; height: 24px; }
#store-load-error h2 { margin: 0; font-size: 17px; }
#store-load-error p { margin: 0; color: var(--store-text); font-size: 13.5px; line-height: 1.5; }
#store-load-error .btn { margin-top: 4px; }

.quickview-modal .qv-body { display: flex; gap: 20px; text-align: left; }
.quickview-modal .qv-thumb { width: 140px; height: 140px; flex-shrink: 0; border-radius: 12px; overflow: hidden; background: var(--store-placeholder); display: flex; align-items: center; justify-content: center; }
.quickview-modal .qv-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.quickview-modal .qv-info { flex: 1; min-width: 0; }
@media (max-width: 480px) { .quickview-modal .qv-body { flex-direction: column; } .quickview-modal .qv-thumb { width: 100%; height: 180px; } }

/* ---- Small neutral tag chip (e.g. "Combo" label on a cart line) — not a
   sale/discount signal, so it uses the navy-tint treatment, not coral. ---- */
.tag-chip { display: inline-block; font-size: 10px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; color: var(--store-primary-dark); background: var(--store-primary-light); border-radius: 4px; padding: 2px 7px; margin-left: 4px; }

/* ---- Breadcrumb — "Home / Category / Product" trail at the top of
   products.html (category context) and product.html. Small, muted, and
   entirely secondary to the page's own <h1>/product title below it — this
   is an orientation aid (where am I, how do I go back up a level), not a
   heading in its own right, which is why it's plain text links rather than
   anything styled like a button or tab. The current/last item (the page
   you're actually on) is never a link — same convention breadcrumbs always
   follow — so it's rendered as a plain span by the caller. ---- */
.store-breadcrumb { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; font-size: 12.5px; color: var(--store-text); margin-bottom: var(--sp-2, 16px); }
.store-breadcrumb a { color: var(--store-text); font-weight: 600; }
.store-breadcrumb a:hover { color: var(--store-primary); }
.store-breadcrumb .crumb-sep { color: var(--store-border); }
.store-breadcrumb .crumb-current { color: var(--store-ink); font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 60vw; }

/* ---- Listing toolbar (products.html / combos.html) — replaces the old
   plain "form fields in a card" filters-bar with a proper e-commerce
   toolbar: a result count on the left, Sort + a "Filters" toggle on the
   right. The actual filter fields (category/school/size/price/on-page
   search) live in a panel that's collapsed by default and expands beneath
   the toolbar — same fields, same JS-driven load()/storeApi() calls as
   before, just no longer permanently taking up a whole row of vertical
   space above every single product grid regardless of whether anyone
   actually wants to filter. Active filters (once applied) surface as
   removable chips under the toolbar so it's obvious at a glance what's
   currently narrowing the grid, and there's always a one-click way back
   out rather than having to reopen the panel and manually clear each
   field. ---- */
.listing-toolbar {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 12px 16px; margin-bottom: var(--sp-2, 16px); background: var(--store-surface);
  border: 1px solid var(--store-border); border-radius: 14px;
}
/* Real bug fix: this used to be a plain `flex-wrap: wrap` on the row
   above with no other rules — fine most of the time, but once the
   result-count text plus the whole controls group (Sort by + select +
   Filters button) together didn't quite fit on one line, `.toolbar-
   controls` (a single flex item) would wrap onto its own line and land
   wherever `justify-content: space-between` puts a lone wrapped item —
   an abrupt, uncontrolled-looking break at whatever width that happened
   to be, not a designed layout. `.result-count` now shrinks/truncates
   first (min-width: 0 + ellipsis) so the controls — the actual
   interactive part — stay on one line and intact at ordinary desktop/
   laptop/tablet widths; the deliberate, styled stacked layout below only
   kicks in at a specific, tested breakpoint instead of an accidental one. */
.listing-toolbar .result-count {
  font-size: 13px; font-weight: 650; color: var(--store-text);
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.listing-toolbar .result-count strong { color: var(--store-ink); }
.listing-toolbar .toolbar-controls { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.listing-toolbar .sort-field { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--store-text); font-weight: 600; white-space: nowrap; }
.listing-toolbar .sort-field select { padding: 8px 30px 8px 12px; font-size: 13px; border-radius: 9px; background-position: right 10px center; }
.filter-toggle-btn {
  display: inline-flex; align-items: center; gap: 7px; padding: 8px 14px; border-radius: 9px;
  border: 1px solid var(--store-border); background: var(--store-surface); color: var(--store-ink);
  font-size: 13px; font-weight: 650; cursor: pointer; transition: border-color .15s var(--ease), background .15s var(--ease);
}
.filter-toggle-btn:hover { border-color: var(--store-primary); background: var(--store-primary-light); }
.filter-toggle-btn svg { width: 15px; height: 15px; }
.filter-toggle-btn .active-count {
  display: inline-flex; align-items: center; justify-content: center; min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: 99px; background: var(--store-coral); color: #fff; font-size: 10px; font-weight: 800;
}
.filter-toggle-btn.open { border-color: var(--store-primary); background: var(--store-primary-light); color: var(--store-primary-dark); }

.filter-panel {
  display: flex; gap: 14px; flex-wrap: wrap; align-items: end; padding: 18px 20px; margin: -4px 0 var(--sp-2, 16px);
  background: var(--store-surface); border: 1px solid var(--store-border); border-radius: 14px;
}
.filter-panel .field { margin-bottom: 0; min-width: 150px; flex: 1 1 150px; }
.filter-panel .field.narrow { flex: 0 1 110px; min-width: 90px; }

.filter-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: -6px 0 var(--sp-2, 16px); }
.filter-chip {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 8px 5px 12px; border-radius: 99px;
  background: var(--store-primary-light); color: var(--store-primary-dark); font-size: 12.5px; font-weight: 650;
}
.filter-chip button {
  display: inline-flex; align-items: center; justify-content: center; width: 17px; height: 17px; border-radius: 50%;
  border: none; background: rgba(20,41,63,0.12); color: inherit; cursor: pointer; font-size: 12px; line-height: 1; padding: 0;
}
.filter-chip button:hover { background: rgba(20,41,63,0.22); }
.filter-chip.clear-all { background: var(--store-coral-light); color: var(--store-coral-dark); }
.filter-chip.clear-all button { background: rgba(209,72,58,0.14); }
.filter-chip.clear-all button:hover { background: rgba(209,72,58,0.26); }

/* Below this width the controls no longer have comfortable room next to
   the result count even with it shrinking — rather than let that squeeze
   continue (tiny select, cramped Filters button), the toolbar switches to
   a deliberate two-row layout: result count on its own line, Sort +
   Filters sharing the full width underneath so both stay easy to read
   and tap. */
@media (max-width: 760px) {
  .listing-toolbar { flex-wrap: wrap; row-gap: 10px; }
  .listing-toolbar .result-count { flex: 1 1 100%; white-space: normal; overflow: visible; text-overflow: clip; }
  .listing-toolbar .toolbar-controls { flex: 1 1 100%; justify-content: space-between; }
  .listing-toolbar .sort-field { flex: 1 1 auto; min-width: 0; }
  .listing-toolbar .sort-field select { flex: 1 1 auto; min-width: 0; }
}
@media (max-width: 640px) {
  .listing-toolbar { padding: 10px 14px; }
  .listing-toolbar .sort-field span { display: none; }
  /* Slightly taller tap targets now that Sort/Filters are the full-width
     row a thumb actually taps, matching the ~44px comfortable touch-
     target guideline more closely than the desktop 8px vertical padding
     did. */
  .listing-toolbar .sort-field select { padding-top: 10px; padding-bottom: 10px; }
  .filter-toggle-btn { padding-top: 10px; padding-bottom: 10px; }
  .filter-panel { padding: 16px; }
}

/* ---- Order history card list (orders.html) — a card per order (date,
   status pill, total, View button) instead of the old raw HTML <table>,
   which read as a leftover admin-panel data grid rather than something a
   shopper browsing their own purchase history would expect. Same data,
   same /my-orders API response, same order-detail link — only the
   presentation changed, and it reflows naturally on a phone screen the
   way a table with 6 columns never could without horizontal scrolling. ---- */
.order-card-list { display: flex; flex-direction: column; gap: 12px; }
.order-card {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap; padding: 16px 20px;
  background: var(--store-surface); border: 1px solid var(--store-border); border-radius: 14px;
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
}
.order-card:hover { border-color: var(--store-primary); box-shadow: var(--shadow-sm); }
.order-card .oc-id { font-weight: 800; color: var(--store-ink); font-size: 14.5px; min-width: 64px; }
.order-card .oc-meta { flex: 1; min-width: 160px; display: flex; flex-direction: column; gap: 2px; }
.order-card .oc-date { font-size: 12.5px; color: var(--store-text); }
.order-card .oc-pay { font-size: 12px; color: var(--store-text); }
.order-card .oc-total { font-weight: 800; color: var(--store-ink); font-size: 15px; min-width: 90px; text-align: right; }
@media (max-width: 480px) {
  .order-card { padding: 14px 16px; gap: 10px; }
  .order-card .oc-total { text-align: left; }
  .order-card .oc-actions { width: 100%; }
  .order-card .oc-actions .btn { width: 100%; justify-content: center; }
}

/* ---- Sticky mobile "buy bar" (product.html) — Add to Cart / Buy Now
   pinned to the bottom of the viewport on phone/tablet widths, where the
   real buttons further up the page scroll out of view on a page this
   long (gallery + info + trust cards). Desktop already keeps the real
   .pd-cta-row comfortably in view without scrolling far, so this only
   renders under the same 800px breakpoint product.html's own layout
   already collapses to one column at — same buttons, same
   addSelectedToCart() handlers (wired in product.html), just a second,
   fixed-position entry point to them. Page content gets matching bottom
   padding (see product.html) so this bar never covers the last real
   content on the page. ---- */
.pd-mobile-buybar {
  display: none; position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
  background: var(--store-surface); border-top: 1px solid var(--store-border);
  box-shadow: 0 -6px 20px rgba(26,26,26,0.08); padding: 10px 14px; gap: 10px;
  padding-bottom: max(10px, env(safe-area-inset-bottom));
}
@media (max-width: 800px) { .pd-mobile-buybar { display: flex; } }
.pd-mobile-buybar .btn { flex: 1; justify-content: center; padding: 12px 14px; font-size: 14px; }
.pd-mobile-buybar .mb-price { display: flex; flex-direction: column; justify-content: center; flex-shrink: 0; max-width: 92px; }
.pd-mobile-buybar .mb-price .now { font-weight: 800; color: var(--store-ink); font-size: 14.5px; line-height: 1.2; }
.pd-mobile-buybar .mb-price .was { font-size: 10.5px; color: var(--store-text); text-decoration: line-through; }
/* Only product.html's markup includes .pd-mobile-buybar, and it's already
   in the DOM (static markup) before shell.js appends the floating
   WhatsApp button at the very end of <body> — so this sibling selector
   only ever matches on that one page, at the same ≤800px width the buy
   bar itself appears at, lifting the FAB clear above it instead of the
   two overlapping in that bottom-right corner. */
@media (max-width: 800px) { .pd-mobile-buybar ~ .store-whatsapp-fab { bottom: 82px; } }

/* ---- Pagination — real page numbers rather than infinite scroll, so a
   1000+ item catalog stays fast (one bounded page of results per request,
   never "load everything and filter client-side"). Current page + up to
   2 neighbors on each side always show; anything further away collapses
   into a "…" so this never turns into a giant unusable number strip on a
   50-page catalog — see renderPagination() in store/shell.js. ---- */
.pagination { display: flex; align-items: center; justify-content: center; gap: 6px; margin: var(--sp-4, 32px) 0; flex-wrap: wrap; }
.page-btn {
  min-width: 34px; height: 34px; padding: 0 8px; border-radius: 8px; border: 1px solid var(--store-border);
  background: var(--store-surface); color: var(--store-ink); font-size: 13px; font-weight: 650; cursor: pointer;
  display: flex; align-items: center; justify-content: center; transition: background .15s var(--ease), border-color .15s var(--ease);
}
.page-btn:hover:not(:disabled):not(.active) { background: var(--store-bg); border-color: var(--store-primary); }
.page-btn.active { background: var(--store-primary); border-color: var(--store-primary); color: #fff; cursor: default; }
.page-btn:disabled { opacity: .4; cursor: not-allowed; }
.page-ellipsis { color: var(--store-text); font-size: 13px; padding: 0 4px; user-select: none; }
.pagination-info { text-align: center; font-size: 12.5px; color: var(--store-text); margin-top: -14px; margin-bottom: var(--sp-3, 24px); }

/* ---- Footer ----
   Full-bleed wrapper + centered .inner, same pattern as .store-header/
   .store-subnav/.store-trustbar above, so the footer's own tinted
   background spans the whole viewport width instead of stopping at the
   content column. A separate full-bleed .store-footer-bottom strip (dark,
   see below) carries just the copyright line — the light .store-footer
   background above it is exactly where a shop's logo/name renders (same
   light surface the header itself uses), so an existing tenant logo never
   needs a different treatment down here than it already gets up top, and
   never sits against a dark background that could clash with a
   transparent-background image. */
.store-footer { background: var(--store-surface); border-top: 1px solid var(--store-border, var(--border)); margin-top: var(--sp-6, 64px); }
.store-footer > .inner { max-width: var(--store-max); margin: 0 auto; padding: var(--sp-5, 48px) 20px var(--sp-4, 32px); }

.foot-top { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--sp-4, 32px); }
.foot-col-title { font-size: 12.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--store-ink); margin-bottom: 14px; }
/* :not(.foot-contact-btn):not(.foot-contact-item) keeps this plain-link
   styling off (a) the WhatsApp CTA pill button in the brand column, and
   (b) the phone/email <a> rows in the "Get in Touch" column — same
   specificity-guarding pattern as .store-page a:hover:not(:where(.btn))
   above, needed for the same reason: ".foot-col a" (class+tag) would
   otherwise outrank a single class like ".foot-contact-item" and flatten
   its flex row (icon beside text) into a block, pushing the icon onto its
   own line above the text — exactly the phone/email break reported after
   the first icon-sizing fix (the sibling address/hours <div> rows were
   never affected, since this selector only matches <a> tags). */
.foot-col a:not(.foot-contact-btn):not(.foot-contact-item) { display: block; font-size: 13.5px; color: var(--store-text); font-weight: 600; margin-bottom: 11px; }
.foot-col a:not(.foot-contact-btn):not(.foot-contact-item):hover { color: var(--store-primary); text-decoration: none; }

.foot-logo { font-weight: 800; font-size: 18px; color: var(--store-ink); letter-spacing: -0.01em; display: flex; align-items: center; margin-bottom: 12px; }
.foot-logo.has-image img { max-height: 40px; max-width: 180px; width: auto; object-fit: contain; display: block; }
.foot-tagline { font-size: 12.5px; color: var(--store-text); font-style: italic; margin-bottom: 8px; }
.foot-about { font-size: 13px; color: var(--store-text); line-height: 1.55; margin-bottom: 16px; max-width: 320px; }
.foot-cta { margin-top: 2px; }

.trust-badge { display: flex; align-items: center; gap: 10px; font-size: 12.5px; font-weight: 650; color: var(--store-ink); flex: 1 1 200px; }
.trust-badge .icon {
  width: 32px; height: 32px; border-radius: 50%; background: var(--store-primary-light); color: var(--store-primary-dark);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.trust-badge .icon svg { width: 16px; height: 16px; }
.foot-trust {
  display: flex; flex-wrap: wrap; gap: 16px 28px; margin-top: var(--sp-5, 48px);
  padding-top: var(--sp-3, 24px); border-top: 1px solid var(--store-border, var(--border));
}

/* ---- Store Information (footer "Get in Touch" column) ----
   Every piece here is independently optional (see hasContactInfo in
   store/shell.js) — the whole column, and each item/button inside it,
   only ever renders when the shop owner actually set that field, so an
   empty settings.store_address/phone_number/etc. never leaves a gap or an
   empty-looking column here. */
.foot-contact { display: flex; flex-direction: column; gap: 12px; margin-bottom: 14px; }
.foot-contact-item {
  display: flex; align-items: flex-start; gap: 8px; font-size: 12.5px; color: var(--store-text);
  font-weight: 600; line-height: 1.4;
}
a.foot-contact-item:hover { color: var(--store-primary); text-decoration: none; }
.foot-contact-item .icon { width: 16px; height: 16px; flex-shrink: 0; color: var(--store-primary); margin-top: 1px; }
/* The span above sizes the icon's box, but the raw <svg> markup itself
   (see FOOT_ICONS in store/shell.js) carries no width/height attribute of
   its own, so without this rule the browser falls back to its native/
   intrinsic SVG size instead of the 16px box — exactly the blown-up
   phone/mail/pin/clock icons seen in production. Same fix already applied
   to .trust-badge .icon svg and .foot-contact-btn svg below/above; this
   was the one icon spot missing it. */
.foot-contact-item .icon svg { width: 16px; height: 16px; display: block; }
.foot-contact-btn {
  display: inline-flex; align-items: center; gap: 7px; padding: 9px 18px; border-radius: 999px;
  font-size: 12.5px; font-weight: 700; color: #fff; transition: transform .15s var(--ease), box-shadow .15s var(--ease);
}
.foot-contact-btn svg { width: 15px; height: 15px; }
.foot-contact-btn:hover { transform: translateY(-1px); text-decoration: none; color: #fff; }
.foot-contact-btn.whatsapp { background: #25d366; }
.foot-contact-btn.whatsapp:hover { box-shadow: 0 6px 16px rgba(37, 211, 102, .35); }
.foot-contact-btn.maps { background: var(--store-primary); }
.foot-contact-btn.maps:hover { box-shadow: 0 6px 16px rgba(0,0,0,.18); }

/* Bottom copyright bar — the one deliberately DARK band on the storefront
   footer, same --store-primary-dark accent the trust-bar/promo-strip
   already use elsewhere on this page (never a new one-off color). No logo
   or image ever renders here — see the .store-footer comment above for
   why that split keeps a tenant's logo out of any dark-background
   scenario entirely. */
.store-footer-bottom { background: var(--store-primary-dark); color: rgba(255,255,255,.75); }
.store-footer-bottom > .inner {
  max-width: var(--store-max); margin: 0 auto; padding: 16px 20px;
  display: flex; align-items: center; justify-content: center; font-size: 12px; text-align: center;
}

/* Floating WhatsApp button — fixed bottom-right on every storefront page,
   only ever added to the DOM when a WhatsApp Number is set (see
   store/shell.js), so a shop that hasn't set one simply never gets this
   element at all rather than a hidden/disabled button. */
.store-whatsapp-fab {
  position: fixed; right: 20px; bottom: 20px; z-index: 40; width: 54px; height: 54px; border-radius: 50%;
  background: #25d366; color: #fff; display: flex; align-items: center; justify-content: center;
  box-shadow: 0 6px 20px rgba(0,0,0,.22); transition: transform .15s var(--ease), box-shadow .15s var(--ease);
}
.store-whatsapp-fab:hover { transform: translateY(-2px) scale(1.04); box-shadow: 0 10px 26px rgba(0,0,0,.28); color: #fff; }
.store-whatsapp-fab svg { width: 28px; height: 28px; }

/* ---- Contact page (public/store/contact.html) ---- */
.contact-info-grid { display: flex; flex-direction: column; gap: 20px; }
.contact-info-row { display: flex; align-items: flex-start; gap: 14px; }
.contact-info-row .icon {
  width: 38px; height: 38px; border-radius: 10px; flex-shrink: 0;
  background: var(--store-primary-light); color: var(--store-primary-dark);
  display: flex; align-items: center; justify-content: center;
}
.contact-info-row .icon svg { width: 18px; height: 18px; }
.contact-info-label { font-size: 11.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--store-text); margin-bottom: 3px; }
.contact-info-value { font-size: 14.5px; font-weight: 600; color: var(--store-ink); }
a.contact-info-value:hover { color: var(--store-primary); text-decoration: none; }

.auth-divider { display: flex; align-items: center; gap: 12px; margin: 16px 0; color: var(--store-text); font-size: 12px; }
.auth-divider::before, .auth-divider::after { content: ''; flex: 1; height: 1px; background: var(--store-border); }

/* ---- Cards (any .card inside a store page get the storefront's rounder,
   airier treatment instead of the admin panel's tighter boxed card) ---- */
.store-page .card { border-radius: 18px; border-color: var(--store-border); box-shadow: var(--shadow); padding: 24px 26px; background: var(--store-surface); }
.store-page .card + .card { margin-top: -4px; }

/* ---- Auth pages (login / signup) ---- */
.store-auth-wrap { min-height: calc(100vh - 140px); display: flex; align-items: center; justify-content: center; padding: 48px 20px; }
.store-auth-card {
  background: var(--store-surface); border-radius: 22px; padding: 40px 38px; width: 100%; max-width: 420px;
  box-shadow: var(--shadow-lg); border: 1px solid var(--store-border); text-align: center;
}
.store-auth-avatar {
  width: 52px; height: 52px; border-radius: 50%; margin: 0 auto 16px;
  background: linear-gradient(135deg, var(--store-primary), var(--store-primary-dark)); color: #fff;
  display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 19px; letter-spacing: -0.02em;
}
.store-auth-card h1 { font-size: 20px; margin-bottom: 4px; text-align: center; }
.store-auth-card .sub { color: var(--store-text); font-size: 13px; margin-bottom: 24px; text-align: center; }
.store-auth-card form { text-align: left; }
.store-auth-card .btn { width: 100%; justify-content: center; padding: 11px 16px; }
.store-auth-card .switch-line { margin-top: 18px; margin-bottom: 0; text-align: center; }

/* ---- Mini trust row — a small row of honest-capability chips (Cash on
   delivery / Easy returns / Secure checkout), reused on the product detail
   page (right under its Add to Cart / Buy Now buttons) and at checkout
   (right above Place Order) — the two moments a shopper is actually
   deciding whether to commit, which is where this reassurance is useful,
   not just decoration in the footer. ---- */
.mini-trust-row { display: flex; gap: 16px; flex-wrap: wrap; }
.mini-trust-row span { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 650; color: var(--store-text); }
.mini-trust-row svg { width: 15px; height: 15px; color: var(--store-primary); flex-shrink: 0; }

/* ---- Cart ---- */
.cart-line { display: flex; align-items: center; gap: var(--sp-2, 16px); padding: var(--sp-2, 16px) 0; border-bottom: 1px solid var(--store-border); }
.cart-line:last-child { border-bottom: none; }
.cart-line .thumb { width: 58px; height: 58px; border-radius: 12px; flex-shrink: 0; overflow: hidden; background: var(--store-placeholder); display: flex; align-items: center; justify-content: center; }
.cart-line .thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cart-line .info { flex: 1; min-width: 0; }
.cart-line .info .pname { font-weight: 650; font-size: 13.5px; margin-bottom: 2px; color: var(--store-ink); }
.cart-line .info .punit { font-size: 12px; color: var(--store-text); }
.cart-line .stepper { display: flex; align-items: center; gap: 0; border: 1px solid var(--store-border); border-radius: 99px; overflow: hidden; }
.cart-line .stepper button { width: 28px; height: 28px; border: none; background: #fff; cursor: pointer; font-weight: 700; font-size: 14px; color: var(--store-ink); transition: background .15s var(--ease); }
.cart-line .stepper button:hover { background: var(--store-bg); }
.cart-line .stepper span { min-width: 26px; text-align: center; font-size: 13px; font-weight: 650; }
.cart-line .line-total { width: 84px; text-align: right; font-weight: 700; font-size: 13.5px; }
.cart-line .remove-link { font-size: 11.5px; color: var(--store-text); font-weight: 600; background: none; border: none; cursor: pointer; padding: 0; }
.cart-line .remove-link:hover { color: var(--danger); text-decoration: underline; }

.summary-panel { background: var(--store-bg); border-radius: 16px; padding: 18px 20px; margin-top: 4px; }
.summary-panel .cart-total-row.grand { border-top: 1px solid var(--store-border); }

/* ---- Checkout ---- */
.checkout-layout { display: grid; grid-template-columns: 1fr 380px; gap: 24px; align-items: start; }
@media (max-width: 860px) { .checkout-layout { grid-template-columns: 1fr; } }
.checkout-layout .summary-col { position: sticky; top: 90px; }
.checkout-step-label {
  display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: 50%;
  background: var(--store-primary); color: #fff; font-size: 11.5px; font-weight: 700; margin-right: 8px; flex-shrink: 0;
}
.checkout-layout h3 { display: flex; align-items: center; }

.pay-options { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; }
.pay-option { position: relative; }
.pay-option input { position: absolute; opacity: 0; }
.pay-option label {
  display: block; margin: 0; padding: 12px 14px; border: 1.5px solid var(--store-border); border-radius: 12px;
  cursor: pointer; font-size: 13px; font-weight: 650; color: var(--store-ink); text-align: center;
  transition: border-color .15s var(--ease), background .15s var(--ease), box-shadow .15s var(--ease);
}
.pay-option input:checked + label { border-color: var(--store-primary); background: var(--store-primary-light); color: var(--store-primary-dark); box-shadow: 0 0 0 3px rgba(30,58,95,.12); }
.pay-option label:hover { border-color: var(--store-primary); }

/* ---- Order confirmation ---- */
.confirm-check {
  width: 62px; height: 62px; border-radius: 50%; margin: 0 auto 16px; background: var(--success-light);
  color: var(--success); display: flex; align-items: center; justify-content: center; font-size: 30px;
}

/* ---- Order tracker (order-detail.html) ---- */
.tracker { display: flex; align-items: flex-start; margin: 6px 0 22px; }
.tracker-step { flex: 1; text-align: center; position: relative; }
.tracker-step .dot {
  width: 24px; height: 24px; border-radius: 50%; background: var(--store-placeholder); border: 2px solid var(--store-border);
  margin: 0 auto 6px; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700;
  color: var(--store-text); position: relative; z-index: 1;
}
.tracker-step::before { content: ''; position: absolute; top: 11px; left: -50%; width: 100%; height: 2px; background: var(--store-border); z-index: 0; }
.tracker-step:first-child::before { display: none; }
.tracker-step.done .dot { background: var(--store-primary); border-color: var(--store-primary); color: #fff; }
.tracker-step.done::before { background: var(--store-primary); }
.tracker-step.current .dot { background: #fff; border-color: var(--store-primary); color: var(--store-primary); box-shadow: 0 0 0 4px var(--store-primary-light); }
.tracker-step .label { font-size: 11px; font-weight: 650; color: var(--store-text); text-transform: capitalize; }

/* =========================================================================
   MOBILE / TABLET RESPONSIVENESS PASS — CSS ONLY. The storefront already
   had real responsive work in it (product grid column counts, the header's
   640px stack, the category rail, checkout's 860px column collapse, the
   quick-view modal's 480px stack) — this block fills in the specific gaps
   found by walking every store page's actual DOM against every breakpoint
   from 320px (smallest common phone) up through tablet (768–900px) and
   desktop. Nothing here changes any element's class, id, JS behavior, or
   the data it displays — only how existing elements lay out at narrow
   widths. Two real bugs fixed below, not just polish:
     1. .cart-line's five children (thumb/info/stepper/total/remove-link)
        are wide enough, added together, to overflow a ~340px-wide phone
        screen even before the product name gets any room at all — it
        needed an actual mobile layout, not just smaller gaps.
     2. .checkout-layout's summary column carries `position: sticky` for
        its two-column desktop layout; once checkout collapses to one
        column below 860px (existing rule), a sticky element that's also
        the last thing on the page has no scroll room left to visibly
        "stick" against, so it's inert clutter there — turned off below
        exactly where the column collapse already happens, in the same
        breakpoint.
   ========================================================================= */

/* ---- Global small-screen rhythm: less vertical padding than desktop's
   generous 48/64px so a short phone screen isn't mostly whitespace before
   real content appears, and slightly tighter side padding (16px, matching
   the header's own mobile padding above) so cards get a hair more room on
   a 320–375px phone without touching anything wider. ---- */
@media (max-width: 640px) {
  .store-container { padding: 24px 16px 40px; }
  .store-page h1 { font-size: 23px; }
  .store-container h2, .store-page h2 { font-size: 19px; }
}

/* ---- Header: below 380px a text-only shop name (no logo image) plus the
   account/cart actions can be too wide to share the first row even after
   the 640px wrap above puts them on their own row from the search bar —
   give logo and actions each their own full-width row so neither ever
   competes for space with the other, only kicking in this much tighter
   than the existing 640px stack. ---- */
@media (max-width: 380px) {
  .store-header > .inner { row-gap: 6px; }
  .store-header .logo { flex: 1 1 100%; order: 1; }
  .store-header .actions { order: 2; margin-left: 0; flex: 1 1 100%; justify-content: flex-end; }
  .store-header .actions a { font-size: 13px; }
}

/* .mega-menu's mobile-specific position: fixed override used to live here
   — it's now just how .mega-menu always behaves (see the comment on that
   base rule, further up this file), since the overflow it fixed turned
   out to affect far more than just phone-width screens. Nothing
   width-specific needed at this breakpoint any more. */

/* ---- Cart line — real bug fix (see file-header note above): a grid
   layout that keeps the product thumbnail as one tall left-hand cell while
   name/price, the quantity stepper + line total, and Remove each get their
   own row to the right of it, instead of all five elements competing for
   space in a single horizontal row that doesn't fit. Only replaces the
   layout mode (flex -> grid) at this width; every child keeps the exact
   same class, content, and click behavior it already had. ---- */
@media (max-width: 560px) {
  .cart-line {
    display: grid;
    grid-template-columns: 58px 1fr auto;
    grid-template-areas:
      "thumb info   info"
      "thumb stepper total"
      "thumb remove remove";
    column-gap: 12px; row-gap: 6px;
    align-items: center;
  }
  .cart-line .thumb { grid-area: thumb; }
  .cart-line .info { grid-area: info; }
  .cart-line .stepper { grid-area: stepper; justify-self: start; }
  .cart-line .line-total { grid-area: total; justify-self: end; width: auto; }
  .cart-line .remove-link { grid-area: remove; justify-self: end; }
  /* Steppers are the most-tapped control on this page on a touch device —
     28px is under the ~44px comfortable tap-target guideline; bumped up
     slightly here (desktop/mouse users are unaffected, this only applies
     under the same breakpoint as the reflow above). */
  .cart-line .stepper button { width: 32px; height: 32px; }
  /* "Remove" had zero padding (a bare text button) — fine for a mouse
     pointer, a small tap target for a thumb. Padding only, no visual size
     change to the text itself; desktop is unaffected. */
  .cart-line .remove-link { padding: 10px 2px; margin: -10px -2px; }
}

/* ---- Checkout: turn off the summary column's desktop-only sticky
   positioning in the exact same breakpoint where the two-column layout
   above already collapses to one — see the file-header note for why a
   sticky last-child element is pointless once stacked. Card padding is
   also trimmed slightly so three stacked cards (contact, payment, coupon)
   plus the summary don't feel oversized on a narrow phone. ---- */
@media (max-width: 860px) {
  .checkout-layout .summary-col { position: static; top: auto; }
}
@media (max-width: 480px) {
  .store-page .card { padding: 18px 16px; }
}

/* =========================================================================
   MOBILE UI POLISH PASS — visual refinement only, CSS-only, layered on top
   of the responsive pass above (that pass already made every page fit
   without overflow; this one is purely about the cramped/basic FEEL
   reported at 320–414px). Every value below reuses this file's own design
   tokens (--radius/--radius-lg/--shadow-sm/--sp-*) rather than inventing
   new ones, and every rule lives inside an existing max-width breakpoint —
   nothing here changes layout mode, adds markup, or touches anything above
   640px, so desktop is byte-for-byte unaffected. ========================= */

@media (max-width: 640px) {
  /* Header — a touch more breathing room top/bottom and between the logo/
     actions row and the search row below it (was a flat 10px everywhere,
     which reads as cramped once the header is 3 stacked rows instead of
     1). Search input/button grow slightly too, so the search bar reads as
     a real primary action instead of a thin strip. */
  .store-header > .inner { padding: 12px 16px 14px; gap: 12px; }
  .store-search input { padding: 12px 14px; font-size: 14.5px; }
  .store-search button { width: 48px; }
  .store-header .cart-link svg { width: 24px; height: 24px; }

  /* Sub-nav — slightly more room between links (was 18px, felt tight once
     the tap-target padding was added), plus a hairline shadow under the
     strip so it reads as its own distinct band sitting above the page
     content rather than blending into it. The active link additionally
     gets a soft pill background here (mobile only) since the desktop
     underline treatment is harder to notice at a glance while scrolling a
     horizontal strip with a thumb. */
  .store-subnav { box-shadow: 0 2px 6px rgba(20,30,40,0.05); }
  .store-subnav > .inner { gap: 22px; }
  .store-subnav a.active { background: rgba(255,255,255,0.65); border-radius: 8px; border-bottom-color: transparent; }

  /* Breadcrumb/title — a little more separation from the content below so
     the page title reads as a clear heading, not squeezed against the
     first row of filters/products. */
  .store-breadcrumb { margin-bottom: 12px; }
  .store-page h1 { margin-bottom: 14px; }

  /* Section rhythm — tighter than desktop's 40px gap (avoids the "mostly
     empty space" feel on a short phone screen) while keeping sections
     visually distinct via their own heading + accent bar, not raw
     whitespace. */
  .home-layout { gap: 30px; }
  .section-head { margin-bottom: 14px; }
}

/* ---- Product cards — the two-column grid itself is unchanged (see the
   560px rule earlier in this file); this only gives each card more
   deliberate internal spacing/typography so a 150px-wide mobile card
   reads as a considered product tile instead of a shrunk desktop card.
   Border radius steps up to --radius-lg (12px, the same token already
   used for hero/cards elsewhere) instead of the base 10px, for a slightly
   softer, more "app-like" corner at this size. ---- */
@media (max-width: 560px) {
  .product-grid { gap: 12px; }
  .product-card { border-radius: var(--radius-lg, 12px); }
  .product-card .body { padding: 12px 12px 4px; }
  .product-card .card-actions:not(:empty) { padding: 0 12px 12px; }
  .product-card .pname { font-size: 13px; }
  /* Price is the single most important line on a product card — bumped up
     from 14.5px so it visibly outweighs the name/category text above it,
     with the "was" price kept small and muted so the hierarchy (name <
     was-price < now-price) is unambiguous at a glance. */
  .product-card .pprice { font-size: 15.5px; }
  .product-card .price-was { font-size: 11px; }
  .product-card .discount-badge { top: 10px; left: 10px; }
  /* The floating Add to Cart button is this card's one primary action on
     a touch device (there's no hover state to reveal a hidden one) —
     34px was workable but tight; 38px reads as a real tap target and a
     more deliberate, app-like control without covering much more of the
     photo underneath it. */
  .product-card .thumb .add-cart-btn { width: 38px; height: 38px; bottom: 10px; right: 10px; }
}

/* ---- Footer: columns stack to one per row below tablet width (the grid's
   own auto-fit already reduces the column count as space gets tighter, so
   this is really only the "make it fully single-column and centered" step
   for phones), and the trust badges below them settle into a neat 2-up
   grid instead of unevenly wrapping. ---- */
@media (max-width: 720px) {
  .foot-top { grid-template-columns: 1fr; gap: var(--sp-4, 32px); text-align: center; }
  .foot-col a { text-align: center; }
  .foot-about { margin-left: auto; margin-right: auto; }
  .foot-logo { justify-content: center; }
  .foot-contact { align-items: center; }
  .foot-contact-item { text-align: left; }
  .foot-cta { margin: 0 auto; }
  .foot-trust { justify-content: center; text-align: left; }
  .trust-badge { flex: 1 1 45%; }
}
@media (max-width: 480px) {
  .store-whatsapp-fab { right: 14px; bottom: 14px; width: 48px; height: 48px; }
  .store-whatsapp-fab svg { width: 24px; height: 24px; }
}

/* ---- Auth cards (store login/signup AND admin login share this same
   narrow-viewport concern) — desktop padding (36–40px) is comfortable on
   a phone-width card too, but on the smallest real phones (≤360px) it
   starts eating into the already-tight input width. Trimmed only at that
   width; the card's centering/shadow/every other rule is untouched. ---- */
@media (max-width: 360px) {
  .store-auth-card { padding: 28px 22px; }
  .login-card { padding: 24px 20px; }
}

/* ---- Order tracker (order-detail.html) — 4 steps (Pending/Confirmed/
   Shipped/Delivered) fit fine down to ~360px given the page's own 560px
   container cap, but "Confirmed"/"Delivered" are the two longest labels
   and start crowding their neighbors below that. Smaller label type and a
   smaller dot keep all 4 steps comfortably on one row instead of letting
   labels collide, on the narrowest phones only. ---- */
@media (max-width: 360px) {
  .tracker-step .dot { width: 20px; height: 20px; font-size: 10px; }
  .tracker-step::before { top: 9px; }
  .tracker-step .label { font-size: 9.5px; }
}

/* ---- Product detail page (public/store/product.html) — the page's own
   inline stylesheet already collapses .pd-layout to one column at 800px;
   this only tightens the image and gallery-thumbnail sizing a little
   further on true phone widths so the main photo doesn't dominate the
   whole first screen before any product info is visible. ---- */
@media (max-width: 480px) {
  /* product.html's own inline <style> block (loaded after this file, same
     0-1-1 specificity as a bare .pd-thumb-pick) would otherwise win this
     tie by source order and silently ignore this override — the extra
     body.store-page qualifier here is only to safely out-specify that,
     not a meaningful selector change. */
  body.store-page .pd-thumb-pick { width: 56px; height: 56px; }
}
.tracker-step.done .label, .tracker-step.current .label { color: var(--store-ink); }

/* =========================================================================
   ADMIN PANEL — premium SaaS dashboard redesign (Stripe / Linear / Vercel-
   inspired). Replaces the earlier flatter, all-white "Shopify" pass —
   client feedback was that version read as too plain/generic; this keeps
   the same principle (frontend/CSS only, scoped under `body.admin-page`,
   zero HTML/JS/backend changes) but restores a dark sidebar, brings back
   real depth via soft layered shadows instead of flat hairlines, and adds
   the small typographic/motion details ("premium" is mostly these details,
   not a color swap): tabular numerals on stat values, a translucent
   blurred sticky topbar, lift-on-hover cards/buttons, refined focus rings.
     - Still purely additive to the token system already used everywhere
       (--bg, --surface, --border, --radius, --shadow*, --space-*) — almost
       every component (.card, .btn, table, input, .page-content) already
       reads these via var(...), so most of the "redesign" below is just
       new values for the same variables. Explicit component overrides
       only exist where a rule hardcoded a color instead of a token.
     - The sidebar deliberately goes back to `var(--brand-dark)` /
       `var(--brand-darker)` (the original mechanism, one level below)
       instead of a fixed color, specifically so the existing Settings ->
       Admin Panel Theme picker (Navy/Forest/Sunset/Indigo,
       body.admin-page[data-admin-theme="…"] earlier in this file) keeps
       recoloring the whole sidebar correctly, unchanged. Buttons/focus
       rings are brand-driven the same way, for the same reason.
     - Cannot swap icon sets from CSS alone (every icon is an inline SVG
       string in shell.js/index.html, off-limits under "don't touch JS") —
       "better icons" here means refining the existing icon chips
       (.stat-card-icon, .nav-icon) themselves: size, contrast, tinted
       backgrounds, subtle depth.
     - Storefront (.store-page) and the platform-admin panel (its own
       separate stylesheet) cannot be reached by this selector.
   ========================================================================= */
body.admin-page {
  --bg: #f7f8fa;
  --surface: #ffffff;
  --border: rgba(16, 24, 40, 0.07);
  --border-strong: rgba(16, 24, 40, 0.14);
  --text: #101828;
  --text-muted: #667085;
  --radius: 10px;
  --radius-lg: 14px;
  --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.04);
  --shadow: 0 1px 3px rgba(16, 24, 40, 0.06), 0 1px 2px rgba(16, 24, 40, 0.04);
  --shadow-md: 0 4px 12px rgba(16, 24, 40, 0.08), 0 2px 4px rgba(16, 24, 40, 0.04);
  --shadow-lg: 0 16px 40px rgba(16, 24, 40, 0.14), 0 4px 10px rgba(16, 24, 40, 0.05);
  --space-1: 10px;
  --space-2: 18px;
  --space-3: 26px;
  --space-4: 34px;
  background: var(--bg);
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  letter-spacing: 0.01em;
  font-feature-settings: 'cv11', 'ss01';
}
body.admin-page h1, body.admin-page h2, body.admin-page h3, body.admin-page h4 {
  letter-spacing: -0.015em; color: var(--text);
}

/* ---- Sidebar — kept dark (brand-gradient, same mechanism as before so
   every admin theme preset still recolors it correctly), refined with
   real separation from the content area (a soft directional shadow, not
   just a flat edge), calmer/more legible resting-link color, a glowing
   accent-bar active state, and smooth hover motion instead of an instant
   background snap. ---- */
body.admin-page .sidebar {
  background: linear-gradient(160deg, var(--brand-dark) 0%, var(--brand-darker) 100%);
  box-shadow: 1px 0 0 rgba(0, 0, 0, 0.14), 8px 0 24px -8px rgba(0, 0, 0, 0.35);
  position: relative; z-index: 10;
}
body.admin-page .sidebar .brand { letter-spacing: -0.01em; }
body.admin-page .sidebar a {
  color: rgba(255, 255, 255, 0.68);
  transition: background .15s var(--ease), color .15s var(--ease), transform .1s var(--ease);
}
body.admin-page .sidebar a:hover { background: rgba(255, 255, 255, 0.08); color: #fff; transform: translateX(1px); }
body.admin-page .sidebar a.active {
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
  box-shadow: inset 3px 0 0 0 currentColor, inset 0 0 0 1px rgba(255, 255, 255, 0.06);
  font-weight: 650;
}
body.admin-page .nav-icon { opacity: 0.75; }
body.admin-page .sidebar a:hover .nav-icon, body.admin-page .sidebar a.active .nav-icon { opacity: 1; }
body.admin-page .nav-group-header { color: rgba(255, 255, 255, 0.55); transition: background .15s var(--ease), color .15s var(--ease); }
body.admin-page .nav-group-header:hover { background: rgba(255, 255, 255, 0.07); color: rgba(255, 255, 255, 0.92); }
body.admin-page .nav-badge { box-shadow: 0 0 0 2px var(--brand-dark); }

/* ---- Topbar — translucent + blurred while sticky (Linear/Vercel-style
   "floating over content" header), a soft shadow instead of a flat
   border for real depth, and a slightly livelier account-menu hover. ---- */
body.admin-page .topbar {
  background: rgba(255, 255, 255, 0.82);
  backdrop-filter: saturate(180%) blur(10px);
  -webkit-backdrop-filter: saturate(180%) blur(10px);
  border-bottom: none;
  box-shadow: 0 1px 0 rgba(16, 24, 40, 0.06), 0 8px 24px -16px rgba(16, 24, 40, 0.15);
}
body.admin-page .topbar h1 { letter-spacing: -0.02em; }
body.admin-page .account-trigger { transition: background .15s var(--ease); }
body.admin-page .account-trigger:hover { background: rgba(16, 24, 40, 0.05); }
body.admin-page .account-dropdown { box-shadow: var(--shadow-lg); border-color: var(--border); border-radius: 12px; }
body.admin-page .topbar-logo { box-shadow: var(--shadow-sm); }

/* ---- Cards / stat cards — soft layered shadow instead of a flat border
   for definition (a hint of border remains, mostly for crisp edges on
   low-DPI screens), gentle lift-on-hover, tabular numerals on the big
   stat values so a changing number never visually jitters, and a refined
   icon chip (tinted background + a whisper of depth) standing in for
   "better icons" within a CSS-only constraint. ---- */
body.admin-page .card, body.admin-page .stat-card {
  border-color: var(--border);
  box-shadow: var(--shadow);
  transition: box-shadow .18s var(--ease), transform .18s var(--ease), border-color .18s var(--ease);
}
body.admin-page .stat-card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); border-color: var(--border-strong); }
body.admin-page .card.quiet { border-color: var(--border); box-shadow: none; }
body.admin-page .stat-card .value, body.admin-page .hero-stat-value { font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
body.admin-page .stat-card .label { letter-spacing: 0.07em; }
body.admin-page .stat-card-icon { box-shadow: inset 0 0 0 1px rgba(16, 24, 40, 0.04); }
body.admin-page .dash-hero-card { box-shadow: var(--shadow-lg); }
body.admin-page .empty-state-icon, body.admin-page .account-avatar { box-shadow: inset 0 0 0 1px rgba(16, 24, 40, 0.04); }

/* ---- Buttons — brand-driven (not a fixed neutral) so every admin theme
   preset still produces a correctly-colored primary action; adds the
   soft colored shadow + hover-lift language Stripe/Linear/Vercel buttons
   share, real transitions instead of instant state changes, and a
   slightly wider, more confident touch target. ---- */
body.admin-page .btn {
  padding: 9px 17px; border-radius: 9px;
  transition: background .15s var(--ease), border-color .15s var(--ease), box-shadow .18s var(--ease), transform .12s var(--ease), color .15s var(--ease);
}
body.admin-page .btn:hover { box-shadow: var(--shadow-sm); }
body.admin-page .btn-primary {
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.1), 0 0 0 1px rgba(16, 24, 40, 0.02);
}
body.admin-page .btn-primary:hover { box-shadow: var(--shadow-md); transform: translateY(-1px); }
body.admin-page .btn-primary:active { transform: translateY(0); }
body.admin-page .btn:focus-visible, body.admin-page input:focus, body.admin-page select:focus, body.admin-page textarea:focus {
  /* Plain rgba fallback first for browsers without color-mix() support;
     modern browsers apply the color-mix() line right after it instead,
     which tints the ring with whichever brand color the current admin
     theme preset has set, rather than a fixed hardcoded hue. */
  outline: none; box-shadow: 0 0 0 3.5px rgba(30, 58, 95, 0.18);
  box-shadow: 0 0 0 3.5px color-mix(in srgb, var(--brand) 18%, transparent);
}
body.admin-page input:focus, body.admin-page select:focus, body.admin-page textarea:focus { border-color: var(--brand); }

/* ---- Forms — more breathing room, softer resting border, refined
   placeholder tone, so a stacked form reads as spacious rather than a
   dense data-entry grid. ---- */
body.admin-page .field { margin-bottom: 18px; }
body.admin-page input, body.admin-page select, body.admin-page textarea {
  border-color: var(--border-strong); padding: 9px 13px;
  transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
}
body.admin-page input::placeholder, body.admin-page textarea::placeholder { color: #98a2b3; }
body.admin-page label { letter-spacing: 0.02em; }

/* ---- Tables — quieter, border-led header (no fill) with letter-spaced
   caps, generous row padding, a soft shadow-and-radius wrapper instead of
   a hard boxed border, and a gentle row hover — closer to how Stripe/
   Linear present tabular data than a dense spreadsheet grid. ---- */
body.admin-page th, body.admin-page td { padding: 14px 16px; }
body.admin-page th { background: transparent; color: var(--text-muted); letter-spacing: 0.06em; border-bottom-color: var(--border-strong); }
body.admin-page .table-wrap { border-color: var(--border); box-shadow: var(--shadow); }
body.admin-page tbody tr { transition: background .12s var(--ease); }
body.admin-page tbody tr:hover { background: rgba(16, 24, 40, 0.025); }

/* ---- Badges — refined pill proportions with a hairline ring for a touch
   more definition against light backgrounds, unchanged semantic colors. ---- */
body.admin-page .badge { border-radius: 999px; font-weight: 650; box-shadow: inset 0 0 0 1px rgba(16, 24, 40, 0.04); }

/* ---- Page content — generous, responsive outer padding: compact on
   phones/tablets so nothing feels cramped-then-suddenly-huge, opening up
   progressively on wider screens for the airy, uncluttered feel a
   premium dashboard reads as. Mirrors the breakpoints already used
   elsewhere in this file rather than inventing new ones. ---- */
body.admin-page .page-content { padding: 22px; }
@media (min-width: 700px) { body.admin-page .page-content { padding: var(--space-4); } }
@media (min-width: 1100px) { body.admin-page .page-content { padding: 40px 48px; } }

/* ---- Motion baseline — every interactive surface above transitions
   smoothly rather than snapping; respected only where already applied,
   nothing here changes layout or breakpoints, so responsiveness is
   unaffected. ---- */
@media (prefers-reduced-motion: reduce) {
  body.admin-page .sidebar a, body.admin-page .stat-card, body.admin-page .card,
  body.admin-page .btn, body.admin-page tbody tr { transition: none; }
}

/* =====================================================================
   Client Admin DASHBOARD — page-exclusive redesign (public/admin/index.html)
   =====================================================================
   Scoping rule, same technique already used for the marketing homepage
   (body.mkt-home): index.html adds `dashboard-page` to <body> itself
   (see its inline script) alongside the shared `admin-page` class shell.js
   always sets. Two kinds of rules follow:
   1. Brand-new classes this redesign introduces (.dashboard-secondary-grid,
      .quick-actions-grid, .quick-action-btn, .skeleton-stat-card,
      .skeleton-quick-action) — these don't exist anywhere else in the
      codebase, so they're written unscoped; there is nothing for them to
      collide with.
   2. Enhancements to classes shared with Products/Orders/POS/Customers/
      Reports/Settings/import.html (.stat-card, .card.quiet, .dash-hero-card,
      .attention-row, .getting-started-item) — every one of these is
      prefixed `body.dashboard-page` so the base rule those other pages
      read is completely untouched; only this page's own instances pick up
      the new look. ===================================================== */

/* ---- Hero stat card: richer depth (dual radial glow + faint grid
   texture), a touch more internal spacing, and a subtle press-affordance
   on the linked value — still the single largest, most confident element
   on the page, just with more of the polish Stripe/Linear give their one
   "headline number" cards. ---- */
body.dashboard-page .dash-hero-card {
  padding: 30px var(--space-4);
  background:
    radial-gradient(circle at 0% 0%, rgba(255,255,255,.10) 0%, transparent 55%),
    linear-gradient(135deg, var(--brand) 0%, var(--brand-dark) 100%);
  box-shadow: var(--shadow-lg), 0 0 0 1px rgba(255,255,255,.04) inset;
}
body.dashboard-page .dash-hero-card::after {
  width: 260px; height: 260px; top: -70px; right: -70px;
  background: radial-gradient(circle, rgba(232,93,78,.16) 0%, transparent 70%);
}
body.dashboard-page .dash-hero-icon {
  background: rgba(255,255,255,.16); box-shadow: inset 0 0 0 1px rgba(255,255,255,.14);
  transition: transform .18s var(--ease);
}
body.dashboard-page .hero-stat-value { transition: opacity .15s var(--ease); }
body.dashboard-page .hero-stat-value:active { opacity: .8; }

/* ---- Secondary KPI row: hover-lift with a slightly stronger shadow and
   a small icon-chip scale, giving these cards the same "alive under the
   cursor" feel Stripe's dashboard cards have — Products/Reports/import.html
   keep their existing plainer .stat-card hover exactly as-is since this
   only fires under body.dashboard-page. ---- */
body.dashboard-page .stat-card {
  border-radius: 14px;
  transition: box-shadow .18s var(--ease), transform .18s var(--ease), border-color .18s var(--ease);
}
body.dashboard-page .stat-card:hover {
  box-shadow: var(--shadow-lg); transform: translateY(-3px); border-color: var(--border-strong);
}
body.dashboard-page .stat-card:hover .stat-card-icon { transform: scale(1.07); }
body.dashboard-page .stat-card-icon { transition: transform .18s var(--ease); }
body.dashboard-page .stat-card .value { font-size: 26px; }

/* ---- Skeleton placeholders for the KPI row (previously this row simply
   stayed empty until data arrived — now it shimmers in sync with the rest
   of the page, then real cards replace these on the very same #stat-row
   element via .innerHTML). Reuses the existing global .skeleton system
   as-is, just gives the placeholder card a fixed presence. ---- */
body.dashboard-page .skeleton-stat-card { display: flex; flex-direction: column; }

/* ---- New: Sales Trend chart + Quick Actions row. Two new cards sitting
   between the KPI row and the Recent Orders / Needs Attention grid — the
   chart reuses WskCharts (already used on Reports) inside the standard
   .card.quiet / .wsk-chart containers, so it inherits chart theming for
   free; only the column proportions and card spacing below are new. ---- */
.dashboard-secondary-grid {
  display: grid; grid-template-columns: 1.6fr 1fr; gap: var(--space-3); align-items: stretch; margin-bottom: var(--space-3);
}
.dashboard-secondary-grid .card { margin-bottom: 0; }
#chart-sales-trend { min-height: 180px; }

.quick-actions-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.quick-action-btn {
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
  padding: 14px; border-radius: var(--radius); border: 1px solid var(--border);
  color: inherit; text-decoration: none; background: var(--surface);
  transition: border-color .15s var(--ease), background .15s var(--ease), transform .15s var(--ease), box-shadow .15s var(--ease);
}
.quick-action-btn:hover {
  border-color: var(--border-strong); background: #f8fafb; transform: translateY(-2px);
  box-shadow: var(--shadow-sm); text-decoration: none; color: inherit;
}
.quick-action-icon {
  width: 30px; height: 30px; border-radius: 8px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--brand-light); color: var(--brand-dark);
  transition: transform .15s var(--ease);
}
.quick-action-icon svg { width: 16px; height: 16px; }
.quick-action-btn:hover .quick-action-icon { transform: scale(1.08); }
.quick-action-label { font-size: 13px; font-weight: 650; }
.skeleton-quick-action { display: flex; flex-direction: column; align-items: flex-start; }

/* ---- Recent Orders / Needs Attention: same hover-lift language as the
   KPI row above, applied only where those widgets live on this page. ---- */
body.dashboard-page .dashboard-grid .card.quiet { transition: box-shadow .18s var(--ease); }
body.dashboard-page .attention-row { border-radius: 8px; padding-left: 8px; padding-right: 8px; transition: background .15s var(--ease), transform .12s var(--ease); }
body.dashboard-page .attention-row:hover { transform: translateX(2px); }
body.dashboard-page .getting-started-item:hover { transform: translateY(-1px); box-shadow: var(--shadow-sm); }

/* ---- Responsive: tablet stacks the chart/quick-actions row before the
   existing 900px breakpoint already used by .dashboard-grid, so both
   two-column areas collapse to one column at consistent widths; phones
   drop the KPI row to two-up and shrink the hero number so it never
   wraps awkwardly. ---- */
@media (max-width: 980px) {
  .dashboard-secondary-grid { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
  body.dashboard-page .stat-grid { grid-template-columns: repeat(2, 1fr); }
  body.dashboard-page .hero-stat-value { font-size: 36px; }
  .quick-actions-grid { grid-template-columns: 1fr; }
}

/* =====================================================================
   Store Appearance (public/admin/settings.html "Store Appearance" card)
   =====================================================================
   All brand-new class names — nothing here exists on any other admin
   page, so unlike the dashboard block above there's no shared-class
   collision risk and no body.<page>-page scoping needed. ===================================================== */
.appearance-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--space-3); margin-top: var(--space-2); }
.appearance-field { display: flex; flex-direction: column; }
.appearance-field-head { margin-bottom: 8px; }
.appearance-field-head label { display: block; font-weight: 650; font-size: 13.5px; margin-bottom: 3px; }
.appearance-dims { display: block; font-size: 11.5px; color: var(--text-muted); line-height: 1.4; }

.appearance-dropzone {
  position: relative; width: 100%; border-radius: var(--radius-lg);
  border: 1.5px dashed var(--border-strong); background: #f8fafb;
  overflow: hidden; cursor: pointer; display: flex; align-items: center; justify-content: center;
  transition: border-color .15s var(--ease), background .15s var(--ease);
}
.appearance-dropzone:hover, .appearance-dropzone:focus-visible { border-color: var(--brand); background: var(--brand-light); outline: none; }
.appearance-dropzone.drag-over { border-color: var(--accent); background: var(--accent-light); }
.appearance-file-input { position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%; }
.appearance-preview-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; background: #fff; pointer-events: none; }
.appearance-empty {
  display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center;
  padding: 18px; color: var(--text-muted); font-size: 12.5px; pointer-events: none;
}
.appearance-empty-icon { width: 26px; height: 26px; color: var(--border-strong); }
.appearance-pending-badge {
  position: absolute; top: 8px; left: 8px; background: var(--accent-dark); color: #fff;
  font-size: 10.5px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 999px; pointer-events: none; box-shadow: var(--shadow-sm);
}

.appearance-progress { height: 5px; border-radius: 999px; background: var(--border); margin-top: 8px; overflow: hidden; }
.appearance-progress-bar { height: 100%; width: 0%; background: var(--brand); border-radius: 999px; transition: width .15s var(--ease); }

.appearance-actions { display: flex; align-items: center; gap: 8px; margin-top: 10px; }

@media (max-width: 560px) {
  .appearance-grid { grid-template-columns: 1fr; }
}
