/* ==========================================================================
   Naveed Mobiles — header, footer, and homepage layout.
   Split from main.css so the token/base layer stays readable as the shop grows.
   ========================================================================== */

/* -------------------------------------------------------------- header -- */

/* Frosted, because it is sticky and genuinely floats over scrolling content.
   Fixed white/light, not theme-variable — see the --header-* token comment
   in main.css. The solid --header-bg-solid fallback is what browsers without
   backdrop-filter get, and navy-ink-on-that measures far past AA — the blur
   is a depth cue, never what makes the text readable. */
.site-header {
  background: var(--header-bg-solid);
  color: var(--header-ink);
  position: sticky;
  top: 0;
  z-index: 40;
  border-bottom: 1px solid var(--header-border);
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header {
    background: var(--header-bg);
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
  }
}

/* DESKTOP columns: logo, filters, search field, actions.
   [logo] [filters]        [search]        [cart] [menu] [monogram]

   The filter icon lives in .site-header__lead in the MARKUP, between nothing
   and the brand, because on mobile it belongs to the left rail. Here it is
   placed explicitly into column 2 instead, which is what puts the logo at the
   extreme left where the owner asked for it (2026-08-23) without needing two
   copies of the button. Explicit grid-column beats source order; that is the
   whole reason this is a grid and not a flex row. */
.site-header__inner {
  position: relative;   /* anchors .nav-menu below, without affecting the grid */
  display: grid;
  grid-template-columns: auto auto 1fr auto;
  align-items: center;
  gap: 1rem;
  min-height: 64px;
}
/* `grid-row: 1` on all four is NOT decoration — leave it off and the header
   renders 112px tall with the filter button alone on a first row.

   Auto-placement: .filter-toggle comes FIRST in the markup (it lives in the
   mobile lead rail) but is placed in column 2. The sparse packing cursor is
   then past column 2, so the brand — which comes later in the DOM and wants
   column 1 — cannot go backwards in row 1 and starts row 2, taking everything
   after it along. A definite column with an auto row is exactly the case that
   trips this. Pinning the row makes placement fully explicit, and DOM order
   stops mattering, which is the whole point of placing them by hand. */
@media (min-width: 768px) {
  .site-header__inner { min-height: 72px; }
  .brand                { grid-column: 1; grid-row: 1; }
  .filter-toggle        { grid-column: 2; grid-row: 1; }
  .site-search          { grid-column: 3; grid-row: 1; }
  .site-header__actions { grid-column: 4; grid-row: 1; }
}

/* ---- the top bar is STICKY and never hides (owner, 2026-08-23) ----
   It used to slide off the top on a downward scroll, leaving the navy filter
   bar pinned at the viewport edge in its place. That only ever made sense
   because there WAS a second bar to take over the top; with the filter bar
   gone, hiding this row would leave the page with no chrome at all and no way
   back to search, filters, cart or menu without scrolling to the top.

   Removed rather than disabled: the `.is-nav-hidden` / `.is-nav-shifting`
   rules, the `--nm-top-shift` custom property, the transform, the no-blur
   window during the slide, and the whole scroll handler in main.js that drove
   them. `position: sticky` on .site-header above is now the entire mechanism.

   Removing a feature means checking what it left behind — see the
   `ReferenceError: root is not defined` note in main.js, which is exactly this
   mistake made once already. What this one owned: `var root` in main.js (its
   only user), and the `overflow-anchor: none` on `html` in main.css, whose
   comment described a feedback loop between this transform and CSS scroll
   anchoring. The property stays (anchoring can still fight late-loading
   images) but its comment no longer claims a cause that has been deleted. */

/* Scoped to __inner rather than the whole header. The reason used to be that
   the navy filter bar was a sibling with its own white-on-navy text; the bar
   is gone, but the scoping stays correct and cheap — the filter PANEL is still
   a sibling of this row, and a broader `.site-header a` would reach into it. */
.site-header__inner a { color: var(--header-ink); text-decoration: none; }

.brand { display: inline-flex; align-items: center; gap: 0.375rem; }
@media (min-width: 768px) { .brand { gap: 0.5rem; } }

/* Desktop logo art, painted as backgrounds and declared ONLY inside the
   min-width query below. As <img> elements these were fetched on every phone
   and then hidden — `display: none` does not cancel an image request. A
   background-image inside a non-matching media query is never requested at
   all, so the mobile header now ships zero raster logo bytes: its mark is the
   inline SVG lockup and live text.

   Sizes come from the source aspect ratios, so neither is squashed:
   icon 536x650 (0.825) at 40px tall is 33px wide; wordmark 1633x585 (2.79)
   at 30px tall is 84px wide. */
/* ---- the logo lockup: icon + wordmark, at every width ----
   Owner request: the desktop lockup on mobile too, replacing the drawn
   SVG-and-text substitute that briefly held the mobile slot.

   Painted as backgrounds rather than <img>. That started as a way to stop
   hidden images being fetched (`display: none` does not cancel an image
   request, which is how 635KB of logo was reaching phones that showed none of
   it); it stays because it lets both halves be resized per breakpoint from one
   place, with no markup change.

   COST, stated plainly: showing the art on mobile means mobile pays for it
   again, roughly 136KB over the wire for the two PNGs. That is the owner's
   call and it is a reasonable one, but it is also exactly what finding M1 in
   the audit is about. Vector-tracing these two files removes the cost outright
   and is the fix that makes this decision free — the sizes below already come
   from the source aspect ratios, so an SVG swap is a one-line URL change.

   Sizes from the sources, so neither is squashed:
   icon 536x650 (0.825), wordmark 1633x585 (2.79). */
