/* cogitator/datasheet.css v104 */


/* DevAgent24 - 2026-07-16
   What: v76 — Removed the .ds--tyranids / .ds--astartes faction colour token
         classes and their static #ffffff --ds-faction-color-text. The datasheet
         root now carries a NEUTRAL default (--banner charcoal); the real accent
         and its contrast-derived ink arrive as inline custom properties from
         faction-colours.js (factions.accent_color). Also repointed the two
         static #ffffff declarations that sit directly on the faction gradient
         (.ds-unit-name, .ds-category-badge) at --ds-faction-color-text.
   Why:  Only two factions had token classes, so the other 26 fell through to
         .ds--astartes and rendered Space Marine navy. Navy-as-default is what
         made the bug invisible for Space Marines; the fallback is now neutral
         and no faction colour is a static CSS value anywhere in this file. */

/* DevAgent23 - 2026-07-16
   What: v75 — Added the .ds-cv-* (list-convention text) rules, ported verbatim
         from codex-col3.css v3's transitional block. Home for them here — the
         .ds-* namespace owner, loaded on the Army Builder AND Codex pages — so
         ArmyBuilderCol2.js v77's new WARGEAR OPTIONS / WARGEAR ABILITIES
         ConventionTextBlock styles correctly (codex-col3.css is NOT loaded on
         the builder page). Same move DevAgent22 made for .ds-equip-* in v74.
   Why:  Builder v77 renders wargear_options_text / wargear_abilities_text via
         ConventionTextBlock; those .ds-cv-* classes existed only in
         codex-col3.css, so the builder panel was unstyled without them.
   Note: codex-col3.css v3 still carries its own identical .ds-cv-* copy;
         values match, so the duplicate is harmless. Stripping it there is a
         later codex-col3.css tidy (same as the still-duplicated .ds-equip-*). */

/* DevAgent22 - 2026-07-15
   v74 — Added the .ds-equip-* equipment-pill rules (type-coloured solid scheme,
   matching army-view-col2.css v8 / codex-col3.css v3). These previously lived only
   in codex-col3.css (transitional home), which the Builder page does not load — so
   the Builder datasheet's pills were unstyled. This is their permanent .ds- home.
   NOTE: codex-col3.css still carries an identical copy; it can be stripped in a
   follow-up once confirmed army-builder-col2.css is loaded on the codex page too
   (per codex-col3.css v1's "loaded globally" note). */
/* Army Builder Column 2 — Unit Datasheet panel styles */

/* DevAgent10 - 2026-06-01
   What: v73 — Datasheet header image reworked for transparent-PNG art: the photo
         is widened, switched to object-fit:contain (no crop) and right-anchored
         so wide models extend left across the faction gradient, behind the stats
         (z-index 0). A left-fading mask dissolves the model into the gradient.
         Tunable levers: .ds-unit-photo width, mask stop %, .ds-header gradient
         stops, .ds-header__content padding-right. */

/* DevAgent10 - 2026-06-01
   What: v72 — Wargear option bullets (GW style): top-level options use a filled
         square marker, sub-options a hollow circle. Lists switched from flex to
         block so list markers render (flex items suppress markers). */

/* DevAgent10 - 2026-06-01
   What: v71 — Review feedback on v70: .ds-stat__inv-label-cell now align-self:
         center so "INVULNERABLE SAVE" (and the optional note beneath it) is
         vertically centred against the inv shield. The stat grid's
         align-items:end was bottom-aligning the cell, dropping a single-line
         label below the shield's centre. */

/* DevAgent10 - 2026-06-01
   What: v70 — Review feedback on v69:
         1. Ability group header now uses .ds-banner (full width). New
            .ds-ability-group__body padded container; .ds-ability-group__header
            rule removed.
         2. .ds-stat__inv-label-cell stacks label + footnote, vertically centred
            against the inv shield; .ds-stat__inv-footnote restyled to sit under
            the label.
         4. .ds-leader-block__desc italic. */

/* DevAgent32 - 2026-07-19
   What: v104 — .ds-ability__desc gets list rules, so an ability that says
         "select one of the following:" shows its options as an indented
         bulleted list.
   Why:  Upstream ships REAL MARKUP for these. Bladeguard Veteran Squad's
         description (waha_datasheet_abilities, datasheet 000000071, line 2)
         is a lead-in sentence, a <br>, then
         <ul><li><b>Swords of the Chapter:</b> …</li><li><b>Shields of the
         Chapter:</b> …</li></ul>. UL, LI and B are all in codex-col3's
         ALLOWED_TAGS, so the structure already survived the sanitiser intact —
         nothing was being dropped and no text was being lost.
         ⚠ The flattening is base.css:107, `ul, ol { list-style: none }`, plus
         the `*` box-model reset zeroing padding — so the options arrived
         structurally correct, unmarked and unindented. Exactly the cause fixed
         for the wargear options in v78 and for the popup body in v81, and
         fixed the same way: markers declared locally rather than by widening
         the allow-list to admit upstream's inline style.
         ⚠ CSS ONLY. codex-col3.js is not involved in this fix and the rules
         text is untouched — this changes how the options LOOK, never what they
         SAY. */

/* DevAgent29 - 2026-07-19
   What: v103 — .ds-equip-pill gets vertical-align: middle.
   Why:  An inline-flex box aligns on its BASELINE by default, which puts the
         pill's inner text on the sentence's baseline and pushes the pill's
         padding and border below it — so the pill sat low against the words
         either side of it. `middle` aligns the pill's own centre to the middle
         of the surrounding lowercase instead, which is what reads as centred. */

/* DevAgent29 - 2026-07-19
   What: v102 — .ds-equip-pill__qty goes to full opacity and a slightly larger
         size.
   Why:  It now appears on every pill (codex-col3 v29 shows x1 as well as x2),
         so it has to read at a glance rather than sit back as a footnote. */

/* DevAgent29 - 2026-07-19
   What: v101 — stripes narrowed again, 6px → 4px. vertical-align removed from
         .ds-equip-pill__qty.
   Why:  Jérôme review. The align nudge is no longer needed: codex-col3 v25
         emits a plain lowercase 'x', which sits on the baseline by
         construction, rather than U+00D7. */

/* DevAgent29 - 2026-07-19
   What: v100 — dual-pill stripes narrowed 9px → 6px; .ds-equip-pill__qty gets
         vertical-align: -0.08em.
   Why:  Jérôme review. ⚠ The '×' is U+00D7 MULTIPLICATION SIGN, which is
         centred on the mathematical axis — higher than the baseline the digit
         beside it sits on — so it read as raised. This nudges it into line
         rather than replacing the glyph with a lowercase 'x', which would sit
         correctly but read as a letter next to a number. */

/* DevAgent29 - 2026-07-19
   What: v99 — the dual pill's rule is raised to .ds-equip-pill.ds-equip-pill--dual
         and uses the `background` SHORTHAND. Adds .ds-equip-pill__qty.
   Why:  v98 relied on SOURCE ORDER to beat .ds-equip-pill's background
         shorthand, and it rendered as an empty white pill — white text on the
         base rule's fallback surface, gradient absent. Order-dependence is the
         wrong mechanism for this: the rule is now more SPECIFIC (0,2,0 vs
         0,1,0), so it wins wherever it sits and survives any future reshuffle
         of this file.
         .ds-equip-pill__qty carries the '×2' that upstream writes as a bare
         number before the weapon name. */

