/* ============================================================
   Chewtoy's Dugout — components · "Night Pitch" (2026-09)
   Requires tokens.css. Prefix: .ctd-  (never collides with the
   legacy tree, so both can coexist during migration). Global element
   rules live in base.css, which loads only on migrated pages.
   ============================================================ */

/* ---------- Type ---------- */
.ctd-display, .ctd-h1, .ctd-h2, .ctd-h3 {
  font-family: var(--font-display); text-transform: uppercase;
  letter-spacing: var(--track-display); line-height: var(--lh-tight);
}
.ctd-h1 { font-size: clamp(2.25rem, 4.5vw, var(--fs-56)); font-weight: 700; }
/* The badge is TWO colours, and both of them are on its letters: a cream
   crown over a gold face. So the second line of a display heading is gold,
   not the violet it was until 2026-09-07 — on a purple ground the violet
   was the one light in the room that the logo does not contain. The accent
   ROLE is untouched: --accent-2 is still the violet, and still what a focus
   ring, an info rule and the 404 numeral are drawn in. */
.ctd-h1 .alt, .ctd-h2 .alt { color: var(--accent); }

/* ---------- The varsity voice ----------
   One line per page — the page's own title — is set in the face the tags and
   nameplates are printed in, so the site opens in the product's handwriting.
   It stops there: h2/h3, nav, buttons, eyebrows and prices stay Oswald, which
   is what keeps the hierarchy readable (and Varsity unreadable below ~20px).
   These page-title classes are the h1 of a migrated template — they wear
   .ctd-h2 for their SIZE, so this cannot be folded into .ctd-h1. The rest of
   the set lives where its own rule does, because a page stylesheet loads after
   this one and would otherwise win the family back: woo.css (basket, checkout,
   order received, account, login) and product.css (the team-set page).
   Varsity's sidebearings are wider than Oswald's, so it needs less tracking
   than --track-display to sit right. */
.ctd-h1,
.ctd-doc__title,
.ctd-shop__title,
.ctd-hub__title {
  font-family: var(--font-varsity);
  letter-spacing: 0.005em;
}
.ctd-h2 { font-size: var(--fs-32); font-weight: 500; letter-spacing: 0.02em; }
.ctd-h3 { font-size: var(--fs-20); font-weight: 500; letter-spacing: 0.03em; line-height: var(--lh-snug); }
.ctd-h4 { font-size: var(--fs-16); font-weight: 700; }
.ctd-eyebrow { font-size: var(--fs-12); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--text-3); font-weight: 600; }
.ctd-lede { font-size: var(--fs-18); color: var(--text-2); max-width: var(--measure); text-wrap: pretty; }
.ctd-prose { max-width: var(--measure); font-size: var(--fs-17); color: var(--text-2); text-wrap: pretty; }
.ctd-prose p + p { margin-top: var(--s-4); }
.ctd-prose strong { color: var(--text); }
.ctd-muted { color: var(--text-3); }
.ctd-small { font-size: var(--fs-14); }
.ctd-xs { font-size: var(--fs-13); }
.ctd-num { font-variant-numeric: tabular-nums; }
.ctd-strong { color: var(--text); font-weight: 700; }

/* ---------- Layout ---------- */
.ctd-wrap { width: min(100% - 2 * var(--s-6), var(--wrap)); margin-inline: auto; }
.ctd-wrap--narrow { width: min(100% - 2 * var(--s-6), var(--wrap-narrow)); margin-inline: auto; }
.ctd-section { padding-block: var(--s-16); }
.ctd-section--tight { padding-block: var(--s-10); }
/* Headed by a nameplate (.ctd-plate, ornament.css) hung from the steel cross
   member on the section's top edge: the plate owns the top spacing, so the
   section gives up its own. Doubled so it beats a page stylesheet's
   padding-block on the same element regardless of load order. */
.ctd-section.ctd-section--plated { padding-top: 0; }
/* Its ground is on a PSEUDO-ELEMENT rather than on the section, and that is
   the whole point of the rewrite: a background on the section is the width of
   the page and cannot be told to stop at the steel frame. Tint, touchline and
   border now belong to the bay instead (2026-09-05, owner: the card grounds
   "currently stretch the whole screen"), so what shows either side of the
   frame is the pitch paving the page stands on. --bay-gutter is 0 below the
   girder gate, so this is the old full-bleed section at every width where
   there is no frame to line.

   The ground sits at z-index 0 and the section's own children are lifted above
   it — the arrangement .ctd-section--wood already needed for .ctd-planks, so
   the two share one rule below. A negative z-index on the ground instead would
   drop it behind body's background and it would never be seen at all.

   The border moved with it, so the section is --bw shorter top and bottom than
   it was. Both edges carry a cross member centred on them, which is what makes
   that free. */
.ctd-section--alt { position: relative; }
.ctd-section--alt::before {
  content: ""; position: absolute; inset: 0;
  left: var(--bay-gutter); right: var(--bay-gutter);
  z-index: 0; pointer-events: none;
  background: var(--fx-turf), var(--bg-alt);
  border-block: var(--bw) solid var(--line-1);
  box-shadow: var(--fx-touchline);
}
/* Boarded: the section is lined with timber instead of tinted. Compose it with
   --alt (which supplies the border and the chalk touchline) or use it alone.
   The layer itself is ctd_planks() in the markup and .ctd-planks in
   ornament.css; all this does is make room for it.

   THE CONTENT TAKES THE Z-INDEX, not the layer. Giving .ctd-planks a negative
   one would drop it behind the section's own background and it would never be
   seen; lifting the content is the only arrangement that works whether or not
   the section is painted. `> *` rather than a class, because every child of a
   section is content — the boarding is the exception and it is excluded here
   by name. */
.ctd-section--wood { position: relative; }
.ctd-section--alt > :not(.ctd-planks):not(.ctd-turf),
.ctd-section--wood > :not(.ctd-planks),
.ctd-section--pitch > :not(.ctd-turf) { position: relative; z-index: 1; }
/* The turf weave is the PAGE's ground showing through an alt section, and
   boarding is opaque timber laid over the top of it: leaving both on paints a
   diagonal thread grid across the planks, which is two materials in one box. */
