/* ============================================================
   Chewtoy's Dugout — a Full Squad (Standard Team Set) product page

   woocommerce/content-single-product-team-set.php and its two parts,
   woocommerce/single-product/team-set-contents.php (the box card in the
   buy column) and team-set-sheet.php (one sprue on the bench). Loads only on
   Standard team-set products (ctd_is_team_set_product()); the generic
   product page is still the legacy tree with css/single-product.css.

   THE 2026-09-28 REWORK, in four blocks:
     1 · the set    the kit render beside the buy column
     2 · the base   how a Blitz Base goes together, in the current stills,
                    and the 360 viewer in a dialog
     3 · the box    the line-up as the event's roster ticket, then every
                    sprue on one bench at one scale
     4 · what next  the Blitz Builder panel beside "Not your team?"

   components.css does the generic work (the render panel, the sheet head,
   the event panels, the kit badges). This file is the page layout plus the
   v3 skin for the markup WooCommerce and the two scripts own and query, which
   must keep its class names: .product_title, .price, .variations_form,
   .variations, .reset_variations, .single_variation, .woocommerce-variation-*,
   .quantity/.qty, .single_add_to_cart_button, .product_meta and the runtime
   .tb-qtywrap/.tb-step/.tb-live-subtotal (tb-single-variant-sync.js).

   The drawn sprues need css/v3/parts.css loaded ahead of this file
   (inc/enqueue.php): it carries the resin, the tag pigments ([data-cat]) and
   .ctd-part-scale. The line-up's tag chips read the same pigments.

   Breakpoints: 1100px (the bench becomes a strip), 1000px (one column),
   800px (the steps stack), 700px (the ticket stacks), 640px (phone).
   ============================================================ */

/* ---------- page ---------- */
.ctd-product__section { padding-block: var(--s-6) var(--s-12); }
/* woocommerce_before_single_product fires before the sections, outside any
   .ctd-wrap: give Woo's notices (added-to-basket after a non-AJAX POST, "please
   choose product options", quantity errors) the page gutter and width. */
.ctd-product .woocommerce-notices-wrapper { width: min(100% - 2 * var(--s-6), var(--wrap)); margin: var(--s-6) auto 0; display: flex; flex-direction: column; gap: var(--s-3); }
.ctd-product .woocommerce-notices-wrapper:empty { display: none; }

/* The section titles: the sheet head's varsity title, a size down from a page
   title because there are three of them. */
.ctd-product__h { font-size: clamp(2rem, 4.2vw, 3rem); }

/* ============================================================
   1 · THE SET
   ============================================================ */
/* The render takes the bigger share: it is the whole kit, and every base
   count is set on its piece. What's in the box sits under it, and the
   Blitz Builder hand-off under the buy column, so the two columns come out
   about the same height. The three-row areas let each column run on at its
   own pace: the summary spans the first two rows, the box card the last
   two. The buy column is minmax()'d so the grid can never squeeze it under
   a usable width. Below 1000px the areas go and the four stack in source
   order: picture, buy column, contents, the alternative — what it looks
   like, what it costs, what exactly is in it, what else there is. */
.ctd-product__top {
  display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(320px, 1fr);
  grid-template-areas: "media summary" "box summary" "box upsell";
  grid-template-rows: auto auto auto;
  gap: var(--s-6) var(--s-10); align-items: start;
}
.ctd-product__media { grid-area: media; min-width: 0; display: flex; flex-direction: column; gap: var(--s-3); }
.ctd-product__summary { grid-area: summary; }
.ctd-product__box-card { grid-area: box; min-width: 0; }
.ctd-product__top > .ctd-product__upsell { grid-area: upsell; }

.ctd-product__kit { padding: clamp(10px, 2vw, 22px); box-shadow: var(--fx-render-well), var(--fx-rim); }
.ctd-product__kit .ctd-kit__img { display: block; }
.ctd-product__kit img { height: auto; object-fit: contain; }
/* A team with no render: its initials on the panel, its base counts beneath. */
.ctd-product__kit--portrait { flex-direction: column; gap: var(--s-4); aspect-ratio: 16 / 10; }
.ctd-product__kit-counts { display: flex; gap: var(--s-2); }
.ctd-product__kit-counts .ctd-kit__qty { position: static; transform: none; font-size: var(--fs-16); }

