/* ==========================================================================
   Naveed Mobiles — design system
   Light only. Navy primary, one accent.
   Elevation is hairlines, not shadows.
   ========================================================================== */

/* --------------------------------------------------------------- fonts ---
   Design §3.1/§8: Cabinet Grotesk (display), Geist Sans (body), Geist Mono
   (data), Noto Nastaliq Urdu. Subset and SELF-HOSTED — no Google Fonts, no
   build step, one fewer third-party connection on a slow 4G handshake.

   The woff2 files are not in the repo yet. Until they are, the stacks below
   fall back to system faces that hold the design intent (the mono fallbacks
   all support tabular numerals, which is what §3.2 actually depends on).
   See docs/FONTS.md for exact sources and the @font-face block to paste here.
   -------------------------------------------------------------------------- */

:root {
  /* ---------- Brand (sampled from the logo) ---------- */
  --navy:       #252265;
  --navy-deep:  #1A1847;
  --navy-soft:  #4A4790;

  /* Navy is a GRADIENT now, not a flat fill (owner request). Every dark-blue
     surface on the site pulls from these four so the depth stays consistent
     and there is one place to retune it. Kept as separate ramps because the
     shapes differ: a button is small and reads best on a diagonal, a full
     bleed bar or footer wants the light edge at the top so the band deepens
     as it falls down the page. Measured, so the gradient never crosses the
     contrast floor mid-surface: every light-theme stop below holds white text
     between 9.06:1 and 18.25:1. The dark-theme ramp further down runs lighter
     and bottoms out at 5.63:1 on its hover stop, which still clears AA. */
  --navy-grad:       linear-gradient(135deg, #322D8A 0%, #1A1847 100%);
  --navy-grad-hover: linear-gradient(135deg, #3F39A2 0%, #262363 100%);
  /* The ramp for a horizontal band. It was `--navy-grad-bar` and belonged to
     the navy filter bar; that bar was removed on 2026-08-23 and the token went
     with it, on the grounds that an unused token is an invitation to bring
     back the thing it was for.

     It is back the same day under a new name and a new owner: the social strip
     under the header (owner request). Renamed rather than restored, because
     the name should say what actually uses it — a future reader finding
     "grad-bar" would go looking for a filter bar that no longer exists.

     Same 180deg direction and stops as the footer, so the page is bracketed by
     two navy bands that deepen the same way instead of pointing opposite. */
  --navy-grad-social: linear-gradient(180deg, #2E2A78 0%, #1B1950 100%);
  --navy-grad-foot:  linear-gradient(180deg, #262363 0%, #131130 100%);

  /* !! CONTRAST TRAP — READ BEFORE USING (design §2.1) !!
     --cyan is 3.22:1 on white. It FAILS WCAG AA for body text.
     Use it ONLY for: headings 24px+, icon fills, active-state backgrounds,
     decorative rules, the logo.
     For ANY cyan text at body size, and for ALL links, use --cyan-ink. */
  --cyan:       #049DD8;
  /* Darkened from #027AAB (4.80:1) for the high-contrast pass. 6.04:1 on
     white, 5.76:1 on --surface — clears AA for body text with room spare. */
  --cyan-ink:   #026A94;
  /* The neutral that replaced the old light-blue wash. Grey, never blue —
     the same value the header's hover already hardcodes. */
  --state-tint: #EBEEF4;
  /* Was #FFFFFF, back when the PAGE was grey and a white fill was itself the
     state change. The page is white now (owner request, 2026-08-22), so a
     white fill is no fill: hover and selected would both be invisible on every
     surface, not just on cards. The two tokens have therefore CONVERGED —
     everything sits on white now, so there is only one state fill left. Kept
     as an alias rather than find-and-replaced across four files: the name
     still says *why* a rule reaches for it, and if the page ever leaves white
     the distinction comes back by editing one line. */
  --cyan-wash:  var(--state-tint);

  /* ---------- Text ----------
     High-contrast pass: every value below was pushed darker. The old ramp
     cleared AA; this one clears AAA for body text (7:1) and gives the page
     the harder edge the flat white surfaces need to not look washed out. */
  --ink:        #0D0D14;   /* 18.9:1 on white. Still never #000000. */
  --ink-soft:   #3F4354;   /* was #55596B — 9.9:1 on white */
  /* Darkened from the design's #8A8FA3, which measures 2.9:1 and so fails the
     4.5:1 floor §10 itself mandates. It carries captions and hints ("Choose an
     option to continue") — meaningful text, not decoration — so the floor wins
     over the swatch. Pushed again for the high-contrast pass, from #686C7E
     (4.69:1 on --surface) to 6.53:1 on --surface, 6.86:1 on white. */
  --ink-faint:  #5A5F72;

  /* ---------- Surfaces ----------
     A cool neutral, NOT the logo's cream #F9F3E2. Cyan on that cream is
     2.90:1, failing even the large-text threshold — it would make half the
     brand palette unusable on the shop's own background.

     WHITE, at the owner's request (2026-08-22): body and header the same
     white, everywhere on the site. This supersedes the "page is deliberately
     a SHADE off white" rule that used to stand here, and it has one real
     consequence that had to be paid for rather than ignored — that half-step
     between #EEF0F6 and #FFFFFF was the ONLY thing separating a card from the
     page after the minimalist pass removed card borders. White on white is
     nothing, so the hairline border is back on .card and .pcard. Cards are
     edges again, not tone. */
  --surface:        #FFFFFF;
  --surface-raised: #FFFFFF;
  --hairline:       #D2D6E0;

  /* ---------- Status: SEMANTIC ONLY, never decorative ----------
     The moment amber appears as decoration, the trust position collapses. */
  --status-ok:  #17705E;
  --status-low: #9C5A0C;
  --status-out: #96303A;

  /* ---------- Type ---------- */
  --font-display: "Cabinet Grotesk", "Geist Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-body:    "Geist Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono:    "Geist Mono", ui-monospace, "Cascadia Mono", Consolas, "SF Mono", Menlo, monospace;
  --font-urdu:    "Noto Nastaliq Urdu", serif;

  /* ---------- Shape: one radius scale, everywhere ---------- */
  --r-sm: 6px;    /* inputs, chips, badges */
  --r-md: 10px;   /* cards, buttons */
  --r-lg: 16px;   /* sheets, modals, hero tiles */

  --container: 1280px;

  /* ---------- Motion: intensity 4 (design §7) ----------
     Feedback and hierarchy, never spectacle. Heavy motion janks on mid-range
     Android over 4G, and jank reads as cheap, which reads as fake. */
  --ease-out:  cubic-bezier(0.16, 1, 0.3, 1);
  --dur-fast:  150ms;   /* filter cross-fade */
  --dur-base:  200ms;   /* cart badge, card hover */
  --dur-slow:  320ms;   /* card hover elevation */
  --rise:      24px;    /* section entry */

  /* Real shadows are reserved for genuinely floating things: the mobile filter
     sheet, the cart drawer, dropdowns. Everything else separates by hairline. */
  --shadow-float: 0 12px 32px rgba(26, 24, 71, 0.16);

  /* ---------- Glass (design request) ----------
     Frosted surfaces, applied ONLY to things that genuinely float over content:
     the sticky header, the filter bar, dropdown panels, the mobile filter
     sheet. Deliberately NOT applied to product cards — a card sits on the page
     rather than over it, and blurring 24 of them on a category page is real
     GPU cost on the mid-range Android this design targets, for no depth cue.

     The tint is opaque enough that text contrast is carried by the tint itself,
     never by the blur. That matters: `backdrop-filter` is unsupported or
     disabled often enough (Firefox with the pref off, low-power modes, older
     WebViews) that anything relying on it for legibility fails silently. Check
     contrast against --glass-*-solid, which is what those browsers actually get. */
  --glass-bg:        rgba(255, 255, 255, 0.72);
  --glass-bg-solid:  #F7F8FB;              /* the no-backdrop-filter fallback */
  --glass-border:    rgba(255, 255, 255, 0.55);
  --glass-blur:      blur(14px) saturate(150%);
  /* On the navy header/filter bar the glass is a dark tint, not a light one. */
  --glass-navy:      rgba(26, 24, 71, 0.72);
  --glass-navy-solid:#1F1D52;

  /* ---------- Fixed light header (design request) ----------
     Kept as its own token set even though the site is light-only now. The
     header is a white surface by decision, not by inheritance: it carries the
     logo art and the brand lockup, and separating its ink from --ink means a
     future change to the page palette cannot quietly take the header with it. */
  --header-bg-solid: #FFFFFF;
  --header-bg:       rgba(255, 255, 255, 0.86);
  --header-border:   rgba(26, 24, 71, 0.12);
  --header-hover:    rgba(26, 24, 71, 0.06);
  --header-ink:      #1A1847;
  --header-ink-soft: rgba(26, 24, 71, 0.66);

  color-scheme: light;
}

/* The dark theme lived here: a full token override under
   [data-theme="dark"]. The feature was removed outright at the owner's
   request, so the block, the toggle, its JavaScript and the head bootstrap
   are all gone and the site is light only. The light tokens above are now
   the whole palette. Restoring it is one revert of this commit. */


/* ---------------------------------------------------------------- glass --
   One utility, used by the header, filter bar, dropdown panels and the mobile
   sheet. Solid first, glass only where the browser actually supports it — so
   a browser without backdrop-filter gets a perfectly legible opaque surface
   rather than transparent text over whatever happens to scroll underneath. */
.glass {
  background: var(--glass-bg-solid);
  border: 1px solid var(--glass-border);
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass {
    background: var(--glass-bg);
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
  }
}

/* The navy variant, for surfaces sitting on the brand colour. */
.glass--navy { background: var(--glass-navy-solid); }
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass--navy {
    background: var(--glass-navy);
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
  }
}

/* ---------------------------------------------------------------- reset -- */

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html {
  /* 360px is the design width, not 375 — budget Androids are narrower. */
  -webkit-text-size-adjust: 100%;
  /* The flat colour lives on the ROOT, not on body. body stays transparent so
     the background texture below can sit between the two on a negative
     z-index without the classic trap: a z-index:-1 pseudo-element on an
     element that itself paints an opaque background ends up hidden behind
     that element's own background layer, not above it. */
  background: var(--surface);
  /* The case that put this here is GONE, and saying so is the point of this
     comment: the mobile scroll-hide header used to reflow content next to the
     viewport top on every scroll event, CSS Scroll Anchoring compensated by
     adjusting scrollTop, that fired another scroll event, and the loop
     measured a steady ~34px bounce per frame — the stutter reported over the
     hero banner. The header does not hide any more (owner, 2026-08-23), so
     that loop cannot happen.

     Kept anyway, deliberately and on weaker grounds: anchoring still fights
     late-loading images above the fold, and this page lazy-loads most of its
     art. It is a reading-position aid for inserted ads and async content that
     this shop has no case for, so off globally costs nothing. If you are here
     because something scroll-related is misbehaving, this is now a suspect
     rather than a fix — it no longer has a specific bug behind it. */
  overflow-anchor: none;
}

body {
  position: relative;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;   /* the page body must never scroll horizontally */
}

/* ---------------------------------------------------- background texture --
   Faint accessory line-art (design request, not in the original spec): says
   what the shop sells without a word or a stock photo. Fixed, not scrolling,
   so it reads as paper texture rather than parallax — §7 bans parallax.
   pointer-events: none so it can never intercept a click or a tap. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  /* Two pre-coloured files, not one + currentColor: a background-image SVG is
     fetched as its own isolated document, so currentColor inside it would
     resolve against the SVG's own root, not this page — it would render as
     plain black rather than picking up the page's colour. See the comment in
     bg-accessories.svg for the full reasoning. */
  background-image: url("/assets/img/bg-accessories.svg");
  background-repeat: repeat;
  background-size: 440px 440px;
  opacity: 0.035;
}
@media (prefers-reduced-motion: reduce) {
  /* Not itself animated, but fixed backgrounds seesaw with the page rather
     than pausing on some Android WebViews; reduced motion is a proxy for
     that class of device, so pin it to scroll with the page there instead. */
  body::before { position: absolute; }
}

/* Kill the 300ms tap delay and pick the tap flash deliberately. */
a, button, input, select, textarea, summary, label { touch-action: manipulation; }
a, button { -webkit-tap-highlight-color: rgba(4, 157, 216, 0.20); }

img, svg, video { max-width: 100%; height: auto; display: block; }

a { color: var(--cyan-ink); text-decoration-thickness: 1px; text-underline-offset: 2px; }

button, input, select, textarea { font: inherit; color: inherit; }

/* Visible keyboard focus everywhere. Never outline: none. */
:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
  border-radius: 2px;
}

/* --------------------------------------------------------------- type --- */

h1, h2, h3, h4 { font-family: var(--font-display); font-weight: 700; line-height: 1.1; }

h1 { font-size: clamp(1.75rem, 1.2rem + 2.4vw, 3.5rem); letter-spacing: -0.02em; }
h2 { font-size: clamp(1.5rem, 1.15rem + 1.5vw, 2.25rem); letter-spacing: -0.015em; }
h3 { font-size: clamp(1.15rem, 1rem + 0.6vw, 1.5rem);   letter-spacing: -0.01em; }

p { max-width: 65ch; }

/* THE SIGNATURE (design §3.2): every number on this site is mono with tabular
   figures, so a column of twenty prices aligns and can be scanned at a glance.
   That alignment is the usability win; it is also what makes the shop read as
   a spec sheet rather than a shop window. */
.num, .price, .spec-strip, .mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.spec-strip {
  font-size: 0.8125rem;
  color: var(--ink-soft);
  letter-spacing: 0.01em;
}

/* Urdu descends heavily and needs the room. Loaded only on routes that use it. */
.ur {
  font-family: var(--font-urdu);
  direction: rtl;
  line-height: 2;
}

.u-muted  { color: var(--ink-soft); }
.u-faint  { color: var(--ink-faint); }

/* ------------------------------------------------------------- layout --- */

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: 1rem;
}
@media (min-width: 768px) { .container { padding-inline: 2rem; } }