/* DevAgent29 - 2026-07-19
   What: v98 — the dual pill becomes diagonal STRIPES, and its rule MOVES to
         after .ds-equip-pill.
   Why:  Two problems, one of them invisible. v97 put --pill-c: #4a6a48 on the
         dual modifier purely as a border colour and set a split gradient — but
         the modifier rules sit ABOVE the base rule in this file, and
         .ds-equip-pill's `background: var(--pill-c)` SHORTHAND then reset the
         gradient. The pill rendered as flat #4a6a48, a muddy green that was
         never a chosen colour.
         ⚠ Any modifier that needs its own background must come AFTER
         .ds-equip-pill, or the shorthand wins on source order. The three
         single-colour modifiers are unaffected — they only set --pill-c, which
         is exactly what the shorthand consumes.
         Stripes rather than a two-part split: both colours read at pill size,
         where a 50/50 split just looked like a gradient artefact. */

/* DevAgent29 - 2026-07-19
   What: v97 — .ds-equip-pill--dual, for a weapon that appears in BOTH the
         ranged and the melee table. Split fill: ranged green into melee brown.
   Why:  A name carried by both tables is the old dual-listed defect class — the
         pill used to point at one of them, arbitrarily, and nothing on screen
         showed the choice had been made. The split fill states it, and the
         type badge reads 'R/M' rather than picking a side.
         ⚠ Do NOT "simplify" this to a single colour. The ambiguity is the
         information. */

/* DevAgent29 - 2026-07-19
   What: v96 — .ds-wargear-options__item--note loses its marker and its indent.
   Why:  Those rows (button='*') are NOTES about the options above them, not
         options themselves — "* This model can only be equipped with two ranged
         weapons if one of them is a Pistol". A bullet implied it was another
         choice, and the indent aligned it with the choices it qualifies.
         The asterisk is already in the published text, so the row needs no
         marker of its own. */

/* DevAgent29 - 2026-07-19
   What: v95 — REVERTS v94. .ds-keywords-row is a 7fr/4fr grid again and
         .ds-keywords-row--faction is removed.
   Why:  The keyword split was meant for the Col1 filter panel, not the
         datasheet. The published sheet sets the two side by side. */

/* DevAgent29 - 2026-07-19
   What: v94 — .ds-keywords-row goes from a 7fr/4fr two-column grid to a single
         full-width block, and .ds-keywords-row--faction takes a lighter
         internal divider.
   Why:  KEYWORDS and FACTION KEYWORDS are now two stacked sections
         (codex-col3 v19). The keyword list is much the longer of the two and
         was confined to 7/11ths of the width while a one-word faction list held
         the rest. The faction divider is deliberately lighter than the block's
         own top border so the two read as one region, not two. */

/* DevAgent29 - 2026-07-19
   What: v93 — RENAMED from army-builder-col2.css. Content is unchanged; only
         the filename and this header differ. Version lineage continues (v92 →
         v93), following the precedent set when DevAgent28 renamed the CodexCol*
         files.
   Why:  The datasheet is not a column. It renders at Army Builder col 2, Army
         Viewer col 2, and Codex col 3 — so a name carrying a column position
         was wrong on two of the three surfaces, and reading the Codex datasheet
         out of a file called "army builder col 2" was actively misleading.
         158 of the rules here are .ds-*: this file IS the datasheet component's
         stylesheet, and the three columns are its consumers.
         ⚠ Load position in index.html is UNCHANGED (still between
         army-builder-col1.css and army-builder-col3.css). Moving it would
         reorder the cascade, and this rename is deliberately not that change.
   Note: the .dsv-* twin, army-view-col2.css, still describes itself as a copy
         to "keep in step" with this file. Merging the two namespaces into one
         component is a later session's work; this rename is a precondition for
         it, not the thing itself. */

/* DevAgent29 - 2026-07-19
   What: v92 — .ds-weapon-tags: text-transform uppercase.
   Why:  Upstream casing is transcription variance, not convention. Of 6,568
         wargear rows carrying tags, 6,165 are all-lowercase, 364 all-uppercase
         and ~39 mixed — one datasheet can show 'pistol', 'PISTOL' and 'PISTOl'
         on three different weapons. GW prints them in capitals, so the 94% case
         did not match the published sheet either.
         ⚠ Fixed HERE, not in the payload or the DB. The mirror keeps what
         Wahapedia published; the transform flattens all three casings for
         display, and takes the 'PISTOl' typo with it. Do not normalise the
         source data to achieve this. */

/* DevAgent29 - 2026-07-19
   What: v91 — keywords abandon font-variant-caps for explicit two-tier
         markup, and switch to --font-heading.
   Why:  ⚠ SYNTHESISED small caps scale at a ratio fixed by the browser. Big
         Shoulders Display carries no true small-cap glyphs, so the reduced
         letters sat well below cap height and NO CSS property could raise them
         — that is why v89/v90 could not fix it by size or weight. codex-col3
         v16 now emits .ds-keyword__initial and .ds-keyword__rest, and the ratio
         is set here: 0.86em, close to cap height rather than the browser's
         ~0.75.
         --font-heading is condensed, so it is both narrower and lighter than
         the body face at the same size, which was the other half of the ask.
         The remainder is uppercased HERE, not in the payload. */

/* DevAgent29 - 2026-07-19
   What: v90 — keyword values: font-size --text-sm → --text-base, and an
         explicit font-weight: 400.
   Why:  Jérôme review — thinner and taller. The weight is stated rather than
         inherited because the small caps here are SYNTHESISED (Big Shoulders
         Display carries no true small-cap glyphs), and a synthesised small cap
         is a scaled-down capital that reads heavier than the text around it.
         Larger size plus an explicit lighter weight corrects both at once. */

/* DevAgent29 - 2026-07-19
   What: v89 — keyword values render in SMALL CAPS, as GW sets them: the
         initial letter full height, the rest as reduced capitals.
   Why:  Jérôme review.
         ⚠ This is TYPOGRAPHIC, not textual. The payload keeps upstream's own
         casing ("Adeptus Astartes") and font-variant-caps does the rest.
         text-transform: uppercase would defeat it entirely — with every letter
         already a capital there is no lowercase left to reduce, and the effect
         disappears. Do not "normalise" the keyword text to fix a casing
         inconsistency; the mixed casing IS what makes this render.
         .ds-keywords__label keeps its own uppercase: it is a heading, not a
         keyword, and small-caps on already-capital text is a no-op anyway. */

/* DevAgent29 - 2026-07-19
   What: v88 — the popup cap gains an absolute ceiling:
         min(560px, calc(100vh - 160px)).
   Why:  v86's viewport-only cap worked, but 100vh is whatever the screen
         happens to be — on a tall desktop window it resolved to ~1,080px and
         the popup was still far taller than anything readable. 560px is sized
         for the target device (iPad 10 landscape, ~680px usable after chrome)
         and holds on any larger screen; the vh term still shrinks it further on
         anything shorter. */

