/* Team Builder (P1). Every colour comes from the shared --tcb-* tokens in
   shared-shell.css — this route adds no palette of its own. */

body.team-builder-page {
  background: var(--tcb-page-backdrop);
}

.team-builder-main {
  max-width: 1600px;
  margin: 0 auto;
  padding: clamp(1rem, 3vw, 2.4rem) clamp(0.9rem, 3vw, 2rem) 4rem;
  display: flex;
  flex-direction: column;
  gap: clamp(1.2rem, 2.6vw, 1.8rem);
}

/* Hero art dials — Rockstar Cookie (mirrored). Layout/type come from tcb-hero-text.
   Superseded per breakpoint by the tuning block at the bottom of this file. */
body.team-builder-page {
  --tcb-hero-art-width: 24rem;
  --tcb-hero-art-top: -3rem;
  --tcb-hero-art-right: -1.5rem;
  --tcb-hero-art-shift-y: 0%;
  --tcb-hero-art-opacity: 0.92;
  --tcb-hero-art-width-mobile: 12rem;
  --tcb-hero-art-top-mobile: -1.5rem;
  --tcb-hero-art-right-mobile: -3rem;
}

@media (max-width: 1000px) {
  body.team-builder-page {
    --tcb-hero-art-width: 17rem;
  }
}

@media (max-width: 390px) {
  body.team-builder-page {
    --tcb-hero-art-width-mobile: 9rem;
  }
}

.tb-page-hero {
  position: relative;
}

.tb-page-hero > :not(.hero-bg-art) {
  position: relative;
  z-index: 1;
}