/* ---- the site's vertical rhythm, tightened ~40% (owner, 2026-08-22) ----
   Was clamp(2.5rem, 1.5rem + 4vw, 5rem): 40px top AND bottom at 360px, so 80px
   of nothing between Featured and Categories on the screen with the least room
   to spare, and 160px between sections on desktop. Now 24px/24px at 360px and
   48px at 1280px.

   ONE knob deliberately: every route on the site wraps its content in
   `.section`, so section-to-section spacing everywhere — homepage, listings,
   product, cart, checkout, invoice, the static pages — moves from here, and
   the gap under the header is this rule's top padding on every page except the
   full-bleed homepage hero. Retuning the rhythm means editing this line, not
   hunting per-page overrides. */
.section { padding-block: clamp(1.5rem, 1rem + 2vw, 3rem); }

/* A section heading had NO gap at all to the content it labels — measured 0px
   between "Featured" and the product grid, and the same under "More from the
   shop". The reset zeroes every heading margin and neither .pgrid nor the
   headings themselves put it back, so the two boxes touched.

   That was always true; it was just hidden while 40px of section padding sat
   above the heading and made the block read as separated anyway. Halving the
   padding exposes it, and a heading welded to its own content reads as a
   rendering fault rather than as tight spacing — so 12px goes back in. The
   pass still removes far more than it adds: 32px per section boundary out,
   12px per heading in.

   Scoped to a DIRECT section heading. `.panel h2`, `.cart__summary h2` and the
   admin's `.acard h2` already set their own bottom margin, and a looser
   descendant selector here would be a second opinion on spacing they have
   already decided for themselves. */
