/* ==========================================================================
   DOLL HAUS — components.css
   Header, tray, tiles, accordion, CTA, footer.
   PHASE 1 SCOPE: product tile, retail CTA, detail icon-list row, eyebrow,
   text link. Header / tray / nav / footer land in Phase 2.
   ========================================================================== */


/* --------------------------------------------------------------------------
   EYEBROW + TEXT LINK
   -------------------------------------------------------------------------- */

.eyebrow {
  display: block;
  font-family: var(--f-body);
  font-weight: var(--t-label-w);
  font-size: var(--t-label);
  line-height: 1;
  letter-spacing: var(--t-label-ls);
  text-transform: uppercase;
  color: var(--dh-ink-45);
}

.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 44px;          /* touch target — the type alone was 28px tall */
  font-family: var(--f-body);
  font-weight: var(--t-label-w);
  font-size: var(--t-label);
  letter-spacing: var(--t-label-ls);
  text-transform: uppercase;
  color: var(--dh-ink);
  padding-block: var(--s-1);
}
.link-arrow .icon { width: 20px; height: 20px; stroke-width: 3; }
.link-arrow svg { transition: transform var(--dur) var(--ease); }
.link-arrow:hover svg { transform: translateX(6px); }


/* --------------------------------------------------------------------------
   BUTTONS
   Square corners. No rounded corners anywhere on this site.
   -------------------------------------------------------------------------- */

/* A BUTTON NEVER WRAPS (Blake, 25 Sep 2026): "the text in the button is too
   long and wraps. they buttons should never wrap text like this." A wrapped
   label reads as a layout accident and doubles the control's height, which
   on the tray foot pushed it past the panel edge.

   nowrap is the rule; keeping labels short is the discipline that makes it
   safe. The tray foot is the tightest case on the site — a full-width button
   in a 320px panel — so any label added there wants checking at 320px. The
   ellipsis is a backstop, not a licence: a truncated button label is still a
   bug, it just fails tidily instead of breaking the layout. */
.btn {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 56px;
  padding: 0 var(--s-5);
  background: var(--dh-ink);
  color: var(--dh-invert);
  font-family: var(--f-body);
  font-weight: 500;
  font-size: 14px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  border: 1px solid var(--dh-ink);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.btn:hover { background: var(--dh-accent); border-color: var(--dh-accent); }

.btn--ghost {
  background: transparent;
  color: var(--dh-ink);
}
.btn--ghost:hover { background: var(--dh-ink); color: var(--dh-invert); }

/* Chocogom button — used on the footer subscribe only */
.btn--display {
  font-family: var(--f-display);
  font-size: var(--t-nav);
  letter-spacing: var(--t-nav-ls);
}


/* --------------------------------------------------------------------------
   WALMART MARK
   Walmart's own asset, referenced not traced. It ships in Walmart's brand
   colour, so a filter recolours it per surface:
     · on ink   → light grey   brightness(0) flattens to black, invert() lifts
     · on paper → solid ink
   Recolouring a third-party mark departs from Walmart's guidelines — see the
   note in data.js. Delete the filter to restore the original colour.
   -------------------------------------------------------------------------- */

.wmt-mark {
  display: block;
  width: auto;
  flex: 0 0 auto;
  filter: brightness(0) invert(0.82);   /* ≈ #D1D1D1 light grey */
}

/* On a light surface the mark needs to go dark instead. */
.cta-retail--secondary .wmt-mark,
.editorial__cta .cta-retail .wmt-mark,
.cta-retail:hover .wmt-mark { filter: brightness(0) invert(0.82); }
.editorial__cta .cta-retail .wmt-mark { filter: brightness(0); }
.editorial__cta .cta-retail:hover .wmt-mark { filter: brightness(0) invert(1); }


/* --------------------------------------------------------------------------
   RETAIL CTA — BUY AT WALMART
   The primary conversion element. Catalog site: this is the ONLY purchase
   path. There is no cart, no checkout, no add-to-bag anywhere.

   TYPE-ONLY TREATMENT. "WALMART" is set in Poppins, not reproduced as a
   logo. No spark, no wordmark asset, no lookalike geometry — which means
   there is no third-party mark anywhere in this build to license, approve
   or get wrong. If JAKKS later wants the official Walmart lockup here, it
   drops into .cta-retail__mark and the label logic is unchanged.
   -------------------------------------------------------------------------- */

.cta-retail {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  width: 100%;
  height: 64px;
  padding: 0 var(--s-4);
  background: var(--dh-ink);
  color: var(--dh-invert);
  border: 1px solid var(--dh-ink);
  transition: background var(--dur-label) var(--ease),
              border-color var(--dur-label) var(--ease);
}
.cta-retail:hover {
  background: var(--dh-cta-hover);
  border-color: var(--dh-cta-hover);
}

.cta-retail__label {
  font-family: var(--f-body);
  font-weight: 500;
  font-size: 14px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  line-height: 1;
}

/* The mark sits beside BUY and is sized to it — cap height roughly matches
   the label's cap height so the pair reads as one lockup rather than a logo
   dropped next to some text. */
.cta-retail__mark { height: 22px; margin-left: 2px; }

.cta-retail__ext {
  margin-left: auto;
  width: 22px;
  height: 22px;
  stroke-width: 2.5;
  flex: 0 0 auto;
}

/* Secondary retailer — stacks below at 80% height, outlined, clearly secondary */
.cta-retail--secondary {
  height: 51px;
  margin-top: var(--s-2);
  background: transparent;
  color: var(--dh-ink);
  border: 1px solid var(--dh-ink);
}
.cta-retail--secondary:hover { background: var(--dh-ink); color: var(--dh-invert); }

@media (max-width: 768px) {
  .cta-retail { height: 56px; padding: 0 var(--s-3); gap: var(--s-2); }
  .cta-retail--secondary { height: 45px; }
  .cta-retail__label { font-size: 12px; letter-spacing: 0.1em; }
  .cta-retail__mark { height: 19px; }
}


/* --------------------------------------------------------------------------
   PRODUCT TILE
   Used on every grid, rail and tray. Anatomy is identical everywhere.

   NO CARD. No border, no well, no box. The image field and the type beneath
   it are the whole tile — the layout should read as editorial, not as a grid
   of framed cards. Hover lives in the image scale and the type, not a border.

   NO add-to-cart. NO quick view. NO swatches. NO stock status.
   -------------------------------------------------------------------------- */

.tile {
  display: block;
  position: relative;
  background: transparent;
}

/* SQUARE. Product photography is 1000×1000, so anything other than 1:1
   letterboxes it. Applies everywhere the product tile is used: collection
   grids, property grids, PDP sibling rails and the nav tray. */
.tile__media {
  position: relative;
  overflow: hidden;
  aspect-ratio: 1 / 1;
  background: var(--dh-paper-deep);
}
.tile__media .ph {
  height: 100%;
  aspect-ratio: auto;
}

/* HOVER ZOOM SCALES THE PHOTOGRAPH, NEVER THE PLACEHOLDER.
   The transform used to sit on .ph, which meant that on a tile still waiting
   for photography the only thing that visibly moved was the slot LABEL —
   type sliding and resampling under a scale, which reads as a glitch rather
   than a hover. Scaling the <img> instead means a real photo still pushes in
   and an empty slot is completely static. One rule, every surface. */
.tile__media .ph--filled img { transition: transform var(--dur-tile) var(--ease); }
.tile:hover .tile__media .ph--filled img,
.tile:focus-visible .tile__media .ph--filled img { transform: scale(1.04); }

.tile__body { padding: var(--s-3) 0 0; }

.tile__name {
  font-family: var(--f-display);
  text-transform: uppercase;
  font-size: var(--t-h3);
  line-height: var(--t-h3-lh);
  letter-spacing: var(--t-h3-ls);
  color: var(--dh-ink);
  transition: opacity var(--dur) var(--ease);
}
.tile:hover .tile__name { opacity: 0.58; }

.tile__price {
  margin-top: var(--s-2);
  font-family: var(--f-body);
  font-weight: var(--t-meta-w);
  font-size: var(--t-meta);
  letter-spacing: var(--t-meta-ls);
  color: var(--dh-ink-45);
}

/* --------------------------------------------------------------------------
   PROPERTY TILE
   Homepage carousel and the Option B mega rail. On hover the property name
   crossfades in over a subtle ink scrim while the image scales 1.04.
   -------------------------------------------------------------------------- */

/* Property tiles are LIFESTYLE imagery, not product shots — they keep a
   PORTRAIT editorial crop at 4:5.
   History: this ran at 4 / 4.25 (near-square, 0.941) while the placeholder
   label said 4:5. JAKKS' brand team spotted the mismatch on 17 Sep 2026 —
   the LABEL was right and the LAYOUT was wrong. Now a true 4:5.
   If this ratio ever changes, change the '4:5' argument in chrome.js
   renderPropTile TOO, or the slot goes back to telling the client a ratio
   the page does not use. */
.prop-tile .tile__media { position: relative; aspect-ratio: 4 / 5; }
/* Darker inactive field so the slot reads against the paper. */
.prop-tile .ph { background-color: #DCDCDC; }

/* THE NAME IS ALWAYS VISIBLE (Blake, 29 Sep 2026): "the names of the
   collections don't come up until you hover over the images. Change so they
   should always be visible."

   It used to be opacity: 0 with a flat --dh-scrim wash brought in on hover,
   which meant the four properties were unlabelled until someone moved a
   mouse over them - and on a phone there is no hover at all, so the names
   were simply never shown.

   At rest the veil is now a BOTTOM GRADIENT rather than the flat wash: it
   carries the name at full contrast while leaving the top 42% of the
   photograph untouched, which a wash over the whole tile would have dulled
   on every tile at once. Hover still does something - it brings the flat
   scrim up over the whole tile - so the affordance that told you these are
   clickable is kept rather than traded away.

   The transition moves from opacity to background because the element no
   longer fades; it changes material. */
.prop-tile__veil {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  padding: var(--s-4);
  background: linear-gradient(to top,
    rgba(18, 18, 18, 0.80)  0%,
    rgba(18, 18, 18, 0.42) 26%,
    rgba(18, 18, 18, 0)    58%);
  opacity: 1;
  transition: background var(--dur-label) var(--ease);
}
.prop-tile:hover .prop-tile__veil,
.prop-tile:focus-visible .prop-tile__veil { background: var(--dh-scrim); }

.prop-tile__name {
  font-family: var(--f-display);
  text-transform: uppercase;
  font-size: var(--t-h3);
  line-height: var(--t-h3-lh);
  letter-spacing: var(--t-h3-ls);
  color: var(--dh-invert);
}


/* --------------------------------------------------------------------------
   CAROUSEL CONTROLS — fat chevron arrows + hairline progress rail
   -------------------------------------------------------------------------- */

.carousel__track {
  display: grid;
  grid-auto-flow: column;
  gap: var(--s-4);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* TOUCH. Momentum on older iOS, and contain stops a swipe past the first
     tile from triggering Safari's back-gesture instead of scrolling the rail. */
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}
.carousel__track::-webkit-scrollbar { display: none; }
.carousel__track > * { scroll-snap-align: start; }

.carousel__controls {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  margin-top: var(--s-4);
}

.carousel__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 66px;            /* 44 → 66, +50% */
  height: 66px;
  border: 1px solid var(--dh-ink-45);   /* darker than the standard hairline */
  color: var(--dh-ink);
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
              opacity var(--dur) var(--ease);
}
.carousel__btn .icon { width: 48px; height: 48px; }   /* 32 → 48, +50% */
.carousel__btn:hover:not(:disabled) { background: var(--dh-ink); color: var(--dh-invert); border-color: var(--dh-ink); }
.carousel__btn:disabled { opacity: 0.28; cursor: default; }

.carousel__progress {
  position: relative;
  flex: 1;
  height: 1px;
  background: var(--dh-hairline);
  overflow: hidden;
}
/* PHONES DRIVE THE RAIL WITH A THUMB.
   The arrows and the progress rule are a pointer affordance — they tell a
   mouse user there is more to the right. A touch device already knows: the
   rail is a native horizontal scroller with momentum and the tiles bleed off
   the edge. Keeping them costs a row of chrome under every rail and two
   controls nobody taps. */
@media (max-width: 768px) {
  .carousel__controls { display: none; }
  .carousel { padding-bottom: 0; }
}

.carousel__progress i {
  position: absolute;
  inset: 0 auto 0 0;
  display: block;
  height: 100%;
  background: var(--dh-ink);
  transition: transform var(--dur) var(--ease), width var(--dur) var(--ease);
}


/* LIMITED EDITION mark — the accent colour's primary job. */
.tile__le,
.le-mark {
  display: inline-block;
  font-family: var(--f-body);
  font-weight: 500;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--dh-accent);
}
.tile__le { margin-bottom: var(--s-2); }