.ctd-section--alt.ctd-section--wood::before { background: var(--bg-alt); }
/* Turfed: the section is laid with grass rather than tinted or boarded. The
   third material band — and the one that must NOT be composed with --alt,
   which is where it differs from --wood. Timber is laid OVER a tint and hides
   it; grass is the surface itself, so anything the section paints underneath
   comes through the sod and tints the pitch. It shipped as --alt + --pitch for
   an afternoon and the whole bay read as a card-coloured wash. Use it alone.
   The bay border and chalk touchline --alt would have brought are no loss: a
   steel cross member closes this bay top and bottom at every width.

   THE LAYER IS CONFINED TO THE BAY, which is the one thing this modifier has
   to say that --wood does not. .ctd-planks is emitted inside a section whose
   ::before already stops at the steel; .ctd-turf is inset:0 on the section
   itself, so without this the grass runs out under the frame and past it.

   .ctd-turf is also excluded from the --alt lift above, for the reason
   .ctd-planks is — and the trap is sharper here. That selector is (0,2,0)
   against .ctd-turf’s own (0,1,0), so it wins outright and turns an inset:0
   layer into a relative block in the flow. It does not degrade; it collapses. */
.ctd-section--pitch { position: relative; }
.ctd-section--pitch > .ctd-turf {
  left: var(--bay-gutter); right: var(--bay-gutter);
  /* FULL STRENGTH, and the same argument the gutters make: nothing reads over
     this layer, so there is nothing for a lowered amplitude to protect. The
     generator lays an opaque sod rect across the whole box before it draws a
     single blade, so at 1 the grass IS the ground and nothing behind it can
     show through — which is the point. At --turf-amp (0.88) whatever the
     section painted underneath came through the sod and tinted the pitch;
     composed with --alt that was a solid --bg-alt wash the colour of a card. */
  opacity: 1;
}
.ctd-stack { display: flex; flex-direction: column; gap: var(--s-4); }
.ctd-stack--sm { gap: var(--s-2); }
.ctd-stack--lg { gap: var(--s-8); }
.ctd-row { display: flex; align-items: center; gap: var(--s-4); }
.ctd-row--sm { gap: var(--s-2); }
.ctd-row--wrap { flex-wrap: wrap; }
.ctd-between { justify-content: space-between; }
.ctd-grid { display: grid; gap: var(--s-6); }
.ctd-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.ctd-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.ctd-grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.ctd-grid--8 { grid-template-columns: repeat(8, minmax(0, 1fr)); }
.ctd-grid--tight { gap: var(--s-3); }

/* ---------- Icons (inline SVG sprite, stroke-based, 24 grid) ---------- */
.ctd-i { width: 20px; height: 20px; stroke: currentColor; fill: none; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; flex: none; }
.ctd-i--sm { width: 16px; height: 16px; }
.ctd-i--lg { width: 24px; height: 24px; }
.ctd-i--xl { width: 32px; height: 32px; stroke-width: 1.5; }

/* ---------- Notice bar (store paused, announcements) ---------- */
.ctd-notice {
  display: flex; justify-content: center; align-items: center; gap: var(--s-2);
  padding: var(--s-2) var(--s-4); font-size: var(--fs-14); text-align: center;
  background: var(--warn-bg); color: var(--text); border-bottom: var(--bw) solid var(--line-1);
}
.ctd-notice strong { color: var(--warn); }
.ctd-notice .ctd-i { color: var(--warn); }

/* ---------- Header ---------- */
.ctd-header {
  position: sticky; top: 0; z-index: var(--z-header);
  background: var(--ink-950); border-bottom: var(--bw) solid var(--line-1); box-shadow: var(--fx-rim), var(--shadow-1);
}
.ctd-header__inner { height: var(--header-h); display: flex; align-items: center; justify-content: space-between; gap: var(--s-6); }
.ctd-brand { display: flex; align-items: center; gap: var(--s-3); color: var(--text); text-decoration: none; }
.ctd-brand svg { height: 40px; width: auto; }
.ctd-nav { display: flex; align-items: center; gap: var(--s-1); }
.ctd-nav__link {
  display: inline-flex; align-items: center; gap: 6px; min-height: var(--control-h); padding: 0 var(--s-3);
  border-radius: var(--r-1); font-family: var(--font-display); font-weight: 500; text-transform: uppercase;
  letter-spacing: 0.06em; font-size: var(--fs-14); color: var(--text-2); text-decoration: none;
}
.ctd-nav__link { position: relative; }
.ctd-nav__link::after {
  content: ""; position: absolute; left: var(--s-3); right: var(--s-3); bottom: 6px; height: 2px;
  background: var(--accent); transform: scaleX(0); transform-origin: center;
  transition: transform var(--t-base) var(--ease);
}
.ctd-nav__link:hover { color: var(--text); background: var(--surface); }
.ctd-nav__link:hover::after { transform: scaleX(1); }
.ctd-nav__link--active { color: var(--text); box-shadow: inset 0 -2px 0 var(--accent); border-radius: 0; }
.ctd-nav__link--active::after { content: none; }
.ctd-header__actions { display: flex; align-items: center; gap: var(--s-1); }
.ctd-iconbtn {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: var(--control-h); height: var(--control-h); border-radius: var(--r-1);
  color: var(--text-2); border: var(--bw) solid transparent;
}
.ctd-iconbtn:hover { color: var(--text); border-color: var(--line-1); }
.ctd-count {
  position: absolute; top: 2px; right: 0; min-width: 20px; height: 20px; padding: 0 5px;
  border-radius: var(--r-pill); background: var(--accent); color: var(--on-gold);
  font-size: var(--fs-12); font-weight: 700; display: flex; align-items: center; justify-content: center;
}

/* ---------- Buttons: the casting ----------
   One cast piece — THE CASTING in tokens.css says what it is and why. Two
   pseudo-elements, both clipped to the same silhouette: ::before is the RIM
   (the bevel — one diagonal gradient, lit top-left, dark bottom-right) and
   ::after is the FACE, inset by the bevel's depth and carrying the metal. The
   sheen is the face's first background layer, raked across on hover by a
   background-position change, so no template that emits a .ctd-btn has to
   change and no third element is needed. The label sits on top, engraved.

   The element itself is NOT clipped: it carries the focus outline and, on a
   header control, the count stud, and both want the full box.

   A bare .ctd-btn and a --secondary are the same gunmetal casting. It used to
   be that a bare one was "the flat control" — but the owner asked for these
   across the site, and a flat rectangle two inches from a cast one is the
   exact thing the 2026-09-07 round was removing. */