.brand__mark, .brand__word {
  display: block;
  flex: 0 0 auto;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
}
.brand__mark {
  width: 29px; height: 35px;
  background-image: url("/assets/img/logo-icon-new.png");
}
.brand__word {
  width: 73px; height: 26px;
  background-image: url("/assets/img/logo-wordmark.png");
}
/* Full size from 768px up, where the row is not competing with three icon
   groups for a 360px line. */
@media (min-width: 768px) {
  .brand__mark { width: 33px; height: 40px; }
  .brand__word { width: 84px; height: 30px; }
}

/* Capped rather than filling the whole middle column: at 1280px the field was
   702px wide, far past the ~60ch a product query ever needs, and the slack it
   ate is what pushed the brand and the actions row to opposite edges. Capping
   it pulls the header back into one composition. `margin-inline: auto` keeps
   the field optically centred in the column it no longer fills. */
.site-search { display: flex; gap: 0.5rem; min-width: 0; }
.site-search input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 44px;
  padding: 0.5rem 0.875rem;
  /* Flat at rest (owner request: minimalist pass) — the fill against the
     white header is the only edge, no drawn border. A transparent border
     of the same width is kept rather than removing the property outright,
     so focus does not add 1px and shove the field's neighbours sideways. */
  border: 1px solid transparent;
  border-radius: 999px;
  background: #F2F3F7;
  color: var(--header-ink);
}
@media (min-width: 640px) {
  .site-search { max-width: 440px; margin-inline: auto; width: 100%; }
}
.site-search input::placeholder { color: var(--header-ink-soft); }
.site-search input:focus { background: #FFFFFF; border-color: var(--cyan); }
/* ---- mobile header: one row, brand centred, 77px tall ----
   Was 110px: a two-row grid with the monogram spanning a 78px left column and
   the search field permanently occupying row two. The owner asked for 30% off
   that height, the search field replaced by an icon, and the brand centred, and
   those three are the same change — dropping the search row is most of the 33px.

   Columns are 1fr auto 1fr, NOT the desktop `auto auto 1fr auto`. The brand
   has to sit in the optical centre of the VIEWPORT, and with `auto ...` the
   middle column starts wherever the left group ends, so the brand lands off
   centre by half the difference between the two side groups. Equal 1fr rails
   centre it regardless of what the sides contain — which matters here even
   though both rails currently hold two 44px icons (filters + search on the
   left, cart + menu on the right) and happen to be the same 90px: the moment
   one side gains or loses an icon, `1fr auto 1fr` keeps the brand centred and
   `auto` would not. */
@media (max-width: 767px) {
  .site-header__inner {
    grid-template-columns: 1fr auto 1fr;
    grid-template-rows: auto;
    align-items: center;
    column-gap: 0.375rem;
    row-gap: 0;
    min-height: 77px;
    padding-block: 0.4375rem;
  }
  .site-header__lead    { grid-column: 1; grid-row: 1; justify-self: start; }
  .brand                { grid-column: 2; grid-row: 1; justify-self: center; min-width: 0; }
  .site-header__actions { grid-column: 3; grid-row: 1; justify-self: end; }
  /* The desktop rule places this in column 2 on its own; on mobile it belongs
     to the lead rail's flex flow, so the placement has to be released or the
     grid pulls it out of the rail and into the brand's column. */
  .filter-toggle        { grid-column: auto; }

  /* The search field becomes a second row only while it is open, so the bar
     is one row at rest. Full width there, since it no longer shares the row
     with anything. */
  .site-search { grid-column: 1 / -1; grid-row: 2; margin-top: 0.4375rem; }
  .site-search .btn { display: none; }   /* Enter submits; the button is noise here */

  /* WhatsApp is desktop-only. The right rail is cart + menu, and
     contact_whatsapp is still unset — so without this rule the row would
     silently grow a third icon the day that setting is filled in, and push
     the brand off centre. */
  .header-wa { display: none; }
}

/* ---- the mobile left rail: filters, then search ----
   `display: contents` is the resting state, not a mobile override. Above 767px
   the rail as a concept does not exist: the search toggle is `display: none`
   and the filter button is placed straight into the desktop grid's column 2 by
   the rule at the top of this file. `display: contents` is what lets one
   button belong to a flex rail at one width and to a grid column at another
   without shipping two copies of it.

   Gap is tighter than the actions group's: two icons that belong to the same
   rail should read as a pair, and at 360px every pixel here comes straight
   out of the brand's centred column. */
.site-header__lead { display: contents; }
@media (max-width: 767px) {
  .site-header__lead {
    display: flex;
    align-items: center;
    gap: 0.125rem;
  }
}

/* ---- search toggle ----
   Same flat-at-rest treatment as every other header control: nothing until a
   cursor or focus lands on it. Desktop never sees it — the field is always
   visible there and a toggle would only add a click. */
.search-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 999px;
  background: transparent;
  color: var(--header-ink);
  cursor: pointer;
  transition: background var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out);
}
.search-toggle:hover, .search-toggle:focus-visible { background: #EBEEF4; }
.search-toggle[aria-expanded="true"] { background: #EBEEF4; border-color: var(--cyan); }
@media (max-width: 767px) { .search-toggle { display: inline-flex; } }

/* ---- filter toggle ----
   The navy bar's job, in one 44px icon, at every width (owner, 2026-08-23).
   Same flat-at-rest shape as search and menu so the header reads as one set of
   controls rather than three treatments.

   `position: relative` is for the count badge, which is not decoration here:
   this button is the ONLY route to the filters now, so the badge is the only
   thing on the page that says a catalogue has been narrowed. */
.filter-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 999px;
  background: transparent;
  color: var(--header-ink);
  cursor: pointer;
  transition: background var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out);
}
.filter-toggle:hover, .filter-toggle:focus-visible { background: #EBEEF4; }
.filter-toggle[aria-expanded="true"] { background: #EBEEF4; border-color: var(--cyan); }
/* Filters ARE applied: solid cyan, so the resting state and the narrowed state
   are not a matter of spotting a small badge. Navy ink on cyan — cyan text on
   white fails AA at this size (see the --cyan trap in main.css). */
.filter-toggle:has(.filter-count) {
  background: var(--cyan);
  border-color: var(--cyan);
  color: var(--navy-deep);
}
.filter-count {
  position: absolute;
  top: 1px;
  inset-inline-end: 1px;
  display: inline-grid;
  place-items: center;
  min-width: 17px;
  height: 17px;
  padding-inline: 4px;
  border-radius: 9px;
  background-color: var(--navy-deep);
  background-image: var(--navy-grad);
  color: #FFFFFF;
  font-size: 0.6875rem;
  /* Ring in the header's white so the badge reads as sitting ON the icon
     rather than merging with the cyan fill behind it. */
  box-shadow: 0 0 0 2px var(--header-bg-solid);
}

/* Collapsed by default ONLY where JavaScript can reopen it. With scripting off
   the toggle above is inert, so the field must stay visible rather than be
   sealed behind a dead control — the same degrade-first rule the filter panel
   follows. */
@media (max-width: 767px) {
  .has-js .site-search[hidden] { display: none; }
}
.site-search[hidden] { display: none; }

.site-header__actions { display: flex; align-items: center; gap: 0.375rem; }
/* Scoped to __inner for the same reason as the `a` rule above — .btn--quiet
   is not used inside the filter bar, but scoping precisely costs nothing
   and keeps this file honest about what it actually touches.

   Flat at rest (owner request: minimalist pass) — these used to carry a
   permanent light-grey pill fill, which is what read as "boxed" against the
   plain white header even without a drawn border. Now nothing shows until
   a visitor's cursor or focus actually lands on one; Cart stays the row's
   one accent (solid cyan) regardless, so there is still a clear focal
   point among otherwise-invisible icon buttons. */
.site-header__inner .btn--quiet {
  color: var(--header-ink);
  border-color: transparent;
  background: transparent;
  border-radius: 999px;
  padding-inline: 0.875rem;
  transition: background var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out);
}
.site-header__inner .btn--quiet:hover,
.site-header__inner .btn--quiet:focus-visible {
  /* Neutral grey, NOT white: the header itself is white, so a white hover
     would be no hover at all. Hardcoded for the same reason the old value
     was — see the .menu-toggle note below. */
  background: #EBEEF4;
  color: var(--navy-deep);
}
/* Cart was the one filled accent in this row. It is a plain icon at every
   width now (owner request: desktop matching what mobile already does), so
   this rule keeps only the shape and lets .cart-link below strip the fill.
   Nothing else in the header is a non-quiet .btn, so this selector exists for
   the cart alone. */
.site-header__inner .btn:not(.btn--quiet) {
  color: var(--header-ink);
  border-radius: 999px;
  padding-inline: 1rem;
}


/* Icon inside a header action. Sized against the 24x24 grid the rest of the
   project's icons use, so the WhatsApp mark matches the category set. */
.site-header__actions .btn svg { flex: 0 0 auto; }

/* Mobile: keep the 44px tap target (design §8 is a hard rule — the visual box
   shrinks, the touch area does not), but drop the WhatsApp word to its icon
   and tighten the padding. That is ~90px back on a 360px header without
   making anything harder to hit. */
@media (max-width: 767px) {
  .site-header__actions { gap: 0.25rem; }
  .site-header__actions .btn--quiet { padding-inline: 0.625rem; }
  .site-header__actions .btn:not(.btn--quiet) { padding-inline: 0.75rem; }
  /* WhatsApp drops to its icon here. Cart is handled outside this query, since
     it is icon-only at every width now. */
  .btn__label--wa {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
}

/* Visually hidden, NOT display:none — the cart is an icon at every width now,
   and without this the link would have no accessible name at all. The word
   stays in the a11y tree and out of the layout. */
.cart-link__label {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Site menu: 3-bar toggle + the dropdown it opens. At EVERY width now (owner,
   2026-08-23): it was mobile-only while the navy filter bar gave the header a
   second row, and Home / Delivery / Guides / About / Contact could live in the
   footer alone on desktop. With that bar gone this header is all the chrome
   the page has, so those links need a door in it at every width. */
.menu-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 999px;   /* same pill language as the rest of the cluster */
  background: transparent;   /* flat at rest, matching .btn--quiet — minimalist pass */
  color: var(--header-ink);
  cursor: pointer;
  transition: background var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out);
}
.menu-toggle:hover, .menu-toggle:focus-visible { background: #EBEEF4; }
/* #EBEEF4 is --state-tint's value, hardcoded rather than referenced: the
   header is a fixed white surface with its own --header-* tokens,
   which would leave --header-ink (dark) text sitting on a dark background —
   the header stays fixed-white regardless of theme, so nothing inside it may
   pull from a token that swaps for dark mode. */
.menu-toggle[aria-expanded="true"] { background: #EBEEF4; border-color: var(--cyan); }

/* Light glass, matching the white header it opens from.

   Full width under the header on mobile; on desktop it is capped and pinned to
   the RIGHT, under the button that opens it, because a 1280px-wide sheet of
   five links reads as a site-wide banner rather than as a menu. */
.nav-menu {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 45;
  padding: 0.375rem;
  border-radius: 0 0 var(--r-md) var(--r-md);
  box-shadow: var(--shadow-float);
  background: var(--header-bg-solid);
  border: 1px solid var(--header-border);
  border-top: none;
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .nav-menu {
    background: var(--header-bg);
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
  }
}
.nav-menu[hidden] { display: none; }
/* `right: 2rem`, not 0. This is positioned against .site-header__inner, which
   IS the .container — and absolute positioning resolves against the PADDING
   box, so `right: 0` puts the panel flush with the viewport edge, 2rem past
   the gutter every other thing in the header lines up with. Matching the
   container's own desktop padding-inline puts it under the actions cluster
   where it belongs. */
@media (min-width: 768px) {
  .nav-menu {
    left: auto;
    right: 2rem;
    width: min(260px, calc(100vw - 4rem));
    border-radius: 0 0 var(--r-md) var(--r-md);
  }
}
.nav-menu nav { display: grid; gap: 1px; }
.nav-menu a {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0.625rem 0.875rem;
  border-radius: var(--r-sm);
  color: var(--header-ink);
  text-decoration: none;
  font-weight: 600;
}
.nav-menu a:hover, .nav-menu a:focus-visible { background: var(--header-hover); }

/* Urdu monogram at the header's right edge, where Cart used to sit. It is
   the only thing in this block now, so it can run the header's full height
   rather than splitting the space with text. The header's min-height
   (64/72px) is the ceiling; the monogram sits just inside it.
   768px+ only: there is no room for a fifth element in the actions row
   below that without crowding the tap targets already there.

   Flat (owner request: minimalist pass). It used to sit inside a
   cyan-washed, cyan-bordered plaque — a "seal" treatment — which read as a
   boxed sticker rather than part of the header surface. The image now sits
   directly on the white header like the icon-mark and wordmark do on the
   left; a small opacity dip plus lift on hover is the only affordance, no
   border or fill at rest. */
.brand__urdu {
  display: none;
  align-items: center;
  justify-content: center;
  margin-left: 0.75rem;
  opacity: 0.92;
  transition: opacity var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}
.brand__urdu:hover, .brand__urdu:focus-visible {
  opacity: 1;
  transform: translateY(-1px);
}
@media (min-width: 1024px) {
  .brand__urdu {
    width: 63px;
    height: 52px;
    background-image: url("/assets/img/logo-urdu-monogram.png");
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
  }
}
/* Size and art both live inside the 1024px query below, so nothing about this
   image is requested on a phone. Aspect 1890/1550 = 1.219, so 52px tall is
   63px wide. */
/* Now from 768px (tablet) rather than 1024px: the actions row gained the room
   when WhatsApp dropped to an icon below 768px and the search field stopped
   filling the whole middle column. */
@media (min-width: 768px) { .brand__urdu { display: inline-flex; } }
/* The header grows to 72px min-height at 768px+, so above 1280px there is
   room for the monogram to fill more of it. */
@media (min-width: 1280px) { .brand__urdu img { height: 58px; } }
/* This used to be `display: none !important` from 768px up — belt and
   suspenders for a panel that was mobile-only. The menu is a desktop control
   now, so that rule would hide the panel its own button opens. Removed, not
   loosened: there is no width at which this panel should be unreachable. */

/* The theme toggle lived here. Feature removed outright at the owner's
   request: the button, its JavaScript, the head bootstrap and every
   [data-theme="dark"] rule are gone, and the site is light only. */

.cart-count {
  display: inline-grid;
  place-items: center;
  min-width: 22px; height: 22px;
  padding-inline: 5px;
  border-radius: 11px;
  background-color: var(--navy-deep);
  background-image: var(--navy-grad);
  color: #FFFFFF;
  font-size: 0.75rem;
}

/* ---- mobile cart: a plain icon, not a filled pill ----
   Owner request (minimalist). Desktop keeps the solid cyan, which is still the
   one accent in that row; on mobile the row is three bare icons and a filled
   pill among them reads as the odd one out rather than as emphasis.

   The count rides the icon as a badge instead of sitting inline beside it, so
   the control stays a 44px square rather than growing a word wide. `overflow:
   visible` is set explicitly because the badge is deliberately outside the
   icon's box. */
/* A 44px square with no fill, at every width. The count rides the icon as a
   badge rather than sitting inline beside it, so the control never grows a
   word wide and desktop and mobile are genuinely the same control.

   The badge anchors to .cart-link ITSELF, not to a wrapper around the glyph.
   An earlier pass wrapped the svg and badge in a span so the badge could hang
   off the icon; that span is a plain inline box, and the badge inside it
   inflated its line height enough to render the button 67px tall inside a 72px
   header. No wrapper, no inflation. */
.site-header__inner .btn.cart-link {
  position: relative;
  background: transparent;
  background-image: none;
  color: var(--header-ink);
  box-shadow: none;
  border-color: transparent;
  width: 44px;
  height: 44px;
  min-height: 44px;
  padding: 0;
  border-radius: 999px;
}
.site-header__inner .btn.cart-link:hover,
.site-header__inner .btn.cart-link:focus-visible { background: #EBEEF4; }

.cart-count {
  position: absolute;
  top: 2px;
  inset-inline-end: 2px;
  min-width: 17px;
  height: 17px;
  padding-inline: 4px;
  border-radius: 9px;
  font-size: 0.6875rem;
  /* Ring in the header's own white so the badge reads as sitting ON the icon
     rather than merging with the stroke behind it. */
  box-shadow: 0 0 0 2px var(--header-bg-solid);
}
/* Zero is not news. Hiding it keeps the resting row clean and makes the badge
   mean something when it does appear.

   Drives off the ATTRIBUTE, which main.js keeps in step with the text. The
   `:empty` selector that used to sit here was dead weight: the span always
   contains a digit, so it is never empty, and the attribute shipped with no
   value, so neither half of the old rule could ever match and every empty
   cart rendered a badge reading "0". */
.cart-count[data-cart-count="0"] { display: none; }
/* Confirms the action registered. 200ms, once, on real change only. */
.cart-count.is-bump { animation: nm-bump 200ms var(--ease-out); }
@keyframes nm-bump { 50% { transform: scale(1.15); } }

/* -------------------------------------------------------- filter panel --
   Was a navy BAR: a second sticky row under the header carrying category,
   brand and price as inline chips. The owner removed it outright on
   2026-08-23, at every width. The same three controls are now a panel that
   drops from the header's filter icon.

   Nothing about the FORM changed — still one plain GET submitting to /search
   with every value in the URL, still degrading to radio lists and two range
   sliders without JavaScript. Only the container moved, which is why this
   section is a re-skin rather than a rewrite.

   Three elements, three jobs:
     .filterbar         — the invisible positioner. No background, no border,
                          no pointer events; it exists to hang the panel off
                          the bottom of the sticky header.
     .filterbar__inner  — the .container, so the panel lines up with the page
                          gutter instead of the viewport edge.
     .fbar__controls    — the actual panel: the surface, the border, the
                          shadow, and the thing that opens and closes.
   -------------------------------------------------------------------------- */

.filterbar {
  position: absolute;
  top: 100%;          /* the bottom edge of .site-header__inner */
  left: 0;
  right: 0;
  z-index: 45;
  /* pointer-events are handed back on .fbar__controls alone. Without this the
     full-width invisible wrapper would swallow every tap on the strip of page
     directly under the header, whether the panel is open or not — a dead zone
     nothing would explain. */
  pointer-events: none;
}
.filterbar__inner {
  display: block;
  padding-block: 0;
  pointer-events: none;
}

/* THE PANEL. Same light glass, hairline and float shadow as the nav menu it
   sits beside, because they are the same kind of object: a sheet dropped from
   a control in the header. */
.fbar__controls {
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.25rem;
  padding: 0.375rem;
  border: 1px solid var(--header-border);
  border-top: none;
  border-radius: 0 0 var(--r-md) var(--r-md);
  box-shadow: var(--shadow-float);
  background: var(--header-bg-solid);
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .fbar__controls {
    background: var(--header-bg);
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
  }
}
/* Capped and left-aligned on desktop: a 1280px sheet holding three controls
   reads as a returning navigation bar, which is the exact thing that was just
   removed. It hangs under the page gutter, near the button that opens it. */
@media (min-width: 768px) {
  .fbar__controls { width: min(320px, calc(100vw - 4rem)); }
}

/* Closed by default ONLY where JavaScript can reopen it. With scripting off
   the header's filter button is inert, so the panel renders OPEN — as a static
   block in the header's flow rather than an overlay, since an overlay nothing
   can dismiss would cover the page permanently. Same degrade-first rule the
   search field follows. */
.has-js .filterbar { position: absolute; }
.has-js .fbar__controls { display: none; }
.has-js .fbar__controls.is-open { display: flex; }
html:not(.has-js) .filterbar { position: static; }

.fbar__field { position: relative; display: flex; align-items: center; gap: 0.5rem; }

/* Ink, not white-on-navy: the panel is a light surface now. Every colour in
   this section moved for that one reason. */
.fbar__label {
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--ink-faint);
}

/* Filter chips, not boxes. Pill radius is the current convention for a
   dropdown filter (chip + trailing caret reads as "opens a menu"), and it
   separates these from the rectangular inputs elsewhere on the page. */
.fbar__toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
  padding: 0.375rem 0.875rem;
  /* Plain text at rest (owner request): the pill only appears once a cursor is
     on it, it is open, or a filter is actually applied. The border stays
     declared as transparent rather than removed — a border that appears on
     hover would otherwise shift the label by a pixel every time. */
  border: 1px solid transparent;
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  transition: background var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}
.fbar__toggle:hover,
.fbar__toggle:focus-visible { background: var(--state-tint); border-color: var(--hairline); }
.fbar__toggle[aria-expanded="true"] { background: var(--state-tint); border-color: var(--cyan); }
/* An APPLIED filter is solid cyan, so a narrowed panel never looks like a
   fresh one. `:has()` keeps this in CSS rather than duplicating the PHP-side
   count the header badge derives. Navy ink on cyan, since cyan text at this
   size fails contrast either way (main.css --cyan comment). */
.fbar__field:has(input:checked:not([value=""])) .fbar__toggle {
  background: var(--cyan);
  border-color: var(--cyan);
  color: var(--navy-deep);
}
.fbar__field:has(input:checked:not([value=""])) .fbar__label { color: rgba(26, 24, 71, 0.72); }

.fbar__value { font-size: 0.875rem; font-weight: 600; max-width: 12ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fbar__chev { flex: 0 0 auto; transition: transform var(--dur-base) var(--ease-out); }
.fbar__toggle[aria-expanded="true"] .fbar__chev { transform: rotate(180deg); }

/* The dropdown itself. Opacity+translate rather than height, so it can be
   transitioned smoothly and is collapsed to ~0ms by the global
   prefers-reduced-motion rule in main.css without a second override here. */
.fbar__panel {
  position: absolute;
  top: calc(100% + 0.375rem);
  left: 0;
  z-index: 50;
  min-width: 240px;
  max-height: min(60vh, 420px);
  overflow-y: auto;
  padding: 0.375rem;
  border-radius: var(--r-md);
  box-shadow: var(--shadow-float);
  display: grid;
  gap: 1px;
  /* Glass, with the opaque fallback first — see the .glass utility in main.css. */
  background: var(--glass-bg-solid);
  border: 1px solid var(--glass-border);
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .fbar__panel {
    background: var(--glass-bg);
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
  }
}
.fbar__panel[hidden] { display: none; }
.fbar__panel.is-open { animation: fbar-in var(--dur-base) var(--ease-out); }
@keyframes fbar-in { from { opacity: 0; transform: translateY(-4px); } }

.fbar__opt {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 40px;
  padding: 0.375rem 0.625rem;
  border-radius: var(--r-sm);
  color: var(--ink);
  font-size: 0.9375rem;
  cursor: pointer;
}
/* The dropdown panel is a near-white glass surface, so the white --cyan-wash
   would vanish on it. Neutral tint for hover; selection adds a cyan rail so
   the two states stay distinguishable from each other, not just from rest. */
.fbar__opt:hover { background: var(--state-tint); }
.fbar__opt input { accent-color: var(--cyan); flex: 0 0 auto; }
.fbar__opt-icon { display: inline-flex; color: var(--ink-soft); flex: 0 0 auto; }
.fbar__opt:has(input:checked) {
  background: var(--state-tint);
  box-shadow: inset 3px 0 0 var(--cyan);
  font-weight: 600;
}

/* ---- price range: a dropdown like Category and Brand, not exposed in the
   bar itself. The panel is a light glass surface (see .fbar__panel), so its
   contents use the same ink/hairline tokens as the Category/Brand options,
   not the white-on-navy colours the bar chrome uses. ---- */
.fbar__panel--price {
  min-width: 270px;
  display: grid;
  gap: 0.75rem;
  padding: 1rem 0.875rem;
}
.fbar__price-out {
  display: block;
  font-size: 0.9375rem;
  font-weight: 700;
  color: var(--ink);
  text-align: center;
}
.fbar__range { position: relative; width: 100%; height: 44px; display: flex; align-items: center; }
.fbar__track {
  position: absolute;
  inset-inline: 0;
  height: 4px;
  border-radius: 2px;
  background: var(--hairline);
}
.fbar__fill {
  position: absolute;
  height: 100%;
  border-radius: 2px;
  background: var(--cyan);
}
.fbar__panel-apply { width: 100%; min-height: 44px; }
/* Both inputs stack on the same track. pointer-events are re-enabled on the
   thumbs only, so whichever handle is under the cursor is the one that drags
   instead of the topmost input swallowing every press. */
.fbar__range input[type="range"] {
  position: absolute;
  inset-inline: 0;
  width: 100%;
  margin: 0;
  background: none;
  pointer-events: none;
  -webkit-appearance: none;
  appearance: none;
  height: 44px;
}
.fbar__range input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  pointer-events: auto;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: #FFFFFF;
  border: 2px solid var(--cyan);
  cursor: grab;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
}
.fbar__range input[type="range"]::-moz-range-thumb {
  pointer-events: auto;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: #FFFFFF;
  border: 2px solid var(--cyan);
  cursor: grab;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
}
.fbar__range input[type="range"]::-moz-range-track { background: none; }

.fbar__go { min-height: 44px; padding-inline: 1.25rem; }
.filterbar .fbar__go { background: var(--cyan); color: var(--navy-deep); }
.filterbar .fbar__go:hover { background: #03B4F5; }
/* The bar's own "Filters" disclosure (.fbar__more, .fbar__more-count) was
   styled here: a full-width white-on-navy row that only existed under 720px.
   The trigger is .filter-toggle in the header now and serves every width, so
   these rules had no selector left to match. Deleted rather than left as dead
   weight — a rule that cannot match is a rule the next reader has to prove is
   dead. */

/* Each control gets its own row. This used to be a <720px override of a
   horizontal bar; the panel is a vertical stack at every width now, so it is
   the default and there is no second layout to keep in sync. Full-width rows
   also keep every toggle clear of the 44px tap-target floor without measuring
   anything (design §8). */
.fbar__field { flex: 1 1 auto; justify-content: space-between; }
.fbar__toggle { flex: 1 1 auto; justify-content: space-between; }
.fbar__value { max-width: none; }
.fbar__range { flex: 1 1 auto; width: auto; min-width: 120px; }
/* The nested dropdown fills the panel's width rather than opening a
   240px-minimum box that could overhang it. */
.fbar__panel { left: 0; right: 0; min-width: 0; }
.fbar__go { flex: 1 1 auto; }
/* Clear is an action on the whole panel, not a fourth filter, so it sits full
   width under the three controls with a rule above it. */
.fbar__clear {
  justify-content: center;
  border-top: 1px solid var(--hairline);
  margin-top: 0.125rem;
  padding-top: 0.5rem;
}

/* --------------------------------------------------------- social bar --
   Five brand marks in a NAVY GRADIENT band below the header (owner,
   2026-08-23): centred on mobile, right aligned on desktop. A sibling of
   .site-header, not a child, so it scrolls away rather than following the
   shopper down the page.

   Same 180deg ramp as the footer, so the page is bracketed top and bottom by
   two navy bands that deepen in the same direction. background-color is set
   before background-image, so a browser that cannot paint the gradient still
   gets brand navy rather than a transparent strip.

   THE LOGOS CHANGE APPEARANCE HERE, and it is worth knowing why before
   "fixing" it. Four of the five (Facebook, YouTube, WhatsApp, TikTok) are
   drawn on an OPAQUE WHITE square base. On the white page that base was
   invisible and they read as bare glyphs; on navy the base shows, so they read
   as white rounded app tiles with a coloured mark inside. Instagram has no
   white base — its own gradient fills the tile — so it matches. That is why
   border-radius on the image matters more now than it did: without it these
   are five hard-cornered blocks.

   ---- HEIGHT, and the rule it breaks ----
   Cut twice at the owner's request. First 25%, marks held at 18px: 49px to
   37px on mobile, 39px to 29px on desktop. Then MOBILE ONLY a further 20%,
   37px to 30px, with the marks scaled down to match (18px to 14px) because
   there was no longer room to hold them. Desktop is untouched by the second
   cut and keeps 29px and 18px.

   The bar was 49px because the TAP TARGET was 44px with 4px of padding around
   it. There is nothing else in that number, so every cut comes out of the
   target: 44px to 34px, then 34px to 27px. That is BELOW the 44px floor design
   §8 sets and this project holds everywhere else. Stated plainly rather than
   quietly shipped: these are secondary off-site links, not the buying path,
   and it is a deliberate owner-approved exception rather than an oversight —
   the one place on the shop where §8 does not hold.

   READ THIS BEFORE SHRINKING IT AGAIN. 27px clears WCAG 2.2 AA's Target Size
   (Minimum) by three pixels; the floor there is 24x24 CSS px. A third 20% cut
   would land at 22px and breach it, and unlike §8 that is not a house rule to
   trade away — it is the accessibility conformance the rest of the site is
   built to. There is no room left in this number.

   The alternative was keeping a 44px target inside a 37px box using negative
   margins. Rejected: the overhang lands under the sticky header, which paints
   above it and would swallow those taps, and over the page below, where it
   would silently steal taps from content. An invisible strip that opens
   Facebook when you meant to tap the page is worse than a smaller target.
   -------------------------------------------------------------------------- */
.social-bar {
  background-color: var(--navy);
  background-image: var(--navy-grad-social);
  /* A light hairline would be invisible on navy; this is the same edge the
     old filter bar used to close itself off from the page. */
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}
.social-bar__inner {
  display: flex;
  align-items: center;
  justify-content: center;    /* mobile: centred */
  gap: 0.125rem;
  padding-block: 1px;
}
@media (min-width: 768px) {
  .social-bar__inner { justify-content: flex-end; padding-block: 0; }
}

.social-bar__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 27px;
  height: 27px;
  border-radius: 999px;
  text-decoration: none;
  /* No hover FILL, on purpose — see the note about the white bases above. A
     tinted hover square behind four marks and not the fifth would read as a
     rendering fault. Lift and opacity carry the affordance instead, the same
     treatment .brand__urdu uses. */
  opacity: 0.92;
  transition: opacity var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}
@media (min-width: 768px) {
  .social-bar__link { width: 28px; height: 28px; }
}
.social-bar__link:hover,
.social-bar__link:focus-visible { opacity: 1; transform: translateY(-1px); }

/* 14px on mobile, 18px on desktop. The two cuts asked for different things and
   that is why this is split rather than one value: the 25% pass held the marks
   at 18px while the bar shrank around them, and the mobile-only 20% pass left
   no room to keep doing that — a 30px bar cannot hold an 18px mark and still
   look like a mark rather than a full-bleed tile.

   Super Tiny Icons are square tiles meant to be masked by the page; ~24% is the
   squircle the real app icons use, and on navy it is what stops all five being
   hard-cornered blocks. */
.social-bar__link img {
  width: 14px;
  height: 14px;
  border-radius: 24%;
}
@media (min-width: 768px) {
  .social-bar__link img { width: 18px; height: 18px; }
}

/* ------------------------------------------------------------ homepage -- */

/* Hero slideshow — a deliberate, user-approved exception to design §6 ("no
   auto-rotating carousels"); see CLAUDE.md for the record and JS mitigations
   (slow interval, pause on hover/focus, permanent stop after interaction,
   silent under prefers-reduced-motion). Crossfade via opacity, not a sliding
   transform: an opacity-only transition is what the site-wide
   prefers-reduced-motion rule in main.css already collapses to ~0ms, so
   "no transition under reduced motion" falls out of the existing rule for
   free rather than needing a second, carousel-specific override. */
.slideshow {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-lg);
  background: var(--surface-raised);
  aspect-ratio: 4 / 3;
}

/* Full-bleed hero: edge to edge, no rounded corners or side border, and no
   section padding above it so it sits flush under the filter bar. The
   horizontal border is kept as a hairline separator from the content below. */
.section--hero { padding-block: 0 clamp(1.25rem, 0.8rem + 1.5vw, 2rem); }
.slideshow--bleed {
  border-radius: 0;
  border-inline: none;
  border-top: none;
}
@media (min-width: 640px)  { .slideshow { aspect-ratio: 21 / 9; } }
@media (min-width: 900px)  { .slideshow { aspect-ratio: 2.6 / 1; } }

.slideshow__track { position: relative; width: 100%; height: 100%; }

.slideshow__slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-slow) var(--ease-out);
}
.slideshow__slide.is-active { opacity: 1; pointer-events: auto; }
.slideshow__slide a, .slideshow__slide.is-active { display: block; width: 100%; height: 100%; }
.slideshow__slide img { width: 100%; height: 100%; object-fit: cover; }

