/* cogitator/army-editor.css v148 */

/* DevAgent71 - 2026-10-15
   What: v148 — .apu-row loses its --text-sm: the rows now take the modal
         body's own size, the same as the intro sentence above them.
   Why:  Jérôme, iPad: the "models" line can have bigger text too. */

/* DevAgent71 - 2026-10-15
   What: v147 — .apu-card__bar text larger (--text-sm -> --text-md) with more
         padding (--space-2 / --space-3) and a slight letter-spacing
         (0.02em) instead of the heading tracking; rows indented to match.
         Colours unchanged.
   Why:  Jérôme, iPad: "the text in the red headers is barely readable". */

/* DevAgent71 - 2026-10-14
   What: v146 — the points notice is a modal (army-editor.js v96, DEV71-13 to
         DEV71-16). NEW .apu-modal and .apu-* : one card per unit / detachment
         after the Munitorum Field Manual — a coloured bar (name + arrow), then
         dotted-leader rows "(±N) new pts". Increase red (--red), decrease
         green (--green), both ways neutral (--text-dim). .apu-total, the
         closing line. The v145 .abs__points rules are REMOVED (nothing renders
         them any more).
   Why:  Jérôme after the iPad check: "It should be a modal", Field Manual
         layout, our own colours. */

/* DevAgent71 - 2026-10-14
   What: v145 — NEW .abs__points (+ __head, __title, __close, __list): the
         points notice, a strip under the army banner (army-editor.js v95,
         DEV71-2). Neutral, not amber or red: it reports, it flags nothing.
         Its list scrolls past 160px so the columns keep their height.
   Why:  v22 stage S8 — points changed with newer data, told once per device. */


/* DevAgent70 - 2026-10-14
   What: v144 — .create-army .format-option--selected.format-option--outdated:
         a selected Outdated detachment in Edit Army Details is red (border,
         fill, name, checkmark) instead of green (army-create.js v18).
   Why:  Jérôme: red until the player switches to a valid detachment
         (DEV70-32). */


/* DevAgent70 - 2026-10-14
   What: v143 — NEW .abs__name-row: the army name and the ⚠ OUTDATED label on
         one line (the name still ellipsises; the label never shrinks). NEW
         .abs__det-flag: the small ⚠ after an Outdated detachment's name in
         the facts row, a bare tappable triangle (army-editor.js v94).
   Why:  Jérôme, iPad: the army-level flag beside the name; a deprecated
         detachment marked where it is named (DEV70-31). */


/* DevAgent70 - 2026-10-14
   What: v142 — an Outdated row's Dismiss is a fixed 100px; the action button
         fills the rest of the row. Type, colours and height stay the plain
         .ap-modal__cancel / .ap-modal__confirm.
   Why:  Jérôme, iPad: Dismiss smaller, about 100px (DEV70-30). */


/* DevAgent70 - 2026-10-14
   What: v141 — an Outdated row's two buttons take equal widths across the
         row (.ae-row-actions > button: flex 1 1 0). Type, colours and height
         stay the plain .ap-modal__cancel / .ap-modal__confirm.
   Why:  Jérôme: similar-sized buttons make more sense here (DEV70-29). */


/* DevAgent70 - 2026-10-14
   What: v140 — an Outdated row's buttons are the modal footer's, exactly:
         .ae-row-actions is the footer's right-aligned row (gap
         var(--space-3)), and .ae-row-btn / --major / --minor lose every
         rule, so Dismiss is a plain .ap-modal__cancel and the action a plain
         .ap-modal__confirm, as in Edit Name.
   Why:  Jérôme: "should use the same exact styling as this" (DEV70-28). */


/* DevAgent70 - 2026-10-14
   What: v139 — .ae-row-btn--minor (Dismiss) takes the action button's type:
         heading font, weight, tracking and case, at the same size. Outlined
         beside the filled action; only width and fill differ.
   Why:  Jérôme: same font, case and size (DEV70-27). */


/* DevAgent70 - 2026-10-14
   What: v138 — .ae-row-btn--minor takes the same text size as --major
         (--text-md); only its width stays smaller.
   Why:  Jérôme: Dismiss and Update should have the same text size. */


/* DevAgent70 - 2026-10-14
   What: v137 — .ae-row-btn--major / --minor: an Outdated row's acting button
         (Update to Latest Version, Open Army Details, Remove Unit) wider with
         larger text, its Dismiss / Keep button narrower and smaller
         (army-editor.js v92).
   Why:  Jérôme, iPad: the update button bigger, Dismiss smaller (DEV70-26). */


/* DevAgent70 - 2026-10-14
   What: v136 — an Outdated row's reasons lose the list indent, flush with the
         intro above them (.ae-outdated-part .ae-issue-list; army-editor.js v90).
   Why:  Jérôme, iPad: both text blocks aligned the same (DEV70-23). */


/* DevAgent70 - 2026-10-14
   What: v135 — NEW .ae-row-actions / .ae-row-btn: an Outdated row's own
         buttons, medium and side by side (army-editor.js v89, DEV70-20). The
         section's Update All / Keep Old Versions keep the modal's full size.
   Why:  Jérôme, iPad check: resolve each part on its own, or all at once. */


/* DevAgent70 - 2026-10-14
   What: v134 — v22 stage S7, OUTDATED PARTS (army-editor.js v88,
         army-editor-datasheet.js v16, army-picker.js v34):
         · .abs-outdated-tag is a BUTTON now (DEV70-16): pointer-events
           restored, a pointer cursor and the native button chrome reset. Its
           look is v117's.
         · NEW .abs__outdated — the banner's warning sign and 'Outdated', in
           the tag's red fill so it reads on every faction accent.
         · NEW .apk-review--outdated — the armies home card's 'Outdated'
           strip, the .apk-review shape in red.
         · NEW .ae-review-section, .ae-review-section__title,
           .ae-review-actions, .ae-outdated-part (+ __name, __confirm,
           __error) — the load and unit modals' sections and rows.
         · .abs-unit__outdated is unchanged (the card badge stays text).
   Why:  the Outdated surfaces of starter rulings and DEV70-11, -16, -17. */


/* DevAgent65 - 2026-10-11
   What: v133 — the WARGEAR OPTIONS banner's triangle (.abc2-stale-flag) is
         filled white, so it reads on any faction colour; its outline and '!'
         stay amber. CSS only — the SVG's first path is the triangle body.
   Why:  Jérôme, iPad: transparent, it was hard to see on the dark banner. */

/* DevAgent65 - 2026-10-11
   What: v132 — datasheet.js v95:
         · .abc2-stale-flag is the bare warning triangle now — no box, no text —
           in the army card flag's own treatment (amber, inline-flex, 13px SVG).
         · .abc2-outdated-remove: the × on each OUTDATED WARGEAR row, a stepper-
           sized button in the row's red.
   Why:  Jérôme, iPad review: the triangle is enough; a line must be removable. */

/* DevAgent65 - 2026-10-10
   What: v131 — OUTDATED WARGEAR (datasheet.js v94): the stale choices are the
         first block of the wargear editor, in the regular item-row look on a
         RED ground (.abc2-item--outdated, --red / --red-light, the theme's
         existing pair), with an italic '⚠ No longer on the datasheet' note
         under the pill (.abc2-item__outdated-note). v130's hatched box
         (.abc2-stale, .abc2-stale__*) is removed.
   Why:  Jérôme, iPad review: at the top, under its own header, as a regular
         option highlighted in red. */

/* DevAgent65 - 2026-10-10
   What: v130 — after the iPad review (DEV65-9, DEV65-10):
         · REMOVED: .abs-unit__review and the triangle's pointer cursor — the
           card's review line and tap target are withdrawn (army-editor-units.js
           v35). The card is v128's again.
         · KEPT: .ae-pop* and the blocked-arrow override — the blocked Add
           popover (army-editor-browser.js v17) stays as built.
         · NEW: .ae-issue-list / .ae-issue-footer — the load and unit modals
           (army-editor.js v86): a list of names or issues, and a footer of up to
           three full-width buttons, stacked, because three labels do not fit
           side by side in .ap-modal's 372px (see the v106 note on
           .abc2-preset-footer--triple).
         · NEW: .abc2-stale-flag / .abc2-stale* — WARGEAR OPTIONS' '⚠ Outdated'
           label and its read-only list of stale choices (datasheet.js v93).
   Why:  Jérôme: the card line and triangle were too small to tap; a triangle
         means "open the unit", and the unit says what is wrong. */

/* DevAgent65 - 2026-10-10
   What: v129 — the tap-to-read popover (.ae-pop*, army-editor-popover.js v1),
         the unit card's 'Wargear needs review' line (.abs-unit__review,
         army-editor-units.js v34), a pointer cursor on the card's triangle,
         and the blocked Add arrow kept grey on hover/press.
   Why:  Gate 1 P4/P5 (DEV65-3, -4, -5): warnings readable on the iPad, where
         a `title` is never shown.
   ⚠ THE BLOCKED ARROW IS NO LONGER :disabled (army-editor-browser.js v17
     makes it aria-disabled so the tap arrives), so .abc1-add:hover:not(:disabled)
     would now tint it amber. .abc1-add--blocked:hover / :active override that;
     its look is otherwise exactly v128's.
   ⚠ .ae-pop LOOKS LIKE THE CODEX GLOSSARY POPOVER (.ds-popup, datasheet.css)
     but is its own rule set: that one is absolute inside .ds-datasheet and
     draggable; this one is fixed, rendered into <body>, and closes on a tap
     outside. Nothing else in this file changed. */

/* DevAgent64 - 2026-10-09
   What: v128 — the force disposition moves into the modal's header (army-
         create.js v17): .dtx-modal__name is a size up (--text-xl), and
         .dtx-modal__fd, in parentheses after it, keeps v127's value size
         (--text-md). v127's body box and its __fd-label / __fd-value rules
         are removed.
   Why:  Jérôme: the box took too much space. */

/* DevAgent64 - 2026-10-09
   What: v127 — .dtx-modal__fd / __fd-label / __fd-value: the detachment's
         force disposition at the top of the reading modal's body
         (army-create.js v16). The card shows it in its existing
         .format-option__desc line, so no card rule changes.
   Why:  Jérôme: the force disposition was missing from the step. */

/* DevAgent64 - 2026-10-09
   What: v126 — .dtx-strat__alt / __alt-text / __alt-cp: a stratagem's
         alternative effect that costs extra CP (detachment-text.js v4), as
         the printed card draws it — under a dotted rule, the extra cost in
         the turn colour at the right ('+1 CP').
         · .dtx-strat__more > li: a secondary effect that is a bare <li>, with
           no <ul> of its own (Methodical Brutality), keeps its bullet inside
           the block instead of hanging past its left border.
   Why:  Hulking Brutes' 'Or:' line costs +1CP in the book (Jérôme's photo). */

/* DevAgent64 - 2026-10-09
   What: v125 — .abw-detachment__origin is removed with the chapter badge it
         styled (army-create.js v13). ⚠ Dated entries naming it are history.
   Why:  Jérôme: redundant — the chapter is named above the cards. */

/* DevAgent64 - 2026-10-09
   What: v124 — iPad review (Jérôme), with army-create.js v12.
         · .abw-subtitle / __army: the line under the Detachment title —
           faction (• chapter) bold, in capitals, in the faction's colour
           (set inline), then the battle size. The title above it drops to
           var(--space-2) below (.abw-step-title--with-sub).
         · .abw-dp--footer: the Detachment Points count moves into the
           footer beside Next — the same .abw-dp parts, without the box's
           bottom margin, centred in the row. .abw-dp--over still turns it red.
         · .abw-count / --none are removed with the "N detachments selected"
           text they styled.
         ⚠ army.css's .create-army__cancel-btn has no reader in army-create.js
           any more (v12 drops the footer Cancel). It is left in army.css:
           this session cannot see every file that might use it.
   Why:  Fewer boxes on the step; the count sits where the decision is made. */

/* DevAgent64 - 2026-10-09
   What: v123 — each (?) sits closer to its own card and further from the
         next column's (Jérôme, iPad review).
         · .abw-detachments: column-gap var(--space-8) (32px), was
           var(--space-3) (12px) like the rows; the cards narrow by 10px each.
           The row gap stays var(--space-3).
         · .abw-detachment-row: the card-to-circle gap is 6px, was 10px.
   Why:  At 10px from its card and 12px from the next one, a circle read as
         belonging to either. */

/* DevAgent64 - 2026-10-09
   What: v122 — at 840px (army.css v102) the steps' content widens with the
         page instead of leaving room beside it (Jérôme, iPad review).
         · Battle size: .create-army .format-grid--row uses auto-fit, not
           army.css's auto-fill, so the three cards stretch across the row.
           auto-fill kept a fourth, empty column at 840px and pushed the cards
           left. Scoped to .create-army; army.css's rule is untouched.
         · Detachment cards: two columns, always — repeat(2, minmax(0, 1fr))
           replaces auto-fill minmax(240px), which made three at 840px. The
           cards widen instead.
   Why:  The width was meant for the content, not for more columns. */

/* DevAgent64 - 2026-10-09
   What: v121 — iPad review (Jérôme).
         · The (?) sits 10px from its card, was 20px (.abw-detachment-row gap).
         · .abw-detachment-group__title is removed: a chapter army's group
           titles now wear army.css's .create-army__label, the look of the
           wizard's other field labels (army-create.js v11).
   Why:  The wizard is 840px wide now (army.css v102); the circle reads as the
         card's own, and the group titles as the step's labels. */

/* DevAgent64 - 2026-10-09
   What: v120 — iPad review (Jérôme).
         · .create-army__body.abw-body--detachment is removed: the Detachment
           step keeps the wizard's 640px, like the other two steps. With it,
           .abw-detachments' column minimum goes back to v118's 240px — two
           columns either way at 640px.
         · .dtx-modal is 560px wide (was 720px), and its height is capped at
           the viewport less 200px, so 100px stays clear above and below.
           Scoped .ap-modal.dtx-modal (0,2,0), so it wins over .ap-modal's own
           max-height without depending on load order.
   Why:  The two-column (1/3 + 2/3) layout was dropped; the step no longer
         needs the room. */

/* DevAgent64 - 2026-10-09
   What: v119 — the Detachment step's reading modal and its (?) circles
         (army-create.js v9), and the shared detachment text panel
         (detachment-text.js v1). Appended at the end, in two blocks:
         · WIZARD: .abw-body--detachment (the step widens to 1070px at most),
           .abw-detachment-group / __title (a chapter army's two groups),
           .abw-detachment-row (card + circle), .abw-detachment__help (the
           (?) — lighter and shorter than the card, centred on it, 20px
           away), .abw-count (the footer's "N detachments selected").
           .abw-detachments' column minimum rises 240px -> 280px, in place,
           so a card keeps its width with the circle beside it.
         · PANEL: .dtx-modal (the house .ap-modal at 720px, in its own class;
           .ap-modal itself is untouched) and every .dtx-* rule. Stratagem
           bands take the book's turn colours — green either player's, blue
           yours, red the opponent's — as --dtx-turn on the block; the band
           also prints the turn, so colour never carries it alone. Lists get
           their markers back inside .dtx (base.css removes them).
   Why:  Let a player read a detachment before choosing it (starter DevAgent64,
         Gate 1 rulings). */

/* DevAgent62 - 2026-10-08
   What: v118 — the Force Disposition step left the creation wizard
         (army-create.js v8), so its two rules go: .abw-dispositions and
         .create-army .abw-disposition:disabled, with the latter's comment. The
         SELECTED CARD STATE note no longer lists force disposition. Nothing
         else changes; edit mode reuses existing classes (.abw-note,
         .abw-note--warn, army.css's .create-army__input--locked).
         ⚠ Dated entries below naming .abw-disposition are history.
   Why:  Starter DevAgent62 §2 — the step leaves the wizard; dead CSS goes with
         it. */

/* DevAgent60 - 2026-10-07
   What: v117 — two Gate 6 review changes (Jérôme).
         · The OUTDATED label is RED, on the card and in the header: filled
           var(--red) with var(--text-inv), the theme's red (the card's
           modified points, over budget). The hatch is dropped from both
           labels — the colour carries it now.
         · An outdated unit's datasheet column takes the older-version look
           (DEV60-12): .ds-older on the column (army-editor-datasheet.js v15),
           styled in datasheet.css v128. Only this column's HEADER is styled
           here — the same hatch, via var(--older-hatch), !important for the
           same inline-shorthand reason codex.css gives.
   Why:  Jérôme review. */

/* DevAgent60 - 2026-10-07
   What: v116 — the OUTDATED marks (Gate 6, ruling DEV60-6), appended at the
         end of the file. Two places, one look:
         · .abs-unit__outdated — a badge in the unit card's badge row
           (army-editor-units.js v33). It joins the shared badge rule's shape
           and takes the HATCH the Codex uses for an older datasheet (codex.css
           v5, DEV60-9), on a neutral ground: amber is Warlord's and the tier's,
           green is Bodyguard's, and outdated is a fact about the data, not a
           warning — so it is neither colour.
         · .abs-outdated-tag — the same mark in the Army Editor's datasheet
           header (army-editor-datasheet.js v14), on the faction accent, so its
           ground is the translucent white chip the header's other controls
           wear. Out of flow, right-aligned, so the spacers still centre the
           title; .abs-col-header--outdated reserves the room on BOTH sides so
           a long title wraps short of it and stays centred.
         ⚠ The datasheet itself is NOT restyled in the Editor (DEV60-11).
   Why:  Show which units resolve from datasheet rows that have since changed. */

/* DevAgent56 - 2026-09-21
 and .combine-modal__option is a button.
         Scoped to .ap-modal so it reaches the attach and combine-or-separate
         dialogs and nothing else.
   Why:  Jérôme: more space between the two lines of the explanation text. */

/* DevAgent45 - 2026-09-14
   What: v76 — unit names read bolder and their wrapped lines breathe (Jérôme).
         .abd-body .combine-modal__option-title gains font-weight 800 and
         line-height 1.3 (base.css sets button line-height:1, so a two-line name
         was cramped), and .abd-attach-note gets line-height 1.3 so a wrapped
         warning is not cramped either. The bolding of names inside the attach
         modal and the notes is JS (profile v25, editor v37).
   Why:  Jérôme: bold the unit names; open up the line spacing in the name box. */

/* DevAgent45 - 2026-09-14
   What: v75 — the 'Already led by X' note (.abd-attach-note) goes grey and
         italic instead of amber (Jérôme). The holder's '#N' is part of the
         string (resolver v16), so it inherits the same grey italic.
   Why:  Jérôme review. */

/* DevAgent45 - 2026-09-14
   What: v74 — two review fixes (Jérôme). (1) The Unit Profile choice buttons:
         the label is vertically centred (justify-content: center) so it aligns
         with the ✓, which is centred in the button's touch-target min-height —
         v73 left the label at the flex-column top while the ✓ sat mid-height, so
         they never lined up. Text is centred again with ~40px side margins so a
         wrapping name never reaches the check, and the ✓ lives in the left
         margin. (2) The attachment drawer slides UNDER the card: a -20px top
         margin tucks its top behind the card (which is lifted above it with
         z-index inside an isolated .abs-unit__stack), and its top padding grows
         by the same 20px so the text still clears the card's bottom edge.
   Why:  Jérôme: the check misaligned with one- and two-line names and was being
         written over; the drawer should slide under the card, not sit below it. */

/* DevAgent45 - 2026-09-14
   What: v73 — four review tweaks (Jérôme). (1) The Unit Profile choice buttons
         left-align with a fixed left gutter for the ✓, so a name that wraps to
         two lines keeps a clean, constant left margin instead of centred text
         crowding the check. (2) The attachment drawer is indented 20px and is
         its own rounded box (full border, own radius) rather than a squared
         extension of the card — so .ub-card.abs-unit--has-drawer squaring is
         removed. (3) The bodyguard name in the drawer is bold + uppercase
         (.abs-unit__attach-name). The Led By role-tag change is JS-only
         (profile v22).
   Why:  Jérôme review on the Tyranid army: multi-line names read messily against
         the check; the drawer should indent like a child; the name should stand
         out. */