.ctd-product__media-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s-2) var(--s-4); }
.ctd-product__kit-cap { margin: 0; text-align: left; flex: 1 1 280px; }
.ctd-product__spin-open { cursor: pointer; gap: 6px; height: 32px; padding: 0 12px; font: 600 var(--fs-13)/1 var(--font-display); letter-spacing: 0.08em; text-transform: uppercase; }
.ctd-product__spin-open:hover { background: color-mix(in srgb, var(--gold-500) 16%, transparent); }

/* ---------- buy column ---------- */
.ctd-product__summary { gap: var(--s-4); min-width: 0; }
.ctd-product__head { display: flex; align-items: center; gap: var(--s-3); }
.ctd-product__eyebrow { color: var(--violet-400); letter-spacing: 0.14em; }
/* The h1 is ctd_team_set_title(): the team's name in varsity, "Full Squad"
   as its gold second line (.ctd-title .alt, components.css). Scoped here
   because .product_title is also the LEGACY product page's heading. */
.ctd-product__summary .product_title { margin: 0; font-size: clamp(2.3rem, 4.4vw, 3.4rem); }
.ctd-product__summary .product_title .alt { font-size: 52%; }

/* Woo's own price line, and with one variation it is a single figure — so it
   is THE price on the page (the copy Woo prints inside .single_variation is
   hidden further down). It renders at priority 10, server-side, which is why
   it survives the store being paused and why js/tb-single-variant-sync.js can
   read it for the qty subtotal. */
.ctd-product__summary .price {
  display: flex; align-items: baseline; gap: var(--s-2); flex-wrap: wrap; margin: 0;
  font-family: var(--font-display); font-size: var(--fs-40); font-weight: 700; color: var(--gold-300);
  font-variant-numeric: tabular-nums; line-height: 1; text-shadow: var(--fx-glow-price);
}
.ctd-product__summary .price del { color: var(--text-3); font-size: var(--fs-18); font-weight: 500; }
.ctd-product__summary .price ins { text-decoration: none; }
.ctd-product__summary .price .woocommerce-Price-currencySymbol { font-size: 0.75em; }
.ctd-product__summary .woocommerce-product-details__short-description { color: var(--text-2); font-size: var(--fs-17); line-height: 1.5; max-width: var(--measure); text-wrap: pretty; }
.ctd-product__summary .woocommerce-product-details__short-description p { margin: 0; }
.ctd-product__summary .woocommerce-product-details__short-description p + p { margin-top: var(--s-2); }

/* "What's in the box": the stall card's finish, a heading on a plate, three
   figures across a ruled strip, then the diamond list. */
