:root {
  --ink: #151515;
  --paper: #f5f4f1;
  --paper-subtle: #eae9e5;
  --white: #ffffff;
  --line: #dedcd7;
  --muted: #4f4d49;
  --lime: #d7ff3f;
  --radius: 4px;
  --transition: 0.22s cubic-bezier(0.16, 1, 0.3, 1);
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 14px/1.5 'Manrope', -apple-system, BlinkMacSystemFont, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}
button, input, select, textarea { font: inherit; }
button { cursor: pointer; border: 0; background: transparent; }
a { color: inherit; text-decoration: none; cursor: pointer; }
img { width: 100%; display: block; object-fit: cover; }
:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

.mono { font-family: 'DM Mono', monospace; letter-spacing: 0.08em; text-transform: uppercase; }
.eyebrow {
  font: 11px/1.3 'DM Mono', monospace;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 500;
}

/* Announcement Bar */
.announce {
  min-height: 38px;
  background: var(--ink);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  font: 11px/1 'DM Mono', monospace;
  letter-spacing: 0.12em;
  padding: 4px 52px 4px 12px;
  text-align: center;
  z-index: 25;
}
.announce-content {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  flex-wrap: nowrap;
  max-width: 100%;
  white-space: nowrap;
}
.announce-copy { display: inline; }
/* The number in the claim line is the one thing in this bar a thumb wants to press:
   underlined and solid, so it reads as the action rather than as part of the sentence.
   app.js fills it from the constant, so index.html still names no number. */
.announce-phone {
  color: inherit;
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
  /* The number is the only thing in this bar a shopper acts on, so it is set as the
     largest text in the bar — 17px against the 11px sentence around it (14px against 9px
     on a phone) — instead of reading as one more word in the line. */
  font-size: 17px;
  letter-spacing: 0.02em;
  /* …and it is a thumb target: 44px tall like every other control on the page, rather
     than the 12px-tall run of text it used to be. The bar grows to hold it (38px → 52px)
     instead of the link spilling out of its own background, because a tap area that
     overhangs the bar is a tap the page behind it steals. `vertical-align` centres the
     sentence against it rather than leaving it on the link's baseline. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 8px;
  vertical-align: middle;
}
.announce-phone:hover,
.announce-phone:focus-visible { color: var(--lime); }
.announce button {
  position: absolute;
  right: 14px;
  color: #fff;
  /* Smaller than the number beside it, and 44x44 like every other control: an 18px
     glyph in a 40px box made the dismiss cross the largest thing in a bar whose whole
     point is the number. */
  font-size: 15px;
  line-height: 1;
  width: 44px;
  height: 44px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0.8;
  transition: var(--transition);
}
.announce button:hover { opacity: 1; }
.announce.hide { display: none; }
/* Dismissed for good. index.html puts this class on <html> while the head is parsed,
   before the bar exists, so a returning shopper never sees it — not even for a frame. */
html.announce-dismissed .announce { display: none; }

/* Navigation */
.nav {
  height: 76px;
  padding: 0 clamp(18px, 4vw, 56px);
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--line);
  background: rgba(245, 244, 241, 0.94);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  position: sticky;
  top: 0;
  z-index: 30;
}
.logo {
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -1.4px;
  display: inline-flex;
  align-items: center;
  user-select: none;
}
.logo i { font-style: normal; color: #6b675f; font-weight: 700; }
.nav-links {
  display: flex;
  gap: 28px;
  margin-left: 45px;
  flex: 1;
}
.nav-links a {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  position: relative;
  padding: 8px 0;
  color: var(--ink);
  transition: color var(--transition);
}
.nav-links a:hover, .nav-links a.active {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 8px;
}
.actions { display: flex; gap: 14px; align-items: center; }
.icon-btn {
  background: none;
  padding: 6px;
  font-size: 18px;
  line-height: 1;
  color: var(--ink);
  position: relative;
  border-radius: var(--radius);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition);
}
.icon-btn:hover { background: rgba(0,0,0,0.05); }
.hamb {
  display: none;
  font-size: 22px;
  padding: 6px;
  margin-right: 8px;
}

/* Page View Controller */
.page { display: none; }
.page.active { display: block; animation: fadeIn 0.25s ease-out; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }

/* Hero Section */
.hero {
  min-height: calc(100vh - 114px);
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  background: #dfdeda;
  position: relative;
  border-bottom: 1px solid var(--line);
}
.hero-copy {
  padding: clamp(48px, 8vw, 130px) clamp(22px, 6vw, 90px);
  align-self: center;
}
/* .hero h2 is the same headline on a listing page, where it steps down to a second-level
   heading (one h1 per document, and there it is the pair's name). Both tags are listed so
   the headline looks identical when a visitor navigates from a pair back to the home view. */
.hero h1, .hero h2 {
  font-size: clamp(46px, 6.2vw, 98px);
  line-height: 0.92;
  letter-spacing: -0.075em;
  margin: 20px 0 24px;
  max-width: 720px;
  font-weight: 800;
}
/* The headline is one ink, and that is deliberate: "LOOKING" used to be an outline here - a
   transparent fill with a -webkit-text-stroke as the whole of its ink - which is a word painted
   in nothing the moment the stroke's colour is the fill it sits on. Plain text cannot be painted
   in nothing, so there is no rule here and the word inherits the h1: same font, same black.
   test-hero-video.js W2/W3 fail if any .hero h1 rule paints text transparent or strokes it. */
.hero p {
  max-width: 420px;
  color: #3b3936;
  font-size: 15px;
  line-height: 1.65;
  margin-bottom: 34px;
}
.hero-image {
  min-height: 520px;
  position: relative;
  overflow: hidden;
  background: #cfceca;
}
.hero-stamp {
  position: absolute;
  bottom: 28px;
  right: 28px;
  color: #fff;
  background: rgba(21, 21, 21, 0.9);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.2);
  width: 110px;
  height: 110px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font: 9px/1.35 'DM Mono', monospace;
  letter-spacing: 0.12em;
  text-align: center;
  box-shadow: 0 10px 30px rgba(0,0,0,0.22);
  transform: rotate(-6deg);
  animation: stampWobble 40s ease-in-out infinite;
  user-select: none;
}
@keyframes stampWobble {
  0%, 100% { transform: rotate(-6deg); }
  50% { transform: rotate(-3deg); }
}
@media (prefers-reduced-motion: reduce) {
  .hero-stamp { animation: none; }
}

