/* ============================================================
   Chewtoy's Dugout — chrome · "Night Pitch" (2026-09)
   Header extras, phone drawer, popovers (account, mini-cart), search,
   overlay/dialog shell, toast host, back-to-top, tab bar visibility.
   Loads on EVERY page with tokens.css + components.css; everything is
   scoped under .ctd-scope wrappers so legacy pages are untouched.
   ============================================================ */

/* ============================================================
   THE HEADER — stone facia in a steel channel
   Two board courses held between two rivetted flanges cut from the
   girder's own face; the nav items and the three controls are the
   site's casting set into the channel between them (gunmetal, the
   live page and a full basket in gold — 2026-09-16, replacing the
   bolted nameplates), and the brand badge is hung over the bar so
   its pennant overhangs the lower flange.

   Everything here spends a --nav-*, --fx-nav-* or --btn-* token from
   tokens.css. Nothing is re-derived locally: a custom property
   resolves its var() references at the element that DECLARES it,
   so overriding --nav-rivet-r down here would move the shoes' chamfer
   and leave every geometry derived from it exactly where it was. The
   phone tiers therefore live in tokens.css, on the same selector.
   ============================================================ */
.ctd-header {
  background: var(--ink-950);
  border-bottom: 0;
  box-shadow: 0 6px 16px -8px var(--ink-950);
  /* the badge hangs BELOW the bar, so the bar may not clip it, and the
     flow needs the overhang reserved or the sign lands on the first
     thing on the page. max() because the badge is sized in px and the bar
     is not: shrink the badge past --nav-h and a bare calc() goes NEGATIVE,
     which pulls the page up UNDER a sticky header instead of reserving
     nothing. */
  margin-bottom: max(0px, calc(var(--nav-brand-h) - var(--nav-h)));
}
/* The boarding, inset by the flange depth and shadowed along both long
   edges so it sits INSIDE the channel rather than behind it.

   LEFT AND RIGHT ARE --nav-bar-inset, NOT 0. That token is 0 below the girder
   gate — the bar runs the full width, as it always did — and half the page
   minus half the facia above it, which lands the ends plumb on the frame's
   outer faces. It is a percentage inside a custom property, so it only works
   on a full-width box; these three layers and the two shoes are all inset:0
   inside the header, so they are. See tokens.css. */
.ctd-header__board {
  position: absolute; left: var(--nav-bar-inset); right: var(--nav-bar-inset);
  top: var(--nav-flange); bottom: var(--nav-flange); z-index: 0;
  background: var(--fx-nav-facia); box-shadow: var(--fx-nav-facia-edge);
}
.ctd-header__flange {
  position: absolute; left: var(--nav-bar-inset); right: var(--nav-bar-inset);
  height: var(--nav-flange); z-index: 1;
  background: var(--fx-nav-beam);
}
.ctd-header__flange--t { top: 0; box-shadow: inset 0 1px 0 var(--girder-rivet-hi), 0 4px 10px -4px var(--ink-950); }
.ctd-header__flange--b { bottom: 0; box-shadow: inset 0 -1px 0 var(--girder-lo), 0 -4px 10px -4px var(--ink-950); }

/* ---------- the generated channel ----------
   js/ctd-girder.js hands each flange to js/ctd-steel.js, the same painter the
   girders and cross members use, and the painter sets [data-steel] once the
   markup is actually in. These rules are what that attribute buys: the
   gradient beam and the tiled rivet run get out of the way, because the same
   picture is in the markup now and drawn better — craters, weeps and a chewed
   outline that happen ONCE instead of a tile repeating along the bar.

   THE ATTRIBUTE IS SET LAST, so a module that fails to load, a browser that
   will not run it, a crawler or a thrown exception all leave the gradient
   channel exactly as it was. The switch is one attribute and only something
   that has already succeeded ever sets it.

   The rivet size is stepped down here rather than in the painter: a 16px
   flange cannot hold the frame's 7px dome. paintSteel reads it off the element
   with getComputedStyle, which resolves the cascade, so this override reaches
   it.

   The cast becomes a filter for the same reason the members' does: the
   generated plate's edges are nicked, and a box-shadow would throw a crisp
   rectangle off a silhouette that is not one. */
.ctd-header__flange { --girder-rivet-r: var(--nav-flange-r); }
.ctd-header__flange svg, .ctd-header__cap svg { display: block; width: 100%; height: 100%; }
.ctd-header__flange[data-steel] { background: none; box-shadow: none; }
.ctd-header__flange[data-steel] .ctd-header__rivets { display: none; }
.ctd-header__flange[data-steel="h"] { filter: var(--fx-steel-cast-h); }
/* The shoe loses its clip with its background: the drawn fitting IS that
   outline, and clipping a shape to a polygon it already is only costs a
   composite layer. Same trade the tee makes in ornament.css. */
.ctd-header__cap[data-steel] { background: none; box-shadow: none; clip-path: none; }
/* --fx-nav-flange-rivets is one dome drawn for its own square box, every
   layer centred at 50%/50% of it — so tiling the WHOLE token at pitch by
   box gives a run of identical hardware with no per-layer positions to
   keep in step. Same trick the section nameplate uses for its corners. */