.slideshow--static .slideshow__slide { position: relative; }

.slideshow__headline {
  position: absolute;
  left: 1rem; right: 1rem; bottom: 1rem;
  padding: 0.5rem 0.75rem;
  border-radius: var(--r-sm);
  background: rgba(26, 24, 71, 0.86);
  color: #FFFFFF;
  font-family: var(--font-display);
  font-size: 1.0625rem;
  width: fit-content;
}

/* Arrows and dots: 44px tap targets, visible against any photo via a solid
   scrim rather than relying on image contrast. */
.slideshow__arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: none;
  border-radius: 50%;
  background: rgba(26, 24, 71, 0.55);
  color: #FFFFFF;
  cursor: pointer;
  transition: background var(--dur-base) var(--ease-out);
}
.slideshow__arrow:hover, .slideshow__arrow:focus-visible { background: rgba(26, 24, 71, 0.8); }
.slideshow__arrow--prev { left: 0.625rem; }
.slideshow__arrow--next { right: 0.625rem; }

.slideshow__dots {
  position: absolute;
  z-index: 2;
  left: 50%;
  bottom: 0.75rem;
  transform: translateX(-50%);
  display: flex;
  gap: 0.5rem;
  padding: 0.375rem 0.5rem;
  border-radius: 999px;
  background: rgba(26, 24, 71, 0.4);
}
.slideshow__dot {
  width: 44px;
  height: 44px;
  margin: -12px 0;   /* visual dot stays small; tap target stays 44px */
  display: grid;
  place-items: center;
  border: none;
  background: transparent;
  cursor: pointer;
}
.slideshow__dot::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.55);
  transition: background var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}