/* Shared Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 14px padding around 11px type came to 43px on screen: one pixel under the minimum
     every other control on the page holds. The call and text buttons — the whole point of
     the shop — are .btn, so they hold it too. */
  min-height: 44px;
  background: var(--ink);
  color: #fff;
  border: 1px solid var(--ink);
  padding: 14px 26px;
  font: 11px/1 'Manrope', sans-serif;
  font-weight: 800;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  border-radius: var(--radius);
  transition: var(--transition);
  cursor: pointer;
}
.btn:hover {
  background: #2b2b2b;
  border-color: #2b2b2b;
  transform: translateY(-2px);
  box-shadow: 0 4px 14px rgba(0,0,0,0.12);
}
.btn.light {
  background: transparent;
  color: var(--ink);
  border-color: var(--ink);
}
.btn.light:hover {
  background: var(--ink);
  color: #fff;
  transform: translateY(-2px);
}
.btn.white {
  background: #fff;
  color: var(--ink);
  border-color: #fff;
}
.btn.white:hover {
  background: var(--paper);
  border-color: var(--paper);
  transform: translateY(-2px);
}
.btn-row { display: flex; gap: 12px; flex-wrap: wrap; }

/* Three category tiles under the hero headline. Equal-width slices keep the row
   on one line at every desktop width; the longest label ("Clothing & Accessories")
   wraps inside its own tile instead of forcing the row past the headline column.
   Buttons stretch to match, so the three tiles share one baseline and height. */
.hero-tiles {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  max-width: 660px;
}
.hero-tiles .btn {
  padding: 16px 12px;
  min-height: 54px;
  font-size: 10px;
  line-height: 1.35;
  letter-spacing: 0.07em;
  text-align: center;
}
/* Narrow screens: one full-width tile per row beats three cramped ones. */
@media (max-width: 700px) {
  .hero-tiles { grid-template-columns: 1fr; max-width: 340px; }
}

/* Layout Sections */
.section {
  padding: clamp(55px, 6.5vw, 100px) clamp(18px, 4vw, 56px);
  max-width: 1600px;
  margin: auto;
}
.section-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  margin-bottom: 32px;
  gap: 16px;
  position: relative;
  padding-left: 70px;
}
.section-head::before {
  content: attr(data-index);
  position: absolute;
  left: 0;
  bottom: 0;
  font: 800 72px/1 'DM Mono', monospace;
  letter-spacing: -0.03em;
  color: rgba(21, 21, 21, 0.08);
  pointer-events: none;
}
/* Narrow phones: the section h2 and the "view all" link stop fitting on one line here,
   and the nowrap link is what gets pushed out - 6px of horizontal page scroll at
   360px, 45px at 320px. Wrapping drops the link onto its own line instead of off
   the screen, and leaves wider layouts exactly as they were. */
@media (max-width: 420px) {
  .section-head { flex-wrap: wrap; }
}
.section h2 {
  font-size: clamp(32px, 4.2vw, 56px);
  letter-spacing: -0.065em;
  line-height: 1;
  margin: 8px 0 0;
  font-weight: 800;
}
.text-link {
  font-weight: 800;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-bottom: 1px solid;
  padding-bottom: 4px;
  color: var(--ink);
  transition: opacity var(--transition);
  white-space: nowrap;
  display: inline-block;
}
.text-link:hover { opacity: 0.7; }

/* Product Grid & Cards */
.products {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 20px;
}
.product {
  min-width: 0;
  position: relative;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  transition: transform var(--transition), box-shadow var(--transition);
}
/* Hidden only while the reveal observer is armed — app.js sets body.reveals-armed when
   it has actually wired an IntersectionObserver up. Cards are visible by default so a
   render path that never reaches the observer (or a browser without one) can't draw an
   empty grid. :not(.reveal) keeps the revealed card's own rules intact, including the
   hover lift below. */