/* ── Panels ─────────────────────────────────────────────────────────── */
.tb-panel {
  border: 1px solid var(--tcb-line);
  border-radius: 1.1rem;
  background: linear-gradient(150deg, rgba(28, 23, 45, 0.72), rgba(17, 13, 27, 0.92));
  box-shadow: var(--tcb-shadow);
  padding: clamp(1rem, 2.2vw, 1.4rem);
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.tb-panel-head {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.tb-panel-head h2 {
  margin: 0;
  font-family: "CookieRun KR", "Outfit", sans-serif;
  font-size: clamp(1.05rem, 2.2vw, 1.3rem);
  color: var(--tcb-ink);
}

.tb-panel-note {
  margin: 0;
  color: var(--tcb-ink-soft);
  font-size: 0.9rem;
  line-height: 1.5;
  max-width: 62ch;
}

/* ── Chips (destination + filters) ──────────────────────────────────── */
.tb-type-row,
.tb-context-row,
.tb-collection-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.tb-chip {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.1rem;
  padding: 0.55rem 1rem;
  border-radius: 0.85rem;
  border: 1px solid var(--tcb-line);
  background: var(--tcb-surface-strong);
  color: var(--tcb-ink-soft);
  cursor: pointer;
  font-family: "Outfit", sans-serif;
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}

.tb-chip:hover {
  border-color: var(--tcb-brand);
  color: var(--tcb-ink);
}

.tb-chip.is-active {
  border-color: var(--tcb-brand);
  background: rgba(154, 114, 255, 0.16);
  color: var(--tcb-ink);
}

.tb-chip-label {
  font-family: "CookieRun KR", "Outfit", sans-serif;
  font-size: 0.95rem;
}

.tb-chip-note {
  font-size: 0.72rem;
  color: var(--tcb-ink-soft);
}

/* On a phone the seven category chips are competing for a screen's width, and
   the second line under each label is what makes them tall. Drop to label-only
   there: the note only paraphrases the label it sits under ("Arena / Ladder
   teams", "Guild Boss Battle / Per boss"), so at this width it costs a row of
   height to say what the label already said. `display: none` rather than
   visually-hidden, so the chip announces itself as just its label instead of
   reading the note out too — same reasoning as the long/short action labels
   below. Scoped to the type row, so a note on any other chip row keeps its own
   behaviour. */
@media (max-width: 699px) {
  .tb-type-row .tb-chip-note {
    display: none;
  }
}

.tb-chip-context,
.tb-chip-filter {
  flex-direction: row;
  align-items: center;
  padding: 0.45rem 0.9rem;
  border-radius: 999px;
  font-size: 0.88rem;
  font-weight: 600;
}

/* ── Category tones ─────────────────────────────────────────────────────
   The seven site categories, in the colours the Community Guides page gives
   them, so one category reads the same wherever it appears.

   COPIED, not shared: the values below are the `.community-guides-page
   .guide-category-tone--*` blocks in community-guides/styles.css, which are
   themselves an override of an earlier palette in the same file. Pulling the
   winning half of a two-layer cascade out of a 5,000-line route stylesheet is
   a bigger change than this is worth — but it does mean a colour retuned there
   has to be retuned here. The category LIST is genuinely shared, in
   assets/js/site-categories.js.

   Used by four things: the category chips in section 1, the collection filter
   chips, the badge on each saved build, and the posted-build glow on the saved
   build row.

   `--tb-cat-rgb` is the same colour as `--tb-cat-line`/`--tb-cat-glow` (the
   gradient's light stop) held as a bare channel triplet, so a consumer can pick
   its own alpha with `rgb(var(--tb-cat-rgb) / a)` instead of needing a new
   pre-mixed rgba() per opacity. Retuning a tone means retuning it here too. */
.tb-cat-tone--arena {
  --tb-cat-fill: linear-gradient(135deg, #ff5fa2, #c0246b);
  --tb-cat-line: rgba(255, 95, 162, 0.7);
  --tb-cat-glow: rgba(255, 95, 162, 0.28);
  --tb-cat-rgb: 255 95 162;
}

.tb-cat-tone--guild-boss {
  --tb-cat-fill: linear-gradient(135deg, #ff7a45, #c2410c);
  --tb-cat-line: rgba(255, 122, 69, 0.7);
  --tb-cat-glow: rgba(255, 122, 69, 0.28);
  --tb-cat-rgb: 255 122 69;
}

.tb-cat-tone--story-campaign {
  --tb-cat-fill: linear-gradient(135deg, #5cc98a, #1f8a52);
  --tb-cat-line: rgba(92, 201, 138, 0.72);
  --tb-cat-glow: rgba(92, 201, 138, 0.26);
  --tb-cat-rgb: 92 201 138;
}

.tb-cat-tone--cookie-alliance {
  --tb-cat-fill: linear-gradient(135deg, #22b8e6, #0e7490);
  --tb-cat-line: rgba(34, 184, 230, 0.72);
  --tb-cat-glow: rgba(34, 184, 230, 0.26);
  --tb-cat-rgb: 34 184 230;
}

.tb-cat-tone--cake-towers {
  --tb-cat-fill: linear-gradient(135deg, #b574ff, #7e22ce);
  --tb-cat-line: rgba(181, 116, 255, 0.72);
  --tb-cat-glow: rgba(181, 116, 255, 0.26);
  --tb-cat-rgb: 181 116 255;
}

.tb-cat-tone--events {
  --tb-cat-fill: linear-gradient(135deg, #f5a623, #b8780c);
  --tb-cat-line: rgba(245, 166, 35, 0.72);
  --tb-cat-glow: rgba(245, 166, 35, 0.28);
  --tb-cat-rgb: 245 166 35;
}

.tb-cat-tone--other {
  --tb-cat-fill: linear-gradient(135deg, #94a3b8, #586577);
  --tb-cat-line: rgba(148, 163, 184, 0.7);
  --tb-cat-glow: rgba(148, 163, 184, 0.22);
  --tb-cat-rgb: 148 163 184;
}

/* Every chip carries its category's colour, the way the guides page's tabs do
   — that is what makes the colour an identity rather than a highlight. Only
   the SELECTED one is filled: seven live gradients in a single-select row read
   as seven selections, so the rest keep the colour on the edge and a wash of
   it behind, which is enough to recognise without competing. */
.tb-chip[class*="tb-cat-tone--"] {
  position: relative;
  border-color: var(--tb-cat-line);
  background:
    linear-gradient(var(--tcb-surface-strong), var(--tcb-surface-strong)) padding-box,
    var(--tb-cat-fill) border-box;
  border-color: transparent;
}

/* The wash. A tinted overlay rather than a second background, so the gradient
   above keeps painting the border. */
.tb-chip[class*="tb-cat-tone--"]::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--tb-cat-fill);
  opacity: 0.14;
  pointer-events: none;
  transition: opacity 0.15s ease;
}

.tb-chip[class*="tb-cat-tone--"] > * {
  position: relative;
}

.tb-chip[class*="tb-cat-tone--"]:hover {
  color: var(--tcb-ink);
  box-shadow: 0 2px 12px var(--tb-cat-glow);
}

.tb-chip[class*="tb-cat-tone--"]:hover::before {
  opacity: 0.26;
}

.tb-chip[class*="tb-cat-tone--"].is-active {
  color: #fff;
  box-shadow: 0 2px 16px var(--tb-cat-glow);
}

.tb-chip[class*="tb-cat-tone--"].is-active::before {
  opacity: 1;
}

/* The note is dimmed against the panel; on the filled active chip it has to
   ride the gradient instead. */
.tb-chip[class*="tb-cat-tone--"].is-active .tb-chip-note {
  color: rgba(255, 255, 255, 0.85);
}

.tb-context-wrap {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding-top: 0.35rem;
  border-top: 1px solid var(--tcb-line);
}

/* `display: flex` above outranks the hidden attribute's UA `display: none`,
   which left the "Choose one" label on screen for Arena (no context needed). */
.tb-context-wrap[hidden],
.tb-context-empty[hidden] {
  display: none;
}

/* ── "Other" destination ────────────────────────────────────────────────
   The free-text category label that replaces the context chip row when the
   build type is Other. Same frame and hidden-attribute fix as the chip row
   above, so the two never both occupy the panel. */
.tb-other-wrap {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding-top: 0.35rem;
  border-top: 1px solid var(--tcb-line);
}

.tb-other-wrap[hidden] {
  display: none;
}

/* The field reads as one thought with its note, so it is capped rather than
   stretched to the panel width the way the two-column meta grid fields are. */
.tb-other-field input {
  max-width: 36rem;
}

.tb-other-note {
  margin: 0;
  color: var(--tcb-ink-soft);
  font-size: 0.82rem;
  line-height: 1.5;
}

.tb-context-label,
.tb-field-label {
  margin: 0;
  font-size: 0.74rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--tcb-ink-soft);
}

.tb-field-label em {
  font-style: normal;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  opacity: 0.75;
}

.tb-context-empty,
.tb-empty-note {
  margin: 0;
  color: var(--tcb-ink-soft);
  font-size: 0.88rem;
  line-height: 1.5;
}

.tb-empty-note a,
.tb-signin-note a {
  color: var(--tcb-brand-strong);
}

/* ── Team meta fields ───────────────────────────────────────────────── */
.tb-meta-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr);
  gap: 0.9rem;
}

@media (max-width: 720px) {
  .tb-meta-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

.tb-field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  min-width: 0;
}

.tb-field input,
.tb-field textarea,
.tb-picker input {
  width: 100%;
  border: 1px solid var(--tcb-line);
  border-radius: 0.7rem;
  background: rgba(10, 8, 18, 0.75);
  color: var(--tcb-ink);
  font: 600 0.95rem "Outfit", sans-serif;
  padding: 0.6rem 0.75rem;
  outline: none;
  resize: vertical;
}

.tb-field input:focus,
.tb-field textarea:focus,
.tb-picker input:focus {
  border-color: var(--tcb-brand);
  box-shadow: 0 0 0 3px rgba(154, 114, 255, 0.14);
}

/* NOTE (2026-08-08): two page-side controls that used to live here are gone.

   1. The treasure row. Treasures are picked on the team card's own TREASURES
      panel, which is the only treasure UI now. Removing its `.tb-treasure-row`
      rule was not just dead-code cleanup: that class is ALSO the card's, this
      stylesheet loads after team-build-card.css, and the two rules had equal
      specificity — so the page copy silently won and rendered the card's
      treasures at flex-start with a 12px gap instead of its own centred 20px.

   2. The formation (front/middle/rear) picker, with its `.tb-formation-*` and
      `.tb-row-chip` rules. A cookie's position is fixed by the game and cannot
      be chosen, so the control described a mechanic that does not exist.

   Do not reintroduce an unscoped `.tb-treasure-*` rule here; scope anything
   new under `.team-builder-page` or a page-only class. */

/* ── Editor host ────────────────────────────────────────────────────── */
.tb-editor-host {
  min-width: 0;
  overflow-x: auto;
}

/* ── Status + actions ───────────────────────────────────────────────── */
.tb-status {
  margin: 0;
  min-height: 1.2rem;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--tcb-ink-soft);
}

.tb-status.is-success {
  color: #7ce0b0;
}

.tb-status.is-error {
  color: #ff9db1;
}

.tb-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
}

.tb-action {
  margin-top: 0;
}

.tb-action-primary {
  border-color: rgba(154, 114, 255, 0.5);
  background: linear-gradient(135deg, #7a54e8, var(--tcb-brand));
}

/* ── Phone action labels ────────────────────────────────────────────────
   "Export team graphic" and "Save to my builds" say what they do, which is
   worth the width on a desktop and is most of a phone's line on its own —
   four of them wrap the row to three lines. Below 720px each button swaps to
   a short form (app.js → actionLabel emits both).

   `display: none` rather than visually-hidden text on purpose: hidden-but-
   present text would be read out ON TOP of the visible label and the button
   would announce itself twice. Exactly one label is in the accessibility
   tree at any width.

   "Clear" has no short form — it is already one word. */
.tb-action-short {
  display: none;
}

@media (max-width: 720px) {
  .tb-action-long {
    display: none;
  }

  .tb-action-short {
    display: inline;
  }
}

.tb-signin-note {
  font-size: 0.85rem;
  color: var(--tcb-ink-soft);
  max-width: 46ch;
  line-height: 1.5;
}

/* ── Collection ─────────────────────────────────────────────────────── */

/* Filters on the left, sort on the right. They wrap onto separate lines on a
   phone rather than squeezing the chip row. */
.tb-collection-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
}

.tb-collection-toolbar > .tb-collection-filters {
  flex: 1 1 auto;
}

/* A select, not a chip row: the filters are already chips, and a second row of
   pills beside them would read as more filters. */
.tb-collection-sort {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-left: auto;
  color: var(--tcb-ink-soft);
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.tb-collection-sort[hidden] {
  display: none;
}

/* The native dropdown arrow is drawn by the browser hard against the frame's
   right edge, which reads as cramped inside a 999px pill and is unreachable
   from CSS — there is no element to pad. So drop the native chrome and paint
   our own chevron as a background image, which `background-position` can hold
   clear of the border. Same swap the guide builder's selects make. */
.tb-collection-sort select {
  /* Dropping the native appearance also drops the UA's intrinsic minimum
     height, so state it: the pill keeps the size it had, in every browser
     rather than whatever each one's widget metrics happened to give. */
  box-sizing: border-box;
  min-height: 2.05rem;
  padding: 0.4rem 1.75rem 0.4rem 0.75rem;
  border: 1px solid var(--tcb-line);
  border-radius: 999px;
  background-color: var(--tcb-surface-strong);
  color: var(--tcb-ink);
  font-family: "Outfit", sans-serif;
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: normal;
  text-transform: none;
  cursor: pointer;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%23ecf2ff' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.7rem center;
  background-size: 0.62rem;
}

.tb-collection-sort select:hover,
.tb-collection-sort select:focus-visible {
  border-color: var(--tcb-brand);
}

.tb-collection {
  display: grid;
  gap: 0.6rem;
  margin-top: 0.6rem;
}

.tb-build-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.8rem;
  align-items: center;
  padding: 0.75rem 0.9rem;
  /* 2px, so the category colour on it reads as a deliberate edge rather than
     a hairline. The row's WIDTH is unaffected — it is stretched by the grid,
     so the global `box-sizing: border-box` absorbs the extra pixel a side into
     the content box — but its height is content-driven and border-box only
     constrains a specified size, so each row grows 2px taller (114 -> 116). */
  border: 2px solid var(--tcb-line);
  border-radius: 0.9rem;
  background: rgba(23, 20, 37, 0.72);
  /* Inert placeholder — only `.is-posted` gives it a real value. It exists on
     the base rule so every state below can COMPOSE the glow into its own
     box-shadow instead of replacing it; see .is-posted. */
  --tb-posted-glow: 0 0 rgba(0, 0, 0, 0);
  box-shadow: var(--tb-posted-glow);
}

/* ── Category border ────────────────────────────────────────────────────
   Every saved build wears its category's colour on its edge, posted or not.
   The badge names the category in words; this is what makes a collection
   sortable by eye without reading any of them.

   Deliberately a SEPARATE channel from the posted glow below, which sits
   just inside it and stays posted-only. The two questions a row answers are
   "what is this build for" and "is it public", and conflating them into one
   treatment is what would make either unreadable.

   Gated on the tone class rather than folded into the base rule's `border`
   shorthand, and that gate is load-bearing: an unresolvable `--tb-cat-rgb`
   would be invalid at computed-value time, and CSS does NOT fall back to the
   earlier declaration for that — border-color would reset to its INITIAL
   `currentcolor` and paint a bright edge, and inside the `border` shorthand
   it would drop the border's width and style too. Requiring the class means
   a row that somehow arrives without one simply keeps --tcb-line.

   Ahead of the three state rules below, which are the same specificity and
   still take the border while a row is posted, being edited or previewed. */
.tb-build-row[class*="tb-cat-tone--"] {
  border-color: rgb(var(--tb-cat-rgb) / 0.5);
}

/* ── Posted glow ────────────────────────────────────────────────────────
   A build that has been shared to one of the site's pages is lit from within,
   in its own category's colour. The "Posted" pill says the same thing in
   words, but the pill is 0.74rem of text among three other pills — the glow is
   what makes a shared build findable at a glance while scrolling a collection,
   and colouring it by category means the row answers "posted?" and "posted
   WHERE?" in one read.

   Three layers: a rim light on the inside edge, a bloom that falls off inward
   (blur with negative spread pushes the shadow's solid core outside the
   padding box, so only the fringe lands — that is what makes it a glow rather
   than a vignette), and a matching outer bloom so the row lifts off the panel.

   The row must carry `tb-cat-tone--{category}` for the colour to resolve;
   app.js puts it there alongside `is-posted`. Without it `--tb-cat-rgb` is
   unset and the whole shadow list drops to invalid-at-computed-value-time,
   i.e. no glow — which is the right failure, not a grey one.

   Sets no border-color of its own any more: the category border above already
   paints that edge for every row, and having posted ALSO brighten it would
   put two different signals on the same channel. Posted is the glow. */
.tb-build-row.is-posted {
  --tb-posted-glow:
    inset 0 0 0 1px rgb(var(--tb-cat-rgb) / 0.3),
    inset 0 0 28px -6px rgb(var(--tb-cat-rgb) / 0.6),
    0 0 16px -6px rgb(var(--tb-cat-rgb) / 0.42);
}

/* Editing is the louder state and takes the border, but a posted row keeps its
   glow underneath rather than losing it for as long as it is open in the
   editor — hence the composed list. */
.tb-build-row.is-editing {
  border-color: var(--tcb-brand);
  box-shadow: 0 0 0 1px rgba(154, 114, 255, 0.25), var(--tb-posted-glow);
}

/* ── Saved build preview ────────────────────────────────────────────────
   The expanded read-only card. It is the SAME card the builder and the PNG
   exporter render, in --view mode, so a preview can never drift from what
   you get when you open or export the build.

   The row is a two-column grid (details | actions); the preview spans both
   and drops in underneath. `align-items: center` on the row would centre the
   short columns against a tall card, so the row switches to `start` while a
   preview is open. */
.tb-build-row.is-previewing {
  align-items: start;
  border-color: rgba(154, 114, 255, 0.55);
}

.tb-build-preview {
  grid-column: 1 / -1;
  min-width: 0;
  padding-top: 0.75rem;
  border-top: 1px solid var(--tcb-line);
  /* Belt and braces only. The card handles its own narrow layout: --view is
     `width: 100%` and below a 1080px container its team grid becomes a
     horizontal snap rail that scrolls itself (team-build-card.css). This
     catches anything that layout does not, so an unexpected overflow scrolls
     inside the row instead of widening the whole page. */
  overflow-x: auto;
}

/* The row's own paddings already frame the card; it needs no second one. */
.tb-build-preview .tb-team {
  margin: 0;
}


.tb-build-main {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.tb-build-title {
  margin: 0;
  font-family: "CookieRun KR", "Outfit", sans-serif;
  font-size: 0.98rem;
  color: var(--tcb-ink);
}

.tb-build-meta {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.74rem;
}

.tb-build-badge,
.tb-build-context,
.tb-build-visibility {
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  border: 1px solid var(--tcb-line);
  color: var(--tcb-ink-soft);
  font-weight: 700;
}

/* The build card's category badge is always filled — unlike the chips it is a
   statement, not a choice, and it is the fastest way to read a collection. */
.tb-build-badge {
  border-color: var(--tb-cat-line, rgba(154, 114, 255, 0.45));
  background: var(--tb-cat-fill, transparent);
  color: #fff;
  box-shadow: 0 2px 10px var(--tb-cat-glow, transparent);
}

.tb-build-visibility.is-public {
  border-color: rgba(124, 224, 176, 0.45);
  color: #7ce0b0;
}

/* Why an "Other" row has no Post button. Plain text, not a pill — it is an
   explanation sitting among the status pills, not a fourth status. */
.tb-build-note {
  color: var(--tcb-ink-soft);
  font-weight: 600;
  opacity: 0.8;
}

.tb-build-cookies {
  display: flex;
  gap: 0.25rem;
}

.tb-build-cookies img {
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--tcb-line);
  background: var(--tcb-surface-strong);
}

/* The actions sit in the row's top-right corner, opposite the title, with the
   "..." toggle hard against the right edge and Preview immediately left of it.
   Those two are the only buttons EVERY row has, so anchoring them to that
   corner puts them in the same place in every row — the Post buttons, which
   only an unposted postable build shows, grow leftwards from there rather than
   pushing the constant pair around.

   `align-self`, not the row's `align-items`. The row deliberately centres its
   columns (and switches to `start` while a preview or the tray is open, for
   reasons documented on those rules); this pins the actions to the top without
   touching how .tb-build-main is aligned or making those two rules redundant.

   `justify-content: flex-end` matters once the cluster wraps: an admin's
   unposted row is four buttons wide, and a wrapped second line should stay
   hard right under the first rather than drifting back to the left. */
.tb-build-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.35rem;
  align-self: start;
}

/* The two groups inside it are markup for the phone layout's benefit only.
   Here they are `display: contents`, so all four buttons are flex items of
   .tb-build-actions itself and the cluster is laid out exactly as it would be
   with no wrappers at all — same single line, same 0.35rem gaps. The wrappers
   only become boxes below 720px, where the groups go different places. */
.tb-build-post,
.tb-build-quick {
  display: contents;
}

/* ── Phone: four bands, and the row stops being a grid ──────────────────
   Preview and the "..." toggle keep the top-right corner beside the title,
   the same as on a wide screen. Everything else gets the full card width:

     title ............................ Preview  "..."
     badges / context / status  (its own full-width line)
     cookie portraits ................... Post buttons
     [tray] [preview]

   FLEX, not grid, and that is the whole reason this block looks different
   from the wide layout. The Post buttons have to share the portraits' line
   when they fit and drop to a line of their own when they do not, and grid
   has no such thing — an `auto` track just takes max-content and starves the
   track beside it (that is exactly how .tb-build-main once came out ZERO
   wide here). Wrapping when a line runs out of room is flex's own behaviour,
   so the row becomes a flex container and each band is a flex LINE.

   `justify-content: space-between` is what makes the wrap land correctly: on
   a shared line it pushes the portraits left and the Post buttons right, and
   on a line of its own a single item sits at the START — so a wrapped Post
   pair comes down the LEFT of the card rather than staying pinned right.

   Both halves of the markup dissolve to `display: contents` so their children
   become the row's own flex items. The bands are then just flex-basis: 100%
   forces a line to itself (meta, the tray, the preview), and `flex: 1 1 0` on
   the title stops a long title from claiming its content width and shoving
   the quick pair onto a line of its own.

   `order` is needed because the DOM emits Post before the quick pair — which
   is what keeps the wide layout's left-to-right reading and its tab order.
   Tab order therefore reaches Post before Preview here, where Post sits
   lower; that mismatch predates this block and is unchanged by it. */
@media (max-width: 720px) {
  .tb-build-row {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    /* The 0.8rem base gap is a COLUMN gap between two grid tracks; as a flex
       row-gap it would also space the four bands apart, where the stacked
       title/meta/portraits used to sit 0.35rem apart inside .tb-build-main. */
    column-gap: 0.8rem;
    row-gap: 0.4rem;
  }

  .tb-build-main,
  .tb-build-actions {
    display: contents;
  }

  /* Basis 0, not auto: a long title's content width would otherwise count
     towards the line and wrap the quick pair down below it. */
  .tb-build-title {
    order: 1;
    flex: 1 1 0;
    min-width: 0;
  }

  /* `align-self`, because the row centres its items: a title that wraps to two
     or three lines would otherwise drag the pair down to the middle of them,
     and it is meant to hold the top corner. */
  .tb-build-quick {
    order: 2;
    flex: 0 0 auto;
    display: flex;
    gap: 0.35rem;
    align-self: start;
  }

  .tb-build-meta {
    order: 3;
    flex: 1 1 100%;
  }

  /* Never shrinks — when the line is tight it is the Post pair that wraps,
     not the portraits that squash. */
  .tb-build-cookies {
    order: 4;
    flex: 0 0 auto;
  }

  .tb-build-post {
    order: 5;
    flex: 0 1 auto;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
  }

  .tb-build-menu,
  .tb-build-preview {
    order: 6;
    flex: 1 1 100%;
  }
}

.tb-mini {
  padding: 0.35rem 0.7rem;
  border-radius: 0.6rem;
  border: 1px solid var(--tcb-line);
  background: var(--tcb-surface-strong);
  color: var(--tcb-ink-soft);
  cursor: pointer;
  font: 700 0.76rem "Outfit", sans-serif;
}

.tb-mini:hover {
  border-color: var(--tcb-brand);
  color: var(--tcb-ink);
}

.tb-mini-primary {
  border-color: rgba(154, 114, 255, 0.5);
  color: var(--tcb-brand-strong);
}

/* Admin-only spotlight post. Gold reads as "curated" against the purple
   primary without needing a second explanatory label. */
.tb-mini-spotlight {
  border-color: rgba(255, 209, 122, 0.55);
  color: var(--tcb-accent-warm, #ffd17a);
}

.tb-mini-spotlight:hover {
  border-color: rgba(255, 209, 122, 0.85);
}

.tb-mini-danger:hover {
  border-color: rgba(255, 157, 177, 0.6);
  color: #ff9db1;
}

/* ── Per-build overflow menu ────────────────────────────────────────────
   Preview and the Post buttons stay in the row; the management actions live
   behind the "..." toggle. It expands IN the row rather than floating over
   it — see the note in app.js: a dropdown panel covers the next row's own
   toggle, so the click aimed at it lands on this row's menu instead. */
.tb-menu-toggle {
  min-width: 2rem;
  padding: 0.35rem 0.5rem;
  font-size: 0.95rem;
  line-height: 1;
}

.tb-menu-toggle.is-active {
  border-color: var(--tcb-brand);
  color: var(--tcb-ink);
}

.tb-build-menu {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.35rem;
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px dashed var(--tcb-line);
}

.tb-build-menu[hidden] {
  display: none;
}

/* The tray's buttons are the row's own buttons, one disclosure down — same
   size, same weight, so nothing reads as a different KIND of action. */
.tb-menu-item:focus-visible {
  border-color: var(--tcb-brand);
  color: var(--tcb-ink);
}

/* The row is a two-column grid centred on its content. With the tray open the
   columns should sit at the top, the same as when a preview is open.

   The open row also lights up from the bottom-right corner in its category's
   colour — the corner the tray's buttons right-align into, so the wash reads
   as coming FROM the thing that just opened. It is the same colour as the
   posted glow, so on a posted row the two reinforce rather than argue.

   ELLIPSE, not circle. A circle sized to the farthest corner of a row this
   wide and short puts the top-right corner at ~10% of its radius, so the whole
   right-hand end — including the "..." toggle up in the actions column — sits
   in near-full tint and the effect stops reading as a corner. An ellipse's
   radii follow the box, so it fades out at the left edge and the top edge
   together however tall the row grows.

   Kept deliberately dim: `.tb-mini` fills itself with opaque
   --tcb-surface-strong, so the tray's own buttons are never washed, but their
   1px --tcb-line (#3a3352) border is what separates them from the row and it
   has to stay the lighter of the two. The peak below sits well under it.

   `background-image`, not the `background` shorthand. Should --tb-cat-rgb ever
   go missing the longhand falls back to its initial `none` and the base rule's
   background-color still paints; the shorthand would take the row's fill down
   with it. */
.tb-build-row.is-menu-open {
  align-items: start;
  background-image: radial-gradient(
    ellipse at 100% 100%,
    rgb(var(--tb-cat-rgb) / 0.18),
    rgb(var(--tb-cat-rgb) / 0.07) 40%,
    rgb(var(--tb-cat-rgb) / 0) 72%
  );
}

/* ── Picker ─────────────────────────────────────────────────────────── */
.tb-picker {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: grid;
  place-items: center;
  padding: 1rem;
  background: rgba(4, 2, 8, 0.72);
  backdrop-filter: blur(3px);
}

.tb-picker[hidden] {
  display: none;
}

.tb-picker-panel {
  width: min(1120px, 100%);
  max-height: min(80vh, 1040px);
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  padding: 1rem;
  border: 1px solid var(--tcb-line);
  border-radius: 1rem;
  background: var(--tcb-surface);
  box-shadow: var(--tcb-shadow);
}

.tb-picker-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.tb-picker-head h3 {
  margin: 0;
  font-family: "CookieRun KR", "Outfit", sans-serif;
  font-size: 1rem;
  color: var(--tcb-ink);
}

.tb-picker-close {
  width: 2rem;
  height: 2rem;
  border-radius: 0.5rem;
  border: 1px solid var(--tcb-line);
  background: var(--tcb-surface-strong);
  color: var(--tcb-ink);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}

.tb-picker-grid {
  display: grid;
  gap: 0.45rem;
  overflow-y: auto;
  min-height: 0;
  /* The grid scrolls and the tiles inside it are the drag surface on touch;
     without this a flick that starts on a tile can chain to the page behind
     the modal once the list hits its end. */
  overscroll-behavior: contain;
}

/* Sized for the 3.4rem icons below, so a name still gets a readable share of
   the tile instead of being ellipsised to nothing. */
.tb-picker-grid {
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 11rem), 1fr));
}