.ctd-btn {
  --btn-c: var(--btn-cut); --btn-b: var(--btn-bevel);
  --btn-ci: calc(var(--btn-c) - var(--btn-b) * 0.41);   /* the face's cut, one bevel in */
  --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: var(--s-2);
  min-height: var(--btn-h); padding: 0 var(--s-6);
  border: 0; border-radius: 0; background: none; cursor: pointer; white-space: nowrap; text-decoration: none;
  font-family: var(--font-display); font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  font-size: var(--fs-16); line-height: 1;
  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-btn::before, .ctd-btn::after { content: ""; position: absolute; pointer-events: none; }
.ctd-btn::before { inset: 0; z-index: -2; clip-path: var(--btn-clip); background: var(--btn-rim); }
.ctd-btn::after {
  inset: var(--btn-b); z-index: -1; clip-path: var(--btn-clip-in);
  background-image: var(--fx-sheen), var(--btn-face);
  background-size: 60% 100%, 100% 100%; background-repeat: no-repeat;
  background-position: -120% 0, 0 0;
  transition: filter var(--t-fast) var(--ease), background-position 520ms var(--ease);
}
.ctd-btn--primary {
  --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);
}
/* --secondary IS the base casting, restated so the class has a rule of its
   own: gate:builder-css requires one for every class the builder emits, and a
   template reading `.ctd-btn--secondary` should find the metal it names. */
.ctd-btn--secondary {
  --btn-rim: var(--fx-btn-rim-gun); --btn-rim-press: var(--fx-btn-rim-gun-press);
  --btn-face: var(--fx-btn-face-gun);
}
.ctd-btn--inlay {
  --btn-rim: var(--fx-btn-rim-gold); --btn-rim-press: var(--fx-btn-rim-gold-press);
  --btn-face: var(--fx-btn-face-inlay);
  color: var(--gold-300); text-shadow: var(--fx-btn-engrave-lit);
}
.ctd-btn--danger { color: var(--err); text-shadow: var(--fx-btn-engrave-plain); }
/* the pennant: flat top, deep bottom corners, hard drop plate */
.ctd-btn--pennant {
  --btn-c: var(--btn-cut-pen); --btn-b: var(--btn-bevel-sm);
  --btn-clip: polygon(0 0, 100% 0, 100% calc(100% - var(--btn-c)), calc(100% - var(--btn-c)) 100%,
                      var(--btn-c) 100%, 0 calc(100% - var(--btn-c)));
  --btn-clip-in: polygon(0 0, 100% 0, 100% calc(100% - var(--btn-ci)), calc(100% - var(--btn-ci)) 100%,
                         var(--btn-ci) 100%, 0 calc(100% - var(--btn-ci)));
  filter: var(--fx-btn-plate);
}
/* GHOST: the letters, and a gold rule that draws in under them on hover.
   No rim, no face — ::after is repurposed as the rule. */
.ctd-btn--ghost { color: var(--text-2); text-shadow: var(--fx-btn-engrave-plain); filter: none; }
.ctd-btn--ghost::before { display: none; }
.ctd-btn--ghost::after {
  inset: auto var(--s-6) 8px; height: 2px; clip-path: none;
  background: var(--gold-500); background-image: none;
  transform: scaleX(0); transform-origin: left;
  transition: transform var(--t-base) var(--ease);
}
.ctd-btn--ghost:hover { color: var(--text); }
.ctd-btn--ghost:hover::after { transform: scaleX(1); }

/* ---- states ----
   Hover is the lamp finding the casting: the face brightens, the sheen rakes,
   a gunmetal rim warms to gold so the secondary answers too. Pressing seats
   it — 2px down, the cast shadow gone, and the bevel's light REVERSED. Focus
   is a ring, not a glow: it has to be unmistakable on a gold face. */
.ctd-btn:hover { color: var(--btn-hot-ink); }
.ctd-btn:hover::before { background: var(--fx-btn-rim-gold); }
.ctd-btn:hover::after { filter: brightness(1.22); background-position: 220% 0, 0 0; }
.ctd-btn--primary:hover { color: var(--on-gold); filter: var(--fx-btn-cast-hot); }
.ctd-btn--primary:hover::before { background: var(--btn-rim); }
.ctd-btn--primary:hover::after { filter: brightness(1.08); }
.ctd-btn--inlay:hover { color: var(--btn-hot-ink); text-shadow: var(--fx-btn-engrave-hot); }
.ctd-btn--inlay:hover::before { background: var(--btn-rim); }
.ctd-btn--inlay:hover::after { filter: brightness(1.18); }
.ctd-btn--pennant.ctd-btn--primary:hover { filter: var(--fx-btn-plate-hot); }
.ctd-btn--pennant:hover { filter: var(--fx-btn-plate); }
.ctd-btn--danger:hover { color: var(--err); }
.ctd-btn:active { transform: translateY(2px); filter: var(--fx-btn-cast-press); }
.ctd-btn:active::before { background: var(--btn-rim-press); }
.ctd-btn:active::after { filter: brightness(0.9); }
.ctd-btn--pennant:active { transform: translate(2px, 2px); filter: var(--fx-btn-plate-press); }
.ctd-btn--ghost:active { transform: none; filter: none; }
.ctd-btn:focus-visible { outline: 2px solid var(--gold-300); outline-offset: 4px; }

/* ---- sizes ----
   The cut and the bevel step with the height, or a small casting is a big one
   photographed from further away. */
.ctd-btn--sm { --btn-c: var(--btn-cut-sm); --btn-b: var(--btn-bevel-sm); min-height: var(--btn-h-sm); padding: 0 var(--s-4); font-size: var(--fs-13); letter-spacing: 0.08em; }
.ctd-btn--sm.ctd-btn--pennant { --btn-c: var(--btn-cut-pen-sm); filter: var(--fx-btn-plate-sm); }
.ctd-btn--lg { --btn-c: var(--btn-cut-lg); min-height: var(--btn-h-lg); padding: 0 var(--s-8); font-size: var(--fs-18); }
.ctd-btn--block { width: 100%; }
.ctd-btn[disabled], .ctd-btn--disabled { opacity: 0.42; pointer-events: none; filter: var(--fx-btn-cast-off); }