.slideshow__dot.is-active::before { background: #FFFFFF; transform: scale(1.25); }

/* Shrunk from 168px cards (owner request): the rail showed under 2.5 cards
   at 360px before, which read as oversized for what is essentially a
   shortcut row, not the main event. 124px keeps 44px clear of the tap
   target floor even after the icon and padding shrink with it. */
.cat-rail {
  display: flex;
  gap: 0.625rem;
  margin-top: 0.625rem;
  padding-bottom: 0.5rem;
  scroll-snap-type: x mandatory;
}
.cat-rail__item {
  flex: 0 0 124px;
  scroll-snap-align: start;
  padding: 0.5rem;
  text-decoration: none;
  color: inherit;
  transition: transform var(--dur-slow) var(--ease-out);
}
@media (hover: hover) { .cat-rail__item:hover { transform: translateY(-2px); } }
.cat-rail__media { position: relative; }
.cat-rail__item img { border-radius: var(--r-sm); aspect-ratio: 4 / 3; object-fit: cover; width: 100%; }
.cat-rail__icon {
  position: absolute; left: 0.375rem; bottom: 0.375rem;
  display: grid; place-items: center;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--surface-raised);
  color: var(--navy);
  box-shadow: 0 2px 6px rgba(26, 24, 71, 0.22);
}
/* --navy on --surface-raised, which is the pairing the token is tuned for. */
.cat-rail__name { display: block; margin-top: 0.375rem; font-weight: 600; font-size: 0.875rem; }
.cat-rail__ur   { display: block; font-size: 0.8125rem; color: var(--ink-soft); }