.ctd-product__contents {
  display: flex; flex-direction: column; gap: var(--s-4); padding: var(--s-4) var(--s-5) var(--s-5);
  border: var(--bw) solid var(--line-2); border-radius: 12px;
  background: var(--fx-event-card); box-shadow: var(--fx-event-card-shadow);
}
.ctd-product__contents-h {
  display: flex; align-items: center; gap: var(--s-2); margin: 0;
  font: 700 var(--fs-14)/1 var(--font-display); letter-spacing: 0.16em; text-transform: uppercase; color: var(--text);
}
.ctd-product__contents-h::before { content: ""; width: 9px; height: 9px; flex: none; background: var(--gold-500); transform: rotate(45deg); }
.ctd-product__spec {
  list-style: none; margin: 0; padding: var(--s-3) 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); gap: var(--s-3);
  border-block: var(--bw) solid var(--line-1);
}
.ctd-product__spec li { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ctd-product__spec li + li { padding-left: var(--s-3); border-left: var(--bw) solid var(--line-1); }
.ctd-product__spec-n {
  font-family: var(--font-display); font-weight: 700; font-size: var(--fs-32); line-height: 1;
  color: var(--gold-300); font-variant-numeric: tabular-nums;
}
.ctd-product__spec-l {
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.1em; font-weight: 600;
  font-size: var(--fs-13); color: var(--text);
}
.ctd-product__spec-s { font-size: var(--fs-13); line-height: 1.3; color: var(--text-3); }
.ctd-product__list li { font-size: var(--fs-16); }
.ctd-product__bg-note { display: flex; gap: var(--s-2); align-items: flex-start; margin: 0; }
.ctd-product__bg-note .ctd-i { color: var(--accent-2); margin-top: 2px; }

/* the "select your options" nudge (functions.php, priority 25) and the Set
   Size row are furniture for a choice that no longer exists: one option on
   the Set Size select. js/team-set-size.js picks it, tells Woo, and only then
   adds .ctd-set-armed - so with no JS, or if the option is missing, the
   native row and its nudge are still there and still work. */
.variable-product-notice { font-size: var(--fs-13); color: var(--text-3); }
.ctd-set-armed .ctd-product__summary .variable-product-notice { display: none; }

/* ---------- WooCommerce variation form (core markup, no override) ---------- */
.ctd-product__summary form.cart { display: flex; flex-direction: column; gap: var(--s-3); margin: 0; }
.ctd-product__summary .variations { width: 100%; border-collapse: collapse; }
.ctd-product__summary .variations th, .ctd-product__summary .variations td { padding: 0; border: 0; vertical-align: middle; text-align: left; }
.ctd-product__summary .variations th.label { padding-right: var(--s-3); font-size: var(--fs-13); font-weight: 600; color: var(--text-2); white-space: nowrap; }
.ctd-product__summary .variations td.value { display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap; }
.ctd-product__summary .variations select {
  color-scheme: dark; min-height: var(--control-h); padding: 0 var(--s-3); flex: 1; min-width: 160px;
  background: var(--ink-900); color: var(--text); border: var(--bw) solid var(--line-2); border-radius: var(--r-1);
  font-family: var(--font-body); font-size: var(--fs-16);
}
.ctd-product__summary .reset_variations { font-size: var(--fs-13); color: var(--text-3); }
.ctd-set-armed .ctd-product__summary .variations { display: none; }
/* Woo prints the armed variation's price inside .single_variation as well as
   the product's own at priority 10. One price on the page: the server-rendered
   one, which is also what tb-single-variant-sync.js reads for its subtotal. */
.ctd-product__summary .single_variation .price { display: none; }

.single_variation_wrap { display: flex; flex-direction: column; gap: var(--s-3); }
.single_variation:empty { display: none; }
.woocommerce-variation-description { color: var(--text-2); font-size: var(--fs-14); }
.woocommerce-variation-description p { margin: 0; }
.woocommerce-variation-availability .stock { margin: 0; font-size: var(--fs-13); color: var(--text-3); }
.woocommerce-variation-availability .out-of-stock { color: var(--err); }

/* quantity (Woo input) + the stepper tb-single-variant-sync.js wraps round it,
   and Add to Basket — one row, same height */
.woocommerce-variation-add-to-cart { display: flex; align-items: stretch; gap: var(--s-3); flex-wrap: wrap; }
.ctd-product__summary .quantity { display: flex; }
.ctd-product__summary .qty {
  width: 64px; min-height: var(--control-h-lg); text-align: center; appearance: textfield;
  background: var(--ink-900); color: var(--text); border: var(--bw) solid var(--line-2); border-radius: var(--r-1);
  font-weight: 700; font-variant-numeric: tabular-nums;
}
.ctd-product__summary .qty::-webkit-outer-spin-button, .ctd-product__summary .qty::-webkit-inner-spin-button { appearance: none; margin: 0; }
.tb-qtywrap { display: inline-grid; grid-template-columns: 44px 56px 44px; border: var(--bw) solid var(--line-2); border-radius: var(--r-1); overflow: hidden; }
.tb-qtywrap .qty { width: auto; min-height: var(--btn-h-lg); border: 0; border-inline: var(--bw) solid var(--line-2); border-radius: 0; }
.tb-step { display: flex; align-items: center; justify-content: center; min-height: var(--btn-h-lg); color: var(--text-2); font-size: var(--fs-20); }
.tb-step:hover { background: var(--surface-2); color: var(--text); }

/* Add to basket is the gold casting since 2026-10-09: inc/product-generic.php
   adds .ctd-btn .ctd-btn--primary .ctd-btn--lg to WooCommerce's own button,
   and components.css draws it. This only fits it to the row. Without that
   class (a template WooCommerce prints some other way) it keeps the flat gold
   it had, so it is never an unstyled button. */
.ctd-product__summary .single_add_to_cart_button { flex: 1; }
.ctd-product__summary .single_add_to_cart_button:not(.ctd-btn) {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  min-height: var(--btn-h-lg); padding: 0 var(--s-6); border-radius: var(--r-1);
  background: var(--accent); border: var(--bw) solid var(--accent); color: var(--on-gold);
  font-family: var(--font-display); font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; font-size: var(--fs-18);
  white-space: nowrap; cursor: pointer;
}
.ctd-product__summary .single_add_to_cart_button:not(.ctd-btn):hover { background: var(--accent-hover); border-color: var(--accent-hover); }
/* Woo disables the button until a size is armed (.wc-variation-selection-needed
   / .wc-variation-is-unavailable + disabled) */
.ctd-product__summary .single_add_to_cart_button[disabled],
.ctd-product__summary .single_add_to_cart_button.disabled { opacity: 0.45; cursor: not-allowed; }
.ctd-product__summary .single_add_to_cart_button.loading { opacity: 0.7; }
.tb-live-subtotal { flex-basis: 100%; font-size: var(--fs-14); color: var(--text-2); font-variant-numeric: tabular-nums; }
.tb-live-subtotal[hidden] { display: none; }

/* ---------- the base choice (inc/product-base-choice.php, D2) ----------
   Plain radios inside the add-to-cart form: a Full Squad's top, a tag line's
   base, a blank part's. The input is a hidden sibling of the card it draws,
   so :checked styles the card with no :has() (Firefox 115) and no JS. */
.ctd-basepick { margin: 0; padding: 0; border: 0; min-inline-size: 0; display: flex; flex-direction: column; gap: var(--s-2); }
.ctd-basepick__q { float: left; width: 100%; padding: 0; margin: 0 0 var(--s-2); font-family: var(--font-display); font-weight: 500; text-transform: uppercase; letter-spacing: var(--track-display); font-size: var(--fs-16); color: var(--text); }
.ctd-basepick__opts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-2); }
.ctd-basepick--part .ctd-basepick__opts { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.ctd-basepick__opt { position: relative; display: block; cursor: pointer; }
.ctd-basepick__input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.ctd-basepick__card {
  display: flex; flex-direction: column; gap: var(--s-2); height: 100%; padding: var(--s-3);
  background: var(--ink-900); border: var(--bw) solid var(--line-2); border-radius: var(--r-1);
}
.ctd-basepick__opt:hover .ctd-basepick__card { border-color: var(--line-1); background: var(--surface-2); }
.ctd-basepick__input:checked + .ctd-basepick__card { border-color: var(--accent); background: var(--surface-2); box-shadow: var(--fx-rim); }
.ctd-basepick__input:focus-visible + .ctd-basepick__card { outline: 2px solid var(--accent); outline-offset: 2px; }
.ctd-basepick__img { width: 100%; height: auto; border-radius: var(--r-1); }
.ctd-basepick__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ctd-basepick__kicker { font-size: var(--fs-12); text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--text-3); }
.ctd-basepick__name { font-family: var(--font-display); font-weight: 600; text-transform: uppercase; letter-spacing: var(--track-display); font-size: inherit; color: var(--text); }
.ctd-basepick__input:checked + .ctd-basepick__card .ctd-basepick__name { color: var(--accent); }
.ctd-basepick__line { font-size: var(--fs-13); line-height: 1.4; color: var(--text-2); }
.ctd-basepick__warn { font-size: var(--fs-12); line-height: 1.4; color: var(--text-3); }
.ctd-basepick__note { margin: 0; font-size: var(--fs-13); color: var(--text-3); }
@media (max-width: 480px) {
  .ctd-basepick__opts, .ctd-basepick--part .ctd-basepick__opts { grid-template-columns: minmax(0, 1fr); }
  .ctd-basepick__card { flex-direction: row; align-items: center; }
  .ctd-basepick__img { width: 96px; flex: none; }
}

