/**
 * Build & Training Menu - War 2050
 * Slide-up panel above the action bar.
 * z-index: --z-panel (400) - above the HUD (--z-hud, 100), below the
 * tutorial overlay (--z-tutorial-chrome, 1500). See docs/HUD-LAYOUT.md §2.
 *
 * TYPE (docs/TYPE-LEGIBILITY.md): nothing here renders below 0.75rem/12px,
 * and no text uses --color-text-muted (#64748b measures 4.03:1 on this
 * panel's background - it fails AA body text everywhere in the HUD).
 * Where 12px cost room it was taken out of letter-spacing, which is the
 * cheapest thing on a caps label to give up: tracking removes word
 * grouping, so a tracked 8px label is the worst of both worlds (§5).
 */

.build-menu {
    position: fixed;
    /* Region D's shared geometry - see variables.css and the identical
       block in hudPanels.css. The old 92px sat inside the action bar's
       98px footprint. */
    bottom: var(--region-d-bottom);
    left: 50%;
    transform: translateX(-50%);
    width: min(560px, calc(100vw - 32px));
    max-height: min(520px, var(--region-d-max-height));
    display: flex;
    flex-direction: column;
    min-height: 0;
    /* OPAQUE, NOT NEARLY OPAQUE.
       rgba(...,0.96) is too little transmission to read as glass and too much
       to read as solid, so it read as a rendering fault: the Overseer's card
       sits directly behind these panels, and at 4% his portrait, his current
       line and the word EXPENDABLE were all legible as ghost text THROUGH the
       panel's own content. Two layers of type in the same pixels.

       Solid rather than a blurred material, deliberately. The HUD chrome
       already blurs over the world, and stacking a second glass layer on top
       of a first is the thing every platform's material guidance warns against
       — a panel over the HUD is the content layer, not another pane. */
    background: var(--color-primary-900);
    border: 1px solid var(--color-primary-700, #1a2744);
    border-radius: 8px;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
    z-index: var(--z-panel);
    overflow: hidden;
    animation: bmSlideUp 0.22s ease;
}

/* The laptop-short shrink (§4.5, max-height: 700px) is gone: it was two
   constants approximating "stay clear of region A", and
   --region-d-max-height computes that exactly, at every viewport height,
   from the same tokens the action bar uses. */

.build-menu.hidden {
    display: none;
}

@keyframes bmSlideUp {
    from { opacity: 0; transform: translateX(-50%) translateY(12px); }
    to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}

/* ---------- Header ---------- */

.bm-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 14px;
    border-bottom: 1px solid var(--color-primary-700, #1a2744);
    background: rgba(26, 39, 68, 0.4);
    flex-shrink: 0;
}

.bm-title {
    font-family: var(--font-display, 'Orbitron', sans-serif);
    font-size: var(--text-xs);
    /* 2px of tracking on 11.5px Orbitron was the panel's widest caps run and
       its least readable. 12px with 1.6px still fills the header. */
    letter-spacing: 1.6px;
    color: var(--color-accent-gold, #d4af37);
}

.bm-close {
    display: flex;
    align-items: center;
    justify-content: center;
    /* 44x44 touch target (WCAG 2.5.5) around the small glyph */
    min-width: 44px;
    min-height: 44px;
    background: none;
    border: none;
    color: var(--color-text-secondary, #94a3b8);
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
    padding: 0 4px;
    transition: color 0.15s ease;
}

.bm-close:hover { color: var(--color-text-primary, #f8fafc); }

/* ---------- Body ---------- */

.bm-body {
    overflow-y: auto;
    padding: 6px;
    /* See .hp-body - lets the sheet scroll instead of outgrowing its cap. */
    flex: 1 1 auto;
    min-height: 0;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
}

.bm-loading,
.bm-empty {
    padding: 28px 16px;
    text-align: center;
    font-family: var(--font-heading, 'Rajdhani', sans-serif);
    color: var(--color-text-secondary, #94a3b8);
    font-size: 0.9rem;
    line-height: 1.6;
}

/* ---------- Rows ---------- */

.bm-row {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    min-height: 44px;
    padding: 10px 12px;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 6px;
    cursor: pointer;
    text-align: left;
    color: var(--color-text-primary, #f8fafc);
    font-family: var(--font-body, 'Exo 2', sans-serif);
    transition: background 0.15s ease, border-color 0.15s ease;
}

.bm-row:hover:not([disabled]) {
    background: rgba(26, 39, 68, 0.6);
    border-color: var(--color-primary-600, #243352);
}

/* Unavailable rows are separated by SURFACE, not by alpha - the same
   argument achievements.css makes for its locked rows, and for the same
   reason. At opacity 0.4 the three things a blocked row exists to say
   ("NO FACILITY", the cost, the name) measured 1.61:1, 2.15:1 and 3.69:1:
   the row went dim exactly where the player needed to read why they could
   not have it. Flat and unlit says the same thing and keeps every figure
   at its own full-strength colour. */
.bm-row[disabled],
.bm-row.bm-disabled {
    background: rgba(10, 15, 26, 0.55);
    border-color: var(--color-primary-700, #1a2744);
    cursor: not-allowed;
}

.bm-row[disabled] .bm-icon,
.bm-row.bm-disabled .bm-icon {
    color: var(--color-text-secondary, #94a3b8);
}

/* The structure the current tutorial objective is asking for */
.bm-row.bm-wanted {
    border-color: var(--color-accent-gold, #d4af37);
    background: rgba(212, 175, 55, 0.08);
    animation: bmWantedPulse 2s ease-in-out infinite;
}

@keyframes bmWantedPulse {
    0%, 100% { box-shadow: 0 0 0 rgba(212, 175, 55, 0); }
    50%      { box-shadow: 0 0 14px rgba(212, 175, 55, 0.35); }
}

.bm-icon {
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    color: var(--color-accent-gold, #d4af37);
}

.bm-info {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1;
    min-width: 0;
}

.bm-name {
    font-family: var(--font-heading, 'Rajdhani', sans-serif);
    font-weight: 600;
    font-size: 0.95rem;
    letter-spacing: 0.5px;
    display: flex;
    align-items: center;
    /* The OBJECTIVE chip beside it is 12px caps now; on a 390px sheet a long
       structure name plus the chip can exceed the row, so the pair wraps
       rather than pushing the chip off the edge. */
    flex-wrap: wrap;
    gap: 4px 8px;
}

.bm-objective {
    font-family: var(--font-display, 'Orbitron', sans-serif);
    font-style: normal;
    font-size: var(--text-xs);
    letter-spacing: 0.8px;
    color: var(--color-primary-900, #0a0f1a);
    background: var(--color-accent-gold, #d4af37);
    padding: 1px 6px;
    border-radius: 3px;
}

.bm-desc {
    font-size: 0.78rem;
    color: var(--color-text-secondary, #94a3b8);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.bm-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 3px;
    font-family: var(--font-mono, monospace);
    font-size: var(--text-xs);
}

.bm-cost {
    color: var(--color-text-secondary, #94a3b8);
}

/* Costs the player cannot currently meet */
.bm-cost.bm-short {
    color: var(--color-accent-red-light, #ef4444);
}

.bm-cost.bm-free {
    color: var(--color-accent-green-light, #10b981);
}

.bm-time {
    color: var(--color-accent-cyan, #06b6d4);
    margin-left: auto;
}

/* Carries the reason a row is refused - "NO FACILITY", "CAN'T AFFORD",
   "BUSY 2m". The longest of those is 11 characters, so tracking comes down
   to 0.6px to buy the size back; at 12px it measures ~95px and the row's
   .bm-info (min-width: 0) absorbs it on a 390px sheet. */
.bm-lock {
    font-family: var(--font-display, 'Orbitron', sans-serif);
    font-size: var(--text-xs);
    letter-spacing: 0.6px;
    color: var(--color-text-secondary, #94a3b8);
    border: 1px solid var(--color-primary-600, #243352);
    border-radius: 3px;
    padding: 2px 6px;
    flex-shrink: 0;
}

/* ---------- Phone: bottom sheet ---------- */
/* docs/HUD-LAYOUT.md §5, Phone: full-width sheet. Changed from the
   original "anchored to bottom: 0 and occlude the action bar by stacking
   order" - see the matching note in hudPanels.css. Occlusion looked fine
   in a desktop emulator and wrong on real hardware: the bar's gold border
   reads straight through the sheet's 96% background, and the bar was
   overflowing the viewport anyway, so half of it was never coverable. The
   sheet now docks above a bar that fits, and neither has to yield. */
@media (max-width: 640px) {
    .build-menu {
        left: 0;
        right: 0;
        bottom: var(--region-d-bottom);
        transform: none;
        width: 100%;
        max-height: var(--region-d-max-height);
        border-radius: 8px 8px 0 0;
        /* The desktop bmSlideUp keyframes bake in translateX(-50%), which
           would drag a full-width, left:0 sheet off-centre; a sheet-only
           animation keeps the same "slide up" feel without that transform. */
        animation: bmSlideUpSheet 0.22s ease;
    }

    .bm-desc { display: none; }
}

@keyframes bmSlideUpSheet {
    from { opacity: 0; transform: translateY(24px); }
    to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
    .build-menu,
    .bm-row.bm-wanted { animation: none; }
}

/* -----------------------------------------------------------------
   Training space

   Units are capped by the housing their buildings provide. Shown above
   the unit list so the player sees the constraint before choosing,
   rather than after the server refuses the order.
   ----------------------------------------------------------------- */

.bm-housing {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin: 2px 4px 8px;
    padding: 7px 10px;
    background: rgba(192, 80, 63, 0.07);
    border: 1px solid rgba(192, 80, 63, 0.3);
    border-radius: 6px;
}

.bm-housing.is-full {
    background: rgba(248, 113, 113, 0.1);
    border-color: rgba(248, 113, 113, 0.55);
}

/* "TRAINING SPACE" was 8px with 1px of tracking - fourteen tracked
   characters at half the body size, and the constraint the whole block
   exists to state. */
.bm-housing-key {
    flex-shrink: 0;
    font-family: var(--font-display);
    font-size: var(--text-xs);
    letter-spacing: 0.6px;
    color: var(--color-accent-red-light, #f87171);
}

.bm-housing-value {
    font-family: var(--font-heading);
    font-weight: var(--font-semibold);
    font-size: 0.9rem;
    color: var(--color-text-primary);
}

.bm-housing-note {
    margin-left: auto;
    font-size: var(--text-xs);
    color: var(--color-text-secondary);
}

/* -----------------------------------------------------------------
   Vehicle bays — the second, separate pool

   Troops and vehicles are housed independently: a base with every bunk
   taken can still build a tank, and building one gives bunks back. The
   two readouts are therefore two boxes, not two figures in one — a
   single box would state that they share, and a player who believes
   they share will read the whole feature as broken.

   The accent carries the same message as the layout. Training space is
   the red of the Barracks; bays take the purple of the Factory, which
   is where vehicles come from and what the Motor Pool block is
   coloured on the base grid.
   ----------------------------------------------------------------- */

.bm-housing--vehicle {
    background: rgba(155, 89, 182, 0.09);
    border-color: rgba(192, 132, 252, 0.35);
}

.bm-housing--vehicle.is-full {
    background: rgba(192, 132, 252, 0.12);
    border-color: rgba(192, 132, 252, 0.6);
}

.bm-housing--vehicle .bm-housing-key {
    color: #d8b4fe;   /* 8.9:1 on the panel fill; the red key's opposite number */
}

/* Opens the garrison panel. This line is where a player finds out they are
   full, so the remedy belongs on it rather than somewhere they have to go
   looking for. Not margin-left:auto — the note already claims that, and two
   auto margins would put this in the middle of the row. */
.bm-housing-manage {
    flex-shrink: 0;
    min-height: 32px;
    padding: 0 10px;
    background: rgba(26, 39, 68, 0.9);
    border: 1px solid var(--color-primary-700);
    border-radius: 4px;
    color: var(--color-text-primary);
    font-family: var(--font-body);
    font-size: var(--text-xs);
    letter-spacing: 0.5px;
    cursor: pointer;
}

.bm-housing-manage:hover { border-color: var(--color-accent-gold); }
.bm-housing-manage:focus-visible {
    outline: 2px solid var(--color-accent-gold);
    outline-offset: 2px;
}

/* The row is tight on a phone; let it wrap rather than squeeze the figures. */
@media (max-width: 640px) {
    .bm-housing { flex-wrap: wrap; }
    .bm-housing-note { margin-left: 0; flex-basis: 100%; order: 3; }
    .bm-housing-manage { margin-left: auto; }
}

@media (max-width: 600px) {
    .bm-housing { flex-wrap: wrap; gap: 4px 8px; }
    .bm-housing-note { margin-left: 0; width: 100%; }
}

/* =================================================================
   ORDER FORM (unit rows)
   A unit row is not a single-tap action like a building row: the player
   chooses how many first. Ordering five used to mean opening this menu
   five times.
   ================================================================= */

.bm-row--unit {
    /* The base .bm-row is a button; this one is a div and needs the same
       cursor treatment inverted — the row itself is not clickable, only the
       controls inside it are. */
    cursor: default;
    flex-wrap: wrap;
}

.bm-order {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin-left: auto;
}

.bm-qty {
    display: flex;
    align-items: stretch;
    border: 1px solid var(--color-primary-600, #243352);
    border-radius: 3px;
    overflow: hidden;
}

.bm-qty-btn {
    /* 34px so it clears the 24px minimum comfortably on a phone without
       making the row taller than the text beside it. */
    min-width: 34px;
    min-height: 34px;
    background: var(--color-primary-700, #1a2744);
    color: var(--color-text-primary, #f8fafc);
    border: 0;
    font-size: 1.05rem;
    line-height: 1;
    cursor: pointer;
}

.bm-qty-btn:hover { background: var(--color-primary-600, #243352); }
.bm-qty-btn:active { background: var(--color-accent-gold-dark, #b8962f); }
.bm-qty-btn:focus-visible {
    outline: 2px solid var(--color-accent-gold-light, #f4d03f);
    outline-offset: -2px;
}

.bm-qty-input {
    width: 3.25rem;
    min-height: 34px;
    text-align: center;
    background: var(--color-primary-800, #111827);
    color: var(--color-text-primary, #f8fafc);
    border: 0;
    border-left: 1px solid var(--color-primary-600, #243352);
    border-right: 1px solid var(--color-primary-600, #243352);
    font-family: var(--font-heading, 'Rajdhani', sans-serif);
    font-size: 1rem;
    font-variant-numeric: tabular-nums;
}

.bm-qty-input:focus-visible {
    outline: 2px solid var(--color-accent-gold-light, #f4d03f);
    outline-offset: -2px;
}

/* The spinners duplicate the +/- buttons and are far too small to hit on a
   phone, so they are removed rather than left as a second, worse control. */
.bm-qty-input::-webkit-outer-spin-button,
.bm-qty-input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}
.bm-qty-input { -moz-appearance: textfield; appearance: textfield; }

.bm-train {
    min-height: 34px;
    padding: 0 0.85rem;
    background: var(--color-accent-gold, #d4af37);
    color: var(--color-primary-900, #0a0f1a);
    border: 0;
    border-radius: 3px;
    font-family: var(--font-heading, 'Rajdhani', sans-serif);
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    font-size: 0.82rem;
    cursor: pointer;
    white-space: nowrap;
}

.bm-train:hover { background: var(--color-accent-gold-light, #f4d03f); }
.bm-train:focus-visible {
    outline: 2px solid var(--color-accent-gold-light, #f4d03f);
    outline-offset: 2px;
}

/* "max 81" was a readout, and reaching it meant pressing + eighty times. It is
   a button now, and styled as an outlined one rather than a filled one so it
   stays clearly secondary to TRAIN beside it — same weight as the stepper's
   own controls, which is what it is. */
.bm-qty-max {
    font-family: var(--font-display, 'Orbitron', sans-serif);
    font-size: var(--text-xs);
    color: var(--color-accent-gold, #d4af37);
    letter-spacing: 0.04em;
    white-space: nowrap;
    background: transparent;
    border: 1px solid var(--color-accent-gold, #d4af37);
    border-radius: 4px;
    /* Tall enough to hit on a phone without growing the row: the stepper
       buttons beside it are the reference, not the text it replaced. */
    padding: 7px 10px;
    cursor: pointer;
}

.bm-qty-max:hover,
.bm-qty-max:focus-visible {
    background: rgba(212, 175, 55, 0.16);
}

.bm-qty-max:active {
    background: rgba(212, 175, 55, 0.28);
}

/* On a phone the order controls get their own line rather than squeezing the
   unit's name and cost into a sliver. */
@media (max-width: 480px) {
    .bm-order {
        width: 100%;
        margin-left: 0;
        justify-content: flex-start;
        padding-top: 0.5rem;
    }
}

/* =================================================================
   CONVERSION LINE (vehicle rows)

   A vehicle is the only thing in the game bought partly with units the
   player already owns, and troops that vanish are not a cost anyone
   forgives an interface for burying. So the conversion gets its own
   line ABOVE the resource costs rather than another chip beside them:
   a player scanning "200 gold" down the cost row must not be able to
   read past "4 x Infantry" without seeing it.

   Everything here is stated in a colour rather than a weight of grey,
   for the same reason the disabled rows are: a shortfall the player has
   to act on is exactly the text that must not be dimmed.
   ================================================================= */

/* Both go empty on a row that has nothing to put in them — a conversion
   prints its resources in the sentence, not on the cost line. An empty
   span is still a flex item, and would spend its parent's gap. */
.bm-convertline:empty,
.bm-costline:empty { display: none; }

.bm-convert {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 3px 6px;
    margin-top: 4px;
    padding: 3px 6px 3px 4px;
    /* The tinted plate is what separates this from the cost line beside
       it at a glance, before any of the words are read. */
    background: rgba(155, 89, 182, 0.1);
    border-left: 2px solid #c084fc;
    border-radius: 0 4px 4px 0;
    font-family: var(--font-mono, monospace);
    font-size: var(--text-xs);
}

/* Names the transaction outright. Two words, because "CONVERTS" alone
   does not say what is being spent and that is the whole warning. */
.bm-convert-tag {
    font-family: var(--font-display, 'Orbitron', sans-serif);
    font-size: var(--text-xs);
    letter-spacing: 0.5px;
    color: var(--color-primary-900, #0a0f1a);
    background: #d8b4fe;
    padding: 1px 5px;
    border-radius: 3px;
    white-space: nowrap;
}

.bm-troop {
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
    color: var(--color-text-primary, #f8fafc);
    font-weight: var(--font-semibold, 600);
    white-space: nowrap;
}

/* The stock check, in the same place the cost is. A player who needs
   four and holds three has to be able to see both numbers at once —
   sending them to the garrison panel to find out is the failure this
   line exists to prevent. */
.bm-troop-have {
    font-weight: 400;
    color: var(--color-text-secondary, #d3dce8);
}

/* Short on troops. Red on the figure AND on the stock count, since
   which of the two is wrong is the question being answered. */
.bm-troop.is-short,
.bm-troop.is-short .bm-troop-have {
    color: var(--color-accent-red-light, #ef4444);
}

.bm-convert-and,
.bm-convert-arrow {
    color: var(--color-text-secondary, #d3dce8);
}

.bm-convert-out {
    color: var(--color-accent-cyan-light, #22d3ee);
    white-space: nowrap;
}

/* --- build limits -------------------------------------------------------
   A capped structure carries its tally at all times ("2 / 5 built"), not only
   once it is exhausted: the number a player needs before they hit the wall is
   the same number they need after it. */

.bm-count {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--color-text-secondary);
    white-space: nowrap;
}

.bm-count-maxed {
    color: var(--color-accent-gold);
    font-weight: var(--font-semibold);
}

/* Exhausted rows are stood down, not dimmed with opacity — that drags the
   tally through the same filter and the tally is the one thing worth reading
   on a row you cannot press (docs/TYPE-LEGIBILITY.md: every state expressed as
   alpha was hiding text at 1.6:1). Flatter surface, muted name, full-strength
   count. */
.bm-row.bm-maxed {
    background: var(--color-primary-800);
    border-color: rgba(212, 175, 55, 0.12);
    cursor: default;
}

.bm-row.bm-maxed .bm-name,
.bm-row.bm-maxed .bm-desc {
    color: var(--color-text-muted);
}

.bm-row.bm-maxed .bm-icon {
    color: var(--color-text-muted);
    opacity: 0.7;
}

.bm-lock-maxed {
    background: rgba(212, 175, 55, 0.18);
    color: var(--color-accent-gold);
    border-color: rgba(212, 175, 55, 0.4);
}

/* --- readability lift, build + research --------------------------------
   These two panels are the densest reading in the game — a list of names,
   descriptions, costs and times, all set at the 12px floor. A floor is a
   minimum, not a target, and at arm's length on a phone 12px of muted grey
   is legible in the WCAG sense and still hard work.

   Lifting the tokens for the panel rather than editing forty rules: every
   size and colour inside already resolves through these, so one block moves
   the lot and nothing can be missed. Contrast goes up with the size, because
   the complaint was both. */
.build-menu,
#research-panel {
    --text-xs: 0.8125rem;   /* 13px, was 12 */
    --text-sm: 0.9375rem;   /* 15px, was 14 */
    --color-text-muted: #b8c4d4;      /* was #8592a5 — 4.7:1 -> 8.9:1 on panel fill */
    --color-text-secondary: #d3dce8;  /* was #94a3b8 — 6.9:1 -> 11.6:1 */
}

/* The refusal that is also a way out: a Field Barracks Tent offered on the
   infantry row that has just said it cannot be trained. Sized and positioned
   exactly like .bm-lock so the row does not reflow when a player who has no
   Barracks at all sees the plain chip instead — but gold and raised, because
   this one is a button and the chip beside it is not. */
.bm-rent {
    font-family: var(--font-display, 'Orbitron', sans-serif);
    font-size: var(--text-xs);
    letter-spacing: 0.6px;
    /* Paper and ink. A bond is a printed instrument, so the button is printed:
       off-white stock, black impression, a hairline rule for the plate edge.
       Among dark panels it reads as a document rather than as another control,
       which is the distinction worth drawing — this one costs real money. */
    color: var(--color-bond-ink, #0a0b0d);
    background: var(--color-bond-paper, #ece7da);
    border: 1px solid var(--color-bond-rule, rgba(10, 11, 13, 0.22));
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.5);
    border-radius: 2px;
    padding: 5px 10px;
    flex-shrink: 0;
    cursor: pointer;
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

/* The price, marked with the same bond glyph the HUD chip carries, so the
   number is traceable to the balance it is spent from. */
.bm-rent-price {
    display: inline-flex;
    align-items: center;
    gap: 3px;
}

.bm-rent-bond {
    width: 13px;
    height: 13px;
    flex-shrink: 0;
}

.bm-rent b {
    font-weight: 700;
}

.bm-rent:hover,
.bm-rent:focus-visible {
    background: var(--color-bond-paper-lit, #f7f4ec);
    border-color: var(--color-bond-ink, #0a0b0d);
}

/* Countdown above offer, right-aligned to the row's edge like the single chip
   it replaces. Stacked rather than side by side because both are ~95px and the
   row has ~200px of tail on a 390px sheet — laid out in a line they push the
   unit's name into an ellipsis, and the name is the one thing on the row that
   cannot be abbreviated. */
.bm-refusal {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 4px;
    flex-shrink: 0;
}

/* War Bond price on a rented row, where a resource cost sits on every other
   one. Paper-coloured, like every other bond figure, and at the same size as
   .bm-costline so the two kinds of row stay on one rhythm. (It was gold, on
   the reasoning that gold was the bond colour — it never was. Gold is the gold
   resource, which is the confusion the bond palette exists to end.) */
.bm-bond {
    font-size: var(--text-xs);
    color: var(--color-bond-paper, #ece7da);
    font-weight: 600;
}

/* "RENTED" — the chip that says this row will not put you into placement mode.
   Outlined rather than filled: it is a label like LVL 4 and MAX beside it, not
   a button. */
.bm-lock-rented {
    color: var(--color-bond-paper, #ece7da);
    border-color: rgba(236, 231, 218, 0.5);
}


/* A rented structure is offered as a row of terms rather than one button, so
   the discount is visible where the choice is made. The group keeps the label
   with its prices; the terms wrap rather than shrink, because an abbreviated
   price is worse than a second row. */
.bm-rent-group {
    display: inline-flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.bm-rent-title {
    font-family: var(--font-display, 'Orbitron', sans-serif);
    font-size: var(--text-xs);
    letter-spacing: 0.6px;
    color: var(--color-accent-bond, #ece7da);
}

.bm-rent-terms {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

/* The term itself, above its price, so the eye reads 4W / 1400 as one thing.
   A lighter ink rather than opacity — TYPE-LEGIBILITY rule 2, and a faded
   impression is what a worn note looks like, not a deliberate hierarchy. */
.bm-rent-term {
    font-weight: 700;
    color: var(--color-bond-ink-soft, rgba(10, 11, 13, 0.62));
}

/* What the longer commitment saves. The reason the tier exists, so it is not
   hidden in a tooltip. Printed in reverse — ink block, paper letters — the way
   a cancellation stamp sits on a certificate. */
.bm-rent-save {
    font-size: 0.85em;
    font-weight: 700;
    padding: 1px 5px;
    border-radius: 2px;
    color: var(--color-bond-paper, #ece7da);
    background: var(--color-bond-ink, #0a0b0d);
}

/* Priced beyond the player's balance. Still shown — telling someone the price
   and not whether they can meet it just makes them tap to find out.
   It stops being paper rather than fading: the instrument is not on offer at
   this balance, and a half-transparent note says that far less clearly than an
   unprinted one. Also TYPE-LEGIBILITY rule 2 — state must not ride on opacity,
   which at 0.55 took the price itself below AA. */
.bm-rent.is-dear {
    color: var(--color-text-secondary, #94a3b8);
    background: transparent;
    border-color: var(--color-primary-600, #24406b);
    box-shadow: none;
}

.bm-rent.is-dear .bm-rent-term { color: var(--color-text-secondary, #94a3b8); }

.bm-rent.is-dear .bm-rent-save {
    color: var(--color-text-secondary, #94a3b8);
    background: var(--color-primary-700, #1a2744);
}

/* ---------------------------------------------------------------------------
   Field requisitions. The rented structures, kept apart from the construction
   list: they cost War Bonds rather than resources, stand at once, and come
   down when their term ends. Listed among ordinary buildings they read as
   ordinary orders, which is exactly what a real-money purchase must not do.
   --------------------------------------------------------------------------- */
.bm-requisitions {
    padding: 12px;
    margin-bottom: 10px;
    border: 1px solid rgba(236, 231, 218, 0.30);
    border-radius: 3px;
    background: rgba(236, 231, 218, 0.05);
}

.bm-req-head {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-bottom: 10px;
}

.bm-req-title {
    font-family: var(--font-display, 'Orbitron', sans-serif);
    font-size: var(--text-sm);
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--color-accent-bond, #ece7da);
}

.bm-req-sub {
    font-size: 12px;
    color: var(--color-text-secondary, #94a3b8);
}

.bm-req-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* Each offer keeps its own label and term row; they stack rather than sitting
   side by side, because two four-term ladders on one phone line cannot both be
   read. */
.bm-req-item .bm-rent-group {
    display: flex;
    width: 100%;
}

.bm-req-item .bm-rent {
    flex: 1 1 auto;
    min-width: 84px;
    justify-content: center;
}

/* --- Touch targets inside panels ----------------------------------------
   The action bar is exemplary — every button is exactly 44x44 — and every
   failure measured was INSIDE a panel: the `i` dots at 22x22 (below even
   WCAG 2.2 AA's 24px floor), the rent term buttons at 28 tall, the troop
   steppers at 28x28, TRAIN at 34, Manage at 32.

   Those five carry the consequential choices on the screen. The steppers set
   how many troops go out, which sets the payload, which sets the life that
   comes back; the rent buttons are the real-money controls.

   HIT AREA, NOT VISUAL SIZE. Every one of these is sized to sit in a dense
   row and making them physically bigger would rewrite layouts that are
   otherwise fine. A transparent ::after centred on the control extends what
   the finger can hit while the drawn element stays exactly where it was —
   which is the mechanism both platform guidelines explicitly sanction, an
   icon that "appears to be 24x24dp" inside a full 48x48dp target. */
.bm-help,
.bm-rent,
.bm-qty-btn,
.bm-train,
.bm-housing-manage {
    position: relative;
}

.bm-help::after,
.bm-rent::after,
.bm-qty-btn::after,
.bm-train::after,
.bm-housing-manage::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: max(100%, var(--touch-target));
    height: max(100%, var(--touch-target));
    transform: translate(-50%, -50%);
    /* Behind the control's own content so it never intercepts a click meant
       for something the button draws inside itself. */
    z-index: 0;
}

/* The rent row's buttons sit shoulder to shoulder, so a 44px hit area on each
   would overlap its neighbour and the overlap resolves to whichever is later
   in the DOM. Spacing them is the honest fix: the guidance is a minimum
   target AND a minimum gap, not one at the expense of the other. */
.bm-rent-terms {
    gap: 8px;
}

/* The real-money price, said in real money. */
.bm-rent-usd {
    display: block;
    font-size: var(--text-xs);
    color: var(--color-text-muted);
    line-height: 1.1;
}

/* --- The requisitions block, when it is answering something -------------
   The block renders whenever the player owns the building a tent stands in
   for — it is a catalogue and worth browsing. `.is-needed` is the other case:
   the wall is up right now, and the offer stops being a shelf and becomes an
   answer. The difference is a rule and a sentence, not a colour change, so an
   unblocked player's screen is not shouted at. */
.bm-requisitions.is-needed {
    border-left: 3px solid var(--color-accent-gold);
}

.bm-req-pointer {
    margin: var(--space-1) 0 var(--space-2);
    font-size: var(--text-xs);
    line-height: 1.45;
    color: var(--color-text-secondary);
}

/* A panel whose data is still in flight asserts nothing. */
.bm-loading {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-2) 0;
}

.bm-skeleton {
    display: block;
    height: 44px;
    border-radius: var(--radius-sm);
    background: linear-gradient(90deg,
        var(--color-primary-800) 0%,
        var(--color-primary-700) 50%,
        var(--color-primary-800) 100%);
    background-size: 200% 100%;
    animation: bmSkeleton 1.2s ease-in-out infinite;
}

@keyframes bmSkeleton {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

/* The shimmer is decoration, not information — the placeholder shape already
   says "loading" on its own, so it goes rather than being replaced. */
@media (prefers-reduced-motion: reduce) {
    .bm-skeleton { animation: none; }
}
