/* ========================================
   ZeroLoss GPS - editorial layer
   Warm panels, chromatic rims, bento tiles.
   Used by the homepage and the product page.
   ======================================== */

/* ===== SHARED PIECES ===== */

/* small caps label with a leading rule, as used beside section copy */
.label-rule { font-size: .68rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--text2); max-width: 16ch; }
.label-rule::before { content: ''; display: block; width: 5.5rem; height: 1px; background: var(--border-light); margin-bottom: 1.1rem; }

/* chromatic hairline echoing the product's rim light */
.chroma-line { height: 3px; border-radius: 999px; background: var(--chroma); }

.panel { background: var(--panel); border-radius: var(--radius); overflow: hidden; position: relative; }
.panel-deep { background: var(--panel-deep); color: #efe7dd; }


/* pill CTA with a trailing arrow */
.btn-pill {
  display: inline-flex; align-items: center; gap: .8rem;
  color: #fff; border-radius: 999px;
  /* Pink, not filtered glass - see the note on .btn-primary. */
  background: var(--accent);
  border: 1px solid transparent;
  box-shadow: 0 6px 18px rgba(244, 67, 108, .22);
  position: relative; overflow: hidden;
  padding: .95rem 1.5rem; min-height: 48px;
  font-size: .72rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  transition: background .25s var(--ease), transform .25s var(--ease), box-shadow .25s var(--ease);
}
.btn-pill:hover {
  background: var(--accent-lt);
  box-shadow: 0 8px 22px rgba(244, 67, 108, .3);
}
/* The arrow is drawn rather than typed. As a character it could not be
   centred honestly: place-items centres the line box, but the glyph's ink
   sits about 2px below that box's middle, and the exact amount depends on
   the font - a nudge tuned to Plus Jakarta Sans would be wrong the moment a
   machine fell back to Segoe UI. An SVG has no bearings, so centring the box
   centres the mark, at every size and in every font. */
.btn-pill .dot { width: 22px; height: 22px; border-radius: 999px; background: rgba(255,255,255,.16); display: grid; place-items: center; }
.btn-pill .dot svg { width: 11px; height: 11px; display: block; }
.btn-pill-light {
  background: rgba(255, 255, 255, .42);
  -webkit-backdrop-filter: blur(30px) saturate(180%) brightness(1.25);
          backdrop-filter: blur(30px) saturate(180%) brightness(1.25);
  border: 1px solid rgba(255, 255, 255, .62);
  box-shadow: none;
  position: relative; overflow: hidden;
  color: var(--ink);
}
.btn-pill-light:hover { background: rgba(255, 255, 255, .58); border-color: rgba(255, 255, 255, .95); }
.btn-pill-light .dot { background: rgba(22,18,15,.12); }

/* the hero pill is the one button sitting on photography rather than in a
   layout, so it reads large at the shared size - trimmed, but held at 44px
   so it stays a comfortable tap target */
.hero-copy .btn-pill { padding: .7rem 1.15rem; min-height: 44px; font-size: .66rem; gap: .6rem; }
.hero-copy .btn-pill .dot { width: 18px; height: 18px; }
.hero-copy .btn-pill .dot svg { width: 9px; height: 9px; }

/* ===== HERO ===== */
.hero {
  position: relative; border-radius: var(--radius); overflow: hidden;
  min-height: clamp(21rem, 46vw, 33rem);
  display: flex; align-items: flex-start; background: var(--panel);
}
/* The photograph is square and the hero is roughly 2.3:1, so only about 43%
   of the source height is visible. Anchoring low keeps the tracker and its
   ZERO LOSS branding inside the frame instead of clipping them off. */
.hero-art { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 58% 82%; border-radius: 0; }
/* ink copy over a bright sky needs a ground; this fades out before it
   reaches the product so the picture is never washed out */
.hero::before {
  content: ''; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(96deg, rgba(246,242,236,.95) 0%, rgba(246,242,236,.88) 32%, rgba(246,242,236,.45) 54%, rgba(246,242,236,0) 72%);
}
.hero-copy { position: relative; z-index: 2; padding: clamp(1.5rem, 3.5vw, 3rem); max-width: 34rem; }
.hero-copy h1 { margin: 1.1rem 0 1.5rem; }
.hero-copy h1 em { font-style: normal; color: var(--accent); }

/* ===== STATEMENT ROW ===== */
.statement { display: grid; grid-template-columns: minmax(9rem, .38fr) 1fr; gap: clamp(1.5rem, 5vw, 4rem); align-items: start; }
.statement-body { font-size: clamp(1.15rem, 2.1vw, 1.6rem); font-weight: 500; line-height: 1.42; letter-spacing: -.02em; max-width: 34ch; }
.statement-body .dim { color: var(--muted); }
@media (max-width: 780px) { .statement { grid-template-columns: 1fr; gap: 1.5rem; } }

/* ===== GALLERY ROW ===== */
/* four photographs of equal weight, so equal columns and one square crop -
   the sources are a mix of square and portrait and cover reconciles them */
.gallery-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(.6rem, 1.4vw, 1.1rem); align-items: center; }
.gallery-row img { border-radius: 18px; width: 100%; aspect-ratio: 1 / 1; object-fit: cover; }

/* the rail's "there is more this way" arrow. Hidden until the script has
   confirmed the row actually overflows, so it never appears on a desktop
   where all four photographs are already in view. It sits under the rail
   rather than over it - a quiet drawn line, not a control competing with
   the photograph. */