/* SKU / category meta is admin furniture, not sales copy */
.ctd-product__summary .product_meta { display: none; }

/* store-paused callout (inc/store-paused.php renders .ctd-callout--warn here) */
.ctd-product__summary .ctd-callout { align-items: flex-start; }
.ctd-product__summary .ctd-callout .ctd-i { margin-top: 2px; }
.ctd-product__summary .ctd-callout a { color: var(--link); }

/* trust line */
.ctd-product__trust { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-4); }
.ctd-product__trust li { display: inline-flex; align-items: center; gap: 6px; }
.ctd-product__trust .ctd-i { color: var(--accent); }

/* The Blitz Builder hand-off: the event's "These are our defaults · Make
   your own online" panel, at the foot of the buy column because it is the
   alternative to the button above it. One column at this width. */
.ctd-product__upsell.ctd-custom { grid-template-columns: 1fr; padding: var(--s-5); }
.ctd-product__upsell .ctd-custom__h { font-size: clamp(1.3rem, 1.9vw, 1.6rem); }
.ctd-product__upsell p { font-size: var(--fs-16); }
.ctd-product__upsell .ctd-btn { justify-self: start; }

/* ============================================================
   2 · THE BASE — how it goes together
   The home page's three steps (index.php), set in this page's own words:
   each still is cropped to the part it is about, and the two with a second
   still turn to it on hover or focus.
   ============================================================ */