/* --------------------------------------------------------------------------
   PRODUCT DETAILS — PLAIN "TITLE: TEXT" ROWS
   Client feedback 24 Aug 2026: "Remove all icons in product details section.
   Keep it simple. Don't need the lines in between product details either.
   Can it just be Bomber Jacket: then the rest of the type." So: no icon
   gutter, no hairlines, one run of text per row with a bold inline title.

   NOTE: this deliberately supersedes the earlier "title and body must be
   blocks" fix — they now share a line BY DESIGN, and the colon+space the
   renderer emits after the title keeps them from running together the way
   "BodysuitGreen bodysuit…" once did. Bracket tags STILL never render.
   -------------------------------------------------------------------------- */

.detail-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.detail-row {
  padding-block: 10px;
  font-family: var(--f-body);
  font-size: var(--t-body);
  line-height: var(--t-body-lh);
}

.detail-row__title {
  display: inline;
  font-weight: 600;
  color: var(--dh-ink);
}

.detail-row__text {
  display: inline;
  font-weight: var(--t-body-w);
  color: var(--dh-ink-70);
}

/* Rows with no title (e.g. "11” Fashion Doll") are all .text and read as a
   lead-in line, so they keep full ink. */
.detail-row--untitled .detail-row__text { color: var(--dh-ink); }


/* ==========================================================================
   PHASE 2 — GLOBAL CHROME
   Announcement bar · header · tray + sub-trays · desktop nav A/B/C ·
   floating tab · footer.
   ========================================================================== */


/* --------------------------------------------------------------------------
   ANNOUNCEMENT BAR
   -------------------------------------------------------------------------- */

.anno {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--anno-h);
  padding-inline: var(--s-4);
  background: var(--dh-ink);
  color: var(--dh-invert);
  font-family: var(--f-body);
  font-weight: 500;
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-align: center;
  /* ONE LINE, ALWAYS. --anno-h is a fixed 34px that the header position
     depends on, so a second line does not make the bar taller — it spills
     out of it and shunts the header. The type shrinks instead. */
  white-space: nowrap;
}

/* At 440px the full sentence needs ~9px to fit inside the gutters. Sized off
   the viewport rather than a fixed step so it stays on one line at every
   width between here and the desktop 12px. */
@media (max-width: 900px) {
  .anno {
    padding-inline: var(--s-3);
    font-size: clamp(7px, 2.05vw, 12px);
    letter-spacing: 0.08em;
  }
}


/* --------------------------------------------------------------------------
   HEADER
   Sticky, translucent. The bottom hairline only appears once scrolled past
   40px. On the homepage it starts as a transparent overlay on the hero with
   inverted content, then swaps to the solid paper treatment on scroll.
   -------------------------------------------------------------------------- */

.header {
  /* AT REST: out of flow, transparent, sitting directly under the promo bar
     so it overlays the hero without pushing it down.
     ONCE STUCK: fixed to the top, black, condensed, hides on scroll-down. */
  position: absolute;
  top: var(--anno-h);
  left: 0;
  right: 0;
  z-index: var(--z-header);
  height: var(--header-h);
  background: transparent;
  color: var(--dh-ink);
  transition: height var(--dur) var(--ease);
  border-bottom: 1px solid transparent;
  transition: background var(--dur) var(--ease),
              color var(--dur) var(--ease),
              transform var(--dur) var(--ease);
}

/* Over the dark hero the resting header needs light type. */
body[data-header='overlay'] .header { color: var(--dh-invert); }

/* GLASS (Blake, 16 Sep 2026). The stuck bar was a solid --dh-ink slab; it is
   now a translucent pane with the page moving underneath it.

   0.72, not the 0.4-0.5 that reads as "more glass": this bar carries WHITE
   type and sits over light pages as well as the dark hero. At 0.72 over white
   the effective fill is about #545454, which holds white type at roughly
   7.4:1 — comfortably past AA. Thinner than that and the blur starts doing
   the legibility work, which it cannot be relied on to do.

   saturate() is what stops a blurred photograph from going grey behind the
   pane and keeps the hero's colour reading through it. */
.header.is-stuck {
  position: fixed;
  top: 0;
  height: var(--header-h-stuck);
  background: rgba(18, 18, 18, 0.72);
  -webkit-backdrop-filter: blur(18px) saturate(150%);
  backdrop-filter: blur(18px) saturate(150%);
  color: var(--dh-invert);
  border-bottom-color: rgba(255, 255, 255, 0.14);
}

/* No backdrop-filter, no glass — and a 0.72 wash over live content with no
   blur is worse than a solid bar, not more stylish. Those browsers get what
   they had before. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .header.is-stuck { background: var(--dh-ink); }
}
/* Condensed once stuck — the 35px of air is a resting-state gesture. */
.header.is-stuck .header__inner { padding-block: 0; }

/* STUCK SCALE
   The bar itself stays at --header-h-stuck (56px); only its CONTENTS come
   down. Shrinking the bar as well would make the whole thing jump twice.
   Both values are expressed against the same --t-nav base as their resting
   states, so the resting/stuck relationship survives a type-scale change. */

.header.is-stuck .shop-trigger {
  font-size: calc(var(--t-nav) * 1.3);
  -webkit-text-stroke-width: 0.75px;   /* +1 weight step, was 0.45px */
}

/* The bar is only as tall as the bar — the panels hang below it and must not
   be clipped. */
.header { overflow: visible; }

/* While a panel is open the header itself gets the panel's background so the
   bar and the panel read as one surface. */
.header.has-panel { background: var(--dh-paper); color: var(--dh-ink); }