.ctd-header__rivets {
  position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%);
  height: calc(var(--nav-flange-r) * 4);
  /* TWO runs at coprime pitches. One run of one alloy is a row of identical
     heads however well the dome is drawn; two that never come back into phase
     read as a flange that has been worked on. THE IRON RUN IS FIRST now: the
     flange is brass, so the cool heads are the ones that show and the brass
     run is the quieter of the two rather than the loud one. */
  background-image: var(--fx-nav-flange-rivets-b), var(--fx-nav-flange-rivets);
  background-size: var(--nav-flange-pitch-b) calc(var(--nav-flange-r) * 4),
                   var(--girder-pitch) calc(var(--nav-flange-r) * 4);
  background-position: 18% 50%, 50% 50%; background-repeat: repeat-x, repeat-x;
  pointer-events: none;
}

/* ---------- the shoes on the ends ----------
   Hung OFF the facia's end by their own width, not lapped over it, and that is
   the tee strap's rule rather than a new one: a fitting that laps the bar it
   finishes has to be paid for out of the row's clearance, and the rack has
   about 32px of it at the left and 21px at the right — see .ctd-header__inner
   below. Butted, they cost the row nothing and the whole assembly still
   measures the 1360 the bar used to.

   ONLY WHERE THERE IS AN END TO CLOSE. Below the girder gate --nav-bar-inset
   is 0, the facia runs off both sides of the screen and a shoe would be a
   fitting bolted to thin air — the same gate, and the same argument, as the
   bronze tees in ornament.css. `display` is what does it, which also means the
   painter never generates one: a hidden element measures zero and
   js/ctd-steel.js declines a box it cannot see.

   THE WAIST DECIDES WHICH SIDE THE HEADS ARE ON. --fx-nav-cap sizes them to a
   waist-wide tile so this one declaration can move all three at once; the flat
   layers under them are the full width and have no positioning slack, so the
   same value passes straight through. Same mechanism, and the same reason, as
   .ctd-joint--l / --r. */
.ctd-header__cap { display: none; }

@media (min-width: 1360px) {
  /* html.ctd-v3, not bare .ctd-header: --nav-bar-inset is only overridden on a
     v3 route (see the note in tokens.css), so on a legacy one the facia still
     runs the full width and these would hang off both ends of the screen —
     the right-hand one at `right: -40px`, which is 40px of scrollable overflow
     nobody asked for. */
  html.ctd-v3 .ctd-header__cap {
    display: block; position: absolute; top: 0; z-index: 1;
    width: var(--nav-cap-w); height: var(--nav-h);
    background: var(--fx-nav-cap); box-shadow: var(--fx-nav-cap-edge);
  }
  .ctd-header__cap--l {
    left: calc(var(--nav-bar-inset) - var(--nav-cap-w));
    background-position-x: left;
    clip-path: polygon(
      0 0, calc(100% - var(--nav-chamfer)) 0, 100% var(--nav-chamfer),
      100% var(--nav-flange),
      calc(100% - var(--nav-cap-notch)) var(--nav-flange),
      calc(100% - var(--nav-cap-notch)) calc(100% - var(--nav-flange)),
      100% calc(100% - var(--nav-flange)), 100% calc(100% - var(--nav-chamfer)),
      calc(100% - var(--nav-chamfer)) 100%, 0 100%);
  }
  .ctd-header__cap--r {
    right: calc(var(--nav-bar-inset) - var(--nav-cap-w));
    background-position-x: right;
    clip-path: polygon(
      var(--nav-chamfer) 0, 100% 0, 100% 100%, var(--nav-chamfer) 100%,
      0 calc(100% - var(--nav-chamfer)), 0 calc(100% - var(--nav-flange)),
      var(--nav-cap-notch) calc(100% - var(--nav-flange)),
      var(--nav-cap-notch) var(--nav-flange),
      0 var(--nav-flange), 0 var(--nav-chamfer));
  }

  /* ---------- and the page shows either side of it ----------
     The bar is an object now, so the header may not go on painting a black
     band the whole width behind it — that band IS the full-width nav the owner
     asked to be rid of, and leaving it in would have made the inset invisible.
     What shows outside the ends is the frame and the ground the page already
     stands on, which is the same thing that shows outside a cross member.

     The cast goes with it. --fx-steel-cast-h is a drop-shadow on each flange
     and follows the metal's own chewed outline, so the bar keeps its shadow;
     the header's box-shadow was a rectangle the width of the screen. */
  html.ctd-v3 .ctd-header { background: none; box-shadow: none; }
}

/* The row lines up with the FRAME, not with the copy measure — the rack needs
   the room and the bar is part of the steelwork rather than part of the page.
   Above the girder gate --nav-measure IS the facia's width, so the row is the
   bar; `.ctd-wrap`'s --girder-pad padding then keeps the badge and the account
   button off the timber's cut ends. */
.ctd-header .ctd-header__inner {
  position: relative; z-index: 2;
  /* .ctd-wrap is a `width:`, not a max-width, so this has to restate the
     whole expression rather than cap it — capping a width does nothing. */
  width: min(100% - 2 * var(--nav-row-inset), var(--nav-measure));
  height: var(--nav-h); gap: var(--s-4);
}
.ctd-header__menu { display: none; }
.ctd-header__version { height: 20px; }

/* ---------- the badge, hung over the bar ----------
   align-self overrides the row's centring so flex-start pins the badge's
   top to the bar's and all the growth goes DOWNWARD: a sticky header is
   flush with the top of the viewport, so anything above the bar's top
   edge is clipped off the screen on scroll. flex:none is load-bearing —
   base.css sets img{max-width:100%} and a shrinkable flex item squeezed
   the 2.49:1 mark to 1.74:1. */