/* DevAgent29 - 2026-07-19
   What: v87 — the top-level wargear option marker is DRAWN as a 7px box
         instead of set as a glyph.
   Why:  v85 used U+25AA BLACK SMALL SQUARE, whose rendered size is decided by
         the font and came out smaller than the nested circle beneath it — the
         opposite of the intended hierarchy. A background box is exact,
         font-independent, and guaranteed larger than the ~6px nested marker. */

/* DevAgent29 - 2026-07-19
   What: v86 — .ds-popup gets a height cap and a scrolling body.
   Why:  The popup now carries Wahapedia's full published rule text. The CORE
         Leader ability is ~4KB of markup and rendered at its natural height,
         running off the bottom of the screen with no way to reach the end.
         Flex column: the header is fixed, the body scrolls.
         The cap is calc(100vh - 160px) — iPad 10 landscape is 820 CSS px tall,
         so this leaves roughly 660px after browser chrome and the app header,
         and adapts down on anything smaller.
         ⚠ min-height: 0 on the body is load-bearing, not decoration: a flex
         child will not shrink below its content height without it, and the
         overflow would never engage. */

/* DevAgent29 - 2026-07-19
   What: v85 — wargear option markers swapped: the top-level option takes a
         SQUARE, the nested choice list takes a CIRCLE. v79 had them the other
         way round.
   Why:  Matches the published sheet, and the nested level matches what
         upstream itself declares — the <ul> inside the description carries
         list-style-type:circle, which the P4 sanitiser strips along with every
         other attribute. */

/* DevAgent29 - 2026-07-19
   What: v84 — .ds-leader-block__desc gets a rule. It had NONE: the class is
         named in two changelog entries (v69, v71) and was never defined, so the
         leader footnote rendered edge-to-edge while the lists above it sat
         inside .ds-leader-block__body's padding. Now padded to match, and
         italic as v71 intended.
   Why:  leader_footer is upstream's asterisked attachment note; it read as
         unstyled overflow rather than as part of the block. */

/* DevAgent29 - 2026-07-19
   What: v83 — REVERTS v82's solid .ds-header background. The right-fading
         faction gradient is restored exactly as it was.
   Why:  Jérôme's call: the gradient is wanted for its own sake, independently
         of the photo it was originally built to blend. ⚠ Do not "clean this up"
         on the reasoning that photos are gone — that reasoning was already
         tried here and rejected. Only .ds-unit-photo-placeholder stays removed
         (v82), which was the actual request. */

/* DevAgent29 - 2026-07-19
   What: v82 — .ds-header is a solid faction colour. The right-fading gradient
         and .ds-unit-photo-placeholder are gone.
   Why:  The gradient existed to dissolve a transparent-PNG unit photo into the
         faction colour on the right-hand side. photo_url is dropped and
         Wahapedia publishes no image URLs, so it faded to white behind nothing.
         ⚠ The BROWSE-LIST photo element (.ub-card--simple, army-builder-col1
         .css) is a different thing and stays — its layout depends on it. */

/* DevAgent29 - 2026-07-19
   What: v81 — .ds-popup__body: italic removed, list markup styled.
   Why:  The popup now carries Wahapedia's full published rule text
         (waha_abilities.description), where it used to carry a short authored
         note — so a blanket font-style: italic set entire core rules in
         italics. Upstream also ships real <ul>/<li> inside that text, and the
         P4 sanitiser strips the inline list-style, so bullets arrived
         structurally correct and unmarked. Same cause as the wargear options
         in v78; the markers are declared here rather than by widening the
         allow-list to admit style attributes.
         ⚠ Italic is NOT removed globally — .ds-leader-block__desc and the
         inv-save footnote keep their own. */

/* DevAgent29 - 2026-07-19
   What: v80 — .ds-composition__list / __item: composition lines render as a
         bulleted list, bold, one bullet per row, matching the published block.
   Why:  They rendered as plain paragraphs. ⚠ No points styling was added:
         .ds-comp-points / .ds-comp-points-row / __label / __badge already exist
         with the amber badge, and codex-col3 v10 now uses them. The v6
         renderer had invented a parallel unstyled .ds-points set. */

/* DevAgent29 - 2026-07-19
   What: v79 — wargear options become a marked list whose marker is the
         PUBLISHED one. waha_datasheet_options.button holds '•' on 2,775 rows
         and '*' on 57.
         ⚠ button is a FORMATTING FLAG, not a marker: '*' means the option
         renders italic. Every option takes the same bullet; the flagged rows
         take italics. The asterisk is never printed — it is an instruction,
         not content.
   Why:  v78 marked only the <ul> embedded in a description, so an option with
         one choice and no embedded list rendered with no marker at all. */

/* DevAgent29 - 2026-07-19
   What: v78 — styles two blocks the mirror payload introduced.
         1. .ds-weapon-rules__* — the type='Wargear profile' footnote below the
            weapon tables (e.g. Rhino Primaris "One Shot"). Indented to align
            with the weapon name column, name in bold, vertical rhythm matching
            a weapon row.
         2. Wargear options list markup. Upstream ships real
            <ul style="list-style-type:circle"><li> inside the description, and
            the P4 sanitiser strips every attribute — including that inline
            style — so the lists arrived structurally correct and visually
            unmarked. ⚠ The markers are set HERE, not by preserving upstream's
            inline style: allowing style through the allow-list to fix a bullet
            would let arbitrary CSS in with it.
   Why:  Both blocks rendered as unstyled body text. */

/* DevAgent29 - 2026-07-19
   What: v77 — accent-ink reassignment on the datasheet header.
         1. .ds-unit-name is now a CONSTANT #ffffff, no longer
            var(--ds-faction-color-text).
         2. .ds-stat-block__inv-label and .ds-stat__inv-footnote now TAKE
            var(--ds-faction-color-text), replacing a hardcoded
            rgba(255,255,255,0.85). The 0.85 softening is preserved as opacity
            so it applies to either ink.
   Why:  Observed on Adeptus Custodes (#B8860B), the lightest accent in the
         palette. The derivation was correct and applied to the wrong elements.
         The unit name is large, bold and carries a text-shadow — it holds
         against a light accent without help, and white is what it should be on
         all 24 other factions too, so it is not a contrast decision at all.
         The invulnerable-save label is small and thin, which is precisely where
         contrast matters, and it was static white on every faction.
         ⚠ Hardcoding black there would fix Custodes and make the label
         illegible on the 23 dark accents. The variable is the fix; the
         hardcoded value was the bug.
   Note: this file is the .ds-* namespace owner and is shared with Army Builder
         Col2, which takes the same change. That is intended — divergence
         between the datasheet surfaces is the defect class this namespace
         exists to prevent. */