.header__inner {
  display: grid;
  /* minmax(0, 1fr), NOT 1fr (Blake, 15 Sep 2026: "the logo should always be
     centered ... the ABOUT menu item is pushing the logo to the left").

     `1fr` is shorthand for `minmax(auto, 1fr)`, and `auto` as a minimum means
     min-content. The right-hand group is wider than the left — SHOP alone on
     one side, ABOUT + socials + SUBMIT on the other — so column 3 grew past
     its equal share to fit, stole the difference from column 1, and carried
     the centre column left with it. Measured at 1440: 472px of air on the
     left, 190px on the right. The lockup was never centred; it only looked
     centred when the two sides happened to balance.

     minmax(0, 1fr) lets both side columns take exactly half of what is left
     after the lockup, so the lockup sits on the true midpoint at every width.
     The side groups keep justify-self start/end, so they hug the gutters. */
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  height: 100%;
  /* NO VERTICAL PADDING (Blake, 15 Sep 2026). It used to carry
     padding-block: var(--header-pad), which is what put every group 13px
     BELOW the bar's own centre — measured, not guessed. .header already has
     an explicit height, so `align-items: center` against the full box gives
     exactly the equal air the padding was trying to produce, and SHOP, the
     logo, About and the socials all land on one true centre line.
     --header-pad still sets the bar's height through --header-h. */
  padding-block: 0;
  transition: padding var(--dur) var(--ease);
}

/* Columns are assigned EXPLICITLY. The middle is taken out of flow on
   mobile so the wordmark can centre on the viewport rather than on whatever
   the two side groups leave over — and without these, the right-hand group
   simply slid into the vacated middle track. */
.header__left  { grid-column: 1; justify-self: start; }
/* display:flex, not the default block. As a block its height came from a
   LINE BOX, and the inline-flex anchor inside sat on that line's baseline —
   which left a descender's worth of space underneath and floated the logo 5px
   above the centre line SHOP and the socials sit on. Measured, then fixed. */
.header__mid   { grid-column: 2; justify-self: center; display: flex; align-items: center; }
.header__right { grid-column: 3; justify-self: end; display: flex; align-items: center; gap: var(--s-4); }

/* SHOP is the menu trigger. The word IS the icon — no hamburger, no bars. */
/* SHOP is the whole navigation, so it carries more weight than a nav item.

   GENUINELY BOLDER, WITHOUT SYNTHETIC BOLD.
   Chocogom ships Regular only. `font-weight: bold` makes the browser fake it
   by smearing each glyph horizontally — the counters fill in and it looks
   broken, which is why the spec forbids it. `-webkit-text-stroke` instead
   grows the stroke EVENLY outward from the existing outline, so the letter
   shapes stay true and just gain mass. `paint-order` keeps the stroke behind
   the fill so edges stay crisp.

   1.8× the nav size (a further 20% on the previous 1.5×) and tracking opened
   to 0.14em so the extra weight has room to breathe. */
.shop-trigger {
  font-family: var(--f-display);
  text-transform: uppercase;
  /* 1.8 -> 1.45 (Blake, 21 Sep 2026). Desktop only: the 768px rule below
     already sets its own value, and .is-stuck sets another, so neither is
     touched by this. The keyhole is sized in em off this, so it steps down
     with the word and keeps the pairing. */
  font-size: calc(var(--t-nav) * 1.45);
  line-height: 1;
  letter-spacing: 0.14em;
  color: inherit;
  padding: var(--s-2) 0;

  /* ONE WEIGHT STEP HEAVIER (Blake, 15 Sep 2026) — was 0.6px.
     Chocogom ships a single Regular and HANDOFF forbids `font-weight` on it:
     the browser fakes the weight and the counters smear, and qa.js fails the
     build for it outright. Weight here is stroke width, so "+100" is a
     heavier stroke. All three SHOP strokes moved by the same factor (x1.67)
     rather than the same number of pixels, so the rest / stuck / mobile
     states keep their relationship at their different sizes. */
  -webkit-text-stroke: 1px currentColor;
  paint-order: stroke fill;

  transition: font-size var(--dur) var(--ease);
}

/* Mobile: the jump to 1.8× would eat the bar, so it lands at 1.35× — bigger
   and heavier than it was, still proportionate to a 440px viewport. The
   stroke thins to match, since 0.6px on smaller type reads as mud. */
/* PHONE NAV — everything comes down together.
   SHOP and the bento shrink as one because the mark is sized in em off the
   trigger, the social icons drop to 20px with tighter spacing, and the
   wordmark loses 10%. The point is that the three groups stay in proportion
   at 440px instead of the bar being all SHOP. */
@media (max-width: 768px) {
  .shop-trigger {
    font-size: calc(var(--t-nav) * 1.1);
    letter-spacing: 0.1em;
    gap: 0.34em;
    -webkit-text-stroke-width: 0.58px;   /* +1 weight step, was 0.35px */
  }
}

/* Once the tray is open it carries its own heading and its own close X, so
   the header's SHOP would be a second, conflicting label sitting right next
   to it. visibility (not display) keeps the header row's geometry — the
   wordmark stays centred — while taking the trigger out of the tab order,
   which matters because the tray runs a focus trap. */
.shop-trigger[aria-expanded='true'] {
  visibility: hidden;
  pointer-events: none;
}

.shop-trigger__label { display: block; }


/* --------------------------------------------------------------------------
   BENTO MARK — the four-dot grid to the left of SHOP

   SIZED IN em, NOT px. Every dimension is a fraction of the trigger's own
   font-size, so the mark tracks SHOP through the mobile step-down AND the
   sticky-header shrink without a single extra media query or is-stuck rule.

   SQUARE DOTS. The supplied CSS rounded them to 2px and morphed them to
   circles on open. This site has one hard rule — no rounded corners, checked
   by tools/qa.js — so the dots stay square and the open state is carried by
   the rotation and the inward pull instead. To restore the original
   behaviour, add `border-radius: 2px` here and `50%` in the expanded block,
   and relax the check in qa.js.
   -------------------------------------------------------------------------- */

.shop-trigger {
  /* The trigger is the flex row: mark, then word. */
  display: inline-flex;
  align-items: center;
  gap: 0.42em;
}

.bento {
  --bento-size:     0.74em;   /* ≈ the cap height of SHOP beside it */
  --bento-dot:      0.28em;
  --bento-converge: 0.09em;
  --bento-dur:      340ms;

  position: relative;
  display: block;
  flex: 0 0 auto;
  width: var(--bento-size);
  height: var(--bento-size);
  transition: transform var(--bento-dur) var(--ease);
}

.bento > span {
  position: absolute;
  width: var(--bento-dot);
  height: var(--bento-dot);
  background: currentColor;
  transition: transform var(--bento-dur) var(--ease);
}

/* The four corners, each a beat behind the last. */
.bento > span:nth-child(1) { top: 0; left: 0; transition-delay: 0ms; }
.bento > span:nth-child(2) { top: 0; left: calc(var(--bento-size) - var(--bento-dot)); transition-delay: 40ms; }
.bento > span:nth-child(3) { top: calc(var(--bento-size) - var(--bento-dot)); left: 0; transition-delay: 80ms; }
.bento > span:nth-child(4) { top: calc(var(--bento-size) - var(--bento-dot));
                             left: calc(var(--bento-size) - var(--bento-dot)); transition-delay: 120ms; }

.shop-trigger:hover .bento { transform: rotate(45deg); }

/* OPEN STATE — rotated, with all four dots pulled toward the centre.
   Driven off the trigger's own aria-expanded, so the icon can never disagree
   with the control's announced state. */
.shop-trigger[aria-expanded='true'] .bento { transform: rotate(45deg); }
.shop-trigger[aria-expanded='true'] .bento > span:nth-child(1) {
  transform: translate(var(--bento-converge), var(--bento-converge));
}
.shop-trigger[aria-expanded='true'] .bento > span:nth-child(2) {
  transform: translate(calc(var(--bento-converge) * -1), var(--bento-converge));
}
.shop-trigger[aria-expanded='true'] .bento > span:nth-child(3) {
  transform: translate(var(--bento-converge), calc(var(--bento-converge) * -1));
}
.shop-trigger[aria-expanded='true'] .bento > span:nth-child(4) {
  transform: translate(calc(var(--bento-converge) * -1), calc(var(--bento-converge) * -1));
}

@media (prefers-reduced-motion: reduce) {
  .bento, .bento > span { transition-duration: 1ms; transition-delay: 0ms; }
}

/* Full-size target. The mark's own box is smaller than the floor in BOTH
   directions once it became the real lockup — the audit caught it at 41x44 on
   mobile, where the 1.49:1 lockup is narrower than the 44px minimum. */
.header__logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* The floor keeps the tap target legal; line-height: 0 stops the anchor's
     inline box adding a descender's worth of space under the mark, which was
     the last 4px between the logo's centre and everything else's. */
  line-height: 0;
  min-height: 44px;
  min-width: 44px;
  color: inherit;
}
/* DESKTOP: +25% on the nav size. Mobile keeps the smaller figure below —
   at 440px the bigger mark crowds SHOP and the social row. */
.header__logo .wordmark {
  font-size: calc(var(--t-nav) * 1.25);
  border-color: currentColor;
  opacity: 0.9;
  transition: font-size var(--dur) var(--ease),
              padding var(--dur) var(--ease),
              letter-spacing var(--dur) var(--ease);
}