.tb-picker-item {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--tcb-line);
  border-radius: 0.6rem;
  background: var(--tcb-surface-strong);
  color: var(--tcb-ink-soft);
  cursor: pointer;
  font: 600 0.78rem "Outfit", sans-serif;
  text-align: left;
  min-width: 0;
}

.tb-picker-item:hover {
  border-color: var(--tcb-brand);
  color: var(--tcb-ink);
}

/* Both pickers run the same icon size. Cookie portraits and treasure art are
   what people actually scan for — the name is confirmation, not the thing
   being looked for — and at the old 2.1rem neither was recognisable without
   reading the label beside it. */
.tb-picker-item img {
  width: 3.4rem;
  height: 3.4rem;
  object-fit: contain;
  flex: none;
}

/* Anchors the "All 3" order badge; treasures are the only picker with one. */
.tb-picker-item-treasure {
  position: relative;
}

.tb-picker-item span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tb-picker-clear {
  grid-column: 1 / -1;
  justify-content: center;
  border-style: dashed;
}

/* ── Treasure picker: "All 3" mode ──────────────────────────────────────
   The mode switch, the running selection, and the order badge that says
   which slot a chosen treasure will land in. */
.tb-picker-modes {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.tb-picker-modes[hidden],
.tb-picker-footer[hidden] {
  display: none;
}

.tb-picker-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--tcb-line);
}