/* DevAgent10 - 2026-06-01
   What: v69 — New classes for DevAgent10 datasheet enhancements:
         .ds-stat__inv-footnote      — invulnerable_save_note footnote under stats
         .ds-ability-group*          — parent/child ability group block
         .ds-leader-block__desc      — CORE Leader special description
         .ds-wargear-condition       — wargear option scope/condition prefix
   Why:  Surface ability groups, invulnerable_save_note, leader descriptions and
         wargear conditions in the Army Builder datasheet. */

/* DevAgent6 - 2026-05-28
   What: v64 — Added .ab-col-2__header-add-btn--loading explicit CSS rule.
         Without it, the button's painted background gets stuck on var(--amber-dim)
         after the :active state fires — the disabled attribute prevents pointer
         events but doesn't reset the visual state. The explicit rule ensures
         the loading appearance is intentional, and removing the class + disabled
         lets the transition snap back to var(--amber) cleanly.
   Why: "Add to Army" button never returned to amber after adding a unit. */

/* DevAgent5a - 2026-05-28
   What: v5 changes:
         1. Header is a single full-width block. Unit image absolutely positioned
            at right so long names (e.g. TYRANID WARRIORS WITH RANGED BIO-WEAPONS)
            never get compressed.
         2. Base size moved to second meta row beside the category badge.
         3. Stat block nudged 2px lower via margin-top.
         4. Weapon banner icon filter removed — PNGs are already white.
         5. Weapon table font bumped: th and td from --text-xs (11px) to
            --text-sm (13px).
   Why:  Browser feedback — long names broke, icons showed black, table text small. */

/* ============================================================
   PLACEHOLDER
   ============================================================ */

.ab-col-2__placeholder {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-8);
}

.ab-col-2__placeholder-text {
  font-size: var(--text-base);
  color: var(--text-dim);
  font-style: italic;
  text-align: center;
}

/* ============================================================
   DATASHEET ROOT
   ============================================================ */

.ds-datasheet {
  background: var(--surface);
  border: none;
  border-radius: 0;
  overflow: hidden;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--text);
  /* Required so the draggable popup is positioned relative to the datasheet */
  position: relative;
}

/* ============================================================
   FACTION COLOUR TOKENS
   ============================================================ */

/* NEUTRAL DEFAULT ONLY. Every faction's real colour is supplied at render time
   by faction-colours.js as inline --ds-faction-color / --ds-faction-color-text
   custom properties on .ds-datasheet, sourced from factions.accent_color.
   These values apply only when a faction has no accent_color in the DB — they
   are a deliberately neutral charcoal, NOT a faction colour, and must never be
   set to one. Do not reintroduce per-faction classes here: adding or changing a
   faction colour is a DB-only change. */
.ds-datasheet {
  --ds-faction-color:      var(--banner);
  --ds-faction-color-text: var(--text-inv);
}

/* ============================================================
   SECTION BANNERS — faction colour
   ============================================================ */

.ds-banner {
  background: var(--ds-faction-color);
  color: var(--ds-faction-color-text);
  font-family: var(--font-heading);
  font-size: var(--text-base);
  font-weight: var(--heading-weight);
  letter-spacing: var(--heading-tracking);
  text-transform: var(--heading-transform);
  padding: var(--space-1) var(--space-3);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* Weapon banner: title left, column headers right aligned with table columns */
.ds-banner--weapon {
  justify-content: space-between;
  padding-right: 0; /* cols need to align flush with table */
}

.ds-banner__weapon-left {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 1 1 auto; /* takes up the name column space */
}

/* Column header labels in the banner — match table column widths */
.ds-banner__weapon-cols {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}

.ds-banner__col {
  width: 45px;
  min-width: 45px;
  text-align: center;
  font-family: var(--font-heading);
  font-size: var(--text-base);
  font-weight: var(--heading-weight);
  letter-spacing: var(--heading-tracking);
  color: var(--ds-faction-color-text);
  padding: 0 var(--space-1);
}

.ds-banner__col--range {
  width: 55px;
  min-width: 55px;
}

.ds-banner__col--d {
  padding-right: 14px;
}

/* PNGs are already white — no filter needed */
.ds-banner__icon-img {
  width: 16px;
  height: 16px;
  object-fit: contain;
  opacity: 0.9;
  flex-shrink: 0;
}

/* ============================================================
   HEADER — single full-width block, image absolutely positioned
   ============================================================ */

.ds-header {
  position: relative;
  /* Faction gradient: solid left → transparent right, fading into the surface
     colour. Originally built to dissolve a transparent-PNG unit photo on the
     right; retained deliberately for its own sake now that no photo exists. */
  background:
    linear-gradient(
      to right,
      var(--ds-faction-color) 0%,
      var(--ds-faction-color) 40%,
      rgba(255, 255, 255, 0) 90%
    ),
    var(--surface);
  overflow: hidden;
}

/* Unit photo: absolute, right-anchored, full height, decorative. Widened and
   set to contain so wide transparent-PNG models show whole and extend left over
   the gradient; a left-fading mask dissolves the model into the faction colour.
   Stays at z-index 0 (behind the stats). */
.ds-unit-photo {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: 55%;
  object-fit: contain;
  object-position: right center;
  display: block;
  z-index: 0;
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 55%, #000 100%);
          mask-image: linear-gradient(to right, transparent 0%, #000 55%, #000 100%);
}

/* .ds-unit-photo-placeholder removed in v82 — no photos exist to stand in
   for. .ds-unit-photo is retained above for surfaces that still pass one. */

/* Content sits above the image (z-index 1). The model passes behind the stats,
   so only a little right padding is reserved to keep the rightmost stat box off
   the header edge. */
.ds-header__content {
  position: relative;
  z-index: 1;
  padding: var(--space-3) var(--space-3) var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding-right: 10%;
}

/* ── Row 1: Unit name ── */

.ds-unit-name {
  font-family: var(--font-heading);
  font-size: var(--text-2xl);
  font-weight: var(--heading-weight);
  letter-spacing: var(--heading-tracking);
  text-transform: var(--heading-transform);
  /* Constant, not derived: large bold type with a shadow reads on every accent
     in the palette, light or dark. */
  color: #ffffff;
  margin: 0;
  line-height: 1.05;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}

/* ── Row 2: category badge + base size ── */

.ds-header__meta-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.ds-category-badge {
  display: inline-block;
  font-family: var(--font-heading);
  font-size: var(--text-xs);
  font-weight: var(--heading-weight);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: rgba(0, 0, 0, 0.35);
  color: var(--ds-faction-color-text);
  padding: 3px var(--space-2);
  border-radius: var(--radius-sm);
}

.ds-category-badge--format {
  background: #444444;
  border: none;
}

/* .ds-base-size removed — base size moved to admin block */

/* ── Stat block: 6-column grid ── */

/* DevAgent5a: ds-stat-block IS the grid. 6 equal columns = 6 stats in row 1.
   Inv square in col 3 row 2, label spanning cols 4-6 row 2. */
.ds-stat-block {
  display: grid;
  /* 6 fixed-width stat columns + 1fr for profile name label */
  grid-template-columns: repeat(6, 46px) 1fr;
  column-gap: 8px;
  row-gap: 0;
  margin-top: 2px;
  padding-bottom: var(--space-1);
  align-items: end;
}

/* Stack of stat rows for multi-profile units */
.ds-stat-block-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* Second and subsequent rows: no stat labels (shown on row 1 only) */
.ds-stat-block--nolabels .ds-stat__label {
  display: none;
}

/* Profile name label — col 7, row 1, vertically centred with squares */
.ds-stat__profile-name {
  grid-column: 7;
  grid-row: 1;
  display: flex;
  align-items: center;
  padding-bottom: 2px;
  font-family: var(--font-heading);
  font-size: var(--text-sm);
  font-weight: var(--heading-weight);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.9);
  white-space: nowrap;
}

.ds-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 40px;
}