.section > .container > h1,
.section > .container > h2 { margin-bottom: 0.75rem; }

/* Wide content scrolls inside its own container, never the page. */
.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* -------------------------------------------------------------- pieces -- */

/* The hairline is BACK, and this file's original "elevation is hairlines, not
   shadows" line stands again. The minimalist pass had dropped it because the
   card's white sat on a grey page and that tonal step drew the edge for free.
   The page is white now (owner request), so there is no step: without a border
   a card is literally invisible — not subtle, not flat, absent. A 1px hairline
   is still the lightest possible way to say "this is one object", which is
   what minimalist was asking for in the first place. */
.card {
  background: var(--surface-raised);
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  /* 44px minimum tap target, including chips and variant selectors. */
  min-height: 44px;
  padding: 0.625rem 1.25rem;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  /* Flat colour first as the fallback, then the gradient as an image on top:
     a browser that cannot paint the gradient still gets brand navy and legible
     white text rather than a transparent button. */
  background-color: var(--navy);
  background-image: var(--navy-grad);
  color: #FFFFFF;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: background-image var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out);
}
.btn:hover  { background-color: var(--navy-soft); background-image: var(--navy-grad-hover); }
.btn:active { transform: scale(0.98); }

/* Plain text at rest (owner request): a secondary action is a word until you
   reach for it. The border is kept declared as `transparent` rather than
   dropped — .btn reserves 1px for it, and removing the declaration would let
   the label jump a pixel the moment the hover state paints one. `.is-active`
   is the "selected" half of the same rule, for anywhere a quiet button marks
   a current choice rather than an action. */