.tb-picker-hint {
  margin: 0;
  flex: 1 1 12rem;
  min-width: 0;
  color: var(--tcb-ink-soft);
  font-size: 0.82rem;
  line-height: 1.45;
}

.tb-picker-footer-actions {
  display: flex;
  gap: 0.4rem;
}

.tb-picker-item.is-picked {
  border-color: var(--tcb-brand);
  background: rgba(154, 114, 255, 0.18);
  color: var(--tcb-ink);
}

/* Absolute so the badge never reflows the tile — a picked and an unpicked
   treasure have to occupy exactly the same box, or the grid jumps under the
   finger between taps. */
.tb-picker-order {
  position: absolute;
  top: 0.25rem;
  right: 0.3rem;
  display: grid;
  place-items: center;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 50%;
  background: var(--tcb-brand);
  color: #120b22;
  font-size: 0.7rem;
  font-weight: 800;
  line-height: 1;
}

/* =============================================================================
   HERO TUNING DIALS — /team-builder/
   =============================================================================
   Same shape as the block at the bottom of guild-boss/styles.css: one block
   per breakpoint, each with the same seven controls, kept here so hand-tuning
   the hero never means hunting through the file.

   Direct properties on page-scoped selectors, NOT --tcb-hero-* dials. The
   shared ladder in shared-shell.css switches from var-driven to literal values
   below 639px (`.tcb-hero-text h1 { font-size: 1.4rem }`), so a var-based
   scaffold works at the top three breakpoints and silently does nothing at the
   bottom two. This also collapses the art split — above 699px the shared
   system reads --tcb-hero-art-*, below it reads --tcb-hero-art-*-mobile — so
   the same three lines work in every block.

   These sit ABOVE the shared ladder, so this hero stops tracking future
   changes to it. Delete or comment out any single line to hand that one dial
   back to the shared value.

   Values are what the page already computed at each width, so the block is a
   no-op as written. Edit freely.
   ========================================================================== */