/* DevAgent45 - 2026-09-14
   What: v72 — the attachment moves from a card badge to a DRAWER below the card
         (Jérôme). New: .abs-unit__stack (card + drawer stacked, and the flex
         child beside the remove button in edit mode), .abs-unit__attach (the
         drawer, flush under the card, top border dropped so the card's squared
         bottom edge is the divider), .abs-unit__attach-arrow / -num, and
         .ub-card.abs-unit--has-drawer (squared bottom corners). The
         --editing flex:1 moves from .ub-card to .abs-unit__stack. .abs-unit__leads
         leaves the shared badge rule (it is a drawer now). Adds .abd-target__num
         for the grey '#N' on Leader/Support/Led By buttons (profile v21).
   Why:  Jérôme: show the attachment as a drawer with an arrow, and disambiguate
         a repeated bodyguard with #N. */

/* DevAgent45 - 2026-09-14
   What: v71 — two review tweaks (Jérôme). The Warlord holder note
         (.abd-warlord__note) is centred. On the army-list card, 'Leads X' and a
         new 'Warlord' indicator are BADGES: .abs-unit__warlord and
         .abs-unit__leads join the shared .abs-unit__epic badge rule, Warlord
         filled amber to stand out, and the old .abs-unit__leads text-line rule
         is removed (it is a badge now, army-editor-units.js v26).
   Why:  Jérôme: centre the note; show Leads/Warlord as badges, not text. */

/* DevAgent45 - 2026-09-14
   What: v70 — the selected state of the Unit Profile choice buttons is remade
         (Jérôme): green border + green-light background + green label with a ✓
         on the LEFT, vertically centred (::before), replacing the amber
         base.combine-modal__option--selected inside .abd-body. Scoped to
         .abd-body, so the combine-or-separate modal keeps the amber look. The
         base rule carries no checkmark, so there is no double. .abd-body
         .combine-modal__option gains position:relative and symmetric side
         padding to seat the check without unbalancing the centred label. A
         selected button holds its green on :hover (the base :hover flips to
         amber otherwise). Applies to Enhancements, Leader/Support/Led By and the
         new Warlord button alike. REMOVED: .abd-attach__badge (the green+check
         says 'attached' now) and the checkbox rules .abd-warlord / __check /
         __label (Warlord is a button — army-editor-profile.js v20).
   Why:  Jérôme: every selected Unit Profile button should read like a chosen
         wizard card, and Warlord should toggle like one. */

/* DevAgent45 - 2026-09-14
   What: v69 — Warlord + Leader/Support/Led By styling in the Unit Profile
         (.abd-*), all appended at the end of the file. The target buttons reuse
         .abd-body .combine-modal__option (the enhancement style); only the
         'Attached' badge (.abd-attach__badge), the role tag (.abd-attach__role),
         the 'already led by' note (.abd-attach-note) and the Warlord checkbox
         row (.abd-warlord / __check / __label / __note) are new.
   Why:  v10.6 (army-editor-profile.js v19). 🔴 No .aum-enh--taken here — a taken
         attachment slot is show-but-allow: labelled and still clickable, never
         the disabled dim an enhancement gets. */

/* DevAgent44 - 2026-09-09
   What: v68 — the 50/50 split made real. `flex: 1 1 0` (unitless basis) is an
         invalid shorthand in Safari and was dropped, leaving the panel at its
         content height. The panel's basis is now 0px, and the list beneath an
         open panel gets the same explicit 0px basis (.abc1-filters +
         .ab-col__list — the list's own flex:1 in army-editor-columns.css is a
         0% basis, which is content when the height is not definite).
   Why:  Jérôme review on iPad: the split read as 60/40. */

/* DevAgent44 - 2026-09-09
   What: v67 — .abc1-filters splits the column 50/50 with the list instead of
         growing to its content under a 42dvh cap: flex: 1 1 0 on the panel,
         which the list already carries, so each takes half whatever the
         column's height. The rule between them goes 1px → 2px and a step
         darker than --border (color-mix, --border as the fallback).
   Why:  Jérôme review on iPad: the open panel left room for three cards. */

/* DevAgent44 - 2026-09-09
   What: v66 — the browser's filter panel chrome, five rules (.abc1-filter-
         toggle, its --open state, its __count badge, .abc1-filters, and the
         .cc1 padding inside it). The panel's own controls are the Codex's
         (.cc1-*, codex-col1.css v6) and are NOT restyled here. Paired with
         army-editor-browser.js v12.
   Why:  v10.7. The Codex's panel arrives in the browser column (25%, the same
         width as the Codex's FILTERS column), collapsible because it shares
         the column with the list. ⚠ Chrome goes HERE, not in
         army-editor-columns.css, which codex-col2 shares. ⚠ The panel is a
         flex child of .ab-col, capped in height and scrolling on its own; the
         list beneath keeps flex:1 and its own scroll. */

/* DevAgent42 - 2026-09-08
   What: v65 — .abw-steps__close: the wizard's [×] at the right end of the step
         row, dark-on-white, pushed right by margin-left:auto. Paired with
         army-create.js v5 (the [×] left the chrome) and army.css v71.
   Why:  The [×] belongs in the step-nav row, not the dark chrome above it. */

/* DevAgent42 - 2026-09-08
   What: v64 — .abw-step--clickable: a reached step in the wizard nav is now a
         button that jumps back to it, so it gets a pointer and a hover tint. A
         later step is disabled and inert. Paired with army-create.js v4 and the
         header/footer reflow in army.css v70.
   Why:  The step nav became clickable-to-jump-back. */

/* DevAgent42 - 2026-09-08
   What: v63 — the chapter-detachment origin badge (.abw-detachment__origin)
         moves out of the top-right corner into the card flow, below the DP
         line, where it no longer overlaps a long detachment name. The
         selected-state nudge that offset it away from the checkmark is removed
         with it — in flow it never shares the checkmark's corner. Detachment
         rows are now equal-height (grid-auto-rows: 1fr) so a chapter card is no
         taller than a chapter-less one, the badge's top margin is dropped and
         its vertical padding tightened to kill the whitespace around it, and
         the detachment card's top/bottom padding is trimmed a step so heights
         match. The badge hugs its label (align-self: start) rather than
         stretching full width — a flex-column child blockifies, so inline-block
         alone did not hold.
   Why:  On a Space Marine chapter, the floating badge overlapped the
         detachment name, then read with too much space around it and made the
         card taller than its neighbours (Jérôme, Black Templars screenshots). */

/* DevAgent42 - 2026-09-08
   What: v62 — two affordance fixes.
         C: .abs__close is restyled from a 44px × icon button into a left-
            aligned [← Armies] text button in the established --amber (the
            primary-action orange; NOT a second orange). line-height and
            text-transform are set locally because base.css resets both and the
            label is now text. The class name is unchanged — a rename is v10.8's.
         D: .abs__columns .ab-col__header gains a dark --banner default when it
            carries no inline accent ([style] absent or empty), so a null
            faction colour degrades to a readable charcoal header instead of the
            white-title-on-white-surface the Genestealer Cults gap produced. An
            inline accent still wins; the Codex header is untouched.
   Why:  The × read as close/discard on a save-as-you-go surface; and a missing
         accent rendered invisible rather than plain. */

/* DevAgent42 - 2026-09-08
   What: v61 — the green + checkmark selected state extends to EVERY wizard card,
         not just force disposition: battle size, detachment and disposition now
         all read green when selected, scoped .create-army .format-option so it
         reaches nothing else. A selected chapter detachment drops its origin
         badge below the checkmark to avoid the top-right collision. The
         disposition disabled-card rule is retained.
         ⚠ .abw-fixed is now unused — army-create v2 renders the single
         disposition as a card, not that box. Left in place; flagged for a
         later cleanup rather than removed in feature work.
   Why:  Jérôme: apply the disposition treatment to army sizes and detachments
         too, so the whole wizard reads one way.

   PREVIOUS (v60): .abw-disposition-only green + checkmark. Superseded above. */

/* DebugAgent2 - 2026-09-08
   What: v59 — .abs__glyph img is REPLACED by .abs__glyph-mask, the sizing for
         the banner glyph now rendered as a CSS mask (army-editor.js v29). One
         rule out, one rule in; rule count unchanged.
   Why:  Jérôme: the army glyph in the header should be white, like the text.
         An <img> keeps the asset's black fill; a mask takes the banner ink. */

/* MaintenanceAgent1 - 2026-09-08
   What: v58 — corrected the header description: it named three class families
         (.abw-*, .abs-*, .apk-*) while the file holds nine. No rule changed.
   Why:  A line-2 description read as current must describe the file as it is. */

/* DevAgent39 - 2026-09-08
   What: v57 — RENAMED from army-builder.css v56. No rule changed. Linked from
         index.html v10 in the same position, LAST.
   Why:  The surface it styles is army-editor.js. Class prefixes are unchanged;
         comments below naming army-builder-* files are history and stay. */

/* DevAgent38 - 2026-08-31
   What: v56 — the remove button takes a bright destructive red rather than
         --red.
   Why:  Jérôme. --red is #8b1a1a, a deep GW-plate crimson chosen for error text
         on parchment; against a white card it reads as dark brown rather than
         as a delete control.

   ⚠ A LITERAL, AND ONLY HERE. It is deliberately NOT added to variables.css:
     that file's palette is the datasheet aesthetic, and a system destructive
     red belongs to one control, not to the theme. If a second control ever
     needs it, that is the moment to make it a token. */

/* DevAgent38 - 2026-08-31
   What: v55 — the army list's Edit toggle and its remove buttons, and the
         column title centred against the column rather than between two
         differently-sized buttons.
   Why:  army-builder-col3.js v19.

   ⚠ THE TITLE IS ABSOLUTELY POSITIONED. .ab-col__header is a flex row and the
     [+] and Edit are different widths, so a flex title between them sits
     off-centre by half the difference. Taking it out of flow is what makes it
     true to the column. */

/* DevAgent38 - 2026-08-31
   What: v54 — the enhancement cost badge regains its height.
   Why:  ⚠ base.css SETS `button { line-height: 1 }`, AND AN ENHANCEMENT OPTION
         IS A BUTTON. The badge is padded 1px top and bottom, so almost all of
         its height comes from the line box it sits in — at line-height 1 it
         collapses to the glyph height, while in UNIT COMPOSITION it inherits
         the datasheet's 1.5 and looks right. Restated here rather than
         adjusting the padding, so the two badges stay the same shape from the
         same numbers. */

/* DevAgent38 - 2026-08-31
   What: v53 — the cost badge keeps its shape inside the centred option title,
         and a disabled Edit Wargear button goes grey rather than pale amber.
   Why:  ⚠ v49 MADE .combine-modal__option-title A FLEX CONTAINER, which turned
         the badge into a flex ITEM. .ds-comp-points-row__badge is inline with a
         min-width and no display of its own — inline boxes ignore width, so its
         padding was doing the shaping. As a flex child it became a block-level
         box and stretched to the line's height, deforming the rectangle.
         flex: 0 0 auto plus inline-block gives it back its content sizing.

   ⚠ A disabled amber button reads as an enabled one that is merely faint —
     button:disabled in base.css only drops opacity to 0.45, which on amber is
     still amber. Grey says unavailable. */

/* DevAgent38 - 2026-08-31
   What: v52 — the enhancement options are full width, matching the Edit Wargear
         button. ⚠ TWO RULES FOR ONE SELECTOR ARE MERGED INTO ONE: v42 set
         width: auto and v49 added centring without mentioning width, so v42's
         value survived and the buttons stayed narrow. Two blocks styling the
         same selector is how a later change silently fails to take.
   Why:  Jérôme. */

/* DevAgent38 - 2026-08-31
   What: v51 — the footer total reads red when it is not the base tier price,
         matching the unit card's points.
   Why:  army-builder-detail.js v15. Same colour, same test, two places. */

/* DevAgent38 - 2026-08-31
   What: v50 — .abs-unit__enhancement, .abs-unit__enhancement-cost and
         .abs-unit__surcharge are REMOVED. Their lines left the card
         (army-builder-col3.js v18) and a rule with no element is a thing the
         next agent has to disprove.
   Why:  Jérôme. */

/* DevAgent38 - 2026-08-31
   What: v49 — no gap above the first section header, header text at the army
         list's role-header size, the cost head 10px taller, and enhancement
         options shaped like the Edit Wargear button.
   Why:  Jérôme. ⚠ The gap was .abd-body's top padding: the headers pull
         themselves full width with a negative side margin, but the vertical
         padding still applied, so the first bar started 16px down. */

/* DevAgent38 - 2026-08-31
   What: v48 — section headers are full-width bars of a fixed height, matching
         the cost footer's head, and the cost breakdown's figures align with the
         footer total's right edge.
   Why:  army-builder-detail.js v13. The headers were .ab-category-header, the
         army list's role header — right there, wrong here, where a section is a
         panel rather than a group of cards. */

/* DevAgent38 - 2026-08-31
   What: v47 — the breakdown sits above its head, so the border and padding move
         with it. .abd-cost__row--total is REMOVED: the head shows the total and
         a second copy directly beneath it said the same number twice.
   Why:  army-builder-detail.js v12. */

/* DevAgent38 - 2026-08-31
   What: v46 — the UNIT COST footer: a collapsed head showing the total, and a
         breakdown beneath it when opened.
   Why:  army-builder-detail.js v11.

   ⚠ IT SITS OUTSIDE .abd-body, so it stays put while the sections scroll —
     the price is the thing a player checks against the army total, and it
     should not have to be scrolled to. */

/* DevAgent38 - 2026-08-31
   What: v45 — the DEFAULT / CUSTOMIZED readout above the Edit Wargear button.
   Why:  army-builder-detail.js v10.

   ⚠ IT IS NOT A CONTROL. No cursor, no hover, no pointer events — it looks like
     a segmented toggle because that shape reads as 'one of these two', but
     nothing here is tappable and it must not grow a click handler. */

/* DevAgent38 - 2026-08-31
   What: v44 — the unit total drops its label and takes amber, matching the
         points everywhere else in the app. .aum-total__label is REMOVED — its
         markup is gone (army-wargear-modal.js v4) and a rule with nothing to
         style is a thing the next agent has to disprove.
   Why:  Jérôme. */

/* DevAgent38 - 2026-08-31
   What: v43 — the footer total stacks value over label, the footer button takes
         the amber outline, and 'Your unit costs' matches the composition text
         beside it.
   Why:  Jérôme. ⚠ .ds-comp-points__head SETS NO font-size — it inherits the
         datasheet section's, which is larger than the --text-sm composition
         items above it. Fine in a 50% datasheet panel, too loud in a 250px
         column, so the size is set HERE and SCOPED to .awm-composition rather
         than changed in datasheet.css, which the Codex shares. */

/* DevAgent38 - 2026-08-31
   What: v42 — the modal widens by 250px for a UNIT COMPOSITION column on the
         left, divided by a rule; only the options column scrolls. The space
         above DEFAULT WARGEAR is halved.
   Why:  Jérôme.

   ⚠ THE SCROLL MOVES OFF .ap-modal__body. The body is now the OPTIONS column
     only, and the two columns sit in .awm-cols above it — so the cap belongs on
     .awm-options. Capping the body would scroll both.
   ⚠ The composition column carries its own overflow as a safety net: it is
     meant to stay put, but a composition taller than the modal should scroll
     rather than be clipped. */

/* DevAgent38 - 2026-08-31
   What: v41 — five review changes: the modal is 420px and 60vh, the footer
         total is styled again, DEFAULT WARGEAR reads larger, and the Customize
         Wargear button takes amber text.
   Why:  ⚠ THE FOOTER TOTAL WAS UNSTYLED, NOT MISLAID. v22 removed .aum-total
         with the old modal footer and v39 brought the markup back without it,
         so 'Unit total' and '150 pts' rendered as two inline spans run
         together. Restored here, laid out for a horizontal footer this time.

   ⚠ The .ap-modal__footer is a flex ROW. .aum-total must not be a flex column
     inside it, which is what the original rule assumed. */

/* DevAgent38 - 2026-08-31
   What: v40 — the wargear modal gets a width and, more importantly, a HEIGHT
         CAP with a scrolling body.
   Why:  ⚠ v22 REMOVED THE .aum-modal SIZING WHEN THE MODALS WERE RETIRED, and
         v39 brought a modal back without restoring it — so it inherited
         .ap-modal's 420px and, critically, NO MAX HEIGHT. A datasheet with four
         option blocks runs far past an iPad's viewport with no way to scroll to
         the footer.

   ⚠ THE CAP IS ON THE BODY, NOT THE MODAL. .ap-modal is overflow: hidden, so a
     max-height there would clip the footer rather than scroll to it. The header
     and footer stay fixed and only the option list moves. */

/* DevAgent38 - 2026-08-31
   What: v39 — the wargear rows go back to a single line, because they live in a
         modal again and have the width for it. The two-rectangle overlap from
         v38 and the column-width fixes from v34/v36/v37 are all removed. Adds
         the 'Customize Wargear' button.
   Why:  ⚠ FOUR LAYOUTS WERE TRIED IN A 256px COLUMN and each traded the
         overflow for something worse — wrapping rows gave uneven heights,
         shrinking the pill moved the wrap inside it, stacking made every row
         double height, overlapping rectangles read as broken. The control needs
         width the column does not have. Recorded so nobody tries a fifth.

   ⚠ .abc2-* NOW MEANS 'IN THE WARGEAR MODAL' AGAIN. The prefix predates the
     column and outlived it. */

/* DevAgent38 - 2026-08-31
   What: v38 — TWO RECTANGLES, OVERLAPPING. The pill keeps its own bordered row
         at the height it always had; the stepper gets a second bordered box
         below and to the right, pulled up so it intersects the first.
   Why:  Jérôme. v37 made them one tall box, which was not the ask.

   ⚠ THE WRAPPER IS WHAT MAKES THE OVERLAP POSSIBLE. .abc2-item is the pill's
     rectangle again, so the stepper cannot live inside it — .abc2-item-group
     stacks the two and the negative margin overlaps them.
   ⚠ The stepper box is OPAQUE and sits above, so the pill row's border is
     broken cleanly where they meet rather than showing through. */

/* DevAgent38 - 2026-08-31
   What: v37 — the wargear row stacks: the pill on its own full-width line, the
         stepper beneath it, right-aligned, inside the same bordered row.
   Why:  Jérôme. ⚠ THIS IS THE THIRD ATTEMPT AND THE FIRST THAT HOLDS. v34
         wrapped the row and produced two wildly different row heights; v36 let
         the pill shrink, which just moved the wrapping inside the pill. Giving
         the pill the whole width means it never wraps at all, and every row is
         the same height — which neither of the other two achieved.

   ⚠ The pill stays LEFT-aligned and the stepper goes RIGHT: the + lands under
     the thumb and the steppers line up down the column. */

/* DevAgent38 - 2026-08-31
   What: v36 — a wargear row no longer wraps. The stepper holds its place on the
         line and the pill takes whatever width is left, wrapping its text
         inside the row.
   Why:  ⚠ v34's flex-wrap FIXED THE OVERFLOW AND LOOKED WORSE. A row whose pill
         was a few pixels too wide dropped its stepper onto a line of its own,
         so the list ran at two wildly different row heights and read as broken.
         Shrinking the pill instead keeps every row one stepper tall.

   ⚠ min-width: 0 ON THE NAME IS WHAT MAKES THIS WORK. A flex item will not go
     below its intrinsic width without it, so the pill would push the stepper
     out of the column exactly as before. */