.shop-block__card { overflow: hidden; }
.shop-block__card img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
.shop-block__body { padding: 1rem; display: grid; gap: 0.625rem; }

/* -------------------------------------------------------------- footer -- */

.site-footer {
  margin-top: clamp(1.5rem, 1rem + 2vw, 3rem);
  /* Deepens downward, so the page closes into the darkest navy on the site
     rather than stopping on a flat block. Same ramp direction as .filterbar. */
  background-color: var(--navy-deep);
  background-image: var(--navy-grad-foot);
  color: rgba(255, 255, 255, 0.92);
  padding-top: 1.75rem;
}
.site-footer a { color: rgba(255, 255, 255, 0.92); text-decoration: none; }
.site-footer a:hover { color: #FFFFFF; text-decoration: underline; }
/* 44px minimum tap target (design §8) — the Shop/Help link lists rendered at
   21px (line-height only, no padding), failing this project's own mobile rule. */
.site-footer .plain a { display: flex; align-items: center; min-height: 44px; }
/* The Shop column lists every category (~70 rows) in one flat list, making the
   footer roughly 2x the height of the rest of the homepage on mobile and a
   single unscannable wall on desktop. Multi-column at wider viewports keeps
   mobile as one readable column (now touch-target-sized) but lets desktop
   scan a grid instead of scrolling a list. */
/* .plain sets display:grid (later in this file, same specificity), which CSS
   multi-column layout has no effect on — column-count is silently ignored on
   a grid container. ul.site-footer__categories (element+class) outranks the
   plain class selector so the override actually wins the cascade. */
ul.site-footer__categories { display: block; gap: 0; }
@media (min-width: 480px) { ul.site-footer__categories { column-count: 2; column-gap: 1.5rem; } }
@media (min-width: 768px) { ul.site-footer__categories { column-count: 3; } }
.site-footer__categories li { break-inside: avoid; }
.site-footer h3 {
  font-size: 0.8125rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--cyan);
  margin-bottom: 0.625rem;
}
.site-footer .u-muted, .site-footer .u-faint { color: rgba(255, 255, 255, 0.66); }
.site-footer__name { font-size: 1.25rem; margin-bottom: 0.375rem; }
.site-footer__grid {
  display: grid;
  gap: 1.25rem;
  grid-template-columns: 1fr;
}
@media (min-width: 640px) { .site-footer__grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 960px) { .site-footer__grid { grid-template-columns: 2fr 1fr 1fr 1.5fr; } }
.site-footer__base {
  margin-top: 1.25rem;
  padding-block: 0.75rem;
  border-top: 1px solid rgba(255, 255, 255, 0.14);
}

.plain { list-style: none; display: grid; gap: 0.375rem; }