/* Stat labels: --text-sm (13px) */
.ds-stat__label {
  font-family: var(--font-heading);
  font-size: var(--text-sm);
  font-weight: var(--heading-weight);
  letter-spacing: 0.06em;
  /* No text-transform — Sv and Ld must render in mixed case */
  color: rgba(255, 255, 255, 0.9);
  line-height: 1.2;
}

/* Stat squares */
.ds-stat__value {
  font-family: var(--font-heading);
  font-size: var(--text-xl);
  font-weight: var(--heading-weight);
  color: var(--text-hi);
  line-height: 1;
  background: #ffffff;
  border: 2px solid var(--text-hi);
  width: 46px;
  height: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2px var(--space-1);
}

/* ── Invulnerable save — CSS Grid row 2, column 3 (under Sv) ── */

/* Inv square cell: grid-column 3, grid-row 2 — always under Sv */
.ds-stat__inv-square {
  grid-column: 3;
  grid-row: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-top: 6px;
}

/* Inv label cell: spans cols 4–6, row 2 — stacks the label and the optional
   restriction footnote, vertically centred against the inv shield. */
.ds-stat__inv-label-cell {
  grid-column: 4 / 8;
  grid-row: 2;
  /* Override the stat grid's align-items:end so a single-line label is centred
     against the shield rather than bottom-aligned in the row. */
  align-self: center;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

/* Inv save — shield shape: square top, rounded point at bottom */
.ds-stat__value--inv {
  background: #ffffff;
  border: 2px solid var(--amber-dim);
  color: var(--amber-dim);
  font-family: var(--font-heading);
  font-size: var(--text-xl);
  font-weight: var(--heading-weight);
  line-height: 1;
  width: 46px;
  height: 52px;   /* taller than wide to accommodate the point */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2px var(--space-1) 8px; /* extra bottom padding pushes text up from point */
  /* Shield shape: flat top, sides taper to a point at bottom-centre */
  clip-path: polygon(0% 0%, 100% 0%, 100% 65%, 50% 100%, 0% 65%);
  /* Border via outline since clip-path clips the border */
  border: none;
  outline: 2px solid var(--amber-dim);
  outline-offset: -2px;
}

/* "INVULNERABLE SAVE" label */
.ds-stat-block__inv-label {
  font-family: var(--font-heading);
  font-size: var(--text-xs);
  font-weight: var(--heading-weight);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  /* Derived ink: small thin type is where contrast actually matters. */
  color: var(--ds-faction-color-text);
  opacity: 0.85;
  white-space: nowrap;
}

/* ── Deadly Demise — small skull badge overlapping bottom edge of W square ── */

/* W cell needs position:relative so the badge can be absolute */
.ds-stat--w {
  position: relative;
}

/* Badge: 14px circle, centred horizontally, overlapping bottom edge of W square */
.ds-stat__dd-badge {
  position: absolute;
  bottom: -9px;   /* half of 18px — straddles the bottom edge */
  left: 50%;
  transform: translateX(-50%);
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--text-hi);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
  border: 1px solid rgba(255,255,255,0.4);
}

.ds-stat__dd-skull {
  width: 12px;
  height: 12px;
  object-fit: contain;
  /* PNG is white on transparent — no filter needed */
  display: block;
}

/* ============================================================
   WEAPON TABLES
   ============================================================ */

.ds-section--weapons {
  border-top: 2px solid #000000;
}

.ds-section--weapons + .ds-section--weapons {
  /* Thin separator between ranged and melee weapon sections */
  border-top: var(--border-width) solid var(--border);
}

.ds-weapon-table {
  width: 100%;
  border-collapse: collapse;
  /* Bumped from --text-xs (11px) to --text-sm (13px) */
  font-size: var(--text-sm);
}

.ds-weapon-table th {
  font-family: var(--font-heading);
  /* Bumped from --text-xs to --text-sm */
  font-size: var(--text-sm);
  font-weight: var(--heading-weight);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
  padding: var(--space-1) var(--space-2);
  text-align: center;
  border-bottom: var(--border-width) solid var(--border);
}

.ds-weapon-table th.ds-wt__name,
.ds-weapon-table td.ds-wt__name {
  text-align: left;
  /* auto — fills remaining space after stat columns take their 60px each */
}

/* Stat columns: fixed 45px each — name column gets the rest */
.ds-weapon-table th:not(.ds-wt__name),
.ds-weapon-table td:not(.ds-wt__name) {
  width: 45px;
  min-width: 45px;
  max-width: 45px;
}

/* RANGE column: slightly wider than other stat columns */
.ds-weapon-table th.ds-wt__range,
.ds-weapon-table td.ds-wt__range {
  width: 55px;
  min-width: 55px;
  max-width: 55px;
}

/* D column: extra right space — !important overrides generic td padding */
.ds-weapon-table th.ds-wt__d,
.ds-weapon-table td.ds-wt__d {
  padding-right: 14px !important;
}

.ds-weapon-table td {
  padding: var(--space-1) var(--space-2);
  text-align: center;
  border-bottom: var(--border-width) solid var(--surface-lo);
  vertical-align: middle;
}

/* Stripe applied per weapon group by JS — not nth-child */
.ds-wt__row--stripe {
  background: #f0f0f0;
}

.ds-wt__row--parent td {
  background: var(--amber-light) !important;
  color: var(--text-dim);
  padding: var(--space-1) var(--space-2);
}

.ds-wt__row--sub td {
  /* No padding-left — indent is on the name cell only */
}

.ds-wt__name--sub {
  padding-left: var(--space-5) !important;
}

.ds-wt__name--multi {
  font-weight: 600;
}

.ds-weapon-tags {
  font-size: var(--text-xs);
  color: var(--text-dim);
  /* Display-only: upstream casing is inconsistent per row. See the v92 note. */
  text-transform: uppercase;
}

.ds-weapon-select-note {
  font-size: var(--text-xs);
  color: var(--text-dim);
  font-weight: 400;
  font-style: italic;
}

/* ============================================================
   TWO-COLUMN ROW — Abilities | Unit Composition
   ============================================================ */

.ds-two-col-row {
  display: grid;
  grid-template-columns: 7fr 4fr;
  border-top: var(--border-width) solid var(--border);
  align-items: stretch; /* full height so divider goes all the way down */
}