.reveals-armed .product:not(.reveal) {
  opacity: 0;
  transform: translateY(20px);
}
.product.reveal {
  opacity: 1;
  transform: none;
  transition: opacity 0.4s ease-out, transform 0.4s ease-out;
}
.product:hover {
  transform: translateY(-3px);
  box-shadow: 0 12px 30px rgba(0,0,0,0.06);
}
.product-image {
  /* Beige panel, matched to the hero: the owner asked 2026-10-29 for one beige
     across the page — the hero's #dfdeda — on the steps banner and on the card
     panels alike, replacing the darker #d3c9b0 the panels took in Ship 8. The
     white-canvas photos still sit cleanly on it. The hairline underneath
     stays from Ship 3. */
  background: #dfdeda;
  border-bottom: 1px solid var(--line);
  aspect-ratio: 1;
  overflow: hidden;
  position: relative;
  cursor: pointer;
  display: block;
  width: 100%;
  padding: clamp(10px, 3%, 22px);
  color: inherit;
  text-align: left;
}
.product-image::after {
  content: '';
  position: absolute;
  left: 18%;
  right: 18%;
  bottom: 8%;
  height: 10px;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(0,0,0,.16), transparent);
  opacity: 0;
  transition: opacity var(--transition);
}
.product:hover .product-image::after { opacity: 1; }
.product-image img {
  width: 100%;
  height: 100%;
  object-fit: contain; /* never crop — the whole sneaker stays visible */
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.product:hover .product-image img { transform: scale(1.06); }
/* Shortlist star: a card is a link-filled article, so the star is a sibling
   button parked over the photo's corner, never nested inside the photo link.
   The article is position: relative, so the button's corner is the card's. */
.save-btn {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 2;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: #fff;
  border: 1px solid var(--line);
  font-size: 18px;
  line-height: 1;
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
}
.save-btn.is-saved { background: var(--ink); color: #fff; }
.saved-title { font-size: clamp(26px, 4.5vw, 52px); letter-spacing: -0.06em; margin: 10px 0 30px; }
.saved-list { margin-top: 20px; }
.saved-actions { margin-top: 30px; }
.p-info {
  padding: 16px;
  display: flex;
  flex-direction: column;
  flex: 1;
}
.p-info .brand {
  font: 10px/1.2 'DM Mono', monospace;
  color: var(--muted);
  text-transform: uppercase;
  font-weight: 500;
}
.p-info .name {
  font-weight: 800;
  font-size: 14px;
  line-height: 1.3;
  margin: 5px 0 3px;
  /* Two lines are reserved so cards line up — but not under 44px: the title is a link
     to the pair's page and the second way into it a thumb reaches for, so it is a thumb
     target, not a line of text that happens to be clickable. */
  min-height: 44px;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  overflow-wrap: anywhere;
  cursor: pointer;
  padding: 5px 0;
  text-decoration: none;
  color: inherit;
  background: transparent;
  border: 0;
  text-align: left;
}
.p-info .name:hover { text-decoration: underline; }
.p-info .color, .p-info .sizes {
  color: var(--muted);
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.p-info .sizes { margin-bottom: 12px; }
/* When a size search is what drew this card, the size line is the answer, not the
   pair's run: ink rather than grey, so "US 11" reads as what was found. */
.p-info .sizes-match { color: var(--ink); font-weight: 700; }
.quick {
  width: 100%;
  margin-top: auto;
  /* 44px, not the 30px the 10px type and 10px padding added up to: it is the card's
     main call to action and the most-tapped thing in the grid. Centred rather than
     left-aligned because it is now a link as well as a label. */
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 10px 12px;
  background: var(--ink);
  color: #fff;
  font: 10px/1 'Manrope', sans-serif;
  font-weight: 800;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  text-decoration: none;
  border-radius: var(--radius);
  transition: var(--transition);
  opacity: 0.9;
}
.quick:hover {
  background: #2c2c2c;
  opacity: 1;
}

/* Brand Grid Section */
/* The grid sizes its own columns. The tile list is not a fixed length — the six
   roadmap brands, plus "All Brands", plus every other brand the catalog carries (five
   more today) — so a hand-picked column count goes wrong the moment the catalog
   changes: at eight columns the last row stopped short and left a hole where two tiles
   would have been, and the shorter rows it drew at other widths stretched the tiles
   past the shape the rest of the page is drawn in. auto-fit lays out as many 150px
   tracks as the row actually holds, so the tiles fill the width at every size and
   there is no per-breakpoint column count left to keep in sync. */
.brand-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
}
.brand-tile {
  height: 120px;
  background: #eae9e5;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  display: grid;
  place-items: center;
  gap: 6px;
  font-size: 13px;   /* what the "Soon" tag and anything unmarked falls back to */
  font-weight: 800;
  transition: var(--transition);
  cursor: pointer;
  padding: 10px 8px;
  text-align: center;
  user-select: none;
  width: 100%;       /* <button> inside a grid cell */
  font-family: inherit;
}

/* == The wordmarks ==
   Six of these tiles used to be a grey box with the brand typed into it, which was the
   one place on an otherwise carefully set page that read as a placeholder. A brand's
   lockup is mostly its letterforms, so each mark is *set*, not fetched: the two
   families the page already loads — Manrope at 800, and DM Mono for the one label here
   that is not a brand — shaped by weight, width, slant, tracking and case. No logo file
   is requested from anyone and there is no artwork to license, which is the point: the
   look is a wordmark's, the file is not somebody else's.

   A mark is described in two places and nowhere else: its entry in BRAND_MARKS in
   app.js (which words it and says how the name is written) and its rule here (which
   sets it). Each rule sets --wm-scale (how large the name is set) and --wm-skew (width
   and lean), so the whole of a brand's mark is one line.

   The text is scaled, not stretched, and the base size is one size for every tile: the
   tiles are within a few pixels of each other at every viewport width (auto-fit holds
   them near their 150px floor, and a phone fits two of them across), so a size that
   changes with the viewport would only make a phone's tiles and a desktop's tiles
   disagree about the same name. */
.brand-tile .wm {
  display: inline-block;
  font-family: 'Manrope', sans-serif;
  font-weight: 800;
  font-size: calc(15px * var(--wm-scale, 1));
  letter-spacing: var(--wm-track, -0.03em);
  line-height: 1;
  text-transform: var(--wm-case, uppercase);
  transform: var(--wm-skew, none);
}
/* JORDAN — condensed caps, leaning forward, closed up */
.brand-tile[data-mark="condensed"] .wm { --wm-scale: 1.15; --wm-skew: scaleX(0.88) skewX(-7deg); }
/* NIKE — four letters, set as large as the tile allows and leaning hard */
.brand-tile[data-mark="oblique"] .wm { --wm-scale: 1.5; --wm-track: -0.015em; --wm-skew: scaleX(0.86) skewX(-13deg); }
/* adidas — lowercase, upright, closed up tight */
.brand-tile[data-mark="bars"] .wm { --wm-scale: 1.3; --wm-case: none; --wm-track: -0.05em; }
/* The three bars, drawn rather than lifted: three bars whose tops step up to the right.
   The clip is cut before the skew, so the bars lean the way the mark leans, and they
   live inside .wm so they follow the name wherever the tile centres it (and take the
   tile's colour with them, which is what the dimmed "Soon" state needs). */
.brand-tile[data-mark="bars"] .wm::after {
  content: '';
  display: block;
  width: 21px;
  height: 9px;
  margin: 7px auto 0;
  background: repeating-linear-gradient(90deg, currentColor 0 4px, transparent 4px 8.5px);
  clip-path: polygon(0 100%, 100% 100%, 100% 30%, 66.6% 30%, 66.6% 55%, 33.3% 55%, 33.3% 78%, 0 78%);
  transform: skewX(-14deg);
}
/* new balance — lowercase and slanted, the way the tag reads */
.brand-tile[data-mark="lower"] .wm { --wm-scale: 0.95; --wm-case: none; --wm-skew: skewX(-10deg); }
/* TRAVIS SCOTT — two words in wide-tracked caps */
.brand-tile[data-mark="wide"] .wm { --wm-scale: 1.05; --wm-track: 0.12em; }
/* Supreme — the box: white, oblique, title case, on the red the box is printed in.
   Scoped to a tile that is actually stocked, so a brand that ran out goes back to the
   reserved dashed treatment instead of wearing a red box that says nothing. */
.brand-tile[data-mark="box"]:not(.soon) { background: #ce1126; border-color: #ce1126; }
.brand-tile[data-mark="box"]:not(.soon) .wm { color: #fff; }
.brand-tile[data-mark="box"] .wm { --wm-scale: 1.3; --wm-case: none; --wm-skew: skewX(-10deg); }
/* "All Brands" is the way back to the whole catalog, not a brand, so it is set as the
   label it is: the page's mono, spaced out, at the size the eyebrows are set in. */
.brand-tile[data-mark="all"] .wm {
  font-family: 'DM Mono', monospace;
  font-weight: 500;
  font-size: 11px;
  letter-spacing: 0.16em;
}
/* A brand the catalog carries that has no mark above: the same title treatment, set
   down as the name gets longer, so "The North Face" sits where "Huffy" sits. */
.brand-tile[data-len="med"] .wm { --wm-scale: 0.86; }
.brand-tile[data-len="long"] .wm { --wm-scale: 0.72; }

/* The lime fill is the only cue a tile is live, so keyboard focus has to
   reproduce it — otherwise the grid is invisible to anyone not on a mouse. */
.brand-tile:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.brand-tile:hover, .brand-tile.selected {
  background: var(--lime);
  border-color: #b4db1b;
  transform: translateY(-3px);
  box-shadow: 0 6px 16px rgba(0,0,0,0.06);
}
/* The box keeps its own fill when it is picked — the red is the brand's, not the
   page's — so the lime shows up as the border instead and a picked tile still reads as
   picked. The lift and the shadow come from the rule above. */
.brand-tile[data-mark="box"]:not(.soon):hover,
.brand-tile[data-mark="box"]:not(.soon).selected {
  background: #ce1126;
  border-color: var(--lime);
}
/* Brands listed on the roadmap but not stocked yet: dashed and recessed, so a
   visitor can tell "not here yet" from "not a real category". They still take
   focus and still explain themselves if clicked. */
.brand-tile.soon {
  border-style: dashed;
  border-color: #cfccc5;
  color: var(--muted);
}
.brand-tile.soon:hover {
  background: var(--paper);
  border-color: #bfbbb2;
  transform: none;
  box-shadow: none;
}
.brand-tile .tag {
  display: block;
  font: 8px/1 'DM Mono', monospace;
  letter-spacing: 0.14em;
  font-weight: 500;
  color: #6b675f;
  text-transform: uppercase;
}

/* Product detail bullet list (non-sneaker listings) */
.product-details {
  margin: 20px 0 0;
  padding-left: 18px;
  font-size: 14px;
  line-height: 1.7;
  color: var(--muted);
}

/* Category Tile */
.category-tile {
  position: relative;
  display: block;
  height: clamp(340px, 42vw, 600px);
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--ink);
  color: #fff;
}
/* The two banners in #categories sit back-to-back with nothing between them, so
   two dark gradients read as one giant block. A gap on the second tile — not on
   the section — separates them without moving the first tile. */
.category-tile + .category-tile { margin-top: 24px; }
.category-tile img {
  height: 100%;
  object-position: center 68%;
  transition: transform 1.2s ease;
}
.category-tile::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(21,21,21,0.8) 0%, rgba(21,21,21,0.1) 60%);
  pointer-events: none;
}
.category-tile:hover img { transform: scale(1.04); }
.category-tile:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.category-tile-copy {
  position: absolute;
  left: clamp(24px, 5vw, 64px);
  bottom: clamp(28px, 5vw, 56px);
  z-index: 2;
}
.category-tile-copy .eyebrow { color: #fff; }
.category-tile h3 {
  font-size: clamp(30px, 4.5vw, 64px);
  letter-spacing: -0.06em;
  line-height: 0.95;
  margin: 10px 0 22px;
  font-weight: 800;
}
.category-tile:hover .btn.white { transform: translateY(-2px); }
@media (prefers-reduced-motion: reduce) {
  .category-tile img, .category-tile:hover img { transition: none; transform: none; }
}

/* Authenticity Banner */
/* The banner shares the hero's beige: the owner asked 2026-10-29 for one beige
   across the page — the hero's #dfdeda — on the steps banner and on the card
   panels alike, replacing the darker #d3c9b0 this banner took earlier the same
   day. Both banners (How it works, Our Standard) take it, because they are the
   same component and a split pair would read as a mistake. */
.auth {
  background: #dfdeda;
  max-width: none;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.auth-inner { max-width: 1488px; margin: auto; }
.auth-grid {
  display: grid;
  grid-template-columns: 1.2fr 0.8fr;
  gap: 6vw;
  align-items: center;
}
.auth h2 {
  font-size: clamp(38px, 4.8vw, 70px);
  letter-spacing: -0.06em;
  line-height: 0.98;
  max-width: 660px;
  margin: 10px 0 18px;
}
.auth p {
  color: #3f3e3a;
  max-width: 460px;
  font-size: 15px;
  line-height: 1.6;
}
.steps { border-top: 1px solid #a8a49c; margin-top: 10px; }
.step {
  display: flex;
  align-items: center;
  gap: 24px;
  padding: 18px 0;
  border-bottom: 1px solid #a8a49c;
}
.step b { font: 12px 'DM Mono', monospace; color: #555; }
.step span { font-size: 19px; font-weight: 800; letter-spacing: -0.02em; }

/* Footer */
.footer {
  background: var(--ink);
  color: #fff;
  padding: 64px clamp(18px, 4vw, 56px) 28px;
}
.foot-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 32px;
  max-width: 1600px;
  margin: auto;
}
.foot-grid h4 {
  font: 11px 'DM Mono', monospace;
  color: #aaa;
  letter-spacing: 0.1em;
  margin: 0 0 16px;
  text-transform: uppercase;
}
.foot-grid a {
  display: block;
  margin: 9px 0;
  font-size: 13px;
  color: #dfdedc;
  transition: color var(--transition);
}
.foot-grid a:hover { color: #fff; text-decoration: underline; }
.foot-bottom {
  border-top: 1px solid #333;
  margin-top: 56px;
  padding-top: 22px;
  font: 11px 'DM Mono', monospace;
  color: #b0ada6;
  display: flex;
  justify-content: space-between;
  max-width: 1600px;
  margin-left: auto;
  margin-right: auto;
  flex-wrap: wrap;
  gap: 12px;
}

/* Shop View */
.shop-top {
  padding: 50px clamp(18px, 4vw, 56px) 20px;
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  max-width: 1600px;
  margin: auto;
}
/* The vault has no cart, so the shop header says how a pair is actually claimed before
   the grid starts. Aligned to .section so it sits over the same column as the cards. */
.shop-note {
  max-width: 1600px;
  margin: 0 auto;
  padding: 0 clamp(18px, 4vw, 56px);
  font-size: 13px;
  line-height: 1.7;
  color: var(--muted);
}
.shop-top h1, .shop-top h2, .about h1, .about h2 {
  font-size: clamp(42px, 6vw, 84px);
  letter-spacing: -0.08em;
  line-height: 0.92;
  margin: 10px 0 0;
  font-weight: 800;
}
.shop-layout {
  display: grid;
  grid-template-columns: 240px 1fr;
  gap: 40px;
}
.filters {
  border-top: 1px solid var(--line);
}
.filter {
  border-bottom: 1px solid var(--line);
  padding: 18px 0;
}
.filter label {
  font: 11px/1 'DM Mono', monospace;
  letter-spacing: 0.08em;
  font-weight: 700;
  display: block;
  margin-bottom: 10px;
  color: var(--ink);
}
.filter select, .filter input {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid #d0cdc6;
  background: #fff;
  border-radius: var(--radius);
  font-size: 13px;
}
.shop-actions {
  display: flex;
  gap: 12px;
  margin-bottom: 24px;
  justify-content: space-between;
}
.shop-actions input {
  padding: 11px 16px;
  border: 1px solid #ccc;
  background: #fff;
  flex: 1;
  max-width: 360px;
  border-radius: var(--radius);
  font-size: 13px;
}
/* The sidebar owns SEARCH above 820px. This second field is mobile-only, so
   the shop page no longer shows two identical search boxes side by side. */
#mobileSearch { display: none; }
.filter-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 11px 16px;
  border: 1px solid var(--ink);
  border-radius: var(--radius);
  background: #fff;
  color: var(--ink);
  font: 11px/1 'Manrope', sans-serif;
  font-weight: 800;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}
.filter-toggle[aria-expanded="true"] { background: var(--ink); color: #fff; }
.shop-actions select {
  padding: 11px 16px;
  border: 1px solid #ccc;
  background: #fff;
  border-radius: var(--radius);
  font-size: 13px;
}
.shop-actions #sizeFilter {
  flex: 0 1 190px;
  min-width: 0;
}
.clear-filters-btn {
  margin-top: 18px;
  width: 100%;
  padding: 10px;
  font: 10px 'DM Mono', monospace;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border: 1px dashed var(--muted);
  border-radius: var(--radius);
  color: var(--ink);
}
.clear-filters-btn:hover { background: #eae8e3; }

/* The search modal shows four cards and then the way to the rest: a button that
   hands the query to the shop grid, where every match is listed. */
.see-all-results {
  grid-column: 1 / -1;
  justify-self: center;
  margin: 8px 0 4px;
  padding: 11px 20px;
  border: 1px solid var(--ink);
  border-radius: var(--radius);
  background: #fff;
  color: var(--ink);
  font: 800 12px/1 'Manrope', sans-serif;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}
.see-all-results:hover { background: var(--ink); color: #fff; }

/* Empty State */
.empty-state {
  grid-column: 1/-1;
  padding: 80px 20px;
  text-align: center;
  color: var(--muted);
}
.empty-state p {
  font-size: 16px;
  font-weight: 700;
  margin-bottom: 16px;
  max-width: 400px;
  margin-left: auto;
  margin-right: auto;
}

/* Product Detail View */
.breadcrumbs {
  margin-bottom: 24px;
  font-size: 12px;
  color: var(--muted);
  letter-spacing: 0.02em;
}
.breadcrumbs a {
  color: var(--muted);
  text-decoration: none;
  transition: color 0.2s;
}
.breadcrumbs a:hover { color: var(--ink); }
.breadcrumbs span {
  margin: 0 8px;
  color: var(--line);
}
.breadcrumbs span:last-child {
  color: var(--ink);
  font-weight: 600;
}
@media (max-width: 600px) {
  .breadcrumbs {
    font-size: 11px;
    margin-bottom: 16px;
  }
  .breadcrumbs span {
    margin: 0 6px;
  }
}
.product-page {
  padding: 36px clamp(18px, 4vw, 56px) 90px;
  max-width: 1480px;
  margin: auto;
}
.detail {
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  gap: 5vw;
}
.gallery {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  /* The details column is taller than the gallery on a multi-photo listing
     (bullets, sizes, call buttons), and .details already takes align-self:
     start for its sticky position. Without the same here the gallery
     stretches to the row height and the spare space lands BETWEEN its rows
     — on the 3-photo Mariano page that read as a whole empty tile-height
     gap before the third photo. Top-aligned, the gallery is only as tall
     as its own rows. */
  align-self: start;
}
/* Each shot is a button rather than a bare <img>, so looking closer is reachable with
   Tab and Enter the way every other action on this page is — a click-only <img> is the
   one thing here a keyboard could not do. The tinted panel and the hairline move to
   this wrapper; the photo itself is still drawn whole and uncropped (object-fit:
   contain below), which is right for browsing and useless for checking a stitch line,
   so a press opens it full-viewport instead (see .zoom). */
.gallery-shot {
  display: block;
  width: 100%;
  padding: 0;
  aspect-ratio: 1;
  overflow: hidden;
  background: #e7e5e0;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  cursor: zoom-in;
}
/* Single-photo listings: span the full gallery width */
.gallery-shot:only-child { grid-column: 1 / -1; }
.gallery-shot img {
  width: 100%;
  height: 100%;
  object-fit: contain; /* never crop — the whole sneaker stays visible */
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.gallery-shot:hover img { transform: scale(1.04); }
.details {
  position: sticky;
  top: 100px;
  align-self: start;
}
.details h1 {
  font-size: clamp(32px, 3.8vw, 48px);
  letter-spacing: -0.06em;
  line-height: 1.05;
  margin: 8px 0;
  font-weight: 800;
}
.meta {
  font-size: 12px;
  color: var(--muted);
  line-height: 2;
  border-top: 1px solid var(--line);
  padding-top: 16px;
  font-family: 'DM Mono', monospace;
}
.size-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 8px;
  margin: 12px 0 24px;
}
.size-btn {
  padding: 12px 6px;
  background: #fff;
  border: 1px solid #ccc;
  border-radius: var(--radius);
  font-weight: 700;
  font-size: 13px;
  transition: var(--transition);
  text-align: center;
}
.size-btn:hover { border-color: var(--ink); }
/* A listing reached through a size search marks the sizes that answered it, so the run
   it is held in and the size that was asked for cannot be confused. */
.size-btn.is-match { box-shadow: inset 0 0 0 2px var(--ink); }
.size-match-note {
  margin: 12px 0 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
}
.size-btn.selected {
  background: var(--ink);
  color: #fff;
  border-color: var(--ink);
}
.detail-btns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 8px;
}
.detail-btns .btn:first-child { grid-column: span 2; padding: 16px; font-size: 12px; }
.perks {
  margin: 28px 0;
  border-top: 1px solid var(--line);
}
.perks p {
  margin: 0;
  border-bottom: 1px solid var(--line);
  padding: 12px 0;
  font-size: 13px;
  color: #333;
  display: flex;
  align-items: center;
  gap: 8px;
}
.perks p b { color: #151515; }

/* About View */
.about {
  padding: 70px clamp(18px, 8vw, 130px) 100px;
  max-width: 1400px;
  margin: auto;
}
.about > p {
  max-width: 620px;
  font-size: 18px;
  line-height: 1.7;
  margin: 28px 0 45px;
  color: #333;
}
.about-hero {
  height: 480px;
  margin: 50px 0;
  border-radius: var(--radius);
  overflow: hidden;
}
.about-hero img { height: 100%; object-fit: cover; }

/* Modals & Drawers */
.overlay {
  position: fixed;
  inset: 0;
  background: rgba(20, 20, 20, 0.55);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s cubic-bezier(0.16, 1, 0.3, 1);
  z-index: 40;
}
.overlay.open { opacity: 1; pointer-events: auto; }

.drawer-close {
  font-size: 24px;
  line-height: 1;
  padding: 4px 8px;
  color: var(--ink);
}
.drawer-close:hover { opacity: 0.7; }

/* Mobile Menu Drawer */
.mobile-drawer {
  position: fixed;
  left: 0;
  top: 0;
  height: 100dvh;
  width: min(320px, 85%);
  background: #fff;
  z-index: 50;
  transform: translateX(-100%);
  visibility: hidden;
  /* Off-screen is not hidden: a drawer parked at translateX(-100%) is still in
     the tab order and still exposed to assistive technology while closed. Hiding
     it the same way the order modal hides (visibility, delayed to the end of the
     slide) keeps the slide-in and takes the closed drawer out of both. */
  transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1), visibility 0s 0.35s;
  display: flex;
  flex-direction: column;
  box-shadow: 8px 0 35px rgba(0,0,0,0.15);
}
.mobile-drawer.open { transform: none; visibility: visible; transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1), visibility 0s; }
.mobile-drawer-head {
  padding: 22px;
  border-bottom: 1px solid var(--line);
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.mobile-links {
  display: flex;
  flex-direction: column;
  padding: 24px;
  gap: 18px;
}
.mobile-links a {
  font-size: 16px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* Search Modal */
.search-modal {
  position: fixed;
  inset: 0;
  background: var(--paper);
  z-index: 60;
  transform: translateY(-100%);
  visibility: hidden;
  /* Same rule as the drawer above: translated off-screen is still reachable,
     so visibility — delayed to the end of the slide-out — is what actually
     closes it to keyboards and assistive technology. */
  transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1), visibility 0s 0.35s;
  padding: 30px clamp(20px, 8vw, 120px);
  overflow-y: auto;
}
.search-modal.open { transform: none; visibility: visible; transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1), visibility 0s; }
.search-modal-close {
  position: absolute;
  right: clamp(20px, 4vw, 50px);
  top: 25px;
  font-size: 32px;
  line-height: 1;
  padding: 8px;
  color: var(--ink);
}
.search-bar {
  display: flex;
  border-bottom: 2px solid var(--ink);
  margin-top: 75px;
  padding-bottom: 8px;
}
.search-bar input {
  font-size: clamp(26px, 4.5vw, 52px);
  letter-spacing: -0.06em;
  border: 0;
  background: none;
  outline: 0;
  width: 100%;
  font-weight: 700;
}
.suggestions { margin-top: 32px; }
.suggestions-chips {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 12px;
}
.suggestions button {
  background: #fff;
  border: 1px solid var(--line);
  padding: 9px 16px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  transition: var(--transition);
}
.suggestions button:hover {
  background: var(--ink);
  color: #fff;
  border-color: var(--ink);
}

/* Order Modal
   Opened by app.js when a desktop click lands on a tel:/sms: link — a pointer
   with no phone behind it. Centred card, two columns: the message the shopper
   types, and the code their phone scans. Phone-width layouts stack (see the
   responsive query at the foot of this file), though touch devices normally open
   the dialer instead of this. */
.order-modal {
  position: fixed;
  left: 50%;
  top: 50%;
  width: min(880px, calc(100vw - 32px));
  max-height: calc(100dvh - 32px);
  overflow-y: auto;
  background: #fff;
  border-radius: var(--radius);
  box-shadow: 0 30px 80px rgba(0,0,0,0.35);
  z-index: 70;
  padding: clamp(22px, 3.2vw, 36px);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translate(-50%, -46%) scale(0.98);
  transition: opacity 0.3s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.3s cubic-bezier(0.16, 1, 0.3, 1),
              visibility 0.3s;
}
.order-modal.open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translate(-50%, -50%) scale(1);
}
.order-modal-close {
  position: absolute;
  right: 14px;
  top: 10px;
  font-size: 30px;
  line-height: 1;
  padding: 6px 10px;
  color: var(--ink);
}
.order-modal-close:hover { opacity: 0.7; }
.order-modal-grid {
  display: grid;
  grid-template-columns: 1fr minmax(240px, 290px);
  gap: clamp(18px, 3vw, 34px);
  align-items: start;
}
.order-modal h2 {
  font-size: clamp(21px, 2.4vw, 27px);
  letter-spacing: -0.03em;
  line-height: 1.15;
  margin: 10px 0 10px;
}
.order-modal-lede {
  font-size: 13.5px;
  line-height: 1.65;
  color: var(--muted);
  margin: 0;
}
.order-field { margin-top: 20px; }
.order-field label {
  display: block;
  font: 700 10px/1 'Manrope', sans-serif;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 8px;
}
.order-field textarea {
  width: 100%;
  min-height: 108px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: #fff;
  color: var(--ink);
  font: 500 13.5px/1.6 'Manrope', sans-serif;
  resize: vertical;
}
/* Keyboard focus keeps the site's ring (:focus-visible at the top of this file). This rule
   used to set outline: 0, so the only cue a keyboard user got was the border turning black —
   which at 1px on a white field is not a cue, and focus with no visible indicator fails
   WCAG 2.4.7. The border colour stays as the mouse cue; the ring is for tabbing. */
.order-field textarea:focus {
  border-color: var(--ink);
}
.order-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
  margin-top: 14px;
}
.order-hint {
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--muted);
  margin: 14px 0 0;
  max-width: 42ch;
}
.order-qr {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 16px;
  text-align: center;
}
.order-qr-tabs {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #fff;
  margin-bottom: 16px;
}
.order-qr-tabs button {
  font: 700 10px/1 'Manrope', sans-serif;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 8px 14px;
  border-radius: 999px;
  transition: var(--transition);
}
.order-qr-tabs button[aria-pressed="true"] {
  background: var(--ink);
  color: #fff;
}
.order-qr-code {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 232px;
}
.order-qr-code svg {
  display: block;
  width: 100%;
  max-width: 232px;
  height: auto;
  background: #fff;
  border-radius: 2px;
}
.order-qr-note {
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--muted);
  margin: 14px 0 0;
}