.ctd-product__how .ctd-sheethead .ctd-btn { justify-self: start; }
.ctd-product__steps { display: grid; gap: var(--s-6); margin: 0; padding: 0; list-style: none; }
@media (min-width: 800px) { .ctd-product__steps { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.ctd-product__step { display: flex; flex-direction: column; gap: var(--s-3); }
.ctd-product__step p { margin: 0; color: var(--text-2); line-height: 1.5; font-size: var(--fs-17); }
.ctd-product__step-img {
  position: relative; margin: 0 0 var(--s-1); aspect-ratio: 16 / 10; overflow: hidden;
  border-radius: 9px; border: var(--bw) solid var(--line-1); background: var(--panel-top);
  transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-base) var(--ease);
}
.ctd-product__step-a, .ctd-product__step-b { position: absolute; inset: 0; }
.ctd-product__step-img img { width: 100%; height: 100%; object-fit: cover; object-position: var(--pos, 50% 50%); }
.ctd-product__step-b { opacity: 0; transition: opacity var(--t-base) var(--ease); }
.ctd-product__step:hover .ctd-product__step-b,
.ctd-product__step:focus-within .ctd-product__step-b { opacity: 1; }
.ctd-product__step:hover .ctd-product__step-img { border-color: var(--line-2); box-shadow: var(--fx-rim-strong), var(--fx-lift); }
.ctd-product__step-n {
  position: absolute; left: 12px; top: 12px; z-index: 1; width: 36px; height: 36px; border-radius: 50%;
  display: grid; place-items: center; background: var(--gold-300); color: var(--on-gold);
  font: 700 var(--fs-17)/1 var(--font-display); box-shadow: var(--fx-step-n);
}
.ctd-product__step-h { margin: 0; font: 700 var(--fs-24)/1 var(--font-varsity); text-transform: uppercase; color: var(--gold-300); }
.ctd-product__how-cap { margin: 0; }

/* ============================================================
   3 · THE BOX — the line-up, then the sprues
   ============================================================ */

/* ---------- the roster ticket ----------
   The event team label's table (owner, 2026-09-26): numbers, the model, the
   count and its tags as solid blocks, a 40MM pill on a big guy and a heavy
   gold rule parting the big guys from the 32mm players. */
.ctd-ticket {
  border: var(--bw) solid var(--line-2); border-radius: 12px; overflow: hidden;
  background: var(--fx-event-card); box-shadow: var(--fx-event-card-shadow);
}
.ctd-ticket__row {
  display: grid; grid-template-columns: 72px minmax(190px, 1fr) 56px minmax(0, 2.1fr);
  gap: var(--s-4); align-items: center; padding: var(--s-3) var(--s-5);
  border-top: var(--bw) solid var(--line-1);
}
.ctd-ticket__row--head {
  border-top: 0; padding-block: var(--s-3); background: var(--fx-card-plate);
  font: 600 var(--fs-12)/1 var(--font-display); letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-3);
}
/* The heavy rule: under the last big guy, over the first 32mm player. */
.ctd-ticket__row--big + .ctd-ticket__row:not(.ctd-ticket__row--big) { border-top: 3px solid var(--gold-600); }
.ctd-ticket__row--big { background: color-mix(in srgb, var(--violet-600) 10%, transparent); }
.ctd-ticket__no b { font: 700 var(--fs-24)/1 var(--font-display); color: var(--gold-300); letter-spacing: 0.01em; white-space: nowrap; }
.ctd-ticket__who { display: flex; flex-wrap: wrap; align-items: center; gap: 4px var(--s-2); min-width: 0; }
.ctd-ticket__who b { font: 600 var(--fs-17)/1.2 var(--font-display); letter-spacing: 0.02em; color: var(--text); }
.ctd-ticket__who small { flex-basis: 100%; font-size: var(--fs-13); line-height: 1.3; color: var(--text-3); }
.ctd-ticket__qty { font: 600 var(--fs-17)/1 var(--font-display); color: var(--text-2); text-align: right; }
.ctd-ticket__row--head > * { font: inherit; letter-spacing: inherit; color: inherit; }
.ctd-ticket__list { display: flex; flex-wrap: wrap; gap: 6px; }
.ctd-ticket__none { font-size: var(--fs-14); color: var(--text-3); }
.ctd-ticket__notes { display: flex; flex-direction: column; gap: var(--s-2); margin-top: calc(-1 * var(--s-2)); }
.ctd-ticket__notes p { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }

/* A tag as a solid block in its category pigment: the same --tag-face the
   drawn tags on the sprues below resolve from [data-cat] (css/v3/parts.css),
   modelled by --fx-tag-plate exactly as the builder's HTML tags are
   (.rack-fly in css/v3/builder.css), so a chip here and a tag on the
   drawing are lit alike. The full name, where the moulding carries the
   printed code. Flat-bottomed, not a pill: nothing on a sprue is a lozenge. */
.ctd-tagchip {
  display: inline-flex; align-items: center; min-height: 26px; padding: 4px 9px 3px; border-radius: var(--r-1);
  font: 600 var(--fs-12)/1.1 var(--font-display); letter-spacing: 0.06em; text-transform: uppercase; white-space: nowrap;
}
.ctd-tagchip[data-cat] {
  background-color: var(--tag-face);
  background-image: var(--fx-tag-plate);
  box-shadow: inset 0 1px 0 var(--tag-lit), inset 0 -2px 0 var(--tag-lo);
  color: var(--tag-ink);
  text-shadow: var(--fx-tag-cast);
}
/* In the rules, not on a tag: an outline, so nobody looks for it in the box. */
.ctd-tagchip--rules { border: var(--bw) dashed var(--line-2); color: var(--text-3); }
.ctd-tagchip--key { min-height: 22px; padding: 2px 7px 1px; }

/* ---------- the bench: every sprue at one scale ----------
   Row one: the two 32mm roster frames side by side. Row two: the
   progression sprue and, beside it, the big guys' 40mm rungs side by side.
   js/team-set-sheets.js sets --part-px-32 on the bench so that the wider
   row exactly fills it (row two, when it carries Ogre Clans' six rungs);
   .ctd-part-scale (parts.css) turns that into each drawing's width, and a
   40mm frame takes 40/32 of it, as the bases do. The bench is the render
   panel (.ctd-render), so the frames lie on the same purple as the kit.

   The 0.4 fallback is only for the moment before the painter runs: it keeps
   the empty mounts from collapsing. --bench-px-max caps the scale, because a
   wide monitor would otherwise draw the frames bigger than the parts. */
.ctd-product__sub {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-1) var(--s-3); margin: var(--s-4) 0 0;
  font: 700 var(--fs-20)/1.1 var(--font-varsity); text-transform: uppercase; letter-spacing: 0.01em; color: var(--text);
}
.ctd-product__sub span { font: 600 var(--fs-13)/1.3 var(--font-display); letter-spacing: 0.1em; color: var(--text-3); }
.ctd-bench {
  --part-px-32: 0.4px;
  --part-px: var(--part-px-32);
  --bench-gap: var(--s-6);
  --bench-px-max: 0.62;
  /* The strip's floor: a phone cannot fit a roster frame at a readable size,
     so the frame is drawn a little wider than the strip and panned. */
  --bench-px-min: 0.3;
  --bench-strip: 0;
  display: flex; flex-direction: column; gap: var(--bench-gap);
  padding: clamp(16px, 2.2vw, 28px);
}
.ctd-bench:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* Both rows centre their frames. Row two ran the progression sprue to one
   edge and the big guys' frames to the other while those were a 40mm roster
   frame that nearly filled it; a rung is a sixth of that width, so Human's
   one Ogre sat at the far edge with a hole in the middle (2026-10-05). */
.ctd-bench__row { display: flex; gap: var(--bench-gap); align-items: flex-start; justify-content: center; }
/* The big guys' sprues: one 40mm rung each (2026-10-05), side by side at a
   closer gap than the bench's, so they read as one set beside the
   progression sprue. The painter sizes row two with this gap in it, so six
   rungs fit on one line; the wrap is the fallback for a bench it never
   reaches. (Until 2026-10-05 the group stacked a 40mm roster frame over the
   big guys' progression sprue, held to the frame's width by --bench-big-w,
   which the painter no longer sets.) */