@media (max-width: 1200px) {
  body[data-route="team-builder"] .tb-page-hero .hero-bg-art {
    width: 22rem;
    top: -3rem;
    right: -1.5rem;
  }
  body[data-route="team-builder"] .tb-page-hero h1 {
    font-size: 2rem;
    max-width: none;
  }
  body[data-route="team-builder"] .tb-page-hero .hero-copy {
    font-size: 1.1rem;
    max-width: 46rem;
  }
}

@media (max-width: 1000px) {
  body[data-route="team-builder"] .tb-page-hero .hero-bg-art {
    width: 22rem;
    top: -2rem;
    right: -1.5rem;
  }
  body[data-route="team-builder"] .tb-page-hero h1 {
    font-size: 2rem;
    max-width: none;
  }
  body[data-route="team-builder"] .tb-page-hero .hero-copy {
    font-size: 1.1rem;
    max-width: 36rem;
  }
}

@media (max-width: 860px) {
  body[data-route="team-builder"] .tb-page-hero .hero-bg-art {
    width: 22rem;
    top: -2rem;
    right: -2.5rem;
  }
  body[data-route="team-builder"] .tb-page-hero h1 {
    font-size: 1.8rem;
    max-width: none;
  }
  body[data-route="team-builder"] .tb-page-hero .hero-copy {
    font-size: 1.1rem;
    max-width: 32rem;
  }
}