.ds-two-col-row__left {
  border-right: var(--border-width) solid var(--border);
}

/* ── Abilities ── */

.ds-abilities__body {
  padding: var(--space-2) var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.ds-ability {
  font-size: var(--text-sm);
  line-height: 1.5;
  margin: 0;
}

.ds-ability--shared {
  color: var(--text-dim);
}

.ds-ability__type {
  font-family: var(--font-heading);
  font-weight: var(--heading-weight);
  font-size: var(--text-sm);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text);
}

.ds-ability--unit {
  color: var(--text);
}

.ds-ability__name {
  font-weight: 700;
}

/* Ability descriptions carrying an embedded list — "select one of the
   following:" and its options. The markup is upstream's own; base.css:107
   strips the marker and the global reset strips the indent, so both are
   restored here. Same remedy as .ds-wargear-options__item ul (v78) and
   .ds-popup__body ul (v81). */
.ds-ability__desc ul,
.ds-ability__desc ol {
  margin: var(--space-1) 0 0 0;
  padding-left: var(--space-5);
  list-style: disc;
}

.ds-ability__desc ol {
  list-style: decimal;
}

/* Upstream nests a further level where a choice bundles sub-choices. */
.ds-ability__desc ul ul {
  margin-top: var(--space-1);
  padding-left: var(--space-4);
  list-style: circle;
}

.ds-ability__desc li {
  font-size: inherit;
  line-height: 1.5;
  color: inherit;
}

.ds-ability__desc li + li {
  margin-top: var(--space-1);
}

/* ── Unit Composition + Points ── */

.ds-composition__body {
  padding: var(--space-2) var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.ds-composition__body p {
  font-size: var(--text-sm);
  line-height: 1.5;
  margin: 0;
}

/* Composition lines — one bullet per row, as printed. */
.ds-composition__list {
  margin: 0;
  padding-left: var(--space-5);
  list-style: square;
}

.ds-composition__item {
  font-size: var(--text-sm);
  line-height: 1.5;
  font-weight: 700;
  color: var(--text);
}

.ds-composition__item + .ds-composition__item {
  margin-top: var(--space-1);
}

.ds-composition__profile + .ds-composition__profile {
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: var(--border-width) solid var(--border);
}

.ds-composition__profile p + p {
  margin-top: 4px;
}

/* .ds-composition__equipment dim colour removed — DevAgent5a v8 */

.ds-comp-points {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-top: var(--space-1);
  border-top: var(--border-width) solid var(--border);
  padding-top: var(--space-2);
}

.ds-comp-points-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

/* .ds-comp-points-row__label dim colour removed — DevAgent5a v8 */

.ds-comp-points-row__badge {
  font-family: var(--font-heading);
  font-size: var(--text-sm);
  font-weight: var(--heading-weight);
  background: var(--amber);
  color: var(--text-inv);
  padding: 1px var(--space-2);
  border-radius: var(--radius-sm);
  min-width: 36px;
  text-align: center;
}

/* ============================================================
   WARGEAR OPTIONS
   ============================================================ */

.ds-section--wargear {
  border-top: var(--border-width) solid var(--border);
}

.ds-wargear-list {
  margin: 0;
  padding: var(--space-2) var(--space-3) var(--space-2) var(--space-6);
  list-style: square;
}

.ds-wargear-list > li {
  margin-bottom: var(--space-1);
}

.ds-wargear-list > li:last-child {
  margin-bottom: 0;
}

.ds-wargear-list li {
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text);
}

.ds-wargear-suboptions {
  margin: var(--space-1) 0 0 var(--space-2);
  padding-left: var(--space-4);
  list-style: circle;
}

.ds-wargear-suboptions li {
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text);
}

/* ============================================================
   KEYWORDS ROW — stays charcoal
   ============================================================ */

.ds-keywords-row {
  display: grid;
  grid-template-columns: 7fr 4fr;
  border-top: 2px solid var(--banner);
  background: var(--banner);
}

.ds-keywords-cell {
  padding: var(--space-2) var(--space-3);
  font-family: var(--font-heading);
  font-size: var(--text-base);
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: 0.02em;
  color: var(--banner-text);
  /* display:block — label and value are inline text nodes that wrap together */
  display: block;
}

/* Two-tier keyword. The ratio is explicit because synthesised small caps are
   not adjustable — see the v91 note. */
.ds-keyword {
  white-space: nowrap;
}

.ds-keyword__rest {
  font-size: 0.86em;
  text-transform: uppercase;
}

.ds-keywords-cell--faction {
  border-left: var(--border-width) solid rgba(255, 255, 255, 0.15);
}

.ds-keywords__label {
  display: inline;
  font-family: var(--font-heading);
  font-weight: var(--heading-weight);
  font-size: var(--text-sm);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.7;
}

/* ============================================================
   ABILITY LINKS — dashed underline buttons
   ============================================================ */

/* DevAgent5a - 2026-05-28
   What: Ability names rendered as inline buttons with dashed underline.
         Tapping opens AbilityPopup. No default button chrome.
   Why:  Keeps the datasheet clean while making ability descriptions accessible. */

.ds-ability__link {
  display: inline;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font-family: inherit;
  font-size: inherit;
  font-weight: inherit;
  color: var(--text);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-style: dashed;
  text-underline-offset: 3px;
  text-decoration-color: var(--text-dim);
}

.ds-ability__link:hover {
  color: var(--amber-dim);
  text-decoration-color: var(--amber-dim);
}

.ds-ability__link--unit {
  font-weight: 700;
}

/* ============================================================
   ABILITY POPUP — draggable floating block
   ============================================================ */

/* DevAgent5a - 2026-05-28
   What: Popup is absolute-positioned inside .ds-datasheet (position:relative).
         No backdrop. User drags via the header. Width 380px (100px less than
         the previous 480px modal). z-index above all content but below nothing.
   Why:  Floating reference UX — user can read popup alongside datasheet content. */

.ds-popup {
  position: absolute;
  z-index: var(--z-above);
  background: var(--surface);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  width: 280px;
  overflow: hidden;
  /* Header fixed, body scrolls. */
  display: flex;
  flex-direction: column;
  max-height: min(560px, calc(100vh - 160px));
  /* Prevent text selection while dragging */
  user-select: none;
}

.ds-popup__header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  background: var(--ds-faction-color);
  color: var(--ds-faction-color-text);
  padding: var(--space-2) var(--space-3);
  cursor: grab;
}

.ds-popup__header:active {
  cursor: grabbing;
}

.ds-popup__title {
  font-family: var(--font-heading);
  font-size: var(--text-md);
  font-weight: var(--heading-weight);
  letter-spacing: var(--heading-tracking);
  text-transform: var(--heading-transform);
}

.ds-popup__close {
  position: absolute;
  right: var(--space-2);
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  color: var(--ds-faction-color-text);
  font-size: var(--text-xl);
  line-height: 1;
  cursor: pointer;
  padding: 0 var(--space-1);
  opacity: 0.8;
}

.ds-popup__close:hover {
  opacity: 1;
}