.ctd-header .ctd-brand {
  position: relative; z-index: 3;
  align-self: flex-start; flex: none; min-height: 0; gap: 0;
}
.ctd-brand__mark { height: var(--nav-brand-h); width: auto; max-width: none; filter: var(--fx-nav-brand-cast); }
.ctd-brand svg { height: calc(var(--nav-brand-h) * 0.34); width: auto; }

/* ---------- the nav rack: castings in the channel ----------
   The nameplates were bolted, beaten brass plates from 2026-09-07 to
   2026-09-16 — their recipe and the argument for it are in the Decision Log
   under those dates. They are the site's casting now, the same one the
   controls on the right and every button on the page are: the channel's full
   height, flush to the flanges, gunmetal at rest. The page you are on is the
   GOLD casting — the lit-layer rule again, light the thing you are aiming at,
   and on this bar that is where you already are. The enamel badge, the wear,
   the alternating bolt alloys and the runs onto the boards went with the
   plates; a casting is one piece and carries no fixings.

   components.css draws a flat nav link with an accent underline on ::after.
   Here ::after is the casting's face, so its box, transform and background
   are all restated, and the flat hover fill is turned off.

   THE LABEL IS RESTATED TOO (2026-09-16). components.css sizes a nav link at
   --fs-14, which is a text link's size; these are buttons in a 64px channel
   and the owner's words were "too small for options". --fs-16 at 700, with
   --s-6 either side instead of --s-5 — the casting has a 3px rim and a 10px
   corner cut to clear before the letters start, so the padding that reads as
   comfortable on a flat link reads as tight on this one. */