/* DevAgent38 - 2026-08-31
   What: v35 — the unit-name line clamp is removed. A name wraps to as many
         lines as it needs and the card grows with it.
   Why:  Two lines is too few in a 25% column on a 9.8" iPad — 'Marneus Calgar
         in Armour of Antilochus' and 'Terminator Assault Squad' were being cut.
         A truncated unit name in your own army list is worse than a tall card.

   ⚠ THE CARD ALREADY GROWS. .abc1-row/.abs-unit-wrap .ub-card is height: auto
     with a 70px minimum (v26), so removing the clamp needs no height change.
   ⚠ overflow: hidden STAYS. The clamp is gone, not the containment — a name
     with no spaces still must not paint outside the card. */

/* DevAgent38 - 2026-08-31
   What: v34 — a wargear row wraps instead of overflowing at narrow column
         widths, and its pill wraps instead of running under the stepper.
   Why:  ⚠ v12 GAVE .abc2-item A FIXED width: 400px. The detail column is 25% of
         the viewport — about 256px on a 1024pt iPad — so the row was 144px
         wider than its column and the pill ran under the stepper while
         MODEL(S) collided with the +. Photographed on the device.

   🔴 THE ROW NOW FILLS ITS COLUMN AND WRAPS. Wide columns keep the single-line
     layout; narrow ones drop the stepper beneath the pill. No breakpoint and no
     second layout to maintain — and the tap targets never shrink, which is the
     wrong direction to fix a small screen in. */

/* DevAgent38 - 2026-08-31
   What: v33 — the count moved off a trimmed instruction and onto its item row.
   Why:  army-builder-detail.js v5. */

/* DevAgent38 - 2026-08-31
   What: v32 — the model/models label under a wargear quantity drops 2px.
   Why:  Jérôme review.

   ⚠ EXPRESSED AS calc(var(--text-xs) - 2px), NOT AS A RAW 9px. --text-xs is
     already the smallest step on the scale, so there is no token below it; the
     calc keeps the label tied to the token it sits beneath rather than becoming
     a loose pixel value that stops tracking a future scale change. */

/* DevAgent38 - 2026-08-31
   What: v31 — the wargear quantity carries a 'model' / 'models' label beneath
         it. ⚠ .abc2-step__n keeps its width and weight; the stack around it is
         new, so the steppers do not shift.
   Why:  army-builder-detail.js v4. */

/* DevAgent38 - 2026-08-31
   What: v30 — a card with a one-line name keeps the standard 70px even when it
         carries badges. Only a wrapped name makes it grow.
   Why:  ⚠ v26 STACKED THE POINTS OVER THE MODEL COUNT and both inherited the
         body's 1.55 line-height, making that stack ~43px — taller than the
         16.5px name beside it. The name row took the stack's height, and with a
         badge row underneath the content passed 70px on every badged card.
         Tightening the line-heights brings a one-line name plus badges to ~68px,
         inside the 70px minimum, while a second name line still grows the card.

   ⚠ The numbers below are load-bearing, not cosmetic. .ub-card__info adds 16px
     of padding and a 4px gap; the stack and the badge row have to fit in the
     54px that leaves. */

/* DevAgent38 - 2026-08-31
   What: v29 — the banner's bottom border matches the column separators.
   Why:  Jérôme review. It was 2px of var(--border) (#c8c2b8, the parchment
         border) against the columns' 2px #444444, so the thickness already
         agreed and only the colour did not.

   ⚠ #444444 IS A LITERAL IN army.css, not a token — .ab-col--1 and .ab-col--2
     both write it directly and there is no variable for it. Repeated here as a
     literal for that reason; introducing a token would mean editing army.css,
     which this session has no other cause to touch. If one is ever added, these
     three places change together. */

/* DevAgent38 - 2026-08-31
   What: v28 — all four column headers take one fixed height, so the two that
         carry a 40px button match the two that carry only a title.
   Why:  Jérôme review. */

/* DevAgent38 - 2026-08-31
   What: v27 — .abs-unit__loadout is REMOVED. Its line is gone from the card
         (army-builder-col3.js v16) and a rule with no element is a thing the
         next agent has to disprove.
   Why:  Jérôme review. */

/* DevAgent38 - 2026-08-31
   What: v26 — cards grow with their content, the points and model count share a
         right-hand stack, and a modified price reads red.
   Why:  Jérôme review.

   ⚠ .ub-card IS A FIXED 70px IN army-builder-col1.css v44, and is shared with
     the Codex's unit list. The height override is scoped to the two Builder row
     wrappers so the Codex keeps its uniform rows. */

/* DevAgent38 - 2026-08-31
   What: v25 — the instance number beside a repeated datasheet's name.
   Why:  army-builder-col3.js v14. */

/* DevAgent38 - 2026-08-31
   What: v24 — the detail column takes column one's background, and its
         enhancement buttons narrow and separate.
   Why:  Jérôme review. ⚠ army.css sets .ab-col--3 to var(--surface) while
         .ab-col--1 inherits .ab-col's var(--bg) — the override below is scoped
         to .abd-col so the Codex's own third column is untouched. */

/* DevAgent38 - 2026-08-31
   What: v23 — .abs-unit-wrap's spacing rules are RESTORED, and the detail
         column gains section headers.
   Why:  ⚠ v22 DELETED THEM WHILE ASSERTING IT HAD NOT. They lived inside the
         UNIT DRAWER block, and removing that block took them with it — while
         the v22 note above claimed the wrapper survived. The comment described
         the intent; the file did something else. Every gap in the army list
         collapsed, which is the fourth time this exact trap has fired.
         A note that a rule survives is worth nothing unless the rule is
         checked; .abs-unit-wrap matched a grep only because of an unrelated
         two-line-name selector further down. */

/* DevAgent38 - 2026-08-31
   What: v22 — the drawer's rules are REMOVED and the detail column's scroll body
         added. The .abc2-* and .aum-* families are KEPT and reused unchanged:
         the controls moved from the modals into the column, so their styling
         moved with them rather than being rewritten.
   Why:  army-builder-detail.js v1, army-builder-col3.js v13.

   ⚠ THE .aum-modal SCOPES WERE DEAD AND ARE FIXED, NOT LEFT. No modal renders
     any more, so the enlarged selection-row pill and the modal footer total
     were styling nothing. The pill rule is re-scoped to .abd-body; the footer
     total's rules are removed with the footer that held them.

   ⚠ .abs-unit-wrap SURVIVES THE DRAWER. It owns the army list's row spacing,
     because .ub-card carries margin-bottom and .ub-card:last-child zeroes it.
     Removing it collapses every gap in that column — the same trap the browser
     row and the drawer each hit. */

/* DevAgent38 - 2026-08-31
   What: v21 — the add arrow becomes an amber outline on white, a blocked card's
         text dims, and the sub-faction badge returns alongside EPIC HERO.
         .abs-unit__epic is BACK — v20 removed it with the badge and the badge
         is back (army-builder-col1.js v7).
   Why:  Jérôme review. */

/* DevAgent38 - 2026-08-31
   What: v20 — the add button takes the amber of the old ADD TO ARMY control and
         a blocked state; the browser and toggle buttons grow to 40px; unit
         names wrap to two lines. .abs-unit__epic is REMOVED — the badge is gone
         from both cards (col1 v6, col3 v11) and a rule with no element is a
         thing the next agent has to disprove.
   Why:  Jérôme review. */

/* DevAgent38 - 2026-08-31
   What: v19 — the browser row: card plus a → button that adds the unit.
   Why:  army-builder-col1.js v5. The card gives up 60px so the button has
         somewhere to be. */

/* DevAgent38 - 2026-08-31
   What: v18 — the add-mode layout and the [+] toggle in the army list header.
   Why:  army-builder.js v12, army-builder-col3.js v10.

   ⚠ NO COLUMN WIDTH IS DEFINED HERE. army.css already sets .ab-col--1 to 25%
     and .ab-col--2 to 50%; both list columns use --1 and the datasheet uses
     --2, so 25/50/25 and 25/25/50 both fall out of which columns render. The
     only rule below is the one that hides the detail column in add mode. */

/* DevAgent38 - 2026-08-31
   What: v17 — the army name reads as tappable, and the banner's ⋮ menu is
         replaced by a large × that closes the army.
   Why:  army-builder.js v11. */

/* DevAgent38 - 2026-08-31
   What: v16 — MODELS / WARGEAR and DEFAULT WARGEAR swap weights. The section
         titles take the larger, darker treatment; the DEFAULT WARGEAR heading
         inside the wargear section takes the smaller, dimmer one.
   Why:  Jérôme review. The two rules differed in exactly two declarations —
         font-size and color — so this is those two, exchanged. Everything else
         about both labels is unchanged. */

/* DevAgent38 - 2026-08-31
   What: v15 — the enlarged pill is confined to the selection ROWS. v12 scoped
         it to .aum-modal, which also caught the pills inside an option's prose,
         where the extra height reads as a mismatch against the datasheet.
   Why:  Jérôme review. A pill in prose is the datasheet's; a pill in a
         selection row is a tap target sitting beside a 28px stepper. */

/* DevAgent38 - 2026-08-31
   What: v14 — the taken-enhancement state.
   Why:  army-unit-modals.js v8. An enhancement held by another unit is shown
         and disabled rather than removed, so it needs to read as unavailable
         without reading as an error — it is a normal state, not a problem. */

/* DevAgent38 - 2026-08-31
   What: v13 — the DEFAULT WARGEAR heading and its inline reset control, and the
         enhancement cost badge. .abc2-defaults and
         .abc2-option__instruction--default are REMOVED — the button is gone and
         the default block renders a heading rather than an instruction line, so
         neither has an element any more. A rule with nothing to style is a
         thing the next agent has to disprove.
   Why:  Jérôme review. */

/* DevAgent38 - 2026-08-31
   What: v12 — wargear rows settle between v10's and v11's heights, take a fixed
         width so they align, and the pill grows a pixel each way. The modal
         footer gains the unit total.
   Why:  Jérôme review.

   🔴 THE PILL RULE IS SCOPED TO .aum-modal, AND THAT SCOPE IS THE WHOLE POINT.
     .ds-equip-pill is datasheet.css's, this file loads after it, and an
     unscoped rule here would resize every pill on the Codex datasheet too. The
     v11 warning below still stands for unscoped rules; a rule confined to this
     modal is the one safe way to touch a .ds- class from here, and it is
     PADDING ONLY — the colours, the badge and the radius stay the datasheet's. */

/* DevAgent38 - 2026-08-31
   What: v11 — wargear rows hug their content, and their controls are sized for
         a finger. The name and cost styling is NOT here: the pill is
         .ds-equip-pill and the cost is .ds-comp-points-row__badge, both the
         datasheet's own classes from datasheet.css v118, applied directly in
         army-unit-modals.js v5.
   Why:  Jérôme review.

   ⚠ DO NOT RESTYLE .ds-equip-pill OR .ds-comp-points-row__badge FROM THIS FILE.
     It loads after datasheet.css, so a rule here would silently change the
     Codex datasheet and the Builder's too. The point of reusing those classes
     is that one definition serves both. */

/* DevAgent38 - 2026-08-31
   What: v10 — drawer corrections: card spacing restored, the drawer joined to
         its card, taller rows with a chevron, and an opening animation.
   Why:  Jérôme review.

   ⚠ .ub-card CARRIES ITS OWN margin-bottom, AND .ub-card:last-child ZEROES IT.
     Wrapping each card in .abs-unit-wrap made every card the last child of its
     own wrapper, so every gap in the list collapsed. The margin moves to the
     wrapper and the card's is zeroed inside it — do not put spacing back on
     .ub-card here, it is shared with the browser column.

   ⚠ THE CLOSE IS NOT ANIMATED, only the open. The drawer unmounts when the
     card is deselected, and a Preact component cannot animate its own removal
     without being kept mounted through the transition. Animating open is the
     half worth having; the other half needs state this does not justify. */

/* DevAgent38 - 2026-08-31
   What: v9 — the unit drawer, and the modal wrapper the two loadout modals use.
   Why:  army-builder-col3.js v4 and army-unit-modals.js v1. The .abc2-* rules
         from v3 are KEPT and reused unchanged — the controls moved from column
         2 into the modals, so their styling moved with them rather than being
         rewritten. Only .abc2-loadout (the panel wrapper that no longer exists)
         is gone. */

/* DevAgent38 - 2026-08-31
   What: v8 — the enhancement restriction note, in two states.
   Why:  army-builder-col2.js v3. The unapplied state is the louder of the two
         deliberately: it means the resolver saw a restriction and could not
         act on it, so the player has to. */

/* DevAgent38 - 2026-08-31
   What: v7 — the default-wargear option is marked, and the button offered to
         units saved without one is styled.
   Why:  army-builder-col2.js v2. */

/* DevAgent38 - 2026-08-31
   What: v6 — the sort dropdown matches the search field's height, 32px, in both
         columns.
   Why:  .ab-sort-select is `height: 34px; min-height: var(--touch-target)` in
         army-builder-col1.css v44, and the 44px min-height won — the control
         stood 12px taller than the field beside it.

   ⚠ THIS TAKES THE CONTROL BELOW THE 44px TOUCH TARGET, deliberately and on
     request. Cogitator runs on an iPad, and --touch-target exists because a
     32px control is a harder tap. The select keeps its full row width, and its
     native picker opens from anywhere in the box, so the loss is smaller here
     than on a bare button — but it is a real one, and reverting is a matter of
     deleting the two lines below. */

/* DevAgent38 - 2026-08-31
   What: v5 — the sort row is laid out properly: a flexible left element
         (col1's search, col3's spacer), then a dropdown sized to its longest
         option, then the icon.
   Why:  v4 put the icon between the search and the select and set
         `flex: 1` on the select, which stretched it across col3 and squeezed
         col1's search.

   ⚠ .ab-sort-select IS `flex: 0 0 120px; width: 120px; margin-left: auto` IN
     army-builder-col1.css v44. All three must be overridden together — v4
     changed the flex-grow only, which is why the fixed 120px width and the auto
     margin both survived and kept fighting the row. */

/* DevAgent38 - 2026-08-31
   What: v4 — the sort-control icon, and the column-3 card's meta row rebuilt so
         badges sit left and the model count sits right beneath the points, one
         step larger. .abc1-inarmy and .abs-unit__role are REMOVED — their
         markup is gone (col1 v2, col3 v2), and a rule with no element is a
         thing the next agent has to disprove.
   Why:  Jérôme review. The column-3 card is now the same card as the browser's,
         so it takes .cc2-glyph-slot from codex-col2.css as column 1 does.

   ⚠ .abs-unit__models IS NO LONGER PART OF THE FLOW OF BADGES. It is the right
     half of a space-between row; adding a third child to .abs-unit__meta will
     push it off the right edge rather than wrap. */

/* DevAgent38 - 2026-08-31
   What: v3 — added the browser deltas (.abc1-*) and the column-2 loadout panel
         (.abc2-*). Nothing in v1 or v2 changed.
   Why:  army-builder-col1.js v1 and army-builder-col2.js v1. The column frames,
         the cards and the ADD TO ARMY button all come from
         army-builder-col1.css v44 and datasheet.css v118, both already linked;
         only the controls those files never had are added here.

   ⚠ .cc2-glyph-slot / .cc2-glyph are codex-col2.css's, and the browser card
     reuses them deliberately — it is the same card, rendered in a different
     column. If that stylesheet ever stops being loaded, this breaks visually
     and not functionally. */

/* DevAgent38 - 2026-08-31
   What: v2 — added the .abs-* family: the army surface shell, its banner, the
         three-column frame and the unit-row detail lines including the issue
         flags. Nothing in v1 changed.
   Why:  army-builder.js v2 and army-builder-col3.js v1. The columns reuse
         .ab-col*, .ub-card* and .ab-col-3__* from army-builder-col1.css v44 and
         army-builder-col3.css v6, both already linked; only what those files do
         not carry is added here.

   ⚠ .ab-col-3__header CANNOT BE STYLED FROM army-builder-col3.css. That file's
     v5 changelog entry lost its opening comment delimiter, so seven lines of
     prose sit at top level and a CSS parser swallows the .ab-col-3__header rule
     into an invalid selector. Verified with tinycss2, not by reading. The rule
     is empty there so nothing renders wrong today, but a declaration added to
     it silently does nothing. .abs-col3-header below exists to avoid that trap
     rather than to work around a style. */

/* DevAgent38 - 2026-08-31
   What: v1 — new stylesheet for army-builder.js v1 and army-picker.js v1.
         Everything those surfaces need beyond army.css v68 lives here, and
         nothing else does.
   Why:  army.css is 2,673 lines and is shared with the dormant Army surfaces.
         Growing it for a rebuild would mean editing the stylesheet those files
         still depend on. A new file keeps the two apart and makes the rebuild's
         styling reviewable on its own.

   ⚠ LOADS LAST IN index.html, AFTER codex-col3.css. A rule here wins over
     every other stylesheet regardless of specificity intent — the same
     property codex-col3.css has over datasheet.css. Nothing below targets a
     .ds-* or .cc3-* class, so the datasheet renderer is untouched when it is
     reused in the Builder.

   ⚠ THE PREFIXES ARE NEW ON PURPOSE. army.css already owns .army-builder,
     .ab-columns and .ab-col*, from the dormant three-column builder. This file
     uses .abw- (army builder wizard) and .apk- (army picker) so no selector
     here can collide with one still serving a file on disk.

   ⚠ Any list rendered by these surfaces re-establishes its own markers.
     base.css's `ul, ol { list-style: none }` reset has destroyed bulleted
     structure in rules prose three separate times. */

/* ============================================================
   STEP INDICATOR
   ============================================================ */

.abw-steps {
  display: flex;
  align-items: stretch;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-6);
  background: var(--surface);
  border-bottom: 2px solid var(--border);
  flex-shrink: 0;
  overflow-x: auto;
}

/* The wizard's [×], at the right end of the step-nav row. Dark-on-white to sit
   in this light bar, pushed right by margin-left:auto past the steps. Cancels
   the wizard to the armies list; nothing is written until the final POST. */
.abw-steps__cancel {
  flex-shrink: 0;
  align-self: center;
  margin-left: auto;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  line-height: 1;
  color: var(--text-dim);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}

.abw-steps__cancel:hover {
  background: var(--surface-lo);
  border-color: var(--border-hi);
  color: var(--text);
}

.abw-step {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  white-space: nowrap;
  opacity: 0.45;
  transition: opacity var(--transition-fast), background var(--transition-fast);
}

.abw-step--done    { opacity: 0.8; }
.abw-step--current { opacity: 1; background: var(--amber-light); }

/* A step already reached is a button that jumps back to it. The pointer and
   hover tint say so; a later step is disabled (base.css dims it) and inert. */
.abw-step--clickable { cursor: pointer; }
.abw-step--clickable:hover { background: var(--surface-lo); opacity: 1; }

.abw-step__n {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--surface-lo);
  border: 1px solid var(--border);
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: var(--text-xs);
  color: var(--text-dim);
}

.abw-step--current .abw-step__n {
  background: var(--amber);
  border-color: var(--amber);
  color: var(--text-inv);
}

.abw-step--done .abw-step__n {
  background: var(--surface-lo);
  border-color: var(--border-hi);
  color: var(--text);
}

.abw-step__label {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: var(--text-sm);
  letter-spacing: var(--heading-tracking);
  text-transform: uppercase;
  color: var(--text);
}

/* ============================================================
   DETACHMENT POINTS METER
   Display only. Nothing here gates the Next button — over budget still
   advances, because Cogitator surfaces constraints and never enforces them.
   ============================================================ */