.social-row { display: flex; align-items: center; gap: var(--s-2); }
/* The icon is 24px; the TARGET is 44px. Padding does the work and a negative
   margin on the row gives the space back, so the icons look exactly as
   before and the tap area is three times bigger. */
.social-row a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  color: inherit;
  transition: opacity var(--dur) var(--ease);
}
.social-row a:hover { opacity: 0.55; }
.header__right .social-row { margin-right: calc(var(--s-2) * -1); }

/* SHOP is short enough to fall under the 44px floor on its own. */
.shop-trigger { min-height: 44px; }


/* --- Homepage overlay state ---------------------------------------------
   At the very top of the hero the header is transparent and sits over the
   video. The moment you scroll it becomes the solid dark bar used sitewide.
   ------------------------------------------------------------------------ */


@media (max-width: 768px) {
  .header__right { gap: 0; }
  /* 10% under the base nav size — the +25% desktop mark is far too wide for
     a 440px bar. Symmetric padding keeps the words optically centred in
     their rule, which the wider desktop tracking was masking. */
  /* Sized so the centred mark still clears the two side groups at 375px,
     the narrowest phone worth designing for. Asymmetric padding cancels the
     trailing letter-spacing on the last glyph, which is what stops the words
     sitting visibly left-of-centre inside their own rule. */
  .header__logo .wordmark {
    font-size: calc(var(--t-nav) * 0.78);
    letter-spacing: 0.18em;
    padding: 8px 10px 8px calc(10px + 0.18em);
  }
  /* Icons small and tight. 44px wide × 3 was a third of a 440px bar and read
     as three icons adrift in white space. 32×44 keeps a FULL-HEIGHT target
     around an 18px glyph — narrower than Apple's 44² ideal, but the height
     is the axis a thumb actually misses on in a header strip, and the three
     targets are contiguous with no dead space between them. */
  .header__right .social-row { gap: 0; }
  .header__right .social-row .icon { width: 20px; height: 20px; }
  .header__right .social-row .icon { width: 18px; height: 18px; }
  .header__right .social-row a { min-width: 32px; min-height: 44px; }

  /* THE WORDMARK IS CENTRED ON THE VIEWPORT, not on what is left over.
     `1fr auto 1fr` only centres the middle track while BOTH side tracks fit
     inside their 1fr. At 440px the social row is wider than its share, so it
     pushed the mark off-centre by exactly the overflow. Taking the mark out
     of flow makes it independent of whatever the two side groups weigh. */
  .header__inner { position: relative; }
  .header__mid {
    position: absolute;
    /* SPANS THE WHOLE ROW. An absolutely-positioned grid child is positioned
       against its GRID AREA, not the grid container — left:50% of the `auto`
       middle track is 50% of the wordmark itself, which left the mark parked
       wherever the track happened to start. Spanning 1/-1 makes the area the
       full row, so 50% is genuinely the middle of the bar. */
    grid-column: 1 / -1;
    left: 50%;
    transform: translateX(-50%);
    justify-self: start;
  }
  .social-row { gap: var(--s-3); }
}


/* --------------------------------------------------------------------------
   SCRIM — the one sanctioned drop shadow surface
   -------------------------------------------------------------------------- */

.scrim {
  position: fixed;
  inset: 0;
  z-index: var(--z-scrim);
  background: var(--dh-scrim);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-scrim) var(--ease), visibility 0s linear var(--dur-scrim);
}
.scrim.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--dur-scrim) var(--ease), visibility 0s;
}


/* --------------------------------------------------------------------------
   TRAY — mobile navigation, and desktop nav Option A
   Full-height panel sliding from the left. One level visible at a time.
   No accordions inside. Back returns one level, never straight to closed.
   -------------------------------------------------------------------------- */

.tray {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  z-index: var(--z-tray);
  width: min(88vw, 420px);
  /* Fully opaque — nothing behind the tray should read through it. */
  background: var(--dh-paper);
  opacity: 1;
  box-shadow: 0 0 60px rgba(18, 18, 18, 0.18);
  transform: translateX(-100%);
  visibility: hidden;
  transition: transform var(--dur-tray) var(--ease),
              visibility 0s linear var(--dur-tray);
  overflow: hidden;
}
.tray.is-open {
  transform: none;
  visibility: visible;
  transition: transform var(--dur-tray) var(--ease), visibility 0s;
}

/* Option A widens the identical tray on desktop. */
@media (min-width: 1025px) {
  body[data-nav='A'] .tray { width: 480px; }
}

.tray__head {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--header-h);
  padding-inline: var(--s-4);
  border-bottom: var(--hair);
  background: var(--dh-paper);
}
.tray__brand .wordmark {
  font-size: var(--t-nav);
  letter-spacing: 0.22em;
}
/* The X needs a real target and its own layer. The tray head is a sibling of
   the panel viewport, and a panel that grows can otherwise paint over the
   corner the X sits in — the button was there and simply could not be hit. */
.tray__close {
  position: relative;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44px minimum touch target — the icon alone was a ~24px hit area. */
  min-width: 44px;
  min-height: 44px;
  margin-right: calc(var(--s-2) * -1);
  color: var(--dh-ink);
}

/* Panel stack — one visible at a time, siblings slide out of view. */
.tray__viewport {
  position: relative;
  height: calc(100% - var(--header-h));
  overflow: hidden;
}

.tray-panel {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  background: var(--dh-paper);
  opacity: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  transform: translateX(100%);
  visibility: hidden;
  transition: transform var(--dur-tray) var(--ease),
              visibility 0s linear var(--dur-tray);
}
.tray-panel[data-level='1'] { transform: translateX(-100%); }
.tray-panel.is-active {
  transform: none;
  visibility: visible;
  transition: transform var(--dur-tray) var(--ease), visibility 0s;
}
.tray-panel.is-behind { transform: translateX(-28%); visibility: visible; }

.tray-panel__head { padding: var(--s-5) var(--s-4) var(--s-4); }

/* Heading left, the Shop All shortcut right, sharing one baseline. */
.tray-panel__head--split {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
}

.tray-back {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-family: var(--f-display);
  text-transform: uppercase;
  font-size: var(--t-nav);
  letter-spacing: var(--t-nav-ls);
  color: var(--dh-ink-45);
  margin-bottom: var(--s-4);
  transition: color var(--dur) var(--ease);
}
.tray-back:hover { color: var(--dh-ink); }
.tray-back .icon { width: 20px; height: 20px; stroke-width: 3; }

.tray-panel__title {
  font-family: var(--f-display);
  text-transform: uppercase;
  font-size: var(--t-h3);
  line-height: var(--t-h3-lh);
  letter-spacing: var(--t-h3-ls);
}

/* Level-1 / property list rows */
.tray-list { list-style: none; margin: 0; padding: 0; border-top: var(--hair); }
.tray-list li { border-bottom: var(--hair); }

.tray-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  width: 100%;
  padding: var(--s-4);
  text-align: left;
  font-family: var(--f-display);
  text-transform: uppercase;
  font-size: var(--t-nav);
  line-height: 1.25;
  letter-spacing: var(--t-nav-ls);
  transition: background var(--dur) var(--ease);
}
.tray-row:hover { background: var(--dh-paper-deep); }

/* SHOP ALL — a shortcut across the range, not a third collection and not a
   CTA. It carries the SAME ink as the list rows; size alone sets it apart. */
.tray-all {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  flex: 0 0 auto;
  font-family: var(--f-display);
  text-transform: uppercase;
  font-size: calc(var(--t-nav) * 0.72);
  line-height: 1;
  letter-spacing: var(--t-nav-ls);
  color: inherit;                       /* same ink as .tray-row */
  transition: opacity var(--dur) var(--ease);
}
.tray-all:hover { opacity: 0.55; }
.tray-all .icon { width: 14px; height: 14px; stroke-width: 3; }
.tray-row .icon { width: 20px; height: 20px; stroke-width: 3; flex: 0 0 auto; }

/* Level-2/3 product tiles — two per row, oversized and confident.
   The tiles are the point. */
/* minmax(0, 1fr), never plain 1fr.
   A bare `1fr` track is `minmax(auto, 1fr)`, so its floor is the content's
   MIN-CONTENT width. Placeholder tiles carry a `white-space: nowrap` slot
   label, which makes their min-content width the full label — wider than the
   column. The track then refuses to shrink and the grid overflows sideways.
   Tiles with a real photo have no text, which is why the DC panel looked
   fine and every placeholder panel did not. */
.tray-tiles {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 20px;      /* 20px gutters, 32px vertical rhythm — the tiles */
  row-gap: 32px;         /* are the point, so they get room to breathe */
  padding: var(--s-4);
}
.tray-tiles .tile__name { font-size: 15px; letter-spacing: 0.07em; }

/* NO FRAME AROUND THE PHOTO.
   A filled slot paints the white sweep behind its image. On the page that is
   invisible — the product shots are already on white — but in the tray it
   sits on paper grey, so the sweep reads as a white border ruled around
   every doll. Dropping the fill in the tray only lets the photograph sit
   directly on the tray surface. Placeholder tiles keep their field: without
   it they would be nothing at all. */
.tray-tiles .tile__media { background: transparent; }
.tray-tiles .ph--filled  { background-color: transparent; }