.ctd-header .ctd-nav { position: relative; z-index: 2; flex: none; gap: var(--s-2); }
.ctd-header .ctd-nav__link {
  --btn-c: var(--btn-cut); --btn-b: var(--btn-bevel);
  --btn-ci: calc(var(--btn-c) - var(--btn-b) * 0.41);
  --btn-clip: polygon(
      var(--btn-c) 0, calc(100% - var(--btn-c)) 0, 100% var(--btn-c),
      100% calc(100% - var(--btn-c)), calc(100% - var(--btn-c)) 100%,
      var(--btn-c) 100%, 0 calc(100% - var(--btn-c)), 0 var(--btn-c));
  --btn-clip-in: polygon(
      var(--btn-ci) 0, calc(100% - var(--btn-ci)) 0, 100% var(--btn-ci),
      100% calc(100% - var(--btn-ci)), calc(100% - var(--btn-ci)) 100%,
      var(--btn-ci) 100%, 0 calc(100% - var(--btn-ci)), 0 var(--btn-ci));
  --btn-rim: var(--fx-btn-rim-gun); --btn-rim-press: var(--fx-btn-rim-gun-press);
  --btn-face: var(--fx-btn-face-gun);
  position: relative; isolation: isolate;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  height: max(var(--nav-plate-h), calc(var(--nav-h) - var(--nav-flange) * 2)); min-height: 0;
  padding: 0 var(--s-6); min-width: calc(var(--s-6) * 2 + 48px);
  border-radius: 0; background: none; white-space: nowrap;
  font-size: var(--fs-16); font-weight: 700; letter-spacing: 0.06em;
  color: var(--gold-300); text-shadow: var(--fx-btn-engrave-light);
  filter: var(--fx-btn-cast);
  transition: filter var(--t-base) var(--ease), transform var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.ctd-header .ctd-nav__link::before, .ctd-header .ctd-nav__link::after { content: ""; position: absolute; pointer-events: none; }
.ctd-header .ctd-nav__link::before { inset: 0; z-index: -2; clip-path: var(--btn-clip); background: var(--btn-rim); border-radius: 0; box-shadow: none; }
.ctd-header .ctd-nav__link::after {
  inset: var(--btn-b); height: auto; z-index: -1; clip-path: var(--btn-clip-in);
  background: var(--btn-face); transform: none;
  transition: filter var(--t-fast) var(--ease);
}
.ctd-header .ctd-nav__link > .ctd-i { position: relative; z-index: 1; }
.ctd-header .ctd-nav__link:hover { color: var(--btn-hot-ink); background: none; }
.ctd-header .ctd-nav__link:hover::before { background: var(--fx-btn-rim-gold); }
.ctd-header .ctd-nav__link:hover::after { filter: brightness(1.22); transform: none; }
.ctd-header .ctd-nav__link:active { transform: translateY(2px); filter: var(--fx-btn-cast-press); }
.ctd-header .ctd-nav__link:active::before { background: var(--btn-rim-press); }
.ctd-header .ctd-nav__link:active::after { filter: brightness(0.9); }
.ctd-header .ctd-nav__link:focus-visible { outline: 2px solid var(--gold-300); outline-offset: 3px; }
/* the page you are on */
.ctd-header .ctd-nav__link--active {
  --btn-rim: var(--fx-btn-rim-gold); --btn-rim-press: var(--fx-btn-rim-gold-press); --btn-face: var(--fx-btn-face-gold);
  color: var(--on-gold); text-shadow: var(--fx-btn-engrave-dark); box-shadow: none;
}
.ctd-header .ctd-nav__link--active:hover { color: var(--on-gold); filter: var(--fx-btn-cast-hot); }
.ctd-header .ctd-nav__link--active:hover::before { background: var(--btn-rim); }
.ctd-header .ctd-nav__link--active:hover::after { filter: brightness(1.08); }

/* ---------- the controls: castings, set in the channel ----------
   Search, basket and account are the site's button casting (components.css,
   THE CASTING in tokens.css) in its icon form, and they are the height of
   the steel channel exactly — the bar less its two flanges — so they sit
   flush between them instead of floating in the middle with dead board above
   and below, which was the first thing the owner said about them.

   max() with the plate height is the phone tiers: at 640 and below the
   channel is 36px and --nav-plate-h is 40, so the controls hold at 40 and lap
   the flanges by 2px rather than drop under the touch target.

   --btn-ctl-w is 84 on the desktop bar and 74 from 1320 down (tokens.css, the
   1320 block: below there the badge is centred and every px of this end costs
   it 2.4 of height). The glyph goes up with the casting — 20px in a 56px
   control was a mark in a lot of metal, 24 in a 64px one is a control.

   Gunmetal at rest. The basket goes GOLD when there is something in it —
   inc/cart-dropdown.php adds .is-live for a count above zero, on first paint
   and on every fragment refresh — which is the lit-layer rule doing the one
   job it has on this bar: light the thing you are aiming at. The drawer's own
   close button is outside .ctd-header__inner and stays a plain icon button. */
.ctd-header__actions { position: relative; flex: none; gap: var(--s-2); }
.ctd-header__inner .ctd-iconbtn {
  --btn-c: var(--btn-cut); --btn-b: var(--btn-bevel);
  --btn-ci: calc(var(--btn-c) - var(--btn-b) * 0.41);
  --btn-clip: polygon(
      var(--btn-c) 0, calc(100% - var(--btn-c)) 0, 100% var(--btn-c),
      100% calc(100% - var(--btn-c)), calc(100% - var(--btn-c)) 100%,
      var(--btn-c) 100%, 0 calc(100% - var(--btn-c)), 0 var(--btn-c));
  --btn-clip-in: polygon(
      var(--btn-ci) 0, calc(100% - var(--btn-ci)) 0, 100% var(--btn-ci),
      100% calc(100% - var(--btn-ci)), calc(100% - var(--btn-ci)) 100%,
      var(--btn-ci) 100%, 0 calc(100% - var(--btn-ci)), 0 var(--btn-ci));
  --btn-rim: var(--fx-btn-rim-gun); --btn-rim-press: var(--fx-btn-rim-gun-press);
  --btn-face: var(--fx-btn-face-gun);
  position: relative; isolation: isolate;
  border: 0; border-radius: 0; background: none;
  color: var(--gold-300); text-shadow: var(--fx-btn-engrave-light);
  filter: var(--fx-btn-cast);
  transition: filter var(--t-base) var(--ease), transform var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.ctd-header__inner .ctd-iconbtn::before, .ctd-header__inner .ctd-iconbtn::after { content: ""; position: absolute; pointer-events: none; }
.ctd-header__inner .ctd-iconbtn::before { inset: 0; z-index: -2; clip-path: var(--btn-clip); background: var(--btn-rim); }
.ctd-header__inner .ctd-iconbtn::after {
  inset: var(--btn-b); z-index: -1; clip-path: var(--btn-clip-in);
  background-image: var(--btn-face); background-repeat: no-repeat;
  transition: filter var(--t-fast) var(--ease);
}
.ctd-header__inner .ctd-iconbtn { width: var(--btn-ctl-w); height: max(var(--nav-plate-h), calc(var(--nav-h) - var(--nav-flange) * 2)); color: var(--bone-100); }
.ctd-header__inner .ctd-iconbtn .ctd-i { position: relative; z-index: 1; width: 24px; height: 24px; filter: var(--fx-nav-icon); }
.ctd-header__inner .ctd-iconbtn[aria-expanded="true"] { background: none; border-color: transparent; color: var(--bone-100); }
.ctd-header__inner .ctd-iconbtn[aria-expanded="true"]::before { background: var(--fx-btn-rim-gold); }
.ctd-header__inner .ctd-iconbtn[aria-expanded="true"]::after { filter: brightness(1.22); }
.ctd-header__inner .ctd-iconbtn:hover { background: none; border-color: transparent; color: var(--bone-100); }
.ctd-header__inner .ctd-iconbtn:hover::before { background: var(--fx-btn-rim-gold); }
.ctd-header__inner .ctd-iconbtn:hover::after { filter: brightness(1.22); }
.ctd-header__inner .ctd-iconbtn:active { transform: translateY(2px); filter: var(--fx-btn-cast-press); }
.ctd-header__inner .ctd-iconbtn:active::before { background: var(--btn-rim-press); }
.ctd-header__inner .ctd-iconbtn:active::after { filter: brightness(0.9); }
.ctd-header__inner .ctd-iconbtn:focus-visible { outline: 2px solid var(--gold-300); outline-offset: 3px; }
/* the live basket */
.ctd-header__inner .ctd-iconbtn.is-live {
  --btn-rim: var(--fx-btn-rim-gold); --btn-rim-press: var(--fx-btn-rim-gold-press);
  --btn-face: var(--fx-btn-face-gold);
  color: var(--on-gold);
}
.ctd-header__inner .ctd-iconbtn.is-live .ctd-i { filter: var(--fx-nav-icon-on); }
.ctd-header__inner .ctd-iconbtn.is-live:hover { color: var(--on-gold); filter: var(--fx-btn-cast-hot); }
.ctd-header__inner .ctd-iconbtn.is-live:hover::before { background: var(--btn-rim); }
.ctd-header__inner .ctd-iconbtn.is-live:hover::after { filter: brightness(1.08); }
.ctd-header__inner .woocommerce-cart.ctd-iconbtn { text-decoration: none; }

/* The basket count: a brass stud on the plate's corner, not a UI dot. */
.ctd-header__inner .ctd-count, .ctd-header__inner .cart-count {
  top: -5px; right: -5px; min-width: 19px; height: 19px; padding: 0 4px;
  border-radius: var(--r-pill);
  background: var(--fx-nav-count); box-shadow: var(--fx-nav-count-edge);
  color: var(--on-gold); font-family: var(--font-display); font-size: var(--fs-12); line-height: 1;
}

/* ---------- search (desktop: inline reveal) ---------- */
.ctd-search { display: flex; align-items: center; }
.ctd-search[hidden] { display: none; }
.ctd-search .ctd-input { width: 260px; }
/* The field is set in the channel, so it fills it — same height expression as
   the controls it opens beside. At --control-h-sm it floated 12px off both
   flanges once the bar went to 100 (2026-09-16), which is the dead board
   above and below that the owner objected to on the buttons themselves. The
   drawer's search is .ctd-drawer__search and is not this. */
.ctd-header__inner .ctd-search .ctd-input {
  height: max(var(--nav-plate-h), calc(var(--nav-h) - var(--nav-flange) * 2));
}
/* The field inside the field. .ctd-input is a styled WRAPPER — it draws the
   milled channel and the border, and the control inside it has to be stripped
   or the browser draws its own 2px inset box and grey fill inside ours. The
   drawer's search has had this line since it was written (.ctd-drawer__search
   input, below); the desktop reveal never got it, and a 64px channel is not
   the place to leave a raw UA input showing. Focus still reads: .ctd-input
   carries :focus-within in components.css, exactly as the drawer's does. */
.ctd-header__inner .ctd-search .ctd-input input {
  flex: 1; min-width: 0; background: none; border: 0; outline: none; color: var(--text); font: inherit;
}
/* THE RACK STEPS ASIDE FOR THE FIELD. Every item in this row is flex:none —
   the castings may not be squeezed to fractions of themselves — so a 260px
   field opening into a full row does not compress anything, it OVERFLOWS, and
   the account button lands out past the shoe. The rack is the only item in
   the row that the field replaces rather than competes with (you are typing a
   destination instead of picking one), so it is the one that goes. :has() on
   the row rather than a class, because header.js only toggles [hidden] on the
   form and there is no state to keep in step. */
.ctd-header__inner:has(#headerSearch:not([hidden])) .ctd-nav { display: none; }

/* ---------- popovers (account menu, mini-cart) ---------- */
.ctd-popover {
  position: absolute; top: calc(100% + var(--s-2)); right: 0; z-index: var(--z-header);
  width: min(340px, calc(100vw - 2 * var(--s-4)));
  background: var(--surface); border: var(--bw) solid var(--line-2); border-radius: var(--r-2);
  box-shadow: var(--shadow-2); color: var(--text); text-align: left;
}
.ctd-popover[hidden] { display: none; }
.ctd-popover__head { display: flex; flex-direction: column; gap: var(--s-2); padding: var(--s-4) var(--s-5); border-bottom: var(--bw) solid var(--line-1); }
.ctd-popover__head .ctd-btn { align-self: flex-start; margin-top: var(--s-1); }
.ctd-popover__section { padding: var(--s-3) var(--s-2) var(--s-2); border-bottom: var(--bw) solid var(--line-1); }
.ctd-popover__section:last-child { border-bottom: 0; }
.ctd-popover__section > .ctd-eyebrow { display: block; padding: 0 var(--s-3) var(--s-1); }
.ctd-account { position: relative; }

.ctd-menu { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.ctd-menu__item {
  display: flex; align-items: center; justify-content: flex-start; gap: var(--s-3);
  min-height: var(--control-h); padding: 0 var(--s-3); border-radius: var(--r-1);
  color: var(--text-2); text-decoration: none; font-weight: 600; font-size: var(--fs-14);
}
.ctd-menu__item .ctd-i { color: var(--text-3); }
.ctd-menu__item:hover { background: var(--surface-2); color: var(--text); }
.ctd-menu__item:hover .ctd-i { color: var(--accent); }
.ctd-menu__item--logout { color: var(--text-3); }
.ctd-menu__item--lg {
  justify-content: space-between; min-height: 52px; padding: 0 var(--s-4);
  font-family: var(--font-display); font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; font-size: var(--fs-16);
  border-bottom: var(--bw) solid var(--line-1); border-radius: 0;
}
/* The three builders indented under Builders in the phone drawer. Smaller and
   sentence-cased on purpose: they are members of the row above, not peers of
   Shop and Help. The rule left of them is the indent made visible. */
.ctd-menu__item--sub {
  justify-content: flex-start; gap: var(--s-3); min-height: 46px;
  padding-left: var(--s-6); text-transform: none; letter-spacing: 0.02em;
  font-family: var(--font-body); font-weight: 600; font-size: var(--fs-14);
  color: var(--text-2);
  box-shadow: inset 2px 0 0 var(--line-2);
}
.ctd-menu__item--sub .ctd-i { flex: none; }
/* Two lines in one row: the name, and what the builder is for. */
.ctd-menu__item--rich { align-items: flex-start; padding-top: var(--s-2); padding-bottom: var(--s-2); min-height: 0; }
.ctd-menu__item--rich .ctd-i { margin-top: 3px; flex: none; }
.ctd-menu__text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.ctd-menu__text strong { font-weight: 700; color: inherit; }
.ctd-menu__item--rich[aria-current] { background: var(--surface-2); color: var(--text); }
.ctd-menu__item--rich[aria-current] .ctd-i { color: var(--accent); }

/* ---------- the builder group on the plate rack ----------
   One plate ("Builders") that drops the three builders under it. No JS: the
   panel is the mini-cart's opacity/visibility recipe driven by :hover and
   :focus-within, so it survives a soft navigation and works from the keyboard.

   THE GAP UNDER THE PLATE IS BRIDGED BY A PSEUDO-ELEMENT. .ctd-popover hangs
   at `top: calc(100% + var(--s-2))`, and that 8px of nothing is enough for the
   pointer to leave both the plate and the panel on the way down — the menu
   would blink shut mid-travel. ::before fills the gap with a transparent strip
   that is part of the panel's hover area. */
.ctd-navgroup { position: relative; display: inline-flex; }
.ctd-navgroup__menu {
  left: 0; right: auto; width: min(320px, calc(100vw - 2 * var(--s-4)));
  opacity: 0; visibility: hidden; transform: translateY(-4px);
  transition: opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease), visibility 0s linear var(--t-fast);
}
.ctd-navgroup__menu::before { content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: var(--s-2); }
.ctd-navgroup:hover .ctd-navgroup__menu,
.ctd-navgroup:focus-within .ctd-navgroup__menu {
  opacity: 1; visibility: visible; transform: translateY(0); transition-delay: 0s;
}
/* The caret is the only thing that says the plate has more under it. It sits
   inside the label run, so the plate's own min-width still holds the shape. */
.ctd-header .ctd-nav__link--group { gap: var(--s-1); }
.ctd-nav__caret { transition: transform var(--t-fast) var(--ease); }
.ctd-navgroup:hover .ctd-nav__caret,
.ctd-navgroup:focus-within .ctd-nav__caret { transform: rotate(180deg); }

/* Mini-cart: the wrapper keeps its legacy hooks (.header-cart-dropdown, .cart-dropdown-menu,
   .force-show) for js/cart-dropdown.js; only the shell is restyled here. */
.header-cart-dropdown { position: relative; }
.ctd-header .cart-dropdown-menu {
  position: absolute; top: calc(100% + var(--s-2)); right: 0; z-index: var(--z-header);
  width: min(380px, calc(100vw - 2 * var(--s-4))); max-height: 70vh; overflow-y: auto; /* lint-allow: vh — popover cap, not a page lock */
  background: var(--surface); border: var(--bw) solid var(--line-2); border-radius: var(--r-2); box-shadow: var(--shadow-2);
  opacity: 0; visibility: hidden; transform: translateY(-4px);
  transition: opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease), visibility 0s linear var(--t-fast);
}
.ctd-header .cart-dropdown-menu.force-show,
.ctd-header .header-cart-dropdown:hover .cart-dropdown-menu,
.ctd-header .header-cart-dropdown:focus-within .cart-dropdown-menu { opacity: 1; visibility: visible; transform: none; transition-delay: 0s; }
.ctd-header .cart-count { top: 0; }
/* Mini-cart contents (inc/cart-dropdown.php) on migrated pages, where the legacy
   css/cart-dropdown.css no longer loads. The cart page rework (Phase 3) replaces this. */
.ctd-header .cart-dropdown-menu .fas, .ctd-header .cart-dropdown-menu .fa { display: none; }
.ctd-header .cart-dropdown-empty { padding: var(--s-6); text-align: center; display: flex; flex-direction: column; gap: var(--s-3); align-items: center; color: var(--text-2); }
.ctd-header .cart-dropdown-empty__ring { display: none; }
.ctd-header .btn-continue-shopping, .ctd-header .btn-view-cart, .ctd-header .btn-checkout {
  display: inline-flex; align-items: center; justify-content: center; min-height: var(--control-h); padding: 0 var(--s-4);
  border: var(--bw) solid var(--line-2); border-radius: var(--r-1); background: var(--surface-2); color: var(--text); text-decoration: none;
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em; font-size: var(--fs-14);
}
.ctd-header .btn-checkout { background: var(--accent); border-color: var(--accent); color: var(--on-gold); }
.ctd-header .cart-dropdown-header { display: flex; justify-content: space-between; align-items: center; gap: var(--s-3); padding: var(--s-3) var(--s-4); border-bottom: var(--bw) solid var(--line-1); }
.ctd-header .cart-dropdown-header h4 { font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em; font-weight: 500; font-size: var(--fs-14); color: var(--text-2); }
.ctd-header .cart-dropdown-items { display: flex; flex-direction: column; }
.ctd-header .cart-dropdown-item { border-bottom: var(--bw) solid var(--line-1); }
.ctd-header .cart-dropdown-item__summary { display: flex; gap: var(--s-3); padding: var(--s-3) var(--s-4); align-items: flex-start; }
.ctd-header .cart-item-image { width: 48px; flex: none; }
.ctd-header .cart-item-image img { width: 48px; height: 48px; object-fit: cover; border-radius: var(--r-1); border: var(--bw) solid var(--line-1); }
.ctd-header .cart-item-details { flex: 1; min-width: 0; font-size: var(--fs-14); color: var(--text-2); }
.ctd-header .cart-item-name { color: var(--text); font-weight: 600; }
.ctd-header .cart-item-meta { display: flex; gap: var(--s-2); flex-wrap: wrap; font-size: var(--fs-13); color: var(--text-3); }
.ctd-header .cart-item-quantity-price { display: flex; justify-content: space-between; gap: var(--s-3); margin-top: var(--s-1); font-variant-numeric: tabular-nums; }
.ctd-header .cart-item-quantity-price .price { color: var(--gold-300); font-family: var(--font-display); }
.ctd-header .remove-cart-item { width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center; border-radius: var(--r-1); color: var(--text-3); font-size: var(--fs-16); }
.ctd-header .remove-cart-item::before { content: "×"; }
.ctd-header .remove-cart-item:hover { color: var(--err); background: var(--err-bg); }
.ctd-header .cart-dropdown-review { display: none; padding: 0 var(--s-4) var(--s-3); font-size: var(--fs-13); color: var(--text-2); }
.ctd-header .cart-dropdown-item--builder.is-expanded .cart-dropdown-review { display: block; }
.ctd-header .cart-dropdown-expand-hint { font-size: var(--fs-12); color: var(--text-3); text-transform: uppercase; letter-spacing: var(--track-caps); }
.ctd-header .cdr-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: var(--s-1); }
.ctd-header .cdr-chip { display: inline-flex; align-items: center; height: 24px; padding: 0 var(--s-2); border: var(--bw) solid var(--line-2); border-radius: var(--r-1); background: var(--ink-900); font-size: var(--fs-12); color: var(--text); }
.ctd-header .cart-dropdown-footer { padding: var(--s-4); display: flex; flex-direction: column; gap: var(--s-3); border-top: var(--bw) solid var(--line-1); }
.ctd-header .cart-dropdown-footer .cart-total-row { display: flex; justify-content: space-between; font-weight: 700; font-variant-numeric: tabular-nums; }
.ctd-header .cart-dropdown-actions { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-2); }