@media (max-width: 699px) {
  body[data-route="team-builder"] .tb-page-hero .hero-bg-art {
    width: 22rem;
    top: -1.2rem;
    right: -2rem;
  }
  body[data-route="team-builder"] .tb-page-hero h1 {
    font-size: 1.6rem;
    max-width: 20rem;
  }
  body[data-route="team-builder"] .tb-page-hero .hero-copy {
    font-size: 0.9rem;
    max-width: 24rem;
  }
}

@media (max-width: 530px) {
  body[data-route="team-builder"] .tb-page-hero .hero-bg-art {
    width: 22rem;
    top: -3.2rem;
    right: -2rem;
  }
  body[data-route="team-builder"] .tb-page-hero h1 {
    font-size: 1.4rem;
    max-width: 16rem;
  }
  body[data-route="team-builder"] .tb-page-hero .hero-copy {
    font-size: 0.9rem;
    max-width: 24rem;
  }
}

@media (max-width: 390px) {
  body[data-route="team-builder"] .tb-page-hero .hero-bg-art {
    width: 20rem;
    top: -3.2rem;
    right: -4rem;
  }
  body[data-route="team-builder"] .tb-page-hero h1 {
    font-size: 1.4rem;
    /* 70% of the hero, so the title keeps clear of the art rather than
       wrapping against a fixed rem measure at the narrowest width. */
    max-width: 60%;
  }
  body[data-route="team-builder"] .tb-page-hero .hero-copy {
    font-size: 0.9rem;
    max-width: 24rem;
  }
  .team-builder-main{
    padding: 1rem 0.25rem;
  }
  .tb-panel {
    padding: 0.5rem;
  }
}