/* Slot labels elsewhere shrink-to-fit on one line, but a tray column is only
   ~180px wide — at that size a one-line label is clipped to nothing. Here
   they wrap instead, and stop forcing the track wider than its share. */
.tray-tiles .ph { padding: var(--s-2); }
.tray-tiles .ph__label { white-space: normal; font-size: 9px; letter-spacing: 0.1em; }
.tray-tiles .ph__meta  { font-size: 8px; }

/* The panel's CTA stays pinned to the bottom of the tray, so VIEW COLLECTION
   is reachable without scrolling past every tile first. */
.tray-foot {
  position: sticky;
  bottom: 0;
  margin-top: auto;
  padding: var(--s-4);
  background: var(--dh-paper);
  border-top: var(--hair);
  z-index: 1;
}
/* THE TIGHTEST BUTTON ON THE SITE (25 Sep 2026).

   Full width inside a ~234px panel at 320px, and it carries the longest
   labels we have: "View Warner Bros. Doll Collection" is 33 characters, and
   the client direction of 26 Aug 2026 — "All places should say Warner Bros.
   Doll Collection wherever applicable" — means it cannot be shortened.

   Measured at 320px with the desktop geometry: the button is 234px wide and
   only 152px of that is usable, because `padding: 0 var(--s-5)` puts 41px
   down each side. A full-width button does not need desktop side padding —
   there is nothing beside it to be separated from. The label needed 240px
   and had 152px, so nowrap was truncating it.

   Three things give it back, all scoped to this button so nothing else on
   the site moves:
     · side padding down to --s-3, which returns ~50px
     · tracking from 0.12em to 0.04em, worth ~8% of the line
     · a fluid size that only steps down where it has to; 14px from ~430px up

   If a longer label is ever added here, re-measure at 320px rather than
   trusting this comment — and treat a clipped label as a bug, not a
   tolerable outcome. */
/* THE PANEL'S OWN PADDING, ON NARROW PHONES.
   At 320px the foot's 24px inline padding costs the button 48px of the 320
   it has. Shrinking the label further was the wrong lever - it was already
   at the 10px floor and still 7px short, because the earlier "needs 240px"
   figure was read off an ALREADY-CLIPPED element, where scrollWidth stops
   at the content box and under-reports. Giving the button the width back
   costs nothing: 16px still separates it from the panel edge. */
@media (max-width: 480px) {
  .tray-foot { padding-inline: var(--s-3); }
}

.tray-foot .btn {
  width: 100%;
  padding-inline: var(--s-3);
  letter-spacing: 0.04em;
  /* 3.6vw/11.5px was still 15-20px short of the longest label below 428px,
     measured on the deployed page. Solved against the real numbers instead
     of estimated again: at each failing width, required size = current size
     x usable / needed, which gave 10.45px at 320, 12.17px at 360 and
     13.24px at 390 -- a vw coefficient of 3.2 or lower at every one. 3.1
     takes it with ~3% to spare and reaches the 14px cap at about 452px. */
  font-size: clamp(10px, 3.1vw, 14px);
}


/* --------------------------------------------------------------------------
   DESKTOP NAV — Options B and C
   Both drop from beneath the header, full width, and open on CLICK (never
   hover — hover menus are fragile in a demo).
   -------------------------------------------------------------------------- */

/* Anchored to the header by DOM, not by measurement. `top: 100%` puts the
   panel immediately below the bar whether the header is absolute (at rest)
   or fixed (stuck), and being a header child it can never be painted under
   the scrim. */
.megapanel {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: var(--z-mega);
  background: var(--dh-paper);
  border-bottom: var(--hair);
  box-shadow: 0 24px 60px rgba(18, 18, 18, 0.10);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-12px);
  transition: opacity var(--dur) var(--ease),
              transform var(--dur) var(--ease),
              visibility 0s linear var(--dur);
  max-height: calc(100svh - var(--header-h) - var(--anno-h));
  overflow-y: auto;
}
.megapanel.is-open {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease), visibility 0s;
}

.megapanel__inner { padding-block: var(--s-6); }

/* Row one — collection headings */
.mega-collections {
  display: flex;
  gap: var(--s-7);
  padding-bottom: var(--s-5);
  border-bottom: var(--hair);
  margin-bottom: var(--s-5);
}
.mega-collections a {
  font-family: var(--f-display);
  text-transform: uppercase;
  font-size: var(--t-h2);
  line-height: var(--t-h2-lh);
  letter-spacing: var(--t-h2-ls);
  transition: opacity var(--dur) var(--ease);
}
.mega-collections a:hover { opacity: 0.5; }

/* Row two — all seven properties as tiles in one scrolling row */
.mega-rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(206px, 1fr);
  gap: var(--s-4);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: var(--s-3);
  scrollbar-width: thin;
}
.mega-rail > * { scroll-snap-align: start; }

/* --- Option C: split column ---------------------------------------------- */
.megapanel--split .megapanel__inner {
  display: grid;
  grid-template-columns: minmax(240px, 1fr) minmax(0, 2fr);
  gap: var(--s-7);
  align-items: start;
}

.mega-nav { list-style: none; margin: 0; padding: 0; }
.mega-nav__group + .mega-nav__group { margin-top: var(--s-5); }
.mega-nav__collection {
  display: inline-block;
  font-family: var(--f-display);
  text-transform: uppercase;
  font-size: var(--t-h3);
  letter-spacing: var(--t-h3-ls);
  padding-block: var(--s-2);
}
.mega-nav__props { list-style: none; margin: var(--s-2) 0 0; padding: 0; }
.mega-nav__props button {
  display: block;
  width: 100%;
  text-align: left;
  padding: 7px 0;
  font-family: var(--f-body);
  font-weight: 400;
  font-size: 15px;
  color: var(--dh-ink-45);
  transition: color var(--dur) var(--ease);
}
.mega-nav__props button:hover,
.mega-nav__props button.is-active { color: var(--dh-ink); }

.mega-stage { position: relative; min-height: 320px; }
.mega-stage__set {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s-4);
  position: absolute;
  inset: 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-label) var(--ease), visibility 0s linear var(--dur-label);
}
.mega-stage__set.is-active {
  opacity: 1;
  visibility: visible;
  position: relative;
  transition: opacity var(--dur-label) var(--ease), visibility 0s;
}

/* Trays and mega panels never coexist. */
@media (max-width: 1024px) { .megapanel { display: none; } }
@media (min-width: 1025px) {
  body[data-nav='B'] .tray,
  body[data-nav='C'] .tray { display: none; }
}


/* --------------------------------------------------------------------------
   FLOATING TAB — DOLL HAUS @ WALMART
   Fixed to the right edge, vertically centred.
   NO close icon. Not dismissible. Present on every page, every breakpoint.

   STRUCTURE: an invisible full-height rail pins the position; the tab itself
   is an ordinary horizontal flex lockup rotated 90° as a single unit. Because
   the type and the mark sit in normal horizontal flow, align-items centres
   them against each other and they cannot drift.

   After rotation the anchor's HEIGHT becomes the tab's visual width, and its
   horizontal padding becomes the visual top/bottom padding — which is why a
   single symmetric padding value guarantees equal air at both ends.
   -------------------------------------------------------------------------- */

.shop-tab-rail {
  position: fixed;
  top: 0;
  bottom: 0;
  right: 0;
  z-index: var(--z-tab);
  width: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* The rotated anchor overflows this narrow box in layout terms; hit
     testing uses the transformed geometry, so only the visible tab is
     clickable. */
  pointer-events: none;
}

.shop-tab {
  pointer-events: auto;
  display: flex;
  align-items: center;
  /* MUST NOT SHRINK. The rail is a 48px-wide flex container, so a default
     flex item collapses to 48px and the content — the Walmart mark, last in
     the lockup — spills outside the tab's background. Rotated, that reads as
     the logo floating off the end of the tab. flex:0 0 auto lets the anchor
     take its natural content width so the background wraps the whole lockup. */
  flex: 0 0 auto;
  height: 48px;                 /* becomes the visual WIDTH once rotated */
  padding-inline: 32px;         /* becomes equal visual TOP and BOTTOM */
  background: var(--dh-accent);
  /* White type, not ink. The spec calls for ink on the accent field, but ink
     on lacquer red is a contrast failure and ink on the current grayscale
     accent would be invisible. White holds up on both. */
  color: var(--dh-invert);
  white-space: nowrap;
  transform: rotate(90deg);
  transform-origin: center;
  transition: height var(--dur) var(--ease), background var(--dur) var(--ease);
}
.shop-tab:hover { height: 56px; background: var(--dh-accent-hot); }

.shop-tab__text {
  font-family: var(--f-display);
  text-transform: uppercase;
  font-size: var(--t-nav);
  line-height: 1;
  letter-spacing: 0.18em;
}

/* Sized to the type and centred on it by the parent's align-items. */
.shop-tab__mark {
  display: inline-block;
  height: 1em;
  width: auto;
  vertical-align: middle;
  /* The "T" of AT carries 0.18em of trailing letter-spacing that the mark
     does not. Cancelling exactly that much leaves the font's own word space
     (~0.25em); adding another 0.25em on top makes the gap before the mark
     twice a word space, so the logo reads as its own element rather than as
     the next word. Keep the 0.18em tied to .shop-tab__text's letter-spacing.
       -0.18em (cancel tracking) + 0.50em (two word spaces) = 0.32em */
  margin-left: 0.32em;
  filter: brightness(0) invert(0.82);
}