/* Photo Zoom
   Opened by a press on a gallery shot (.gallery-shot). The gallery draws every pair
   whole and uncropped, which is the right way to browse and the wrong way to check the
   thing this shop actually sells on: the stitch line, the box label, the production
   stamp. This shows that same file at the largest size the viewport allows.
   Two deliberate choices: the src is the URL already on the page, so it comes out of
   the browser's cache and costs no second download; and we never scale the image up
   ourselves, so the phone's own pinch still magnifies on top of it — which is the
   magnifier a low-vision shopper expects to keep.
   Sits above the search and order dialogs (60/70) and below the toast (90), which can
   therefore still report a copy over an open photo. */
.zoom {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
  padding: clamp(12px, 3vw, 46px);
  background: rgba(21, 21, 21, 0.97);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.25s cubic-bezier(0.16, 1, 0.3, 1), visibility 0.25s;
}
.zoom.open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}
.zoom img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  background: #fff;
  border-radius: var(--radius);
  box-shadow: 0 30px 80px rgba(0,0,0,0.5);
  touch-action: pinch-zoom;   /* the phone magnifies further on its own */
}
/* The close button and the arrows are the whole navigation here, so they are held at
   the same 44px minimum the rest of the page's thumb targets use. */
.zoom-close,
.zoom-step {
  position: absolute;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  color: #fff;
  font-size: 30px;
  line-height: 1;
  border-radius: 50%;
  transition: background 0.2s ease;
}
.zoom-close { top: 10px; right: 12px; }
.zoom-step { top: 50%; transform: translateY(-50%); }
.zoom-step.prev { left: 8px; }
.zoom-step.next { right: 8px; }
/* display above outranks the user-agent [hidden] rule, so a single-photo listing has to
   be told to hide its arrows explicitly. */