/* ---------- Cards ---------- */
.ctd-card {
  position: relative;
  background: var(--fx-card-ground), var(--surface);
  border: var(--bw) solid var(--line-1); border-radius: var(--r-2);
  display: flex; flex-direction: column; overflow: hidden; color: var(--text); text-decoration: none;
  box-shadow: var(--fx-rim);
}
/* A gold bar wipes in along the top edge of a card you can click. */
.ctd-card--interactive {
  transition: transform var(--t-base) var(--ease), box-shadow var(--t-base) var(--ease), border-color var(--t-fast) var(--ease);
}
.ctd-card--interactive::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 2px; z-index: 2;
  background: var(--accent); transform: scaleX(0); transform-origin: left;
  transition: transform var(--t-base) var(--ease);
}
.ctd-card--interactive:hover { border-color: var(--line-2); transform: translateY(-2px); box-shadow: var(--fx-rim-strong), var(--fx-lift); }
.ctd-card--interactive:focus-visible { transform: translateY(-2px); }
.ctd-card--interactive:hover::before, .ctd-card--interactive:focus-visible::before { transform: scaleX(1); }
.ctd-card__media { background: var(--ink-900); border-bottom: var(--bw) solid var(--line-1); aspect-ratio: 4 / 3; overflow: hidden; }
.ctd-card__media--wide { aspect-ratio: 16 / 9; }
.ctd-card__media img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--t-base) var(--ease); }
.ctd-card--interactive:hover .ctd-card__media img { transform: scale(1.03); }
/* Three gouges rake the artwork on hover. Media only - never over copy. */
.ctd-card--interactive .ctd-card__media { position: relative; }
.ctd-card--interactive .ctd-card__media::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background-image: var(--fx-claw);
  mask-image: var(--fx-claw-mask); -webkit-mask-image: var(--fx-claw-mask);
  opacity: 0; transition: opacity var(--t-base) var(--ease);
}
.ctd-card--interactive:hover .ctd-card__media::after,
.ctd-card--interactive:focus-visible .ctd-card__media::after { opacity: 0.7; }
.ctd-card__body { padding: var(--s-6); display: flex; flex-direction: column; gap: var(--s-3); flex: 1; }
.ctd-card__body--sm { padding: var(--s-4); gap: var(--s-2); }
.ctd-card__foot { padding: var(--s-4) var(--s-6); border-top: var(--bw) solid var(--line-1); display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s-3); }
/* THE FOOT WRAPS, IN TWO STAGES. A foot is a note on the left and a CTA on the
   right, and the note is the only one of the pair that can shrink — a button is
   `white-space: nowrap`, so on a narrow card the note squeezed down to its
   longest word and the button carried on out past the card edge (the builders
   hub Star Player card sat 19px past it at 390px). Stage one: the row wraps, so
   the note keeps its line and the CTA drops to its own. Stage two: on a card too
   narrow to hold even one line of label — a 320px phone leaves 207px inside the
   foot and that label is 258px — the label itself wraps, which is what the
   padding-block and the centring are for. Both stages only ever fire in the case
   that used to overflow: the wrap decision is made on max-content sizes, so a
   pair that fits stays on one row, one line, exactly as before. */
.ctd-card__foot .ctd-btn { white-space: normal; text-align: center; padding-block: var(--s-2); }
.ctd-card--row { flex-direction: row; align-items: stretch; }
.ctd-card--row .ctd-card__media { width: 40%; aspect-ratio: auto; border-bottom: 0; border-right: var(--bw) solid var(--line-1); }


/* ---------- the poster ----------
   A card that is a sheet of paper nailed to the boarding. js/ctd-poster.js
   builds the svg to the element's OWN box — no pattern, no period, a different
   sheet every time — and hands back --poster-pad-* for the inset the copy has
   to keep to, because a curled corner eats up to 52px and a nail keeps its own
   clearance for the rust it has bled.

   PROGRESSIVE ENHANCEMENT. Everything under `.is-poster` is what the module
   turns on once there is actually a sheet behind the copy. Without the module
   the element is still whatever card it was — ground, border and all — which
   is the ctd-reveal.js rule: nothing is hidden by CSS alone, and this
   generator has no static tile to fall back to the way the four surfaces do
   (a poster is an OBJECT; a tile of them would repeat, and a distinctive mark
   inside a repeating tile is how an eye finds the period). */
.ctd-poster {
  position: relative;
  --poster-pad-x: 30px; --poster-pad-t: 30px; --poster-pad-b: 30px;
}
.ctd-poster > svg {
  position: absolute; inset: 0; z-index: 0;
  display: block; width: 100%; height: 100%; max-width: none;
  pointer-events: none;
}
.ctd-poster__body {
  position: relative; z-index: 1;
  display: flex; flex-direction: column; gap: var(--s-3);
  height: 100%; flex: 1;
}
.ctd-poster.is-poster > .ctd-poster__body {
  padding: var(--poster-pad-t) var(--poster-pad-x) var(--poster-pad-b);
  /* The sheet is drawn rotated about the middle of its box; this turns the
     copy through the same angle about the same point, so the print sits on the
     paper rather than beside it. js/ctd-poster.js sets the angle. */
  transform: rotate(var(--poster-tilt, 0deg));
}
@media (prefers-reduced-motion: reduce) { .ctd-poster.is-poster > .ctd-poster__body { transform: none; } }

/* LOAD-BEARING on any light stock. base.css sets an explicit colour on h1-h4
   and on links, and an explicit colour on the element always beats a colour
   inherited from an ancestor — so a heading inside a poster comes out
   bone-on-bone and vanishes, while every unstyled span beside it inherits the
   paper's ink correctly. The poster is the first thing on this site that can
   put page copy on a LIGHT ground, which is why nothing has hit this before.
   Everything inside the body takes its colour from the body; the body takes
   its colour from the stock. */