/* Mobile: narrower, and dropped clear of the thumb zone. */
@media (max-width: 768px) {
  /* Up into the first third of the window. It used to sit at 30% from the
     bottom, which put it over the product photograph on a PDP and level with
     the fold on the homepage. Anchored to the TOP so it lands in the same
     place whatever the page height. */
  .shop-tab-rail {
    width: 36px;
    top: 33%;
    bottom: auto;
    height: auto;
  }
  .shop-tab {
    height: 36px;
    padding-inline: 24px;
  }
  .shop-tab:hover { height: 42px; }
  .shop-tab__text { font-size: 13px; letter-spacing: 0.14em; }
}


/* --------------------------------------------------------------------------
   FOOTER
   -------------------------------------------------------------------------- */

.footer {
  background: var(--dh-paper-deep);
  padding-top: var(--s-7);
  padding-bottom: var(--s-5);
  /* No gap above — the footer butts straight onto the editorial band. */
  margin-top: 0;
}

/* Full width with a small margin. The link row and the social row hold the
   left and right edges at every browser size, rather than being pinned to a
   1440px content column. */
.footer__inner {
  width: 100%;
  max-width: none;
  margin-inline: auto;
  padding-inline: var(--s-4);
}

.footer__keep { text-align: center; padding-bottom: var(--s-7); }
.footer__keep h2 { margin-bottom: var(--s-5); }

.subscribe {
  display: flex;
  align-items: flex-end;
  gap: var(--s-3);
  max-width: 460px;
  margin-inline: auto;
}
.subscribe__field { flex: 1; text-align: left; }
.subscribe input {
  width: 100%;
  padding: 10px 0;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--dh-ink);
  font-family: var(--f-body);
  font-size: 15px;
  color: var(--dh-ink);
}
.subscribe input::placeholder { color: var(--dh-ink-45); }
.subscribe button { flex: 0 0 auto; min-height: 44px; padding-bottom: 4px; }

.subscribe__note {
  margin-top: var(--s-3);
  font-family: var(--f-body);
  font-size: 13px;
  color: var(--dh-ink-70);
  min-height: 1.4em;
}

/* --------------------------------------------------------------------------
   AGE GATE (COPPA) — stands in front of every e-mail sign-up.

   Same rhythm as .subscribe: hairline-underlined controls, square corners,
   grayscale. The selects are BODY face on purpose — Chocogom draws no digits
   (38 zero-contour glyphs, see HANDOFF), so a year set in the display face
   would reserve width and paint nothing.
   -------------------------------------------------------------------------- */

/* .subscribe is display:flex, which beats the UA stylesheet's [hidden]
   rule — without this the "hidden" form stays on screen. */
.subscribe[hidden] { display: none; }

.agegate {
  max-width: 460px;
  margin-inline: auto;
  text-align: left;
}
.agegate__prompt {
  margin-bottom: var(--s-3);
  font-family: var(--f-body);
  font-size: 15px;
  color: var(--dh-ink);
}
.agegate__row {
  display: flex;
  align-items: flex-end;
  gap: var(--s-3);
  flex-wrap: wrap;
}
.agegate__select {
  flex: 1 1 120px;
  min-width: 0;                /* never let the control set the track floor */
  min-height: 44px;
  padding: 10px 0;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--dh-ink);
  border-radius: 0;
  font-family: var(--f-body);
  font-size: 15px;
  color: var(--dh-ink);
}
.agegate__go { flex: 0 0 auto; min-height: 44px; padding-bottom: 4px; }
.agegate__note {
  margin-top: var(--s-3);
  font-family: var(--f-body);
  font-size: 13px;
  color: var(--dh-ink-70);
  min-height: 1.4em;
}
.agegate__blocked {
  max-width: 460px;
  margin-inline: auto;
  font-family: var(--f-body);
  font-size: 15px;
  color: var(--dh-ink-70);
}

/* STACKED at every breakpoint — social, then links, then copyright.
   No separator rules; the spacing does the separating. */
.footer__mid {
  display: flex;
  flex-direction: column-reverse;   /* social row first, links below */
  align-items: center;
  gap: var(--s-6);
  /* Top padding halved (Blake, 15 Sep 2026) — it is the space above the DH
     bug, which is the first thing up this reversed stack. Was var(--s-6). */
  padding-block: calc(var(--s-6) / 2) 0;
}

.footer__links {
  /* One line: Contact · Privacy · Terms · Accessibility Statement.
     The copyright is also an item in this list, so it is pushed onto its own
     row with flex-basis:100% rather than being pulled out of the markup. */
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--s-4) var(--s-5);
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Copyright is the last item and takes the full row, below the link line. */
.footer__links .footer__copy {
  flex: 0 0 100%;
  text-align: center;
  margin-top: var(--s-2);
  font-size: var(--t-meta);
  letter-spacing: var(--t-meta-ls);
  color: var(--dh-ink-45);
  /* WAS nowrap. The line now carries the copyright plus two legal links, so
     on a phone it has to be allowed to wrap — nowrap pushed it off the side
     of the viewport, which the layout audit fails on. */
  white-space: normal;
  text-wrap: balance;
}

/* THE LEGAL LINKS — small, pipe-separated, riding with the copyright
   (Blake, 15 Sep 2026). Set at the copyright's own size and colour rather
   than the 14px of the links row above: they are a legal footnote, not
   navigation, and sizing them as navigation would make them the loudest
   thing in the footer's last line. */