.gallery-rail { position: relative; }
.rail-hint {
  display: none;
  margin: .9rem auto 0;
  padding: .5rem 1.5rem;            /* thin mark, comfortable target */
  color: var(--text2); opacity: .55;
  transition: opacity .35s var(--ease);
}
.rail-hint[hidden] { display: none; }
.rail-hint.is-on { display: block; }
.rail-hint svg { width: 64px; height: 12px; display: block; }
/* the line itself travels, so the movement reads as "scroll this way"
   rather than as a blinking control */
.rail-hint.is-on svg { animation: rail-nudge 2.1s var(--ease) infinite; }
/* once the reader has scrolled, the arrow has done its job */
.rail-hint.is-off { opacity: 0; pointer-events: none; }
.rail-hint.is-off svg { animation: none; }

@keyframes rail-nudge {
  0%, 55%, 100% { transform: translateX(0); opacity: 1; }
  75%           { transform: translateX(7px); opacity: .7; }
  90%           { transform: translateX(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .rail-hint.is-on svg { animation: none; }
}

/* ===== FEATURE GRID ===== */

/* ===== FRAMED SHOWCASE ===== */
.showcase { display: grid; grid-template-columns: 1fr 1.1fr 1fr; gap: clamp(1rem, 3vw, 2.5rem); align-items: center; padding: clamp(1.5rem, 4vw, 3.5rem); }
.showcase h2 { max-width: 11ch; }
.showcase-side p { color: var(--text2); font-size: .88rem; line-height: 1.6; max-width: 30ch; }
.showcase-side h3 { margin-bottom: .5rem; }
.frame { position: relative; padding: 6px; border-radius: 20px; background: var(--chroma); }
.frame img { border-radius: 15px; width: 100%; display: block; aspect-ratio: 3 / 4; object-fit: cover; }
@media (max-width: 860px) { .showcase { grid-template-columns: 1fr; } .showcase h2 { max-width: none; } }

/* ===== BENTO =====
   The artwork carries no text and leaves space for it, so each tile is the
   picture plus live HTML copy positioned into that space. Copy stays real
   text: it reflows, scales with the reader's settings and is selectable. */
.bento { display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(.6rem, 1.4vw, 1rem); }
.bento-tile {
  position: relative; overflow: hidden; border-radius: 18px;
  display: block; color: #fff; text-decoration: none;
  background: #2a1f2b;
}
.bento-tall { grid-row: span 2; aspect-ratio: 620 / 1124; }
.bento-wide { grid-column: span 2; aspect-ratio: 1000 / 427; }
.bento-tile:not(.bento-tall):not(.bento-wide) { aspect-ratio: 640 / 560; }
/* The image carries the tile's radius rather than relying on the tile to
   clip it. overflow:hidden plus border-radius leaves a hairline of the
   parent showing along the curve - the browser antialiases the clip and the
   image edge separately - which read as pale nicks in the corners. Rounding
   the image itself removes the seam because there is nothing left to clip. */
.bento img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }

/* copy sits in the empty half of the picture */
.bento-copy {
  position: absolute; z-index: 2; left: 0; top: 0; bottom: 0; width: 58%;
  padding: clamp(1rem, 2vw, 1.5rem);
  display: flex; flex-direction: column; justify-content: center;
}
.bento-copy b { display: block; font-size: clamp(1.4rem, 2.9vw, 2.3rem); font-weight: 700; letter-spacing: -.03em; line-height: 1.04; }
.bento-copy p { font-size: clamp(.78rem, 1vw, .84rem); line-height: 1.5; margin-top: .6rem; opacity: .92; }
.bento-copy h3 { font-size: clamp(.95rem, 1.3vw, 1.1rem); }

.bento-copy-top { bottom: auto; justify-content: flex-start; width: auto; right: 0; }
.bento-copy-bottom { top: auto; width: 100%; justify-content: flex-end;
  background: linear-gradient(to top, rgba(16,12,20,.85), rgba(16,12,20,0)); }
.bento-lead { font-size: clamp(.88rem, 1.2vw, .95rem); font-weight: 600; line-height: 1.35; }
/* the supporting line under a tile caption: lighter, and set back so it
   reads as secondary to the line above it */
.bento-sub { font-size: clamp(.76rem, 1vw, .82rem); font-weight: 400; line-height: 1.4; margin-top: .3rem; opacity: .82; }
/* the cream tile's copy sits together at the foot, price line directly above
   its description, the way the other tiles read */
.bento-copy-split { width: 100%; justify-content: flex-end; }
.bento-copy-split b { font-size: clamp(1.15rem, 2vw, 1.6rem); }
.bento-copy-split p { font-size: clamp(.72rem, .9vw, .8rem); margin-top: .35rem; max-width: 26ch; }

/* the cream tile needs ink copy */
.bento-tile-price { background: #efdad5; color: var(--ink); }
.bento-tile-price .bento-copy p { color: var(--text2); opacity: 1; }

/* ===== TABLET =====
   Two columns. The tall tile is portrait (0.55) while its neighbours are
   landscape, so on one row it stands ~350px taller and leaves a hole beside
   it. Letting it span both rows fills that space, and `dense` lets the last
   single-column tile back-fill the gap the wide tiles cannot use. Its own
   aspect-ratio has to go: with a definite height from the row span, the ratio
   would drive the width and push the tile out of its column. */
@media (max-width: 900px) and (min-width: 721px) {
  .bento { grid-template-columns: 1fr 1fr; grid-auto-flow: row dense; }
  .bento-tall { grid-row: span 2; aspect-ratio: auto; }
  .bento-wide { grid-column: span 2; }
}

@media (max-width: 720px) {
  .bento { grid-template-columns: 1fr 1fr; }
  .bento-tall { grid-row: auto; }
}

/* the bento's call to action, sitting under the grid rather than inside a tile */
.bento-foot { display: flex; justify-content: center; margin-top: clamp(1.25rem, 2.5vw, 1.75rem); }

/* ===== CLOSING BAND ===== */
.closing-band {
  background: var(--panel-deep); color: #efe7dd; border-radius: var(--radius);
  padding: clamp(1.5rem, 4vw, 3rem);
  display: flex; align-items: center; justify-content: space-between; gap: 1.5rem; flex-wrap: wrap;
}
.closing-band h2 { max-width: 20ch; }

/* ===== FOOTER ===== */
/* wins over the muted .site-footer a colour */
.site-footer a.footer-logo { display: inline-block; color: #efe7dd; padding: 0; }
.site-footer a.footer-logo { margin-bottom: clamp(1.5rem, 3vw, 2.25rem); }
.site-footer a.footer-logo:hover { color: #fff; }
.footer-logo .logo-mark { height: 44px; }
.footer-grid { display: grid; grid-template-columns: repeat(3, minmax(8rem, 1fr)) 1.3fr; gap: clamp(1.5rem, 3vw, 2.5rem); }
/* the sign-up sits in the grid's fourth column, which was otherwise an empty
   quarter of the footer on a wide screen */
.footer-sub-note { color: rgba(239,231,221,.55); font-size: .8rem; line-height: 1.5; margin: 0 0 .85rem; max-width: 26ch; }
.subscribe { display: flex; gap: .4rem; background: rgba(255,255,255,.08); border: 1px solid rgba(239,231,221,.22); border-radius: 999px; padding: .35rem .35rem .35rem 1.1rem; }
.subscribe input { flex: 1; background: none; border: none; color: #efe7dd; font-size: .85rem; outline: none; min-width: 0; }
.subscribe input::placeholder { color: rgba(239,231,221,.5); }
.subscribe button {
  background: rgba(255, 255, 255, .88); color: var(--ink); border-radius: 999px;
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  padding: .6rem 1.15rem; font-size: .72rem; font-weight: 700; letter-spacing: .06em;
}
.subscribe button:hover { background: #fff; }
.subscribe-msg { font-size: .76rem; color: #fff; margin-top: .6rem; min-height: 1.1em; }
@media (max-width: 860px) { .footer-grid { grid-template-columns: 1fr 1fr; } .footer-sub { grid-column: 1 / -1; } .footer-sub-note { max-width: none; } }

/* ===== PRODUCT PAGE ===== */
.pdp-hero { display: grid; grid-template-columns: 1.05fr .95fr; gap: clamp(1.5rem, 4vw, 3.5rem); align-items: start; }
@media (max-width: 900px) { .pdp-hero { grid-template-columns: 1fr; } }

/* gallery: one scroll-snapping track, dots below */
.pdp-stage { background: var(--panel); border-radius: var(--radius); overflow: hidden; }
.pdp-track {
  display: flex; overflow-x: auto; scroll-snap-type: x mandatory;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.pdp-track::-webkit-scrollbar { display: none; }
/* the photographs fill the stage rather than floating inside a mat: no
   padding, no inner radius of their own, and cover instead of contain so a
   portrait source still meets both edges. The stage's own radius clips them. */
.pdp-slide { flex: 0 0 100%; scroll-snap-align: center; display: block; }
.pdp-slide img { display: block; width: 100%; height: clamp(18rem, 46vw, 30rem); object-fit: cover; border-radius: 0; }
.pdp-dots { display: flex; gap: .45rem; justify-content: center; margin-top: .9rem; }
.pdp-dot { width: 34px; height: 34px; border-radius: 999px; position: relative; }
.pdp-dot::after { content: ''; position: absolute; inset: 14px 8px; border-radius: 999px; background: var(--border-light); transition: background .25s var(--ease); }
.pdp-dot.is-on::after { background: var(--ink); }

/* buy column */
.pdp-buy h1 { font-size: clamp(1.7rem, 3.4vw, 2.6rem); margin: .5rem 0 .9rem; }
/* the buy column reads as three stacked registers - name, description, then
   the claim that closes the sale - divided by hairlines rather than by
   whitespace alone */
.pdp-sub {
  color: var(--text2); font-size: .95rem; line-height: 1.6; max-width: 46ch;
  border-top: 1px solid var(--border-light);
  margin-top: .95rem; padding-top: .95rem;
}
.pdp-sub-claim {
  display: block; color: var(--ink); font-weight: 700;
  border-top: 1px solid var(--border-light);
  margin-top: .95rem; padding-top: .95rem;
}
.pdp-price { margin: 1.25rem 0 .35rem; }
.pdp-tax { color: var(--muted); font-size: .78rem; }
.pdp-option { margin-top: 1.5rem; }
.pdp-option-label { display: block; font-size: .68rem; font-weight: 700; letter-spacing: .13em; text-transform: uppercase; color: var(--muted); margin-bottom: .6rem; }
.pdp-chips { display: flex; gap: .5rem; flex-wrap: wrap; }
.pdp-chip {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .6rem .95rem; border-radius: 999px;
  border: 1px solid var(--border-light); background: var(--card);
  font-size: .8rem; font-weight: 600;
}
.pdp-chip svg { width: 15px; height: 15px; }
.pdp-note { color: var(--muted); font-size: .78rem; margin-top: .6rem; }
/* Wider than it was, but still short of the column. Run edge to edge and a
   six-character field reads as a search bar on a desktop; the cap keeps it
   looking like what it is. Phones get the full width, where the column is
   narrow enough that a cap would strand the button. */
.pdp-pin { display: flex; gap: .5rem; max-width: 28rem; }
@media (max-width: 720px) { .pdp-pin { max-width: none; } }
.pdp-pin input {
  flex: 1; min-width: 0; border-radius: 999px; border: 1px solid var(--border-light);
  background: var(--card); padding: .8rem 1.1rem; color: var(--text); font-size: .9rem; outline: none;
  transition: border-color .25s, box-shadow .25s;
}
.pdp-pin input:focus { border-color: var(--ink); box-shadow: 0 0 0 3px rgba(22,18,15,.08); }
.pdp-pin .btn-ghost { padding: .8rem 1.4rem; min-height: 0; }
.pdp-pin-result { font-size: .8rem; color: var(--text2); margin-top: .55rem; min-height: 1.15em; }
.pdp-ctas { display: flex; gap: .7rem; flex-wrap: wrap; margin-top: 1.75rem; }
.pdp-ctas .btn-ghost { min-height: 48px; }
/* four promises, given equal weight in a 2x2 of quiet tiles - a bullet
   list read as an afterthought under the buy button */
.pdp-trust { display: grid; grid-template-columns: 1fr 1fr; gap: .5rem; margin-top: 1.35rem; }
.pdp-trust li {
  display: flex; align-items: center; gap: .55rem;
  font-size: .78rem; font-weight: 600; color: var(--text2);
  background: var(--card); border: 1px solid var(--border);
  border-radius: 12px; padding: .62rem .7rem;
}
.pdp-trust svg { width: 17px; height: 17px; flex: none; color: var(--accent); }

/* marquee strip between sections */
.pdp-marquee { overflow: hidden; border-block: 1px solid var(--border); padding: .85rem 0; background: var(--bg2); }
.pdp-marquee-track { display: flex; width: max-content; animation: pdp-marquee 42s linear infinite; }
.pdp-marquee span { font-size: .74rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--text2); padding: 0 1.5rem; white-space: nowrap; }
.pdp-marquee span::after { content: '\2022'; margin-left: 1.5rem; color: var(--accent); }
@keyframes pdp-marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { .pdp-marquee-track { animation: none; } }

/* section heads with the accented second line */
.pdp-head { margin-bottom: clamp(1.5rem, 3vw, 2.25rem); }
.pdp-head h2 em { font-style: normal; color: var(--accent); }

/* alternating image/text rows */
.pdp-split { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(1.5rem, 4vw, 3.5rem); align-items: center; }
.pdp-split-rev .pdp-split-media { order: 2; }
.pdp-split-media { border-radius: var(--radius); overflow: hidden; background: var(--panel); }
.pdp-split-media img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: 0; }
.pdp-split-text h2 { margin: .6rem 0 .9rem; }
.pdp-split-text h2 em { font-style: normal; color: var(--accent); }
.pdp-split-text p { color: var(--text2); font-size: .95rem; line-height: 1.65; margin-bottom: 1.25rem; max-width: 46ch; }
@media (max-width: 860px) {
  .pdp-split { grid-template-columns: 1fr; }
  .pdp-split-rev .pdp-split-media { order: 0; }
  /* stacked, a button sized to its label leaves a lopsided gap beside it */
  .pdp-split-text .btn-ghost { display: flex; width: 100%; justify-content: center; }
}

/* closing CTA */
.pdp-cta h2 em { font-style: normal; color: var(--accent); }
.pdp-cta-sub { color: rgba(239,231,221,.75); font-size: .92rem; margin: .5rem 0 1rem; }
.pdp-cta .price-row { color: #efe7dd; }
.pdp-cta .price-now { color: #fff; }
.pdp-cta .price-mrp { color: rgba(239,231,221,.5); }
/* Centred, not flex-start. The column is only as wide as the pill, so
   centring sits the link under the middle of the button - where it
   reads as belonging to it. Flush left put it under the pill's left
   edge, which is a long way from the label because of how much side
   padding the pill carries, so the two looked unrelated. */
.pdp-cta-btns { display: flex; flex-direction: column; gap: .75rem; align-items: center; }
.pdp-cta-link { color: rgba(239,231,221,.75); font-size: .82rem; text-decoration: underline; text-underline-offset: 3px; }
.pdp-cta-link:hover { color: #fff; }

/* ===== STICKY BUY BAR (phones only) ===== */
.buybar { display: none; }
@media (max-width: 720px) {
  /* a floating pill rather than an edge-to-edge bar: it sits above the page
     instead of walling it off, and clears the home indicator on its own */
  .buybar {
    display: flex; align-items: center; gap: .6rem;
    position: fixed; z-index: 180;
    left: clamp(.6rem, 3vw, 1rem); right: clamp(.6rem, 3vw, 1rem);
    bottom: calc(.6rem + env(safe-area-inset-bottom, 0px));
    padding: .35rem .35rem .35rem 1.35rem;
    border-radius: 999px;
    /* the same glass recipe as .btn-pill-light: a thin fill and a bright,
       saturated backdrop filter, so the page reads through it rather than
       being covered by a near-white slab */
    background: rgba(255, 255, 255, .42);
    -webkit-backdrop-filter: blur(30px) saturate(190%) brightness(1.22);
            backdrop-filter: blur(30px) saturate(190%) brightness(1.22);
    border: 1px solid rgba(255, 255, 255, .62);
    box-shadow: 0 18px 40px -22px rgba(22, 18, 15, .38);
    transform: translateY(160%); transition: transform .35s var(--ease-out);
  }
  .buybar-on .buybar { transform: none; }
  .buybar-info { flex: 1; min-width: 0; }
  .buybar-info b { display: block; font-size: .76rem; line-height: 1.2; }
  .buybar .price-row { margin: .15rem 0 0; gap: .45rem; }
  .buybar .price-now { font-size: .92rem; }
  .buybar .price-mrp { font-size: .72rem; }
  .buybar .btn-primary { flex: none; border-radius: 999px; padding: .6rem 1.05rem; min-height: 40px; font-size: .68rem; }
  /* Reserved on the one page that has a buy bar, and reserved there whether
     or not the bar is currently showing - revealing it must not change the
     document height under the reader, which is half of what made it flicker.
     Keyed off the bar existing in the markup rather than off its visible
     state, so every other page keeps a normal footer instead of a dead strip. */
  body:has(.buybar) .site-footer { padding-bottom: 5rem; }
}
@media (prefers-reduced-motion: reduce) { .buybar { transition: none; } }

/* ===== TOUCH TARGETS ===== */
@media (max-width: 900px) {
  .subscribe { padding: .4rem .4rem .4rem 1.15rem; }
  .subscribe input, .subscribe button { min-height: 44px; }
  .site-footer a { padding: .62rem 0; }
}

/* ===== PHONES =====
   Rows that read as composed grids on a desktop become swipeable
   rails, so nothing is squeezed below a legible size. */
@media (max-width: 720px) {
  /* the photograph fills the whole hero here too; the copy rides on top of
     it, so the wash turns vertical to sit behind the headline */
  .hero { min-height: clamp(27rem, 104vw, 36rem); }
  .hero-art { object-position: 62% 68%; }
  .hero::before {
    background: linear-gradient(180deg, rgba(246,242,236,.96) 0%, rgba(246,242,236,.9) 34%, rgba(246,242,236,.45) 56%, rgba(246,242,236,0) 76%);
  }
  .hero-copy { max-width: none; padding: 1.35rem 1.25rem 1.5rem; }
  .hero-copy h1 { margin: .9rem 0 1.25rem; }

  .gallery-row {
    display: flex; overflow-x: auto; scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
    margin-inline: calc(-1 * clamp(1.25rem, 4vw, 2.5rem));
    padding-inline: clamp(1.25rem, 4vw, 2.5rem);
    scroll-padding-inline: clamp(1.25rem, 4vw, 2.5rem);
    align-items: stretch;
  }
  .gallery-row::-webkit-scrollbar { display: none; }
  .gallery-row img { flex: 0 0 min(74vw, 19rem); scroll-snap-align: start; aspect-ratio: 1 / 1; }

  .showcase { padding: 1.25rem; gap: 1.5rem; }
  .frame img { aspect-ratio: 4 / 5; }
  .bento { grid-template-columns: 1fr; }
  .bento > *, .bento-tall { min-height: 13rem; }
  /* On a phone a 2.34:1 tile is too short to hold copy, so the tiles take a
     comfortable aspect, the picture holds its right-hand subject, and the
     copy moves to a scrim along the bottom where it is always legible. */
  .bento { grid-template-columns: 1fr; }
  /* stretched grid items resolve aspect-ratio from the row height, which
     shrinks their width - start alignment lets width lead instead */
  .bento > * { align-self: start; width: 100%; }
  .bento-tall, .bento-wide, .bento-tile:not(.bento-tall):not(.bento-wide) { aspect-ratio: 4 / 3; }
  .bento-tall { aspect-ratio: 3 / 4; grid-column: auto; }
  .bento-wide { grid-column: auto; }
  .bento-wide img { object-position: 72% center; }
  .bento-copy, .bento-copy-top, .bento-copy-split {
    top: auto; bottom: 0; width: 100%; right: 0; justify-content: flex-end;
    padding: 1.1rem 1.15rem;
    background: linear-gradient(to top, rgba(16,12,20,.88) 40%, rgba(16,12,20,0));
  }
  .bento-tile-price .bento-copy {
    background: linear-gradient(to top, rgba(248,242,235,.94) 40%, rgba(248,242,235,0));
  }
  .bento-copy b { font-size: 1.6rem; }
  .bento-copy p { font-size: .85rem; }
  .closing-band { padding: 1.5rem 1.35rem; }
  .closing-band h2 { max-width: none; }
  /* once the band stacks, a pill sized to its label leaves an odd gap to the
     right of it - so it runs the full width instead */
  .closing-band .btn-pill { width: 100%; justify-content: center; }
  .pdp-buy h1 em { font-size: .6em; }
  .pdp-ctas .btn-pill, .pdp-ctas .btn-ghost { flex: 1 1 100%; justify-content: center; }
  .pdp-cta-btns { width: 100%; }
  .pdp-cta-btns .btn-pill { width: 100%; justify-content: center; }
  .pdp-dot { width: 44px; height: 44px; }
  .pdp-dot::after { inset: 19px 12px; }
  .pdp-cta-link { padding: .7rem 0; min-height: 44px; display: inline-flex; align-items: center; }
}

/* ===== GLASS FALLBACK ===== */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .btn-pill { background: var(--ink); }
  .btn-pill-light { background: #fff; }
  .subscribe button { background: #fff; }
}

/* Blur is the expensive part of this effect while a phone scrolls. The
   buttons are small, so it stays - but the radius comes down and the tints
   go more opaque to hold contrast without leaning on the blur. */
@media (max-width: 720px) {
  /* blur radius is the expensive part while a phone scrolls, so it comes
     down; the brightness filter still supplies the contrast */
  /* The pink buttons carry no backdrop filter to thin, so only the light
     variants need lightening here. */
  .btn-pill-light, .btn-ghost {
    -webkit-backdrop-filter: blur(16px) saturate(170%) brightness(1.22);
            backdrop-filter: blur(16px) saturate(170%) brightness(1.22);
  }
}

/* Registered so they can be interpolated. Declared outside the motion
   query because a property has to exist before a rule can read it, even
   when nothing is animating it. */
@property --zl-sc { syntax: '<number>'; inherits: false; initial-value: 1.14; }
@property --zl-py { syntax: '<length>'; inherits: false; initial-value: 0px; }

/* ========================================
   MOTION
   Everything here animates transform and opacity only, so it stays on the
   compositor and never triggers layout. The whole layer is wrapped in a
   no-preference query: a reader who asks for reduced motion gets the static
   page, not a de-tuned version of this one.
   ======================================== */
@media (prefers-reduced-motion: no-preference) {

  @keyframes zl-rise { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
  @keyframes zl-settle { from { transform: scale(1.06); } to { transform: none; } }

  /* --- hero entrance: the picture settles while the copy rises over it --- */
  .hero-art { animation: zl-settle 1.4s var(--ease-out) both; }
  .hero-copy > * { animation: zl-rise .75s var(--ease-out) both; }
  .hero-copy > *:nth-child(1) { animation-delay: .08s; }
  .hero-copy > *:nth-child(2) { animation-delay: .20s; }

  /* --- grids cascade rather than arriving as one block --- */
  .js .reveal .bento > *,
  .js .reveal .gallery-row img,
  .js .reveal .grid > .card {
    opacity: 0; transform: translateY(18px);
    transition: opacity .6s var(--ease-out), transform .6s var(--ease-out);
  }
  .js .reveal.in .bento > *,
  .js .reveal.in .gallery-row img,
  .js .reveal.in .grid > .card { opacity: 1; transform: none; }

  .js .reveal.in .bento > *:nth-child(2),
  .js .reveal.in .gallery-row img:nth-child(2),
  .js .reveal.in .grid > .card:nth-child(2) { transition-delay: .08s; }
  .js .reveal.in .bento > *:nth-child(3),
  .js .reveal.in .gallery-row img:nth-child(3),
  .js .reveal.in .grid > .card:nth-child(3) { transition-delay: .16s; }
  .js .reveal.in .bento > *:nth-child(4),
  .js .reveal.in .gallery-row img:nth-child(4),
  .js .reveal.in .grid > .card:nth-child(4) { transition-delay: .24s; }
  .js .reveal.in .bento > *:nth-child(5) { transition-delay: .32s; }

  /* --- artwork breathes on hover, clipped by the tile --- */
  .gallery-row img { transition: transform .7s var(--ease-out); }
  .gallery-row img:hover { transform: scale(1.03); }
  .pdp-split-media { overflow: hidden; }

  /* --- the arrow leans the way it points --- */
  .btn-pill .dot { transition: transform .25s var(--ease); }
  .btn-pill:hover .dot { transform: translate(2px, -2px); }

  /* --- marquee pauses when a reader hovers it, so it can be read --- */
  .pdp-marquee:hover .pdp-marquee-track { animation-play-state: paused; }

  /* ========================================
     APPLE-ISH REFINEMENTS

     Three things separate this kind of motion from the usual fade-up.

     Duration. Nothing here is quick. A reveal that takes a second reads as
     the page settling; the same reveal at 300ms reads as a jump.

     One curve. Everything uses --ease-out, a hard decelerate with no
     overshoot. Bounce draws attention to the animation; this draws
     attention to the thing arriving.

     Everything keys off .reveal.in, which theme.js guarantees within 2.5s
     whatever the observer does. No rule below can strand content invisible,
     because the failsafe that covers the existing reveals covers these too.
     ======================================== */

  /* Longer and shorter: more time, less distance. A big translate reads as
     a slide; a small one reads as focus arriving. */
  .js .reveal {
    transition: opacity 1s var(--ease-out), transform 1s var(--ease-out);
    transform: translateY(20px);
  }

  /* Copy inside a section arrives in sequence rather than as a slab. The
     delays are small - the eye should register order, not wait through it. */
  .js .reveal .section-head > *,
  .js .reveal .pdp-head > *,
  .js .reveal .closing-band > * {
    opacity: 0; transform: translateY(14px);
    transition: opacity .85s var(--ease-out), transform .85s var(--ease-out);
  }
  .js .reveal.in .section-head > *,
  .js .reveal.in .pdp-head > *,
  .js .reveal.in .closing-band > * { opacity: 1; transform: none; }
  .js .reveal.in .section-head > *:nth-child(2),
  .js .reveal.in .pdp-head > *:nth-child(2),
  .js .reveal.in .closing-band > *:nth-child(2) { transition-delay: .09s; }
  .js .reveal.in .section-head > *:nth-child(3),
  .js .reveal.in .pdp-head > *:nth-child(3),
  .js .reveal.in .closing-band > *:nth-child(3) { transition-delay: .18s; }

  /* Photographs settle out of a slight zoom instead of appearing at rest.
     1.03 is deliberately almost nothing: at 1.1 it is an effect, at 1.03 it
     is just a picture coming into focus. The wrapper clips the overflow so
     nothing shifts the layout while it runs. */
  /* One transform expression per picture, fed by two registered custom
     properties. Registering them is what makes this work: an unregistered
     custom property cannot be interpolated, so the settle and the hover
     would jump rather than ease. Registered, each can carry its own
     transition while the other is written every frame.

     --zl-sc is the resting overscale. It is what gives the drift somewhere
     to go: at 1.14 a tile has 7% of its height spare at top and bottom, and
     the drift travels 5.5%, so an edge can never come into view. */
  .bento img,
  .home-guide img,
  .blog-hero img,
  .pdp-split-media img {
    transform: translate3d(0, var(--zl-py, 0px), 0) scale(var(--zl-sc, 1.14));
    transition: --zl-sc 1s var(--ease-out);
    will-change: transform;
  }

  /* Settling in from a shade wider, then resting. */
  .js .reveal .home-guide img,
  .js .reveal .blog-hero img,
  .js .reveal .pdp-split-media img { --zl-sc: 1.19; }
  .js .reveal.in .home-guide img,
  .js .reveal.in .blog-hero img,
  .js .reveal.in .pdp-split-media img { --zl-sc: 1.14; }

  /* Hover breathes against the same resting scale. */
  .bento-tile:hover img,
  .pdp-split-media:hover img,
  .home-guide:hover img { --zl-sc: 1.19; }

  .home-guide, .blog-hero { overflow: hidden; }

  /* Cards lift on hover with the same curve as everything else, so the page
     feels like one material rather than a set of separately built parts. */
  .card-lift, .home-guide, .blog-card, .blog-related-card {
    transition: transform .5s var(--ease-out), border-color .5s var(--ease-out),
                box-shadow .5s var(--ease-out);
  }

  /* The header thins as you leave the top of the page. Driven by a class
     from theme.js rather than by scroll position in CSS, so the threshold
     lives in one place. */
  .nav { transition: padding .45s var(--ease-out), background-color .45s var(--ease-out); }
  .nav.is-scrolled { padding-top: .55rem; padding-bottom: .55rem; }
  .nav.is-scrolled .logo-mark { transform: scale(.92); }
  .logo-mark { transition: transform .45s var(--ease-out); }

  /* Scroll-linked drift on the hero copy. --zl-parallax is written by
     theme.js on a rAF and read here, so the maths stays in JS and the
     painting stays in CSS.

     The copy rather than the picture, for a mechanical reason: .hero-art is
     the img itself and already carries the settle animation, and a running
     animation beats a transform declared on the same element. Moving the
     text against a still photograph reads the same way and fights nothing. */
  .hero-copy { will-change: transform; transform: translate3d(0, var(--zl-parallax, 0px), 0); }

}

/* --- press feedback: a flat squeeze, no lift, no shadow --- */
.btn-pill, .btn-primary, .btn-ghost, .btn-pill-light {
  transition: background .25s var(--ease), border-color .25s var(--ease),
              backdrop-filter .25s var(--ease), transform .16s var(--ease);
}
.btn-pill:active, .btn-primary:active, .btn-ghost:active, .btn-pill-light:active { transform: scale(.97); }

/* --- keyboard focus has to be visible on glass, where a subtle ring vanishes --- */
.btn-pill:focus-visible, .btn-primary:focus-visible, .btn-ghost:focus-visible,
.btn-pill-light:focus-visible, .bento-tile:focus-visible, .pdp-dot:focus-visible {
  outline: 2px solid var(--ink); outline-offset: 3px;
}

/* ===== BLOG =====
   A reading column, not a landing page. The measure is deliberately narrow
   and the type a shade larger than the rest of the site: these pages exist
   to be read start to finish, which nothing else here asks for. */
.blog-index-head { padding: 1rem 0 2.5rem; }
.blog-index-head h1 { font-size: clamp(1.9rem, 4.5vw, 2.8rem); }
.blog-index-head .lead { margin-top: .9rem; max-width: 60ch; }

.blog-grid { display: grid; gap: 1rem; padding-bottom: 3rem; }
.blog-card {
  display: block; padding: 1.5rem 1.6rem; background: var(--card);
  border: 1px solid var(--border); border-radius: 18px;
  transition: border-color .25s var(--ease), transform .25s var(--ease);
}
.blog-card:hover { border-color: var(--border-light); transform: translateY(-2px); }
.blog-card h2 { font-size: 1.18rem; margin: .55rem 0 0; line-height: 1.35; }
.blog-card p { color: var(--text2); font-size: .92rem; margin-top: .5rem; line-height: 1.6; }
.blog-card-meta { display: block; margin-top: .9rem; font-size: .76rem; color: var(--muted); }

.blog-tag {
  display: inline-block; font-size: .64rem; text-transform: uppercase;
  letter-spacing: .14em; font-weight: 700; color: var(--accent);
}

.blog-post { padding: 1rem 0 3rem; }
.blog-head { padding-bottom: 1.75rem; border-bottom: 1px solid var(--border); }
.blog-head h1 { font-size: clamp(1.9rem, 4.5vw, 2.7rem); margin-top: .5rem; line-height: 1.15; }
.blog-standfirst { margin-top: 1rem; font-size: 1.08rem; line-height: 1.65; color: var(--text2); max-width: 62ch; }
.blog-byline { margin-top: 1.1rem; font-size: .78rem; color: var(--muted); }

/* The article itself. 68ch is about 12 words a line, which is where prose
   stops being tiring to read. */
.blog-body { max-width: 68ch; }
.blog-body h2 { font-size: clamp(1.25rem, 2.6vw, 1.55rem); margin: 2.5rem 0 .9rem; }
.blog-body p { margin-bottom: 1.05rem; line-height: 1.75; color: var(--text2); }
.blog-body strong { color: var(--text); font-weight: 600; }
.blog-body ul { margin: 0 0 1.2rem 1.1rem; }
.blog-body li { margin-bottom: .6rem; line-height: 1.7; color: var(--text2); }
.blog-body a { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
.blog-body details.faq { margin-top: .6rem; }

/* A figure that is the point of a paragraph, given room to be one. */
.blog-callout {
  background: var(--card2); border-left: 3px solid var(--accent);
  border-radius: 0 12px 12px 0; padding: .95rem 1.2rem;
  font-weight: 600; color: var(--text) !important;
}

.blog-cta {
  margin-top: 3rem; padding: 1.75rem; background: var(--card);
  border: 1px solid var(--border); border-radius: 18px; text-align: center;
}
.blog-cta h2 { font-size: 1.3rem; }
.blog-cta p { color: var(--text2); margin: .7rem auto 1.3rem; max-width: 48ch; line-height: 1.65; }

.blog-related { margin-top: 3rem; }
.blog-related h2 { font-size: 1.15rem; margin-bottom: 1rem; }
.blog-related-grid { display: grid; gap: .8rem; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.blog-related-card {
  display: block; padding: 1.1rem 1.2rem; background: var(--card);
  border: 1px solid var(--border); border-radius: 14px;
  transition: border-color .25s var(--ease);
}
.blog-related-card:hover { border-color: var(--border-light); }
.blog-related-card strong { display: block; margin-top: .45rem; font-size: .95rem; line-height: 1.4; }
.blog-related-read { display: block; margin-top: .5rem; font-size: .72rem; color: var(--muted); }

/* ===== IN-ARTICLE ADVERT =====
   Deliberately quieter than the end-of-article block. It interrupts someone
   mid-read, so it earns its place by being small, stating the price, and
   offering one action rather than three. */
.blog-ad {
  display: flex; gap: 1.1rem; align-items: center;
  margin: 2.25rem 0; padding: 1.15rem 1.25rem;
  background: var(--card2); border: 1px solid var(--border);
  border-radius: 16px;
}
.blog-ad-thumb { flex: none; display: block; border-radius: 12px; overflow: hidden; }
.blog-ad img { width: 84px; height: 84px; display: block; border-radius: 12px; object-fit: cover; background: var(--panel); }
.blog-ad-copy { min-width: 0; }
.blog-ad-copy strong { display: block; margin-top: .25rem; font-size: 1rem; }
.blog-ad-copy p { margin: .35rem 0 0; font-size: .85rem; line-height: 1.55; color: var(--text2); }
/* The price comes from live inventory, so it is empty until that lands.
   No margin until it has content, or the block sits with a gap in it. */
.blog-ad .price-row { display: flex; align-items: baseline; gap: .5rem; margin-top: .55rem; }
.blog-ad .price-row:empty { display: none; }
.blog-ad .price-now { font-weight: 700; font-size: 1.05rem; }
.blog-ad .price-mrp { text-decoration: line-through; color: var(--muted); font-size: .82rem; }
.blog-ad .price-off { color: var(--accent); font-size: .74rem; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; }

.blog-ad-actions { display: flex; align-items: center; gap: .9rem; flex-wrap: wrap; margin-top: .8rem; }
/* A small pill. min-height stays at 44px so it is still a finger-sized
   target on a phone, whatever the font size suggests. */
.blog-ad-pill {
  padding: .6rem 1.4rem; font-size: .72rem; min-height: 44px;
  letter-spacing: .12em;
}
.blog-ad-pill:disabled { opacity: .55; cursor: not-allowed; }
.blog-ad-link { font-size: .8rem; color: var(--text2); text-decoration: underline; text-underline-offset: 3px; }
.blog-ad-link:hover { color: var(--accent); }

@media (max-width: 560px) {
  /* Stacked, because 84px of image beside text leaves the copy about
     twelve characters wide on a small phone. */
  .blog-ad { flex-direction: column; align-items: flex-start; }
  .blog-ad img { width: 64px; height: 64px; }
}

/* The article's opening image. Height is reserved by the width/height
   attributes on the img, so the text below does not jump when it loads. */
.blog-hero { margin: 1.75rem 0 0; }
.blog-hero img {
  width: 100%; height: auto; display: block;
  border-radius: 18px; background: var(--card2);
}

/* ===== HOME: GUIDES =====
   Cards with the article's own picture on top.

   One column or three, never two. auto-fit with a minmax was the obvious
   way to write this and it is wrong here: at a middling width it gives two
   columns and strands the third card on a row of its own, which reads as a
   layout that has broken rather than one that has wrapped. Three cards want
   either a single row or a single column. */
.home-guides { display: grid; gap: 1.1rem; grid-template-columns: 1fr; }
@media (min-width: 940px) {
  .home-guides { grid-template-columns: repeat(3, 1fr); }
}
.home-guide {
  display: block; background: var(--card); border: 1px solid var(--border);
  border-radius: 18px; overflow: hidden;
  transition: border-color .25s var(--ease), transform .25s var(--ease);
}
.home-guide:hover { border-color: var(--border-light); transform: translateY(-3px); }
/* A fixed aspect keeps the three cards level whatever the picture is. */
.home-guide img { width: 100%; aspect-ratio: 1200 / 630; height: auto; object-fit: cover; display: block; }
.home-guide-copy { padding: 1.15rem 1.25rem 1.35rem; }
.home-guide-copy h3 { font-size: 1.05rem; line-height: 1.35; margin: .5rem 0 0; }
.home-guide-copy p {
  color: var(--text2); font-size: .87rem; line-height: 1.6; margin-top: .5rem;
  /* Three lines, so a long description cannot make one card taller than
     its neighbours. */
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.home-guide-meta { display: block; margin-top: .85rem; font-size: .74rem; color: var(--muted); }
.home-guides-more { margin-top: 1.75rem; text-align: center; }