.ctd-bench__big { display: flex; flex-flow: row wrap; align-items: flex-start; gap: var(--s-4); }

/* A figure is as wide as its drawing, and the caption wraps under it: the
   caption's inline-size containment keeps it out of the figure's width, so a
   caption wider than a narrow sprue cannot widen the figure and throw out
   the row the painter measured. (Not width: min-content on the figure: the
   drawing's max-width is a percentage, which makes its min-content zero.) */
.ctd-sheet { margin: 0; flex: none; min-width: 0; display: flex; flex-direction: column; gap: var(--s-2); }
.ctd-sheet__art { display: flex; justify-content: center; }
/* A 40MM FRAME IS A BIT BIGGER — the 40:32 of the bases themselves. */
.ctd-sheet--40 .ctd-sheet__art { --part-px: calc(var(--part-px-32) * 40 / 32); }
.ctd-sheet__cap {
  contain: inline-size; text-align: center; text-wrap: pretty;
  font: 600 var(--fs-12)/1.3 var(--font-display); letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-3);
}
/* The mount before the drawings land (and if they never do): a hairline plate
   rather than a collapsed nothing, so the captions are not floating. */
.ctd-sheet__art:empty { width: 240px; min-height: 72px; border: var(--bw) dashed var(--line-1); border-radius: var(--r-1); }

.ctd-bench__foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s-2) var(--s-4); }
.ctd-bench__hint { display: none; margin: 0; }
/* Tag colour = skill category. Each <li> carries data-cat, so parts.css's own
   [data-cat] rules resolve --tag-face for the swatch - one source for the
   pigment, whether it is on a drawn tag, a line-up chip or in this legend. */
.ctd-sheets__legend { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-3); }
.ctd-sheets__legend li { display: inline-flex; align-items: center; gap: 6px; }
.ctd-sheets__swatch {
  width: 10px; height: 10px; flex: none; border-radius: 2px;
  background: var(--tag-face, var(--surface-2)); border: 1px solid var(--line-1);
}

/* THE STRIP. Under 1100px the bench cannot show two roster frames side by
   side at a scale anybody can read (it would be under 0.4px a unit), so it
   becomes one swipeable row of every frame instead. The painter reads
   --bench-strip and sizes a roster frame to most of the strip's width, so
   the next one shows at the edge — but never under --bench-px-min, which on
   a phone makes a frame a little wider than the strip. Snapping is
   proximity, not mandatory, so a frame wider than the strip can be panned
   across rather than jumping from one start to the next. */
@media (max-width: 1099px) {
  .ctd-bench {
    --bench-strip: 1;
    --bench-gap: var(--s-5);
    flex-direction: row; align-items: flex-start;
    overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x proximity;
    scroll-padding-inline: clamp(16px, 2.2vw, 28px);
  }
  .ctd-bench__row, .ctd-bench__big { display: contents; }
  .ctd-sheet { scroll-snap-align: start; }
  .ctd-bench__hint { display: block; }
}

/* ============================================================
   EVERY OTHER PRODUCT (woocommerce/content-single-product.php,
   inc/product-generic.php; on v3 since 2026-10-09). The Full
   Squad's parts: a preset pack's own sprue on the bench, or the
   product's picture on the render panel, beside the same buy
   column, with the box card and the hand-off under them.
   ============================================================ */
/* No kit render to balance against, so the picture and the buy column split
   evenly, and the box card sits under the picture. */
/* Wide screens only: at (0,2,0) this would outrank the one-column rule in the
   1000px query below, which is (0,1,0) — the phone kept two columns. */
@media (min-width: 1001px) { .ctd-product--generic .ctd-product__top { grid-template-columns: minmax(0, 1fr) minmax(320px, 1fr); } }
/* One definite cell: the panel's height comes from its aspect-ratio, and an
   auto row left the img's height: 100% unresolved, so a picture with its own
   width/height ran taller than the panel and was clipped (a pack's sprue
   render lost its bottom row, 2026-10-10). */