/* ---- collapsible page sections ----
   The head is the toggle for its whole section; the chevron is an affordance,
   not the only target. Collapsing hides every sibling after the head, so a
   section's contents do not each need a wrapper.

   Scoped to the page's own sections throughout: the team card names its own
   sections `.tb-panel` too, and they render inside section 2. */
.team-builder-main > .tb-panel > .tb-panel-head {
  position: relative;
  padding-right: 40px;
  cursor: pointer;
  user-select: none;
}

.tb-panel-toggle {
  position: absolute;
  top: 50%;
  right: 0;
  width: 30px;
  height: 30px;
  padding: 0;
  transform: translateY(-50%);
  border: 1px solid rgba(239, 217, 255, 0.28);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.05);
  color: inherit;
  cursor: pointer;
  line-height: 1;
}

.tb-panel-toggle::before {
  content: "▾";
  display: block;
  font-size: 13px;
}

.team-builder-main > .tb-panel.is-collapsed .tb-panel-toggle::before {
  content: "▸";
}

.tb-panel-toggle:hover,
.tb-panel-toggle:focus-visible {
  border-color: var(--tb-cyan, #7fe3ff);
}

.team-builder-main > .tb-panel.is-collapsed > *:not(.tb-panel-head) {
  display: none;
}

/* A collapsed section is a title bar, so it should not keep a panel's height. */
.team-builder-main > .tb-panel.is-collapsed {
  padding-bottom: 12px;
}

.tb-collection-sort {
  margin-left: unset;
  margin-right: auto;
}