.ctd-poster__body :is(h1, h2, h3, h4, h5, h6, p, a, strong, em) { color: inherit; }
/* ---------- Chips, pills, steps, segmented ---------- */
.ctd-chip {
  display: inline-flex; align-items: center; gap: 6px; height: 24px; padding: 0 8px;
  border: var(--bw) solid var(--line-2); border-radius: var(--r-1); background: var(--ink-900);
  font-family: var(--font-display); font-size: var(--fs-13); font-weight: 500; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.ctd-chip--gold { border-color: var(--accent); color: var(--gold-300); }
.ctd-chip--muted { color: var(--text-3); border-style: dashed; background: transparent; }
.ctd-chip--solid { background: var(--bone-100); color: var(--ink-950); border-color: var(--bone-100); }
.ctd-chip__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--cat, var(--bone-500)); flex: none; box-shadow: 0 0 7px color-mix(in srgb, var(--cat, var(--bone-500)) 70%, transparent); }
.ctd-chip--add { border-style: dashed; color: var(--text-3); background: transparent; cursor: pointer; height: 32px; padding: 0 10px; }
.cat-g { --cat: var(--cat-g); } .cat-a { --cat: var(--cat-a); } .cat-s { --cat: var(--cat-s); }
.cat-p { --cat: var(--cat-p); } .cat-m { --cat: var(--cat-m); } .cat-d { --cat: var(--cat-d); } .cat-t { --cat: var(--cat-t); }
.cat-c { --cat: var(--cat-c); }
/* Full-word aliases. js/builder-v2/ui/skill-display.js's getCategoryClassName()
   and the star filter bar in inc/builder-app-partial.php both emit the whole
   word, so without these every category dot in the builder falls back to
   var(--bone-500) — one grey for all of them — and body.cb-mode, which
   redefines only --cat-*, does nothing at all on that page. "Extraordinary" has
   no hue of its own and rides with Trait; "all" is the neutral filter and is
   deliberately left with the fallback. "Characteristic" is the +1 stat tags
   (2026-09-16) and does have its own. */
.cat-general { --cat: var(--cat-g); } .cat-agility { --cat: var(--cat-a); }
.cat-strength { --cat: var(--cat-s); } .cat-passing { --cat: var(--cat-p); }
.cat-mutation { --cat: var(--cat-m); } .cat-devious { --cat: var(--cat-d); }
.cat-characteristic { --cat: var(--cat-c); }
.cat-trait, .cat-traits, .cat-extraordinary { --cat: var(--cat-t); }

.ctd-pill {
  display: inline-flex; align-items: center; gap: 6px; min-height: 36px; padding: 0 var(--s-3);
  border: var(--bw) solid var(--line-2); border-radius: var(--r-pill); background: transparent;
  color: var(--text-2); font-size: var(--fs-14); font-weight: 600; cursor: pointer; white-space: nowrap;
}
.ctd-pill:hover { border-color: var(--bone-500); color: var(--text); }
.ctd-pill--on { background: var(--bone-100); border-color: var(--bone-100); color: var(--ink-950); }

.ctd-steps { display: flex; gap: var(--s-2); align-items: center; flex-wrap: wrap; }
.ctd-step {
  display: inline-flex; align-items: center; gap: 6px; min-height: 32px; padding: 0 var(--s-3);
  border-radius: var(--r-pill); border: var(--bw) solid var(--line-1); font-size: var(--fs-13);
  font-weight: 600; color: var(--text-3); white-space: nowrap;
}
.ctd-step--done { color: var(--text-2); border-color: var(--line-2); }
.ctd-step--done .ctd-i { color: var(--ok); }
.ctd-step--on { background: var(--bone-100); color: var(--ink-950); border-color: var(--bone-100); }

.ctd-seg { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; border: var(--bw) solid var(--line-2); border-radius: var(--r-1); overflow: hidden; }
.ctd-seg__opt { padding: var(--s-3) var(--s-4); display: flex; flex-direction: column; gap: 2px; border-left: var(--bw) solid var(--line-2); cursor: pointer; color: var(--text-2); }
.ctd-seg__opt:first-child { border-left: 0; }
.ctd-seg__opt strong { color: var(--text); font-family: var(--font-display); font-weight: 500; font-size: var(--fs-18); letter-spacing: 0.02em; }
.ctd-seg__opt--on { background: var(--bone-100); color: var(--ink-950); }
.ctd-seg__opt--on strong, .ctd-seg__opt--on .ctd-eyebrow { color: var(--ink-950); }

/* ---------- Price ---------- */
.ctd-price { display: flex; align-items: baseline; gap: var(--s-2); font-family: var(--font-display); }
.ctd-price__from { font-size: var(--fs-13); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--text-3); font-weight: 500; }
.ctd-price__amount { font-size: var(--fs-28); font-weight: 700; color: var(--gold-300); font-variant-numeric: tabular-nums; line-height: 1; text-shadow: var(--fx-glow-price); }
.ctd-price__amount--sm { font-size: var(--fs-20); }
.ctd-price__meta { font-family: var(--font-body); font-size: var(--fs-14); color: var(--text-3); }

/* ---------- Table ---------- */
.ctd-table { width: 100%; border-collapse: collapse; font-size: var(--fs-14); font-variant-numeric: tabular-nums; }
.ctd-table th { text-align: left; font-size: var(--fs-12); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--text-3); font-weight: 600; padding: var(--s-2) var(--s-3); border-bottom: var(--bw) solid var(--line-2); }
.ctd-table td { padding: var(--s-3); border-bottom: var(--bw) solid var(--line-1); vertical-align: top; color: var(--text-2); }
.ctd-table td:first-child { color: var(--text); font-weight: 600; }
.ctd-table--compact td { padding: var(--s-2) var(--s-3); }
.ctd-table .num { text-align: right; }
.ctd-table tr:last-child td { border-bottom: 0; }
.ctd-table--rules tr:last-child td { border-bottom: var(--bw) solid var(--line-1); }

/* ---------- Bone rule (section divider, the one ornament) ---------- */
.ctd-rule { display: flex; align-items: center; gap: var(--s-3); color: var(--bone-500); }
.ctd-rule__line { flex: 1; height: 1px; background: var(--fx-fade-line); }
.ctd-rule__label { font-family: var(--font-display); font-size: var(--fs-16); font-weight: 500; letter-spacing: 0.14em; text-transform: uppercase; color: var(--text); white-space: nowrap; }
.ctd-rule--sm .ctd-rule__label { font-size: var(--fs-13); color: var(--text-2); }