.footer__links .footer__copy .footer__legal-link {
  font-size: inherit;
  letter-spacing: inherit;
  color: var(--dh-ink-45);
  /* A 44px touch target without 44px of visible padding: the hit area grows
     around the type instead of the type growing. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--dh-hairline);
}
.footer__links .footer__copy .footer__legal-link:hover {
  color: var(--dh-ink);
  text-decoration-color: currentColor;
}
.footer__legal-sep {
  display: inline-block;
  padding: 0 8px;
  color: var(--dh-ink-45);
  opacity: 0.55;
}

/* Social icons at 200%. The footer is going to grow — this is the anchor. */
.footer .social-row { gap: var(--s-5); }
.footer .social-row .icon { width: 64px; height: 64px; stroke-width: 1.6; }
.footer__links a {
  font-family: var(--f-body);
  font-weight: 400;
  font-size: 14px;
  color: var(--dh-ink-70);
  transition: color var(--dur) var(--ease);
}
.footer__links a:hover { color: var(--dh-ink); }



@media (max-width: 768px) {
  .subscribe { flex-direction: column; align-items: stretch; gap: var(--s-4); }
  .subscribe button { width: 100%; }
  .footer .social-row .icon { width: 48px; height: 48px; }
  .footer__links { gap: var(--s-3) var(--s-4); }
  .footer__links a { font-size: 13px; }
  .footer__links .footer__copy { white-space: normal; }
}


/* --------------------------------------------------------------------------
   HYPHEN TIGHTENING
   In uppercase Chocogom at nav tracking, a hyphen picks up the letter-spacing
   on BOTH sides and reads as a word space — "SUPER - VILLAINS", "SCOOBY - DOO".
   chrome.js wraps hyphens in .hy so the sidebearings can be pulled back in.
   -------------------------------------------------------------------------- */

/* CHOCOGOM RESCUE — the base class for any character the display face
   cannot draw. See the measurement note above hy() in chrome.js: Chocogom
   has NO glyph outline for any digit or any punctuation mark, so those
   characters reserve width and paint nothing, and the browser never falls
   back because the glyph technically exists.

   .cg puts the character in the body face so it is actually visible.
   .hy / .dt / .at are optical modifiers on top, for the three that need
   individual tuning. Everything else takes the default and looks right.

   Sized in em throughout so it tracks whatever it sits inside — a nav row,
   a hero headline, a tray label. */
/* EACH OF THESE MUST STAND ALONE.
   .cg is the base that DH_HY() emits, but .hy / .dt / .at are also written
   by hand in the static HTML, where there is no .cg alongside them. Moving
   font-family into .cg alone silently returned the hard-coded colon and '@'
   in index.html to Chocogom — which draws neither — so they vanished. They
   are grouped here so any one of them works on its own. */
.cg,
.hy,
.dt,
.at {
  display: inline-block;
  font-family: var(--f-body);
  font-weight: 500;
  letter-spacing: 0;
}

/* HYPHEN. The whole reason "SCOOBY-DOO" looked like "SCOOBY DOO": the glyph
   drew nothing, which was misdiagnosed as tracking and patched with negative
   margins. It now genuinely renders, and the tightening is what it was
   always meant to be — cancelling the display tracking either side so the
   hyphen binds the two words instead of floating between them.
   No vertical nudge: that was compensating for Chocogom's low hyphen, and
   this is Poppins' hyphen now, which sits correctly. */
.hy {
  font-size: 0.92em;
  margin-inline: -0.07em;
}

/* FULL STOP. Part of the brand name — "Warner Bros." must keep its period.
   Pulled back against the letter it belongs to so it reads as one unit. */
.dt {
  font-size: 0.9em;
  margin-left: -0.1em;
  margin-right: 0.02em;
}

/* A .cm comma modifier lived here for one day, 24-25 Sep 2026, while the hero
   headline was set with commas. The headline went back to periods, nothing
   else used it, and an unused modifier in this group is the kind of thing that
   gets copied by mistake. Removed. If a comma ever needs the same treatment,
   it was font-size 0.9em with margin -0.1em / 0.02em — identical to .dt — and
   hy() already rescues runtime commas into the body face via plain .cg. */

/* AT SIGN. Slightly small and optically centred on the cap height so it
   reads as part of the display setting, not a pasted-in character. */
.at {
  font-size: 0.72em;
  vertical-align: 0.08em;
  margin-right: 0.02em;
}

/* TRADEMARK MARK
   Sized in em so it stays proportional wherever it lands — nav row, hero
   headline, tray label. Raised to sit against the cap line rather than the
   baseline, and set at the lighter body face so it does not carry Chocogom's
   weight at three times the size it should be. */
.tm {
  display: inline-block;
  font-family: var(--f-body);
  font-weight: 400;
  font-size: 0.42em;
  letter-spacing: 0;
  line-height: 1;
  /* Against the cap line. 1em of the PARENT is ~2.4em here, so the shift is
     expressed in the mark's own em to stay proportional. */
  transform: translateY(-1.15em);
  margin-left: 0.08em;
  opacity: 0.75;
}

/* ™ inside a button. The global 0.42em + cap-line shift is tuned for
   Chocogom at headline scale; on a 14px Poppins button label it rendered as
   a detached dot (Blake, 26 Aug 2026). Larger, and raised only enough to
   read as superscript against button caps. */
.btn .tm {
  font-size: 0.62em;
  transform: translateY(-0.35em);
  opacity: 0.9;
}

/* (.hy now lives with .cg above — it is a modifier, not a rule of its own.) */


/* --------------------------------------------------------------------------
   ABOUT link in the header, beside SHOP.
   Client, 1 Sep 2026: "Can we add a click out to the About page on the top by
   shop?" SHOP stays the dominant control — About is a quiet secondary item at
   nav scale, never competing with it.
   -------------------------------------------------------------------------- */
.header__left { display: flex; align-items: center; gap: var(--s-4); }

.header__about {
  /* inline-flex + min-height, not padding: the layout audit measures the hit
     box and failed this at 61x24. The floor is 44px and the link has to meet
     it without changing where the word sits on the bar. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--f-body);
  font-size: var(--t-nav);
  /* +200 from the inherited 400 (Blake, 15 Sep 2026). Poppins is the BODY
     face and ships real weights — 600 is already in the font link, so this is
     a genuine cut, not a browser-synthesised one. The no-font-weight rule is
     Chocogom's alone (HANDOFF typography; qa.js enforces it), and nothing
     here is Chocogom.

     Desktop-only by construction: this link is display:none below 768px,
     where About moves into the tray. No extra media query needed. */
  font-weight: 600;
  line-height: var(--t-nav-lh);
  letter-spacing: var(--t-nav-ls);
  text-transform: uppercase;
  text-decoration: none;
  color: inherit;                 /* follows the header, incl. overlay mode */
  white-space: nowrap;
}
.header__about:hover { opacity: 0.7; }

/* --------------------------------------------------------------------------
   THE DH BUG AS THE SHOP ICON (Blake, 15 Sep 2026)

   Replaces the four-square bento. BOTH files ship and CSS chooses; there is
   no mask and no currentColor. These PNGs carry no alpha — the black file is
   black art on a WHITE square, the white file is white art on a BLACK square
   — so masking them by alpha drew a solid disc. The opaque ground is the
   asset's design: each file is meant to sit on the nav colour that matches it.

   WHICH FILE, WHEN:
     light nav (a PDP at rest)          -> black file  (white ground blends)
     dark nav  (hero overlay, or stuck) -> white file  (black ground blends)
     tray open                          -> black file, always

   That last rule is not an exception — the tray is --dh-paper and slides out
   from the left UNDER the SHOP trigger, so the ground behind the icon turns
   light the moment it opens, whatever the header was doing.

   The open state scales the mark. It does NOT rotate: the bento could spin
   45 degrees because it was four dots, a logo cannot.
   -------------------------------------------------------------------------- */
.shop-key {
  /* The keyhole is TALLER than it is wide (132.67 x 211.39), so it is sized
     by HEIGHT off the trigger's own font-size — width follows the ratio.
     Sizing it by width made it tower over SHOP. */
  --key-h:   1.18em;
  --key-dur: 340ms;

  display: block;
  flex: 0 0 auto;
  height: var(--key-h);
  width: calc(var(--key-h) * 132.67 / 211.39);
  transition: transform var(--key-dur) var(--ease);
}
.shop-key .icon--key {
  display: block;
  width: 100%;
  height: 100%;
  /* currentColor: the header sets its own colour for the light bar, the dark
     hero overlay and the stuck bar, and the mark follows it. This is what the
     two PNGs and their opacity swap used to do by hand. */
  color: inherit;
}

.shop-trigger:hover .shop-key { transform: scale(1.08); }
.shop-trigger[aria-expanded='true'] .shop-key { transform: scale(0.86); }

@media (prefers-reduced-motion: reduce) {
  .shop-key { transition-duration: 1ms; }
}

/* The opaque-ground blend rules that used to live here are gone with the
   PNG marks they propped up (21 Sep 2026). The keyhole and the lockup are
   vector with real transparency, so nothing needs blending into its surface.
   NOTE: .tray__bug was left dangling at the head of the rule below when the
   footer bug was removed, which silently handed the tray mark a margin. */

/* ABOUT now sits on the RIGHT, ahead of the socials. */
.header__right .header__about { margin-right: var(--s-2); }

/* --------------------------------------------------------------------------
   THE BUG IN THE FOOTER — black, large, above the socials.
   .footer__mid is column-reverse, so this is last in the markup and first up
   the stack. Decorative: alt="" and aria-hidden, since the wordmark and the
   copyright already name the brand for a screen reader.
   -------------------------------------------------------------------------- */
.footer__lockup {
  display: block;
  /* The lockup is 659.46 x 121.48 — a 5.43:1 band, not a square, so it is
     sized wider than the bug it replaced or it reads as a thin strip. */
  width: clamp(240px, 34vw, 420px);
  margin-inline: auto;
  color: var(--dh-ink);
}
.footer__lockup .icon--lockup {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 659.46 / 121.48;
}

/* The tray's brand mark — the real bug, on --dh-paper, and a link home.
   Sized 20% up from the first pass (Blake, 15 Sep 2026): was
   clamp(34px, 7vw, 46px). */
.tray__brand {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* A control now, so it owes the 44px floor in both directions — the mark
     itself can fall under that at the small end of the clamp. */
  min-width: 44px;
  min-height: 44px;
  line-height: 0;
}
.tray__key {
  /* Sized by HEIGHT — the keyhole is 132.67 x 211.39, taller than it is
     wide, so driving it by width would tower over the close X beside it.
     Height tracks the old bug's optical size, not its box. */
  display: block;
  height: clamp(41px, 8.4vw, 55px);
  width: calc(clamp(41px, 8.4vw, 55px) * 132.67 / 211.39);
  color: var(--dh-ink);
}
.tray__key .icon--key { display: block; width: 100%; height: 100%; }


/* --------------------------------------------------------------------------
   THE NAV LOCKUP — the real stacked logo, replacing the boxed Chocogom
   placeholder (Blake, 15 Sep 2026).

   TWO IMAGES, NOT A MASK. The first cut used mask-image + currentColor and
   painted nothing: Chromium blocks a CSS mask subresource over file://, and
   this site must open by double-clicking index.html. Same restriction that
   forced the icon sprite inline in icons.js. <img src> is unaffected.

   Which file, when — the same rule the SHOP bug follows:
     light nav (a PDP at rest)          -> black
     dark nav  (hero overlay, or stuck) -> white

   SIZED BY WIDTH: 110px on desktop. Width plus a fixed aspect-ratio means the
   height follows on its own, so the mark can never distort. The BAR does not
   move — .header is a fixed --header-h (126px desktop = 35 + 56 + 35) with
   overflow visible, so a 74px lockup overflows the 56px row and still sits
   inside the header box. Nothing below the header shifts.
   -------------------------------------------------------------------------- */
.header__lockup {
  position: relative;
  display: block;
  width: 110px;
  aspect-ratio: 1957 / 1314;
  transition: width 240ms var(--ease);
}
.header__lockup-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: opacity 240ms var(--ease);
}

/* Default: the light header takes the black file. */
.header__lockup--black { opacity: 1; }
.header__lockup--white { opacity: 0; }

/* A dark bar takes the white file — the hero overlay before it sticks, and
   the stuck bar on every page (.header.is-stuck paints --dh-ink). */
.header--overlay:not(.is-stuck) .header__lockup--black,
.header.is-stuck .header__lockup--black { opacity: 0; }
.header--overlay:not(.is-stuck) .header__lockup--white,
.header.is-stuck .header__lockup--white { opacity: 1; }

/* STUCK: width comes from --lockup-w-stuck, the same token the bar height is
   derived from. ONE value drives both, so the 8px of air above and below the
   logo cannot drift apart from the logo it is measured against — change the
   token and the bar follows. */