.ds-popup__body {
  padding: var(--space-4);
  font-size: var(--text-xs);
  line-height: 1.6;
  color: var(--text);
  /* min-height: 0 lets this flex child shrink below its content height, which
     is what allows overflow-y to engage at all. */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  /* Long rule text is worth being able to select, unlike the drag header. */
  user-select: text;
}

/* Upstream rule text ships real list markup; the sanitiser strips its inline
   list-style, so the markers are declared here. */
.ds-popup__body ul,
.ds-popup__body ol {
  margin: var(--space-2) 0;
  padding-left: var(--space-5);
  list-style: square;
}

.ds-popup__body ol {
  list-style: decimal;
}

.ds-popup__body ul ul {
  margin: var(--space-1) 0 0 0;
  padding-left: var(--space-4);
  list-style: circle;
}

.ds-popup__body li + li {
  margin-top: var(--space-1);
}

.ds-popup__body p {
  margin: 0 0 var(--space-2) 0;
}

.ds-popup__body p:last-child {
  margin-bottom: 0;
}

/* ============================================================
   ADMIN BLOCK — dev/reference metadata, below keywords
   ============================================================ */

.ds-admin-block {
  padding: var(--space-2) var(--space-3);
  background: var(--surface-lo);
}

.ds-admin-block__title {
  font-family: var(--font-heading);
  font-size: var(--text-xs);
  font-weight: var(--heading-weight);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: var(--space-1);
}

.ds-admin-block__fields {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px var(--space-4);
}

.ds-admin-block__row {
  display: flex;
  gap: var(--space-2);
  font-size: var(--text-xs);
  line-height: 1.5;
}

.ds-admin-block__label {
  color: var(--text-dim);
  white-space: nowrap;
  flex-shrink: 0;
  min-width: 80px;
}

.ds-admin-block__value {
  color: var(--text);
  font-family: var(--font-body);
}

/* ============================================================
   LEADER BLOCK — leads / led by, inside Unit Composition
   ============================================================ */

.ds-leader-block {
  /* No margin or border — sits flush as direct child of ds-composition */
  overflow: hidden;
  color: var(--text);
  font-family: var(--font-body);
}

.ds-leader-block__body {
  padding: var(--space-2) var(--space-3);
  color: var(--text);
  font-family: var(--font-body);
  background: var(--surface);
}

.ds-leader-block__intro {
  font-size: var(--text-sm);
  color: var(--text);
  margin: 0 0 var(--space-1);
}

.ds-leader-block__list {
  margin: 0;
  padding-left: var(--space-4);
  list-style: disc;
}

.ds-leader-block__list li {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text);
  line-height: 1.5;
}

/* leader_footer — upstream's attachment note. Sits outside __body, so it
   carries its own matching padding. */
.ds-leader-block__desc {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  font-style: italic;
  line-height: 1.5;
  color: var(--text);
  background: var(--surface);
}

/* ============================================================
   SPECIAL ABILITY BLOCKS — Hunter Organism, Damaged, Transport
   Full-width banner blocks in the right (composition) column
   ============================================================ */

.ds-special-ability-block {
  overflow: hidden;
}

.ds-banner--special {
  display: flex;
  align-items: center;
  gap: 4px;
  /* Inherits font-family, font-size, font-weight from .ds-banner */
}

.ds-banner__skull {
  width: 14px;
  height: 14px;
  object-fit: contain;
  flex-shrink: 0;
  filter: brightness(0) invert(1);
}

.ds-special-ability-block__body {
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text);
  /* font-family inherits from .ds-datasheet */
}

/* ── Ability bullet list (from '* ' patterns in descriptions) ── */
.ds-ability__bullet-list {
  margin: var(--space-1) 0 0 0;
  padding-left: calc(var(--space-4) + 15px);
  list-style: disc;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.ds-ability__bullet-list li {
  font-size: inherit;
  line-height: 1.5;
  color: inherit;
}

/* ============================================================
   SCROLLABLE BODY
   DevAgent5b - 2026-05-28 (v57)
   What: Wraps the datasheet so it scrolls under the fixed header.
         Mirrors .ab-col__list from army-builder-col1.css exactly.
   Why:  Datasheet was not scrollable — header and content scrolled together.
   ============================================================ */

.ab-col-2__body {
  flex: 1;
  overflow-y: auto;
  min-height: 0;
}

/* ============================================================
   HEADER CENTERING SPACER
   DevAgent5b - 2026-05-28 (v57)
   What: Invisible left-side element matching the button width so the
         title flex item is balanced and truly centred in the header.
   Why:  ab-col__title uses flex:1 + text-align:center — without a
         counterweight on the left the title drifts right of centre.
   ============================================================ */

.ab-col-2__header-spacer {
  /* Must match the computed width of .ab-col-2__header-add-btn */
  width: 120px;
  flex-shrink: 0;
}

/* ============================================================
   ADD TO ARMY HEADER BUTTON
   DevAgent5b - 2026-05-28 (v60)
   What: Button height set to 32px — matches Col1's .ab-col__view-btn exactly
         so the header needs no padding override. Width stays 120px.
         min-height removed.
   Why:  32px button + default var(--space-3) padding = same header height as Col1/Col3.
   ============================================================ */

.ab-col-2__header-add-btn {
  flex-shrink: 0;
  width: 120px;
  height: 32px;
  background: var(--amber);
  border: none;
  border-radius: var(--radius);
  color: var(--text-inv);
  font-family: var(--font-heading);
  font-size: var(--text-sm);
  font-weight: var(--heading-weight);
  letter-spacing: var(--heading-tracking);
  text-transform: var(--heading-transform);
  cursor: pointer;
  transition: background 80ms ease;
  box-shadow: var(--shadow-sm);
  white-space: nowrap;
}

.ab-col-2__header-add-btn:active {
  background: var(--amber-dim);
}






.ab-col-2__header-add-btn__label {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
}

/* Light spinner variant for use on coloured backgrounds */
.spinner-sm--light {
  border-color: rgba(255, 255, 255, 0.35);
  border-top-color: #ffffff;
}

/* ============================================================
   COLUMN DIVIDER BORDERS
   DevAgent5b - 2026-05-28 (v62)
   What: 2px dark-gray right border separates Col2 from Col3.
         Header bottom border overridden to match column separator colour.
   Why:  Stronger visual separation. army.css border-right removed (v42).
   ============================================================ */

.ab-col--2 {
  border-right: 2px solid #444444;
}

.ab-col--2 .ab-col__header {
  border-bottom-color: #444444;
}

/* ============================================================
   DEVAGENT10 ADDITIONS — 2026-06-01
   ============================================================ */

/* ── Invulnerable save footnote (under the INVULNERABLE SAVE label, light text) ── */
.ds-stat__inv-footnote {
  margin-top: 2px;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-style: italic;
  line-height: 1.3;
  color: var(--ds-faction-color-text);
  opacity: 0.85;
  white-space: nowrap;
}

/* ── Weapon rules footnote (type='Wargear profile') ── */
/* Sits between the melee table and the two-column row, where GW prints it.
   The rule names a tag carried by a weapon row above; no column links the two,
   so the reader makes the connection via the tag. */

.ds-section--weapon-rules {
  border-bottom: var(--border-width) solid var(--border);
}

.ds-weapon-rules__body {
  /* Left padding aligns the text with the weapon NAME column, which sits at
     the table's own td padding. */
  padding: var(--space-2) var(--space-3) var(--space-2) var(--space-4);
}

.ds-weapon-rules__item {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text);
}