/* ---------- Frame (hero panel with bracket corners) ---------- */
.ctd-frame { position: relative; padding: var(--s-8); border: var(--bw) solid var(--line-1); border-radius: var(--r-1); background: var(--fx-turf), var(--ink-900); box-shadow: var(--fx-rim); }
.ctd-frame__c { position: absolute; width: 22px; height: 22px; color: var(--bone-500); }
.ctd-frame__c svg { width: 100%; height: 100%; }
.ctd-frame--tight { padding: var(--s-4); }
.ctd-frame__c--tl { top: -1px; left: -1px; }
.ctd-frame__c--tr { top: -1px; right: -1px; transform: scaleX(-1); }
.ctd-frame__c--bl { bottom: -1px; left: -1px; transform: scaleY(-1); }
.ctd-frame__c--br { bottom: -1px; right: -1px; transform: scale(-1, -1); }

/* ---------- Trust bar ---------- */
.ctd-trust { display: flex; justify-content: space-between; gap: var(--s-4); flex-wrap: wrap; }
.ctd-trust__item { display: flex; align-items: center; gap: var(--s-3); color: var(--text-3); font-size: var(--fs-13); }
.ctd-trust__item strong { display: block; color: var(--text); font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em; font-weight: 500; font-size: var(--fs-14); }
.ctd-trust__item .ctd-i { color: var(--accent); }

/* ---------- Forms ---------- */
.ctd-label { font-size: var(--fs-13); font-weight: 600; color: var(--text-2); }
.ctd-input {
  display: flex; align-items: center; gap: var(--s-2); width: 100%; min-height: var(--control-h);
  padding: 0 var(--s-3); background: var(--ink-900); border: var(--bw) solid var(--line-2);
  border-radius: var(--r-1); color: var(--text); font: inherit;
  /* Milled into the panel rather than drawn on it — see --fx-channel. */
  box-shadow: var(--fx-channel);
}
.ctd-input .ph { color: var(--text-3); }
.ctd-input .ctd-i { color: var(--text-3); }
.ctd-input:focus-within { border-color: var(--focus); }
.ctd-input--sm { min-height: var(--control-h-sm); font-size: var(--fs-14); }
.ctd-input--error { border-color: var(--err); }
.ctd-help { font-size: var(--fs-13); color: var(--text-3); }
.ctd-error { font-size: var(--fs-13); color: var(--err); }
.ctd-qty { display: inline-grid; grid-template-columns: 44px 56px 44px; border: var(--bw) solid var(--line-2); border-radius: var(--r-1); overflow: hidden; }
.ctd-qty > * { display: flex; align-items: center; justify-content: center; min-height: var(--control-h); }
.ctd-qty > span { border-inline: var(--bw) solid var(--line-2); font-weight: 700; font-variant-numeric: tabular-nums; }

/* ---------- Callout / dialog / toast ---------- */
.ctd-callout { display: flex; gap: var(--s-3); padding: var(--s-4); border: var(--bw) solid var(--line-2); border-radius: var(--r-2); background: var(--ink-900); font-size: var(--fs-14); color: var(--text-2); }
.ctd-callout .ctd-i { color: var(--accent); }
.ctd-callout--warn { border-color: var(--warn); background: var(--warn-bg); }
.ctd-callout--warn .ctd-i { color: var(--warn); }
.ctd-callout--err { border-color: var(--err); background: var(--err-bg); }
.ctd-callout--err .ctd-i { color: var(--err); }
.ctd-callout strong { color: var(--text); }

.ctd-dialog { background: var(--surface); border: var(--bw) solid var(--line-2); border-radius: var(--r-2); box-shadow: var(--fx-rim-strong), var(--shadow-2); width: min(560px, 100%); display: flex; flex-direction: column; }
.ctd-dialog__head { display: flex; justify-content: space-between; align-items: center; gap: var(--s-4); padding: var(--s-4) var(--s-6); border-bottom: var(--bw) solid var(--line-1); }
.ctd-dialog__body { padding: var(--s-6); display: flex; flex-direction: column; gap: var(--s-4); }
.ctd-dialog__foot { padding: var(--s-4) var(--s-6); border-top: var(--bw) solid var(--line-1); display: flex; justify-content: flex-end; gap: var(--s-3); }

.ctd-toast { display: inline-flex; align-items: center; gap: var(--s-3); padding: var(--s-3) var(--s-4); background: var(--bone-100); color: var(--ink-950); border-radius: var(--r-1); font-weight: 600; font-size: var(--fs-14); box-shadow: var(--shadow-2); }
.ctd-toast .ctd-i { color: var(--ink-950); }
.ctd-toast--ok { box-shadow: inset 4px 0 0 var(--ok), var(--shadow-2); }
.ctd-toast--err { background: var(--err); color: var(--ink-950); }

/* ---------- WooCommerce's own notices ----------
   woocommerce_output_all_notices fires on the archive loop, before the single
   product and on the cart. Woo's stylesheets are dequeued on both trees and
   css/woocommerce.css is legacy-only, so without these the non-AJAX
   ?add-to-cart= fallback and Woo's quantity errors print as unstyled text. */
.woocommerce-notices-wrapper:empty { display: none; }
.woocommerce-message, .woocommerce-info, .woocommerce-error {
  list-style: none; margin: 0; padding: var(--s-3) var(--s-4);
  border: var(--bw) solid var(--line-2); border-left-width: 4px; border-radius: var(--r-2);
  background: var(--bg-alt); color: var(--text-2); font-size: var(--fs-14);
}
.woocommerce-message { border-left-color: var(--ok); }
.woocommerce-info { border-left-color: var(--accent-2); }
.woocommerce-error { border-left-color: var(--err); background: var(--err-bg); }
.woocommerce-message a, .woocommerce-info a, .woocommerce-error a { color: var(--link); }
.woocommerce-message .button, .woocommerce-info .button, .woocommerce-error .button {
  float: right; margin-left: var(--s-3);
  display: inline-flex; align-items: center; min-height: var(--control-h-sm); padding: 0 var(--s-4);
  border: var(--bw) solid var(--bone-300); border-radius: var(--r-1);
  color: var(--text); text-decoration: none;
  font-family: var(--font-display); font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; font-size: var(--fs-14);
}

/* ---------- Footer ---------- */
/* The footer is bolted on, not ruled off: a run of brass rivets along its
   top edge with green in every hole. Wide pitch — this is a seam closing a
   panel, not the bolt run down a girder. */