.btn--quiet {
  background: transparent;
  color: var(--ink);
  border-color: transparent;
}
.btn--quiet:hover,
.btn--quiet:focus-visible {
  background: var(--cyan-wash);
  border-color: var(--hairline);
}
.btn--quiet.is-active,
.btn--quiet[aria-pressed="true"] {
  background: var(--cyan-wash);
  border-color: var(--cyan);
}

/* Plain text at rest, a pill once you touch it or it is the one selected
   (owner request, superseding the earlier minimalist note here that kept a
   permanent --surface-raised fill). Chips sit directly on the page background
   in both places they are used — variant options on the product page, brand
   filters on the listing rail — so "flat" now means genuinely no fill, and the
   only chip carrying a box is the chosen one. That is also what makes the
   selected state readable: it is the only filled thing in the row.

   The 1px border stays declared as transparent at rest so the row does not
   shift by a pixel per chip when a hover paints one, and the gap between chips
   comes from their container, so a row of unfilled chips still reads as a set
   of separate options rather than a sentence. */
.chip {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0.375rem 0.875rem;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: transparent;
  cursor: pointer;
  transition: background var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out);
}
/* White on the page is only a ~1.15:1 luminance step, so the fill alone is
   not a visible hover. The reserved 1px border does the work; selected below
   swaps the same border to cyan, so rest / hover / selected are three
   distinct states without a tint on any of them. */
.chip:hover, .chip:focus-visible {
  background: var(--cyan-wash);
  border-color: var(--hairline);
}
.chip[aria-pressed="true"], .chip.is-active {
  background: var(--cyan-wash);
  border-color: var(--cyan);
}

.stock       { font-size: 0.875rem; font-weight: 600; }
.stock--ok   { color: var(--status-ok); }
.stock--low  { color: var(--status-low); }
.stock--out  { color: var(--status-out); }

/* -------------------------------------------------------------- motion -- */

.reveal {
  opacity: 0;
  transform: translateY(var(--rise));
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
}
.reveal.is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1 !important; transform: none !important; }
}

/* ------------------------------------------------------------------ a11y */

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

.skip-link {
  position: absolute;
  left: 1rem;
  top: -3rem;
  z-index: 100;
  padding: 0.625rem 1rem;   /* 44px tap target, was 41px */
  background-color: var(--navy);
  background-image: var(--navy-grad);
  color: #FFFFFF;
  border-radius: var(--r-sm);
  transition: top var(--dur-base) var(--ease-out);
}
.skip-link:focus { top: 1rem; }