/* ---------- phone drawer ---------- */
.ctd-drawer { position: fixed; inset: 0; z-index: var(--z-dialog); background: var(--scrim); display: flex; justify-content: flex-end; }
.ctd-drawer[hidden] { display: none; }
.ctd-drawer__panel {
  width: min(360px, 100%); height: 100%; overflow-y: auto; background: var(--ink-900);
  border-left: var(--bw) solid var(--line-2); display: flex; flex-direction: column;
  padding-bottom: env(safe-area-inset-bottom);
}
.ctd-drawer__head { display: flex; align-items: center; justify-content: space-between; height: var(--header-h); padding: 0 var(--s-2) 0 var(--s-4); border-bottom: var(--bw) solid var(--line-1); }
.ctd-drawer__title { font-size: var(--fs-18); font-weight: 500; letter-spacing: 0.08em; }
.ctd-drawer__search { padding: var(--s-4); border-bottom: var(--bw) solid var(--line-1); }
.ctd-drawer__search input { flex: 1; min-width: 0; background: none; border: 0; outline: none; color: var(--text); font: inherit; }
.ctd-drawer__nav { display: flex; flex-direction: column; }
.ctd-drawer__foot { margin-top: auto; padding: var(--s-4); display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); border-top: var(--bw) solid var(--line-1); }
body.ctd-dialog-open { overflow: hidden; }

