/* ==========================================================================
   Watch Locker — watchlocker.ae
   Design system: charcoal + rose gold. Cormorant Garamond / Inter.
   ========================================================================== */

/* ---------- self-hosted brand fonts (no third-party CDN) ----------
   EB Garamond: editorial old-style serif for headings and prices.
   Outfit: clean geometric sans for body and interface.
   Both single-weight (400); the site uses no bold anywhere. */
@font-face{ font-family:"EB Garamond"; font-style:normal; font-weight:400; font-display:swap;
  src:url("../fonts/eb-garamond-latin-400-normal.woff2") format("woff2"); }
@font-face{ font-family:"Outfit"; font-style:normal; font-weight:400; font-display:swap;
  src:url("../fonts/outfit-latin-400-normal.woff2") format("woff2"); }

:root{
  /* lifted graphite palette, tuned to the logo plate (#0f0f0f -> #4b4b4b) */
  --bg:        #16161a;
  --bg2:       #1b1b20;
  --panel:     #212127;
  --panel2:    #26262d;
  /* --line is decorative only (dividers, card edges). --line2 is the boundary
     of anything interactive and must clear WCAG 1.4.11 (3:1) on --bg. */
  --line:      #3a3a42;
  --line2:     #63636f;   /* 3.05:1 on #16161a */

  /* Brand rose gold. --rose is M's specified value; the others are
     hue-locked tints and shades of it (H 13.7), so the family stays one
     colour. Measured on --bg: rose 6.86:1, rose2 9.07:1, rose-dim 5.28:1. */
  --rose:      #c99383;
  --rose2:     #d7afa3;
  --rose-dim:  #bd7a67;
  --rose-deep: #9c6350;

  --text:      #f7f2ec;
  /* THE TWO SECONDARY GREYS HAVE A RULE. They are only six per cent apart, so
     using them by feel reads as drift rather than hierarchy - which is exactly
     what an audit on 2026-08-06 found across a dozen rules. M's decision was to
     keep both and enforce the rule:

       --mute   PROSE and anything you can click. Paragraphs, ledes, body
                links, navigation, and interactive control labels (chips,
                segmented buttons) where dimming would cost affordance.
       --mute2  LABELS YOU CANNOT CLICK. Eyebrows, small-caps labels, meta
                lines, captions, counts, references, spec terms, form labels.

     Two deliberate exceptions, both de-emphasising a heading rather than
     colouring a label: `.wcard.is-sold .wmodel` and `.dprice.soldp` use --mute
     to dim a sold piece's title and price. Leave them.

     If you are unsure which to use, ask whether the text is a sentence or a
     tag. Sentences and links are --mute; tags are --mute2. */
  --mute:      #c5bcb3;
  --mute2:     #a89e94;


  /* ---- surfaces, named. Every one of these was a bare hex somewhere in the
     rules before 2026-08-06; the audit found 16 hard-coded colours outside
     this block, which is how a "consistent" palette drifts without anyone
     noticing. If you need a new shade, add it here first. ---- */
  --panel3:  #232329;   /* top of the watch-card gradient */
  --sink:    #191920;   /* bottom of the watch-card gradient */
  --well:    #1a1a20;   /* photo wells: main image, thumbnails */
  --footer:  #101014;   /* the footer plate, one step below --bg */
  --ink:     #1a1a1e;   /* text ON rose: buttons, chips, the skip link */
  /* A quieter ink for secondary text on rose - a count beside a label. It is a
     COLOUR, not --ink at reduced opacity: opacity blends unpredictably against
     whatever is behind and was the cause of every contrast failure the audit
     found. 4.85:1 on --rose, so it passes AA at 11px. */
  --ink-soft: #3a3030;
  /* Darker still, for a NUMERAL on rose. --ink-soft was chosen for a small-caps
     label and measures 4.85:1, which cleared AA by four hundredths at 11px; a
     numeral badge is now 13px but it is also the thing a buyer reads at a
     glance, so it takes the stronger ink. 5.85:1 on --rose. */
  --ink-num:  #2b2320;
  /* The overture's curtain. One step below --footer, because a plate that
     lifts off the page should read as deeper than anything on it. */
  --curtain:  #0b0b0e;
  --lift:    #2a2a31;   /* the lit top corner of a raised panel */
  --wash-hi: #1c1c21;   /* page wash, top */
  --wash-lo: #131317;   /* page wash, bottom */
  --ok:      #7fb08a;   /* the available dot. The ONLY hue on the site that is
                           neither rose nor neutral, and deliberately so. */

  /* ---- letter-spacing scale. The audit found 21 distinct values across the
     stylesheet, computing to 33 different pixel amounts on screen. There are
     only five intents. Snap to these; do not invent a sixth. ---- */
  --ls-tight:  .02em;   /* serif headings */
  --ls-body:   .04em;   /* body and UI text */
  --ls-wide:   .14em;   /* small-caps labels, buttons, meta */
  --ls-wider:  .18em;   /* section labels, nav */
  --ls-widest: .24em;   /* eyebrows and the brand mark */

  --serif: "EB Garamond", Georgia, "Times New Roman", serif;
  --sans:  "Outfit", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;

  /* ---- type scale (minor third, 16px base). Nothing outside this list. ----
     --fs-micro is for eyebrows, chips and form labels only: never body copy. */
  /* Absolute, at M's instruction (2026-08-06): NOTHING renders at a size that
     is not on this list. The audit found 10px and 12px loose in the rules; both
     were pulled onto the nearest step rather than added as new ones. */
  --fs-micro: 11px;
  --fs-tiny:  13px;
  --fs-sm:    15px;
  --fs-body:  16px;
  --fs-lede:  clamp(16px, 1.15vw, 19px);
  --fs-h4:    21px;
  --fs-h3:    26px;
  --fs-h2:    clamp(28px, 3.2vw, 40px);
  --fs-h1:    clamp(32px, 4.2vw, 50px);
  /* The hero was 76px at full width, which shouted. 60px still clears the
     page H1 (max 50px) so the hierarchy holds. */
  --fs-hero:  clamp(34px, 5vw, 60px);

  /* ---- spacing scale. Snap to these; do not invent values. ---- */
  --s1:4px;  --s2:8px;   --s3:12px; --s4:16px; --s5:24px;
  --s6:32px; --s7:48px;  --s8:64px; --s9:96px; --s10:128px;
  /* the strip the fixed WhatsApp button owns at the foot of every window */
  --wa-keepout:76px;
  /* the sticky header: 84px of bar and its 1px rule. A page panel is the
     window MINUS this, because the header floats over the top of every one. */
  --hdr-h:85px;

  /* ---- minimum comfortable hit area (Apple 44pt) ---- */
  --tap: 44px;

  /* THE PHOTOGRAPH ASPECT RATIO. M's camera and the ERP's gallery export
     produce 1200x1600 PORTRAIT files, and the watermark sits in the bottom
     quarter. Every frame on this site was aspect-ratio:1/1 with
     object-fit:cover, which removed 400px - 25% of the height, 200 from the
     top and 200 from the bottom - and took the watermark with it. That is
     what he meant by "the photos are cropped and lose the watermark", and an
     earlier analysis wrongly cleared the website because it measured a stale
     square mirror of his folder rather than his folder.
     Match the source. If the photography ever changes shape, change this one
     value; do not reintroduce a hard-coded ratio anywhere below. */
  --shot-ar: 3 / 4;

  /* ~68-72 rendered characters in Alata: the readable line length. */
  /* 60ch, not 50ch. M, 2026-08-11, on the FAQ: "the column width is too narrow
     here. Explore making this larger, and applying the same rule universally
     for other sections sharing the same font / style." This token IS that
     universal rule - it governs the FAQ answers, the About lede, the legal
     pages, `.dstory p`, `.step p`, `.gdetail dd` and `.col` - so one value
     moves all of them together, which is what he asked for.
     Rendered at 1440px and compared side by side at 50, 56, 60, 64 and 68ch:
     50ch was 525px and about 68 characters a line, 60ch is 630px and about 81.
     He chose 60ch from the comparison. It is the first width that fills the
     panel rather than leaving a ribbon down the middle, and it is still inside
     the band that reads comfortably - which 68ch, at about 92, is not. */
  --measure: 60ch;

  --wrap: 1220px;
  --r: 2px;
  --ease: cubic-bezier(.22,.61,.36,1);
}

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

html{ scroll-behavior:smooth; -webkit-text-size-adjust:100%; }

body{
  margin:0;
  background:var(--bg);
  color:var(--text);
  font-family:var(--sans);
  font-size:var(--fs-body);
  line-height:1.7;
  -webkit-font-smoothing:antialiased;
  /* overflow-x:hidden on body turns body into a scroll container, which stops
     position:sticky working on descendants in Safari — that is the sticky
     header, the collection filter bar and the watch photograph. overflow:clip
     suppresses the same sideways scroll without creating a scroll container.
     hidden stays as the fallback for browsers that predate clip. */
  overflow-x:hidden;
}
@supports (overflow-x: clip){
  body{ overflow-x:clip; }
}

/* subtle vignette tuned to the logo plate */
body::before{
  content:"";
  position:fixed; inset:0; z-index:-1; pointer-events:none;
  background:
    radial-gradient(1200px 700px at 50% -12%, rgba(201,147,131,.13), transparent 62%),
    radial-gradient(1000px 800px at 100% 100%, rgba(201,147,131,.07), transparent 60%),
    linear-gradient(180deg,var(--wash-hi) 0%, var(--bg) 42%, var(--wash-lo) 100%);
}

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

/* The [hidden] attribute is only display:none in the user-agent stylesheet, so
   ANY author display rule beats it. .wcard is display:flex, which silently
   broke every filter on the collection page: cards were marked hidden and
   stayed on screen. Never remove this rule. */
[hidden]{ display:none !important; }
a{ color:inherit; text-decoration:none; }

h1,h2,h3,h4{ font-family:var(--serif); font-weight:400; margin:0; letter-spacing:var(--ls-tight); line-height:1.15; text-wrap:balance; }
p{ margin:0 0 1.1em; text-wrap:pretty; }
p:last-child{ margin-bottom:0; }