.abw-dp {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  background: var(--surface-lo);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.abw-dp__label {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: var(--text-sm);
  letter-spacing: var(--heading-tracking);
  text-transform: uppercase;
  color: var(--text-dim);
}

.abw-dp__value {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: var(--text-lg);
  color: var(--text-hi);
}

.abw-dp--over {
  background: var(--red-light);
  border-color: var(--red);
}

.abw-dp--over .abw-dp__value { color: var(--red); }

.abw-dp__flag {
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--red);
}

/* ============================================================
   DETACHMENT CARDS
   Built on .format-option so the battle-size cards and these read as one
   family. Only the grid and the origin badge are new.
   ============================================================ */

.abw-detachments {
  display: grid;
  /* v122: two columns, always; the cards take the width. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* Equal-height rows, so a card whose name wraps to two lines does not stand
     taller than the one-line card beside it. Each card stretches
     to the row's tallest; content stays top-aligned, so any slack falls to the
     card's bottom rather than opening a gap under the label. */
  grid-auto-rows: 1fr;
  /* v123: wider between columns than between rows, so a circle reads as its
     own card's. */
  row-gap: var(--space-3);
  column-gap: var(--space-8);
}

/* Slightly tighter top/bottom than the shared .format-option padding, to keep
   the card compact. Horizontal padding is inherited from .format-option. */
.abw-detachment {
  position: relative;
  text-align: left;
  padding-top: var(--space-3);
  padding-bottom: var(--space-3);
}

/* ============================================================
   SELECTED CARD STATE — GREEN + CHECKMARK
   Every selectable card in the creation wizard — battle size and detachment
   — reads GREEN when selected, not the amber army.css gives
   .format-option--selected. Green is the established valid/success colour
   (variables.css --green); this file loads last, so it wins over army.css
   without a specificity fight. Scoped to .create-army so it cannot reach a
   .format-option anywhere else.
   ============================================================ */

.create-army .format-option {
  position: relative;
}

.create-army .format-option--selected {
  border-color: var(--green);
  background: var(--green-light);
}

.create-army .format-option--selected .format-option__name {
  color: var(--green);
}

/* v122: the battle-size row fills the wizard's width. army.css's auto-fill
   keeps an empty fourth column at 840px; auto-fit collapses it, so the three
   cards stretch. */
.create-army .format-grid--row {
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
}

/* The checkmark, top-right, drawn rather than added to the markup. Only on a
   selected card. */
.create-army .format-option--selected::after {
  content: '✓';
  position: absolute;
  top: var(--space-2);
  right: var(--space-3);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--text-md);
  line-height: 1;
  color: var(--green);
}

/* ============================================================
   NOTES + FIXED VALUES
   ============================================================ */

.abw-note {
  padding: var(--space-3) 0;
  font-size: var(--text-sm);
  color: var(--text-dim);
}

.abw-note--warn {
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  background: var(--amber-light);
  border: 1px solid var(--amber);
  border-radius: var(--radius);
  color: var(--text);
}

/* ============================================================
   CREATION SUMMARY
   ============================================================ */

.abw-summary {
  margin-top: var(--space-6);
  padding: var(--space-4);
  background: var(--surface-lo);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.abw-summary__row {
  display: flex;
  gap: var(--space-4);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border);
}

.abw-summary__row:last-child { border-bottom: none; }

.abw-summary__label {
  flex: 0 0 150px;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: var(--text-sm);
  letter-spacing: var(--heading-tracking);
  text-transform: uppercase;
  color: var(--text-dim);
}

.abw-summary__value {
  flex: 1;
  font-size: var(--text-sm);
  color: var(--text);
}

/* ============================================================
   ARMY PICKER DELTAS
   ============================================================ */

/* 🔴 The resolver degrades per unit rather than throwing, and returns issue
   codes. An army carrying them renders normally and says so — visible, and
   deliberately not alarming. Swallowing them is the one failure that makes the
   whole degradation design pointless. */
.apk-review {
  display: inline-block;
  margin-bottom: var(--space-2);
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--amber-light);
  border: 1px solid var(--amber);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--amber-dim);
}

/* Over budget is stated, never corrected. */
.apk-over { color: var(--red); }

/* A transient notice (army-picker.js v21) — a copy confirmed, or an export or
   copy that failed. Fixed to the viewport bottom so it is seen at any scroll
   position; it takes no pointer events, so a tap goes through to the page.
   ⚠ 1100 is one step above the z-index an open card menu takes (army.css). */
.apk-notice {
  position: fixed;
  left: 50%;
  bottom: var(--space-6);
  transform: translateX(-50%);
  z-index: 1100;
  max-width: calc(100vw - 2 * var(--space-6));
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius);
  background: var(--banner);
  color: var(--banner-text);
  box-shadow: var(--shadow-lg);
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1.35;
  text-align: center;
  pointer-events: none;
}

.apk-notice--error {
  background: var(--red);
  color: var(--text-inv);
}


/* ============================================================
   THE ARMY SURFACE — #/army/:id
   Three columns over a fixed banner. The frame is pinned to the viewport and
   only the columns scroll, which is what .ab-col__list and .ab-units__body in
   army-builder-col1/col3.css already assume.
   ============================================================ */

.abs {
  display: flex;
  flex-direction: column;
  height: 100dvh;
  overflow: hidden;
  background: var(--bg);
}

.abs__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--header-height);
  padding: 0 var(--space-4);
  background: var(--banner);
  color: var(--banner-text);
  flex-shrink: 0;
}

.abs__account {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.abs__status {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-10) var(--space-4);
  font-size: var(--text-sm);
  color: var(--text-dim);
}

.abs__status--error { color: var(--red); }

/* ── The banner ──────────────────────────────────────────────
   Carries the army's identity, display only. Its two controls are ← ARMIES and
   the menu at its right end, where the name and the glyph are changed (v93);
   everything else on it is immutable under R14 and is never offered. */

.abs__banner {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  /* 60px tall (v94, Jérôme). The 52px glyph is the tallest child, so 3px
     above and below it plus the 2px rule make 60. A facts line that wraps
     makes the banner taller; nothing is clipped. */
  padding: 3px var(--space-4);
  background: var(--banner);
  color: var(--banner-text);
  /* Matches the column separators — see the v29 note on the literal. */
  border-bottom: 2px solid #444444;
  flex-shrink: 0;
}

/* The army's glyph — display only, no chip (v93). ⚠ The padding carries the
   1px the chip's border used to take, so the glyph is drawn at the size it
   had inside the chip. */
.abs__glyph {
  width: 52px;
  height: 52px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(var(--space-1) + 1px);
}

/* The masked glyph. The asset's own paint is discarded; background-color —
   set inline to the banner ink — fills its alpha. See army-editor.js v29. */
.abs__glyph-mask {
  display: block;
  width: 100%;
  height: 100%;
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
}

.abs__identity {
  flex: 1;
  min-width: 0;
  /* 10px closer to the glyph than the banner's gap puts it (v94, Jérôme). */
  margin-left: -10px;
}

.abs__name {
  font-family: var(--font-heading);
  font-weight: var(--heading-weight);
  font-size: var(--text-xl);
  letter-spacing: var(--heading-tracking);
  text-transform: uppercase;
  color: inherit;
  line-height: 1.1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.abs__facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  /* v94: 2px -> 0, the name-to-facts gap 2px smaller (Jérôme). */
  margin-top: 0;
  font-size: var(--text-xs);
  opacity: 0.85;
}

/* A separator between facts, drawn rather than authored into the text — the
   values themselves are upstream's and are rendered verbatim. */
.abs__facts span + span::before {
  content: '·';
  margin-right: var(--space-2);
  opacity: 0.6;
}

.abs__totals {
  display: flex;
  gap: var(--space-5);
  flex-shrink: 0;
}

.abs__total {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}

.abs__total-label {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  opacity: 0.8;
}

.abs__total-value {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: var(--text-lg);
  /* 2px closer to its label (v94, Jérôme). */
  margin-top: -2px;
}

/* Over budget is stated, never corrected. The banner sits on a faction accent
   of unknown darkness, so this uses a light red rather than var(--red), which
   is tuned for the parchment background. */
.abs__total-value--over { color: #ff9a9a; }

/* The top-left [← Armies] back affordance, not a close ×. Renamed
   .abs__close -> .abs__back at v10.12 (DevAgent47) to name what it does; the
   emit is army-editor.js.
   Orange, from the established --amber (the same primary-action orange
   .create-army__next-btn uses); NOT a second orange. It reads as orange
   regardless of the faction accent behind it, which is why the fill is fixed
   rather than inherited from the banner ink the way the old × was.
   ⚠ base.css sets `button { line-height: 1 }` and a button does not inherit
   text-transform — both are set here because the label is now text.
   40px tall, and its text is the size of the armies home's + CREATE ARMY
   button, .army-picker__new-army-btn in army.css (v93, Jérôme). */
.abs__back {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  height: 40px;
  padding: 0 var(--space-3);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: var(--text-base);
  letter-spacing: var(--heading-tracking);
  text-transform: uppercase;
  line-height: 1;
  color: var(--banner-text);
  background: var(--amber);
  border: 1px solid var(--amber-dim);
  border-radius: var(--radius);
  cursor: pointer;
  transition: background var(--transition-fast);
}

.abs__back:hover  { background: var(--amber-dim); }
.abs__back:active { background: var(--amber-dim); }

/* The ← of ← ARMIES — drawn, not a text character (v94, army-editor.js v52).
   A character sits at a height its font decides, or a fallback font's where
   the heading font has no arrow; this icon is centred by .abs__back's
   align-items, with the button's gap before the label. 1em square, so it
   follows the label's size; stroked in currentColor. */
.abs__back-icon {
  width: 1em;
  height: 1em;
  flex-shrink: 0;
}

/* The army menu at the banner's right end — ArmyMenu (ArmyActions.js), the
   component the armies home's cards use. army.css styles every ArmyMenu; the
   rules here only seat the trigger on the banner. See v92. */
.abs__menu {
  flex-shrink: 0;
}

/* The banner's ink on a translucent white chip, so the bars read on any
   faction accent. army.css keeps the 38x38 size. */
.army-menu__btn.abs__menu-btn {
  color: inherit;
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.25);
}

/* ⚠ All three restated: .army-menu__btn:hover would repaint the chip light with
   dark bars, and a tapped button keeps :hover on iPad. */
.army-menu__btn.abs__menu-btn:hover {
  color: inherit;
  background: rgba(255, 255, 255, 0.22);
  border-color: rgba(255, 255, 255, 0.25);
}

/* ── Army-level flags ────────────────────────────────────── */

.abs__flags {
  padding: var(--space-2) var(--space-4);
  background: var(--amber-light);
  border-bottom: 1px solid var(--amber);
  flex-shrink: 0;
}

/* ── The three columns ───────────────────────────────────── */

.abs__columns {
  display: flex;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

/* ⚠ Set here rather than in army-builder-col3.css — see the v2 note. */
.abs-units-header {
  color: var(--text-inv);
}

/* ============================================================
   UNIT ROWS
   Detail lines beneath the .ub-card name row. The card itself, its photo slot
   and its name/points row all come from army-builder-col1.css v44.
   ============================================================ */

.abs-unit__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  font-size: var(--text-xs);
  line-height: 1.2;
  color: var(--text-dim);
}

.abs-unit__badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

/* Right half of the row, under the points. One step up from the badges. */
.abs-unit__models {
  flex-shrink: 0;
  font-size: var(--text-sm);
  color: var(--text-dim);
  white-space: nowrap;
}

.abs-unit__epic,
.abs-unit__subfaction,
.abs-unit__tier,
.abs-unit__warlord,
.abs-unit__bodyguard {
  padding: 1px var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--surface-lo);
  border: 1px solid var(--border);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 600;
}

/* The army's single Warlord — filled amber so it reads as the headline fact on
   the card, not one badge among several (v71). */
.abs-unit__warlord {
  background: var(--amber);
  border-color: var(--amber);
  color: var(--text-inv);
}

/* 🔴 BODYGUARD IS NOT A NEUTRAL BADGE (v79). The shared rule's surface-lo/border
   is already worn by EPIC HERO and the chapter — both datasheet-identity facts —
   and this is a command-structure fact, like Warlord. Amber is taken (filled by
   Warlord, outlined by the tier badge), so it takes GREEN: unused elsewhere on
   the card, and the same green the Unit Profile marks an attached target with,
   which is this very relationship seen from the leader's side. */
.abs-unit__bodyguard {
  background: var(--green-light);
  border-color: var(--green);
  color: var(--green);
}

/* A faction- or detachment-scoped price is the interesting case; 'base' is not
   badged at all. */
.abs-unit__tier {
  background: var(--amber-light);
  border-color: var(--amber);
  color: var(--amber-dim);
}

/* ============================================================
   ISSUE CODES
   🔴 The resolver degrades per unit rather than throwing. A flagged unit still
   renders, still counts, and says what needs review. Visible, and deliberately
   not alarming — this is an upstream refresh moving something under a saved
   army, not an error the player made.
   ============================================================ */

.abs-unit--flagged {
  border-left: 3px solid var(--amber);
}

.abs-issues {
  margin-top: var(--space-2);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* ⚠ RESTORED AT v113, EXACTLY AS IT WAS. v112 stripped this to a plain line
   because the unit card stacked two of them into a wall of panels — but
   .abs-issue is emitted in FOUR places, not one: the unit card, and three in the
   army banner's .abs__flags strip, one of which is the write-failure line. That
   change quietened a failed save. The unit card no longer renders these at all
   (army-editor-units.js v32), so the box comes back untouched for the strip that
   always wanted it. 🔴 A SHARED CLASS IS NOT A LOCAL ONE — the same fault this
   milestone cleaned out of source-check.css, committed here by the agent that
   cleaned it. */
.abs-issue {
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--amber-light);
  border: 1px solid var(--amber);
  font-size: var(--text-xs);
  line-height: 1.35;
  color: var(--text);
}

.abs-busy {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  color: var(--text-dim);
}


/* ============================================================
   COLUMN 1 — BROWSER DELTAS
   The card, its glyph slot and the role headers come from
   army-builder-col1.css v44 and codex-col2.css. Only the search field is new.
   ============================================================ */

.abc1-search {
  flex: 1;
  min-width: 0;
  height: 32px;
  padding: 0 var(--space-2);
  font-size: var(--text-sm);
}

/* ── Filter panel toggle — last in the search/sort row (v66) ──
   Same 32px height as the search field and the sort select. `position:
   relative` anchors the count badge. ⚠ base.css sets button line-height: 1;
   the SVG child does not care. */
.abc1-filter-toggle {
  position: relative;
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-dim);
  cursor: pointer;
  transition: border-color var(--transition-fast), color var(--transition-fast), background var(--transition-fast);
}

.abc1-filter-toggle--open {
  background: var(--amber-light);
  border-color: var(--amber);
  color: var(--amber-dim);
}

/* Active-filter count. Geometry follows .cc1-acc__count so the badge on the
   toggle reads like the badges inside the panel. */
.abc1-filter-toggle__count {
  position: absolute;
  top: -6px;
  right: -6px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--amber);
  color: var(--text-inv);
  border-radius: 9px;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 700;
  line-height: 1;
}

/* ── The panel — a flex child of .ab-col between the row and the list (v66).
   Splits the column's remaining height 50/50 with the list (v67): both are
   flex: 1 1 0 with min-height 0 (.ab-col__list's own flex:1 is the same),
   so neither depends on the column's height being definite. Each scrolls on
   its own. ⚠ SIBLING of .ab-col__list, never its wrapper. */
.abc1-filters {
  flex: 1 1 0px;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: var(--surface);
  border-bottom: 2px solid var(--border);
  border-bottom-color: color-mix(in srgb, var(--border) 75%, #000000 25%);
}

/* The list beneath an OPEN panel: the same explicit basis, so the two halves
   come from the flex algorithm, not from either one's content (v68). When
   the panel is closed this selector matches nothing and the list keeps its
   own flex:1 from army-editor-columns.css. */
.abc1-filters + .ab-col__list {
  flex: 1 1 0px;
  min-height: 0;
}

/* The Codex panel's outer padding is sized for a column it owns outright;
   inside a shared column the bottom run is trimmed to match the row above. */
.abc1-filters .cc1 {
  padding: var(--space-1) var(--space-3) var(--space-3);
}

/* ============================================================
   COLUMN 2 — LOADOUT PANEL
   Sits above the datasheet when an army unit is selected.
   ============================================================ */

.abc2-block { margin-bottom: var(--space-4); }
.abc2-block:last-child { margin-bottom: 0; }

/* The section titles — MODELS, WARGEAR. The larger, darker treatment, since
   they head the modal's sections. */
.abc2-block__title {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: var(--text-base);
  letter-spacing: var(--heading-tracking);
  text-transform: uppercase;
  color: var(--text);
  margin-bottom: var(--space-2);
}

.abc2-none {
  font-size: var(--text-sm);
  color: var(--text-dim);
  font-style: italic;
}

/* ── Model sizes ─────────────────────────────────────────
   One button per published size, priced. The set is the resolver's; nothing
   here derives a squad size. */

.abc2-sizes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.abc2-size {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 64px;
  padding: var(--space-2) var(--space-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

.abc2-size:hover:not(:disabled) { border-color: var(--amber); }

.abc2-size--on {
  border-color: var(--amber);
  background: var(--amber-light);
}

.abc2-size__n {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: var(--text-lg);
  color: var(--text-hi);
}

.abc2-size__cost {
  font-size: var(--text-xs);
  color: var(--text-dim);
}

/* ── Wargear options ─────────────────────────────────────
   🔴 The instruction renders verbatim. No code parses it, so it is prose on
   the page and the items beneath it are the only thing the app understands. */

.abc2-option {
  /* v42: halved above, so the DEFAULT WARGEAR heading sits closer to its rule. */
  padding: var(--space-1) 0 var(--space-3);
  border-top: 1px solid var(--border);
}

.abc2-option:first-of-type { border-top: none; }

.abc2-option__instruction {
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text);
  margin-bottom: var(--space-2);
  white-space: pre-line;
}

.abc2-items {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* ⚠ FILLS THE COLUMN AND WRAPS. See the v34 note — a fixed 400px overflowed a
   25% column on a 1024pt iPad. The steppers still align because every row is
   the same width; when there is not room for pill and stepper on one line, the
   stepper wraps beneath rather than overlapping.
   ⚠ Height sits between v10's touch-target row and v11's compact one — tall
   enough to tap, not so tall the pill floats in it. */
.abc2-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-1) var(--space-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: var(--text-sm);
  min-height: 38px;
  margin-bottom: var(--space-1);
}

.abc2-item:last-child { margin-bottom: 0; }

.abc2-item--on {
  border-color: var(--amber);
  background: var(--amber-light);
}


/* Holds the count, the pill and the cost badge. ⚠ min-width: 0 is what lets it
   shrink below its content width instead of forcing the row wider — without it
   a flex item refuses to go below its intrinsic size and the stepper is pushed
   out of the column. */
/* The whole row width, so a pill never has to wrap or shrink. */
.abc2-item__name {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
  min-width: 0;
}

/* ⚠ THE PILL KEEPS THE DATASHEET'S nowrap. It now has the full row width to
   itself (v37), so a long name fits without wrapping — which is what makes the
   rows a uniform height. overflow-wrap is the last resort for a single token
   longer than the column, where nowrap alone would overflow. */
.abd-body .abc2-item .ds-equip-pill {
  overflow-wrap: anywhere;
  max-width: 100%;
}

.abc2-item__qty {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}

/* Spacing only. The look is .ds-comp-points-row__badge's — see the v11 note. */
.abc2-item__cost { flex-shrink: 0; }

.abc2-item__qty {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}

/* ⚠ 32px, and a full touch target on the row. These are the controls a player
   taps repeatedly while setting a squad's loadout on an iPad. */
.abc2-step {
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-lo);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-weight: 700;
  color: var(--text);
  cursor: pointer;
  padding: 0;
}

.abc2-step:hover:not(:disabled) {
  border-color: var(--amber);
  background: var(--amber-light);
}


.abc2-step__stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.05;
}