/* ---------- overlay + dialog shell (problem report and every shared dialog) ---------- */
.ctd-overlay { position: fixed; inset: 0; z-index: var(--z-dialog); background: var(--scrim); display: flex; align-items: center; justify-content: center; padding: var(--s-4); overflow-y: auto; }
.ctd-overlay[hidden] { display: none; }
.ctd-overlay .ctd-dialog { max-height: calc(100vh - 2 * var(--s-4)); overflow-y: auto; } /* lint-allow: vh — dialog cap, not a page lock */
.ctd-dialog textarea.ctd-input { min-height: 120px; padding: var(--s-3); resize: vertical; align-items: flex-start; }
.problem-report-form__status[hidden] { display: none; }
.problem-report-form__status--error { border-color: var(--err); background: var(--err-bg); }
.problem-report-form__status--success { border-color: var(--ok); background: var(--ok-bg); }
.ctd-btn--loading { opacity: 0.6; pointer-events: none; }

/* ---------- mini-cart badge pulse ----------
   The .cart-notification toast this block used to skin is gone: Phase 4 folded
   js/cart-dropdown.js onto js/ctd-ui.js's shared toast, so there is now exactly
   one toast implementation in the theme. The badge states stay. */
.ctd-header .cart-count.cart-updated { border: var(--bw) solid var(--bone-100); }
.ctd-header .cart-count.updating { opacity: 0.5; }