.zoom-step[hidden] { display: none; }
.zoom-close:hover, .zoom-step:hover { background: rgba(255,255,255,0.14); }
/* The page-wide focus ring is ink, which is invisible against this backdrop. */
.zoom-close:focus-visible, .zoom-step:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}
.zoom-count {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(10px, 2.5vw, 20px);
  text-align: center;
  font: 500 11px/1 'DM Mono', monospace;
  letter-spacing: 0.12em;
  color: #cfccc5;
}

/* Toast */
.toast {
  position: fixed;
  left: 50%;
  bottom: 28px;
  transform: translate(-50%, 120px);
  background: var(--ink);
  color: #fff;
  padding: 12px 20px;
  z-index: 90;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.4;
  text-align: center;
  border-radius: 999px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.25);
  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
  pointer-events: none;
  max-width: min(480px, calc(100vw - 32px));
  width: max-content;
  box-sizing: border-box;
}
.toast.show { transform: translate(-50%, 0); }

/* Responsive Queries */
@media (max-width: 1024px) {
  .products { grid-template-columns: repeat(3, 1fr); }
}
/* The announcement bar is one line of monospace that cannot reflow, so every width it
   has to survive is a budget. Measured in headless Chrome: the claim and the shipping
   line together are 769px of text inside a `vw - 64px` content box, so the desktop bar
   needs a window about 833px wide before its own line fits — at 640px it spilled 679px
   into 576px and dragged the whole document sideways (691px of page in a 640px window),
   and at 821px it was still clipping. So tablet and phone drop the shipping line and
   step the type down at 860px, with slack above the 833px the full line needs. */