.abc2-step__n {
  min-width: 24px;
  text-align: center;
  font-weight: 700;
  font-size: var(--text-md);
}

/* A quantity here counts models carrying the item. Small and quiet — it labels
   the number, it does not compete with it. */
.abc2-step__unit {
  /* v32: 2px below --text-xs. See the note — no smaller token exists. */
  font-size: calc(var(--text-xs) - 2px);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-dim);
}


/* ============================================================
   SORT CONTROL ICON
   A label for the dropdown, not a direction toggle — the options carry their
   own direction ('Points, high' / 'Points, low'), so a toggle would duplicate
   or contradict them. Shared by columns 1 and 3.
   ============================================================ */

.abx-sort-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.abx-sort-icon {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  color: var(--text-dim);
}

/* ⚠ All three properties, together. See the v5 note — overriding the flex
   alone leaves the 120px width and the auto margin in place. `width: auto` is
   what lets the control size to its longest option; the right padding is kept
   because the native chevron sits in it. */
.abx-sort-row .ab-sort-select {
  flex: 0 0 auto;
  width: auto;
  margin-left: 0;
  /* Matches .abc1-search. See the v6 note on the touch target. */
  height: 32px;
  min-height: 32px;
}

/* Fills the free width so the dropdown and icon sit right. */
.abx-sort-row .abc1-search,
.abx-sort-spacer {
  flex: 1 1 auto;
  min-width: 0;
}


/* ============================================================
   DEFAULT WARGEAR
   Upstream publishes the loadout a unit arrives with as an option like any
   other. It is not a choice, so it reads as a label rather than an instruction.
   ============================================================ */


/* ============================================================
   ENHANCEMENT RESTRICTION
   Shown beneath the picker because a native <option> holds text and nothing
   else. Two states: applied (the resolver checked it) and unapplied (it could
   not, and the player must).
   ============================================================ */

.abc2-restriction {
  margin-top: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border-hi);
  font-size: var(--text-xs);
  line-height: 1.4;
  color: var(--text-dim);
}

.abc2-restriction--unapplied {
  background: var(--amber-light);
  border-color: var(--amber);
  border-left-color: var(--amber);
  color: var(--text);
}

.abc2-restriction__note {
  font-weight: 600;
  color: var(--amber-dim);
}


/* ============================================================
   ARMY LIST ROWS
   🔴 .ub-card CARRIES margin-bottom AND .ub-card:last-child ZEROES IT. Inside a
   wrapper the card is always its own last child, so the gap collapses unless
   the wrapper owns the spacing. Do not delete these with anything else.
   ============================================================ */

.abs-unit-wrap {
  display: block;
  margin-bottom: var(--space-2);
}

.abs-unit-wrap:last-child { margin-bottom: 0; }
.abs-unit-wrap .ub-card   { margin-bottom: 0; }

/* ============================================================
   DETAIL COLUMN
   What the two modals held, in a column. The .abc2-* controls below are reused
   exactly as they were — the behaviour moved, the styling came with it.
   ============================================================ */

/* ⚠ NO VERTICAL PADDING. The section headers are full-width bars pulled out by
   a negative side margin; top padding here would push the first bar down and
   leave a gap the bars themselves cannot fill. Their own bodies supply the
   vertical rhythm. */
.abd-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0 var(--space-4);
}

.aum-enh__restriction {
  display: block;
  margin-top: 2px;
  /* Italic, like every other explainer in this column (v88). */
  font-style: italic;
  color: var(--text-dim);
}

/* The keywords the restriction names (army-editor-profile.js v39). They decide
   whether it applies to this unit; the rest of the sentence is scaffolding. */
.aum-enh__restriction strong {
  font-weight: 700;
  color: var(--text);
}

/* 🔴 The resolver saw a restriction and could not act on it, so the player
   must. Louder than the applied state on purpose. */
.aum-enh__restriction--unapplied {
  color: var(--amber-dim);
  font-weight: 600;
}

/* ⚠ THE UNAPPLIED LINE IS ALREADY 600, so a 700 keyword barely reads against it
   and the amber would be lost to the keyword's own colour. Inside that state the
   keyword goes heavier and keeps the amber — the warning outranks the emphasis. */
.aum-enh__restriction--unapplied strong {
  font-weight: 800;
  color: inherit;
}


/* ============================================================
   DEFAULT WARGEAR HEADING
   Upstream's 'Default Wargear' marker reads as a section heading rather than an
   instruction, because it is not one — it names the kit the unit arrives with.
   ============================================================ */

.abc2-option__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}

/* DEFAULT WARGEAR sits INSIDE the wargear section, so it takes the smaller,
   dimmer treatment the section titles used to have. */
.abc2-option__head-label {
  font-family: var(--font-heading);
  font-weight: 800;
  /* v41: --text-sm -> --text-md. It heads a block inside the modal and was
     reading as smaller than the prose beneath it. */
  font-size: var(--text-md);
  letter-spacing: var(--heading-tracking);
  text-transform: uppercase;
  color: var(--text-dim);
}

/* Beside the heading, where the thing it resets is. Disabled when the loadout
   already is the default. */
.abc2-reset {
  padding: 2px var(--space-2);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-dim);
  cursor: pointer;
  white-space: nowrap;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}

.abc2-reset:hover:not(:disabled) {
  border-color: var(--amber);
  color: var(--amber-dim);
}

/* Spacing only — the badge look is .ds-comp-points-row__badge's. */
.aum-enh__cost {
  margin-left: var(--space-2);
}

/* 🔴 IN THE UNIT PROFILE THE COST SITS AT THE BUTTON'S RIGHT EDGE (v78),
   vertically centred, mirroring the ✓ on the left — so the two markers bracket
   the centred label instead of the cost trailing the name. Out of flow, inside
   the button's 40px right padding, so it never crowds a wrapped name. The
   wargear modal's own pills are untouched (this is scoped to .abd-body). */
.abd-body .combine-modal__option .aum-enh__cost {
  position: absolute;
  right: var(--space-2);
  top: 50%;
  transform: translateY(-50%);
  margin-left: 0;
}


/* ============================================================
   SELECTION-ROW PILL SIZE
   🔴 SCOPED TO .abd-body .abc2-item, AND THE SCOPE IS THE WHOLE POINT. .ds-equip-pill
   lives in datasheet.css and is shared with the Codex; this file loads later,
   so a broader rule would resize pills that are not ours. Confined to a wargear
   SELECTION ROW it touches nothing else — not the Codex, not the Builder's
   datasheet, and not the pills inside an option's prose, which must keep the
   datasheet's proportions because they sit in a sentence.
   ⚠ PADDING ONLY. datasheet.css v118 sets `1px 8px 0` — one pixel on top so the
   pill grows upward and the text stays put. This adds one more each way and
   changes nothing else; the colours, badge and radius stay the datasheet's.
   ============================================================ */

.abd-body .abc2-item .ds-equip-pill {
  padding-top: 2px;
  padding-bottom: 1px;
}


/* ============================================================
   TAKEN ENHANCEMENT
   One enhancement, one unit. Shown disabled with its holder named — dimmed
   rather than flagged, because another unit having it is ordinary.
   ============================================================ */

.aum-enh--taken {
  opacity: 0.55;
  cursor: not-allowed;
}

.aum-enh--taken:hover { background: inherit; }

.aum-enh__taken {
  display: block;
  margin-top: 2px;
  font-weight: 600;
  /* 🔴 ITALIC, LIKE EVERY OTHER EXPLAINER IN THIS COLUMN (v88). See the note on
     .abd-attach-note — these lines are asides about a button, and they are
     parenthesised in army-editor-profile.js v41 to match. */
  font-style: italic;
  color: var(--text-dim);
}


/* ============================================================
   ADD MODE
   Default is 25/50/25 — army list, datasheet, unit detail. Add mode inserts the
   browser at the left and hides the detail column, giving 25/25/50.
   ⚠ ONE RULE. The widths come from army.css's .ab-col--1 / --2; see the v18
     note. Do not add width declarations here — two files setting the same
     column's width is how a layout starts disagreeing with itself.
   ============================================================ */

.abs__columns--adding .ab-col--3 {
  display: none;
}

/* The header chip worn by three controls: the army list's [+], and the unit
   browser's × and its Import Units button (army-editor-browser.js). Sits on a
   faction accent of unknown darkness, so it inherits its colour rather than
   fixing one. ⚠ A disabled one — the [+] while the browser is open, the
   Import Units button always — dims through base.css's button:disabled, and
   the :not(:disabled) guards below keep it flat. */
.abs-browser-toggle {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: var(--space-2);
  font-size: var(--text-lg);
  line-height: 1;
  color: inherit;
  background: rgba(255, 255, 255, 0.18);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--transition-fast);
}

.abs-browser-toggle:hover:not(:disabled)  { background: rgba(255, 255, 255, 0.3); }
.abs-browser-toggle:active:not(:disabled) { background: rgba(255, 255, 255, 0.4); }

/* The browser's own close control, on the right of its header. */
.abs-browser-toggle--close {
  margin-right: 0;
  margin-left: auto;
}


/* ============================================================
   BROWSER ROW
   Card plus add button. The card shows the datasheet, the button adds the unit.

   ⚠ .ub-card CARRIES margin-bottom AND .ub-card:last-child ZEROES IT. Inside
     this wrapper the card is never the last child, so the margin would survive
     as a gap between the card and nothing. The row owns the spacing and the
     card's is zeroed — the same correction .abs-unit-wrap needed.
   ============================================================ */

.abc1-row {
  display: flex;
  align-items: stretch;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.abc1-row:last-child { margin-bottom: 0; }

.abc1-row .ub-card {
  flex: 1;
  min-width: 0;
  margin-bottom: 0;
}

/* Amber, as the ADD TO ARMY control it replaced was. The card gives up this
   width plus the row gap. */
.abc1-add {
  flex: 0 0 52px;
  align-self: stretch;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-xl);
  line-height: 1;
  background: var(--surface);
  color: var(--amber);
  border: 2px solid var(--amber);
  border-radius: var(--radius-lg);
  cursor: pointer;
  transition: background var(--transition-fast), transform var(--transition-fast);
}

.abc1-add:hover:not(:disabled)  { background: var(--amber-light); }
.abc1-add:active:not(:disabled) { transform: scale(0.96); }

/* 🔴 At the Rule of Three's limit, or an Epic Hero already taken. Disabled and
   explained — the button's title carries the reason, so a greyed arrow is not a
   mystery. The unit still browses and still shows its datasheet. */
.abc1-add--blocked {
  background: var(--surface-lo);
  color: var(--text-dim);
  border-color: var(--border);
  cursor: not-allowed;
}

/* ⚠ THE CARD DIMS BUT STAYS SELECTABLE. A unit at its limit still opens its
   datasheet — reading a sheet is not the same intent as adding the unit, so
   this is opacity on the text, not pointer-events on the card. */
.abc1-card--blocked .ub-card__name,
.abc1-card--blocked .ub-card__pts {
  color: var(--text-dim);
  opacity: 0.7;
}


/* ============================================================
   WRAPPING UNIT NAMES
   ⚠ SCOPED TO THE BUILDER'S ROWS. .ub-card__name is army-builder-col1.css v44's
     and is shared with the Codex's unit list, where single-line rows are right.
     .abc1-row is the browser row and .abs-unit-wrap the army list's — an
     unscoped rule would rewrap the Codex too.
   ⚠ army-builder-col1.css sets white-space: nowrap with an ellipsis; both have
     to be undone or the name still truncates at one line.
   ⚠ NO LINE CLAMP (v35). A name takes the lines it needs and the card grows,
     because the card is height: auto with a 70px minimum. overflow-wrap catches
     the one case wrapping cannot: a single token longer than the column.
   ============================================================ */

.abc1-row .ub-card__name,
.abs-unit-wrap .ub-card__name {
  white-space: normal;
  overflow: hidden;
  text-overflow: clip;
  overflow-wrap: anywhere;
  line-height: 1.15;
}


/* ============================================================
   CARD BADGES
   EPIC HERO, and the sub-faction — the chapter on a Space Marines reprint,
   nothing on a plain one. Derived per card from its own faction file.
   ============================================================ */

.abs-unit__epic {
  background: var(--astartes-light);
  border-color: var(--astartes);
  color: var(--astartes);
}

.abs-unit__subfaction {
  background: var(--surface-lo);
  border-color: var(--border-hi);
  color: var(--text-dim);
}


/* ============================================================
   DETAIL SECTION HEADERS
   Same treatment as the army list's role headers, so the two columns read as
   one surface. ⚠ A section with nothing in it renders nothing at all — not a
   header, not an empty-state line.
   ============================================================ */

/* ⚠ NO OUTER MARGIN. The header bars butt against each other like the cost
   footer's head, so the column reads as a stack of panels rather than a list
   of floating groups. */
.abd-section { margin-bottom: 0; }

/* Full width. ⚠ 44px; the cost footer's head is 54px on purpose — see its
   note. */
.abd-section__head {
  display: flex;
  align-items: center;
  height: 44px;
  padding: 0 var(--space-4);
  margin: 0 calc(-1 * var(--space-4));
  background: var(--surface-lo);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  font-family: var(--font-heading);
  font-weight: var(--heading-weight);
  /* Matches .ab-category-header in army-builder-col1.css — the army list's role
     headers. The two columns' bars should read as the same kind of thing. */
  font-size: calc(var(--text-base) * 1.10);
  letter-spacing: var(--heading-tracking);
  text-transform: uppercase;
  color: var(--text-dim);
}

.abd-section__body {
  padding: var(--space-3) 0;
}


/* ============================================================
   DETAIL COLUMN SURFACE
   ⚠ SCOPED TO .abd-col, NOT .ab-col--3. That class is shared — the Codex's
   third column uses it too, and this file loads after army.css, so an unscoped
   rule would repaint a surface this session has no business touching.
   ============================================================ */

.abd-col {
  background: var(--bg);
}

/* Shaped like the Edit Wargear button beneath the wargear section — same width,
   same padding, centred label — so the column's two lists of actions look
   alike.
   ⚠ SCOPED TO .abd-body: the wargear modal uses this class for its combine
   options, where left-aligned full-bleed rows are right.
   ⚠ ONE RULE, DELIBERATELY. This selector was styled in two places until v52
   and the earlier width won. */
.abd-body .combine-modal__option {
  position: relative;
  width: 100%;
  /* Centred both ways (v74). justify-content centres the label on the main
     (vertical) axis so it meets the ✓, which is centred in the touch-target
     min-height; the side padding keeps a wrapping name clear of the check,
     which sits in the left margin. The gutter is symmetric, so nothing shifts
     when the ✓ appears on selection.
     ⚠ 40px, AND IT IS A FLOOR SET BY THE TWO THINGS IN THE GUTTER, not a taste
     value. The ✓ sits at left: var(--space-3); the enhancement cost badge sits
     at right: var(--space-2) and is wider than the check. v81 cut this to 30px
     to buy line width for a per-row role tag, and v82 then removed that tag in
     favour of the LEADERS/SUPPORTS sub-headings — leaving the narrower gutter
     with nothing to pay for and a cost badge running under the enhancement name
     (v86). Restored. Do not tighten it again without shrinking the badge. */
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--space-3) 40px;
  margin-bottom: var(--space-2);
  border-width: 1px;
  min-height: var(--touch-target);
}

.abd-body .combine-modal__option:last-child { margin-bottom: 0; }


/* ============================================================
   INSTANCE NUMBER
   #1, #2 … on a datasheet the army holds more than once. Quieter and smaller
   than the name it follows — it disambiguates two cards, it does not compete
   with them.
   ⚠ Inside .ub-card__name, which is uppercase and heading-weight; both are
   undone here so the number reads as an annotation.
   ============================================================ */

.abs-unit__instance {
  font-size: var(--text-sm);
  font-weight: 400;
  color: var(--text-dim);
  text-transform: none;
  letter-spacing: 0;
}


/* ============================================================
   CARD HEIGHT AND THE RIGHT-HAND STACK
   ============================================================ */

/* Grows for a wrapped name plus badges; never shorter than the fixed height it
   replaces. ⚠ SCOPED — see the v26 note. */
.abc1-row .ub-card,
.abs-unit-wrap .ub-card {
  height: auto;
  min-height: 70px;
}

/* ⚠ align-items: baseline on .ub-card__name-row would align the STACK's first
   line with the name's first line, which is what we want — but the stack needs
   its own alignment for the two figures inside it. */
.abs-unit__right {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  flex-shrink: 0;
  gap: 0;
  /* ⚠ Overrides the body's 1.55. See the v30 note — at 1.55 this stack alone
     was taller than the 70px card allows once a badge row is added. */
  line-height: 1.2;
}

/* 🔴 The total is not the base tier price — a paid wargear option, an
   enhancement, or the surcharge on a repeated selection. Red states that the
   number was changed by something; it is not an error. */
.abs-unit__pts--modified {
  color: var(--red);
}


/* ============================================================
   COLUMN HEADER HEIGHT
   🔴 EQUAL BY CONSTRUCTION, NOT BY COINCIDENCE. .ab-col__header is padded and
   content-sized, so a column carrying a 40px button grew taller than one
   carrying only a title. Fixing the height on all four makes them match
   whatever they hold, rather than relying on every future control being short
   enough not to stretch its header.

   56px is the natural height the padded title already produced — 24px of
   padding plus a ~31px line — so the columns without buttons are unchanged.
   The padding goes to zero because the height now does that job;
   align-items: center on the existing rule centres the contents.

   ⚠ SCOPED TO .abs__columns. .ab-col__header is shared with the Codex, and
   this file loads after the stylesheet that defines it.
   ⚠ A control taller than 56px will overflow rather than stretch. That is the
   intended trade: a ragged header row is worse than a clipped button, and the
   clipping is visible immediately.
   ============================================================ */

.abs__columns .ab-col__header {
  height: 56px;
  padding-top: 0;
  padding-bottom: 0;
}

/* ============================================================
   COLUMN HEADER — LEGIBLE DEFAULT WHEN THERE IS NO ACCENT (D)
   🔴 .ab-col__title is #fff and .ab-col__header is var(--surface) (light).
   With a faction accent the header gets an inline dark-ish background and the
   white title reads; with NO accent, army-editor.js emits an empty style
   string, the header stays light, and the title is white-on-white — invisible,
   not missing. This is exactly how the Genestealer Cults gap showed.

   The fix is a legible default: when the header carries no inline background
   (attribute absent or empty), paint it the dark banner colour so the white
   title reads — the same charcoal the app's other headers use. An inline
   accent still wins by specificity, so a resolved faction is unaffected.

   ⚠ Defensive, not cosmetic. faction-colours.js's three maps are the only
   authored faction lists left, so a real gap is unlikely — but if one happens
   it degrades to plain-but-readable instead of invisible.
   ⚠ SCOPED TO .abs__columns — the Codex's own .ab-col__header is untouched.
   ⚠ Mirrors the .ab-col__view-btn no-accent precedent in
   army-editor-columns.css, matched on the same [style]/[style=''] pair.
   ============================================================ */
.abs__columns .ab-col__header:not([style]),
.abs__columns .ab-col__header[style=''] {
  background: var(--banner);
  color: var(--banner-text);
}


/* ============================================================
   ITEM COUNT
   The '1' an option's sentence used to end with, now sitting beside the item it
   referred to. Bold, so it reads as the quantity the sentence promised rather
   than as part of the item's name.
   ============================================================ */

.abc2-item__count {
  font-weight: 800;
  font-size: var(--text-sm);
  color: var(--text-hi);
  margin-right: var(--space-1);
}