.ctd-footer { border-top: 0; background-color: var(--bg-alt); padding-block: var(--s-12) var(--s-8); box-shadow: var(--fx-seam-edge); }
.ctd-footer { background-image: var(--fx-seam-rivets); background-repeat: repeat-x; background-position: 48px 11px; background-size: 96px 22px; }
.ctd-footer__cols { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s-8); }
.ctd-footer h4 { font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 500; font-size: var(--fs-14); color: var(--text-3); margin-bottom: var(--s-3); }
.ctd-footer ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-2); }
.ctd-footer a { color: var(--text-2); text-decoration: none; }
.ctd-footer a:hover { color: var(--text); }
.ctd-footer__legal { margin-top: var(--s-8); padding-top: var(--s-4); border-top: var(--bw) solid var(--line-1); display: flex; justify-content: space-between; gap: var(--s-4); font-size: var(--fs-13); color: var(--text-3); flex-wrap: wrap; }
/* The non-affiliation line: its own full-width row under the © and links. */
.ctd-footer__legal-note { flex-basis: 100%; font-size: var(--fs-12); color: var(--text-3); }

/* ---------- Mobile tab bar ---------- */
.ctd-tabbar { position: sticky; bottom: 0; z-index: var(--z-sticky); display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); height: var(--tabbar-h); background: var(--ink-900); box-shadow: var(--fx-rim-strong); border-top: var(--bw) solid var(--line-1); padding-bottom: env(safe-area-inset-bottom); }
.ctd-tabbar__item { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; font-size: var(--fs-12); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-3); text-decoration: none; }
.ctd-tabbar__item--on { color: var(--text); }
.ctd-tabbar__item--on .ctd-i { color: var(--accent); }

/* ---------- Sticky action bar (mobile builder / product) ---------- */
.ctd-actionbar { position: sticky; bottom: 0; z-index: var(--z-sticky); display: flex; justify-content: space-between; align-items: center; gap: var(--s-3); padding: var(--s-3) var(--s-4); background: var(--ink-900); box-shadow: var(--fx-rim-strong); border-top: var(--bw) solid var(--line-1); }

/* ---------- Team tile & team card ---------- */
/* A TILE IS A STAMPED TAG, which is what the product is: a pewter plate with
   the crest seated in a turned brass bezel. The two alloys are the point —
   thirty-one brass tiles on a brass frame is the blend the buttons were just
   pulled out of, and pewter is also the one metal here that neither rusts nor
   greens, so this section can carry it thirty-one times without turning into
   weather. NO mill grain on the face: see the --fx-tag-face note. */
.ctd-team { display: flex; flex-direction: column; align-items: center; gap: var(--s-2); padding: var(--s-4); border: 0; border-radius: var(--r-2); background-image: var(--fx-tag-face); text-decoration: none; color: var(--text); text-align: center; }
.ctd-team { transition: transform var(--t-base) var(--ease), box-shadow var(--t-base) var(--ease), filter var(--t-base) var(--ease); box-shadow: var(--fx-tag-edge); }
.ctd-team:hover { transform: translateY(-2px); box-shadow: var(--fx-tag-edge-hover); filter: brightness(1.1); }
.ctd-team:active { transform: translateY(1px); filter: brightness(0.95); }
.ctd-team__crest { transition: box-shadow var(--t-base) var(--ease); }
.ctd-team:hover .ctd-team__crest { box-shadow: var(--fx-bezel-edge), var(--fx-glow-gold); }
/* The bezel is a 3px brass ring: --fx-bezel paints the turned metal on the
   border box and the disc's own fill covers everything inside it. */
.ctd-team__crest { width: 64px; height: 64px; border-radius: 50%; border: 3px solid transparent; background: linear-gradient(var(--ink-900), var(--ink-900)) padding-box, var(--fx-bezel) border-box; box-shadow: var(--fx-bezel-edge); display: flex; align-items: center; justify-content: center; color: var(--text-3); overflow: hidden; }
.ctd-team__crest img { width: 100%; height: 100%; object-fit: cover; }
.ctd-team__name { font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em; font-size: var(--fs-14); font-weight: 500; }
/* The overflow tile is not a tag — nothing is stamped on it — so it keeps
   the dashed card it always was and takes no plate. */
.ctd-team--all { background-image: none; background-color: var(--surface); border: var(--bw) dashed var(--line-2); box-shadow: var(--fx-rim); color: var(--text-2); justify-content: center; }
.ctd-team--all:hover { box-shadow: var(--fx-rim-strong), var(--fx-lift); filter: none; }

/* ---------- Summary panel & player card (builder) ---------- */
/* The offset clears the sticky facia, so it is --nav-h — see base.css's note
   on scroll-margin-top. */
.ctd-summary { position: sticky; top: calc(var(--nav-h) + var(--s-4)); }
.ctd-player { background: var(--surface); border: var(--bw) solid var(--line-1); border-radius: var(--r-2); padding: var(--s-3) var(--s-4); display: flex; flex-direction: column; gap: var(--s-2); box-shadow: var(--fx-rim); }
.ctd-player__head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); }
.ctd-player__num { width: 32px; height: 32px; border-radius: var(--r-1); background: var(--bone-100); color: var(--ink-950); display: flex; align-items: center; justify-content: center; font-family: var(--font-display); font-weight: 700; font-size: var(--fs-16); flex: none; }
.ctd-player__pos { font-family: var(--font-display); font-weight: 500; text-transform: uppercase; letter-spacing: 0.04em; font-size: var(--fs-16); }
.ctd-player__skills { display: flex; flex-wrap: wrap; gap: 6px; }