::selection{ background:rgba(201,147,131,.32); color:#fff; }

:focus-visible{ outline:2px solid var(--rose); outline-offset:3px; border-radius:var(--r); }

.wrap{ width:min(var(--wrap), calc(100% - 48px)); margin-inline:auto; }
.narrow{ width:min(820px, calc(100% - 48px)); margin-inline:auto; }

/* ---------- shared type helpers ---------- */
.eyebrow{
  font-family:var(--sans);
  font-size:var(--fs-micro); letter-spacing:var(--ls-widest); text-transform:uppercase;
  color:var(--rose); font-weight:400; margin:0 0 var(--s4);
}
/* Retired 2026-08-06. A section eyebrow is rose gold everywhere on this site;
   this grey variant existed on two sections only and made them read as a
   different kind of heading for no reason. Left as a no-op rather than
   deleted, so an older cached page does not lose its colour entirely. */
.eyebrow.mute{ color:var(--rose); }

.h-xl{ font-size:var(--fs-h1); }
.h-lg{ font-size:var(--fs-h2); }
.h-md{ font-size:var(--fs-h3); }
.h-sm{ font-size:var(--fs-h4); }

.lede{ font-size:var(--fs-lede); color:var(--mute); max-width:46ch; text-wrap:balance; }
.small{ font-size:var(--fs-sm); color:var(--mute); }
.tiny{ font-size:var(--fs-tiny); color:var(--mute2); letter-spacing:var(--ls-body); }

.hairline{
  width:56px; height:1px; margin:22px 0 0;
  background:linear-gradient(90deg, var(--rose), transparent);
}
.hairline.center{ margin-inline:auto; background:linear-gradient(90deg, transparent, var(--rose), transparent); }

/* BUY / SELL / TRADE / CONSIGN / SOURCE. Every word is a link, in the closing
   band and in the footer alike, so the row is a way into the site rather than
   a label. The underline appears on hover only; five permanently underlined
   words would read as a menu. */
/* These became links on 6 Aug and were left at the line height of the text
   they replaced - 14px tall. A standalone row of links is not covered by the
   WCAG inline-text exception, so each one gets a real target. Padding with a
   matching negative margin, so the row gains a hit area without gaining a
   step of leading. */
.srvrow a, .fserv a{
  color:inherit; text-decoration:none;
  display:inline-block; padding:15px 5px; margin:-15px 0;
}
.srvrow a:hover, .fserv a:hover{ color:var(--rose2); text-decoration:underline; text-underline-offset:3px; }
.srvrow a:focus-visible, .fserv a:focus-visible{ outline:2px solid var(--rose); outline-offset:3px; }

/* ---------- buttons ---------- */
.btn{
  display:inline-flex; align-items:center; gap:10px;
  font-family:var(--sans); font-size:var(--fs-tiny); font-weight:400;
  letter-spacing:var(--ls-wider); text-transform:uppercase;
  min-height:var(--tap); padding:14px 30px; border-radius:var(--r);
  justify-content:center;
  border:1px solid var(--rose-dim); color:var(--rose2);
  background:transparent; cursor:pointer;
  transition:background .35s var(--ease), color .35s var(--ease), border-color .35s var(--ease), transform .35s var(--ease);
}
.btn:hover{ background:var(--rose); border-color:var(--rose); color:var(--ink); }
.btn:active{ transform:translateY(1px); }
.btn:focus-visible{ outline:2px solid var(--rose2); outline-offset:3px; }
.btn[disabled],.btn[aria-busy="true"]{
  opacity:.5; cursor:progress; pointer-events:none;
  background:transparent; border-color:var(--line2); color:var(--mute2);
}
.btn.solid{ background:var(--rose); border-color:var(--rose); color:var(--ink); }
.btn.solid:hover{ background:var(--rose2); border-color:var(--rose2); }
.btn.ghost{ border-color:var(--line2); color:var(--text); }
.btn.ghost:hover{ border-color:var(--rose); background:transparent; color:var(--rose2); }
.btn.sm{ padding:11px 20px; font-size:var(--fs-micro); letter-spacing:var(--ls-wide); }
.btn.wide{ width:100%; justify-content:center; }

/* The guarantee, stated in the hero rather than three clicks away. Mirrors
   the trust chips on the watch pages so the promise is the same everywhere. */
.herochips{
  list-style:none; margin:var(--s6) 0 0; padding:0;
  display:flex; flex-wrap:wrap; gap:var(--s2) var(--s5);
  font-family:var(--sans); font-size:var(--fs-micro); letter-spacing:var(--ls-widest);
  text-transform:uppercase; color:var(--mute2);
}
.herochips li{ display:flex; align-items:center; gap:10px; }
.herochips li + li::before{
  content:""; width:4px; height:4px; border-radius:50%; background:var(--rose);
  margin-right:var(--s3); flex:0 0 auto;
}
@media (max-width:640px){
  .herochips{ gap:var(--s2) var(--s4); font-size:var(--fs-micro); letter-spacing:var(--ls-wide); justify-content:center; }
  .herochips li + li::before{ margin-right:var(--s2); }
}

/* ---------- header ---------- */
.hdr{
  position:sticky; top:0; z-index:60;
  background:rgba(22,22,26,.82);
  backdrop-filter:saturate(140%) blur(14px);
  -webkit-backdrop-filter:saturate(140%) blur(14px);
  border-bottom:1px solid rgba(58,58,66,.9);
  transition:background .3s var(--ease);
}
.hdr-in{ display:flex; align-items:center; justify-content:space-between; gap:24px; height:84px; }
.brand{ display:flex; align-items:center; gap:var(--s3); flex:0 0 auto; min-height:var(--tap); }
.brand .bmark{ height:46px; width:auto; }
.brand .bword{ height:20px; width:auto; }
.brand:hover{ opacity:.86; }
.nav{ display:flex; align-items:center; gap:var(--s6); }
.nav a{
  font-size:var(--fs-tiny); letter-spacing:var(--ls-wide); text-transform:uppercase;
  color:var(--mute); position:relative;
  display:inline-flex; align-items:center; min-height:var(--tap); padding:0 2px;
  transition:color .3s var(--ease);
}
.nav a::after{
  content:""; position:absolute; left:0; right:0; bottom:11px; height:1px;
  background:var(--rose); transform:scaleX(0); transform-origin:left;
  transition:transform .38s var(--ease);
}
.nav a:hover,.nav a[aria-current="page"]{ color:var(--text); }
.nav a:hover::after,.nav a[aria-current="page"]::after{ transform:scaleX(1); }
.hdr-cta{ display:flex; align-items:center; gap:14px; }

.burger{
  display:none; width:var(--tap); height:var(--tap); align-items:center; justify-content:center;
  background:none; border:1px solid var(--line2); border-radius:var(--r); cursor:pointer;
}
.burger span{ display:block; width:18px; height:1px; background:var(--text); position:relative; transition:.3s var(--ease); }
.burger span::before,.burger span::after{
  content:""; position:absolute; left:0; width:18px; height:1px; background:var(--text); transition:.3s var(--ease);
}
.burger span::before{ top:-6px; } .burger span::after{ top:6px; }
.burger[aria-expanded="true"] span{ background:transparent; }
.burger[aria-expanded="true"] span::before{ top:0; transform:rotate(45deg); }
.burger[aria-expanded="true"] span::after{ top:0; transform:rotate(-45deg); }

.mnav{
  display:none; border-top:1px solid var(--line);
  background:rgba(20,20,24,.99); padding:14px 0 22px;
}
.mnav.open{ display:block; }
.mnav a{
  display:block; padding:15px 0; font-size:var(--fs-tiny); letter-spacing:var(--ls-wide);
  text-transform:uppercase; color:var(--mute); border-bottom:1px solid rgba(58,58,66,.7);
}
.mnav a:last-of-type{ border-bottom:0; }
/* NO margin-top on the Contact row. It used to sit 16px below the list, which
   is what made it read as a stray item rather than the last one - M's phone
   screenshot, 2026-08-11. The mobile override further down already turns the
   button into a list row; the gap was the only thing still detaching it. The
   desktop header button is a different element and is untouched. */

/* ---------- hero ----------
   THREE LAYOUTS are kept here on purpose: build.py picks one via WL_HERO
   (default "bleed"). .hero-split and .hero-centred are the alternates and
   are live the moment that variable changes, so they are not dead code.
   Three layouts share one set of pieces. `.hero` is the shell; the variant
   class on it decides whether the product sits beside, beneath, or behind. */
.hero{
  position:relative; overflow:hidden;
  padding:var(--s7) 0 var(--s6);
}
.hero-bg{
  position:absolute; inset:0; z-index:0;
  background:url("../img/hero-bg.jpg") center/cover no-repeat;
  transform:scale(1.04);
}
.hero::after{
  content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:
    radial-gradient(74% 64% at 50% 44%, rgba(28,28,33,.10), rgba(22,22,26,.68) 82%),
    linear-gradient(180deg, rgba(28,28,33,.30), rgba(28,28,33,.06) 34%, var(--bg) 92%);
}
.hero-in{ position:relative; z-index:2; }
.hero-mark{ display:none; }
.hero-centred .hero-mark,
.hero-bleed .hero-mark{
  display:block; width:min(260px, 56vw); margin:0 auto var(--s5);
  opacity:0; animation:rise .75s var(--ease) .06s forwards;
}
.hero h1{
  font-size:var(--fs-hero); font-weight:400; letter-spacing:var(--ls-tight); line-height:1.06;
  max-width:19ch; opacity:0; animation:rise .75s var(--ease) .17s forwards;
}
.hero h1 em{ font-style:normal; color:var(--rose2); }
.hero .lede{ margin:var(--s5) 0 0; max-width:40ch; opacity:0; animation:rise .75s var(--ease) .26s forwards; }
.hero .btnrow{ margin-top:var(--s7); opacity:0; animation:rise .75s var(--ease) .3s forwards; }

/* the featured piece */
.hero-media{
  position:relative; display:block; align-self:center;
  border:1px solid var(--line); border-radius:var(--r); overflow:hidden;
  background:linear-gradient(160deg,var(--lift),var(--well));
  opacity:0; animation:rise .8s var(--ease) .32s forwards;
  transition:border-color .4s var(--ease), box-shadow .4s var(--ease);
}
.hero-media:hover,.hero-media:focus-visible{ border-color:var(--rose-dim); box-shadow:0 22px 60px rgba(0,0,0,.5); }
.hero-media img{ width:100%; aspect-ratio:var(--shot-ar); object-fit:cover; transition:transform .9s var(--ease); }
.hero-media:hover img{ transform:scale(1.035); }
.hero-cap{
  display:flex; align-items:baseline; justify-content:space-between; gap:var(--s4);
  padding:var(--s4) var(--s5); border-top:1px solid var(--line);
  background:rgba(18,18,22,.72); backdrop-filter:blur(8px);
  -webkit-backdrop-filter:blur(8px);
}
.hero-cap b{ font-family:var(--serif); font-weight:400; font-size:var(--fs-h4); color:var(--rose2);
  white-space:nowrap; font-variant-numeric:lining-nums; font-feature-settings:"lnum" 1; }
.hero-cap i{ font-style:normal; display:block; }
.hero-cap .hm-brand{ font-size:var(--fs-micro); letter-spacing:var(--ls-wider); text-transform:uppercase; color:var(--rose); margin-bottom:5px; }
.hero-cap .hm-model{ font-family:var(--serif); font-size:var(--fs-sm); color:var(--text); line-height:1.3; }

/* ---- A: split. Copy left, the piece right. ---- */
.hero-split .hero-in{
  display:grid; grid-template-columns:minmax(0,1fr) minmax(0,440px);
  gap:var(--s8); align-items:center;
}
.hero-split{ padding:var(--s8) 0 var(--s7); }

/* ---- B: centred, compact, product directly beneath ---- */
.hero-centred{ text-align:center; }
.hero-centred .hero-in{ display:grid; place-items:center; min-height:min(52vh, 420px); }
.hero-centred .hero-mark{ margin-inline:auto; }
.hero-centred h1{ max-width:18ch; margin-inline:auto; }
.hero-centred .lede{ margin-inline:auto; text-align:center; }
.hero-centred .btnrow{ justify-content:center; }

/* ---- C: full bleed. The piece is the background. ---- */
.hero-bleed{ text-align:center; padding:var(--s9) 0 var(--s10); }
.hero-bleed .hero-bg{
  background-image:url("../img/hero-piece.jpg");
  background-position:center 44%;
}
.hero-bleed::after{
  background:
    radial-gradient(64% 64% at 50% 42%, rgba(18,18,22,.62), rgba(18,18,22,.88) 78%),
    linear-gradient(180deg, rgba(20,20,24,.70), rgba(20,20,24,.46) 30%, var(--bg) 95%);
}
@media (max-width:760px){
  .hero-bleed{ padding-bottom:var(--s7); }
  /* the square crop holds the dial better than a wide band on a phone */
  .hero-bleed .hero-bg{
    background-image:url("../img/hero-piece-sm.jpg");
    background-position:center 38%;
  }
  .hero-bleed::after{
    background:
      radial-gradient(84% 56% at 50% 40%, rgba(18,18,22,.58), rgba(18,18,22,.84) 82%),
      linear-gradient(180deg, rgba(20,20,24,.66), rgba(20,20,24,.42) 24%, var(--bg) 96%);
  }
}
.hero-bleed .hero-in{ display:grid; place-items:center; min-height:min(68vh, 560px); }
.hero-bleed .hero-mark{ margin-inline:auto; }
.hero-bleed h1{ max-width:18ch; margin-inline:auto; }
.hero-bleed .lede{ margin-inline:auto; text-align:center; color:var(--text); }
.hero-bleed .btnrow{ justify-content:center; }
/* The chips are part of the same centred column as the mark, headline and
   buttons. The separator dot carries a right margin, so the row is nudged by
   half of it to sit optically centred rather than mathematically centred. */
.hero-bleed .herochips{ justify-content:center; }
.hero-bleed .hero-cred{
  position:absolute; left:0; right:0; bottom:var(--s6); z-index:2; margin:0;
  padding-inline:var(--s5); text-align:center;
  font-size:var(--fs-micro); letter-spacing:var(--ls-wide); text-transform:uppercase; color:var(--mute2);
}
.hero-bleed .hero-cred a{ color:var(--rose2); display:inline-block; padding:var(--s2) 0; }
.hero-bleed .hero-cred a:hover{ color:var(--text); }
@media (max-width:760px){
  /* on a phone the hero content is tall, so the credit joins the flow
     rather than floating over the buttons, and the hero is trimmed so the
     first watch card still arrives within one scroll. */
  .hero-bleed{ padding:var(--s7) 0 var(--s6); }
  .hero-bleed .hero-in{ min-height:min(56vh, 420px); }
  .hero-bleed .hero-cred{ position:static; margin-top:var(--s5); }
}

@media (max-width:980px){
  .hero-split .hero-in{ grid-template-columns:1fr; gap:var(--s7); min-height:0; }
  .hero-split .hero-copy{ text-align:center; }
  .hero-split .hero-mark{ margin-inline:auto; }
  .hero-split h1{ margin-inline:auto; }
  .hero-split .lede{ margin-inline:auto; text-align:center; }
  .hero-split .btnrow{ justify-content:center; }
  .hero-media{ max-width:420px; margin-inline:auto; width:100%; }
}
@media (max-width:520px){
  .hero{ padding:var(--s6) 0 var(--s5); }
  .hero-mark{ width:88px; margin-bottom:var(--s4); }
  .hero h1{ max-width:16ch; }
  .hero .btnrow{ margin-top:var(--s5); }
  .hero-cap{ padding:var(--s3) var(--s4); }
}

@keyframes rise{ from{ opacity:0; transform:translateY(22px); } to{ opacity:1; transform:none; } }

/* ---------- sections ---------- */
/* Section rhythm.
   Sections used to carry clamp(64px, 8vw, 128px) top AND bottom, so two
   stacked sections put up to 256px of dead space between them — on About that
   was a 287px hole between one section's last line and the next one's heading,
   and it is worse on a phone where the eye has less to hold on to.

   Two changes. The band is smaller to begin with, and consecutive sections
   that share a background collapse their facing padding to half, the way
   margins would if this were a simpler document. A section that changes
   background (.alt) keeps its full padding, because the tint needs room to
   read as a band rather than a stripe. */
.sec{ --secpad:clamp(var(--s7), 5.5vw, var(--s9)); padding:var(--secpad) 0; position:relative; }
.sec + .sec{ padding-top:calc(var(--secpad) * .45); }
.sec.alt{ background:linear-gradient(180deg, transparent, rgba(33,33,39,.6) 12%, rgba(33,33,39,.6) 88%, transparent); }
.sec-head{ max-width:52ch; margin-bottom:var(--s6); }
.sec-head.center{ text-align:center; margin-inline:auto; }
.sec-head p{ margin-top:var(--s4); }

/* The outcome line at the top of each standard card: what the check means to
   the buyer, before the description of the check itself. */
.cardlead{
  font-family:var(--serif); font-size:var(--fs-h4); color:var(--rose2);
  line-height:1.35; margin:0 0 var(--s3);
}

/* ---------- reveal on scroll ---------- */
/* ---------- reveal on scroll ----------
   N6 (M's choice, 2026-08-07). This was 26px of travel over .9s with a 70ms
   stagger, which meant the fourth card in a row was still arriving 1.11s after
   the first one started - the whole of the sluggishness moving from the home
   page into the collection. Cut to 10px over .38s with a 30ms stagger and a
   sharper ease-out. Nothing else about the mechanism changed.
   DO NOT lengthen these again without saying so; they are a decision, not a
   default. */
:root{ --ease-n6:cubic-bezier(.2,.8,.2,1); }
/* A FLOOR HERE WAS THE WRONG FIX, AND IT LASTED ONE TEST RUN. M reported the
   services cards as looking hidden; the first attempt was `.rv{opacity:.35}` so
   nothing was ever invisible. axe immediately returned 20 colour-contrast
   failures on the home page and 34 on sell, because text dimmed with opacity
   blends against whatever is behind it and its ratio is unknowable - which is
   the exact fault this project already has a gotcha about, reintroduced by the
   person who wrote the gotcha.
   The real cause was in the SCRUBBER, not here: an element sitting in the lower
   half of the viewport at rest was held part-way through its reveal. It now
   resolves to full opacity as soon as it is entirely on screen, so anything you
   can see whole is fully drawn, and anything still below the fold stays at 0
   where it is invisible rather than dim. */
.rv{ opacity:0; transform:translateY(10px);
     transition:opacity .38s var(--ease-n6), transform .38s var(--ease-n6); }
.rv.in{ opacity:1; transform:none; }

/* READ THIS BEFORE ADDING A TRANSITION TO ANYTHING THAT ALSO CARRIES .rv.
   `.wcard` declares its own `transition:border-color,transform,box-shadow` for
   the hover lift. Both selectors are one class deep, so the later one wins the
   whole shorthand - which meant every watch card on the site was revealing with
   the CARD's transition list, where opacity does not appear at all. The cards
   were popping from invisible to visible with no fade, at .4s rather than the
   .38s N6 specifies, and the reveal M chose was never what he was looking at.
   Nothing in the markup or the JavaScript was wrong; one shorthand quietly ate
   another.
   The compound selector below is one class deeper, so it wins, and it carries
   BOTH intents: the reveal's opacity and transform, and the card's own hover
   properties. Transform is shared, so it takes the reveal's .38s - twenty
   milliseconds off the hover lift, which nobody can see.
   If you give another .rv element its own transition, do the same thing there. */
.wcard.rv{
  transition:opacity .38s var(--ease-n6), transform .38s var(--ease-n6),
             border-color .4s var(--ease), box-shadow .4s var(--ease);
}
/* Item 11, the hybrid. A card reveals ONCE with the transition above; from
   then on site.js hands it to a scroll-linked mode and writes opacity and
   transform itself every frame, so scrolling back up and down moves it with
   the finger instead of replaying an animation. The transition must be off in
   that mode or the two fight each other. */
.rv.scrubbed,
.wcard.rv.scrubbed{ transition:none; }

/* THE LANDING of the bow pull (M, 2026-08-08). When the hero is released, the
   collection is not simply scrolled to - it is dealt in, one element at a time,
   behind the hero's exit. `.snapin` is the same reveal with a longer stroke:
   28px of travel rather than 10 and a slightly larger duration, because this
   one arrives at the end of a gesture the visitor made themselves and deserves
   to be felt. site.js sets the per-element delay and then adds `.in`.
   Placed AFTER `.wcard.rv` deliberately - see the note above; equal specificity
   means source order decides, and this must win the duration. */
.rv.snapin{ transform:translateY(28px) scale(.992); transition-duration:.62s; }
/* The landing has to agree with the release about where the camera is. When the
   hero RECEDES rather than flying off the top, the collection should arrive by
   coming forward over it - so it starts slightly large and settles, instead of
   rising from below. Same duration, opposite geometry. */
.rv.snapin.fwd{ transform:translateY(-10px) scale(1.045); }
.rv.snapin.in{ transform:none; }

/* ---------- grids ---------- */
.g3{ display:grid; gap:var(--s5); grid-template-columns:repeat(3,1fr); }
@media (max-width:980px){ .g3{ grid-template-columns:repeat(auto-fit,minmax(min(260px,100%),1fr)); } }
.g4{ display:grid; gap:var(--s5); grid-template-columns:repeat(auto-fill,minmax(min(272px,100%),1fr)); }
/* min(320px,100%) not a bare 320px: a fixed minmax floor cannot shrink, so on
   a 320px screen the track overflowed the page by 17px. Every auto-fit grid
   here takes the same guard. */
.g2{ display:grid; gap:var(--s7); grid-template-columns:repeat(auto-fit,minmax(min(320px,100%),1fr)); align-items:center; }
/* Two prose columns of unequal length: align their tops, not their middles,
   and keep each to a readable measure. */
.g2.pair{ align-items:start; gap:var(--s7) var(--s8); }
/* Cap the whole column, not just its paragraphs, so both blocks are the same
   width whatever their content. Capping only the prose left the headings
   running to different widths, which is what made the pair look uneven. */
.g2.pair > div{ max-width:46ch; }
.g2.pair > div > .lede{ max-width:none; }

/* ---------- cards ---------- */
.card{
  background:linear-gradient(180deg, var(--panel), var(--bg2));
  border:1px solid var(--line); border-radius:var(--r);
  padding:var(--s6) 30px; position:relative; overflow:hidden;
  transition:border-color .4s var(--ease), transform .4s var(--ease);
}
.card:hover,.card:focus-within{ border-color:var(--line2); transform:translateY(-3px); }
.card .num{
  font-family:var(--serif); font-size:var(--fs-tiny); letter-spacing:var(--ls-wider);
  color:var(--rose-dim); display:block; margin-bottom:var(--s4);
}
.card h3{ font-size:var(--fs-h4); margin-bottom:var(--s3); }
.card p{ font-size:var(--fs-sm); color:var(--mute); max-width:46ch; }
.card .more{
  display:inline-flex; align-items:center; gap:var(--s2); margin-top:var(--s5);
  min-height:24px;
  font-size:var(--fs-micro); letter-spacing:var(--ls-wider); text-transform:uppercase; color:var(--rose2);
}
.card .more::after{ content:"→"; transition:transform .3s var(--ease); }
.card:hover .more::after{ transform:translateX(5px); }

/* ---------- watch card ---------- */
.wcard{
  background:linear-gradient(180deg,var(--panel3),var(--sink));
  border:1px solid var(--line); border-radius:var(--r);
  overflow:hidden; display:flex; flex-direction:column;
  transition:border-color .4s var(--ease), transform .4s var(--ease), box-shadow .4s var(--ease);
}
.wcard:hover,.wcard:focus-visible,.wcard:focus-within{ border-color:var(--rose-dim); transform:translateY(-4px); box-shadow:0 18px 44px rgba(0,0,0,.5); }
.wshot{
  position:relative; aspect-ratio:var(--shot-ar); overflow:hidden;
  background:
    radial-gradient(120% 90% at 50% 18%, rgba(201,147,131,.16), transparent 62%),
    linear-gradient(160deg,var(--lift),var(--well));
  display:grid; place-items:center;
}
.wshot img{ width:100%; height:100%; object-fit:cover; transition:transform .8s var(--ease); }
/* No zoom on hover: scaling the frame clips the outer edge of the
   photograph, which is where a watermark lives. The card lifts instead. */
.wcard:hover .wshot img{ transform:none; }
.wshot .ph{ width:auto; height:46%; max-width:56%; object-fit:contain; opacity:.30; }
/* Lifted clear of the C4 metadata strip, which occupies the bottom ~54px of
   the frame. Both only ever appear together on an unphotographed watch, and
   they were overlapping. */
.wshot .phlabel{
  position:absolute; bottom:46px; left:0; right:0; text-align:center;
  font-size:var(--fs-micro); letter-spacing:var(--ls-wider); text-transform:uppercase; color:var(--mute2);
}
/* The marks on a photograph: the ladder badge and, since WEB-14, the arrival
   mark. They stack in one column at the top left. The column - not each mark -
   carries the position, so adding a third mark later needs no new coordinates
   and cannot collide with the other two at any card width. */
.wmarks{
  position:absolute; top:12px; left:12px; right:12px; z-index:2;
  display:flex; flex-direction:column; align-items:flex-start; gap:6px;
  pointer-events:none;
}
.wtag{
  font-size:var(--fs-micro); letter-spacing:var(--ls-wider); text-transform:uppercase;
  padding:6px var(--s3); border-radius:var(--r);
  background:rgba(18,18,22,.86); border:1px solid var(--line2); color:var(--text);
  backdrop-filter:blur(6px);
  -webkit-backdrop-filter:blur(6px);
}
.wtag.rose{ color:var(--rose2); border-color:var(--rose-deep); }
/* WEB-14 - the arrival mark. Top RIGHT, so it never competes with the ladder
   badge on the left: one says what a watch is, the other says when it landed.
   Same plate, same blur, same micro type as .wtag - it is the same family of
   mark, not a new one - with the rose hairline to set it apart at a glance. */
/* M's choice, 2026-08-08: the SOLID rose plate, not the outline and not the
   neutral one. The ladder badge says what a watch IS and stays a quiet plate;
   the arrival mark is a stamp and is meant to pull the eye, so it is the only
   filled mark on the site. Charcoal on rose rather than white - the rose is
   light enough that white text falls under 4.5:1. */
.wnew{
  font-size:var(--fs-micro); letter-spacing:var(--ls-wider); text-transform:uppercase;
  padding:6px var(--s3); border-radius:var(--r);
  background:var(--rose); border:1px solid var(--rose); color:var(--bg);
}
/* Compacted 2026-08-06 at M's request. THE PHOTOGRAPH IS UNTOUCHED - it keeps
   its size and its 3:4 ratio. All of the length was in the body: 20px of
   padding on every side, and five stacked lines each carrying its own bottom
   margin. The margins are gone in favour of one explicit gap, so the rhythm is
   deliberate rather than the sum of whatever each element inherited. */
/* The padding went from 24px to 16px on 2026-08-06 because the body carried
   FIVE stacked lines and was too long. C4 took the metadata row out of the body
   on 2026-08-07, so it now carries three - and the compaction is no longer
   paying for anything. Restoring 24px on the scale brings the card back to
   287x599 - a pixel inside the 600 it measured before - rather than letting it
   quietly shrink by 25px, which is what M meant by keeping the original card
   size. Every card is now that height in its own right; before, the shorter
   ones were being stretched to match the tallest in their row. The photograph
   is untouched at 380px. */
.wbody{ padding:var(--s5); display:flex; flex-direction:column; flex:1; gap:3px; }
.wbrand{ font-size:var(--fs-micro); letter-spacing:var(--ls-widest); text-transform:uppercase; color:var(--rose); margin:0; }
/* The model name now reserves two lines whether or not it needs them. This is
   what pays for option C4 below: the metadata row left the body, and rather
   than let the card shrink, the height it freed goes here - so a two-line name
   like "Quai de l'Ile Titanium Skeleton" is no longer cramped and every card in
   a row is the same height without the grid having to stretch them. */
.wmodel{ font-family:var(--serif); font-size:var(--fs-h4); font-weight:400; line-height:1.22;
         margin:1px 0 0; min-height:calc(2 * 1.22em); }

/* ---------- C4: the metadata row, on the photograph ----------
   M's choice, 2026-08-07. Year, case size and what comes with the watch, as
   icons over a soft gradient at the foot of the picture. THE CONDITION SCORE IS
   DELIBERATELY GONE: the ERP holds sentences like "9/10 (Like new <1 yr old,
   except for one tiny scratch on bracelet)" and the card was truncating to
   "9/10" - keeping the number that invites an argument and throwing away the
   sentence that settles it. The full text lives on the listing page.
   The gradient is a real background, not opacity on the image: the watermark
   sits in the lower quarter of every photograph and must stay legible. */
.wmeta{ font-size:var(--fs-tiny); color:var(--mute2); letter-spacing:var(--ls-tight); margin:0; }
/* The strip is clipped by .wshot{overflow:hidden}, so anything that does not
   fit is simply cut off rather than wrapped - at 414px the "Box or papers"
   chip lost its last 13px. It now wraps and tightens instead of overflowing. */
/* THE FOOT OF THE FRAME IS A COLUMN, NOT A STACK OF COORDINATES (M, 2026-08-11).
   The dots used to be positioned against the bottom edge and the descriptor
   row pushed up out of their way, which is why M saw the descriptors floating
   with a gap beneath them. Both now live in one bottom-anchored column: dots
   first, descriptors last, so the descriptors sit ON the bottom edge and the
   dots ride above them. The column carries the position; neither child knows
   where it is. Add a third row here and nothing needs new numbers. */
.shotfoot{
  position:absolute; left:0; right:0; bottom:0; z-index:3;
  display:flex; flex-direction:column; align-items:center; gap:7px;
  padding:26px 8px 0;
  background:linear-gradient(180deg, transparent, rgba(12,12,15,.88) 46%);
  pointer-events:none;
}
.shotmeta{
  display:flex; align-items:center; justify-content:center; gap:var(--s3);
  flex-wrap:wrap; row-gap:2px;
  padding:0 0 9px;
  font-size:var(--fs-tiny); color:var(--mute); letter-spacing:var(--ls-tight);
}
.shotmeta span{ display:inline-flex; align-items:center; gap:6px; white-space:nowrap; }
.shotmeta svg{ width:13px; height:13px; flex:0 0 13px; color:var(--rose2); }

/* ---------- the card gallery (B1, M's choice 2026-08-11) ----------
   Swipe, arrows on hover, dots always. The dots are the only thing that tells
   a visitor a card holds more than one photograph, so they are the part that
   earns its place; the arrows are a desktop convenience and the swipe is the
   phone one.

   EVERY CONTROL HERE IS A <span aria-hidden="true">, NOT A <button>. The whole
   card is one anchor, and interactive content inside an anchor is invalid HTML
   and a genuine mess for a screen reader - a button you can tab into, inside a
   link you are already inside. So on the card these are pointer affordances
   only: assistive tech and the keyboard skip them entirely and get the real
   gallery, with real buttons, on the listing the card links to. That is the
   whole reason the listing gallery keeps <button>s and this one does not. */
.cshots{ position:absolute; inset:0; z-index:0; }
.cshots img{
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  opacity:0; transition:opacity .5s var(--ease);
}
.cshots img.on{ opacity:1; }
.gdots{ display:flex; align-items:center; justify-content:center; gap:6px; pointer-events:auto; }
.gdots i{
  width:6px; height:6px; border-radius:99px; cursor:pointer; display:block;
  background:rgba(247,242,236,.42);
  box-shadow:0 0 0 1px rgba(0,0,0,.55), 0 1px 3px rgba(0,0,0,.55);
  transition:background .3s var(--ease), transform .3s var(--ease);
}
.gdots i.on{ background:var(--rose); transform:scale(1.35); }
/* The arrow columns stop short of the foot so they never sit over the dots. */
.cnav{
  position:absolute; top:0; bottom:46px; width:34%; z-index:2;
  display:flex; align-items:center; cursor:pointer;
  opacity:0; transition:opacity .28s var(--ease);
}
.cnav.prev{ left:0; justify-content:flex-start; padding-left:8px; }
.cnav.next{ right:0; justify-content:flex-end; padding-right:8px; }
.cnav b{
  width:26px; height:26px; border-radius:50%; display:grid; place-items:center;
  background:rgba(0,0,0,.46); border:1px solid rgba(247,242,236,.26);
}
.cnav b::before{ content:""; width:6px; height:6px; border-top:1.5px solid #fff; border-right:1.5px solid #fff; }
.cnav.prev b::before{ transform:rotate(-135deg); margin-left:2px; }
.cnav.next b::before{ transform:rotate(45deg); margin-right:2px; }
.wcard:hover .cnav{ opacity:1; }
/* No hover on a touch screen, so the arrows would be dead weight - the swipe
   is the control there and the dots say how far along you are. */
@media (hover:none){ .cnav{ display:none; } }
@media (prefers-reduced-motion:reduce){
  .cshots img{ transition:none; }
  .gdots i{ transition:none; }
}
/* THE ROW NEVER WRAPS AND NEVER DROPS A DESCRIPTOR (M, 2026-08-11).
   The rule lives in the `max-width:520px` block near the end of this file,
   NOT here. A second `.shotmeta` rule already existed down there, and a
   duplicate breakpoint up here lost the cascade to it by one line - which is
   how the first attempt at this fix measured as applied and rendered as
   clipped. One breakpoint, one place. Search: THE ROW NEVER WRAPS. */
/* margin-top:auto pins the footer to the bottom of the card so a two-line
   model name never pushes "Details" out of line with its neighbours. Declare
   margin-top ONCE: a second margin-top later in the same block silently wins,
   which is how this broke on the service cards twice and here once. Use
   padding-top for the gap above the rule. */
.wfoot{ margin-top:auto; display:flex; align-items:flex-end; justify-content:space-between; gap:var(--s3);
        padding-top:var(--s5); border-top:1px solid var(--line); }
.wprice{ font-family:var(--serif); font-size:var(--fs-h4); font-weight:400; color:var(--text); line-height:1; font-variant-numeric:lining-nums; font-feature-settings:"lnum" 1; }
.wprice small{ font-family:var(--sans); font-size:var(--fs-micro); letter-spacing:var(--ls-wider); color:var(--mute2); display:block; margin-bottom:3px; }
.wprice.dm{ font-size:var(--fs-body); color:var(--rose2); letter-spacing:var(--ls-body); }
.wgo{ font-size:var(--fs-micro); letter-spacing:var(--ls-wider); text-transform:uppercase; color:var(--rose2); white-space:nowrap; }
.wgo::after{ content:" →"; }

/* ---------- filter bar ----------
   One row: an availability switch, a search box, a Refine button and sort.
   Everything else lives inside the Refine panel, which drops down beneath.
   Sixteen brand chips, three dropdowns, search and sort used to compete for
   the same strip; this reduces it to four controls. */
.fbar{
  position:sticky; top:84px; z-index:40;
  background:rgba(22,22,26,.94); backdrop-filter:blur(12px);
  -webkit-backdrop-filter:blur(12px);
  border-bottom:1px solid var(--line); padding:var(--s4) 0; margin-bottom:var(--s7);
}
.fbar-row{ display:flex; align-items:center; gap:var(--s3); flex-wrap:wrap; }

/* availability switch */
.seg{
  display:inline-flex; border:1px solid var(--line2); border-radius:99px;
  padding:3px; background:rgba(0,0,0,.25); flex:0 0 auto;
}
.segb{
  appearance:none; background:transparent; border:0; cursor:pointer; border-radius:99px;
  /* 44px, the same target every other control on the site holds to. It was
     38px - above the accessibility floor, but the one control that did not
     meet the house standard. */
  padding:0 var(--s5); min-height:var(--tap); white-space:nowrap;
  font-family:var(--sans); font-size:var(--fs-micro); letter-spacing:var(--ls-wider);
  text-transform:uppercase; color:var(--mute);
  transition:background .3s var(--ease), color .3s var(--ease);
}
.segb:hover{ color:var(--text); }
.segb[aria-pressed="true"]{ background:var(--rose); color:var(--ink); }
/* The count beside each label. It was opacity:.7, which on the pressed
   (rose) segment measured 3.83:1 - a real AA failure at 11px. Dimming is done
   with a chosen colour per state instead, so the ratio is knowable. */
/* NUMERAL BADGES. Option C, M's choice 2026-08-07. A count beside a label is
   not a small-caps label - it is a number, and it was being set at 11px in the
   label colour, which measured 4.85:1 on the pressed rose pill: AA by four
   hundredths. "Numeral badge" is now a role of its own, and there are exactly
   four on the site - these three counts and the Refine count. 13px, the
   brighter grey, tabular figures so the digits do not jitter as they change.
   There are SIX on the site, not the four I first counted: these three, the
   Refine count, and the two group-heading counts on the collection (.gcount).
   All six carry this treatment. If you add a seventh, give it this treatment
   too; do not invent a second numeral style. */
.segb .n, .fbtn .cnt{
  font-size:var(--fs-tiny); letter-spacing:var(--ls-body);
  font-variant-numeric:tabular-nums; font-feature-settings:"tnum" 1;
}
.segb .n{ color:var(--mute); margin-left:8px; }
.segb[aria-pressed="true"] .n{ color:var(--ink-num); }
/* AND SO DO THE CARDS. The grids are empty markup until render.js fills them,
   so at first paint the collection was a heading, a second heading and the
   closing band all stacked inside one screen - and when 35 cards arrived,
   everything below the first grid was pushed down past the fold. That was the
   rest of the 0.27 shift on this page.
   Reserving a screen and a half under the first empty grid puts everything
   that follows it BELOW the fold from the first frame, so when the cards land
   nothing a visitor can see moves. `:empty` stops matching the instant the
   cards are in, so the reservation never constrains the real grid. */
.g4:empty{ min-height:150vh; }
#sec-sold .g4:empty, #grid-hits:empty, #grid-near:empty, #wothers:empty{ min-height:0; }

/* THE COUNTS ARRIVE AFTER THE FIRST PAINT, and they were widening their own
   buttons when they did - which shifted the whole toolbar and was most of the
   0.298 layout shift measured on this page. The space is now reserved, so the
   numbers appear into a gap that was always there. `min-width` in `ch` because
   the numerals are tabular; two digits and a hair. An empty badge still
   collapses on the group headings, where nothing is waiting to arrive. */
.segb .n{ display:inline-block; min-width:2.4ch; text-align:right; font-variant-numeric:tabular-nums; }
.grouphead .gcount:empty{ display:none; }

.fsearch{
  display:flex; align-items:center; gap:9px; flex:1 1 220px;
  border:1px solid var(--line2); border-radius:var(--r); padding:0 13px; background:rgba(14,14,17,.55);
}
/* THE FOCUS RING GOES ON THE WRAPPER, because the input inside carries
   `outline:none` so that the field and the bar read as one control. Suppressing
   an outline without putting one back is the commonest way a site fails 2.4.7,
   and it failed here on the single most-used control on the page: a keyboard
   visitor tabbing along the toolbar had no idea when they had reached the
   search box. `:focus-within` puts the ring on the thing that looks like the
   control rather than on the bare input. */
.fsearch:focus-within{
  border-color:var(--rose); outline:2px solid var(--rose); outline-offset:2px;
}
/* F1 (M's choice, 2026-08-07). The bar carried two type treatments that nobody
   chose: switch and Refine at 11px uppercase on --ls-wider, search and sort at
   13px sentence case with letter-spacing:normal - the only text on the site not
   drawn from the tracking scale. Everything in the bar is now one treatment,
   the placeholder and the typed text included. Capitals here are a deliberate
   uniformity; if a future control joins this row, it takes this treatment too. */
.fsearch input, .fsel{
  background:transparent; border:0; color:var(--text);
  font-family:var(--sans); font-size:var(--fs-micro); letter-spacing:var(--ls-wider);
  text-transform:uppercase; padding:0; min-width:0; outline:none;
  min-height:var(--tap);
}
.fsearch input{ width:100%; }
/* The browser's own clear button is a blue X on a dark rose site. Replaced with
   a rose one drawn as a mask, so it inherits nothing from the platform. */
.fsearch input::-webkit-search-cancel-button{
  -webkit-appearance:none; appearance:none;
  width:13px; height:13px; cursor:pointer; background-color:var(--mute2);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 2 L14 14 M14 2 L2 14' stroke='black' stroke-width='1.6' fill='none' stroke-linecap='round'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 2 L14 14 M14 2 L2 14' stroke='black' stroke-width='1.6' fill='none' stroke-linecap='round'/%3E%3C/svg%3E") center/contain no-repeat;
}
.fsearch input::-webkit-search-cancel-button:hover{ background-color:var(--rose2); }
/* The placeholder needs saying explicitly - it does not inherit
   text-transform in every engine. */
.fsearch input::placeholder{
  color:var(--mute2); text-transform:uppercase; letter-spacing:var(--ls-wider);
  font-size:var(--fs-micro); opacity:1;
}
.fsel{
  border:1px solid var(--line2); border-radius:var(--r); padding:0 var(--s3);
  background:rgba(14,14,17,.55); cursor:pointer; flex:0 0 auto;
}
.fsel option{ background:var(--panel); color:var(--text); }

.fbtn{
  appearance:none; cursor:pointer; background:transparent; border:1px solid var(--line2);
  border-radius:var(--r); color:var(--text); min-height:var(--tap); padding:0 var(--s4);
  font-family:var(--sans); font-size:var(--fs-micro); letter-spacing:var(--ls-wider); text-transform:uppercase;
  display:inline-flex; align-items:center; gap:10px; flex:0 0 auto;
  transition:border-color .3s var(--ease), color .3s var(--ease);
}
.fbtn:hover{ border-color:var(--rose); color:var(--rose2); }
.fbtn .cnt{ background:var(--rose); color:var(--ink); border-radius:99px; padding:1px 8px; font-size:var(--fs-micro); letter-spacing:var(--ls-body); }
.fbtn .car{
  width:7px; height:7px; border-right:1px solid currentColor; border-bottom:1px solid currentColor;
  transform:rotate(45deg) translateY(-2px); transition:transform .3s var(--ease);
}
.fbtn[aria-expanded="true"] .car{ transform:rotate(225deg) translateY(-2px); }

/* the panel */
/* The panel is bounded, and every group sits on ONE ROW (M's instruction).
   It began as a wall roughly 740px tall whose brand column grew by a row for
   every brand added, so it could only ever get worse. Now each group is capped
   to a single row height and scrolls inside itself, which means the panel is
   the same size with sixteen brands or sixty. Nothing was removed except the
   Movement group, which M agreed to drop to make the single row fit; the
   `data-move` attribute is still on every card, so restoring it is a markup
   change and nothing else. */
.refpanel{
  margin-top:var(--s4); border:1px solid var(--line); border-radius:var(--r);
  background:var(--panel); padding:var(--s4) var(--s5);
}
.pgrid{
  display:grid; grid-template-columns:repeat(8, minmax(0,1fr));
  gap:var(--s4) var(--s5); align-items:start;
}
.pcol.wide{ grid-column:span 2; }
/* ---- ONE group label, used everywhere a set of things has a heading ----
   The Refine group headings and the footer column headings were the same
   object in two costumes: same font, same case, same size, one rose and one
   grey, for no reason beyond where each grew up. M's call was to make them one
   type in the brand's own formatting. `.eyebrow` is the same family - a rose
   small-caps label - and differs only in tracking, because a section eyebrow
   sits alone on a line while a group heading sits above a list.
   If you add a third place that heads a group, use `.pch`. Do not write a new
   label style. */
.pch, .fhead{
  font-family:var(--sans); font-size:var(--fs-micro); letter-spacing:var(--ls-wider); text-transform:uppercase;
  color:var(--rose); margin:0 0 var(--s2); font-weight:400;
  display:flex; align-items:baseline; gap:var(--s2);
}

/* Every group is the same height, so the row cannot go ragged. */
.pwrap{ position:relative; }
.pchips{
  display:flex; flex-wrap:wrap; gap:6px;
  max-height:118px; overflow-y:auto; overscroll-behavior:contain;
  padding-right:var(--s2); scrollbar-width:thin;
  scrollbar-color:var(--line2) transparent;
}
.pchips::-webkit-scrollbar{ width:6px; }
.pchips::-webkit-scrollbar-track{ background:transparent; }
.pchips::-webkit-scrollbar-thumb{ background:var(--line2); border-radius:6px; }

/* The permanent scroll indicator. A group with more options than fit carries a
   fade and a chevron that STAY on screen — they are the only signal that the
   list continues, so they are not hover-revealed. The chevron dims once the
   reader has reached the bottom rather than disappearing, which would make the
   control flicker as they scroll. */
.pwrap.is-scrollable::after{
  content:""; position:absolute; left:0; right:6px; bottom:0; height:26px;
  pointer-events:none; border-radius:0 0 var(--r) var(--r);
  background:linear-gradient(180deg, rgba(33,33,39,0), var(--panel) 88%);
}
.pwrap.is-scrollable::before{
  content:"\25BE"; position:absolute; right:9px; bottom:1px; z-index:2;
  font-size:var(--fs-micro); line-height:1; color:var(--rose2); pointer-events:none;
  transition:opacity .25s var(--ease);
}
.pwrap.is-scrollable.at-end::before{ opacity:.28; }

.pchip{
  appearance:none; cursor:pointer; background:transparent; border:1px solid var(--line);
  border-radius:99px; color:var(--mute); padding:4px 11px; min-height:26px;
  font-family:var(--sans); font-size:var(--fs-micro); letter-spacing:var(--ls-tight); white-space:nowrap;
  transition:border-color .25s var(--ease), color .25s var(--ease), background .25s var(--ease);
}
.pchip:hover{ border-color:var(--line2); color:var(--text); }
.pchip[aria-pressed="true"]{ border-color:var(--rose); color:var(--rose2); background:rgba(201,147,131,.08); }

/* ---- the price scale ----
   Two native range inputs stacked on one track. Native inputs are used
   deliberately: they are keyboard-operable, announced correctly, and respect
   the platform's own pointer handling. Only the thumbs receive pointer events,
   so the lower handle is never trapped under the upper one's full-width bar. */
.prange{ position:relative; height:34px; padding-top:12px; }
.prtrack{
  position:absolute; left:0; right:0; top:20px; height:3px; border-radius:3px;
  background:var(--line);
}
.prfill{ position:absolute; top:0; bottom:0; background:var(--rose); border-radius:3px; }
.prange input[type=range]{
  position:absolute; left:0; right:0; top:8px; width:100%; height:26px; margin:0;
  appearance:none; -webkit-appearance:none; background:transparent; pointer-events:none;
}
.prange input[type=range]::-webkit-slider-thumb{
  -webkit-appearance:none; pointer-events:auto; cursor:grab;
  width:16px; height:16px; border-radius:50%;
  background:var(--rose); border:2px solid var(--bg);
  box-shadow:0 1px 4px rgba(0,0,0,.6);
}
.prange input[type=range]::-moz-range-thumb{
  pointer-events:auto; cursor:grab;
  width:16px; height:16px; border-radius:50%; border:2px solid var(--bg);
  background:var(--rose);
}
.prange input[type=range]:focus-visible::-webkit-slider-thumb{ outline:2px solid var(--rose2); outline-offset:2px; }
.prange input[type=range]:focus-visible::-moz-range-thumb{ outline:2px solid var(--rose2); outline-offset:2px; }
.prange-out{
  font-family:var(--sans); font-size:var(--fs-micro); letter-spacing:var(--ls-body);
  text-transform:none; color:var(--mute2); margin-left:auto;
}

.pfoot{
  display:flex; align-items:center; gap:var(--s4);
  margin-top:var(--s4); padding-top:var(--s3); border-top:1px solid var(--line);
}
.pres{ font-size:var(--fs-tiny); color:var(--mute2); margin-right:auto; }
.lnk{
  appearance:none; background:transparent; border:0; cursor:pointer; color:var(--rose2);
  font-family:var(--sans); font-size:var(--fs-tiny); letter-spacing:var(--ls-wide); text-transform:uppercase;
  min-height:var(--tap); padding:0 var(--s2);
}
.lnk:hover{ color:var(--rose); }
/* ---------- v26 (2026-08-08) ---------- */

/* The overture is a once-per-visit event now, so the page has to know how to
   look when it is NOT playing. site.js sets .no-overture on <html> before the
   first paint it controls; without this every animated element would sit at its
   `from` keyframe - an invisible headline over an empty hero. */
.no-overture .hero.overture .hline{ opacity:1; clip-path:none; transform:none; animation:none; }
.no-overture .hero.overture .hero-mark,
.no-overture .hero.overture .btnrow,
.no-overture .hero.overture .herochips,
.no-overture .hero.overture .hero-cred,
.no-overture .scrollcue{ opacity:1; animation:none; }
.no-overture .scrollcue .sc-line{ transform:scaleY(1); animation:none; }
.no-overture .hdr{ opacity:1; animation:none; }
/* The photograph keeps its Ken Burns - it is nine seconds long, it is behind
   everything, and on a return visit it is the one part of the sequence that
   still reads as atmosphere rather than as an entrance. */

/* ---------- cards: titles centre, prose does not ----------
   M asked for the service and guarantee cards to follow the centred formatting.
   The rule the site actually settled on is narrower than "centre everything":
   TITLE BLOCKS centre, running prose does not - which is why .col is a centred
   block of left-aligned text. A card is the same shape in miniature: its number,
   heading and lead are a title block and centre; the paragraph underneath is
   prose and stays ragged-right, because forty words of centred text is a
   puzzle, not a paragraph. */
/* REVERTED 2026-08-08. Centring a card's heading over its own left-aligned
   paragraph looked like a mistake rather than a decision, and M said so. Cards
   are left-aligned throughout - services and the guarantee both, because they
   were centred together and should not now disagree with each other. The
   centring rule stands where it belongs: on .sec-head and .pagehead, the two
   TITLE blocks, and nowhere else. */
/* The primary services band is a two-column layout, so centring its column
   headings would pull them away from the copy they belong to. Left as built. */

/* ---------- the detail, as tabs (about page) ---------- */
.gdetail .tabs{ margin-top:var(--s5); }
.gdetail .tabpan{ padding-top:var(--s4); }
.gdetail .tabpan p{ color:var(--mute); max-width:var(--measure); margin:0 auto; }
.gdetail .tiny{ text-align:center; }

/* ---------- search result groups ---------- */
/* "Also mentioning this" is a weaker match by definition, so its heading is
   quieter than "Best matches" rather than the same size shouting twice. */
#sec-near .grouphead{ color:var(--mute); }
#sec-near{ margin-top:var(--s8); }

/* ---------- search suggestions and corrections ----------
   The list hangs off the search field, above everything in the filter bar but
   below the curtain. It is a real listbox: the arrow keys move through it, the
   field keeps focus, and mousedown rather than click picks an entry, because a
   click would arrive after the field's blur had already shut the list. */
.fsearch-wrap{ position:relative; flex:1 1 220px; display:flex; }
.fsearch-wrap .fsearch{ flex:1 1 auto; }
.qsug{
  position:absolute; left:0; right:0; top:calc(100% + 6px); z-index:85;
  background:var(--panel); border:1px solid var(--line2); border-radius:var(--r);
  padding:6px; box-shadow:0 18px 44px rgba(0,0,0,.55);
  display:flex; flex-direction:column; max-height:min(58vh,420px); overflow-y:auto;
}
.qsug[hidden]{ display:none; }
.qsug button{
  appearance:none; background:transparent; border:0; cursor:pointer; text-align:left;
  display:flex; align-items:center; justify-content:space-between; gap:var(--s4);
  color:var(--text); font-family:var(--sans); font-size:var(--fs-tiny);
  letter-spacing:var(--ls-body); padding:0 var(--s3); min-height:var(--tap);
  border-radius:var(--r); transition:background .2s var(--ease);
}
.qsug button:hover,
.qsug button[aria-selected="true"]{ background:rgba(201,147,131,.14); }
/* The kind of thing each suggestion is - Brand, Model, Reference - so a
   reference and a tag that read alike are still told apart. */
.qsug .qs-k{
  font-size:var(--fs-micro); letter-spacing:var(--ls-wider); text-transform:uppercase;
  color:var(--mute2); flex:0 0 auto;
}
.qsug .qs-t{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* A correction is a different offer from a suggestion and should not look like
   one: it is the only entry that appears when nothing matched. */
.qsug .qs-fix{ color:var(--rose2); border-top:1px solid var(--line); border-radius:0; }
.qsug .qs-fix em{ font-style:normal; color:var(--text); }
@media (max-width:980px){ .fsearch-wrap{ flex:1 1 100%; } }

@media (prefers-reduced-motion:reduce){
  .segb,.pchip,.fbtn,.fbtn .car{ transition:none; }
}

.fcount{ font-size:var(--fs-tiny); color:var(--mute2); letter-spacing:var(--ls-body); }
.empty{ text-align:center; padding:var(--s9) var(--s5); color:var(--mute); }

/* ---------- watch detail ---------- */
.crumbs{ font-size:var(--fs-micro); letter-spacing:var(--ls-wide); text-transform:uppercase; color:var(--mute2); padding:var(--s5) 0 0; }
.crumbs a{ display:inline-flex; align-items:center; min-height:var(--tap); }
.crumbs a:hover{ color:var(--rose2); }
/* The separators are dimmed; the page name is not. One rule on `span` used to
   catch both, which took the current watch's brand down to 2.65:1. */
.crumbs .sep{ margin:0 9px; color:var(--line2); }
.crumbs [aria-current]{ color:var(--mute); }

.detail{ display:grid; gap:var(--s7); grid-template-columns:minmax(0,1.02fr) minmax(0,1fr); align-items:start; padding:var(--s6) 0 var(--s5); }
/* THE DETAIL BLOCK IS EMPTY UNTIL render.js FILLS IT, and everything below it
   was jumping 800px when it did - a layout shift of 0.766, on the one page a
   buyer reads most carefully. Both halves now hold their own shape while they
   are empty, so the page is the right height from the first paint and the
   content arrives into space that was already there. `:empty` stops matching
   the moment the content lands, so neither rule constrains the real thing. */
/* `width:100%` is not decoration, it decides WHICH WAY the ratio resolves.
   With `aspect-ratio` and nothing else, a grid item's automatic minimum size is
   computed from the ratio and its height, and that contribution can exceed the
   column. Measured on `/watch/<slug>.html` at 393px: the column was 359px, the
   placeholder's height resolved to 511px, and 511 x 3/4 gave it a width of
   383.4 - seven pixels of horizontal page overflow on a phone, eight at 320px.
   Pinning the width makes the column authoritative and lets the height follow
   the ratio, which is the direction that was always intended.

   It only bit the STATIC search pages. `watch.html?id=` is filled by render.js,
   so `:empty` stops matching almost immediately and the rule never applies for
   long enough to measure; on `/watch/<slug>.html` the block stays empty and the
   placeholder is the final state. The mobile suite tests the dynamic page only,
   which is why five rounds of mobile QA never saw it - the same two-code-paths
   trap as the gallery srcset bug. */
.detail .igwrap:empty{ aspect-ratio:var(--shot-ar); width:100%; }
.detail > .rv:empty{ min-height:60vh; }
.igwrap{
  position:sticky; top:110px;
  border:1px solid var(--line); border-radius:var(--r); overflow:hidden;
  background:linear-gradient(160deg,var(--panel3),var(--well));
}
/* ---------- photo gallery: large image, thumbnail strip beneath ----------
   The strip lives inside .igwrap so it shares the frame. It scrolls
   horizontally rather than wrapping, so a watch with twelve photographs
   never pushes the specification column down the page. */
.gal{ display:block; }
.gal .swatch{ width:100%; height:auto; aspect-ratio:var(--shot-ar); object-fit:contain; display:block; background:var(--well); }

/* THE CROSS-FADE STAGE (A3, M's choice 2026-08-11). site.js wraps the single
   .swatch in this at runtime and adds a second identical layer behind it, so a
   photograph dissolves into the next instead of snapping. Two layers, never
   more: whichever is in front gets .on, and the one behind is loaded with the
   next photograph before anything moves. Opacity only - no width, no position,
   nothing the compositor has to relayout.
   With JavaScript off the wrap never happens and `.gal .swatch` above still
   sizes the frame on its own, which is why that rule keeps its aspect-ratio. */
.galstage{ position:relative; aspect-ratio:var(--shot-ar); background:var(--well); overflow:hidden; }
.galstage .swatch{
  position:absolute; inset:0; width:100%; height:100%; aspect-ratio:auto;
  object-fit:contain; opacity:0; transition:opacity .5s var(--ease);
}
.galstage .swatch.on{ opacity:1; }
/* The run indicator. A hairline that fills across the top of the frame between
   advances, so the movement reads as deliberate rather than as the page
   twitching. It is decorative and inert. */
.galbar{
  position:absolute; top:0; left:0; height:2px; width:0; z-index:4;
  background:var(--rose); opacity:.85; pointer-events:none;
}
@media (prefers-reduced-motion:reduce){
  .galstage .swatch{ transition:none; }
  .galbar{ display:none; }
}

/* Click the left or right of the photograph to step through the gallery, the
   way a lightbox does. Real buttons over the image, so they are keyboard
   reachable and announced; they are inserted by site.js, so with JavaScript
   off nothing appears and every photo is still a plain anchor. The arrows sit
   quiet until the frame is hovered or one of them is focused — a permanent
   pair of chevrons over a watch photograph is a distraction. */
.galframe{ position:relative; }
.galnav{
  position:absolute; top:0; bottom:0; width:22%; min-width:56px;
  appearance:none; border:0; background:transparent; cursor:pointer; padding:0;
  display:flex; align-items:center; z-index:3;
}
.galnav.prev{ left:0; justify-content:flex-start; padding-left:var(--s3); }
.galnav.next{ right:0; justify-content:flex-end; padding-right:var(--s3); }
.galnav span{
  width:38px; height:38px; border-radius:50%;
  background:rgba(0,0,0,.45); border:1px solid rgba(247,242,236,.28);
  backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
  display:grid; place-items:center;
  opacity:0; transform:scale(.9); transition:opacity .25s var(--ease), transform .25s var(--ease);
}
.galnav span::before{
  content:""; width:9px; height:9px; border-top:1.5px solid var(--text);
  border-right:1.5px solid var(--text); display:block;
}
.galnav.prev span::before{ transform:rotate(-135deg); margin-left:3px; }
.galnav.next span::before{ transform:rotate(45deg); margin-right:3px; }
.galframe:hover .galnav span,
.galnav:focus-visible span{ opacity:1; transform:none; }
.galnav:hover span{ background:rgba(0,0,0,.62); border-color:var(--rose); }
.galnav:focus-visible{ outline:2px solid var(--rose); outline-offset:-4px; }
/* On touch there is no hover, so the arrows are always visible but quieter. */
@media (hover:none){
  .galframe .galnav span{ opacity:.85; transform:none; }
}
/* ---------- v26 (2026-08-08) ---------- */


/* ---------- cards: titles centre, prose does not ----------
   M asked for the service and guarantee cards to follow the centred formatting.
   The rule the site actually settled on is narrower than "centre everything":
   TITLE BLOCKS centre, running prose does not - which is why .col is a centred
   block of left-aligned text. A card is the same shape in miniature: its number,
   heading and lead are a title block and centre; the paragraph underneath is
   prose and stays ragged-right, because forty words of centred text is a
   puzzle, not a paragraph. */
/* REVERTED 2026-08-08. Centring a card's heading over its own left-aligned
   paragraph looked like a mistake rather than a decision, and M said so. Cards
   are left-aligned throughout - services and the guarantee both, because they
   were centred together and should not now disagree with each other. The
   centring rule stands where it belongs: on .sec-head and .pagehead, the two
   TITLE blocks, and nowhere else. */
/* The primary services band is a two-column layout, so centring its column
   headings would pull them away from the copy they belong to. Left as built. */

/* ---------- the detail, as tabs (about page) ---------- */
.gdetail .tabs{ margin-top:var(--s5); }
.gdetail .tabpan{ padding-top:var(--s4); }
.gdetail .tabpan p{ color:var(--mute); max-width:var(--measure); margin:0 auto; }
.gdetail .tiny{ text-align:center; }

/* ---------- search result groups ---------- */
/* "Also mentioning this" is a weaker match by definition, so its heading is
   quieter than "Best matches" rather than the same size shouting twice. */
#sec-near .grouphead{ color:var(--mute); }
#sec-near{ margin-top:var(--s8); }

/* ---------- search suggestions and corrections ----------
   The list hangs off the search field, above everything in the filter bar but
   below the curtain. It is a real listbox: the arrow keys move through it, the
   field keeps focus, and mousedown rather than click picks an entry, because a
   click would arrive after the field's blur had already shut the list. */
.fsearch-wrap{ position:relative; flex:1 1 220px; display:flex; }
.fsearch-wrap .fsearch{ flex:1 1 auto; }
.qsug{
  position:absolute; left:0; right:0; top:calc(100% + 6px); z-index:85;
  background:var(--panel); border:1px solid var(--line2); border-radius:var(--r);
  padding:6px; box-shadow:0 18px 44px rgba(0,0,0,.55);
  display:flex; flex-direction:column; max-height:min(58vh,420px); overflow-y:auto;
}
.qsug[hidden]{ display:none; }
.qsug button{
  appearance:none; background:transparent; border:0; cursor:pointer; text-align:left;
  display:flex; align-items:center; justify-content:space-between; gap:var(--s4);
  color:var(--text); font-family:var(--sans); font-size:var(--fs-tiny);
  letter-spacing:var(--ls-body); padding:0 var(--s3); min-height:var(--tap);
  border-radius:var(--r); transition:background .2s var(--ease);
}
.qsug button:hover,
.qsug button[aria-selected="true"]{ background:rgba(201,147,131,.14); }
/* The kind of thing each suggestion is - Brand, Model, Reference - so a
   reference and a tag that read alike are still told apart. */
.qsug .qs-k{
  font-size:var(--fs-micro); letter-spacing:var(--ls-wider); text-transform:uppercase;
  color:var(--mute2); flex:0 0 auto;
}
.qsug .qs-t{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* A correction is a different offer from a suggestion and should not look like
   one: it is the only entry that appears when nothing matched. */
.qsug .qs-fix{ color:var(--rose2); border-top:1px solid var(--line); border-radius:0; }
.qsug .qs-fix em{ font-style:normal; color:var(--text); }
@media (max-width:980px){ .fsearch-wrap{ flex:1 1 100%; } }

@media (prefers-reduced-motion:reduce){
  .galnav span{ transition:none; }
}
/* 10px between thumbnails, not 8. WCAG 2.2's target rule is satisfied either by
   a 24x24 target or by 24x24 of clear space around a smaller one; a 68px
   thumbnail is comfortably big enough, but at an 8px gap the spacing measured
   22.3px and the rule failed on the gap rather than on the target. Two pixels. */
.gstrip{
  display:flex; gap:10px; padding:8px; overflow-x:auto; overscroll-behavior-x:contain;
  scroll-snap-type:x proximity; -webkit-overflow-scrolling:touch;
  border-top:1px solid var(--line); background:rgba(0,0,0,.22);
  scrollbar-width:thin; scrollbar-color:var(--line2) transparent;
}
.gstrip::-webkit-scrollbar{ height:6px; }
.gstrip::-webkit-scrollbar-thumb{ background:var(--line2); border-radius:99px; }
.gthumb{
  flex:0 0 auto; width:68px; height:68px; border-radius:calc(var(--r) - 6px);
  overflow:hidden; border:1px solid var(--line); scroll-snap-align:start;
  display:block; padding:0; background:var(--well); transition:border-color .18s ease, opacity .18s ease;
  opacity:.72;
}
.gthumb img{ width:100%; height:100%; object-fit:cover; display:block; }
.gthumb:hover{ opacity:1; border-color:var(--line2); }
.gthumb.on{ opacity:1; border-color:var(--rose); }
.gthumb:focus-visible{ outline:2px solid var(--rose); outline-offset:2px; opacity:1; }
@media (max-width:820px){
  .gthumb{ width:56px; height:56px; }
}
/* ---------- v26 (2026-08-08) ---------- */


/* ---------- cards: titles centre, prose does not ----------
   M asked for the service and guarantee cards to follow the centred formatting.
   The rule the site actually settled on is narrower than "centre everything":
   TITLE BLOCKS centre, running prose does not - which is why .col is a centred
   block of left-aligned text. A card is the same shape in miniature: its number,
   heading and lead are a title block and centre; the paragraph underneath is
   prose and stays ragged-right, because forty words of centred text is a
   puzzle, not a paragraph. */
/* REVERTED 2026-08-08. Centring a card's heading over its own left-aligned
   paragraph looked like a mistake rather than a decision, and M said so. Cards
   are left-aligned throughout - services and the guarantee both, because they
   were centred together and should not now disagree with each other. The
   centring rule stands where it belongs: on .sec-head and .pagehead, the two
   TITLE blocks, and nowhere else. */
/* The primary services band is a two-column layout, so centring its column
   headings would pull them away from the copy they belong to. Left as built. */

/* ---------- the detail, as tabs (about page) ---------- */
.gdetail .tabs{ margin-top:var(--s5); }
.gdetail .tabpan{ padding-top:var(--s4); }
.gdetail .tabpan p{ color:var(--mute); max-width:var(--measure); margin:0 auto; }
.gdetail .tiny{ text-align:center; }

/* ---------- search result groups ---------- */
/* "Also mentioning this" is a weaker match by definition, so its heading is
   quieter than "Best matches" rather than the same size shouting twice. */
#sec-near .grouphead{ color:var(--mute); }
#sec-near{ margin-top:var(--s8); }

/* ---------- search suggestions and corrections ----------
   The list hangs off the search field, above everything in the filter bar but
   below the curtain. It is a real listbox: the arrow keys move through it, the
   field keeps focus, and mousedown rather than click picks an entry, because a
   click would arrive after the field's blur had already shut the list. */
.fsearch-wrap{ position:relative; flex:1 1 220px; display:flex; }
.fsearch-wrap .fsearch{ flex:1 1 auto; }
.qsug{
  position:absolute; left:0; right:0; top:calc(100% + 6px); z-index:85;
  background:var(--panel); border:1px solid var(--line2); border-radius:var(--r);
  padding:6px; box-shadow:0 18px 44px rgba(0,0,0,.55);
  display:flex; flex-direction:column; max-height:min(58vh,420px); overflow-y:auto;
}
.qsug[hidden]{ display:none; }
.qsug button{
  appearance:none; background:transparent; border:0; cursor:pointer; text-align:left;
  display:flex; align-items:center; justify-content:space-between; gap:var(--s4);
  color:var(--text); font-family:var(--sans); font-size:var(--fs-tiny);
  letter-spacing:var(--ls-body); padding:0 var(--s3); min-height:var(--tap);
  border-radius:var(--r); transition:background .2s var(--ease);
}
.qsug button:hover,
.qsug button[aria-selected="true"]{ background:rgba(201,147,131,.14); }
/* The kind of thing each suggestion is - Brand, Model, Reference - so a
   reference and a tag that read alike are still told apart. */
.qsug .qs-k{
  font-size:var(--fs-micro); letter-spacing:var(--ls-wider); text-transform:uppercase;
  color:var(--mute2); flex:0 0 auto;
}
.qsug .qs-t{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* A correction is a different offer from a suggestion and should not look like
   one: it is the only entry that appears when nothing matched. */
.qsug .qs-fix{ color:var(--rose2); border-top:1px solid var(--line); border-radius:0; }
.qsug .qs-fix em{ font-style:normal; color:var(--text); }
@media (max-width:980px){ .fsearch-wrap{ flex:1 1 100%; } }

@media (prefers-reduced-motion:reduce){
  .gthumb{ transition:none; }
  .gstrip{ scroll-behavior:auto; }
}

.igframe{ position:relative; background:#fff; }
.igframe::before{
  content:"Loading listing…"; position:absolute; inset:0; display:grid; place-items:center;
  font-family:var(--sans); font-size:var(--fs-tiny); letter-spacing:var(--ls-wide); text-transform:uppercase; color:var(--mute2);
}
.igwrap iframe{ position:relative; width:100%; height:680px; border:0; display:block; background:transparent; }
.igcap{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  padding:14px 18px; border-top:1px solid var(--line);
  font-size:var(--fs-micro); letter-spacing:var(--ls-wide); text-transform:uppercase; color:var(--mute2);
}
.igcap a{ color:var(--rose2); display:inline-flex; align-items:center; min-height:var(--tap); }
.dhead .wbrand{ font-size:var(--fs-micro); margin-bottom:var(--s3); }
.dhead h1{ font-size:var(--fs-h1); margin-bottom:var(--s3); }
.dref{ font-size:var(--fs-tiny); color:var(--mute2); letter-spacing:var(--ls-body); }
.dprice{ margin:var(--s6) 0 var(--s2); font-family:var(--serif); font-size:var(--fs-h2); font-weight:400; color:var(--rose2); line-height:1; font-variant-numeric:lining-nums; font-feature-settings:"lnum" 1; }
.dprice.dm{ font-size:var(--fs-h3); letter-spacing:var(--ls-body); }
.dprice-sub{ font-size:var(--fs-tiny); color:var(--mute2); letter-spacing:var(--ls-body); max-width:46ch; }

/* ---------- the listing, revealed in steps (M, 2026-08-08) ----------
   A watch page arrived as one block: photograph, brand, model, price,
   specification, buttons and story all at once, which is a lot of information
   to hand somebody in a single frame and reads as a page LOADING rather than as
   a piece being presented. The steps here are not decoration - they are an
   order of reading. The photograph first, then whose it is and what it costs,
   then the specification line by line the way a condition report is read out,
   then what you can do about it, then the story.

   `--i` is set by render.js in document order and the delay is derived from it,
   so the sequence follows the markup and nothing has to be kept in step by
   hand. A specification ROW - the label and its value - shares one index, so it
   arrives as a row rather than as two halves.

   `both` matters: the fill holds the element at opacity 0 through its delay,
   which is what stops all fourteen pieces flashing into view before their turn.

   The unit is deliberately short. At 90ms this felt like waiting; at 45ms it
   reads as a hand laying things out. Seventeen steps is about 760ms, which is
   under the photograph's own decode time on a cold load - so in practice the
   sequence has finished by the time the picture is there. */
@keyframes stepin{ from{ opacity:0; transform:translateY(16px); } to{ opacity:1; transform:none; } }
.stp{ animation:stepin .58s var(--ease) both; animation-delay:calc(var(--i, 0) * 45ms); }

.spec{ margin:var(--s6) 0; border-top:1px solid var(--line); }
.spec dl{ display:grid; grid-template-columns:150px 1fr; margin:0; }
.spec dt, .spec dd{ padding:var(--s3) 0; border-bottom:1px solid var(--line); margin:0; font-size:var(--fs-sm); }
.spec dt{ color:var(--mute2); letter-spacing:var(--ls-wide); text-transform:uppercase; font-size:var(--fs-micro); padding-top:var(--s4); }
.spec dd{ color:var(--text); }

.dstory h2{ font-size:var(--fs-h3); margin:var(--s7) 0 var(--s4); }
.dstory p{ color:var(--mute); font-size:var(--fs-body); max-width:var(--measure); }
.dspec-line{
  margin-top:var(--s5); padding:var(--s5); border-left:2px solid var(--rose-dim);
  background:rgba(38,38,45,.7); font-size:var(--fs-sm); color:var(--mute); max-width:62ch;
}
.dbtns{ display:flex; gap:var(--s3); flex-wrap:wrap; margin:var(--s5) 0 0; }
.dnote{ margin-top:var(--s5); font-size:var(--fs-tiny); color:var(--mute); line-height:1.75; max-width:52ch; }
.dtrade{ margin-top:var(--s3); }
.dtrade a{ color:var(--rose2); display:inline-flex; align-items:center; min-height:var(--tap); }
.dtrade a:hover{ color:var(--text); }
/* the horological tag row on a watch page */
.wtags{ list-style:none; display:flex; flex-wrap:wrap; gap:var(--s2); margin:var(--s6) 0 0; padding:0; }
.wtags li{
  font-size:var(--fs-micro); letter-spacing:var(--ls-wide); text-transform:uppercase;
  color:var(--mute2); border:1px solid var(--line); border-radius:var(--r);
  padding:6px var(--s3); background:transparent;
}

.tchips{ list-style:none; display:flex; flex-wrap:wrap; gap:var(--s2); margin:var(--s6) 0 0; padding:0; }
.tchips li{ font-size:var(--fs-micro); letter-spacing:var(--ls-wide); text-transform:uppercase;
  color:var(--rose2); border:1px solid var(--line); border-radius:var(--r);
  padding:var(--s2) var(--s3); white-space:nowrap; background:var(--bg2); }


/* ---------- process / steps ---------- */
.steps{ counter-reset:s; display:grid; gap:2px; }
.step{
  /* auto, not 64px: a fixed numeral column plus a 1fr body cannot shrink below
     its content, which overflowed a 272px wrap on a 320px screen. */
  display:grid; grid-template-columns:auto minmax(0,1fr); gap:var(--s5); align-items:start;
  padding:var(--s6) 0; border-bottom:1px solid var(--line);
}
.step:first-child{ border-top:1px solid var(--line); }
.step .n{ font-family:var(--serif); font-size:var(--fs-h3); font-weight:400; color:var(--rose-dim); line-height:1.2; }
.step h3{ font-size:var(--fs-h4); margin-bottom:var(--s2); }
.step p{ color:var(--mute); font-size:var(--fs-sm); max-width:var(--measure); }

/* ---------- form ---------- */
.form{ display:grid; gap:var(--s5); }
.frow{ display:grid; gap:var(--s5); grid-template-columns:1fr 1fr; }
.field label{ display:block; font-size:var(--fs-micro); letter-spacing:var(--ls-wider); text-transform:uppercase; color:var(--mute2); margin-bottom:var(--s2); }
.field input, .field select, .field textarea{
  width:100%; min-height:var(--tap);
  background:rgba(14,14,17,.55); border:1px solid var(--line2);
  border-radius:var(--r); color:var(--text); font-family:var(--sans); font-size:var(--fs-sm);
  padding:var(--s3) var(--s4); outline:none; transition:border-color .3s var(--ease);
}
.field textarea{ min-height:130px; resize:vertical; line-height:1.65; }
.field input:focus-visible,.field select:focus-visible,.field textarea:focus-visible{
  border-color:var(--rose); outline:2px solid var(--rose2); outline-offset:1px;
}
.field input:focus,.field select:focus,.field textarea:focus{ border-color:var(--rose-dim); }
.field select option{ background:var(--panel); }

/* ---------- contact tiles ---------- */
.ctiles{ gap:var(--s4); }
.ctile{
  display:flex; gap:var(--s4); align-items:center; padding:var(--s5);
  min-height:96px;
  border:1px solid var(--line); border-radius:var(--r); background:rgba(33,33,39,.55);
  transition:.35s var(--ease);
}
.ctile:hover{ border-color:var(--rose-dim); }
.ctile .ic{ flex:0 0 auto; width:32px; height:32px; color:var(--rose); display:block; }
.ctile .ic svg{ width:32px; height:32px; fill:currentColor; display:block; }
.ctile:hover .ic{ color:var(--rose2); }
.ctile b{ display:block; font-size:var(--fs-micro); letter-spacing:var(--ls-wider); text-transform:uppercase; color:var(--mute2); margin-bottom:var(--s2); font-weight:400; }
.ctile span{ font-size:var(--fs-sm); color:var(--text); }
a.ctile:hover span{ color:var(--rose2); }

/* ---------- tabbed enquiry form ---------- */
/* `#write` is the landing point for the home page's "Sell or Trade Your Watch"
   button, which stopped opening WhatsApp on 2026-08-11 and now brings a visitor
   here instead. `html{scroll-padding-top:var(--hdr-h)}` already clears the
   sticky header for every in-page anchor, but measured after the jump the block
   sat at 75px against a header bottom of 85 - the reveal's 28px transform is
   still settling when the browser fixes the scroll position, so it lands ten
   pixels high and the eyebrow tucks under the header. The extra margin covers
   the settle and gives the heading air, which it wants anyway: arriving with a
   heading welded to the header bar reads as a mis-click. */
.formswitch{ max-width:720px; margin-inline:auto; scroll-margin-top:calc(var(--hdr-h) + var(--s6)); }
.formswitch .sec-head.center .hairline{ margin-inline:auto; }
.formswitch .tabs{ justify-content:center; }
/* The fourth tab ran off a 320px screen - 35 of its 68 pixels visible, cut
   mid-word - and `body{overflow-x:clip}` meant there was no way to scroll to
   it. The row now scrolls on its own axis, which keeps the tabs on one line
   where there is room and makes them reachable where there is not. */
.tabs{
  display:flex; gap:var(--s2); border-bottom:1px solid var(--line); margin-bottom:var(--s6);
  overflow-x:auto; overscroll-behavior-x:contain; -webkit-overflow-scrolling:touch;
  scrollbar-width:none;
}
.tabs::-webkit-scrollbar{ display:none; }
.tab{ flex:0 0 auto; }
.tab{
  appearance:none; background:none; border:0; cursor:pointer;
  font-family:var(--sans); font-size:var(--fs-micro); letter-spacing:var(--ls-wide);
  text-transform:uppercase; color:var(--mute2);
  padding:0 var(--s4); min-height:var(--tap);
  border-bottom:2px solid transparent; margin-bottom:-1px;
  transition:color .3s var(--ease), border-color .3s var(--ease);
}
.tab:hover{ color:var(--text); }
.tab[aria-selected="true"]{ color:var(--rose2); border-bottom-color:var(--rose); }
.tab:focus-visible{ outline:2px solid var(--rose2); outline-offset:-2px; }
@media (max-width:520px){ .tab{ flex:1 1 0; padding:0 var(--s2); } }

/* ---------- CTA band ---------- */
.band{
  border-top:1px solid var(--line); border-bottom:1px solid var(--line);
  background:
    radial-gradient(60% 120% at 50% 0%, rgba(201,147,131,.13), transparent 65%),
    linear-gradient(180deg,var(--lift),var(--wash-lo));
  text-align:center; padding:clamp(var(--s8), 8vw, var(--s9)) 0;
}
/* The closing promise breaks over two lines: the question on top in the
   heading colour, the answer beneath it in rose gold. */
.findit{ display:block; color:var(--rose2); }

/* ---------- footer ---------- */
/* Compacted 2026-08-06. Raising the footer links to a 44px target the round
   before added 84px of dead height to a column of six, and M was right that it
   showed. The target is now 34px - comfortably past the 24px AA floor, and a
   footer link is a low-risk mis-tap - and the surrounding space came in with
   it. The 44px standard still holds for primary controls; this is the one
   place where holding it cost more than it bought. */
/* The floating WhatsApp button is fixed 22px from the bottom of the window and
   is 54px tall, so it owns the bottom 76px of the viewport on every page. The
   footer's closing line is the one piece of content that reliably ends up
   underneath it, and bottom-aligning the small print (below) brought it closer
   still - "Terms & Conditions" was running under the button. The footer
   therefore keeps that strip clear. One rule, symmetric, and it holds wherever
   the button is. */
/* THE KEEPOUT IS FOR THE FLOATING BUTTON, AND ONLY THE BUTTON'S CORNER NEEDS IT.
   The footer reserved a full 76px strip across its whole width so the WhatsApp
   float could never sit on a link. That produced 84px of blank page under the
   licence line on every page - which is what M saw, and it was most of the
   footer's closing third. The float is 54px square, 22px from the right edge,
   so the only thing it can reach is the bottom-RIGHT corner. The strip is now
   ordinary breathing room, and the clearance is given to the corner alone via
   .ftr-btm .fb-r below. Verified by build/mobile.mjs, which hit-tests every
   page for tappable things under the float. */
.ftr{ padding:var(--s6) 0 var(--s5); border-top:1px solid var(--line); background:var(--footer); }
/* Three columns, not four (M, 2026-08-08). The logo column left the grid and
   became the crest in the closing line, and each list now runs two-up, which
   took the footer from six stacked rows to three and roughly halved its
   height. */
.ftr-grid{ display:grid; gap:var(--s5) var(--s7); grid-template-columns:repeat(3,1fr); margin-bottom:var(--s5); }
.ftr .ftwo{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:0 var(--s4); }
@media (max-width:820px){
  .ftr-grid{ grid-template-columns:1fr; gap:var(--s5); }
  .ftr .ftwo{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}
@media (max-width:420px){ .ftr .ftwo{ grid-template-columns:1fr; } }
/* .fhead is styled with .pch above - one group label, not two.
   In the footer ONLY, the column heading is centred over its list (M,
   2026-08-08). This is the same rule the rest of the site already follows -
   the TITLE of a block is centred, the block's contents are not - applied to
   the one place that had not caught up with it. `.pch` in the Refine panel is
   untouched: that heading labels a control group a reader scans down, not a
   title over a column. */
/* The label sits above the FIRST ITEM, not across the middle of the list.
   `justify-content:center` centred each label over its two-column list while
   the items stayed left-aligned, so the text started between 98px and 212px to
   the right of the column it belonged to, at every width. M reported it; the
   fix is deleting the rule. Every other group label on the site - the same
   `.pch` style on watch pages and in the refine panel - is left-aligned. */
.ftr .fhead{ justify-content:flex-start; }
.ftr ul{ list-style:none; margin:0; padding:0; }
.ftr li{ margin:0; }
/* The footer columns were 30px tall - above the 24px AA floor but under the
   44px the rest of the site holds itself to. */
.ftr ul a{ display:flex; align-items:center; min-height:34px;
  padding:2px 0; font-size:var(--fs-tiny); color:var(--mute);
  line-height:1.5; transition:color .3s var(--ease); }
.ftr a:hover{ color:var(--rose2); }
/* The brand block, moved whole. M asked for the logo, the services caption, the
   location and the licence to travel together to the closing row rather than be
   reduced to a bare crest - "the same way it was before, just relocated". So
   this is the old left-hand column, centred, sitting between the copyright and
   the legal links. */
/* The MARK alone in the footer - no wordmark (M, 2026-08-08). The word
   "Watch Locker" is already in the copyright line six centimetres below it and
   in the header at the top of the same page; printing it a third time made the
   closing row read as a sign-off repeated twice. The crest carries the brand
   here, the way a seal does at the foot of a document. The header keeps the
   full lockup - this is a footer decision, not a logo decision. The alt text is
   still the brand name, so nothing is lost to a screen reader. */
.ftr img.fmark{ width:68px; display:block; margin:0 auto var(--s2); opacity:.92; }
.ftr .fb-c{ display:block; text-align:center; }
.ftr .fb-c .fserv{ margin:0; }
.ftr .fb-c .flic{ display:block; margin:var(--s2) 0 0; max-width:none; }
/* The brand column reads cleaner centred: the mark, the descriptor, the
   services line and the licence all share one axis. */
.ftr-brand.center{ text-align:center; }
.ftr-brand.center img.fmark{ margin-inline:auto; }
.ftr-brand.center .fabout{ max-width:32ch; margin-inline:auto; }
/* The five services under the descriptor, in rose gold. */
.ftr .fserv{
  margin:var(--s2) 0 0; font-size:var(--fs-micro); letter-spacing:var(--ls-wider);
  text-transform:uppercase; color:var(--rose); line-height:1.8;
}
.flic{ margin:var(--s4) 0 0; max-width:34ch; font-size:var(--fs-micro); line-height:1.5; color:var(--mute2); letter-spacing:var(--ls-tight); }

/* The Get in Touch column is an icon list, so its links are flex rows rather
   than blocks. The icon inherits colour, so it warms on hover with the text. */
.ftr ul.ficons a{ display:flex; align-items:center; gap:10px; }
.ftr .fi{ flex:0 0 auto; width:14px; height:14px; display:inline-flex; color:var(--rose-dim); }
.ftr .fi svg{ width:14px; height:14px; fill:currentColor; display:block; }
.ftr ul.ficons a:hover .fi{ color:var(--rose2); }
/* Two column headings are now links to their own page. */
.fhead a{ color:inherit; transition:color .3s var(--ease); }
.fhead a:hover{ color:var(--rose2); }
/* Touch needs the 44px target; a mouse does not, so the roomier hit area
   is applied only where fingers are used. */
@media (max-width:820px){
  .ftr ul a{ padding:var(--s3) 0; font-size:var(--fs-sm); }
  /* Two column headings are links now, so they need a finger-sized target
     too rather than the 13px the heading text alone would give. */
  .fhead a{ display:inline-flex; align-items:center; min-height:var(--tap); }
}
/* Three cells rather than two, so the mark sits on the page's centre line
   however long the copyright or the legal list happens to be. A flex row with
   `margin-left:auto` would centre it between its neighbours instead, which is
   not the same thing and drifts as soon as the wording changes. */
/* ONE SMALL PRINT, not three (M, 2026-08-08). The copyright, the licence line
   and the legal links are the same kind of statement - the quiet legal foot of
   the page - and they were set three different ways: two of them at --fs-tiny
   with body tracking, the licence at --fs-micro with tight tracking, and the
   links carrying 8px of vertical padding on top of that. So they were three
   sizes on three different lines. They now all take the licence line's setting,
   which was the right one of the three, and `align-items:end` puts the LAST
   line of each cell on the same rule. `baseline` would have aligned the first
   line of each, which in the centre cell is the top of the crest - not what is
   wanted. The links drop their padding at desktop widths for the same reason;
   below 820px the row stacks and the 24px finger target comes back. */
.ftr-btm{
  border-top:1px solid var(--line); padding-top:var(--s6); padding-bottom:var(--s2);
  display:grid; grid-template-columns:1fr auto 1fr; align-items:end;
  gap:var(--s5);
  font-size:var(--fs-micro); line-height:1.5; color:var(--mute2); letter-spacing:var(--ls-tight);
}
.ftr-btm .fb-r{ text-align:right; }
/* The float's corner, and nothing else, gets the clearance. */
@media (min-width:821px){ .ftr-btm .fb-r{ padding-right:var(--wa-keepout); } }
@media (max-width:820px){ .ftr-btm{ padding-bottom:calc(var(--wa-keepout) - var(--s5)); } }
.ftr-btm .fb-l, .ftr-btm .fb-r{ display:block; padding-bottom:0; }
@media (min-width:821px){ .ftr-btm .ftr-legal a{ padding:0; } }
@media (max-width:820px){
  .ftr-btm{ grid-template-columns:1fr; justify-items:center; text-align:center; gap:var(--s4); }
  .ftr-btm .fb-r{ text-align:center; }
  /* On a phone the crest reads better above the small print than between two
     lines of it. */
  .ftr-btm .fb-c{ order:-1; }
}
.ftr-legal a{ display:inline-block; padding:var(--s3) 0; }

/* ---------- iOS zooms a field under 16px, so no field is under 16px --------
   M's forms were 15px. Safari on iPhone zooms the whole page in whenever a
   focused field's text is smaller than 16px, and it does not zoom back out - so
   every visitor filling in an enquiry on a phone had to pinch out again
   afterwards, on all three forms. One pixel of type was costing that.
   Applied only on touch pointers: on a desktop 15px is the right size and there
   is no zoom behaviour to avoid. `--fs-body` is 16px, so this is the site's own
   body size rather than a magic number. */
@media (pointer:coarse){
  /* `.field input` is one class deep and a bare `input` is not, so the first
     version of this rule lost to the form styling and the fields stayed at
     15px on a real phone - the media query matched and changed nothing. Match
     the specificity of what it is overriding. */
  .field input, .field select, .field textarea,
  .fsearch input, .fsel, input, textarea, select{ font-size:var(--fs-body); }
}

/* ---------- floating whatsapp ---------- */
.wa{
  position:fixed; right:22px; bottom:22px; z-index:70;
  width:54px; height:54px; border-radius:50%;
  display:grid; place-items:center;
  background:var(--rose); color:var(--ink);
  /* WHY THE SHADOW SHRANK, AND WHY THE COLOUR DID NOT CHANGE.
     M, 2026-08-11: "the whatsapp floating button needs to match the rose gold
     colour in the buttons. They look like 2 different shades of rose gold."

     They are not two shades. Both declare `background:var(--rose)`, both
     compute to rgb(201,147,131), both render - sampled off a 2x screenshot,
     the bubble as a ring so the white glyph is excluded and the button through
     its padding so the label is excluded - at rgb(200,147,131). There is no
     opacity, no filter, no blend mode and no gradient on either.

     The one thing the bubble had that the buttons do not is this shadow, and at
     `0 10px 30px rgba(0,0,0,.5)` it was laying a wide black halo around a 54px
     circle. Against a surround that dark, the same rose reads duller and
     browner - simultaneous contrast, and it is the whole of the difference M
     was seeing. Changing the colour to "match" would have made two things that
     are identical actually differ.

     So the shadow is halved in spread and lightened, which lifts the halo off
     the disc while still separating it from the page. */
  box-shadow:0 6px 16px rgba(0,0,0,.34);
  transition:transform .35s var(--ease), background .35s var(--ease);
}
.wa:hover{ transform:scale(1.07); background:var(--rose2); }
.wa svg{ width:26px; height:26px; fill:currentColor; }

/* ---------- responsive ---------- */
@media (max-width:1000px){
  .detail{ grid-template-columns:1fr; gap:var(--s6); }
  /* relative, not static: the sold ribbon is absolutely positioned inside */
  .igwrap{ position:relative; top:auto; }
  .igwrap iframe{ height:600px; }
  .ftr-grid{ grid-template-columns:1fr 1fr; gap:var(--s6); }
}
@media (max-width:820px){
  .nav, .hdr-cta .btn{ display:none; }
  .burger{ display:flex; }
  .wrap,.narrow{ width:calc(100% - 34px); }
  .frow{ grid-template-columns:1fr; }
  .spec dl{ grid-template-columns:120px 1fr; }
  .pagehead{ padding:var(--s6) 0 var(--s4); }

  /* On a phone the bar stacks: switch on its own line (scrolling sideways if
     it must), then search, Refine and sort sharing the next. The panel chips
     wrap normally, since the panel is the place where space is available. */
  .fbar{ top:84px; padding:var(--s3) 0; margin-bottom:var(--s5); }
  .fbar-row{ gap:var(--s2); }
  /* Fingers need the full 44px on every control in the bar and the panel. */
  .segb{ min-height:var(--tap); }
  .pchip{ min-height:var(--tap); padding:0 var(--s4); display:inline-flex; align-items:center; }
  .seg{
    flex:1 1 100%; overflow-x:auto; scrollbar-width:none; -ms-overflow-style:none;
    -webkit-overflow-scrolling:touch; justify-content:space-between;
  }
  .seg::-webkit-scrollbar{ display:none; }
  .segb{ flex:1 1 auto; padding:0 var(--s3); letter-spacing:var(--ls-wide); }
  .fsearch{ flex:1 1 100%; }
  .fbtn{ flex:1 1 46%; justify-content:center; padding:0 var(--s3); }
  .fsel{ flex:1 1 46%; }
  .refpanel{ padding:var(--s4); }
  /* On a phone one row is impossible, so the groups stack two across and the
     grid itself scrolls past a ceiling - the panel must never grow taller than
     the screen. Clear all and Show results sit outside that scroll, so they are
     always reachable. Each group keeps its own cap and its scroll indicator,
     and the chips go back to a full tap target. */
  .pgrid{
    grid-template-columns:repeat(2, minmax(0,1fr)); gap:var(--s4);
    max-height:min(52vh, 400px); overflow-y:auto; overscroll-behavior:contain;
    padding-right:var(--s2);
  }
  .pcol.wide{ grid-column:span 2; }
  .pchips{ max-height:120px; }
  .pfoot{ flex-wrap:wrap; }
  .pres{ flex:1 1 100%; margin-bottom:var(--s2); }
}
@media (max-width:520px){
  .ftr-grid{ grid-template-columns:1fr; gap:var(--s6); }
  .brand img{ height:32px; }
  .step{ grid-template-columns:44px 1fr; gap:var(--s4); }
  .step .n{ font-size:var(--fs-h4); }
  .g4{ grid-template-columns:repeat(auto-fill,minmax(min(158px,100%),1fr)); gap:var(--s3); }
  .wbody{ padding:var(--s4) var(--s4) var(--s4); }
  .wmodel{ font-size:var(--fs-body); }
  .wprice{ font-size:var(--fs-h4); }
  .wfoot{ flex-direction:column; align-items:flex-start; gap:var(--s2); }
  .igwrap iframe{ height:540px; }
  .btnrow .btn{ width:100%; }
  .dbtns .btn{ width:100%; }
}

/* ==========================================================================
   v24 (2026-08-07). M's decisions on the eleven-item review.
   ========================================================================== */

/* ---------- THE OVERTURE (item 10) ----------
   Rebuilt 2026-08-08 to match M's "Vault Teaser" exactly. He sent the teaser
   and said "do exactly the same", so every keyframe, duration, delay and easing
   below is lifted from it verbatim rather than approximated. THE NUMBERS ARE
   THE DESIGN. If you change one, you are changing his reference, not tuning it.

   COMPRESSED 2026-08-10 ON M'S INSTRUCTION - "shorten the duration of the
   cinematic intro animation for a smoother UX". That supersedes the note below
   about the teaser's numbers being untouchable: he set that rule and he is the
   one relaxing it. Every beat and its ORDER is kept; the whole timeline is
   scaled to about 0.6, and the Ken Burns is cut hardest because it was the one
   still running when a visitor reached for the scroll.

        0.00s  curtain painted, mark begins to resolve      vmark  1.50s
        1.35s  hero photograph begins its settle            vkb    3.60s
        1.48s  curtain begins to lift  (78% of vlift's 1.9) vlift  1.90s
        1.65s  header nav fades up                          vfade  0.80s
        1.85s  the mark in the hero                         vfade  0.90s
        1.90s  headline line one is unmasked                vwipe  0.90s
        2.05s  headline line two is unmasked                vwipe  0.90s
        2.40s  the buttons                                  vfade  0.90s
        2.50s  the trust line                               vfade  0.90s
        2.65s  the scroll cue appears                       vfade  0.80s
        2.80s  the scroll cue's line draws down             vline  1.10s

   Settled at 3.9s instead of 6.2s, and the photograph stops moving at 4.95s
   instead of 11.2s. THE SECOND NUMBER IS THE ONE THAT MATTERED: a visitor who
   scrolls at four seconds used to be drawing the bow while a nine-second scale
   animation was still running on the same element, and `.pulling` kills that
   animation mid-flight - so the photograph snapped from wherever the Ken Burns
   had it to wherever the pull wanted it, every time. Now it is finished before
   any realistic first gesture.

   THE ORIGINAL, for reference if this ever needs restoring:

   Two easing families and no more: --ease for anything settling, and
   --ease-curtain for the curtain and the wipes, which want a firmer start and
   stop. Both are the teaser's. */
:root{ --ease-curtain:cubic-bezier(.76,0,.24,1); }

.curtain{
  position:fixed; inset:0; z-index:98;   /* above the header at 60 and the WhatsApp button at 70 */
  background:var(--curtain); display:grid; place-items:center;
  pointer-events:none;                     /* never intercepts a tap */
  animation:vlift 1.9s var(--ease-curtain) forwards;
  will-change:transform;
}
.curtain-mark{ width:150px; max-width:44vw; height:auto;
               animation:vmark 1.5s var(--ease) forwards; }

/* The mark resolves from slightly small, holds at full size, drifts two per
   cent past it and goes. The hold is the point - without it this reads as a
   loading spinner rather than a title card. */
@keyframes vmark{
   0%{ opacity:0; transform:scale(.86); }
  28%{ opacity:1; transform:scale(1); }
  72%{ opacity:1; transform:scale(1.02); }
 100%{ opacity:0; transform:scale(1.05); }
}
/* The curtain sits still for 78% of its life and then leaves upward in one
   movement. Holding at 0 rather than delaying the animation keeps the plate
   painted from the first frame, so there is no flash of the page beneath.
   visibility:hidden at the end so it is gone even before site.js removes it. */
@keyframes vlift{
   0%,78%{ transform:translateY(0); }
 100%{ transform:translateY(-100%); visibility:hidden; }
}

/* Ken Burns, starting 2.2s in - BEHIND the curtain, so the photograph is
   already moving when the curtain clears it. That is the whole trick; it reads
   as film rather than as a slideshow. `both` holds 1.14 through the delay. */
.hero.overture .hero-bg{
  animation:vkb 3.6s var(--ease) 1.35s both; will-change:transform;
}
@keyframes vkb{ from{ transform:scale(1.14); } to{ transform:scale(1.02); } }

/* The headline is unmasked line by line rather than faded. Note the END state:
   inset(-8% 0 -12% 0), NOT inset(0). A clip box flush with the line box shears
   the descenders off "Locker" and "Luxury" at the moment the wipe completes -
   the negative insets give the glyphs room. This is the teaser's value and it
   is deliberate. */
.hero.overture h1{ opacity:1; animation:none; }
.hero.overture .hline{
  display:block;
  animation:vwipe .9s var(--ease-curtain) both;
  will-change:clip-path,transform;
}
.hero.overture .hline:nth-child(1){ animation-delay:1.9s; }
.hero.overture .hline:nth-child(2){ animation-delay:2.05s; }
@keyframes vwipe{
  from{ clip-path:inset(0 0 100% 0); transform:translateY(26px); }
    to{ clip-path:inset(-8% 0 -12% 0); transform:none; }
}

/* The supporting cast. Plain opacity, staggered, so nothing competes with the
   headline for the first look. The teaser has an eyebrow where this site has
   its mark, and no buttons; the slots map straight across. */
@keyframes vfade{ from{ opacity:0; } to{ opacity:1; } }
.hero.overture .hero-mark { opacity:0; animation:vfade .9s ease 1.85s forwards; transform:none; }
.hero.overture .btnrow    { opacity:0; animation:vfade .9s ease 2.4s forwards; }
.hero.overture .herochips { opacity:0; animation:vfade .9s ease 2.5s forwards; }
.hero.overture .hero-cred { opacity:0; animation:vfade .8s ease 2.65s forwards; }
/* The header belongs to the sequence but lives outside the hero, so it is
   reached from the body. A browser without :has simply shows it immediately,
   which is a graceful thing to lose. */
body:has(.curtain) .hdr{ opacity:0; animation:vfade .8s ease 1.65s forwards; }

/* The cue: the word, then one hairline drawn downward from its top edge. It is
   not a bouncing arrow and should not become one. */
.scrollcue{
  position:absolute; bottom:34px; left:50%; transform:translateX(-50%); z-index:2;
  display:flex; flex-direction:column; align-items:center; gap:12px;
  opacity:0; animation:vfade .8s ease 2.65s forwards;
}
.scrollcue .sc-word{
  font-size:10px; letter-spacing:var(--ls-widest); text-transform:uppercase;
  color:var(--mute2);
}
.scrollcue .sc-line{
  width:1px; height:52px; background:linear-gradient(180deg,var(--rose),transparent);
  transform-origin:center top;
  animation:vline 1.1s var(--ease-curtain) 2.8s both;
}
@keyframes vline{ from{ transform:scaleY(0); } to{ transform:scaleY(1); } }
@media (max-width:820px){ .scrollcue{ display:none; } }

/* ---------- TWO PAGES: the hero and the collection as facing panels -------
   M, 2026-08-08 (second pass): "act as if they are 2 separate pages and as if
   I'm moving to the next page."

   The reason it did not read that way was STRUCTURAL, not animated. The hero
   was a fixed 839px and the collection section was 1629px - two rows of four -
   so on a 1080px screen you always saw 240px of the collection under the hero
   before touching anything, and after the transition you landed on a section
   that ran twice past the bottom of the window. Neither of those is a page, and
   no easing curve fixes it.

   A panel is one WINDOW MINUS THE HEADER. The bar is sticky and floats over
   everything, so a full-window panel has its top 85px behind the bar and its
   bottom 85px under the fold - the same complaint reintroduced by its own fix.
   `scroll-padding-top` moves every snap position down to match, and site.js
   reads the header's height off the element so the two cannot disagree.

   WHERE THIS APPLIES, AND WHY IT IS NOT EVERYWHERE. A card is as tall as it is
   wide times four thirds, plus its text - the photograph's 3:4 crop is fixed
   because the watermark sits in the lower quarter and may never be cut. At the
   site's own measure that is a 599px card, and 599 + a 102px title block + the
   panel's own air needs about 860px of window before it fits. Below that the
   only lever is narrower cards, and narrowing them makes the model names wrap
   to three lines, which puts most of the height straight back: at 1366x768 the
   row has to come down to 199px cards to fit and still overflows. That is a
   worse page than the one it replaces.

   So the panels are conditional, and the site simply behaves as it did before
   underneath the threshold - two rows of four in a normally-flowing section,
   with the bow pull still landing on the collection's top. Nothing breaks; it
   just stops being a page turn and goes back to being a good scroll.

   The cards past the fourth are HIDDEN rather than not rendered so this stays a
   pure CSS switch that `bow-lab.html` can turn on and off live. If M keeps it,
   change render.js to `slice(0, 4)` for `home-avail` and delete the
   `nth-child` rule - four hidden cards is four wasted image requests on the
   most important page of the site. */
html{ scroll-padding-top:var(--hdr-h); }

@media (min-width:900px) and (min-height:860px){
  .hero.page{
    min-height:calc(100vh - var(--hdr-h));
    display:grid; align-content:center;
    padding-block:clamp(var(--s6), 6vh, var(--s9));
  }
  .hero.page .hero-in{ min-height:0; }
  .sec.page{
    min-height:calc(100vh - var(--hdr-h));
    display:grid; align-content:center;
    padding-block:clamp(var(--s1), .5vh, var(--s9));
  }
  /* `@supports` rather than a second min-height on the same rule: the duplicate
     declaration is the ordinary way to write a fallback, and csscheck rejects
     it as a copy-paste slip - which, nine times out of ten, is what it is. */
  @supports (height:100dvh){
    .hero.page, .sec.page{ min-height:calc(100dvh - var(--hdr-h)); }
  }
  /* `repeat(4, ...)` is not decoration. The site's grid is `auto-fill`, so a
     panel at a narrower measure would silently drop to three cards and the
     "one row of four" would quietly stop being true on some screens. */
  /* `.rowfour`, not `.sec.page .g4`: the sold archive below is an ordinary
     flowing section, not a panel, so a descendant selector could never reach
     it - and M's instruction on 2026-08-11 was that the two home grids must
     obey the SAME rule. The class is carried by both grids in build.py. */
  .g4.rowfour{ grid-template-columns:repeat(4, minmax(0, 1fr)); }
  .g4.rowfour > :nth-child(n+5){ display:none; }
  /* The button used to be hidden here, on the argument that the next gesture
     carries you onward and that its 92px decides whether a short laptop fits.
     M read the missing row and missing button as a bug, not as a design, and
     asked for the button back on both grids (2026-08-11). The height is bought
     back by compressing the button's own margin rather than by removing it:
     `mt40` is 40px of air that a full-height panel does not need. The panel's
     own padding and the gap under the title block give up the rest. Measured
     at 1440x900, 1280x880 and 1000x860: the section now ends ABOVE the fold at
     each, which is the whole point - a button clipped by the fold would be a
     worse bug than the one M reported. */
  .sec.page .home-cta{ margin-top:clamp(var(--s1), .5vh, var(--s4)); }
  /* The gap under the title block is generous by design on a long scrolling
     section and merely decorative on a panel; it is the right place to take the
     last few pixels from. */
  .sec.page .sec-head{ margin-bottom:clamp(var(--s2), .9vh, var(--s6)); }
}

/* ---------- the flight: what is switched off while the page turns ----------
   M, 2026-08-11: "the first scroll is still a bit laggy... I want a buttery
   smooth (60fps) transition here."

   The answer was not in the pull's arithmetic, which is compositor-only
   already. It was in three backdrop filters that happen to sit over the only
   moving part of the page. A backdrop filter is recomputed from scratch on
   every frame in which its backdrop changes, and during a page turn the
   backdrop is the whole screen.

     .hdr       sticky, full width, over a hero that is scaling and translating
     .hero-cap  on the hero media, moving with it
     .wtag      up to eight of them, on the collection panel that is arriving

   Measured at 1440x900, ten wheel notches, mean of two runs: suspending the
   header's blur alone took VizCompositor time from 1570ms to 968ms, a 38% cut.
   The grain overlay was tested the same way and cost nothing, so it stays.

   Suspended, not removed. `site.js` puts `wl-flight` on the root when the draw
   begins and takes it off once the page has landed and settled, so the blur is
   absent only while the screen is in motion and is back before anything is
   still enough to look at.

   The declared backgrounds are left EXACTLY as they are. The obvious move is to
   step the alpha up to replace the contrast the blur was giving, and it was
   tried at .93, .90, .88, .86 and .84 - every one of them made the match WORSE.
   Against a hero photograph this dark the blur contributes almost nothing the
   82% panel is not already contributing, so the honest compensation is none:
   a frame captured three notches into the pull differs from the unmodified
   build by a mean of 0.12 and a maximum of 1 in 255, across the whole header. */
html.wl-flight .hdr{
  backdrop-filter:none; -webkit-backdrop-filter:none;
  background:rgba(22,22,26,.86);
}
html.wl-flight .hero-cap{
  backdrop-filter:none; -webkit-backdrop-filter:none;
  background:rgba(18,18,22,.86);
}
html.wl-flight .wtag{
  backdrop-filter:none; -webkit-backdrop-filter:none;
}

/* ---------- the arrival: the collection page coming over the hero ----------
   Three of the four arrivals slide, wipe or fade the collection panel over a
   hero that STAYS WHERE IT IS, which is what makes them read as one page
   replacing another rather than as a document scrolling. site.js sets the
   transform, the clip and the opacity per frame; the two things it cannot set
   from script without a repaint of its own are here.

   The background is the important one. `.sec` is transparent, so a panel slid
   over the hero would show the photograph straight through it and the whole
   effect would look like a bug. It is the page's own background colour, so
   nothing changes visually when the class is removed at the end. */
.sec.arriving{ background:var(--bg); position:relative; z-index:5; will-change:transform, opacity; }
.sec.arriving .wrap{ position:relative; }
/* THE LEADING EDGE is what turns a moving rectangle into a page. Without it the
   sheet is a shape appearing over a photograph; with a hairline and a shadow
   above it, it is a sheet of paper being drawn up over another one. Same
   geometry, entirely different reading, and it costs one declaration.
   The shadow is drawn UPWARD (a negative y offset), which is the only part of
   it anybody sees - everything below the edge is covered by the panel. */
.sec.arriving.sheeting{
  box-shadow:0 -1px 0 var(--line2), 0 -34px 70px rgba(0,0,0,.6);
}
/* The curtain's edge, for the same reason. `--wipe` is written by site.js on
   the same frame as the clip, so the rule sits exactly on the boundary of the
   reveal and travels up with it - the overture's own curtain has a lit edge and
   this is that gesture played backwards. */
/* Replaced 2026-08-10. The edge used to be a 1px pseudo-element positioned with
   `top: var(--wipe)`, written every frame - which is an animated LAYOUT property
   on a full-viewport section, next to an animated `clip-path` on the section
   itself. Both repaint; a trace put this section's paint far above everything
   else on the page during a pull.
   Now the cover slides and the edge is its own bottom border, so the whole
   reveal is one composited transform and the edge travels for free. */
.wipecover{
  position:absolute; inset:0; z-index:2; pointer-events:none;
  background:var(--bg);
  will-change:transform;
}
.wipecover::after{
  content:""; position:absolute; left:0; right:0; bottom:0; height:1px;
  background:linear-gradient(90deg, transparent, var(--rose), transparent);
}

/* ---------- the bow pull: the hero as a drawn string (M, 2026-08-08) --------
   Almost all of this effect lives in site.js, because it is driven by the
   visitor's own gesture rather than by a clock - CSS cannot express "how hard
   are they pulling". What CSS has to do is GET OUT OF THE WAY, and that is what
   these three rules are for.

   The overture leaves running animations on exactly the three elements the pull
   needs to move: `vkb` on the photograph, `vfade` on the cue, `vline` on the
   cue's hairline. A running animation beats an inline style for the properties
   it animates, so without this the JavaScript would set transforms that never
   appeared and the pull would look completely dead. This is the same class of
   bug as the `.wcard.rv` shorthand above: nothing wrong with the code, one
   declaration quietly outranking another.

   `.pulling` is added for the length of the gesture and removed after it, and
   site.js re-seats the Ken Burns with a negative delay so an abandoned draw
   returns the photograph to exactly where in its nine seconds it had got to,
   rather than restarting it. */
/* SWITCHING AN ANIMATION OFF REVEALS WHAT IS UNDERNEATH IT, and underneath
   `vfade` the cue and the credit line are declared `opacity:0` - the animation
   was the only thing holding them up. So the frame `.pulling` landed, before the
   first paint could write an inline value, the scroll cue blinked out and back:
   a measured 0.96 opacity step in one frame, at the very START of the pull. It
   was the second flicker in this mechanism and it had been there from the first
   version. Any rule that says `animation:none` on an element the overture was
   holding must also state what the overture was holding it AT. */
.hero.pulling .hero-bg,   .hero.settled .hero-bg{ animation:none; }
.hero.pulling .scrollcue, .hero.settled .scrollcue{ animation:none; opacity:1; }
.hero.pulling .hero-cred, .hero.settled .hero-cred{ opacity:1; }
.hero.pulling .scrollcue .sc-line,
.hero.settled .scrollcue .sc-line{ animation:none; transform:scaleY(1); }
/* The layers the pull writes to every frame. Promoted only while the gesture
   is live - a permanent will-change on a full-bleed photograph costs memory on
   every page view for an effect that happens once. */
.hero.pulling .hero-in,
.hero.pulling .hero-bg,
.hero.pulling .scrollcue{ will-change:transform, opacity; }

/* ---------- THE HERO'S RESTING STATE AFTER A PULL, AND THE FLICKER ----------
   M, 2026-08-08: "there's a slight flicker at the end of the transition."
   Recorded the transition frame by frame over CDP and the cause was not the
   curtain at all. It was this: at the landing moment the code cleared the
   hero's inline transform, which put `.pulling` back off and let the Ken Burns
   RESTART - and `vkb` begins at scale(1.14) behind a 2.2s delay with `both`, so
   the photograph jumped from 1.0986 to 1.14 in a single frame. A four per cent
   snap on a full-bleed photograph.

   And it is visible, which is the part that is easy to miss: at the landing
   position the hero's last 85px sit under the sticky header, and the header is
   `rgba(22,22,26,.82)` with a blur - eighteen per cent of the hero shows
   through it. Anything that changes there changes on screen.

   Two rules follow, and they are the whole fix.

   1. `.settled` keeps the overture's animations OFF for good once the visitor
      has pulled the hero. They are an ENTRANCE; they have played, and there is
      nothing left for them to say. Nothing can restart and nothing can jump.
      Because the animation is gone, the elements it was holding in place need
      their resting values stated here - otherwise the cue and the credit line,
      which live at `opacity:0` until `vfade` lifts them, would simply vanish.
   2. `.settling` gives the way back a TRANSITION rather than a cut. A four per
      cent scale eased over 700ms behind a blurred bar is imperceptible; the
      same four per cent in one frame is the flicker. The inline values written
      during the settle are the CSS resting values exactly - `scale(1.04)` is
      what `.hero-bg` declares - so when they are cleared at the end, clearing
      them changes nothing. That is the point: every step of the way out has to
      land on a value that is already true. */
.hero.settled .scrollcue{ opacity:1; }
.hero.settled .hero-cred{ opacity:1; }
.hero.settled .scrollcue .sc-line{ transform:scaleY(1); }
/* `--ease-curtain`, NOT `--ease`, and this is the one place on the site where
   that is the whole reason for the choice. `--ease` is fast off the mark - it
   covered a fifth of the settle in its first 45ms, which measured as a 0.10
   opacity step and a 25px slide. `--ease-curtain` is an ease-IN-out: it barely
   moves for its first tenth. The settle is the one movement here that nobody
   should ever notice, so it wants the curve that starts from nothing, and 900ms
   rather than 700 to spread what is left. */
.hero.settling .hero-in,
.hero.settling .hero-bg,
.hero.settling .scrollcue,
.hero.settling .hero-cred,
.hero.settling .scrollcue .sc-line{
  transition:transform .9s var(--ease-curtain), opacity .9s var(--ease-curtain);
}

/* ---------- reading progress ----------
   A 2px rose hairline across the top of the window, scaled from the scroll
   position by site.js. Sits under the curtain (98) and over the header (60). */
.progress{
  position:fixed; top:0; left:0; right:0; height:2px; z-index:97;
  background:linear-gradient(90deg,var(--rose),var(--rose2));
  transform:scaleX(0); transform-origin:left center; pointer-events:none;
  will-change:transform;
}

/* ---------- film grain ----------
   The teaser's finishing layer, and the reason its photographs read as film
   rather than as JPEGs: a fixed 5% turbulence field over the whole window.
   inset:-50% so no edge of the tile can ever reach the viewport. body::before
   is already the vignette; this is body::after. It is a data URI, so it costs
   no request. */
body::after{
  content:""; position:fixed; inset:-50%; z-index:96; pointer-events:none;
  opacity:.05;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E");
}

/* ---------- N6: the section catches the scroll (item 1) ----------
   PROXIMITY, never mandatory. Mandatory snap on a page this long takes the
   scroll away from the visitor entirely - it fights a trackpad flick and makes
   a phone feel broken. Proximity only tidies up a gesture that already landed
   close to a boundary, which is the "one flick lands the collection squarely"
   M asked for, without the cost. Only the hero and the first section opt in.

   `scroll-snap-stop: always` since v42, and it is what makes the home page work
   now that the bow pull is off. Snap-stop does not change where the rests are -
   proximity still decides that - it stops a SINGLE gesture from carrying you
   PAST one. So one flick from the top lands on the collection and goes no
   further, which is the whole of the feel M chose, and the rest of the page is
   left completely alone because there are no further targets.

   The temptation, when the pull came out, was to reach for `mandatory` instead.
   It pins the page: the scroll port must always rest on a snap position, this
   document has two of them inside its top 900px of 3765, and everything below
   is then unreachable - measured, y=815 no matter where you scroll to. That is
   what the first line of this comment has said since it was written. */
@media (min-width:821px) and (prefers-reduced-motion:no-preference){
  html{ scroll-snap-type:y proximity; }
  .snap{ scroll-snap-align:start; scroll-snap-stop:always; }
}

/* ---------- C4: centred heads, SITE-WIDE (item 3) ----------
   M chose C4 for the home page's section heads on 2026-08-07, and the first
   pass applied it there and nowhere else - so the home page was centred and
   every other page was still hard left, which is what he came back on: "the
   website UI and formatting look inconsistent". He was right.

   The centring is therefore a property of the COMPONENTS, not of one page.
   `.sec-head` and `.pagehead` are the site's only two title blocks; both are
   centred here, so any section or page added later is centred without anyone
   having to remember. `.sec-head` already caps itself at 52ch, which is the
   "held to 52 characters" half of the option.

   If this is ever reversed, reverse it HERE - do not go back to centring page
   by page, which is how the two halves drifted apart in the first place. */
.sec-head{ text-align:center; margin-inline:auto; }
.sec-head .eyebrow{ margin-bottom:var(--s3); }
.sec-head .lede{ margin-inline:auto; }
/* `.center` was the opt-in modifier before centring became the default. It is
   still on several elements in build.py and is now simply a no-op; leave it
   rather than churn the markup. */
.sec-head.center{ text-align:center; margin-inline:auto; }

.pagehead{ text-align:center; }
.pagehead .lede{ margin-inline:auto; }
.pagehead .btnrow{ justify-content:center; }
.pagehead .eyebrow{ margin-bottom:var(--s3); }

/* A hairline under a centred title has to taper from BOTH ends, or the rule
   points off to the left while the words sit in the middle - which is what the
   contact page has been doing since the block was centred. Handled here rather
   than by remembering to write `.hairline center` in the markup every time. */
.sec-head .hairline,
.pagehead .hairline{
  margin-top:var(--s5); margin-inline:auto;
  background:linear-gradient(90deg, transparent, var(--rose), transparent);
}

/* ---------- the sort menu (item 7) ----------
   A listbox of our own. It is 138px against the native menu's 158px, and the
   search field is flex:1 1 220px, so it absorbs the difference and the row
   never re-wraps. The menu is absolutely positioned: opening it moves nothing. */
.sortwrap{ position:relative; flex:0 0 auto; }
.sortbtn{ gap:8px; }
.sortbtn .sv{
  color:var(--mute2); font-size:var(--fs-micro);
  letter-spacing:var(--ls-wider); text-transform:uppercase;
}
.sortmenu{
  position:absolute; right:0; top:calc(100% + 6px); z-index:80; min-width:220px;
  background:var(--panel); border:1px solid var(--line2); border-radius:var(--r);
  padding:6px; box-shadow:0 18px 44px rgba(0,0,0,.55);
  display:flex; flex-direction:column;
}
.sortmenu[hidden]{ display:none; }
.sortmenu button{
  appearance:none; background:transparent; border:0; cursor:pointer; text-align:left;
  color:var(--mute); font-family:var(--sans); font-size:var(--fs-micro);
  letter-spacing:var(--ls-wider); text-transform:uppercase;
  padding:0 var(--s3); min-height:var(--tap); border-radius:var(--r);
  transition:background .25s var(--ease), color .25s var(--ease);
}
.sortmenu button:hover{ background:rgba(201,147,131,.12); color:var(--text); }
.sortmenu button[aria-selected="true"]{ color:var(--rose2); }
@media (max-width:820px){
  .sortwrap{ flex:1 1 46%; }
  .sortbtn{ width:100%; justify-content:center; padding:0 var(--s3); }
  .sortmenu{ left:0; right:0; min-width:0; }
}

/* Between 821px and 980px the four controls no longer fit on one line: the
   switch is 350px, the search field will not go below 220px, and Refine plus
   Sort need another 300. The row wrapped there BEFORE this change too - the
   native menu was 158px and the sum still overflowed - but M's requirement is
   that Sort never leaves Refine's line, so the stacked treatment that already
   existed below 820px now starts at 980px instead. Below that boundary Refine
   and Sort share a row at 46% each, which is where he wants them. */
@media (min-width:821px) and (max-width:980px){
  .fbar-row{ gap:var(--s2); }
  .seg{ flex:1 1 100%; }
  .segb{ flex:1 1 auto; }
  .fsearch{ flex:1 1 100%; }
  .fbtn{ flex:1 1 46%; justify-content:center; }
  .sortwrap{ flex:1 1 46%; }
  .sortbtn{ width:100%; justify-content:center; }
  .sortmenu{ left:0; right:0; min-width:0; }
}

/* ---------- the running count (item 9) ---------- */
.fcount{
  font-size:var(--fs-micro); letter-spacing:var(--ls-wider); text-transform:uppercase;
  color:var(--mute2); margin:0 0 var(--s5);
}
.fcount:empty{ display:none; }
.fcount b{ color:var(--text); font-weight:400; font-variant-numeric:tabular-nums; }

/* ---------- M1: Refine as an accordion on a phone (item 8b) ----------
   The desktop panel caps every group at one row and lets it scroll inside
   itself, which is right when a row is a row. At 390px the chips wrap, so that
   same cap showed a row and a half - five little scroll wells stacked down the
   screen, each sliced through the middle of a line of chips. Brand showed 5 of
   its 15. Below 820px each group becomes a disclosure instead: closed by
   default, opening to its full height, nothing clipped and nothing to scroll
   inside. The desktop panel is untouched. */
@media (max-width:820px){
  .refpanel .pgrid{ display:block; }
  .refpanel .pcol{ border-bottom:1px solid var(--line); }
  .refpanel .pcol:last-child{ border-bottom:0; }
  /* The heading becomes the control. build.py still writes an <h2>; site.js
     puts a real <button> inside it, so the heading structure survives for a
     screen reader and the control is a control. */
  .refpanel .pch{
    margin:0; display:flex; align-items:center; gap:10px;
    min-height:var(--tap);
  }
  .refpanel .pch button{
    appearance:none; background:transparent; border:0; cursor:pointer; padding:0;
    width:100%; min-height:var(--tap); display:flex; align-items:center; gap:10px;
    font:inherit; color:inherit; letter-spacing:inherit; text-transform:inherit;
    text-align:left;
  }
  .refpanel .pch .acar{
    width:7px; height:7px; margin-left:auto; flex:0 0 7px;
    border-right:1px solid currentColor; border-bottom:1px solid currentColor;
    transform:rotate(45deg) translateY(-2px);
    transition:transform .25s var(--ease);
  }
  .refpanel .pcol.open .pch .acar{ transform:rotate(225deg) translateY(-2px); }
  /* The cap and the inner scroll are the two things that caused the problem.
     Both go, and the group is simply hidden until it is opened. */
  .refpanel .pwrap{ display:none; }
  .refpanel .pcol.open .pwrap{ display:block; }
  .refpanel .pwrap .pchips{
    max-height:none; overflow:visible; padding-bottom:var(--s4);
  }
  .refpanel .pwrap.is-scrollable::after{ display:none; }
  /* The price group has no .pwrap - it is a slider, not a chip list - so it is
     never collapsed and site.js gives it no button. Leave it that way. */
}

/* ---------- v26 (2026-08-08) ---------- */


/* ---------- cards: titles centre, prose does not ----------
   M asked for the service and guarantee cards to follow the centred formatting.
   The rule the site actually settled on is narrower than "centre everything":
   TITLE BLOCKS centre, running prose does not - which is why .col is a centred
   block of left-aligned text. A card is the same shape in miniature: its number,
   heading and lead are a title block and centre; the paragraph underneath is
   prose and stays ragged-right, because forty words of centred text is a
   puzzle, not a paragraph. */
/* REVERTED 2026-08-08. Centring a card's heading over its own left-aligned
   paragraph looked like a mistake rather than a decision, and M said so. Cards
   are left-aligned throughout - services and the guarantee both, because they
   were centred together and should not now disagree with each other. The
   centring rule stands where it belongs: on .sec-head and .pagehead, the two
   TITLE blocks, and nowhere else. */
/* The primary services band is a two-column layout, so centring its column
   headings would pull them away from the copy they belong to. Left as built. */

/* ---------- the detail, as tabs (about page) ---------- */
.gdetail .tabs{ margin-top:var(--s5); }
.gdetail .tabpan{ padding-top:var(--s4); }
.gdetail .tabpan p{ color:var(--mute); max-width:var(--measure); margin:0 auto; }
.gdetail .tiny{ text-align:center; }

/* ---------- search result groups ---------- */
/* "Also mentioning this" is a weaker match by definition, so its heading is
   quieter than "Best matches" rather than the same size shouting twice. */
#sec-near .grouphead{ color:var(--mute); }
#sec-near{ margin-top:var(--s8); }

/* ---------- search suggestions and corrections ----------
   The list hangs off the search field, above everything in the filter bar but
   below the curtain. It is a real listbox: the arrow keys move through it, the
   field keeps focus, and mousedown rather than click picks an entry, because a
   click would arrive after the field's blur had already shut the list. */
.fsearch-wrap{ position:relative; flex:1 1 220px; display:flex; }
.fsearch-wrap .fsearch{ flex:1 1 auto; }
.qsug{
  position:absolute; left:0; right:0; top:calc(100% + 6px); z-index:85;
  background:var(--panel); border:1px solid var(--line2); border-radius:var(--r);
  padding:6px; box-shadow:0 18px 44px rgba(0,0,0,.55);
  display:flex; flex-direction:column; max-height:min(58vh,420px); overflow-y:auto;
}
.qsug[hidden]{ display:none; }
.qsug button{
  appearance:none; background:transparent; border:0; cursor:pointer; text-align:left;
  display:flex; align-items:center; justify-content:space-between; gap:var(--s4);
  color:var(--text); font-family:var(--sans); font-size:var(--fs-tiny);
  letter-spacing:var(--ls-body); padding:0 var(--s3); min-height:var(--tap);
  border-radius:var(--r); transition:background .2s var(--ease);
}
.qsug button:hover,
.qsug button[aria-selected="true"]{ background:rgba(201,147,131,.14); }
/* The kind of thing each suggestion is - Brand, Model, Reference - so a
   reference and a tag that read alike are still told apart. */
.qsug .qs-k{
  font-size:var(--fs-micro); letter-spacing:var(--ls-wider); text-transform:uppercase;
  color:var(--mute2); flex:0 0 auto;
}
.qsug .qs-t{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* A correction is a different offer from a suggestion and should not look like
   one: it is the only entry that appears when nothing matched. */
.qsug .qs-fix{ color:var(--rose2); border-top:1px solid var(--line); border-radius:0; }
.qsug .qs-fix em{ font-style:normal; color:var(--text); }
@media (max-width:980px){ .fsearch-wrap{ flex:1 1 100%; } }

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{ animation-duration:.001ms !important; animation-iteration-count:1 !important; transition-duration:.001ms !important; scroll-behavior:auto !important; }
  .rv{ opacity:1; transform:none; }
  .hero-mark,.hero h1,.hero .lede,.hero .btnrow,.hero-media{ opacity:1; animation:none; }
  /* The overture is switched off outright rather than played fast. Every piece
     of it must end up in its resting state, and the curtain must not exist. */
  .curtain{ display:none !important; }
  .hero.overture .hline{ opacity:1; clip-path:none; transform:none; animation:none; }
  .hero.overture .hero-mark,.hero.overture .btnrow,
  .hero.overture .herochips,.hero.overture .hero-cred{ opacity:1; animation:none; }
  body:has(.curtain) .hdr{ opacity:1; animation:none; }
  .hero.overture .hero-bg{ animation:none; transform:scale(1.04); }
  .scrollcue{ opacity:1; animation:none; }
  .scrollcue .sc-line{ transform:scaleY(1); animation:none; }
  .progress{ display:none; }
  body::after{ display:none; }
}

@media print{
  .hdr,.mnav,.wa,.fbar,.band,.ftr{ display:none !important; }
  body{ background:#fff; color:#111; }
  body::before{ display:none; }
}


/* ---------- utilities ---------- */
.skip{ position:absolute; left:-9999px; top:0; z-index:200; background:var(--rose); color:var(--ink);
  padding:var(--s3) var(--s5); font-size:var(--fs-tiny); letter-spacing:var(--ls-wide); text-transform:uppercase; }
.skip:focus{ left:12px; top:12px; }
.center{ text-align:center; }
.lede.center{ margin-inline:auto; text-align:center; }
.serif{ font-family:var(--serif); }
.mt10{ margin-top:var(--s3); } .mt20{ margin-top:var(--s5); } .mt22{ margin-top:var(--s5); }
.mt24{ margin-top:var(--s5); } .mt40{ margin-top:var(--s7); }
.mb60{ margin-bottom:var(--s7); } .pt34{ padding-top:var(--s5); } .pt40{ padding-top:var(--s7); }
.pb96{ padding-bottom:var(--s9); } .gap56{ gap:var(--s7); } .g2.start{ align-items:start; }
.tiny.mt{ margin-top:var(--s1); }
.vh{ position:absolute; width:1px; height:1px; margin:-1px; overflow:hidden;
     clip-path:inset(50%); white-space:nowrap; border:0; padding:0; font-size:inherit; }
/* The page head hands straight to the first section, which brings its own
   top padding; a bottom band here only doubled it. */
.pagehead{ padding:var(--s8) 0 0; }
.notfound{ min-height:62vh; display:grid; place-items:center; text-align:center; }

.btnrow{ display:flex; gap:var(--s3); flex-wrap:wrap; margin-top:var(--s6); }
.btnrow.center{ justify-content:center; }
.btnrow.start{ justify-content:flex-start; }

/* ---------- brand strip ---------- */
.brandstrip{ border-block:1px solid var(--line); background:rgba(33,33,39,.4); padding:var(--s5) 0; }
.brandrow{
  display:flex; flex-wrap:wrap; justify-content:center; align-items:center;
  gap:14px 38px;
}
/* Each brand links into the collection, filtered to that brand. They are
   anchors now, so they need a tap target and a focus ring; the appearance is
   unchanged from the labels they replaced. */
.brandrow a{
  font-family:var(--serif); font-size:var(--fs-body); letter-spacing:var(--ls-wide); text-transform:uppercase;
  color:var(--mute); white-space:nowrap; text-decoration:none;
  transition:color .3s var(--ease);
  display:inline-flex; align-items:center; min-height:var(--tap);
}
.brandrow a:hover{ color:var(--rose2); }
.brandrow a:focus-visible{ outline:2px solid var(--rose); outline-offset:4px; border-radius:2px; }

/* ---------- collection grouping ---------- */
.grouphead{
  font-family:var(--serif); font-size:clamp(22px,2.6vw,30px); font-weight:400;
  margin:14px 0 20px; padding-bottom:14px; border-bottom:1px solid var(--line);
  scroll-margin-top:150px;
  display:flex; align-items:baseline; gap:var(--s3);
}
/* Each section carries its own count rather than the page quoting a combined
   total, so "Available" and "Sold" each say how many they hold. */
/* A NUMERAL BADGE, and it was missed on the first pass. Item 6 moved the three
   availability counts and the Refine count onto their own treatment and I said
   there were four on the site; there are SIX - these two group counts are the
   others, and leaving them at 11px in the label grey next to a switch that had
   just gone to 13px tabular is precisely the inconsistency M came back on.
   Same treatment, one rule, no exceptions. */
.gcount{
  font-family:var(--sans); font-size:var(--fs-tiny); letter-spacing:var(--ls-body);
  font-variant-numeric:tabular-nums; font-feature-settings:"tnum" 1;
  color:var(--mute); position:relative; top:-2px;
}
.gcount:empty{ display:none; }
#sec-sold{ margin-top:64px; }
.mag{ color:var(--mute2); font-size:var(--fs-sm); }

/* ---------- available and sold ----------
   Two signals working together. A status dot above the brand states it in
   words on every card, which is what carries on a phone and to a screen
   reader. On top of that a sold watch sits behind a veil that clears on
   hover, so a grid of the archive reads as an archive at a glance.

   The veil is hover-only where hover exists. On touch there is no hover, so
   it settles at a lighter weight that still marks the card without hiding
   the watch — see the (hover:none) block below. */
.wcard.is-sold .wmodel{ color:var(--mute); }

.wstatus{
  display:inline-flex; align-items:center; gap:8px; margin-bottom:10px;
  font-family:var(--sans); font-size:var(--fs-micro); letter-spacing:var(--ls-widest);
  text-transform:uppercase;
}
.wstatus::before{ content:""; width:7px; height:7px; border-radius:50%; flex:0 0 auto; }
.wstatus.av{ color:var(--mute2); }
.wstatus.av::before{ background:var(--ok); box-shadow:0 0 0 3px rgba(127,176,138,.16); }
.wstatus.so{ color:var(--mute2); }
.wstatus.so::before{ background:var(--line2); }

/* The sold treatment M chose: a veil over the photograph carrying the word,
   which lifts on hover, alongside the status dot below. Both are deliberate -
   the veil is what reads across a grid at a glance, the dot is what survives
   on a phone where there is no hover. This was briefly reduced to a bare tint
   on 6 Aug by misreading a note about the section eyebrow; it is the agreed
   design and should stay. */
.veil{
  position:absolute; inset:0; z-index:2; display:grid; place-items:center;
  background:rgba(16,16,20,.58);
  -webkit-backdrop-filter:saturate(.6); backdrop-filter:saturate(.6);
  transition:opacity .35s var(--ease);
}
.veil span{
  font-family:var(--sans); font-size:var(--fs-micro); letter-spacing:var(--ls-widest);
  text-transform:uppercase; color:var(--text);
  border:1px solid var(--rose-dim); padding:10px 20px;
  transition:opacity .35s var(--ease);
}
@media (hover:hover){
  .wcard:hover .veil{ opacity:.12; }
}
@media (hover:none){
  /* No hover to lift it, so it never sits heavy enough to hide the watch. */
  .veil{ background:rgba(16,16,20,.34); }
  .veil span{ font-size:var(--fs-micro); padding:8px 16px; }
}
/* ---------- v26 (2026-08-08) ---------- */


/* ---------- cards: titles centre, prose does not ----------
   M asked for the service and guarantee cards to follow the centred formatting.
   The rule the site actually settled on is narrower than "centre everything":
   TITLE BLOCKS centre, running prose does not - which is why .col is a centred
   block of left-aligned text. A card is the same shape in miniature: its number,
   heading and lead are a title block and centre; the paragraph underneath is
   prose and stays ragged-right, because forty words of centred text is a
   puzzle, not a paragraph. */
/* REVERTED 2026-08-08. Centring a card's heading over its own left-aligned
   paragraph looked like a mistake rather than a decision, and M said so. Cards
   are left-aligned throughout - services and the guarantee both, because they
   were centred together and should not now disagree with each other. The
   centring rule stands where it belongs: on .sec-head and .pagehead, the two
   TITLE blocks, and nowhere else. */
/* The primary services band is a two-column layout, so centring its column
   headings would pull them away from the copy they belong to. Left as built. */

/* ---------- the detail, as tabs (about page) ---------- */
.gdetail .tabs{ margin-top:var(--s5); }
.gdetail .tabpan{ padding-top:var(--s4); }
.gdetail .tabpan p{ color:var(--mute); max-width:var(--measure); margin:0 auto; }
.gdetail .tiny{ text-align:center; }

/* ---------- search result groups ---------- */
/* "Also mentioning this" is a weaker match by definition, so its heading is
   quieter than "Best matches" rather than the same size shouting twice. */
#sec-near .grouphead{ color:var(--mute); }
#sec-near{ margin-top:var(--s8); }

/* ---------- search suggestions and corrections ----------
   The list hangs off the search field, above everything in the filter bar but
   below the curtain. It is a real listbox: the arrow keys move through it, the
   field keeps focus, and mousedown rather than click picks an entry, because a
   click would arrive after the field's blur had already shut the list. */
.fsearch-wrap{ position:relative; flex:1 1 220px; display:flex; }
.fsearch-wrap .fsearch{ flex:1 1 auto; }
.qsug{
  position:absolute; left:0; right:0; top:calc(100% + 6px); z-index:85;
  background:var(--panel); border:1px solid var(--line2); border-radius:var(--r);
  padding:6px; box-shadow:0 18px 44px rgba(0,0,0,.55);
  display:flex; flex-direction:column; max-height:min(58vh,420px); overflow-y:auto;
}
.qsug[hidden]{ display:none; }
.qsug button{
  appearance:none; background:transparent; border:0; cursor:pointer; text-align:left;
  display:flex; align-items:center; justify-content:space-between; gap:var(--s4);
  color:var(--text); font-family:var(--sans); font-size:var(--fs-tiny);
  letter-spacing:var(--ls-body); padding:0 var(--s3); min-height:var(--tap);
  border-radius:var(--r); transition:background .2s var(--ease);
}
.qsug button:hover,
.qsug button[aria-selected="true"]{ background:rgba(201,147,131,.14); }
/* The kind of thing each suggestion is - Brand, Model, Reference - so a
   reference and a tag that read alike are still told apart. */
.qsug .qs-k{
  font-size:var(--fs-micro); letter-spacing:var(--ls-wider); text-transform:uppercase;
  color:var(--mute2); flex:0 0 auto;
}
.qsug .qs-t{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* A correction is a different offer from a suggestion and should not look like
   one: it is the only entry that appears when nothing matched. */
.qsug .qs-fix{ color:var(--rose2); border-top:1px solid var(--line); border-radius:0; }
.qsug .qs-fix em{ font-style:normal; color:var(--text); }
@media (max-width:980px){ .fsearch-wrap{ flex:1 1 100%; } }

@media (prefers-reduced-motion:reduce){
  .veil, .veil span{ transition:none; }
}

/* With no price on a sold card the footer holds only the link, so push it
   to the right rather than letting it sit orphaned on the left. */
.wcard.is-sold .wgo{ margin-left:auto; }
.dprice.soldp{
  font-family:var(--sans); font-size:var(--fs-tiny); letter-spacing:var(--ls-widest); text-transform:uppercase;
  color:var(--mute); display:inline-block;
}

/* ---------- detail spec block ---------- */
.dspec-line b{
  display:block; font-family:var(--sans); font-size:var(--fs-micro); letter-spacing:var(--ls-widest);
  text-transform:uppercase; color:var(--rose); margin-bottom:9px; font-weight:400;
}

/* ---------- reduced motion / mobile refinements ---------- */
@media (max-width:820px){
  .brand .bmark{ height:40px; } .brand .bword{ height:17px; }
  .brandrow{ gap:10px 22px; }
  .brandrow a{ font-size:var(--fs-tiny); }
  }
@media (max-width:520px){
  .brand .bword{ display:none; }
  .brand .bmark{ height:42px; }
}


/* ---------- filter bar rows ---------- */
.fbar-row{ display:flex; align-items:center; gap:var(--s3); flex-wrap:wrap; }
.brandchips{ margin-top:var(--s3); padding-top:var(--s3); border-top:1px solid var(--line); }


/* A single column of prose at the readable measure. The comment used to say it
   "lines up with the page heading"; since the page headings were centred
   site-wide on 2026-08-07 it lines up with nothing, and left it as a narrow
   block of text hard against the left margin under a centred title, with the
   whole right half of the page empty.
   The BLOCK is centred, the TEXT is not. Centring 70-character lines of prose
   is unreadable - the eye loses the start of each line - so the measure moves to
   the middle of the page and the words stay ragged-right, which is what every
   printed page does. */
.col{ max-width:var(--measure); margin-inline:auto; }


/* ---------- services ----------
   One primary band, four secondary cards - the arrangement M chose. Five
   services shown as five equals misrepresented the business: Buy is the
   business, the rest are how stock arrives or services performed for someone
   else.

   Two formatting faults are fixed here, and they are the whole of what was
   wrong with this page:

   1. THE BUTTONS DID NOT LINE UP. `.qcard .btn{margin-top:auto}` was meant to
      pin every button to the bottom of its card, but a later, more specific
      `.qcard .btn.sm{margin-top:var(--s5)}` overrode it, so each button sat
      wherever its own paragraph ended. Do not reintroduce that rule.
   2. THE CARDS WERE DIFFERENT HEIGHTS. The grid now stretches its rows, so a
      row of four ends on one edge whatever the copy does. */
.svcprime{
  border:1px solid var(--line2); border-radius:var(--r);
  padding:clamp(var(--s6), 4vw, var(--s8));
  background:linear-gradient(160deg, rgba(201,147,131,.10), transparent 62%);
  margin-bottom:var(--s6);
}
.svcprime-in{
  display:grid; grid-template-columns:minmax(0,5fr) minmax(0,7fr);
  gap:var(--s7); align-items:start;
}
.qlead{
  font-family:var(--serif); color:var(--rose2); line-height:1.3;
  font-size:var(--fs-h4); margin:var(--s3) 0 0;
}
.qlead.big{ font-size:var(--fs-h3); max-width:18ch; }
.svcprime .lede{ font-size:var(--fs-body); }

.quad{
  display:grid; grid-template-columns:repeat(auto-fit,minmax(min(250px,100%),1fr));
  gap:var(--s5); align-items:stretch;
}
.qcard{
  border:1px solid var(--line); border-radius:var(--r); background:var(--panel);
  padding:var(--s6) var(--s5); display:flex; flex-direction:column; height:100%;
  transition:border-color .35s var(--ease);
}
.qcard:hover{ border-color:var(--line2); }
.qcard h3{ font-size:var(--fs-h3); margin:0; }
/* ONE VOCABULARY ON THIS PAGE. Buy and the four cards were built at different
   times and drifted apart: the card leads had lost their rose entirely
   (`.qcard p` is more specific than `.qlead`, so the grey won), the body text
   ran at two sizes, and the buttons at two more. Buy stays primary through its
   heading size and its tinted panel - that is the hierarchy. Everything else
   below is deliberately identical. */
.qcard .qlead{
  font-family:var(--serif); color:var(--rose2);
  font-size:var(--fs-h4); line-height:1.3; margin:var(--s2) 0 var(--s4);
}
.qcard p:not(.qlead){ font-size:var(--fs-body); color:var(--mute); margin:0; }
/* ONE rule for the button's position, and nothing may override it.
   This has now broken twice, both times because a second rule of equal or
   higher specificity set margin-top again: first `.qcard .btn.sm`, then
   `.qcard > .btn{margin-block-start}`. `margin-block-start` IS margin-top in
   a horizontal writing mode - it is not a separate property.
   The gap below gives the breathing room the second rule was reaching for. */
.qcard .btn{ margin-top:auto; align-self:flex-start; }
.qcard{ gap:var(--s5); }
@media (max-width:900px){
  .svcprime-in{ grid-template-columns:1fr; gap:var(--s5); }
  .qlead.big{ max-width:none; }
}

/* The verdict that closes the guarantee. It was a left-aligned paragraph and
   read as an orphan; it is a conclusion, so it is set as one.
   NOTE: this rule sat immediately above the manifesto comment once before and
   was swallowed by a programmatic slice that cut on that marker. build/csscheck.py
   now asserts it survives every edit. */
.verdict{
  font-family:var(--serif); font-size:var(--fs-h3); line-height:1.45;
  color:var(--text); text-align:center; text-wrap:balance;
  max-width:44ch; margin:var(--s7) auto 0; padding-top:0;
  /* No rule above it. M: "it is part of the same section" - and he is right,
     the border read as a divider between the cards and a new block when the
     sentence is the cards' conclusion. Spacing carries it instead. */
  letter-spacing:var(--ls-tight);
}
.verdict span{ display:block; color:var(--rose2); }

/* The small print under the guarantee.
   This used to be the `.legal` component - the narrow left rail from the Terms
   and Privacy pages - dropped into the middle of a marketing page. That is why
   M called the section "the odd piece out": it was literally a different
   system, with a different measure, a different rhythm and a different density
   from everything around it. It is now the definition-list pattern the watch
   pages already use for specifications, which is compact, reads as reference
   rather than sales copy, and belongs here. */
.gdetail{
  margin-top:var(--s8); padding-top:var(--s6);
  border-top:1px solid var(--line);
}
.gdetail dl{
  display:grid; grid-template-columns:minmax(150px, 22ch) minmax(0,1fr);
  margin:var(--s4) 0 0;
}
.gdetail dt, .gdetail dd{
  padding:var(--s4) 0; border-top:1px solid var(--line); margin:0;
}
.gdetail dt{
  font-family:var(--sans); font-size:var(--fs-micro); letter-spacing:var(--ls-wide);
  text-transform:uppercase; color:var(--rose);
}
.gdetail dd{ color:var(--mute); font-size:var(--fs-sm); max-width:var(--measure); }
.gdetail dt:first-of-type, .gdetail dl > dt:first-of-type + dd{ border-top:0; }
.gdetail .tiny{ margin-top:var(--s5); color:var(--mute2); }
.gdetail .tiny a{ color:var(--rose2); text-underline-offset:3px; }
@media (max-width:760px){
  .gdetail dl{ grid-template-columns:1fr; }
  .gdetail dt{ padding-bottom:0; border-top:1px solid var(--line); }
  .gdetail dd{ padding-top:var(--s2); border-top:0; }
  .gdetail dt:first-of-type{ border-top:0; }
}

/* The FAQ is grouped: twenty-one questions in one undifferentiated list is a
   wall, and a buyer should not have to read past three seller questions to
   find delivery. The heading uses the site's section-label treatment rather
   than a new one. */
/* THREE RULES STACKED. At every group boundary the page drew the previous
   group's closing border, then the heading's own bottom border, then the first
   item's top border - three hairlines within about 40px, which is what M meant
   by "double lines". The heading keeps no rule of its own; the list below it
   supplies the only one. Spacing came down a step at the same time: the groups
   were 48px apart with 24px inside every row, which read as a page half
   filled. */
.faqgroup{ margin-top:var(--s6); }
.faqgroup:first-child{ margin-top:var(--s4); }
.faqhead{
  font-family:var(--sans); font-size:var(--fs-micro); letter-spacing:var(--ls-widest);
  text-transform:uppercase; color:var(--rose); font-weight:400;
  margin:0 0 var(--s2); padding-bottom:0; border-bottom:0;
}

/* ---------- manifesto ---------- */
.manifesto p{ font-family:var(--serif); font-size:clamp(21px,2.4vw,30px); line-height:1.6;
  color:var(--text); max-width:26ch; text-wrap:balance; }
.manifesto p b{ font-weight:400; color:var(--rose2); }


/* ---------- watch page hero photo ---------- */
.igwrap .wshot.big{ aspect-ratio:var(--shot-ar); }
.igwrap .wshot.big img:not(.ph){ width:100%; height:100%; object-fit:cover; }


/* ---------- legal pages ---------- */
.legal .legal-sec{ padding:var(--s6) 0; border-top:1px solid var(--line); scroll-margin-top:110px; }
.legal .legal-sec:first-child{ border-top:0; padding-top:var(--s1); }
.legal h2{ font-size:var(--fs-h3); margin-bottom:var(--s3); }
.legal p{ color:var(--mute); font-size:var(--fs-body); line-height:1.75; max-width:var(--measure); }
.legal a{ color:var(--rose2); text-underline-offset:3px; text-decoration:underline;
  text-decoration-color:rgba(236,201,174,.35); }
.legal a:hover{ text-decoration-color:var(--rose2); }

/* document layout: text column + sticky section index in the former void */
.doc{ display:grid; grid-template-columns:minmax(0,var(--measure)) minmax(0,260px); gap:var(--s9); align-items:start; justify-content:start; }
.doc-toc{ position:sticky; top:110px; }
.doc-toc p{ font-size:var(--fs-micro); letter-spacing:var(--ls-wider); text-transform:uppercase;
  color:var(--mute2); margin:0 0 var(--s4); }
.doc-toc ol{ list-style:none; margin:0; padding:0; border-left:1px solid var(--line); }
.doc-toc a{ display:block; padding:var(--s3) 0 var(--s3) var(--s4);
  font-size:var(--fs-tiny); color:var(--mute2); line-height:1.4;
  border-left:1px solid transparent; margin-left:-1px; transition:.3s var(--ease); }
.doc-toc a:hover{ color:var(--text); }
.doc-toc a[aria-current="true"]{ color:var(--rose2); border-left-color:var(--rose); }
@media (max-width:1040px){
  .doc{ grid-template-columns:1fr; gap:0; }
  .doc-toc{ display:none; }
}
.ftr-legal a{ color:var(--mute2); }
.ftr-legal a:hover{ color:var(--rose2); }


/* ---------- FAQ accordion ---------- */
/* One rule per item, on the top edge, and one to close the LAST item of the
   whole list rather than the last of each group - `:last-of-type` is scoped to
   the parent, so with the questions split across several .faqgroup wrappers it
   was closing every group and doubling into the next heading. */
details.faq{ border-top:1px solid var(--line); }
.faqgroup:last-child details.faq:last-of-type{ border-bottom:1px solid var(--line); }
details.faq summary{
  cursor:pointer; list-style:none; display:flex; justify-content:space-between;
  align-items:center; gap:var(--s5); padding:var(--s4) 0; min-height:var(--tap);
  font-family:var(--serif); font-size:var(--fs-h4); color:var(--text);
  transition:color .3s var(--ease);
}
details.faq summary::-webkit-details-marker{ display:none; }
details.faq summary:hover{ color:var(--rose2); }
details.faq .fplus{ position:relative; width:14px; height:14px; flex:0 0 auto; }
details.faq .fplus::before, details.faq .fplus::after{
  content:""; position:absolute; background:var(--rose); transition:transform .3s var(--ease);
}
details.faq .fplus::before{ left:0; right:0; top:6px; height:1px; }
details.faq .fplus::after{ top:0; bottom:0; left:6px; width:1px; }
details.faq[open] .fplus::after{ transform:scaleY(0); }
details.faq p{ color:var(--mute); font-size:var(--fs-body); line-height:1.7; max-width:var(--measure); padding:0 0 var(--s4); }
details.faq summary:focus-visible{ outline:2px solid var(--rose2); outline-offset:2px; }

/* ===========================================================================
   MOBILE AUDIT REMEDIATION - 8 August 2026
   ---------------------------------------------------------------------------
   Every rule below lives inside a max-width media query, so none of it can
   reach the desktop layout. The desktop site was captured as 51 full-page
   screenshots at 1440, 1280 and 1024 BEFORE this block was written and
   re-captured afterwards; the diff is the proof, not the intention.

   Nothing here changes the design language. The tap-target work in particular
   moves not one pixel: it extends the touchable area with a transparent,
   absolutely-positioned pseudo-element, so the type, the spacing and the
   middot rhythm M tuned are untouched and only the finger gets more room.
   ========================================================================= */

@media (max-width:820px){

  /* --- The collection's filter bar stops being sticky on a phone. -----------
     Measured before: the header (85px) and the filter bar (191px) were BOTH
     sticky, so 276px stayed frozen for the whole scroll against a 464px card.
     On a 375x667 iPhone that is 41% of the screen; on a 320x568 it is 331px,
     58%, leaving 237px. At no scroll position on any iPhone did one complete
     watch card fit between the frozen chrome and the bottom of the screen.

     The bar is three stacked rows on a phone where it is one on the desktop -
     it earned its height there and was charging rent here. The header stays
     sticky, because that is how you get back out; the filters scroll away
     once used, which is the moment they stop being useful. */
  .fbar{ position:relative; top:auto; z-index:60; }
  /* iOS-05, and this one is mine. M: "See how the sort becomes sort of
     invisible when it meets the photo."

     `position:static` un-sticks the bar, which was the point - but a z-index on
     a static element is INERT. So the moment this rule shipped, `.fbar`'s
     z-index:40 stopped applying, and because the bar still carries a
     backdrop-filter it remained a stacking context with no rank: the whole
     filter bar, and the sort menu absolutely positioned inside it, dropped
     beneath the grid that follows it in the document. Hit-testing the open menu
     returned the watch photograph at every point below the first option.

     `position:relative` un-sticks it just as well - relative is not sticky, and
     the bar still scrolls away with the page, which is verified - while giving
     the z-index something to apply to. Measured after: the menu's own buttons
     hit-test at 20%, 50%, 80% and 95% down it.

     The lesson is small and expensive: when you change `position`, check what
     else on that rule depended on it. */

  /* --- Tap targets, with no layout change whatsoever. ----------------------
     Apple asks for 44x44pt. Measured before, on every page: the footer legal
     links were 41px tall, and the short link words - "Buy" 39px wide, "Sell"
     43px, the brand lockup 41px, the "Home" breadcrumb 38px, brand chips like
     "IWC" 31px - all failed on WIDTH.

     Padding them to 44 would have widened the middot-separated service row and
     the brand strip, which is exactly the typography that took several rounds
     to settle. So the box is left alone and a transparent ::after is stretched
     beyond it. It is absolutely positioned, so it contributes nothing to
     layout: the words stay where they are and the finger gets 44px anyway.

     Note these were never WCAG AA failures - 2.5.8 asks for 24x24 and they all
     cleared that. This is Apple's stricter guidance, met properly. */
  .ftr-legal a, .fserv a, .srvrow a, .brandrow a, .crumbs a, .brand{ position:relative; }
  .ftr-legal a::after, .fserv a::after, .srvrow a::after,
  .brandrow a::after, .crumbs a::after, .brand::after{
    content:""; position:absolute; top:50%; left:50%;
    transform:translate(-50%,-50%);
    min-width:44px; min-height:44px;
    width:100%; height:100%;
  }

  /* --- The floating WhatsApp button gets out of the way. -------------------
     It is 54x54, fixed 22px from the bottom-right. The footer reserves space
     for it (--wa-keepout); mid-page content never did. Sampled down every page
     at 393x852 it covered "Enquire on WhatsApp" and "Offer a Trade" on a watch
     listing, and on the contact page the WhatsApp, Instagram and Facebook
     tiles plus the "Find Me a Watch" button - four ways to make contact, on
     the page whose only job is contact.

     site.js now hides it whenever something tappable is underneath it, and
     brings it straight back when the coast is clear. Hidden rather than moved:
     a button that jumps around the screen is worse than one that waits. */
  .wa{ transition:transform .3s var(--ease), background .35s var(--ease),
                  opacity .25s var(--ease), visibility .25s var(--ease); }
  .wa.wa-away{ opacity:0; visibility:hidden; transform:scale(.82) translateY(6px); }

  /* --- The page must not scroll behind an open menu. ---------------------- */
  body.mnav-open{ overflow:hidden; }
}

@media (max-width:520px){

  /* --- One watch per row. M's call, 8 August. ------------------------------
     Two columns put a 174px card with a 229px photograph on a 393px iPhone -
     about the size of a thumbnail in a messaging app, on a site whose whole
     proposition is that the photograph is of the actual watch. Model names
     wrapped to two lines in one card and one in its neighbour, so every row
     ended ragged.

     The 320px iPhone was already dropping to one column and looked markedly
     better for it: a 286px card carrying a 379px photograph. That is now what
     every phone gets. Tablet and desktop keep two, three and four columns
     exactly as they were. */
  /* iOS-02, 8 Aug. THE ONE-COLUMN RULE IS GONE - M's call after seeing both on
     his own phone.

     The audit measured the photograph at 217-260px in two columns against 379px
     in one and concluded the photograph is the product. That was a reasonable
     inference from a number. M looked at it and preferred the density: more
     stock per screen, less scrolling, and a collection that reads as a
     collection. He is the one who sells these watches.

     Removing the override drops every grid back to the base `.g4` rule below,
     `minmax(min(158px,100%),1fr)`, which gives two columns on a phone and one
     at 320px where two will not fit. That covers all SIX grids in one go -
     #grid-available, #grid-sold, #grid-hits, #grid-near and the home page's
     #home-avail and #home-sold - which is what the one-column rule kept getting
     wrong by naming grids individually. Name the class, not the instances.

     What this makes urgent is iOS-01: at a 174px card the marks and the
     descriptors have to be light enough to live on it. See below. */

  /* WLM-M02 - a long unbroken search term must not push the page sideways.
     The counter echoes what was typed, and a 60-character token with no spaces
     - a pasted reference, a serial, a URL - painted straight out of its box and
     took the document to 575px on a 393px screen. 500 characters took it to
     1120px, and ?q=<300 chars> made a shared link open 2456px wide. The box was
     always the right width; the text simply had nowhere to break.

     BOTH echoes, not just the counter. The empty state says "Showing only
     watches matching X" and was the one still pushing the page 14px wide on a
     run of Arabic after the counter was fixed - and it took a text-node
     measurement to see it, because no element's box ever reported an overflow.
     When a bounding rectangle says nothing is wrong, measure the text. */
  .fcount, #emptywhy{ overflow-wrap:anywhere; }

  /* WLM-M05 - the refine panel must fit the screen it opens on.
     The ceiling was already there (`.pgrid` caps at min(52vh,400px) with the
     footer outside the scroll) and it was still not enough, because the cap is
     measured against the VIEWPORT while the panel opens part-way down the page:
     top at y=458 on a 852px screen, 553px tall, so its lower third - including
     Clear all - sat below the fold. A cap cannot see where the thing it caps
     begins.

     Two changes. The grid gets a tighter ceiling so the whole panel clears the
     smallest screen (320x568 leaves 483px under the sticky header; the panel
     now measures about 470). And site.js scrolls the panel into view when it
     opens on a phone, which is what actually solves it at every size. */
  .pgrid{ max-height:min(44vh, 320px); }
}

/* ===========================================================================
   iOS PASS REMEDIATION - 8 August 2026
   ---------------------------------------------------------------------------
   M ran the site through preview-ios.html at iPhone 15/16 (393 x 852) and sent
   seven observations. Everything below is inside a max-width media query, and
   the desktop was captured element by element before and after - 51 captures,
   17,000+ elements, position, size and 43 computed properties each.

   The one change that is NOT desktop-neutral is the stacking fix for the sort
   menu (iOS-05), because that defect exists on the desktop too and fixing it
   only on phones would have been dishonest. It is called out where it happens.
   ========================================================================= */

@media (max-width:820px){

  /* --- iOS-06. The mobile menu, and Contact inside it. ---------------------
     M: "The menu looks very clunky and not well-integrated. Also, the contact
     (while it looks good on the desktop website), looks odd and out of place
     here."

     Measured, the five items disagreed on everything: four links at 53px tall,
     13px type, a bottom hairline, no radius - and Contact at 44px, 11px type,
     a full 1px box with a 2px radius and its own padding. It is the header's
     desktop call to action (WEB-60) following the header into the burger
     panel, where "a different kind of thing" has nowhere to sit.

     So Contact stops being a button here and becomes the last row of the list,
     in rose because it is still the destination we want. Same height, same
     type, same rhythm - the emphasis carried by colour rather than by a box.
     And the list itself loses its final hairline, because a rule under the
     last item is a line to nowhere. */
  .mnav .btn{
    display:block; width:auto; border:0; border-radius:0; background:none;
    padding:15px 0; min-height:0;
    font-size:var(--fs-tiny); letter-spacing:var(--ls-wide); text-transform:uppercase;
    color:var(--rose2); text-align:left;
  }
  .mnav .btn:hover{ background:none; color:var(--rose); }
  .mnav > *:last-child{ border-bottom:0; }

  /* --- iOS-07. The wordmark comes back. -----------------------------------
     `.brand .bword{display:none}` below 520px meant a phone never saw the words
     "Watch Locker" anywhere in the persistent chrome - a nameless crest in an
     85px sticky header carrying 310px of nothing beside it. WEB-28 had already
     settled the opposite: "Footer logo without the wordmark. The crest alone.
     The header keeps the full lockup." The phone had quietly drifted off that.

     The lockup is scaled to fit rather than switched off, and it still clears
     the burger at 320px with room to spare. */
  .brand{ gap:10px; min-width:0; }
  .brand .bword{ display:block; }
}

@media (max-width:520px){
  .brand .bword{ display:block; height:15px; }
  .brand .bmark{ height:36px; }
}
@media (max-width:360px){
  .brand .bword{ height:13px; }
  .brand .bmark{ height:32px; }
}

@media (max-width:520px){

  /* --- iOS-01. The marks and the descriptors, sized for the card they are on.
     M: "Both the tags and the icon descriptors look too bulky on mobile, and
     take a lot of space."

     He is right, and with two columns confirmed as the design (iOS-02) the card
     is about 174px wide - the badge plate and the meta row were drawn for a
     card three times that. "HAUTE HORLOGERIE" wrapped to two lines inside its
     own plate; the meta row wrapped to two rows across the bottom quarter of
     the photograph, which is also where the watermark lives.

     Lighter, not smaller for its own sake: the tracking comes in because
     tracking is what makes short uppercase runs wide, the padding halves, the
     border softens to a hairline of the plate's own colour, and the blur goes -
     it costs a compositing layer per card and is invisible at this size. */
  .wmarks{ top:8px; left:8px; right:8px; gap:4px; }
  .wtag, .wnew{
    font-size:9.5px; letter-spacing:var(--ls-wide); padding:3px 7px;
    border-radius:2px; backdrop-filter:none; -webkit-backdrop-filter:none;
  }
  .wtag{ background:rgba(18,18,22,.9); border-color:rgba(99,99,111,.55); }

  /* THE ROW NEVER WRAPS, AND NEVER DROPS A DESCRIPTOR (M, 2026-08-11).
     It used to do both: wrap to two lines between roughly 400 and 480px, and
     hide the case size below 400px. Measured at every phone width, the three
     ICONS are what does not fit - three of them cost 57px of a 170px row, a
     third of it, and no combination of font size and gap fits with them on.
     The labels alone fit at the full 10.5px with room to spare, including the
     longer "Box and papers" the ERP is moving to. So the icons come off here
     and all three labels stay. Above this breakpoint nothing changes at all:
     the desktop card keeps its icons. */
  .shotmeta{
    flex-wrap:nowrap; gap:9px; padding:16px 6px 7px; font-size:10.5px;
    letter-spacing:var(--ls-body);
  }
  .shotmeta span{ gap:4px; }
  .shotmeta svg{ display:none; }
  .shotmeta .sm-size{ display:inline-flex; }
}
/* --- iOS-03b. The cliff at 401px, which is where M was actually standing ---
   M, 2026-08-11: "the 'BUY - SELL - TRADE - CONSIGN - SOURCE' line does not fit
   as a single row on mobile." The iOS-03 round below had already fixed this and
   the row measured as one line at 320, 360, 375, 390 and 393 - so the first
   answer was "cannot reproduce", which was true and useless.

   What the widths hid is the BREAKPOINT. iOS-03 lives in `max-width:400px`, and
   the moment a viewport reaches 401px the row snaps back to the full desktop
   treatment - 11px on a 2.64px letter-space - with no intermediate step. The
   natural width of the row is 346px, so the headroom over the container reads:

       393px viewport   29.4% headroom     (the iPhone 15/16 Pro - comfortable)
       401px viewport    6.2% headroom     <- the cliff
       405px viewport    7.3%
       412px viewport    9.3%
       414px viewport    9.9%
       470px viewport   26.1%              (comfortable again)

   Six per cent is not a margin, it is a coin toss - and it is exactly where the
   iPhone 16 Pro sits, at 402pt. Safari's metrics for this typeface need only be
   a few per cent wider than Chromium's, or the trailing letter-space needs only
   to be counted differently, and the row wraps. That it did not wrap in the
   harness says the harness is not Safari; it does not say the row fits.

   The fix is not a tighter value, it is the missing step. The shrink now
   continues from 401px to 520px instead of stopping dead, which restores the
   row to about 30% headroom across the whole band of phones that were falling
   off the cliff. Above 520px there is more than 40% and nothing is needed. */
@media (min-width:401px) and (max-width:520px){
  .srvrow, .fserv{
    font-size:clamp(9.5px, 2.5vw, 11px); letter-spacing:var(--ls-wide);
  }
}

/* One step tighter for the narrow phones. Between about 370 and 400px the grid
   is already two columns but the card is only ~165px, and the longest real row
   - "2024 39.8mm Box or papers" - needs 154.7px against 151px of room. This
   buys 11px, which also covers the longer "Box and papers" the ERP is moving
   to. Measured, not guessed; see qa/mobile. */
@media (max-width:400px){
  .shotmeta{ font-size:10px; gap:7px; padding-left:4px; padding-right:4px; }

  /* --- iOS-03. The service row fits one line again. -----------------------
     M: "The 'BUY · SELL · TRADE · CONSIGN · SOURCE' now don't fit in one row."

     Measured at 393px, Chromium fits it and Safari does not - the first
     confirmed WebKit-only finding of this build. Uppercase with .18em tracking
     is exactly where the two engines disagree, because the letter-space after
     the final glyph of each word is measured differently.

     Tracking is the lever, not size. Measured at 393px, the current
     11px/--ls-wider needs 321px against 359px of container - 11% of headroom in
     Chromium, and Safari still wrapped it. So the guess has to go: the size is
     set proportionally with clamp() so it scales with the viewport rather than
     with an assumption about one engine's metrics, and the tracking drops from
     --ls-wider to --ls-wide, which is the token for exactly this - small-caps
     labels and meta. That is roughly 24% headroom at 393 and 6% at 320.

     The row is deliberately NOT set to nowrap. If some engine still cannot fit
     it, a wrap is a far better failure than a page that scrolls sideways - and
     build.py now binds each separator to the word before it, so a wrap can
     never leave a middot stranded at the start of a line the way M's
     screenshot showed. */
  .srvrow, .fserv{
    font-size:clamp(9px, 2.5vw, 10.5px); letter-spacing:var(--ls-wide);
  }

  /* --- iOS-04. The footer stops being two screens. ------------------------
     M: "Footer: The Explore, Services, and get in touch sections take up too
     much space on a mobile, needs around 2 pages of real estate."

     Measured 1,340px - 2.01 screens on a 375pt iPhone, 1.57 on M's 393, and 19%
     of the entire home page. Three columns that sit side by side on the desktop
     become three full-width stacks below 520px: eighteen links in a single
     vertical run.

     The links pair up two-across instead. Eighteen links become nine rows, the
     44pt tap targets are untouched because the rows keep their height and only
     the width halves, and the headings still lead their groups.

     NOT below 361px. A rule already existed at `max-width:420px` putting these
     back to one column, and overriding it everywhere cost 9px of horizontal
     overflow on every page at 320px - "The Sold Archive" and "Terms &
     Conditions" simply do not fit in half of 286px. That rule was there for a
     reason; the fix is to narrow the window it applies to, not to remove it.
     Caught by qa/07-responsive, which is exactly what it is for. */
  .ftr-grid{ gap:var(--s5); }
}

@media (min-width:361px) and (max-width:520px){
  /* iOS-04, continued - see the note above for why this starts at 361px. */
  .fcol .ftwo{
    display:grid; grid-template-columns:repeat(2, minmax(0,1fr));
    column-gap:var(--s4);
  }
}

/* ---------- About: the intro reads with its title ------------------------
   The page title is centred and the two paragraphs under it were left-aligned,
   which M reported as the block not belonging to its heading. Centred to match,
   the same way every `.sec-head` on the site already works. Two short
   paragraphs only - this is not a rule for body copy at large. */
.aboutlede{ text-align:center; margin-inline:auto; }
.aboutlede .lede{ margin-inline:auto; }

/* ---------- About: the six commitments use the SERVICES card ---------------
   They were `.card` - a different component with a large numeral and a smaller
   body size - while the services page used `.qcard`. M asked for one formatting.
   `.qgrid` stretches the rows so a row of three ends on one edge whatever the
   copy does, which is the same fix the services page needed. */
.qgrid{ align-items:stretch; }
.qgrid .qcard{ height:100%; }

/* ---------- FAQ: the link to the governing clause -------------------------
   Each answer that summarises a term carries a link to the clause it summarises,
   because the Terms state in their own first section that they prevail where the
   two differ - so a summary that cannot be checked is a trap. Styled quietly:
   it is a footnote to the answer, not a call to action. */
.faq .faqterms{
  display:inline-block; margin-top:var(--s3);
  font-size:var(--fs-micro); letter-spacing:var(--ls-wide); text-transform:uppercase;
  color:var(--mute2); border-bottom:1px solid transparent; transition:color .3s var(--ease), border-color .3s var(--ease);
}
.faq .faqterms:hover, .faq .faqterms:focus-visible{ color:var(--rose2); border-bottom-color:var(--rose-dim); }
@media (max-width:820px){ .faq .faqterms{ padding:var(--s2) 0; min-height:var(--tap); display:flex; align-items:center; } }

/* ---------- a link inside small print must look like a link ---------------
   M, 2026-08-10: the linked words in the About and FAQ footnotes were the same
   weight and nearly the same colour as the sentence around them, so the line
   read as prose with nothing to click. Weight carries it, with the rose on
   hover the rest of the site already uses. */
.inlink{ font-weight:600; color:var(--text); border-bottom:1px solid var(--rose-dim);
         transition:color .3s var(--ease), border-color .3s var(--ease); }
.inlink:hover, .inlink:focus-visible{ color:var(--rose2); border-bottom-color:var(--rose2); }
/* WCAG 2.5.8 wants 24x24 on a phone, and a 17px-tall inline link in a footnote
   is not that. The target grows without the text moving, the same transparent
   ::after the footer links use. */
@media (max-width:820px){
  /* THE SAME TRANSPARENT ::after THE FOOTER LINKS USE, and `display:inline` is
     load-bearing: `inline-block` was the first attempt and it cost the link its
     WCAG 2.5.8 exemption as a link inside a sentence, so build/mobile.mjs began
     measuring it against the 44pt guideline and failing. Inline keeps the
     exemption, the pseudo-element gives the finger a real target either way,
     and the words do not move. */
  .inlink{ position:relative; display:inline; }
  .inlink::after{
    content:""; position:absolute; top:50%; left:50%;
    transform:translate(-50%,-50%);
    min-width:44px; min-height:44px; width:100%; height:100%;
  }
}

/* ---------- the hero's single line, replacing the three chips -------------
   Three chips read as a specification list on the one screen that should make
   a promise. One sentence, in the chips' own register so the hero's rhythm is
   unchanged. */
.heroline{
  margin-top:var(--s5);
  font-size:var(--fs-micro); letter-spacing:var(--ls-wider); text-transform:uppercase;
  color:var(--rose2);
}

/* ---------- About: Viewings and Brands, centred like every other section ---
   They were a two-column pair reading from the left while everything above and
   below them was centred. `.g2.pair.centred` keeps the two columns and centres
   everything inside them - eyebrow, heading, rule, copy and button. */
.g2.pair.centred > div{ text-align:center; margin-inline:auto; }
.g2.pair.centred > div > .lede{ margin-inline:auto; }
.g2.pair.centred .btnrow{ justify-content:center; }