/* ============================================================
   CUSTOMIZE WARGEAR
   The Unit Profile column's way into the wargear modal. Full width, because it
   is the only thing in its section and a narrow button in a narrow column looks
   like an afterthought.
   ============================================================ */

.abd-wargear-btn {
  display: block;
  width: 100%;
  padding: var(--space-3);
  background: var(--surface);
  border: 2px solid var(--amber);
  border-radius: var(--radius);
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: var(--text-sm);
  letter-spacing: var(--heading-tracking);
  text-transform: uppercase;
  /* ⚠ FULL AMBER, NOT --amber-dim. Against the column's parchment background
     the dimmed tone read as a disabled control. */
  color: var(--amber);
  cursor: pointer;
  min-height: var(--touch-target);
  transition: background var(--transition-fast);
}

.abd-wargear-btn:hover:not(:disabled) { background: var(--amber-light); }

/* Grey, not faint amber. base.css only drops opacity on a disabled button, and
   a dimmed amber still reads as amber — as an enabled control someone should
   try tapping. */
.abd-wargear-btn:disabled {
  border-color: var(--border);
  color: var(--text-dim);
  background: var(--surface-lo);
}


/* ============================================================
   WARGEAR MODAL SIZE
   Wider than .ap-modal's 420px default, because the option rows carry a costed
   pill and a three-button stepper on one line — the width the column could not
   give them is the whole reason this is a modal.
   ============================================================ */

.aum-modal {
  max-width: 670px;
  width: 94vw;
}

/* 🔴 THE SCROLL LIVES HERE. .ap-modal is overflow: hidden, so capping the modal
   itself would cut the footer off instead of letting the list scroll under it.
   Header and footer stay put; the options scroll. */
/* ============================================================
   TWO COLUMNS
   Composition on the left, options on the right, divided by a rule. ⚠ ONLY THE
   OPTIONS SCROLL — the composition is reference and stays put.
   ============================================================ */

.awm-cols {
  display: flex;
  align-items: stretch;
  min-height: 0;
}

.awm-composition {
  flex: 0 0 250px;
  max-width: 250px;
  border-right: var(--border-width) solid var(--border);
  max-height: 60vh;
  /* Safety net only. It should not need to scroll; a very long composition
     should scroll rather than be clipped. */
  overflow-y: auto;
  background: var(--surface);
}

/* ⚠ The datasheet block brings its own section chrome; it sits flush here
   rather than inside the modal's usual body padding. */
.awm-composition .ds-section { border: none; }

.awm-options {
  flex: 1 1 auto;
  min-width: 0;
  max-height: 60vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}


/* ============================================================
   MODAL FOOTER TOTAL
   The unit's resolved cost, beside the Done button.
   ⚠ .ap-modal__footer IS A FLEX ROW, so this is laid out along it — the earlier
     version was a flex column, which stacked the label onto the value and made
     the two collide.
   ============================================================ */

.aum-modal .ap-modal__footer {
  justify-content: space-between;
  align-items: center;
}

.aum-total {
  display: flex;
  align-items: center;
}

/* Amber, as every points figure in the app is. */
.aum-total__value {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: var(--text-lg);
  color: var(--amber-dim);
}

/* Matches the Customize Wargear button that opens this modal. */
.awm-done {
  border: 2px solid var(--amber);
  color: var(--amber);
  font-family: var(--font-heading);
  font-weight: 800;
  letter-spacing: var(--heading-tracking);
  text-transform: uppercase;
}

.awm-done:hover:not(:disabled) { background: var(--amber-light); }

/* ⚠ SCOPED — see the v43 note. The datasheet's own heading is unchanged. */
.awm-composition .ds-comp-points__head {
  font-size: var(--text-sm);
}


/* ============================================================
   WARGEAR STATE READOUT
   Which side the loadout is on. ⚠ Display only — see the v45 note.
   ============================================================ */

.abd-state {
  display: flex;
  margin-bottom: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  /* Not a control: nothing here responds to a tap. */
  pointer-events: none;
  user-select: none;
}

.abd-state__half {
  flex: 1 1 50%;
  padding: var(--space-1) var(--space-2);
  text-align: center;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: var(--text-xs);
  letter-spacing: var(--heading-tracking);
  text-transform: uppercase;
  color: var(--text-dim);
  background: var(--surface);
}

.abd-state__half--on {
  background: var(--amber);
  color: var(--text-inv);
}

/* Unreachable, not merely unoccupied: this datasheet publishes no choices, so
   the loadout can never be customized. */
.abd-state__half--void { opacity: 0.4; }


/* ============================================================
   UNIT COST FOOTER
   Collapsed to a head; the breakdown opens beneath it. Pinned below the
   scrolling sections — see the v46 note.
   ============================================================ */

.abd-cost {
  flex-shrink: 0;
  border-top: 2px solid var(--border);
  background: var(--surface);
}

.abd-cost__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  /* ⚠ 10px taller than .abd-section__head, deliberately — the footer is the
     column's conclusion, not another section bar. */
  height: 54px;
  padding: 0 var(--space-4);
  background: transparent;
  border: none;
  cursor: pointer;
}

.abd-cost__head:hover { background: var(--surface-lo); }

.abd-cost__title {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: var(--text-sm);
  letter-spacing: var(--heading-tracking);
  text-transform: uppercase;
  color: var(--text-dim);
}

.abd-cost__total {
  margin-left: auto;
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: var(--text-lg);
  color: var(--amber-dim);
}

/* 🔴 Not the base tier price — wargear, an enhancement, or the
   repeated-selection surcharge. The same red the unit card uses, driven by the
   same comparison. */
.abd-cost__total--modified { color: var(--red); }

.abd-cost__chev {
  font-size: var(--text-sm);
  color: var(--text-dim);
}

/* Above the head, so its rule separates it from the sections rather than from
   the total. */
/* ⚠ THE SAME RIGHT PADDING AS THE HEAD, so a breakdown figure sits directly
   above the total it adds up to. */
.abd-cost__body {
  padding: var(--space-3) var(--space-4) var(--space-2);
  border-bottom: 1px solid var(--border);
}

.abd-cost__row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  padding: 2px 0;
  font-size: var(--text-sm);
  color: var(--text);
}

.abd-cost__label { min-width: 0; }

.abd-cost__value {
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}

/* 🔴 The components disagree with the resolver's own total — a defect in one of
   them. Loud on purpose: a breakdown that quietly fails to add up is worse than
   none at all. */
.abd-cost__mismatch {
  margin-top: var(--space-2);
  padding: var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--red-light);
  border: 1px solid var(--red);
  font-size: var(--text-xs);
  color: var(--red);
}


/* ============================================================
   ENHANCEMENT OPTION CONTENTS
   The option box itself is styled above, in one place. These are its children.
   ============================================================ */

.abd-body .combine-modal__option-title {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  /* Bolder, and open the wrapped lines up — base.css sets button line-height:1
     which cramped a two-line name (v76). */
  font-weight: 800;
  line-height: 1.3;
}

/* ⚠ THE BADGE MUST NOT BE STRETCHED BY THE FLEX LINE (v53), AND MUST NOT BE
   FLATTENED BY THE BUTTON'S line-height: 1 (v54). Both notes above. */
.abd-body .combine-modal__option-title .ds-comp-points-row__badge {
  display: inline-block;
  flex: 0 0 auto;
  align-self: center;
  line-height: 1.5;
}

.abd-body .combine-modal__option-desc {
  text-align: center;
}


/* ============================================================
   ARMY LIST HEADER — EDIT TOGGLE
   ============================================================ */

/* ⚠ THE POSITIONING CONTEXT. .ab-col__header carries no `position`, so an
   absolutely-positioned child would escape to the nearest positioned ancestor
   and centre against the wrong box. Scoped to the Builder's columns. */
.abs__columns .ab-col__header {
  position: relative;
}

/* Out of flow, so the two unequal buttons cannot pull it off centre. */
.abs-units-title {
  position: absolute;
  left: 0;
  right: 0;
  text-align: center;
  pointer-events: none;
}

.abs-edit-toggle {
  flex-shrink: 0;
  margin-left: auto;
  padding: 0 var(--space-3);
  height: 40px;
  display: flex;
  align-items: center;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: var(--text-sm);
  letter-spacing: var(--heading-tracking);
  text-transform: uppercase;
  color: inherit;
  background: rgba(255, 255, 255, 0.18);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: var(--radius-sm);
  cursor: pointer;
  /* ⚠ Above the absolutely-positioned title, which spans the whole header. */
  position: relative;
  z-index: 1;
  transition: background var(--transition-fast);
}

.abs-edit-toggle:hover  { background: rgba(255, 255, 255, 0.3); }
.abs-edit-toggle--on    { background: rgba(255, 255, 255, 0.4); }

/* ============================================================
   REMOVE BUTTON
   Mirrors the browser's add arrow — same size, opposite side, opposite colour.
   ⚠ Removal is immediate; there is no confirmation. See army-builder-col3.js.
   ============================================================ */

.abs-unit-wrap--editing {
  display: flex;
  align-items: stretch;
  gap: var(--space-2);
}

/* The card+drawer stack is the flex child beside the remove button now, not the
   bare card (v72). In the block wrap it is an ordinary block. isolate so the
   card/drawer z-index (v74) stays contained and cannot reach other rows. */
.abs-unit__stack { display: block; isolation: isolate; }
.abs-unit-wrap--editing .abs-unit__stack { flex: 1; min-width: 0; }

/* The card sits ABOVE the drawer so the drawer's top slides under it (v74). */
.abs-unit__stack .ub-card { position: relative; z-index: 1; }

.abs-remove {
  flex: 0 0 52px;
  align-self: stretch;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-xl);
  font-weight: 700;
  line-height: 1;
  background: var(--surface);
  /* Apple's destructive red. See the v56 note on why it is a literal. */
  color: #ff3b30;
  border: 2px solid #ff3b30;
  border-radius: var(--radius-lg);
  cursor: pointer;
  transition: background var(--transition-fast), transform var(--transition-fast);
}

.abs-remove:hover  { background: rgba(255, 59, 48, 0.1); }
.abs-remove:active { transform: scale(0.96); }


/* ============================================================
   ATTACHMENTS — LEADER / SUPPORT / LED BY  (v69)
   Buttons reuse .abd-body .combine-modal__option (the enhancement style) and
   .combine-modal__option--selected for the current pick. 🔴 SHOW-BUT-ALLOW: a
   slot held by other units is LABELLED and STILL CLICKABLE, so there is
   deliberately no .aum-enh--taken opacity/not-allowed here — only the notes.
   ============================================================ */

/* 🔴 SELECTED = GREEN + LEFT CHECK (v70). Overrides the amber base
   .combine-modal__option--selected inside the Unit Profile only. The base rule
   draws no checkmark, so the ::before below is the only one. Applies to
   Enhancements, Leader/Support/Led By targets and the Warlord button alike. */
.abd-body .combine-modal__option--selected {
  border-color: var(--green);
  background: var(--green-light);
}

.abd-body .combine-modal__option--selected .combine-modal__option-title {
  color: var(--green);
}

/* Hold the green on hover — the base .combine-modal__option:hover flips to
   amber, which on a selected button reads as a state change on touch. */
.abd-body .combine-modal__option--selected:hover {
  border-color: var(--green);
  background: var(--green-light);
}

.abd-body .combine-modal__option--selected:hover .combine-modal__option-title {
  color: var(--green);
}

/* The ✓, on the LEFT and vertically centred (not the wizard's top-right). The
   button carries position:relative and side padding to seat it (see above). */
.abd-body .combine-modal__option--selected::before {
  content: '✓';
  position: absolute;
  left: var(--space-3);
  top: 50%;
  transform: translateY(-50%);
  color: var(--green);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--text-md);
  line-height: 1;
}

/* LEADERS / SUPPORTS within LED BY — a centred label between two hairlines.
   It replaces the per-row role tag (v82): the role is said once for the run
   beneath it instead of on every button, where it competed with the name.
   ⚠ flex: 1 1 0% — SAFARI DROPS a unitless `flex: 1 1 0`, and the rules then
   collapse to nothing. The unit is load-bearing. */
.abd-subhead {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  /* Tight below, so the label reads as belonging to the buttons under it. The
     gap ABOVE is set per position by the two rules below (v84). */
  margin: 0 0 var(--space-2);
}

/* 🔴 POSITION COMES FROM .abd-lb-group, NOT :first-child (v84). Each role run
   sits in its own wrapper (army-editor-profile.js v34), so EVERY sub-heading is
   the first child of its own wrapper — a :first-child rule here hits the second
   heading too, which is exactly how v83 lost the space above SUPPORTS. */

/* The first run has no buttons above it, only the section body's top padding,
   which leaves the heading sitting low. ⚠ A LITERAL, not a token: it is a
   fraction of .abd-section__body's var(--space-3) padding, not a spacing step. */
.abd-lb-group:first-child .abd-subhead { margin-top: -4px; }

/* A later run needs a clear gap from the run above — larger than the gap below
   the heading, or the heading reads as trailing the previous run rather than
   introducing the next. ⚠ 14px LITERAL: this margin collapses through the
   wrapper against the previous run's last button, so it must win outright.
   v84 used 24px, which read as a gap between two blocks rather than a break
   inside one; v85 splits the difference (Jérôme). */
.abd-lb-group + .abd-lb-group .abd-subhead { margin-top: 14px; }

.abd-subhead::before,
.abd-subhead::after {
  content: '';
  flex: 1 1 0%;
  height: 1px;
  background: var(--border);
}

.abd-subhead__label {
  flex: 0 0 auto;
  color: var(--text-dim);
  font-size: var(--text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* 'Already led by X' — a taken slot is still clickable, so this reads as
   information, not a block. Amber, like the other soft notices in this column. */
.abd-attach-note {
  display: block;
  margin-top: 2px;
  color: var(--text-dim);
  font-style: italic;
  /* Open the wrapped warning lines up (v76). */
  line-height: 1.3;
}


/* ============================================================
   WARLORD  (v69, button since v70)
   Characters only. A combine-modal__option button that toggles the army's
   single Warlord on/off (green + check when this unit holds it).
   ⚠ .abd-warlord__note IS REMOVED (v80). The holder line moved INSIDE the
   button as a .abd-attach-note description, like every other explainer in this
   column, so the rule had no element left to style.
   ============================================================ */

/* ⚠ A MODAL'S OPTION ROW IS A BUTTON, and base.css sets button line-height: 1,
   which crushes a description that wraps to two lines (v77). Scoped to .ap-modal
   so the Unit Profile's own buttons (styled above) are untouched. */
.ap-modal .combine-modal__option-title { line-height: 1.25; }
.ap-modal .combine-modal__option-desc  { line-height: 1.45; }

/* The grey '#N' after a repeated target's name on a Leader/Support/Led By
   button (v72). Undoes the button title's uppercase heading treatment so it
   reads as an annotation, like the card's own instance number. */
.abd-target__num {
  font-weight: 400;
  color: var(--text-dim);
  text-transform: none;
  letter-spacing: 0;
}


/* ============================================================
   ATTACHMENT DRAWER  (v72, indented v73)
   '↳ Leads / Supports X', below the army-list card and INDENTED 20px so it
   reads as a child of the card rather than a second full-width row. Its own
   rounded box; the ↳ carries the visual connection to the card above.
   ============================================================ */

.abs-unit__attach {
  position: relative;
  z-index: 0;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-left: 20px;
  /* Slide up 20px so the top tucks under the card (which is z-index 1); the top
     padding grows by the same 20px so the text still lands below the card's
     bottom edge (v74). */
  margin-top: -20px;
  padding: calc(var(--space-2) + 20px) var(--space-3) var(--space-2);
  background: var(--surface-lo);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: var(--text-xs);
  line-height: 1.3;
  color: var(--text);
}

.abs-unit__attach-arrow {
  flex-shrink: 0;
  color: var(--text-dim);
  font-weight: 700;
}

/* The bodyguard name — bold and uppercase so it stands out in the line. */
.abs-unit__attach-name {
  font-weight: 700;
  text-transform: uppercase;
}

.abs-unit__attach-num {
  color: var(--text-dim);
  font-weight: 400;
}


/* ============================================================
   WARGEAR CONTROLS IN THE DATASHEET — DevAgent53 v95
   The .abc2-* controls in their third host: the datasheet's WARGEAR OPTIONS
   block, where datasheet.js v74 renders them for an army selection.
   🔴 EMITTED ONLY BY A POPULATED wargearEditor, so the Codex never carries this
   class and these rules cannot reach it — see the v95 note at the top.
   ⚠ The controls themselves are NOT redeclared. Everything below is either the
     container, or a rule the .abd-body scope already gives a selection row and
     which this host needs for the same reason.
   ============================================================ */

/* The read-only body's own padding is on .ds-wargear-options__body, which this
   state does not render. Same figures, so the block sits where it always did. */
.ds-wargear-options__editor {
  padding: var(--space-2) var(--space-3);
}

/* ⚠ THE TWO RULES THE .abd-body SCOPE WITHHOLDS. A pill in a selection row is
   given room to break and a little more height; in a sentence it must keep the
   datasheet's proportions. This host has both kinds, so the scope is the row,
   exactly as it is in the detail column.
   🔴 overflow-wrap IS LOAD-BEARING HERE, not cosmetic. This block sits in the
   7fr half of .ds-two-col-row — about 326px of a 512px datasheet column on a
   1024pt iPad — and .ds-equip-pill is nowrap, so a long weapon name would paint
   outside the column without it. */
.ds-wargear-options__editor .abc2-item .ds-equip-pill {
  overflow-wrap: anywhere;
  max-width: 100%;
  padding-top: 2px;
  padding-bottom: 1px;
}


/* ============================================================
   PRESETS — DevAgent56 v96
   The WARGEAR OPTIONS banner in the Editor: title, the loaded preset's label with
   its controls, and `Presets ▾` at the right.
   🔴 .ds-banner--presets IS EMITTED ONLY BY A POPULATED wargearEditor, so none of
   this can reach the Codex — see the v96 note at the top.
   ============================================================ */

/* ⚠ IT WRAPS RATHER THAN SQUASHING. This banner lives in the 7fr half of
   .ds-two-col-row — about 326px on a 1024pt iPad — and it can carry a title, a
   preset name, two icon buttons and the menu. Given no room, the group drops to a
   second line; .ds-banner's own flex, gap and colours are untouched. */
/* 🔴 NO flex-wrap. v97 added it as a crowding safety and it became the crowding
   PROBLEM: a flex line breaks BEFORE any item on it is squeezed, so the banner went
   to two rows in a 326px column while the button sat at its full width. The control
   shrinks and the name truncates instead — one row, always.
   ⚠ THE VARIANT'S OWN RIGHT EDGE, so .ds-banner keeps setting every other section's,
   the Codex's included. */
.ds-banner--presets {
  padding-right: 10px;
}

/* The title keeps its own width; everything else yields first. */
.abc2-presets__heading {
  flex-shrink: 0;
}

/* ⚠ THE LABEL CHIP, ITS NAME SPAN AND THE TWO ICON BUTTONS WENT AT v98. The menu
   button carries the name now, and renaming and deleting are the Edit dialog's. */

/* Drawn, not an asset (datasheet.js v78). One size, one stroke. */
.abc2-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  display: block;
}

/* ⚠ A SHADE SMALLER AND QUIETER (v101). It marks a preset rather than naming it, so
   it should not compete with the name beside it. opacity, never a colour — see the
   v101 note. */
.abc2-icon--sm {
  width: 12px;
  height: 12px;
  opacity: 0.68;
}

/* ── `Presets ▾` and its dropdown ─────────────────────────
   ⚠ margin-left: auto PUTS IT AT THE RIGHT WITHOUT TOUCHING .ds-banner. A
   justify-content on the shared rule would move the weapon banners too. */
/* 🔴 IT SHRINKS BEFORE THE ROW WRAPS. At flex-shrink: 0 the banner broke to two
   rows in a 326px column — the datasheet's real width on a 1024pt iPad — instead of
   the name truncating. The section title keeps its width; the preset name is the one
   that gives. flex-wrap on the banner is the last resort, not the first.
   ⚠ min-width: 0 IS WHAT MAKES SHRINKING POSSIBLE: a flex item will not go below its
   content width without it, whatever its flex-shrink says. */
.abc2-presets {
  position: relative;
  /* 🔴 A FLEX CONTAINER, NOT A BLOCK. As a block the inline-flex button sat in a
     line box whose height moved with its first child's baseline — 2px higher when a
     loaded preset put an eye in front of the text. See the v100 note. */
  display: flex;
  /* ⚠ margin-left: auto MOVED TO .abc2-presets__prefix AT v107. The prefix is now
     the first of the two right-hand items; an auto margin left here as well would
     have put a second one between them and thrown the pair to opposite ends of the
     banner. The 8px that separates them is .ds-banner's own gap. */
  flex-shrink: 1;
  min-width: 0;
  /* 🔴 THE CAP LIVES HERE, NOT ON THE BUTTON. The wrapper is the flex item, so
     flex resolves its width first — content size, capped at 220px, shrunk when the
     banner is narrow — and the button then fills it. With the cap on the button
     instead, the wrapper shrank and the button kept its content width and painted
     straight past the banner's edge. */
  max-width: 220px;
}

/* ⚠ RE-DECLARES THE BANNER'S TREATMENT. A button inherits neither font nor
   text-transform from its container (§14), so the label would read as body text
   beside the heading it sits on. */
.abc2-presets__btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  /* ⚠ 3px, NOT 1px (v102). Two more at each end, so it reads as a control rather
     than a label. Padding rather than a height — see the v102 note. */
  padding: 3px var(--space-2);
  background: transparent;
  border: 1px solid currentColor;
  border-radius: var(--radius-sm);
  color: inherit;
  font-family: var(--font-heading);
  font-size: var(--text-xs);
  font-weight: var(--heading-weight);
  letter-spacing: var(--heading-tracking);
  /* ⚠ NOT --heading-transform. It carries a player's own preset name now. */
  text-transform: none;
  line-height: 1.2;
  cursor: pointer;
  white-space: nowrap;
  /* ⚠ IT FILLS ITS WRAPPER, WHICH CARRIES THE CAP. v98's max-width: 60% here
     resolved against a width this button itself established and came out at 60% of
     its own text; an absolute cap here left the button overflowing a shrunk wrapper.
     The wrapper is the flex item, so the wrapper is where a width belongs. */
  width: 100%;
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0.18);
}