/* ---------- Bench rows ---------- */
.ctd-bench { background: var(--surface); border: var(--bw) solid var(--line-1); border-radius: var(--r-2); overflow: hidden; }
.ctd-bench__bar { display: flex; justify-content: space-between; align-items: center; gap: var(--s-3); padding: var(--s-3) var(--s-4); background: var(--ink-900); border-bottom: var(--bw) solid var(--line-1); flex-wrap: wrap; }
.ctd-bench__row { 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-bench__row:last-child { border-bottom: 0; }

/* ---------- Figures (schematics) & photo slots ---------- */
.ctd-figure { display: flex; flex-direction: column; gap: var(--s-3); }
.ctd-figure svg { width: 100%; height: auto; }
.ctd-figure figcaption { font-size: var(--fs-13); color: var(--text-3); }
.ctd-photo { position: relative; background: var(--ink-900); border: var(--bw) solid var(--line-1); border-radius: var(--r-2); overflow: hidden; }
.ctd-photo img { width: 100%; height: 100%; object-fit: cover; }
.ctd-photo--ph { display: flex; align-items: center; justify-content: center; text-align: center; padding: var(--s-4); color: var(--text-3); font-size: var(--fs-13); border-style: dashed; background: var(--fx-turf), var(--ink-900); }
.ctd-photo__cap { position: absolute; left: 0; right: 0; bottom: 0; padding: 6px 10px; font-size: var(--fs-12); letter-spacing: 0.06em; text-transform: uppercase; color: var(--bone-300); background: var(--scrim); }

/* ---------- Breadcrumb ---------- */
.ctd-crumbs { display: flex; align-items: center; gap: var(--s-2); font-size: var(--fs-13); color: var(--text-3); }
.ctd-crumbs a { color: var(--text-3); text-decoration: none; }
.ctd-crumbs a:hover { color: var(--text); }
.ctd-crumbs .ctd-i { width: 14px; height: 14px; }
.ctd-crumbs__current { color: var(--text-2); }

/* ---------- Details / accordion (FAQ) ---------- */
.ctd-details { border-bottom: var(--bw) solid var(--line-1); }
.ctd-details__q { display: flex; justify-content: space-between; align-items: center; gap: var(--s-4); padding: var(--s-4) 0; font-weight: 700; font-size: var(--fs-17); color: var(--text); cursor: pointer; }
.ctd-details__q .ctd-i { color: var(--text-3); }
.ctd-details__a { padding-bottom: var(--s-4); color: var(--text-2); max-width: var(--measure); text-wrap: pretty; }

/* ---------- Carousel chrome ---------- */
/* js/carousel.js drives anything carrying [data-ctd-carousel]; this is the
   chrome it un-hides once it has wired the track up. The track itself is a real
   scroll-snap scroller, so a carousel works — swipe, trackpad, keyboard — with
   the script absent and these controls simply never appear.

   The homepage hero still carries its own copies of these rules under
   .ctd-hero-carousel__* in home.css, with the same numbers; it predates this
   block and can adopt it next time that section is opened. Anything new uses
   these, so a second carousel does not mean a second set of dots. */
.ctd-carousel__track {
  min-width: 0; overflow-x: auto;
  scroll-snap-type: x mandatory;
  /* contain: a swipe past the last slide must not chain into the page scroll
     (or, on a trackpad, into the browser's back gesture). */
  overscroll-behavior-x: contain;
  scrollbar-width: none;              /* the dots are the affordance */
}
.ctd-carousel__track::-webkit-scrollbar { display: none; }
.ctd-carousel__track:focus-visible { outline: 2px solid var(--focus); outline-offset: var(--s-2); }
.ctd-carousel__controls { display: flex; align-items: center; gap: var(--s-2); }
.ctd-carousel__dots { display: flex; align-items: center; gap: var(--s-1); margin-inline: auto; }
/* The hit area is 32px; the mark inside it is 8px. Anything smaller than the
   former is a miss on a phone, anything bigger than the latter is a bullet. */
.ctd-carousel__dot {
  display: flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border: 0; background: none; border-radius: var(--r-1);
}
.ctd-carousel__mark {
  width: 8px; height: 8px; border-radius: var(--r-pill);
  border: var(--bw) solid var(--line-2); background: var(--surface-2);
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.ctd-carousel__dot:hover .ctd-carousel__mark { border-color: var(--bone-500); }
.ctd-carousel__dot.is-current .ctd-carousel__mark {
  background: var(--accent); border-color: var(--accent); transform: scale(1.25);
  box-shadow: var(--fx-glow-gold);
}
@media (prefers-reduced-motion: no-preference) {
  .ctd-carousel__track { scroll-behavior: smooth; }
}

/* ---------- Table of contents (doc pages) ---------- */
.ctd-toc { position: sticky; top: calc(var(--nav-h) + var(--s-6)); display: flex; flex-direction: column; gap: 2px; font-size: var(--fs-14); }
.ctd-toc a { display: block; padding: 10px 12px; color: var(--text-3); text-decoration: none; border-left: 2px solid transparent; }
.ctd-toc a:hover { color: var(--text); }
.ctd-toc a.on { color: var(--text); border-left-color: var(--accent); font-weight: 600; }

/* ---------- Responsive ---------- */
@media (max-width: 1000px) {
  .ctd-grid--3, .ctd-grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ctd-grid--8 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .ctd-footer__cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .ctd-grid--2, .ctd-grid--3, .ctd-grid--4 { grid-template-columns: 1fr; }
  .ctd-grid--8 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .ctd-section { padding-block: var(--s-10); }
  .ctd-nav { display: none; }
  .ctd-h2 { font-size: var(--fs-24); }
  .ctd-lede { font-size: var(--fs-16); }
  .ctd-card--row { flex-direction: column; }
  .ctd-card--row .ctd-card__media { width: 100%; aspect-ratio: 16 / 9; border-right: 0; border-bottom: var(--bw) solid var(--line-1); }
  .ctd-footer__cols { grid-template-columns: 1fr; }
  .ctd-summary { position: static; }
  .ctd-btn--sm, .ctd-pill, .ctd-input--sm, .ctd-chip--add { min-height: var(--control-h); }
}

/* ---------- Reduced motion ----------
   tokens.css already zeroes --t-fast/--t-base, which kills every transition.
   These four rules kill the movement itself, so a lift or a rake never
   snaps into place instead of easing. Colour and glow still apply. */
@media (prefers-reduced-motion: reduce) {
  .ctd-card--interactive:hover, .ctd-card--interactive:focus-visible, .ctd-team:hover { transform: none; }
  .ctd-card--interactive:hover .ctd-card__media img { transform: none; }
  /* The sheen stays home and the casting seats by relighting rather than by
     moving — the bevel still reverses, which is feedback, not decoration. */
  .ctd-btn::after { transition: filter var(--t-fast) var(--ease); background-position: -120% 0, 0 0; }
  .ctd-btn:active, .ctd-btn--pennant:active { transform: none; }
  .ctd-nav__link::after, .ctd-card--interactive::before { transition: none; }
}