/* ---------- toasts ----------
   One host, one toast. A sticky notice (builder-v2 ui/dom.js) is a .ctd-toast
   that does not auto-dismiss; it stacks in this same column, which is why the
   builder no longer computes `bottom: 100px + 68px * index` for itself.
   On an app page the sticky action bar owns the bottom of the viewport, so the
   host clears it. */
.ctd-toasts { position: fixed; left: 50%; bottom: calc(var(--s-6) + env(safe-area-inset-bottom)); transform: translateX(-50%); z-index: var(--z-toast); display: flex; flex-direction: column; align-items: center; gap: var(--s-2); pointer-events: none; width: min(92vw, 480px); }
.ctd-toasts .ctd-toast { pointer-events: auto; cursor: pointer; }
.ctd-toast--sticky { cursor: default; align-items: flex-start; text-align: left; max-width: 100%; }
.ctd-toast__msg { flex: 1; font-weight: 400; line-height: var(--lh-body); }
.ctd-toast__action { color: var(--ink-950); font-weight: 700; text-decoration: underline; white-space: nowrap; }
.ctd-toast__close { display: inline-flex; align-items: center; color: var(--ink-950); flex: none; }
body.ctd-app-page .ctd-toasts { bottom: calc(var(--s-6) + env(safe-area-inset-bottom)); }
@media (max-width: 768px) {
  body.ctd-app-page .ctd-toasts { bottom: calc(var(--actionbar-h) + var(--s-3) + env(safe-area-inset-bottom)); }
}