/* The name itself truncates; the caret never does. */
.abc2-presets__btn-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.abc2-presets__caret {
  flex-shrink: 0;
}

.abc2-presets__btn:disabled {
  opacity: 0.45;
  cursor: default;
}

@media (hover: hover) {
  .abc2-presets__btn:hover:not(:disabled) {
    background: rgba(0, 0, 0, 0.16);
  }
}

/* 🔴 THE HAMBURGER MENU'S OWN PANEL, copied from .army-menu__dropdown rather
   than described — surface, border, radius, shadow, padding and the flex column. It
   floats over the content, so it leaves the banner's typography behind: font-family
   is reset here, which is exactly what v96 forgot.
   ⚠ 2px BELOW THE HAMBURGER'S OFFSET. ⚠ z-index 1000 for the reason army.css v79
   records: a hovered card's transform opens a stacking context and a smaller value
   loses to it. */
.abc2-presets__menu {
  position: absolute;
  /* ⚠ 2px IS THE WHOLE OFFSET. Read as additive at v97, and the menu read as
     detached from the button on device. */
  top: calc(100% + 2px);
  right: 0;
  z-index: 1000;
  /* 🔴 max-content TAKES IT OUT OF SHRINK-TO-FIT. Absolutely positioned inside
     .abc2-presets, its available width is that wrapper's — 220px since v99, or less
     once shrunk — so every row truncated. Now it sizes to its widest row and the cap
     below is its own. ⚠ The -webkit- value first, for Safari versions that want it;
     a browser that understands neither simply falls back to shrink-to-fit. */
  width: -webkit-max-content;
  width: max-content;
  min-width: 168px;
  /* ⚠ MEASURED: `Devourers + Crushing Claws` wants 299px. */
  max-width: 300px;
  padding: var(--space-1);
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  font-family: var(--font-body);
  text-transform: none;
  letter-spacing: normal;
}

/* .army-menu__item's treatment, plus the three slots this menu carries. ⚠ THE
   HAIRLINE IS THE SEPARATOR — one per item, dropped on the last, which is why no
   separate divider element exists any more. */
.abc2-presets__item {
  display: flex;
  align-items: center;
  /* Eye, then name, then mark — hugging the right, as a hamburger item's text does. */
  justify-content: flex-end;
  gap: var(--space-2);
  width: 100%;
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  text-align: right;
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
  white-space: nowrap;
  min-height: unset;
  /* 🔴 DESCENDERS (v109). .abc2-presets__name carries overflow: hidden for its
     ellipsis, and overflow clips to the CONTENT BOX, whose height is the line
     height. base.css sets button { line-height: 1 }, so at --text-sm that box was
     13px while Inter's own em box is 16px — the tails of g, j, p, q and y sat 1.5px
     below it and were cut off. ⚠ .army-menu__item, which this rule is otherwise a
     copy of, has NO inner span and therefore no clip, which is why the hamburger
     menu never showed this and why the fault arrived with the ellipsis, not with
     the menu.
     ⚠ 1.4, NOT THE MINIMUM. The em box needs 1.23 and 1.25 clears it by 0.13px,
     which is not a margin — a bolder weight, a fallback face or a device's
     rounding eats it. 1.4 clears by 1.09px, measured with Inter actually loaded
     across the regular, bold (--on) and amber (--illegal) rows. Rows go 29px ->
     34px, which also makes them a better touch target.
     ⚠ IT BELONGS ON THE ROW, NOT THE SPAN, so all three slots — mark, eye, name —
     share one line box and none can drift. It beats base.css on specificity:
     (0,1,0) against a bare element selector. */
  line-height: 1.4;
  transition: background var(--transition-fast), color var(--transition-fast);
  /* iOS paints this where it ignores :active — see the v96 note. */
  -webkit-tap-highlight-color: var(--amber-light);
}

.abc2-presets__item:first-child {
  border-top-left-radius: var(--radius);
  border-top-right-radius: var(--radius);
}

.abc2-presets__item:last-child {
  border-bottom: none;
  border-bottom-left-radius: var(--radius);
  border-bottom-right-radius: var(--radius);
}

/* ⚠ GATED, WHERE THE HAMBURGER'S IS NOT — see the v97 note. */
@media (hover: hover) {
  .abc2-presets__item:hover:not(:disabled) {
    background: var(--surface-lo);
  }
}

.abc2-presets__item:active:not(:disabled) {
  background: var(--surface-lo);
}

/* ⚠ Later in the cascade than base.css's button:disabled, which it ties with. */
.abc2-presets__item:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ⚠ ITALIC AS WELL AS GREY, ON THE ACTION ROW ONLY (v104). Grey alone can read as a
   rendering accident; italic is unmistakably a state. 🔴 Scoped by class rather than
   by position — see the v104 note — and deliberately NOT applied to a ticked
   `Default Preset`, which is disabled because you are already on it rather than
   because something is being withheld. */
.abc2-presets__item--action:disabled {
  font-style: italic;
}

.abc2-presets__item:disabled:hover {
  background: transparent;
}


.abc2-presets__item--on {
  font-weight: 700;
  color: var(--text-hi);
}

/* ⚠ The player's own text: no uppercasing, and it truncates rather than widening
   the menu. flex: 0 1 auto, so the row stays hugged to the right instead of the name
   stretching across it. */
.abc2-presets__name {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 🔴 IT LEADS THE ROW (v101), so the NAME is the rightmost thing and the right edge
   comes out straight. ⚠ The fixed width no longer aligns names — being last does
   that — it keeps ✓ and ! occupying the same slot as each other. datasheet.js emits
   this only where there IS a mark, so an unmarked row carries no dead space at its
   head and the max-content panel is not widened for nothing. */
.abc2-presets__mark {
  flex-shrink: 0;
  width: 12px;
  text-align: center;
  font-weight: 800;
}

/* 🔴 THE BREAK BETWEEN THE TWO GROUPS — the actions above, the loadouts below.
   A heavier rule on the first row of the second group, because every row already
   has a hairline and one more element would just look like another row. */
.abc2-presets__item--group {
  /* 🔴 -1px PULLS THE 2px RULE OVER THE 1px HAIRLINE IT REPLACES. Without it the
     two stack and the break reads as a doubled line. */
  margin-top: -1px;
  border-top: 2px solid var(--border-hi);
}

/* ⚠ NOTHING ABOVE IT MEANS NOTHING TO BREAK FROM. As the first row the rule drew
   against the menu's own padding and read as a stray line. */
.abc2-presets__item--group:first-child {
  margin-top: 0;
  border-top: none;
}

/* ── The name dialog's two fields ─────────────────────────
   🔴 THE FIELD ITSELF IS .ap-modal__input — EditNameModal's own, already proven on
   this device for height, focus ring and font size. Nothing about it is redeclared
   here; this class carries the autofill suppression and nothing else.
   ⚠ IT HIDES THE BUTTON IN THE FIELD, NOT THE SUGGESTION BAR above the keyboard.
   Apple documents no opt-out — see the v97 note and army-editor.js v71. */
.abc2-preset-input::-webkit-contacts-auto-fill-button {
  visibility: hidden;
  display: none !important;
  pointer-events: none;
  height: 0;
  width: 0;
  margin: 0;
}

.abc2-preset-input::-webkit-credentials-auto-fill-button {
  visibility: hidden;
  display: none !important;
  pointer-events: none;
  height: 0;
  width: 0;
  margin: 0;
}

/* ⚠ THE BOX AND THE SENTENCE ARE ONE TARGET, and the sentence wraps beside the
   box rather than under it — align-items: flex-start, not center. */
.abc2-preset-check {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  cursor: pointer;
}

.abc2-preset-check input {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  margin: 1px 0 0;
}

/* ── The Save dialog's mode toggle (v105) ──────────────────────
   ⚠ CENTRED BY align-self, because .ap-modal__body is a flex COLUMN — a margin:auto
   would centre nothing there. It sizes to its two segments rather than filling the
   width: it is a choice between two things, not a field. */
.abc2-preset-toggle {
  align-self: center;
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.abc2-preset-toggle__seg {
  padding: var(--space-2) var(--space-4);
  background: transparent;
  border: none;
  border-radius: 0;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  /* ⚠ The register is presentation; the words are the markup's. */
  text-transform: uppercase;
  color: var(--text-dim);
  cursor: pointer;
  white-space: nowrap;
  min-height: var(--touch-target);
  transition: background var(--transition-fast), color var(--transition-fast);
  -webkit-tap-highlight-color: var(--amber-light);
}

/* The divider is one border between the two, not one each. */
.abc2-preset-toggle__seg + .abc2-preset-toggle__seg {
  border-left: 1px solid var(--border);
}

/* ⚠ THE AMBER .abc2-size--on ALREADY USES, so a chosen segment reads like every
   other chosen thing here. */
.abc2-preset-toggle__seg--active {
  background: var(--amber-light);
  color: var(--text-hi);
}

@media (hover: hover) {
  .abc2-preset-toggle__seg:hover:not(:disabled):not(.abc2-preset-toggle__seg--active) {
    color: var(--text);
  }
}

/* Nothing to replace. ⚠ Later in the cascade than base.css's button:disabled, which
   it ties with at (0,1,1). */
.abc2-preset-toggle__seg:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* ── The delete confirm's two registers (v105) ────────────────
   🔴 WHAT CANNOT BE UNDONE, and beside it WHAT IS NOT AFFECTED. The second is the
   one that stops a player wondering whether deleting a preset also strips the unit,
   so it is present but quieter — an aside, not a second warning. */
.abc2-preset-danger {
  color: var(--red);
  font-weight: 800;
}

.abc2-preset-aside {
  color: var(--text-dim);
  font-style: italic;
}

/* ⚠ THE TARGET PICKER IS A NATIVE <select> WEARING .ap-modal__input. iOS gives it
   the system picker for free — no popover, no click-away, no z-index, and nothing
   for a nested menu to get wrong on a touch device. */
.abc2-preset-select {
  cursor: pointer;
}

/* 🔴 THE CONSEQUENCE LINE. Quieter than the body text around it, because it
   explains rather than asks — but it is the reason the target is chosen in a dialog
   rather than in a submenu, where three words would have had to carry it. */
.abc2-preset-note {
  font-size: var(--text-sm);
  color: var(--text-dim);
}

/* ⚠ BOTTOM LEFT, WITHOUT TOUCHING THE SHARED FOOTER. .ap-modal__footer is
   justify-content: flex-end for every dialog in the app; margin-right: auto moves
   this one button and nothing else. */
.abc2-preset-delete {
  margin-right: auto;
}

/* ⚠ .abc2-preset-error WENT AT v97. The dialog shows the server's sentence in
   .ap-modal__error now — army.css's own block, which every other dialog uses. A rule
   nobody emits is the dead surface SPECS 10.7 rules against. */


/* 🔴 align-self, NOT display: inline-block. The rule already said inline-block
   and it never did anything: .army-card__body is a flex COLUMN, so this strip is
   a flex item and stretches to the cross axis whatever its display value is.
   inline-block is only read on a child of a block. This is what actually shrinks
   it to its text.
   ⚠ THE ORIGINAL DECLARATIONS ARE LEFT ALONE — this rule only adds. Two emitters
   use .apk-review (units needing review, and stale wargear) and both want the
   same shape. */
.apk-review {
  align-self: flex-start;
  font-style: italic;
}

/* The army card's mark, inside the h2 so it follows a name that wraps. Same
   treatment as the unit card's .abs-unit__flag — see that rule for the optical
   -1px. */
.apk-flag {
  display: inline-flex;
  align-items: center;
  margin-left: var(--space-2);
  color: var(--amber-dim);
  vertical-align: -1px;
}

/* ── The unit card's needs-review mark (v112) ──────────────────
   ⚠ AFTER THE NAME AND THE #N, inside .ub-card__name, so it follows a name that
   wraps instead of being stranded on the first line. A left margin rather than a
   gap, because its parent is not a flex container.
   ⚠ vertical-align: -1px OPTICAL, NOT baseline. A triangle's visual centre sits
   above its box centre — its mass is at the bottom — so baseline alignment reads
   as floating high beside uppercase text. */
.abs-unit__flag {
  display: inline-flex;
  align-items: center;
  margin-left: var(--space-2);
  color: var(--amber-dim);
  vertical-align: -1px;
}


/* ── The banner's `Preset:` label (v107) ───────────────────────
   ⚠ IT CARRIES THE AUTO MARGIN, which .abc2-presets used to. Nothing else about
   the menu changed: the prefix is its SIBLING, so .abc2-presets keeps being the
   menu's positioning context and keeps the 220px cap to itself. The cap is on the
   button's width, not on a label-plus-button pair, which is what it was tuned for.
   ⚠ NO FONT DECLARATIONS. .ds-banner already sets the family, size, weight,
   tracking and uppercase for everything in it — the heading takes them the same
   way. Restating them here would be a second copy of a shared look, which is the
   exact fault source-check.css v18 was just cleaned up for.
   🔴 flex-shrink: 0, SO IT NEVER TRUNCATES. Two words with no natural break: half
   of `PRESET:` is worse than a narrower button, and the button already shrinks and
   ellipsises on purpose. */
.abc2-presets__prefix {
  margin-left: auto;
  flex-shrink: 0;
  /* 🔴 --text-xs, THE BUTTON'S OWN SIZE (v108). Inherited from .ds-banner it was
     --text-base, 15px against the button's 11px — a label four pixels LARGER than
     the control it labels, and level with WARGEAR OPTIONS, so it read as a second
     heading competing with the first. Matching the button puts the hierarchy back:
     heading 15px, then the control and its label together at 11px.
     ⚠ --text-xs IS THE FLOOR. There is no smaller token, so the label does not go
     under the button — it sits level with it and recedes on weight of colour
     instead, which is what the opacity below is for.
     ⚠ Only the SIZE is restated. Family, weight, tracking and the uppercase still
     come from .ds-banner, as the heading's do. */
  font-size: var(--text-xs);
  /* Quieter than WARGEAR OPTIONS — it labels a control, it is not a second
     heading. Opacity rather than a colour, because the banner's foreground is
     --ds-faction-color-text and varies by faction. */
  opacity: 0.75;
}

/* ── Edit Preset's three-button footer (v106) ──────────────────
   🔴 THREE BUTTONS DO NOT FIT IN 372px AT THE SHIPPED PADDING, and this is measured
   against the real variables.css with Big Shoulders Display and Inter actually
   loaded — not estimated. Delete Preset 140 + Cancel 92 + Save Changes 144, plus two
   --space-3 gaps, is 400px. .ap-modal is max-width: 420px with NO media query
   widening it, and .ap-modal__footer spends 2 × --space-6 on padding, leaving 372.
   The 28px shortfall was absorbed by squeezing the two end buttons, and since
   .ap-modal__cancel and .ap-modal__confirm both set a fixed `height: 44px`, the
   wrapped second line spilled out of the button instead of growing it.
   ⚠ THE CLASS IS ON THE FOOTER, PUT THERE BY army-editor.js v76 ONLY WHEN THE DELETE
   BUTTON RENDERS. .ap-modal__footer is shared by every dialog in the app — Delete
   Army, the import report, MaxPointsModal, ChoiceModal — and none of them has this
   problem. Save Preset needs 234px of its 372 and the delete confirm 195, so a rule
   that reached them would be trimming padding for no reason.
   ⚠ TRIMMED, NOT RELABELLED. `Delete Preset` is Jérôme's own wording; shortening it
   is his ruling to make, and padding is not. After the trim the row needs 344px and
   has 372 — 28px of slack, and the labels are fixed strings, so nothing at runtime
   can eat it. */
.abc2-preset-footer--triple {
  gap: var(--space-2);
}

.abc2-preset-footer--triple .ap-modal__cancel {
  padding: 0 var(--space-3);
}

.abc2-preset-footer--triple .ap-modal__confirm {
  padding: 0 var(--space-4);
}


/* ============================================================
   OUTDATED — DevAgent60 v116 (DEV60-6)
   A unit whose army is pinned to a version other than the current one AND
   whose datasheet rows differ between the two. Read-only; the key is absent
   on every other unit, so nothing here renders for an army on current.
   ============================================================ */

/* The card badge — the shared badge shape, in red (v117). */
.abs-unit__outdated {
  padding: 1px var(--space-2);
  border-radius: var(--radius-sm);
  border: 1px solid var(--red);
  background: var(--red);
  color: var(--text-inv);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 600;
}

/* Room for the tag on both sides, so the title stays centred and wraps short
   of it. ⚠ Scoped to .abs__columns like every other header rule here. */
.abs__columns .ab-col__header.abs-col-header--outdated {
  padding-left: 104px;
  padding-right: 104px;
}

/* The header tag. .abs__columns .ab-col__header is already position: relative
   (the Edit toggle's note), which is what anchors this. */
.abs-outdated-tag {
  position: absolute;
  right: var(--space-3);
  top: 50%;
  transform: translateY(-50%);
  padding: 3px var(--space-2);
  border-radius: var(--radius-sm);
  /* v117: red, as the card badge — a fill reads on every faction accent. */
  border: 1px solid var(--red);
  background: var(--red);
  color: var(--text-inv);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: var(--text-xs);
  letter-spacing: var(--heading-tracking);
  text-transform: uppercase;
  line-height: 1.2;
  white-space: nowrap;
  /* v134 (DEV70-16): a button that opens the unit's Outdated modal. */
  margin: 0;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

/* v117 (DEV60-12): the older-version look reaches this column's header too.
   --older-hatch is defined on .ds-older in datasheet.css v128.
   ⚠ !important: the accent arrives as an inline `background:` shorthand
   (army-editor.js headerStyle), which resets background-image. */
.abs__columns .ab-col--2.ds-older .ab-col__header {
  background-image: var(--older-hatch) !important;
}

/* ============================================================
   DETACHMENT STEP — READING (v119, army-create.js v9)
   The cards are v118's. Beside each one, a (?) opens the detachment's text
   in a modal; tapping the card still selects it. The two never share a
   target: the circle is a sibling of the card button, not inside it.
   ============================================================ */

/* A chapter army: the chapter's own detachments, then the parent's. Each
   group's title is a .create-army__label (army.css). */
.abw-detachment-group + .abw-detachment-group {
  margin-top: var(--space-6);
}

/* Card and circle on one line. The card fills the grid cell's height (the
   grid's equal rows); the circle is centred on it. v123: 6px apart (Jérôme),
   off the spacing scale, so a literal. */
.abw-detachment-row {
  display: flex;
  align-items: stretch;
  gap: 6px;
  min-width: 0;
}

.abw-detachment-row .abw-detachment {
  flex: 1 1 auto;
  min-width: 0;
}

/* The (?): lighter than the card and about two thirds of its height, so it
   reads as an aside. 44px keeps it a full touch target. */
.abw-detachment__help {
  flex: 0 0 auto;
  align-self: center;
  width: var(--touch-target);
  height: var(--touch-target);
  padding: 0;
  border-radius: 50%;
  border: 2px solid var(--border);
  background: var(--surface);
  color: var(--text-dim);
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: var(--text-lg);
  line-height: 1;
  cursor: pointer;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}

@media (hover: hover) {
  .abw-detachment__help:hover {
    border-color: var(--border-hi);
    color: var(--text);
  }
}

/* A stored detachment no longer offered has no text to read; the space is
   kept so its card lines up with the others. */
.abw-detachment__help--none {
  visibility: hidden;
  cursor: default;
}

/* v124: the army the step is for, under its title. */
.create-army__step-title.abw-step-title--with-sub {
  margin-bottom: var(--space-2);
}

.abw-subtitle {
  margin-bottom: var(--space-6);
  font-size: var(--text-md);
  color: var(--text);
}

.abw-subtitle__army {
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* v124: the Detachment Points count in the footer, beside Next. The .abw-dp
   box, without its bottom margin, centred in the row. */
.create-army__footer-actions .abw-dp--footer {
  align-self: center;
  margin: 0 var(--space-2) 0 0;
  padding: var(--space-2) var(--space-4);
  white-space: nowrap;
}

/* ============================================================
   DETACHMENT TEXT MODAL (v119)
   The house .ap-modal, wider, in its own class (v120: 560px, and 200px
   shorter than the viewport). Header: the name and a close
   [×]. The body scrolls (.ap-modal__body already does); the footer holds the
   DP and Select / Deselect.
   ============================================================ */

.ap-modal.dtx-modal {
  max-width: 560px;
  width: 94vw;
  max-height: calc(100dvh - 200px);
}

.dtx-modal__header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-right: var(--space-3);
}

.dtx-modal__title {
  flex: 1 1 auto;
  min-width: 0;
}

.dtx-modal__close {
  flex: 0 0 auto;
  width: var(--touch-target);
  height: var(--touch-target);
  padding: 0;
  border: none;
  background: transparent;
  color: var(--banner-text);
  font-family: var(--font-body);
  font-size: var(--text-xl);
  line-height: 1;
  cursor: pointer;
}

/* v128: the name a size up from the header's own; the force disposition after
   it at --text-md. Both inherit the header's face, weight and capitals. */
.dtx-modal__name {
  font-size: var(--text-xl);
}

.dtx-modal__fd {
  font-size: var(--text-md);
}

.dtx-modal__footer {
  justify-content: space-between;
  align-items: center;
}

.dtx-modal__dp {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: var(--text-md);
  letter-spacing: var(--heading-tracking);
  color: var(--text-dim);
}

/* ============================================================
   DETACHMENT TEXT PANEL (v119, detachment-text.js v1)
   Shared: any host draws .dtx. Upstream's text, verbatim, in the book's
   blocks — rules, enhancements, stratagems.
   ============================================================ */

.dtx {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  font-size: var(--text-base);
  line-height: 1.5;
  color: var(--text);
}

.dtx-loading,
.dtx-empty {
  font-size: var(--text-sm);
  color: var(--text-dim);
}

.dtx-error {
  font-size: var(--text-sm);
  color: var(--red);
  padding: var(--space-2) var(--space-3);
  background: var(--red-light);
  border-radius: var(--radius);
  border: 1px solid var(--red);
}

.dtx-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.dtx-section__title {
  margin: 0;
  padding-bottom: var(--space-1);
  border-bottom: 2px solid var(--banner);
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  font-weight: var(--heading-weight);
  letter-spacing: var(--heading-tracking);
  text-transform: var(--heading-transform);
  color: var(--text-hi);
}

/* base.css removes list markers everywhere; upstream's lists keep theirs. */
.dtx ul { list-style: disc;    margin: var(--space-1) 0; padding-left: var(--space-6); }
.dtx ol { list-style: decimal; margin: var(--space-1) 0; padding-left: var(--space-6); }

/* Two orks.json rules carry a table. */
.dtx table {
  border-collapse: collapse;
  margin: var(--space-2) 0;
  font-size: var(--text-sm);
}

.dtx td,
.dtx th {
  border: 1px solid var(--border);
  padding: var(--space-1) var(--space-2);
  text-align: left;
  vertical-align: top;
}

/* ── Rules ──────────────────────────────────────────────── */

.dtx-rule {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.dtx-rule__name {
  margin: 0;
  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);
  color: var(--text-hi);
}

.dtx-rule__heading {
  margin: var(--space-1) 0 0 0;
  font-family: var(--font-heading);
  font-size: var(--text-base);
  font-weight: 800;
  letter-spacing: var(--heading-tracking);
  text-transform: uppercase;
  color: var(--text-hi);
}

.dtx-rule__subtitle {
  font-size: var(--text-sm);
  font-style: italic;
  color: var(--text-dim);
}

.dtx-rule__p--bold {
  font-weight: 700;
}

.dtx-rule__p--boxed {
  padding: var(--space-2) var(--space-3);
  background: var(--surface-lo);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.dtx-rule__accordion,
.dtx-rule__row {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* ── Enhancements ───────────────────────────────────────── */

.dtx-enh {
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

.dtx-enh__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}

.dtx-enh__name {
  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);
  color: var(--text-hi);
}

.dtx-enh__cost {
  flex: 0 0 auto;
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: var(--text-md);
  color: var(--text-hi);
  white-space: nowrap;
}

.dtx-enh__kw {
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  color: var(--text-dim);
}

.dtx-enh__kw-label {
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.dtx-enh__desc {
  margin-top: var(--space-2);
}

.dtx-badge {
  display: inline-block;
  vertical-align: middle;
  padding: 1px var(--space-2);
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1.4;
}

.dtx-badge--upgrade {
  background: var(--surface-lo);
  border: 1px solid var(--border-hi);
  color: var(--text);
}

/* ── Stratagems ─────────────────────────────────────────── */

/* The turn colour, set per block. Blue is the theme's astartes token, used
   here for its colour only. */
.dtx-strat            { --dtx-turn: var(--text-dim); }
.dtx-strat--either    { --dtx-turn: var(--green); }
.dtx-strat--your      { --dtx-turn: var(--astartes); }
.dtx-strat--opponents { --dtx-turn: var(--red); }

.dtx-strat {
  border: 1px solid var(--dtx-turn);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}

.dtx-strat__band {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-1) var(--space-4);
  background: var(--dtx-turn);
  color: var(--text-inv);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.dtx-strat__phases {
  font-weight: 800;
  text-align: right;
}

.dtx-strat__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4) 0 var(--space-4);
}

.dtx-strat__name {
  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);
  color: var(--text-hi);
}

.dtx-strat__cp {
  flex: 0 0 auto;
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: var(--text-md);
  color: var(--dtx-turn);
  white-space: nowrap;
}

.dtx-strat__type {
  align-self: flex-start;
  margin: var(--space-1) var(--space-4) 0 var(--space-4);
  display: inline-block;
  padding: 1px var(--space-2);
  border-radius: var(--radius-sm);
  border: 1px solid var(--dtx-turn);
  color: var(--dtx-turn);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.dtx-strat__fluff {
  padding: var(--space-2) var(--space-4) 0 var(--space-4);
  font-style: italic;
  color: var(--text-dim);
}

.dtx-strat__field {
  padding: var(--space-2) var(--space-4) 0 var(--space-4);
}

.dtx-strat__label {
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-hi);
}

/* v126: an alternative effect that costs extra CP — under a dotted rule, its
   cost at the right in the turn colour, as the printed card draws it. */
.dtx-strat__alt {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  margin: var(--space-2) var(--space-4) 0 var(--space-4);
  padding-top: var(--space-2);
  border-top: 2px dotted var(--border-hi);
}

.dtx-strat__alt-text {
  flex: 1 1 auto;
  min-width: 0;
}

.dtx-strat__alt-cp {
  flex: 0 0 auto;
  padding: 1px var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--dtx-turn);
  color: var(--text-inv);
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: var(--text-md);
  white-space: nowrap;
}

/* v126: a bare <li> with no list around it, indented as a list item would be. */
.dtx-strat__more > li {
  margin-left: var(--space-5);
  list-style: disc;
}

/* The book's unlabelled italic note under the effect. */
.dtx-strat__note {
  padding: var(--space-2) var(--space-4) 0 var(--space-4);
  font-style: italic;
}

/* Every block's last line clears the border by the same amount. */
.dtx-strat > :last-child {
  padding-bottom: var(--space-3);
}


/* ============================================================
   TAP-TO-READ POPOVER (v129, DevAgent65)
   army-editor-popover.js renders this into <body>: a transparent backdrop
   that takes the first tap outside, and a 280px panel placed from the
   tapped element's rect (inline left/top or left/bottom).
   ============================================================ */

.ae-pop-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1200;
  background: transparent;
}