.header.is-stuck .header__lockup { width: var(--lockup-w-stuck); }

/* MOBILE: the resting lockup is smaller again. The stuck width lives in
   tokens.css beside the height it feeds. */
@media (max-width: 768px) {
  .header__lockup { width: 72px; }
}

@media (prefers-reduced-motion: reduce) {
  .header__lockup, .header__lockup-img { transition-duration: 1ms; }
}

/* --------------------------------------------------------------------------
   ABOUT IN THE TRAY — mobile only (Blake, 15 Sep 2026).

   One About control at any width: this appears where the header link
   disappears, at the same breakpoint, so the two can never both be on screen
   and can never both be missing. Set apart from the collection rows above it
   — smaller, lighter, with a rule over it — because it is not a collection
   and should not read as a third one.
   -------------------------------------------------------------------------- */
/* Always rendered — see the ABOUT note further down. The header link is the
   one that is hidden now, not this. */

.tray-about__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  min-height: 44px;
  padding: var(--s-3) var(--s-4);
  border-top: var(--hair);
  font-family: var(--f-body);
  font-size: 13px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--dh-ink-70);
}
.tray-about__link:hover { color: var(--dh-ink); }
.tray-about__link .icon { width: 18px; height: 18px; }

/* ABOUT IS NOT IN THE BAR AT ANY WIDTH (Blake, 15 Sep 2026). It lives in the
   SHOP tray, full stop.

   HOW THIS LANDED. It first moved to the tray at 768, which was wrong: the
   header is a three-column grid with the lockup CENTRED, so the right-hand
   group closes on a fixed midpoint as the window narrows, and ABOUT printed
   ON the logo from roughly 1080 down to 769. That was raised to 1180, then to
   1280 at Blake's request.

   Then the real objection: "the logo should always be centered ... the ABOUT
   menu item is pushing the logo to the left." Measured, it was not — the
   lockup sat at 0.0px from the viewport centre on all seven pages, resting
   and stuck, at 1440 and 1366. The grid was already symmetric.

   But the COMPOSITION was not. SHOP alone on the left against ABOUT + two
   social icons + the SUBMIT button on the right makes a geometrically centred
   logo read as pushed left. That is a real problem and a breakpoint cannot
   fix it, because it is true at every width where ABOUT is in the bar.

   So ABOUT leaves the bar entirely. The right group is socials + SUBMIT, the
   sides balance, and the centred logo now reads as centred. SUBMIT keeps its
   own 900px breakpoint — see .tray-submit below. */
.tray-about { display: block; margin-top: auto; }

/* Belt and braces: chrome.js no longer RENDERS the header link at all, so
   this rule should never have anything to match. It stays as a guard — if
   the markup ever comes back, it comes back invisible rather than shipping a
   second About control and shoving the composition off balance again. */
.header__right .header__about { display: none; }

/* --------------------------------------------------------------------------
   SUBMIT YOUR OBSESSION — the nav entry (Blake, 15 Sep 2026).

   Desktop: to the RIGHT of the socials. Outlined and small so it reads as a
   secondary invitation and never competes with SHOP, which is the navigation.
   Below 768px it disappears and the tray carries it instead — one control at
   any width, same contract as About.
   -------------------------------------------------------------------------- */
.header__submit {
  display: inline-flex;
  align-items: center;
  min-height: 36px;
  padding: 0 var(--s-3);
  border: 1px solid currentColor;
  font-family: var(--f-body);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.14em;
  line-height: 1;
  text-transform: uppercase;
  text-decoration: none;
  color: inherit;                 /* follows the header, incl. overlay mode */
  white-space: nowrap;
  transition: opacity var(--dur) var(--ease);
}
.header__submit:hover { opacity: 0.66; }

/* The tray's SUBMIT row sits above About and carries a touch more weight —
   it is the invitation, About is the reference. */
.tray-about__link--submit {
  color: var(--dh-ink);
  font-weight: 500;
}

@media (max-width: 900px) {
  .header__right .header__submit { display: none; }
  /* The tray's counterpart is flipped on AFTER its base rule, further down —
     see the note there. Putting it here looked tidier and silently lost:
     same specificity, earlier in the file, so `display: none` won and the
     tray Submit vanished at every width. Caught by tools/audit-nav.js. */
}

/* --------------------------------------------------------------------------
   THE PRIVACY TICK (Blake, 15 Sep 2026)

   Sits immediately above the submit on every form that collects a person.
   Unticked by default and required — a pre-ticked box is not consent.

   The box keeps its native appearance on purpose: a custom-drawn checkbox is
   a recurring accessibility regression, and a real <input type="checkbox">
   already announces its state, takes focus and toggles on Space.
   -------------------------------------------------------------------------- */
.consent {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: var(--s-2);
  /* The footer centres its text, and the label inherited it: the line sat
     in the middle of its column, away from the box. Always reads from the
     box (1 Oct 2026). */
  text-align: left;
}
.consent__box {
  width: 16px;
  height: 16px;
  margin: 3px 0 0 0;
  flex: 0 0 auto;
  accent-color: currentColor;
}
.consent__label {
  font-family: var(--f-body);
  font-size: 12px;
  line-height: 1.6;
  color: inherit;
  /* The label is the tap target with the box — 44px of height between them
     at this line-height and size. */
  padding-block: 2px;
}
.consent__link { color: inherit; text-underline-offset: 3px; }


/* The sign-up is a flex ROW (field + button). The privacy tick is a third
   child and must not ride along on that line — it wraps to its own full-width
   row underneath, above the button in reading order on a phone. */
.subscribe { flex-wrap: wrap; }
.subscribe .consent { flex: 1 0 100%; order: 1; }
.subscribe button { order: 2; }

/* --------------------------------------------------------------------------
   SUBMIT YOUR OBSESSION in the tray — an outlined BUTTON (Blake, 15 Sep 2026).

   Matches the desktop header control rather than the rows above it: it is an
   invitation, not another place to drill into. Outlined, never filled, so it
   stays quieter than SHOP while still reading as a control.
   -------------------------------------------------------------------------- */
/* HIDDEN UNTIL 900, even though its container .tray-about appears at 1280.
   The two share a container, not a breakpoint, and each leaves the bar at the
   width where IT starts hitting the centred lockup:

     ABOUT  collides below ~1080 → moves to the tray at 1280
     SUBMIT collides below ~840  → moves to the tray at 900

   Moving both at 1280 would strip the desktop nav of its only call to action
   across a 380px band where it still fits perfectly well. Moving both at 900
   would leave ABOUT sitting on the logo for 280px, which is the bug this
   pass exists to fix. Measured, not guessed — tools/nav-probe style sweep at
   15 widths. */
.tray-submit {
  display: none;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  margin: var(--s-4);
  padding: 0 var(--s-4);
  background: transparent;
  border: 1px solid var(--dh-ink);
  font-family: var(--f-body);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.14em;
  line-height: 1;
  text-transform: uppercase;
  text-decoration: none;
  text-align: center;
  color: var(--dh-ink);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.tray-submit:hover { background: var(--dh-ink); color: var(--dh-invert); }

/* AFTER the base rule, not up with the header's 900px block. Both selectors
   are a single class, so source order decides — and the base rule sets
   `display: none`. Declared earlier, this flip never applied. */
@media (max-width: 900px) {
  .tray-submit { display: flex; }
}


/* --------------------------------------------------------------------------
   COMING SOON — parked product tiles (Blake, 15 Sep 2026)

   A parked product is one whose PDP is not built, so its tile is deliberately
   inert. Until now it looked identical to a live tile, which made correct
   behaviour read as a bug ("I can't click the tray tiles"). The tile now says
   what is true instead.

   Type is set ABOVE .ph__label's weight and tracking rather than in a badge
   chip: the grey box is already a quiet field, and a filled pill inside it
   would read as a UI control on something that cannot be clicked.
   -------------------------------------------------------------------------- */

.ph--soon .ph__label {
  font-weight: 600;
  letter-spacing: 0.2em;
  color: var(--dh-ink-45);
  /* "Coming Soon" is two words, not a slot identifier — it may wrap on a
     narrow tray column rather than shrinking to unreadable. */
  white-space: normal;
  font-size: clamp(9px, 1.2vw, 12px);
}

/* The image case: a parked product that HAS photography. Nothing uses this
   today (all 11 parked products are photo-less) — it exists so the state
   cannot silently revert to looking live the day a photo lands. */
.tile__soon {
  position: absolute;
  left: 50%;
  bottom: var(--s-3);
  transform: translateX(-50%);
  padding: 6px 12px;
  font-family: var(--f-body);
  font-weight: 600;
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--dh-ink);
  background: rgba(255, 255, 255, 0.92);
  white-space: nowrap;
}

/* The tile stays legible and stops pretending to be a destination: no lift,
   no pointer. The link itself is already aria-disabled by the router. */
.tile--soon { cursor: default; }
.tile--soon .tile__media { opacity: 0.85; }
.tile--soon:hover .tile__media { opacity: 0.85; }

/* FORM HONEYPOT (1 Oct 2026), rendered by DH_HONEYPOT in chrome.js. Moved
   off-screen, not display:none, because some bots skip fields that are not
   displayed. Nothing a person can see, tab to or hear. */
.hp {
  position: absolute;
  left: -10000px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