.ctd-product__photo { aspect-ratio: 4 / 3; display: grid; grid-template: minmax(0, 1fr) / minmax(0, 1fr); place-items: center; box-shadow: var(--fx-render-well), var(--fx-rim); }
.ctd-product__photo img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ctd-product__photo--contain img { object-fit: contain; padding: 6%; filter: var(--fx-render-lift); }
.ctd-product__photo--badge img { width: 58%; height: auto; object-fit: contain; }
.ctd-product__thumbs { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s-2); }
.ctd-product__thumbs a { display: block; aspect-ratio: 1; overflow: hidden; border-radius: var(--r-1); border: var(--bw) solid var(--line-1); }
.ctd-product__thumbs a:hover, .ctd-product__thumbs a:focus-visible { border-color: var(--line-2); }
.ctd-product__thumbs img { width: 100%; height: 100%; object-fit: cover; }
/* A pack is ONE sprue, so the bench is never a strip: the frame is centred on
   the panel at the painter's scale, and the panel keeps a sensible height. */
.ctd-product__pack { --bench-strip: 0; --bench-px-max: 0.9; flex-direction: column; align-items: center; justify-content: center; min-height: 280px; overflow: hidden; }
.ctd-product__pack .ctd-bench__row { display: flex; }
.ctd-product__pack .ctd-sheet__cap { display: none; }
.ctd-product__spec-n small { font-size: 50%; margin-left: 1px; color: var(--text-2); }
.ctd-product__tags { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px var(--s-2); }
.ctd-product__tags li { display: inline-flex; align-items: center; gap: 4px; }
.ctd-product__tag-qty { font: 600 var(--fs-13)/1 var(--font-display); color: var(--text-2); }
.ctd-product__box-note { margin: 0; }
.ctd-product__desc { font-size: var(--fs-16); }
.ctd-product__desc p { margin: 0; }
.ctd-product__desc p + p { margin-top: var(--s-2); }
/* A simple product's form is the quantity and the button on one row (the
   variation form's row rule above only reaches .woocommerce-variation-add-to-cart). */
.ctd-product__summary form.cart:not(.variations_form) { flex-direction: row; align-items: stretch; flex-wrap: wrap; }

/* ============================================================
   4 · NOT YOUR TEAM — the stall page's dashed gold panel, as
   components.css draws it. Its twin, the Blitz Builder hand-off, is
   the foot of the buy column (.ctd-product__upsell above).
   ============================================================ */
.ctd-product__next { padding-block: var(--s-10); }
/* "Back to Blitz Bases & Accessories" is a category's name on a button, and
   at 375 it ran 26px past the screen (2026-10-09): on a phone it may wrap. */
@media (max-width: 640px) {
  .ctd-product__notyours .ctd-btn { white-space: normal; text-align: center; line-height: 1.2; padding-block: var(--s-2); max-width: 100%; }
}

/* ============================================================
   single column
   ============================================================ */
@media (max-width: 1000px) {
  .ctd-product__top { grid-template-columns: minmax(0, 1fr); grid-template-areas: none; grid-template-rows: none; gap: var(--s-8); }
  /* Every slot back to auto — the hand-off's own rule is the more specific
     one, and a named area that no longer exists opens an implicit column. */
  .ctd-product__top > *, .ctd-product__top > .ctd-product__upsell { grid-area: auto; min-width: 0; }
}

/* the ticket stacks: number and model on one line, the tags under them */
@media (max-width: 700px) {
  .ctd-ticket__row { grid-template-columns: auto minmax(0, 1fr) auto; gap: var(--s-2) var(--s-3); padding: var(--s-3) var(--s-4); }
  .ctd-ticket__row--head { display: none; }
  .ctd-ticket__row:nth-child(2) { border-top: 0; }
  .ctd-ticket__no { min-width: 44px; }
  .ctd-ticket__tags { grid-column: 1 / -1; }
}

/* ============================================================
   phone
   ============================================================ */
@media (max-width: 640px) {
  .ctd-product__section { padding-block: var(--s-4) var(--s-10); }
  .ctd-product__summary .product_title { font-size: var(--fs-40); }
  .ctd-product__summary .price { font-size: var(--fs-32); }
  .ctd-product__contents { padding: var(--s-4); }
  .ctd-product__spec-n { font-size: var(--fs-28); }
  .ctd-product__summary .qty, .tb-qtywrap .qty, .tb-step, .ctd-product__summary .single_add_to_cart_button { min-height: var(--btn-h-lg); }
}