.ae-pop {
  position: fixed;
  width: 280px;
  max-width: calc(100vw - 16px);
  max-height: min(420px, calc(100vh - 32px));
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--surface);
  border: var(--border-width) solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}

/* Faction colours arrive inline (headerStyle); this is the fallback. */
.ae-pop__header {
  flex: 0 0 auto;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: var(--space-2) 40px;
  background: var(--surface-lo);
  color: var(--text);
}

.ae-pop__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);
  text-align: center;
  line-height: 1.2;
}

/* 44px square: a fingertip, not a cursor. */
.ae-pop__close {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  background: none;
  border: none;
  color: inherit;
  font-size: var(--text-xl);
  line-height: 1;
  cursor: pointer;
  opacity: 0.8;
}

.ae-pop__close:hover { opacity: 1; }

.ae-pop__body {
  overflow-y: auto;
  padding: var(--space-3);
  font-size: var(--text-sm);
  line-height: 1.4;
  color: var(--text);
}

.ae-pop__line { margin: 0; }
.ae-pop__line + .ae-pop__line { margin-top: var(--space-1); color: var(--text-dim); }
.ae-pop__line--lead { font-weight: 600; }

.ae-pop__issues {
  margin: 0;
  padding-left: 1.1em;
}

.ae-pop__issues li + li { margin-top: var(--space-1); }

/* Same amber language as the Add arrow; full width, finger-sized. */
.ae-pop__action {
  display: block;
  width: 100%;
  min-height: 44px;
  margin-top: var(--space-3);
  background: var(--surface);
  color: var(--amber);
  border: 2px solid var(--amber);
  border-radius: var(--radius-lg);
  font-family: var(--font-heading);
  font-size: var(--text-sm);
  letter-spacing: var(--heading-tracking);
  text-transform: uppercase;
  cursor: pointer;
}

.ae-pop__action:active:not(:disabled) { background: var(--amber-light); }
.ae-pop__action:disabled { opacity: 0.5; cursor: default; }

/* v129: blocked is aria-disabled now, not :disabled — keep v128's grey. */
.abc1-add--blocked:hover,
.abc1-add--blocked:active {
  background: var(--surface-lo);
  transform: none;
}


/* ============================================================
   ISSUE MODALS (v130, DevAgent65) — army-editor.js v86
   ============================================================ */

.ae-issue-list {
  margin: var(--space-2) 0;
  padding-left: 1.2em;
  line-height: 1.4;
}

.ae-issue-list li + li { margin-top: var(--space-1); }

/* Stacked, full width: the action first, Dismiss last. Scoped by this class, so
   every other .ap-modal__footer keeps its row. */
.ae-issue-footer {
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-2);
}

.ae-issue-footer .ap-modal__cancel,
.ae-issue-footer .ap-modal__confirm {
  width: 100%;
  margin: 0;
}

/* ============================================================
   WARGEAR OPTIONS — A CHOICE AN UPDATE UNDID (v130) — datasheet.js v93
   ============================================================ */

/* v132: the bare triangle in the banner — the army card flag's treatment. */
.abc2-stale-flag {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  color: var(--amber);
}


/* ============================================================
   OUTDATED WARGEAR (v131) — datasheet.js v94
   The regular .abc2-item row, red instead of white/amber. Read-only: no
   steppers, so the count stack sits alone on the right.
   ============================================================ */

.abc2-item--outdated {
  border-color: var(--red);
  background: var(--red-light);
}

/* Under the pill, on its own line — .abc2-item__name already wraps. */
.abc2-item__outdated-note {
  flex: 1 0 100%;
  font-size: var(--text-xs);
  color: var(--red);
  line-height: 1.3;
}

/* v132: the × on an outdated row — a stepper-sized button, in the row's red. */
.abc2-outdated-remove {
  margin-left: var(--space-2);
  color: var(--red);
  border-color: var(--red);
}

/* v133: the triangle body filled white; outline and '!' keep the amber stroke. */
.abc2-stale-flag svg path:first-child {
  fill: #fff;
}


/* ============================================================
   OUTDATED PARTS (v134, DevAgent70) — v22 stage S7
   ============================================================ */

/* The banner's warning sign and label (army-editor.js v88). A button in the
   facts row; the tag's red fill, so it reads on any faction accent. */
.abs__outdated {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin: 0;
  padding: 1px var(--space-2);
  border: 1px solid var(--red);
  border-radius: var(--radius-sm);
  background: var(--red);
  color: var(--text-inv);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: var(--text-xs);
  letter-spacing: var(--heading-tracking);
  text-transform: uppercase;
  line-height: 1.4;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

/* The armies home card's strip (army-picker.js v34): .apk-review, in red. */
.apk-review--outdated {
  background: var(--red-light);
  border-color: var(--red);
  color: var(--red);
  font-style: normal;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* A section of the load or unit modal, when it holds more than one. */
.ae-review-section + .ae-review-section {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}

.ae-review-section__title {
  font-family: var(--font-heading);
  font-weight: var(--heading-weight);
  font-size: var(--text-sm);
  letter-spacing: var(--heading-tracking);
  text-transform: uppercase;
  color: var(--text-hi);
  margin-bottom: var(--space-2);
}

/* One Outdated part: its name, its reasons, its Update. */
.ae-outdated-part {
  padding: var(--space-2) 0;
  border-bottom: 1px dashed var(--border);
}

.ae-outdated-part:last-child { border-bottom: none; }

.ae-outdated-part__name { margin-bottom: var(--space-1); }

/* The inline confirmation of an Update that clears something (DEV70-17). */
.ae-outdated-part__confirm {
  margin-top: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--amber);
  border-radius: var(--radius-sm);
  background: var(--amber-light);
}

.ae-outdated-part__error {
  margin-top: var(--space-2);
  font-size: var(--text-xs);
  color: var(--red);
}

/* Section and row buttons: stacked, full width, as the modal footers are. */
.ae-review-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.ae-review-actions .ap-modal__cancel,
.ae-review-actions .ap-modal__confirm {
  width: 100%;
  margin: 0;
}

/* v140 (DEV70-28): a row's own buttons — EXACTLY the modal footer's: right-
   aligned, natural width, var(--space-3) apart, each button its plain
   .ap-modal__cancel / .ap-modal__confirm (army.css), as in Edit Name. No size,
   font or width override. (v135–v139 restyled them; withdrawn.) */
.ae-row-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-3);
  margin-top: var(--space-2);
}

/* v142 (DEV70-30): Dismiss a fixed 100px, the action button filling the rest
   of the row — the Edit Name styling otherwise untouched. (v141 split the
   row equally; withdrawn.) */
.ae-row-actions > .ap-modal__cancel {
  flex: 0 0 100px;
  min-width: 0;
  padding: 0 var(--space-2);
}

.ae-row-actions > .ap-modal__confirm {
  flex: 1 1 0;
  min-width: 0;
}

/* v136 (DEV70-23): an Outdated part's reasons sit flush with the intro above
   them — no list indent. Scoped to the Outdated rows; every other issue list
   keeps its indent. */
.ae-outdated-part .ae-issue-list {
  padding-left: 0;
}


/* v143 (DEV70-31): the name and the army's OUTDATED label on one line. The
   name keeps .abs__name's ellipsis; the label keeps its size. */
.abs__name-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

.abs__name-row .abs__name {
  min-width: 0;
}

.abs__name-row .abs__outdated {
  flex: 0 0 auto;
}

/* v143 (DEV70-31): the ⚠ after an Outdated detachment's name — the bare
   triangle, in the banner's ink, with room around it for a finger. */
.abs__det-flag {
  display: inline-flex;
  align-items: center;
  vertical-align: -2px;
  margin: 0 0 0 4px;
  padding: 2px;
  border: none;
  background: transparent;
  color: inherit;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

/* v144 (DEV70-32): in Edit Army Details, a selected detachment the army holds
   on an older dataslate is RED instead of green — border, fill, name and
   checkmark (army-create.js v18). */
.create-army .format-option--selected.format-option--outdated {
  border-color: var(--red);
  background: var(--red-light);
}

.create-army .format-option--selected.format-option--outdated .format-option__name,
.create-army .format-option--selected.format-option--outdated::after {
  color: var(--red);
}


/* v146 (DEV71-13 to DEV71-16): POINTS UPDATE, after the Munitorum Field
   Manual. The body scrolls (.ap-modal__body); the footer stays. */
.apu-modal {
  max-width: 480px;
  width: 94vw;
}

.apu-cards {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: var(--space-3) 0;
}

.apu-card__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: var(--text-md);
  line-height: 1.2;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text-inv);
  background: var(--text-dim);
}
.apu-card--up   .apu-card__bar { background: var(--red); }
.apu-card--down .apu-card__bar { background: var(--green); }

.apu-card__name { min-width: 0; overflow-wrap: anywhere; }
.apu-card__arrow { flex-shrink: 0; font-size: 0.8em; }

.apu-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-3) 0;
  color: var(--text);
}
.apu-row__label { flex-shrink: 1; min-width: 0; }
.apu-row__dots {
  flex: 1 1 var(--space-4);
  min-width: var(--space-4);
  border-bottom: 1px dotted var(--text-dim);
  transform: translateY(-0.25em);
}
.apu-row__cost { flex-shrink: 0; white-space: nowrap; }
.apu-row__cost--up   { color: var(--red); }
.apu-row__cost--down { color: var(--green); }

.apu-total {
  margin: 0;
  font-weight: 700;
  color: var(--text-hi);
}