.ds-weapon-rules__item + .ds-weapon-rules__item {
  margin-top: var(--space-1);
}

.ds-weapon-rules__name {
  font-weight: 700;
}

/* ── Wargear options ── */
/* The description is upstream HTML: a lead-in sentence, a <br>, then a <ul> of
   choices. Markers are declared here because the sanitiser strips upstream's
   inline list-style. */

.ds-wargear-options__body {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  /* Markers are drawn on the item, not by the list. */
  list-style: none;
}

.ds-wargear-options__item {
  position: relative;
  padding-left: var(--space-4);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text);
}

.ds-wargear-options__item::before {
  content: '';
  position: absolute;
  left: 0;
  /* Vertically centred on the first line: half the line-box minus half the box. */
  top: calc(0.75em - 3px);
  width: 7px;
  height: 7px;
  background: var(--text);
}

/* button='*' — a note about the options above, not an option. Upstream's own
   asterisk opens the text, so no marker and no indent. */
.ds-wargear-options__item--note {
  font-style: italic;
  padding-left: 0;
}

.ds-wargear-options__item--note::before {
  content: none;
}

.ds-wargear-options__item + .ds-wargear-options__item {
  margin-top: var(--space-2);
}

.ds-wargear-options__item ul {
  margin: var(--space-1) 0 0 0;
  padding-left: var(--space-6);
  /* Circle — as upstream's own list-style-type declares, before the sanitiser
     strips it. */
  list-style: circle;
}

/* Upstream nests a further level for bundled choices. */
.ds-wargear-options__item ul ul {
  margin-top: var(--space-1);
  padding-left: var(--space-4);
  list-style: disc;
}

.ds-wargear-options__item li {
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text);
}

.ds-wargear-options__item li + li {
  margin-top: var(--space-1);
}

/* ── Ability groups (parent/child, e.g. "Author of the Codex") ── */
/* Rendered full-width after the Abilities body; header reuses .ds-banner. */
.ds-ability-group {
  border-top: var(--border-width) solid var(--border);
}

.ds-ability-group__body {
  padding: var(--space-2) var(--space-3);
}

.ds-ability-group__intro {
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text);
  margin: 0 0 var(--space-1);
}

.ds-ability-group__entries {
  margin: 0;
  padding-left: var(--space-4);
  list-style: disc;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.ds-ability-group__entry {
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text);
}

.ds-ability-group__entry-name {
  font-weight: 700;
}

/* ── Leader block special description (e.g. Apothecary Biologis) ── */
.ds-leader-block__desc {
  font-size: var(--text-sm);
  font-style: italic;
  line-height: 1.5;
  color: var(--text);
  margin: var(--space-1) 0 0;
}

/* ── Wargear option condition/scope prefix ── */
.ds-wargear-condition {
  font-style: italic;
  font-weight: 600;
  color: var(--text-dim);
}

/* ============================================================
   EQUIPMENT PILLS (model_equipment) — DevAgent22 v74
   Home for the .ds-equip-* rules (previously in codex-col3.css v2's transitional
   home). Placed here — the .ds-* namespace owner, loaded on the Army Builder AND
   Codex pages — so the Builder datasheet styles its pills (codex-col3.css is NOT
   loaded on the builder page). Type-coloured solid scheme; the .ds- twin of
   army-view-col2.css v8. Ranged/melee use white text; equipment (light violet)
   uses dark text. No unselected/outline variant (that lives in the Col3 modal).
   ============================================================ */
.ds-equip-intro { margin: 0; }

.ds-equip-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 4px;
}

.ds-equip-pill--ranged    { --pill-c: #2f7d4f; } /* green */
.ds-equip-pill--melee     { --pill-c: #a9793f; } /* light brown */
.ds-equip-pill--equipment { --pill-c: #a98be6; } /* light violet */


.ds-equip-pill {
  display: inline-flex;
  align-items: center;
  /* Centre the pill on the surrounding text — see the v103 note. */
  vertical-align: middle;
  font-size: var(--text-xs);
  line-height: 1.4;
  padding: 1px 8px;
  border-radius: 999px;
  border: var(--border-width) solid var(--pill-c, var(--border));
  background: var(--pill-c, var(--surface));
  color: #ffffff;
  white-space: nowrap;
}

/* Both tables — striped, not a chosen side.
   ⚠ Doubled class on purpose: this must outrank .ds-equip-pill's `background`
   shorthand by SPECIFICITY, not by source order. See the v99 note. */
.ds-equip-pill.ds-equip-pill--dual {
  border-color: #6b6a3f;
  color: #ffffff;
  background: repeating-linear-gradient(
    115deg,
    #2f7d4f 0px, #2f7d4f 4px,
    #a9793f 4px, #a9793f 8px
  );
}

/* Upstream writes the count as a bare number before the name. */
.ds-equip-pill__qty {
  margin-left: 5px;
  font-weight: 700;
  font-size: 1.05em;
  opacity: 1;
}

.ds-equip-pill__type {
  font-weight: 700;
  color: #ffffff;
  margin-right: 5px;
}

.ds-equip-pill--equipment,
.ds-equip-pill--equipment .ds-equip-pill__type {
  color: #2e1f47;
}


/* ============================================================
   LIST-CONVENTION TEXT (DataAgent4 §3) — DevAgent23 v75
   Home for the .ds-cv-* rules (previously in codex-col3.css v2/v3's transitional
   home). Placed here — the .ds-* namespace owner, loaded on the Army Builder AND
   Codex pages — so the Builder datasheet's WARGEAR OPTIONS / WARGEAR ABILITIES
   convention text styles (codex-col3.css is NOT loaded on the builder page).
   .ds-cv-list / .ds-cv-sublist mirror the GW wargear-bullet styling (square /
   circle markers, hanging indents) of .ds-wargear-list without its section
   padding, so the renderer also works inline inside ability rows.
   ============================================================ */
.ds-cv-section {
  border-top: var(--border-width) solid var(--border);
}

.ds-cv-body {
  padding: var(--space-2) var(--space-3);
}

.ds-cv-para {
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text);
  margin: 0 0 var(--space-1);
}

.ds-cv-para:last-child {
  margin-bottom: 0;
}

.ds-cv-list {
  margin: var(--space-1) 0;
  padding-left: var(--space-4);
  list-style: square;
}

.ds-cv-list:last-child {
  margin-bottom: 0;
}

.ds-cv-list li {
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text);
  margin-bottom: var(--space-1);
}

.ds-cv-list li:last-child {
  margin-bottom: 0;
}

.ds-cv-sublist {
  margin: var(--space-1) 0 0 var(--space-2);
  padding-left: var(--space-4);
  list-style: circle;
}