/* ---------- back to top ---------- */
.back-to-top { position: fixed; right: var(--s-6); bottom: calc(var(--s-6) + env(safe-area-inset-bottom)); z-index: var(--z-sticky); background: var(--surface); border-color: var(--line-2); color: var(--text-2); box-shadow: var(--shadow-2); }
.back-to-top[hidden] { display: none; }
.back-to-top:hover { color: var(--text); border-color: var(--bone-500); }
.ctd-i--up { transform: rotate(90deg); }

/* ---------- footer ---------- */
.ctd-footer__btn { color: var(--text-2); font: inherit; text-align: left; }
.ctd-footer__btn:hover { color: var(--text); }
.ctd-footer a:hover, .ctd-footer__btn:hover { text-decoration: underline; text-underline-offset: 0.15em; }

/* ---------- tab bar: phones only ---------- */
.ctd-tabbar { display: none; }

/* ---------- breakpoints ----------
   MEASURED, not guessed. The row's three groups are all flex:none, so the
   rack cannot silently squeeze to fit and an overflow reports as one: badge
   297 + rack 740 + controls 196 + two 16px gaps = 1265px of content, and the
   wrap wants 16px of padding either side, so the bar needs a 1297px viewport
   before it has any air at all. At the old 1280 breakpoint the first width
   that showed the rack was 1281, where the account button's right edge landed
   exactly ON the viewport edge with zero page padding and the badge sat 16px
   off the first plate.
   1320 gives the row about 24px of slack at the first width it appears. Below
   that the drawer takes over. (It was 1000 before the rack existed; six
   bolted plates need far more room than six text links did.) */
@media (max-width: 1320px) {
  .ctd-nav { display: none; }
  .ctd-header__menu { display: inline-flex; }
  .ctd-header__inner { gap: var(--s-3); }

  /* ---------- and the badge centres on the screen ----------
     Owner, 2026-09-08: *"on mobile. ensure the logo is in the center of the
     screen horizontally."* It was not, and `justify-content: space-between`
     is why: the row's two ends are different weights — one button on the left
     against two on a phone or three on a tablet — so space-between puts the
     badge in the middle of what is LEFT, which measured 24px off centre at
     640 and below and 68px off at 700-1320. Constant, at every width, which is
     the tell that it is the ends and not the badge.

     THERE IS NO WAY TO CENTRE THE MIDDLE ITEM OF AN UNEVEN FLEX ROW IN FLOW.
     `flex: 1` on both ends only equalises them while both fit inside their
     share; the heavier one is floored at its own min-content, so at the widths
     that matter it wins the extra space back and the badge drifts again. Grid
     does the same thing for the same reason (`1fr` is `minmax(auto, 1fr)`).
     So the badge comes out of the flow, and the row is menu + controls.

     `left/right: 0` + `margin-inline: auto` rather than a translate: it
     centres on the header's padding box, which IS the screen down here, and
     it puts nothing in the scrollable overflow area — the caution the frame's
     own girders are written to. `width: max-content` because an absolutely
     positioned flex container would otherwise shrink-to-fit its containing
     block rather than its contents.

     TOP, NOT CENTRE. The bar is sticky at the top of the viewport, so the
     badge's growth all has to go DOWNWARD or it is clipped off the screen on
     scroll — the same argument `align-self: flex-start` made while it was in
     the flow, and `.ctd-header`'s margin-bottom still reserves the overhang.

     The two ends are lifted above it as a SAFETY NET, not as layout. The badge
     is capped (--nav-brand-fit, tokens.css) so it cannot reach a control at
     any width the cap knows about — but the version chip beside them only
     renders for admins and is not in that sum, so if the two ever did meet,
     the button has to be the one that stays clickable. */
  .ctd-header .ctd-brand {
    position: absolute; top: 0; left: 0; right: 0;
    margin-inline: auto; width: max-content;
  }
  .ctd-header__menu, .ctd-header__actions { z-index: 4; }
}
@media (max-width: 640px) {
  .ctd-header__version, #searchToggle, #headerSearch { display: none; }
  /* The badge costs 2.49px of row for every px of height, so on a phone the
     row is what caps it, exactly as it is on a wide screen — only here the
     hardware is what has to give. The buttons carry no bolts, so a square
     plate has nothing for its chamfer to collide with and the three controls
     come back to 40px each; that buys the badge its 82px. */
  .ctd-header .ctd-header__inner { gap: var(--s-2); }
  .ctd-header__actions { gap: var(--s-2); }
  .ctd-header__inner .ctd-iconbtn { width: var(--nav-plate-h); }
  .ctd-tabbar { display: grid; }
  .back-to-top { bottom: calc(var(--tabbar-h) + var(--s-4) + env(safe-area-inset-bottom)); right: var(--s-4); }
  body.ctd-app-page .ctd-tabbar { display: none; }
  .ctd-drawer__foot { grid-template-columns: 1fr; }
}