@media (max-width: 860px) {
  .announce {
    font-size: 10px;
    letter-spacing: 0.08em;
    padding-left: 8px;
    /* Clears the 44px dismiss button so a long number and a long sentence cannot run
       under it. */
    padding-right: 66px;
  }
  .announce-phone { font-size: 15px; padding: 0 4px; }
  .announce button { font-size: 13px; }
  .announce-shipping { display: none; }
}
@media (max-width: 600px) {
  #recentlyViewedSection { display: none; }
  .section-head { padding-left: 45px; }
  .section-head::before {
    font-size: 36px;
    left: 0;
  }
  .announce { font-size: 9px; letter-spacing: 0.06em; }
  /* 14px: still the largest text in the bar. The dismiss cross is smaller again, so the
     number is the largest thing in the bar at every width. */
  .announce-phone { font-size: 14px; padding: 0 4px; white-space: nowrap; }
  /* Measured, not guessed: with the tail the claim line needs 361px, and a 390px phone
     leaves the bar 316px between its padding — the centred sentence hung 15px off the
     left edge, 45px on a 360px phone, so the first words were off the screen. Without
     it the line needs ~257px and fits the phones that matter. */
  .announce-tail { display: none; }
  /* Belt and braces for anything narrower (a 320px phone, a split view): the line wraps
     rather than leaving the screen. The number itself never breaks — a phone number cut
     over two lines is unreadable. */
  .announce-content { white-space: normal; flex-wrap: wrap; }
}
@media (max-width: 820px) {
  .nav { height: 66px; }
  .nav-links { display: none; }
  .hamb { display: block; }
  .hero { grid-template-columns: 1fr; min-height: auto; }
  .hero-copy { padding: 50px 20px 40px; order: 1; }
  .hero-image { min-height: 340px; order: 2; }
  .hero-stamp { width: 90px; height: 90px; font-size: 8px; bottom: 18px; right: 18px; }
  .products { grid-template-columns: repeat(2, 1fr); gap: 12px; }
  .p-info { padding: 12px; }
  /* No column count and no second guess at a size: auto-fit already lands on two
     across a phone, and the wordmarks are set once, with the rest of the mark. */
  .brand-tile { height: 80px; }
  .auth-grid { grid-template-columns: 1fr; }
  .foot-grid { grid-template-columns: 1fr 1fr; }
  .foot-grid > div:first-child { grid-column: span 2; }
  .shop-layout { grid-template-columns: 1fr; gap: 0; }
  .filters { display: none; }
  .filters.open {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 12px;
    margin-bottom: 20px;
  }
  .filters .filter:first-child { display: none; } /* search already sits above the grid on mobile */
  .filters .clear-filters-btn { grid-column: 1 / -1; margin: 14px 0 0; }
  .filter-toggle { display: inline-flex; flex: 1; }
  .shop-actions { flex-wrap: wrap; }
  .shop-actions input { max-width: none; flex: 1 1 100%; }
  .shop-actions select { flex: 1; min-width: 0; }
  .shop-actions #sizeFilter { flex: 1; }
  /* The sidebar's search field is hidden below this breakpoint (see the
     .filters .filter:first-child rule), so this one has to turn on here —
     otherwise mobile has no search box anywhere on the page. */
  #mobileSearch { display: block; }
  .detail { grid-template-columns: 1fr; }
  .details { position: static; }
  .product-page { padding: 25px 18px 60px; }
  .gallery { gap: 8px; }
}
@media (max-width: 480px) {
  /* The smallest phones used to drop to one card across, which turned 102 items
     into a very long scroll. Two across at 390px is still readable — a card is
     about 170px wide, the photo stays square, the name stays two lines — so the
     grid stays two-up here, with tighter gaps and padding to keep it compact.
     This deliberately reverses the earlier 1-up rule (see test-mobile H2). */
  .products { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .p-info { padding: 10px; }
  .p-info .name { font-size: 13px; }
  /* The sort menu shared a row with the size filter and the filter toggle, so at
     390px "Sort: Featured" clipped to "Sort: Fe,". The sort label is now just
     "Featured" (the select's aria-label still says "Sort products"), and the sort
     control takes a double share of the row, so the full word fits. */
  .shop-actions #sort { flex: 2 1 0; }
  .hero h1, .hero h2 { font-size: 38px; }
  .foot-grid { grid-template-columns: 1fr; }
  .foot-grid > div:first-child { grid-column: span 1; }
  .toast { border-radius: 16px; padding: 12px 18px; }
}

/* ===== Cleanup pass: skip link, touch targets, footer, ordering CTA, motion ===== */
.skip-link {
  position: absolute;
  left: 12px;
  top: -80px;
  z-index: 200;
  background: var(--ink);
  color: #fff;
  padding: 14px 20px;
  border-radius: var(--radius);
  font: 700 12px/1 'Manrope', sans-serif;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  transition: top var(--transition);
}
.skip-link:focus { top: 12px; }

/* Touch targets: these were 27-32px, below the 44px guideline. */
.icon-btn {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.announce button {
  min-width: 40px;
  min-height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
/* Keep the wordmark's hit area the size of the wordmark, not the whole row. */
.logo { width: fit-content; }

/* WCAG 2.5.8 (Target Size Minimum): the footer links, the footer wordmark and the
   section "view all" arrows were all 20-22px tall — below the 24px floor. A taller
   line box (plus a few px of padding on the arrows) lifts every one of them over it
   without changing how they look. */
.foot-grid a { line-height: 24px; }
.text-link { line-height: 1.6; padding: 4px 0; }

/* Footer: four columns on desktop, with real contact and policy context. */
.foot-grid { grid-template-columns: 1.5fr 1fr 1fr 1.3fr; }
.foot-head {
  font: 11px 'DM Mono', monospace;
  color: #aaa;
  letter-spacing: 0.1em;
  margin: 0 0 16px;
  text-transform: uppercase;
}
.foot-tag { color: #b5b3ad; max-width: 300px; font-size: 13px; line-height: 1.7; margin: 14px 0 0; }
.foot-note { color: #b5b3ad; font-size: 13px; line-height: 1.7; margin: 0; max-width: 280px; }
footer .logo i { color: #b5b2ac; }

/* Product page: size chips are informational, so the contact CTA does the work. */
.order-cta {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin: 8px 0 0;
}
.order-cta .btn { padding: 15px 12px; font-size: 11px; }
.order-note {
  font-size: 12px;
  color: var(--muted);
  margin: 12px 0 0;
  line-height: 1.6;
}

/* The listing's own code: for a desk, where there is no dialer to hand an sms: link to.
   Hidden wherever the pointer is a finger — there a tap on the Text button opens
   Messages directly, and a phone cannot scan its own screen. It is the same question
   app.js asks in phoneHandlesLinks() for the order dialog, asked in CSS here because
   this block only ever has to be hidden, never built. */
.listing-qr {
  display: none;
  flex-wrap: wrap;
  align-items: center;
  gap: 18px;
  margin: 18px 0 0;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper-subtle);
}
@media (hover: hover) and (pointer: fine) {
  .listing-qr { display: flex; }
}
/* 4 screen pixels per module, drawn at that exact size by app.js: the code comes out
   228-260px wide depending on how long the item's sentence is, and every module is a
   whole number of pixels, which is what a scanner's grid sampling needs. Measured
   against two independent decoders: at 2-3px a 57-module code still reads from a
   straight screenshot, but only at 4px does it survive a capture smaller than drawn. */
.listing-qr-code { flex: 0 0 auto; }
.listing-qr-code svg {
  display: block;
  max-width: 100%;
  height: auto;
  background: #fff;
  border-radius: 2px;
}
.listing-qr-note {
  flex: 1 1 150px;
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--muted);
  margin: 0;
}
.listing-qr-note b { color: var(--ink); }
.listing-qr-message {
  display: block;
  margin-top: 8px;
  font-family: 'DM Mono', monospace;
  font-size: 11px;
  line-height: 1.5;
  color: var(--ink);
  word-break: break-word;
}

@media (max-width: 820px) {
  .hamb {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
  }
  .foot-grid { grid-template-columns: 1fr 1fr; }
  /* The order modal stacks: the message and its buttons first, the code beneath. */
  .order-modal-grid { grid-template-columns: 1fr; }
  .order-qr-code { min-height: 0; }
  /* iOS Safari zooms the whole page in the moment a field under 16px takes focus, and
     it does not zoom back out on blur — so tapping the shop's search box or a filter
     left the page stuck at about 1.3x with its own controls half off the screen until
     the shopper pinched it back. The phone search box, the sort menu and the filter
     menus were all 13px. 16px is the exact threshold that suppresses it; only the
     phone layout pays the extra 3px, and the desktop rules keep their 13px. */
  .shop-actions input,
  .shop-actions select,
  .filter input,
  .filter select { font-size: 16px; }
}
@media (max-width: 480px) {
  .foot-grid { grid-template-columns: 1fr; }
  .order-cta { grid-template-columns: 1fr; }
}

/* Honour a reduced-motion preference: the router scrolls and the modals slide. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ===== Hero video slot =====
   There is no still layer any more: the owner asked 2026-10-30 to remove the Jordan
   photo because it showed while the clip was still loading. The clip sits on the
   panel's #cfceca background and is only faded in by JS once it has decoded, so
   dropping video/reel.mp4 in place is the whole install. Reduced-motion users get
   the blank panel, not the clip. */
.hero-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  opacity: 0;
  transition: opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1);
  pointer-events: none;
}
.hero-video.is-ready { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .hero-video { display: none !important; }
}
