/* ==========================================================================
   OODASHIP design system
   --------------------------------------------------------------------------
   Implements the Figma redesign (OODASHIP_sp.pdf / OODASHIP_pc.pdf).

   Bootstrap 5 supplies the grid and form primitives underneath; everything
   visual is defined here. Values are read from the Figma files natively via
   the MCP server (see docs/figma-frames.md for the frame index); the older
   ones were sampled off the PDF exports and are being replaced frame by frame.

   Layout note: the masthead is a navy block beside a gold block on desktop
   and stacks vertically on mobile, which is why it is a flex container with
   a wrap rather than two absolutely positioned bars.
   ========================================================================== */

/* The wordmark is set in Bebas Neue in the Figma. Self-hosted rather than
   pulled from Google's CDN so the page has no third-party font dependency;
   licence in game/static/game/fonts/OFL.txt.

   Figma reports the lockup as "Bebas Neue Bold", which is the commercial Pro
   family. The OFL release is a single 400 weight, but it renders OODASHIP at
   142.7px against the design's 144px at 48px - within a pixel - and asking the
   browser for 700 does not change the advance width, so 400 is used as-is. */
@font-face {
    font-family: "Bebas Neue";
    src: url("../fonts/bebas-neue-400-latin.1a662f8f0b09.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

/* --------------------------------------------------------------------------
   1. Design tokens
   -------------------------------------------------------------------------- */

:root,
:root[data-theme="default"] {
    /* Brand */
    --od-navy: #192a54;
    --od-navy-dark: #101c39;
    --od-gold: #e4c565;
    --od-gold-dark: #d4b34e;

    /* Surfaces */
    --od-white: #ffffff;
    --od-cream: #f4f3e2;
    --od-cream-alt: #faf9ee;
    --od-page-bg: #ffffff;
    --od-info-bg: #bdd7ee;
    /* Modal callouts use a softer blue than page-level info panels, and the
       tick icon a slightly deeper green. Both measured from frames 8/11/12. */
    --od-info-soft: #d6e1fd;
    /* Page-level notice bands (dashboard welcome, decisions validation). */
    --od-notice-bg: #d6e1ff;
    /* The event-card band in the dashboard's game-state legend. The only
       flat yellow in either file - distinct from --od-amber-bg (#fdf3d4),
       which tints warning alerts (1:13865, corroborated on 1:13196). */
    --od-yellow-band: #faf6ac;
    /* Warm grey-beige, used for the instructions tab strip (1:10060) and as the
       edge of the game master's game cards. Distinct from --od-cream (#f4f3e2),
       which is what those cards are FILLED with - this note used to claim the
       fill, and .od-card--game was built on that, which is how the game master's
       dashboard came out grey. */
    --od-beige: #eae9e2;
    --od-grey-soft: #acaba3;
    /* Near-white card fill used by the dashboard progress card and the
       decisions-form checkboxes. */
    --od-surface-soft: #f8f8f4;
    --od-green-tick: #30a053;

    /* Game master round controls (1:12506). Two colours that carry meaning
       rather than status: blue is "process this round", green is "advance to
       the next one". Deliberately distinct from --od-green (#00b237), which
       marks positive figures. */
    --od-blue: #0781bf;
    --od-blue-dark: #06699a;
    --od-green-go: #60be7d;
    --od-green-go-dark: #4da668;

    /* Status */
    --od-green: #00b237;
    --od-red: #b42626;
    /* #ffd1d1, confirmed on both the desktop and mobile delete modals (1:9237,
       30:565). #ffdada came from the PDF exports and appears in no frame. */
    --od-red-bg: #ffd1d1;
    --od-amber-bg: #fdf3d4;

    /* Ink. Body copy is pure black in the Figma, not the softer #333333 it was
       set to from the PDF exports. Checked natively across five frames - the
       masthead component (1:479), top page (1:480), login (1:8702), team
       dashboard (1:13196) and round decisions (1:13973) - and essentially every
       text node reads as black. The single exception is the login form's two
       field labels, which really are #333 and now use --od-text-soft.

       --od-text-strong keeps its own definition rather than being folded in:
       the rules using it were deliberately black before this change and should
       stay black if body ink is ever softened again. */
    --od-text: #000000;
    --od-text-strong: #000000;
    --od-text-soft: #333333;
    --od-text-muted: #5c5c5c;
    --od-text-faint: #989898;
    /* Pre-filled / placeholder values in the decisions form (1:13973). */
    --od-text-placeholder: #818181;
    --od-text-on-dark: #ffffff;

    /* Lines. Container borders are #acaba3 throughout the design - the same
       grey as --od-grey-soft - on the dashboard panels, leaderboard and results
       tables, decisions and create-game fields, and the game master cards. The
       previous #d9d8d0 came from the PDF exports and appears in none of the
       frames read so far, so every bordered container has been a shade too
       light. Kept as its own token because it names a role, not a swatch. */
    --od-border: #acaba3;
    /* #e8e7e0, read on the manual sections and cards (9:125, 13:854, 13:1012),
       the mobile table rules (14:1761) and the mobile progress track (14:1731).
       #eae9e2 came from the PDF exports and appears in no frame - the same
       error as --od-border once had. */
    --od-border-soft: #e8e7e0;
    --od-rule: #1a1a1a;
    /* The all-decisions matrix uses a filled dark header rather than the light
       one the other tables share (1:11997). */
    --od-table-head: #4d4d4d;

    /* Disabled / neutral controls */
    /* #969696, not the #989898 sampled from the PDFs. Confirmed on two frames:
       the decisions form's validate button (1:14216) and the game master
       dashboard's delete button (1:8753). */
    --od-grey: #969696;
    --od-grey-dark: #7d7d7d;

    /* Radii. Measured natively from the pc frames (event card 1:11389 and team
       dashboard 1:13196) rather than estimated off the PDF exports: buttons and
       cards are both 10px, the masthead corner is 30px and tags are 6px.

       The design also has a 28px-tall status chip at 3px, but .od-tag currently
       serves both that and the 50px-tall tag, so one radius has to cover both
       until they are split into separate components. */
    --od-r-pill: 999px;
    --od-r-bar: 30px;
    --od-r-card: 10px;
    --od-r-btn: 10px;
    --od-r-tag: 6px;
    --od-r-chip: 3px;
    --od-r-input: 6px;

    /* Spacing scale */
    --od-s1: 4px;
    --od-s2: 8px;
    --od-s3: 12px;
    --od-s4: 16px;
    --od-s5: 24px;
    --od-s6: 32px;
    --od-s7: 48px;
    --od-s8: 64px;

    /* Type

       Every text node in the Figma file is Meiryo (Bold or Regular) - checked
       against the design natively via the Figma MCP server rather than read off
       the PDF exports, which do not carry family names reliably. Meiryo
       therefore leads the body stack; previously Hiragino did, which meant
       macOS and iOS rendered the whole UI in the wrong face.

       Meiryo ships with Windows and with Microsoft Office on macOS, and cannot
       be self-hosted - the licence does not permit webfont embedding. Where it
       is absent the stack falls through to Hiragino, which is what these
       platforms were already showing, so this is a strict improvement rather
       than a regression risk. */
    --od-font: Meiryo, "Meiryo UI", "Hiragino Kaku Gothic ProN",
               "Hiragino Sans", "Yu Gothic", "MS PGothic",
               -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
               "Helvetica Neue", Arial, sans-serif;

    /* The kana beside the wordmark resolves to the same faces as body text now
       that Meiryo leads both; kept as a separate token because the wordmark is
       a fixed brand lockup and should not follow any later change to body
       type. */
    --od-font-kana: Meiryo, "Meiryo UI", "Hiragino Kaku Gothic ProN",
                    "Hiragino Sans", "Yu Gothic", "MS PGothic", sans-serif;
    /* Type scale. Named by pixel size because the design is an absolute px
       spec, not a ratio - "lg" told you nothing about which frame value it
       was. Every step below appears in at least one measured frame; sizes the
       design uses exactly once (31px login mark, 34px cost figure, 48px
       wordmark, 100px card number) stay literal at their single call site. */
    --od-fs-12: 0.75rem;
    --od-fs-14: 0.875rem;
    --od-fs-16: 1rem;
    --od-fs-18: 1.125rem;
    --od-fs-20: 1.25rem;
    --od-fs-22: 1.375rem;
    --od-fs-24: 1.5rem;
    --od-fs-26: 1.625rem;
    --od-fs-28: 1.75rem;
    --od-fs-30: 1.875rem;
    --od-fs-40: 2.5rem;
    --od-fs-60: 3.75rem;

    /* Chrome */
    --od-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
    /* The header pills carry their own, heavier shadow in the Figma. */
    --od-shadow-pill: 0 3px 6px rgba(0, 0, 0, 0.16);
    --od-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.16);
    --od-content-max: 1200px;
}

/* --------------------------------------------------------------------------
   2. Base
   -------------------------------------------------------------------------- */

/* Own the box model rather than relying on Bootstrap's reset being present -
   without this, padding on .od-main adds to a 100% width and pushes the page
   wider than the viewport. */
*,
*::before,
*::after {
    box-sizing: border-box;
}

body {
    font-family: var(--od-font);
    color: var(--od-text);
    background-color: var(--od-page-bg);
    font-size: var(--od-fs-16);
    line-height: 1.7;
    -webkit-font-smoothing: antialiased;
    /* Long unbroken strings (team tokens, URLs) must not widen the page. */
    overflow-wrap: break-word;
}

/* Media should never exceed its container. */
img,
picture,
svg,
video {
    max-width: 100%;
    height: auto;
}

a {
    color: var(--od-navy);
    text-decoration: none;
}

a:hover {
    color: var(--od-navy-dark);
    text-decoration: underline;
}

.od-shell {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    /* Guard against any single wide child (long table, nowrap header) widening
       the whole page. Wide content scrolls in its own container instead. */
    overflow-x: hidden;
}

.od-main {
    flex: 1 0 auto;
    /* Flex items default to min-width:auto, which refuses to shrink below the
       content's intrinsic width and pushes the page wider than the viewport
       on narrow screens. */
    min-width: 0;
    width: 100%;
    max-width: var(--od-content-max);
    margin: 0 auto;
    padding: var(--od-s7) var(--od-s4) var(--od-s8);
}

/* --------------------------------------------------------------------------
   3. Masthead
   Navy block and gold block sit side by side on desktop with the outer
   bottom corners rounded, and stack on mobile.
   -------------------------------------------------------------------------- */

/* The bar is not full-bleed. In the Figma it is 1200px wide sitting at x=400 of
   a 2000px frame - exactly the content column, which the page panels share
   (the event card panel is x=400.5, w=1199.5). That is also why both bottom
   corners are rounded: it reads as an inset bar, not a browser-wide band.
   Splits 50/50 navy/gold and stands 100px tall on desktop. */
.od-masthead {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    width: 100%;
    max-width: var(--od-content-max);
    margin: 0 auto;
}

.od-masthead__brand {
    display: flex;
    align-items: center;
    gap: var(--od-s5);
    flex: 1 1 50%;
    min-width: 0;
    min-height: 100px;
    padding: var(--od-s4) var(--od-s6);
    background: var(--od-navy);
    color: var(--od-text-on-dark);
    border-bottom-left-radius: var(--od-r-bar);
}

/* The How to Play link sits hard against the navy/gold seam, not next to
   the wordmark. */
.od-masthead__brand .od-masthead__link {
    margin-left: auto;
}

.od-masthead__actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--od-s3);
    flex: 1 1 50%;
    min-width: 0;
    min-height: 100px;
    padding: var(--od-s3) var(--od-s6);
    background: var(--od-gold);
    border-bottom-right-radius: var(--od-r-bar);
}

/* Sizes come from the Figma header component: the OODASHIP lockup is 48px, and
   the (c) mark and kana beside it are both 20px, so the base size is 20px and
   those two sit at 1em.

   The lockup is live Bebas Neue text, not vector art. It had been rebuilt as an
   SVG because the PDF exports flatten type to outlines, but that traced art is
   248x61 (4.07:1) against the design's 144x48 (3:1), so it rendered 36% too
   wide on every page. Set as real type it matches to within a pixel. */
/* STACKED, and the (c) rides HIGH. Customer markup 4 Sep 2026, No.1/No.4,
 * marked (kyoutsuu shuusei) - a common fix, so it lands on the masthead and
 * therefore on every page.
 *
 * This supersedes two earlier notes that used to sit here, and they are gone
 * rather than kept, because both described a one-line lockup that no longer
 * exists: the Figma-centred reading, and then the baseline reading that
 * answered the "copyright symbol should be lower down" report. The customer
 * has now asked for the opposite of that report - the (c) flush with the
 * wordmark's TOP - so the old rule is not a constraint any more, it is just
 * contradicted. Do not "restore" it.
 *
 *      OODASHIP(c)     (c) superscript, top-flush, 0.41 of the wordmark
 *       kana           centred UNDER OODASHIP, not under OODASHIP+(c)
 *
 * Grid, not flex, and that is the load-bearing choice. The kana centres on the
 * WORDMARK alone in both of the customer's reference lockups - measured at
 * -0.55% of the wordmark width in the hero artwork and -0.0% in the login
 * sample. A centred flex column would centre it on the whole row instead,
 * which includes the (c) and its gap and puts the kana 6.2% (8.7px) too far
 * right. Here the kana shares column 1 with the wordmark and centres in it, so
 * the (c) in column 2 cannot pull it off centre - correct by construction
 * rather than by an offset that has to be re-derived whenever the (c) changes
 * size. The (c) still occupies its column, so the (yuubikata) link beside it
 * does not shift.
 *
 * Sizes are measured off the customer's own chip as ratios of the wordmark's
 * ink height H (0.72em of Bebas Neue, so 34.56px at the 48px art below), and
 * the rendered result is within 0.002 of every one of them:
 *
 *     (c) height 0.414 H   top offset 0.000 H   gap after wordmark 0.172 H
 *     kana height 0.379 H  width 0.612 W        gap below wordmark 0.207 H
 *
 * Note these are the MASTHEAD ratios, which are not the login page's - see
 * .od-login__logo. The customer drew both, and they differ because this is
 * optical sizing: the login page's kana ratio applied here would render the
 * kana at 5.6px.
 */
.od-wordmark {
    display: inline-grid;
    grid-template-columns: auto auto;
    font-size: var(--od-fs-20);
    color: var(--od-text-on-dark);
    white-space: nowrap;
}

.od-wordmark__art {
    grid-column: 1;
    grid-row: 1;
    font-family: "Bebas Neue", var(--od-font);
    font-size: 48px;
    font-weight: 400;
    line-height: 1;
    letter-spacing: 0;
}

.od-wordmark:hover {
    color: var(--od-text-on-dark);
    text-decoration: none;
    opacity: 0.9;
}

/* Top-flush with the wordmark, and sized against it rather than against the
 * body text - 29px is 0.414 of the wordmark's 34.56px ink height, per the
 * customer's chip.
 *
 * align-self: start puts the mark's BOX at the top of the row; the translate
 * puts its INK there, which is not the same place and is the whole reason this
 * nudge exists. Measured with canvas measureText() at the rendered sizes:
 *
 *     OODASHIP (Bebas Neue 48px)  ink top = box top + 4.32px
 *     (c)      (Meiryo 29px)      ink top = box top + 8.10px
 *
 * so the mark's ink started 3.78px low, and -0.130em is that 3.78px at 29px.
 * In em so it survives the mobile breakpoint, where the mark drops to 14.5px.
 * Re-measure the same way if either face ever changes.
 *
 * margin-left, not the grid's column-gap: the gap the customer draws is between
 * the two INK edges, and both glyphs carry side bearings, so 3.9px of margin is
 * what lands the ink 5.94px (0.172 H) apart.
 */
.od-wordmark__mark {
    grid-column: 2;
    grid-row: 1;
    align-self: start;
    margin-left: 3.9px;
    font-family: var(--od-font-kana);
    font-size: 29px;
    font-weight: 700;
    line-height: 1;
    transform: translateY(-0.130em);
}

/* Regular weight in the Figma, not bold like the wordmark itself.
 *
 * Column 1 with the wordmark - see the note on .od-wordmark for why that is
 * what centres it correctly.
 *
 * The negative margin is not a fudge: grid row-gap cannot go negative, and the
 * two boxes' natural stacking already leaves 9.17px between the wordmark's ink
 * bottom and the kana's ink top, against the 7.15px (0.207 H) the customer
 * draws. -2.02px is that difference.
 *
 * text-indent cancels the trailing letter-space. letter-spacing adds its space
 * AFTER the last character too, so without this the ink sits half a space left
 * of the box centre and the kana reads as off-centre under the wordmark.
 */
.od-wordmark__kana {
    grid-column: 1;
    grid-row: 2;
    justify-self: center;
    margin-top: -2.02px;
    font-family: var(--od-font-kana);
    font-size: 14.1px;
    font-weight: 400;
    line-height: 1;
    letter-spacing: 0.052em;
    text-indent: 0.052em;
}

/* 22px bold in the Figma header component, not the small-text size it had. */
/* 遊び方 is the only control in the product smaller than a thumb: measured at
   390px it is a 36x20 box, against the 24x24 minimum, and it is in the masthead
   so it is on every page. The padding takes the tap target to 44px; the equal
   negative margin cancels it for layout, so the header is unmoved - the item's
   outer box is the same 20px tall it was, and its siblings do not shift.
   Visually identical, twice the target. */
.od-masthead__link {
    color: var(--od-text-on-dark);
    font-weight: 700;
    font-size: var(--od-fs-22);
    white-space: nowrap;
    padding: 12px 4px;
    margin-top: -12px;
    margin-bottom: -12px;
}

.od-masthead__link:hover {
    color: var(--od-text-on-dark);
    text-decoration: underline;
}

/* The welcome line is 18px regular black on the gold - not bold navy. It is the
   only regular-weight text in the header. */
.od-masthead__welcome {
    font-weight: 400;
    font-size: var(--od-fs-18);
    color: var(--od-text-strong);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* White pill buttons that sit on the gold bar. 130x45 in the Figma with 20px
   bold black type and a heavier drop shadow than the general --od-shadow.
   min-width rather than a fixed width so longer translations still fit. */
.od-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 130px;
    min-height: 45px;
    padding: var(--od-s2) var(--od-s5);
    background: var(--od-white);
    color: var(--od-text-strong);
    font-weight: 700;
    font-size: var(--od-fs-20);
    line-height: 1.2;
    border: none;
    border-radius: var(--od-r-pill);
    box-shadow: var(--od-shadow-pill);
    white-space: nowrap;
    cursor: pointer;
}

/* APP-6. The logout pill is a form now, so the wrapper must not introduce a
   box of its own between the masthead's flex row and the button - otherwise the
   pill stops aligning with the ones beside it. */
.od-pill-form {
    display: inline-flex;
    margin: 0;
}

.od-pill:hover {
    background: var(--od-cream);
    color: var(--od-navy);
    text-decoration: none;
}

/* Build hash: a review aid, not part of the Figma design. Rendered only when
   FEATURE_BUILD_HASH is on, and kept deliberately quiet so it does not read as
   part of the brand lockup. */
.od-version {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: var(--od-fs-12);
    color: rgba(255, 255, 255, 0.45);
    letter-spacing: 0.02em;
}

/* --------------------------------------------------------------------------
   4. Headings
   -------------------------------------------------------------------------- */

/* Measured from the Figma login frame: 26px black text beside a 10px-wide,
   50px-tall navy bar. */
.od-heading {
    display: flex;
    align-items: center;
    gap: var(--od-s4);
    margin: 0 0 var(--od-s5);
    font-size: var(--od-fs-26);
    font-weight: 800;
    color: var(--od-text-strong);
}

.od-heading::before {
    content: "";
    flex: 0 0 auto;
    width: 10px;
    height: 1.92em;
    background: var(--od-navy);
    border-radius: 1px;
}

/* A tag sitting under a page title rather than beside it, as on the team's
   all-decisions screen. The heading's bottom margin alone left the chip
   crowding the title it belongs to, and unlike a heading-row tag it has no
   space of its own. */
.od-heading + .od-tag {
    margin-top: var(--od-s3);
}

/* The label above a page heading ("チーム名："), 28px bold against the heading's
   26px - set larger than the name it introduces (1:13582). */
.od-eyebrow {
    margin: 0 0 var(--od-s2);
    font-size: var(--od-fs-28);
    font-weight: 800;
    color: var(--od-text-strong);
}

/* 22px bold in the Figma - consistent across the dashboard (1:13196), round
   decisions (1:13973) and event card (1:11389) frames. The top page uses 20px;
   that is overridden on .od-home-panel rather than softening this one. */
.od-subheading {
    margin: var(--od-s6) 0 var(--od-s4);
    font-size: var(--od-fs-22);
    font-weight: 800;
    color: var(--od-text);
}

.od-subheading::before {
    content: "\25bc";
    margin-right: var(--od-s2);
    font-size: 0.85em;
}

/* The design uses two heading markers, and they are different sizes: a filled
   triangle at 22px for page sections, and an open circle at 24px for the title
   of a card or panel. Both appear on the dashboard (1:13196) - compare
   the section headings against the card titles - and on the event card
   (1:11963). */
.od-subheading--circle {
    font-size: var(--od-fs-24);
}

.od-subheading--circle::before {
    content: "\25cb";
    font-size: 0.8em;
}

/* One extra step of air above a section heading that follows a full-width card
   rather than another heading. The base 32px is measured for headings that open
   a column or sit under body text; against the flush edge of a table card it
   reads as though the heading belongs to the card above it instead of to the
   panel below. Used on the game master's round-progress section, where the
   teams table ends hard against it.

   A modifier rather than a change to .od-subheading, which is used 46 times
   across 15 templates - the base rule is right for the other 45. And a class
   rather than a rule keyed off the data-capture wrapper this heading sits in:
   that attribute is the screenshot contract, so styling through it would let a
   renamed capture region silently move the layout. */
.od-subheading--spaced {
    margin-top: var(--od-s7);
}

/* Row that pairs a heading with a right-aligned action. */
.od-heading-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--od-s4);
    flex-wrap: wrap;
    margin-bottom: var(--od-s5);
}

.od-heading-row .od-heading {
    margin-bottom: 0;
}

/* --------------------------------------------------------------------------
   5. Cards and panels
   -------------------------------------------------------------------------- */

.od-card {
    background: var(--od-white);
    border: 1px solid var(--od-border);
    border-radius: var(--od-r-card);
    padding: var(--od-s5);
}

.od-card--cream {
    background: var(--od-cream);
    border-color: transparent;
}

/* One card directly under another block in the same stack. Cards carry no
   margin of their own, so on the event card screen the description sat flush
   against the panel holding the card number, reading as one box with a rule
   across it. 16px rather than the 32 above, to match the gap the alert's own
   margin puts between the submitted band and that panel. */
.od-card--stacked {
    margin-top: var(--od-s4);
}

/* The game master's game cards are a CREAM panel with a light edge, drawn the
   same on desktop (1:8753) and mobile (12:225, the gamecard_01/02 component
   from the sp sheet 25:1079 / 25:1104).

   The fill and the edge had been swapped. Scanned across a row of cards in
   1:8753 the card reads
       #ffffff (page) | #eae9e2 (4-5px edge) | #f4f3e2 (374px fill) | #eae9e2
   and the sp frame agrees - its cards are 358px of #f4f3e2 at y=1200. We were
   filling the whole card with #eae9e2, the edge colour, which is a warm grey
   and reads as grey beside the cream used everywhere else on the page.

   --od-beige (#eae9e2) itself is not wrong - the instructions tab strip really
   is filled with it (1:10060, measured 574px of #eae9e2 across the active tab).
   It is only wrong as a card fill, so the token keeps its value and this rule
   uses it in the role the frame gives it.

   1px rather than the frame's 4-5px: at that weight a flat band reads as a
   drawn frame around the card rather than as its edge.

   Only the game master's list is styled this way here. The top page's card list
   is the same object conceptually, but both top-page frames (1:480, 21:212) show
   the empty state, so there is no measurement for it - see the notes. */
.od-card--game {
    background: var(--od-cream);
    border-color: var(--od-beige);
}

/* The status chip is a white pill carrying its meaning in the text colour, not
   a filled tag: red while in progress, grey once complete (1:8753, 25:1082 /
   25:1095).

   Three classes deep because .od-card__head .od-tag further down the sheet
   shrinks head tags to 12px at the same specificity, and being later it wins. */
/* The state chip is 84x40 white on a 10px radius with 18px bold type, centred
   (1:9288 / 1:9454). It was 16px and, more visibly, grey on white - which is
   what made 完了 read as a disabled control rather than a state. Only 進行中 is
   coloured, and it is red (1:9446); every other state is black (1:9454). */
.od-card--game .od-card__head .od-tag {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 84px;
    min-height: 40px;
    background: var(--od-white);
    border-color: var(--od-white);
    border-radius: var(--od-r-card);
    color: var(--od-text);
    font-size: var(--od-fs-18);
}

.od-card--game .od-card__head .od-tag--navy {
    color: var(--od-red);
}

/* Card actions on desktop are the full 50px/20px --card size at a 5px radius,
   pinned to the widths the frame gives them: 194px for the navy primary against
   78px for the grey delete, sitting on the same row (1:9495 / 1:9507, and every
   card on 1:8753).

   These were 30px tall at 12px with a 3px radius, taken from 25:1084 / 25:1086
   - which are *sp* nodes. Applying the phone size at desktop is what made the
   buttons look shrunken. The phone size is still correct on a phone, so it now
   lives in the breakpoint block at the end of this file; it has to be restated
   there at this same selector depth, because the generic `.od-btn--card`
   collapse in that block loses to this two-class rule. */
.od-card--game .od-btn--card {
    min-width: 194px;
    min-height: 50px;
    padding: var(--od-s2) var(--od-s4);
    font-size: var(--od-fs-20);
    border-radius: 5px;
}

/* Delete is grey here, not red, and 78px against the primary's 194px. Both
   files draw it that way and the destructive confirmation still stands behind
   it (1:9507, 25:1086 / 25:1099, and every card on 1:8753). */
.od-card--game .od-btn--danger {
    min-width: 78px;
    background: var(--od-grey);
    border-color: var(--od-grey);
}

.od-card--game .od-meta {
    font-size: var(--od-fs-16);
    line-height: 29px;
    color: var(--od-text-soft);
}

/* Every game card carries a banner between the title and the meta lines: a
   white panel washed with the isometric grid, with the game's state written
   across it at 22px (25:1101, 25:1088; text 25:1089 / 25:1102). We were not
   drawing it at all. The grid is the same artwork the design uses, exported
   from the file rather than approximated in CSS. */
/* The isometric mesh is Figma's own export of the banner node (ダッシュボード柄,
   1:9644), not a lookalike. The file it replaced was a 500x460 tile of a much
   finer mesh: at this band's size `cover` scaled it to 0.72 and cropped away
   the bottom three quarters, so the lines came out thinner and sparser than the
   design - the "hatched lines are darker in Figma" report.

   The export is 410x83 against the banner's 358x72.47 - the same ratio to seven
   decimal places (4.9397590 vs 4.9397592), i.e. the asset *is* the banner at
   1:1 - so `cover` now scales it without cropping or distorting it. */
.od-gamecard__banner {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 72px;
    margin-bottom: var(--od-s4);
    background-color: var(--od-white);
    background-image: url("../images/card-grid.3790ffcacb89.png");
    background-size: cover;
    background-position: center;
    font-size: var(--od-fs-26);
    font-weight: 700;
    text-align: center;
    /* Black, not the softer #333 this was: ゲーム完了 reads pure black in the
       frame (1:9664), as every settled state on this card does. */
    color: var(--od-text);
}

/* Only the in-progress banner is red (1:9656 against 1:9664). */
.od-gamecard__banner--active {
    color: var(--od-red);
}

.od-card--flush {
    padding: 0;
    overflow: hidden;
}

/* Card needing attention, e.g. a team that missed its submission. */
.od-card--flagged {
    border-color: var(--od-red);
}

.od-panel {
    background: var(--od-cream);
    border-radius: var(--od-r-card);
    padding: var(--od-s5);
}

/* The dashboard sidebar panels are not cream. In 1:13196 the team-status panel
   (1:13356) carries only a #acaba3 border with no fill, and the game-progress
   card (1:13523) sits on #f8f8f4 with the same border. Cream is right for the
   top-page and login cards, so these are variants rather than a base change. */
.od-panel--outline {
    background: var(--od-white);
    border: 1px solid var(--od-grey-soft);
}

.od-panel--soft {
    background: var(--od-surface-soft);
    border: 1px solid var(--od-grey-soft);
}

.od-note {
    background: var(--od-info-bg);
    border-radius: var(--od-r-input);
    padding: var(--od-s3) var(--od-s4);
    font-size: var(--od-fs-14);
}

/* The isometric grid is Figma's own export (1:14954, "image 1"), not a
   recreation. It used to be two crossed repeating-linear-gradients, which was
   wrong twice over: too faint, and the wrong *shape* - the gradients tile an
   infinite uniform lattice, while the design's mesh is a bounded diamond that
   is densest in the middle and trails off at the edges. No amount of tuning the
   alpha would have produced that, which is why it never looked right.

   It sits on the shell, not on .od-hero: the frame runs the grid behind the
   whole top page, masthead included, with the mesh visible in the white margins
   either side of the navy bar.

   Placement comes straight from the frame. The image node sits at (-983, -1625)
   in a 2000-wide frame whose centre is x=1000, so the mesh's centre ends up 65px
   right of the page centre, and the page's top edge falls 1625px down the
   original image.

   The shipped file is that original with its first 1625 rows already cut off -
   they sit above the page and can never be seen - so it aligns at `top` and is
   4096x1890 rather than 4096x3515. That trim is most of why it is 343KB rather
   than the 1.4MB Figma exports; the rest is lossless WebP, which on line art
   this sparse beats both PNG (4.1x) and lossy WebP at q90.

   Full 4096 width is kept deliberately: the mesh still has lines running to the
   frame's edges, so cropping it to the 2000-wide frame would put a visible cut
   in the pattern on any monitor wider than that. */
.od-shell--top {
    background-color: var(--od-white);
    background-image: url("../images/hero/hero-grid.bc74b47afb39.webp");
    background-repeat: no-repeat;
    background-size: 4096px 1890px;
    background-position: calc(50% + 65px) top;
}

/* Hero runs full-bleed out of the padded main column. The artwork is
   transparent, so the shell's grid shows through it.
 *
 * The negative bottom margin is the desktop half of the overlap the mobile
 * block already does (see .od-home-panel there): the illustration BLEEDS onto
 * the cream panel, it does not stop above it. In 1:480 the orange piece the man
 * stands on runs to y=1165.5 while the panel starts at y=1100, so its lower
 * 65.5px sit on the cream. We had a 32px gap instead, which reads as two
 * stacked blocks.
 *
 * 102px is that 65.5, plus this box's own 24px bottom padding, plus the 13px of
 * transparent film below the orange piece inside the artwork file: it is cut at
 * y=1178.5 while the orange piece ends at 1165.5, which is the last inked row
 * 1334 of 1351 measured off the file. The panel renders exactly 1000px wide,
 * the same as the frame's, so the 65.5 transfers 1:1. Across the desktop range
 * the exact figure moves between 99.6 and 103 because the art scales with the
 * viewport while the panel does not; one value is within 3px everywhere, which
 * is why this is a constant and not a calc().
 *
 * position: relative is what puts the art ON the cream rather than under it -
 * the panel is static, so a positioned sibling paints over it. pointer-events
 * goes with it: this box now covers the panel's top 102px, and without it the
 * transparent film would swallow clicks meant for the panel. Nothing in here is
 * interactive. */
.od-hero {
    position: relative;
    pointer-events: none;
    margin: calc(var(--od-s7) * -1) calc(50% - 50vw) -102px;
    width: 100vw;
    padding: var(--od-s6) var(--od-s4) var(--od-s5);
}

/* 1423px, measured off the top page (1:480): the illustration runs from x=309
   (the lightbulb group) to x=1732 (the parachutist and the GOAL button) inside
   a 2000px frame whose content column is only 1200 wide (400-1600). So the
   artwork is deliberately *wider* than the column and bleeds past it on both
   sides - about 1.19x - which is why capping it at 1100px made it read as
   narrower than the design.

   The file is 1904x1351, so 1.338x the size it is ever drawn at, and it is
   built rather than exported: the hero is not one node in the frame but eleven
   siblings (wordmark, tagline, parachutist, lightbulb, desk, tablet, puzzle,
   the three highlight bars and the copy over them) with the page's own panel
   and header interleaved between them, so there is nothing to select and
   export in one go. See docs/figma-conversion-notes.md for the node list and
   the compositing step; regenerate it there rather than by hand. */
.od-hero__art {
    display: block;
    width: 100%;
    max-width: 1423px;
    height: auto;
    margin: 0 auto;
}

.od-hero__tagline {
    max-width: 680px;
    margin: var(--od-s4) auto 0;
    text-align: center;
    font-weight: 700;
    font-size: var(--od-fs-16);
    color: var(--od-text);
}

.od-panel--hero {
    border-radius: var(--od-r-bar);
    padding: var(--od-s7) clamp(var(--od-s4), 5vw, var(--od-s8));
}

/* Top page panel, measured from 1:480: 1000px wide sitting inside the 1200px
   content column, split into a wide left column (active games) and a 300px
   right column holding the quick-action and instructions buttons. Headings
   start 35px down and 46px in from the card edge. */
.od-home-panel {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 0 64px;
    max-width: 1000px;
    margin: 0 auto;
    padding: 35px 46px 45px;
}

/* 20px here, against 22px on the game screens. */
.od-home-panel .od-subheading {
    font-size: var(--od-fs-20);
}

.od-home-panel__col > .od-subheading:first-child {
    margin-top: 0;
}

.od-narrow {
    max-width: 720px;
    margin: 0 auto;
}

.od-card__title {
    margin: 0 0 var(--od-s2);
    font-size: var(--od-fs-18);
    font-weight: 800;
}

.od-card__meta {
    margin: 0 0 var(--od-s4);
    font-size: var(--od-fs-14);
    color: var(--od-text-muted);
}

.od-card__actions,
.od-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--od-s3);
}

.od-actions {
    margin-top: var(--od-s5);
}

/* Three cards across the 1200px content column in the Figma (1:8753): 386px
   wide with a ~21px gutter. auto-fit rather than a hard repeat(3) so the grid
   still collapses sensibly on narrower viewports. */
.od-game-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
    gap: 21px;
}

/* Vertically stacked full-width buttons, as used in the Quick Actions block. */
.od-stack {
    display: flex;
    flex-direction: column;
    gap: var(--od-s4);
}

.od-list {
    margin: 0 0 var(--od-s5);
    padding-left: 1.4em;
}

.od-list li {
    margin-bottom: var(--od-s2);
}

/* Numbered steps rendered as individual white cards inside a cream panel,
   with a circled numeral, as used on the team links page. */
.od-steps {
    counter-reset: od-step;
    list-style: none;
    margin: 0;
    padding: 0;
}

.od-step {
    display: flex;
    align-items: flex-start;
    gap: var(--od-s4);
    margin-bottom: var(--od-s4);
    padding: var(--od-s4) var(--od-s5);
    background: var(--od-white);
    border: 1px solid var(--od-border);
    border-radius: var(--od-r-card);
}

.od-step:last-child {
    margin-bottom: 0;
}

/* Two across on the team links page, as the frame lays the four steps out
   (1:11069). One per line on a phone. */
.od-steps--grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--od-s4);
}

.od-steps--grid .od-step {
    margin-bottom: 0;
}

/* 20px navy numeral against 15px body copy (13:521, 13:522).

   The frames set this as a circled numeral glyph - ①②③ - rather than a digit
   inside a drawn ring. Kept as a drawn ring deliberately: Meiryo only carries
   ① through ⑳, and both this list and .od-rank can run past twenty, where the
   glyph silently falls back to a bare digit. The ring renders identically and
   does not have a ceiling. */
.od-step::before {
    counter-increment: od-step;
    content: counter(od-step);
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.5em;
    height: 1.5em;
    border: 1px solid var(--od-navy);
    border-radius: 50%;
    color: var(--od-navy);
    font-weight: 700;
    font-size: var(--od-fs-20);
}

.od-step__text {
    font-size: 15px;
    line-height: 22.5px;
}

.od-stack--tight {
    gap: var(--od-s2);
}

.od-stack--tight .od-btn {
    width: 100%;
}

/* A form that exists only so a button can POST instead of following a link.
   display:contents keeps it out of the box tree, so the button becomes a direct
   flex child of .od-stack and is spaced by the stack's gap exactly like the
   disabled buttons beside it. Without this the form itself becomes the flex
   child and the actions drift out of alignment. margin:0 is a fallback for the
   UA default should display:contents not apply. */
.od-inline-form {
    display: contents;
    margin: 0;
}

/* --------------------------------------------------------------------------
   6. Buttons
   -------------------------------------------------------------------------- */

.od-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--od-s2);
    padding: var(--od-s3) var(--od-s6);
    font-weight: 700;
    font-size: var(--od-fs-16);
    line-height: 1.3;
    border: none;
    border-radius: var(--od-r-btn);
    cursor: pointer;
    text-align: center;
    transition: background-color 0.15s ease, opacity 0.15s ease;
}

.od-btn:hover {
    text-decoration: none;
}

.od-btn--primary {
    background: var(--od-navy);
    color: var(--od-text-on-dark);
}

.od-btn--primary:hover {
    background: var(--od-navy-dark);
    color: var(--od-text-on-dark);
}

.od-btn--muted {
    background: var(--od-grey);
    color: var(--od-text-on-dark);
}

.od-btn--muted:hover {
    background: var(--od-grey-dark);
    color: var(--od-text-on-dark);
}

.od-btn--success {
    background: var(--od-green);
    color: var(--od-text-on-dark);
}

/* Round controls on the game master game screen (1:12506): blue processes the
   current round, green advances to the next. --process-ghost is the same blue
   as an outline, used for "view results" once a round is processed. */
.od-btn--process {
    background: var(--od-blue);
    color: var(--od-text-on-dark);
}

.od-btn--process:hover:not(:disabled) {
    background: var(--od-blue-dark);
    color: var(--od-text-on-dark);
}

.od-btn--process-ghost {
    background: var(--od-white);
    color: var(--od-blue);
    border: 2px solid var(--od-blue);
}

.od-btn--process-ghost:hover:not(:disabled) {
    background: var(--od-blue);
    color: var(--od-text-on-dark);
}

.od-btn--advance {
    background: var(--od-green-go);
    color: var(--od-text-on-dark);
}

.od-btn--advance:hover:not(:disabled) {
    background: var(--od-green-go-dark);
    color: var(--od-text-on-dark);
}

.od-btn--danger {
    background: var(--od-red);
    color: var(--od-text-on-dark);
}

.od-btn--gold {
    background: var(--od-gold);
    color: var(--od-navy);
}

.od-btn--outline {
    background: transparent;
    color: var(--od-navy);
    box-shadow: inset 0 0 0 2px var(--od-navy);
}

.od-btn--block {
    display: flex;
    width: 100%;
}

/* Button sizes.

   The design has four, and they vary in two dimensions at once, so they are
   named for where they are used rather than by a linear s/m/l scale - --card
   carries larger type than --compact despite being the narrower control.

     modifier    height  type  radius  where
     --sm            28  16px     3px  row and table actions (1:13320)
     --card          50  20px     5px  actions inside a card (1:8753)
     --compact       50  16px    10px  form submit, quick actions (1:8702, 1:480)
     --lg            74  24px    10px  primary page action (1:13196, 1:13973)

   Widths are only pinned where the design pins them: --compact and --lg are
   fixed at 300px and 360px, while --sm and --card size to their content
   (194x50 manage against 78x50 delete on the same row).

   The unmodified .od-btn is 16px with a 10px radius and matches no frame. It
   is the fallback for screens not yet measured - mostly game master - so any
   button on a converted screen should carry one of the four above. */
.od-btn--sm {
    min-height: 28px;
    padding: var(--od-s1) var(--od-s3);
    font-size: var(--od-fs-16);
    border-radius: var(--od-r-chip);
}

.od-btn--card {
    min-height: 50px;
    font-size: var(--od-fs-20);
    border-radius: 5px;
}

/* Modal actions are 200x50 with 20px type (1:9237). Same height and type as
   --card but at the standard 10px radius, since they are not inside a card -
   the 5px radius is what marks a card-internal control. */
/* 17px of side padding, not the base 32px: the frame insets ラウンドを見る 5
   決定事項 by 15.6 left and 18.4 right inside its 281px button (1:13936 /
   1:13937). At the base padding the same string needs ~312px, so the button
   overran the frame by a clear 30px. Buttons whose label is shorter than the
   200px minimum are unaffected. */
.od-btn--modal {
    min-width: 200px;
    min-height: 50px;
    padding-left: 17px;
    padding-right: 17px;
    font-size: var(--od-fs-20);
}

/* The new-round popup's primary action is wider than the 200px standard because
   it names the round it leads to: 281px against the 200px of ここに留まる beside
   it (1:13936 / 1:13941). */
.od-btn--modal-wide {
    min-width: 281px;
}

.od-btn--compact {
    min-width: 300px;
    min-height: 50px;
    font-size: var(--od-fs-16);
}

.od-btn--lg {
    min-width: 360px;
    min-height: 74px;
    font-size: var(--od-fs-24);
}

/* Releases the pinned width. 360px is the standard for a 74px-tall button and
   holds on seven frames, but the dashboard header pair (1:13196) is the same
   height at 260px and 170px, so width and height have to be separable. */
.od-btn--auto {
    min-width: 0;
}

/* Buttons stacked in a sidebar column: still 74px tall, but 20px type and full
   column width rather than 24px at 360px (1:12506, 260px inside the 300px
   sidebar). The type steps down with the column, so this is its own size
   rather than --lg plus overrides. */
/* A column button is already full width and centres its label, so the base
   button's 32px side padding buys nothing here - it only takes 64px away from
   the label and wraps it. チームダッシュボード is exactly 200px at this size
   and the sidebar panel leaves 250px, so it broke over two lines the moment the
   quick-action buttons moved inside a panel (1:14803). 12px keeps a gap for a
   label that really is too long without inventing a wrap for one that is not. */
.od-btn--column {
    display: flex;
    width: 100%;
    min-height: 74px;
    padding-left: var(--od-s3);
    padding-right: var(--od-s3);
    font-size: var(--od-fs-20);
}

.od-btn:disabled,
.od-btn.is-disabled {
    background: var(--od-grey);
    color: var(--od-text-on-dark);
    cursor: not-allowed;
    opacity: 0.7;
}

/* The game master's round controls keep their colour when disabled. The frame
   draws ラウンド処理1（待機中）in the same blue as the live ラウンド処理1
   (1:12695 against 1:12696), and the blocked ラウンドを進める variant in the
   same green as the live one (1:12699 against 1:12700) - the state is carried
   by the parenthesised label, not by grey.

   `disabled` stays on the element, so the control is still inert and still
   announced as disabled; only the fill is restored. The inherited opacity and
   not-allowed cursor above remain as the non-colour cues. */
.od-btn--process:disabled {
    background: var(--od-blue);
}

.od-btn--advance:disabled {
    background: var(--od-green-go);
}

/* --------------------------------------------------------------------------
   7. Forms
   -------------------------------------------------------------------------- */

.od-field {
    margin-bottom: var(--od-s5);
}

.od-label {
    display: block;
    margin-bottom: var(--od-s2);
    font-weight: 700;
    font-size: var(--od-fs-16);
    color: var(--od-text);
}

/* Form fields on the game master screens are introduced by a section-style
   label - the same filled triangle and 22px as .od-subheading (1:10944) -
   rather than the small bold label used elsewhere. */
.od-label--section {
    font-size: var(--od-fs-22);
    font-weight: 800;
}

.od-label--section::before {
    content: "\25bc";
    margin-right: var(--od-s2);
    font-size: 0.85em;
}

/* Form fields are 80px tall with square corners and a #acaba3 border, holding
   20px values. Confirmed on two frames - round decisions (1:13973) and create
   game (1:10944) - so this is the shared field style rather than a per-page
   one. Login (1:8702) is the exception at 60px with a 5px radius, and
   overrides this under .od-login__panel. */
.od-input,
.od-select,
.od-textarea {
    display: block;
    width: 100%;
    min-height: 80px;
    padding: var(--od-s3) var(--od-s4);
    font-family: inherit;
    font-size: var(--od-fs-20);
    color: var(--od-text);
    background: var(--od-white);
    border: 1px solid var(--od-grey-soft);
    border-radius: 0;
}

.od-input::placeholder,
.od-textarea::placeholder {
    color: var(--od-text-placeholder);
}

.od-input:focus,
.od-select:focus,
.od-textarea:focus {
    outline: none;
    border-color: var(--od-navy);
    box-shadow: 0 0 0 3px rgba(25, 42, 84, 0.12);
}

.od-help {
    margin-top: var(--od-s3);
    margin-bottom: 0;
    font-size: var(--od-fs-14);
    color: var(--od-text-muted);
}

/* Two columns, not as many as fit. The frame pairs the fields 2 x 2 - rounds
   beside teams, event frequency beside the time limit (1:10944) - where auto-fit
   put three on the first row and stranded the fourth. */
.od-form-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0 var(--od-s5);
}

/* Three across for the three setup steps (1:10944). */
.od-steps--grid-3 {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--od-s4);
}

.od-steps--grid-3 .od-step {
    margin-bottom: 0;
}

.od-actions--end {
    justify-content: flex-end;
}

/* Action rows must wrap rather than push buttons off the edge on narrow
   content columns. */
.od-actions .od-btn {
    flex: 0 1 auto;
    min-width: 0;
}


/* Login links and the game name in the delete confirmation are set in the body
   face and coloured red (13:590, 30:561), not monospace grey.

   The pc team links frame (1:11069) shows the same red on the text
   「アドレスが入る」- "address goes here" - which read as a developer
   placeholder marker, so this was originally left alone. The mobile frame
   settles it: the red is on real URLs there, and the delete modal colours a
   real game name the same way. It is the intended treatment. */
.od-code {
    font-family: inherit;
    font-size: var(--od-fs-14);
    color: var(--od-red);
    word-break: break-all;
}

/* Bootstrap renders inline <code> in #d63384; nothing pink exists anywhere in
   the Figma, so bring it back to the palette. */
code,
kbd,
samp {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    color: var(--od-text);
}

/* Django renders form widgets without our classes, so pick them up by
   position rather than editing every form definition. */
/* Same shared field style as .od-input above, for fields rendered by Django
   widgets that do not carry the class. */
.od-field input[type="text"],
.od-field input[type="password"],
.od-field input[type="number"],
.od-field input[type="email"],
.od-field input[type="date"],
.od-field select,
.od-field textarea {
    display: block;
    width: 100%;
    min-height: 80px;
    padding: var(--od-s3) var(--od-s4);
    font-family: inherit;
    font-size: var(--od-fs-20);
    color: var(--od-text);
    background: var(--od-white);
    border: 1px solid var(--od-grey-soft);
    border-radius: 0;
}

.od-field input:focus,
.od-field select:focus,
.od-field textarea:focus {
    outline: none;
    border-color: var(--od-navy);
    box-shadow: 0 0 0 3px rgba(25, 42, 84, 0.12);
}

/* The decisions form's own values are 22px against the shared field's 20px
   (1:13973 vs 1:10944); everything else about its fields now comes from the
   shared rule. Labels are 20px and help text 18px on a 26px line. */
#decisions-form .od-input,
#decisions-form .od-select {
    font-size: var(--od-fs-22);
}

#decisions-form .od-label {
    font-size: var(--od-fs-20);
}

#decisions-form .od-help {
    font-size: var(--od-fs-18);
    line-height: 26px;
    color: var(--od-text-strong);
}

/* --------------------------------------------------------------------------
   7b. Login
   -------------------------------------------------------------------------- */

/* Login, measured from frame 1:8702. The cream panel is 700x530 centred on the
   content column, with 50px gutters holding 600px-wide fields. */
.od-login {
    width: 100%;
    max-width: 700px;
    margin: 0 auto;
}

/* Login lockup: the vector wordmark, then the mark and kana as live text,
   matching how the Figma frame composes them. The gradient now lives inside
   the SVG rather than being faked with background-clip. */
/* Lower-edge alignment, the same rule the masthead lockup follows (see
   .od-wordmark above): in 1:8702 the kana's ink bottom lands within a pixel of
   the wordmark vector's (401.3 against 400.3) and the (c)'s a few pixels above
   it, so all three read as sitting on one line at the BOTTOM of the 60px
   wordmark. Centred - which is what this was - floated both of them about 20px
   high, which is the "copyright symbol should be lower" report again, this time
   on the login page rather than the masthead.

   flex-end aligns the boxes; the transforms below put the INK on that line,
   because the box bottom and the ink bottom are not the same place. */
/* STACKED, like the masthead - customer markup 4 Sep 2026, No.5, drawn with
 * its own reference sample and the note "please match the balance of the
 * character sizes too".
 *
 *      OODASHIP(c)
 *       kana
 *
 * Same grid as .od-wordmark, and for the same reason: the kana shares column 1
 * with the wordmark so it centres on OODASHIP alone rather than on
 * OODASHIP+(c). See that rule for the full argument.
 *
 * The (c) RATIO here is not the masthead's, and that is deliberate on the
 * customer's part rather than an inconsistency to tidy up. Against the
 * wordmark's ink height H (60.75px, the SVG as rendered):
 *
 *                       login (this)   masthead
 *     (c) height           0.239 H      0.414 H
 *     kana width           0.615 W      0.613 W    <- matched, see below
 *
 * The (c) is optical sizing: this lockup is drawn nearly twice the size, so the
 * small elements do not need to be held up, and 0.239 matches the customer's own
 * hero artwork to within 0.01, which is the cross-check that it is the intended
 * figure.
 *
 * The KANA no longer follows that logic, and this is the one place the customer
 * sample was overridden. Their login sample drew it at 0.152 H / 0.239 W, which
 * is what shipped first and came back as too small to read; it was raised on
 * request to sit at the masthead's proportion instead. So the two lockups now
 * agree about the kana and differ about the (c). See .od-login__kana.
 *
 * The old font-size: clamp(2.5rem, 7vw, 4.5rem) is gone with the flex. Nothing
 * inherited from it - the art is sized in px, the mark and kana set their own -
 * so it had been dead for as long as those three rules have looked like this.
 */
/* grid, NOT inline-grid - the masthead's lockup is inline because it sits in a
   row beside the build hash and the (yuubikata) link, but this one is a
   block-level heading that has to CENTRE on the page. An inline-grid is only as
   wide as its content, so justify-content had no free space to distribute and
   the lockup sat against the left edge of the 700px column instead of in the
   middle of it. Block-level grid fills the column, and justify-content then
   centres the two tracks inside it. */
.od-login__logo {
    display: grid;
    grid-template-columns: auto auto;
    justify-content: center;
    min-width: 0;
    margin-bottom: var(--od-s5);
    line-height: 1;
    color: var(--od-text);
}

/* Unlike the masthead lockup, this one really is vector art in the Figma - a
   247x60 path with the grey gradient - so it stays an SVG.

   Its viewBox is tight to the ink (checked: the rendered ink's top row is the
   image box's top row), which is what lets the (c) below align to the box. */
.od-login__art {
    grid-column: 1;
    grid-row: 1;
    display: block;
    width: min(100%, 247px);
    height: auto;
}

/* Meiryo Bold, the same face as the kana beside it - it had been inheriting the
 * body stack, which picks a different (c) glyph. 29.2px is 0.239 of the
 * wordmark's 60.75px ink height.
 *
 * This used to be 31px sitting on the wordmark's LOWER edge, with a +0.226em
 * nudge to put its ink there. The customer has now asked for the top, so the
 * nudge changes sign and size: align-self: start puts the box at the top, and
 * -0.225em (6.57px at 29.2px) is the measured distance from this element's box
 * top to its ink top, which is what actually lands the ink flush.
 *
 * margin-left rather than a column-gap: the 3.95px (0.065 H) the customer draws
 * is between the two INK edges, and the (c) carries a left side bearing.
 */
.od-login__mark {
    grid-column: 2;
    grid-row: 1;
    align-self: start;
    margin-left: 1.79px;
    font-family: var(--od-font-kana);
    font-size: 29.2px;
    font-weight: 700;
    line-height: 1;
    transform: translateY(-0.225em);
}

/* 24.83px, which matches the MASTHEAD's kana proportion rather than the 0.152 H
 * the customer's own login sample drew. Their sample was followed first and the
 * result was too small to read at 9.84px, so this was raised on request to sit
 * at the same proportion as the header's kana:
 *
 *     kana ink width / wordmark ink width    masthead 0.613, here 0.615
 *
 * Width is the ratio that was matched, not height, and the reason is that this
 * kana is BOLD while the masthead's is regular. Meiryo's bold ink is 0.952em
 * tall against the regular's 0.927em, so at a matched width this one measures
 * 0.389 H against the masthead's 0.378 - a weight difference, not a size one,
 * and chasing the height ratio instead would have made it visibly narrower than
 * the header's.
 *
 * It spans from about 60% through the second O to just past the H, which is
 * exactly what the header's kana spans. Worth knowing because the request was
 * phrased as "the second O to the I": that is a WIDER span (0.72 of the
 * wordmark) than the header actually uses, and matching the header won.
 *
 * margin-top is a plain px because grid row-gap cannot be negative elsewhere and
 * this file keeps the two consistent; 12.66px is what lands the kana's ink
 * 11.91px (0.196 H) below the wordmark's, which is the gap the customer drew and
 * is unchanged. It is re-derived whenever the font-size moves, because the box
 * grows with it - the old value for the 9.84px kana was 12.53px.
 *
 * text-indent cancels the trailing letter-space, so the ink centres in the box
 * rather than sitting half a space to the left of centre.
 */
.od-login__kana {
    grid-column: 1;
    grid-row: 2;
    justify-self: center;
    margin-top: 12.66px;
    font-family: var(--od-font-kana);
    font-size: 24.83px;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0.0516em;
    text-indent: 0.0516em;
    color: var(--od-text);
}

/* 700x530 panel with 50px side gutters and ~58px of top padding, measured from
   the field and label positions in 1:8702. */
.od-login__panel {
    background: var(--od-cream);
    padding: 58px 50px 48px;
}

/* Login fields are 600x60 with a 5px radius and the grey-soft border. Scoped to
   the panel rather than pushed into the global field styles: the round
   decisions form (1:13973) uses 380x80 fields with square corners, so the two
   pages genuinely differ. */
/* The one place the design really does use #333 rather than black. */
.od-login__panel .od-label {
    font-size: var(--od-fs-22);
    line-height: 27px;
    color: var(--od-text-soft);
}

/* min-height, not height: the shared field rule sets min-height: 80px, and a
   min-height beats a plain height, so overriding with height alone would leave
   these fields 80px tall. */
.od-login__panel .od-field input[type="text"],
.od-login__panel .od-field input[type="password"] {
    min-height: 60px;
    padding: var(--od-s3) var(--od-s4);
    font-size: var(--od-fs-18);
    border: 1px solid var(--od-grey-soft);
    border-radius: 5px;
}

.od-login__submit {
    display: flex;
    justify-content: center;
    margin-top: var(--od-s6);
}

/* 300x50 with 16px type - smaller than the 360x74 primary buttons used on the
   dashboard and decisions screens. */
.od-login__submit .od-btn {
    /* Size comes from .od-btn--compact on the button itself. */
    width: auto;
}

.od-login__help {
    margin-top: var(--od-s5);
    margin-bottom: 0;
    font-size: var(--od-fs-16);
    line-height: 28px;
    color: var(--od-text-strong);
    text-align: center;
}

/* Checkbox row: control and its label on one line. */
.od-check {
    display: flex;
    align-items: center;
    gap: var(--od-s2);
    font-weight: 700;
    cursor: pointer;
}

/* 24px square with a 3px radius on the soft surface (1:14020, 1:14021).
 *
 * background-color, NOT the background shorthand. Bootstrap draws the tick as a
 * background-image on :checked and sets appearance:none, so the shorthand reset
 * that image to none and the box stayed empty however many times you clicked it.
 * The state did toggle and submitted correctly - there was simply no way to see
 * it, on the two controls that decide R&D and BD investment.
 *
 * The :checked rule below is scoped to .od-check for the same reason: bare
 * .form-check-input:checked is (0,2,0) and loses to this selector's (0,2,1), so
 * Bootstrap's own checked colour never applied either.
 *
 * accent-color was here too and did nothing - it only styles a native control,
 * and Bootstrap has already replaced this one with appearance:none.
 */
.od-check input[type="checkbox"] {
    width: 24px;
    height: 24px;
    background-color: var(--od-surface-soft);
    border: 1px solid var(--od-grey-soft);
    border-radius: var(--od-r-chip);
    cursor: pointer;
}

.od-check input[type="checkbox"]:checked {
    background-color: var(--od-navy);
    border-color: var(--od-navy);
}

/* Invalid state. Defined here rather than inherited from Bootstrap because the
   decision form's JS toggles .is-invalid and the feedback must show either way. */
.is-invalid {
    border-color: var(--od-red) !important;
}

.invalid-feedback {
    display: none;
    margin-top: var(--od-s2);
    font-size: var(--od-fs-14);
    color: var(--od-red);
}

.is-invalid ~ .invalid-feedback,
.od-field .is-invalid ~ .invalid-feedback {
    display: block;
}

.od-quote {
    margin: 0 0 var(--od-s4);
    padding-left: var(--od-s4);
    border-left: 4px solid var(--od-gold);
    color: var(--od-text);
}

/* Time-limit line in the rules modal: black label, oversized red value. */
/* The prep modal stacks a quiet acknowledgement line over a centred action
   (1:14695, 1:14731), rather than Bootstrap's right-aligned footer row. */
.od-modal-footer--stacked {
    flex-direction: column;
    align-items: center;
    gap: var(--od-s3);
}

.od-modal-ack {
    margin: 0;
    font-size: var(--od-fs-14);
    text-align: center;
    color: var(--od-text);
}

.od-timelimit {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--od-s3);
    margin: 0 0 var(--od-s4);
}

.od-timelimit__icon {
    font-size: 1.6rem;
    line-height: 1;
}

/* The time limit is the loudest thing in the product after the event card
   number: 37px for the label and 53px for the value (1:14699, 1:14703). Both
   are one-off sizes, so they stay literal, and both are clamped so the line
   still fits a phone. */
.od-timelimit__label {
    font-size: clamp(var(--od-fs-24), 5vw, 37px);
    font-weight: 800;
    color: var(--od-text-strong);
}

.od-timelimit__value {
    font-size: clamp(var(--od-fs-30), 7vw, 53px);
    font-weight: 800;
    color: var(--od-red);
}

/* 26px bold red on a 38px line, not body size (1:14707). */
.od-timelimit__note {
    margin: 0;
    text-align: center;
    font-size: var(--od-fs-26);
    line-height: 38px;
    font-weight: 700;
    color: var(--od-red);
}

/* --------------------------------------------------------------------------
   Modals
   Measured from the Figma frames (8, 11, 12): white surface inside a 3px
   #b42626 border, a triangle-free heading with the same navy accent bar the
   page headings use, hairline rules above and below the body, and a grey
   secondary next to a navy primary in the footer.

   Bootstrap's own bg-success/bg-primary/bg-info header bands are overridden
   here rather than edited out of the markup, because the notification modals
   are driven by JS that expects that structure.
   -------------------------------------------------------------------------- */

/* Dialogs are 1000px wide across all three measured modals - round complete
   (1:13148), round prep (1:14689) and delete game (1:9237) - against
   Bootstrap's ~500px default, which was wrapping two 200px actions onto
   separate lines. */
.modal-dialog {
    max-width: 1000px;
}

/* The scrim is a flat #5c5c5c multiplied over the page, not Bootstrap's black
   at half opacity (1:13916). Multiply keeps the cream surfaces warm underneath
   where a black wash greys them out.

   Single-frame evidence: only the team-dashboard popup draws its scrim as an
   explicit rectangle, so there is no second frame to corroborate against. */
.modal-backdrop,
.modal-backdrop.show {
    background-color: #5c5c5c;
    mix-blend-mode: multiply;
    opacity: 1;
}

/* 5px red border on a 10px radius (1:13148, 1:14689) - it was 3px at 6px. */
.modal-content {
    background: var(--od-white);
    border: 5px solid var(--od-red);
    border-radius: var(--od-r-card);
}

/* Modal body copy: a 26px bold lead over 24px regular, both on a 38px line and
   both black - the second line is not a muted subtitle (1:13154, 1:13158). */
.od-modal-lead {
    margin: 0 0 var(--od-s3);
    font-size: var(--od-fs-26);
    line-height: 38px;
    font-weight: 700;
    color: var(--od-text);
}

.od-modal-sub {
    margin: 0;
    font-size: var(--od-fs-24);
    line-height: 38px;
    color: var(--od-text);
}

.modal-header,
.modal-header.bg-success,
.modal-header.bg-primary,
.modal-header.bg-info,
.modal-header.bg-warning,
.modal-header.bg-danger,
.od-modal-header--gold,
.od-modal-header--danger {
    display: flex;
    align-items: center;
    gap: var(--od-s4);
    padding: var(--od-s5) var(--od-s6) var(--od-s4);
    background: var(--od-white) !important;
    color: var(--od-text-strong) !important;
    border-bottom: 1px solid var(--od-border-soft);
}

/* The heading inside a modal is the page heading, not a smaller variant: 26px
   beside a 10x50 navy bar on desktop (1:13918 / 1:13922) and identical at 360px
   on a phone (30:709 / 30:710). It had been 18px beside a 6px bar. */
.modal-title {
    display: flex;
    align-items: center;
    gap: var(--od-s4);
    margin: 0;
    font-size: var(--od-fs-26);
    font-weight: 800;
    color: var(--od-text-strong);
}

.modal-title::before {
    content: "";
    flex: 0 0 auto;
    width: 10px;
    height: 1.92em;
    background: var(--od-navy);
    border-radius: 1px;
}

.modal-body {
    padding: var(--od-s6);
    color: var(--od-text);
    text-align: center;
}

/* Callouts inside a modal are the softer blue, with navy copy. */
.modal-body .alert,
.modal-body .alert-info,
.modal-body .alert-success,
.modal-body .alert-warning {
    background: var(--od-info-soft) !important;
    border: 0 !important;
    border-radius: var(--od-r-input);
    color: var(--od-navy) !important;
    padding: var(--od-s4);
    margin-bottom: 0;
}

/* Secondary modal action: the frames use a warmer grey than the page buttons. */
.modal-footer .btn-secondary,
.modal-footer .od-btn--muted {
    background: var(--od-grey-soft);
    border-color: var(--od-grey-soft);
    color: var(--od-text-on-dark);
}

/* --------------------------------------------------------------------------
   Delete-game modal (1:9237)

   The one modal the design lays out as a document rather than a centred
   announcement, so it opts out of .modal-body's centring and out of the header
   and footer rules the notification modals carry - the frame draws neither.

   Vertical positions in the frame, measured from the dialog's top edge (it sits
   at y=505 and is 645 tall): warning strip 121, lead 192, list 223, prompt 364,
   field 436, actions 540. Those fall out of the margins below rather than being
   pinned, because the confirmation checkbox appears between the field and the
   actions once the name matches and has to be able to push the actions down.
   -------------------------------------------------------------------------- */

/* 45px of side padding is what leaves the frame's 900-wide content inside this
   1000-wide dialog: the 1000 is a border-box that includes the 5px red border
   on each side, so the padding works against 990, not 1000. At a symmetric 50
   the strip and the field come out 890. The frame's own insets read 45 left and
   55 right, so 45 is also the side it measures from. */
.od-modal--delete .modal-body {
    padding: var(--od-s5) 45px;
    text-align: left;
}

.od-modal--delete .modal-header,
.od-modal--delete .modal-footer {
    border: 0;
}

/* Left-aligned 18px bold red on #ffd1d1, 50px tall - not a centred banner
   (strip 1:9737, text 1:9738). */
.od-delete-warning {
    display: flex;
    align-items: center;
    min-height: 50px;
    margin: 0 0 var(--od-s5);
    padding: 0 18px;
    background: var(--od-red-bg);
    border-radius: var(--od-r-input);
    font-size: var(--od-fs-18);
    font-weight: 700;
    line-height: 40px;
    color: var(--od-red);
}

/* 20px bold on a 28px line (1:9720). */
.od-delete-lead {
    margin: 0 0 var(--od-s1);
    font-size: var(--od-fs-20);
    line-height: 28px;
    font-weight: 700;
    color: var(--od-text);
}

/* The four data lines are one 18px regular block on a 30px line (1:9728). The
   first carries no bullet and the rest take a katakana middle dot, which is the
   list mark the frame draws - not a disc. */
.od-delete-name,
.od-delete-list {
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: var(--od-fs-18);
    line-height: 30px;
    color: var(--od-text);
}

.od-delete-list {
    margin-bottom: var(--od-s5);
}

.od-delete-list li::before {
    content: "・";
}

/* 20px bold, two lines on a 28px line, with only the game name in red
   (1:9724). */
.od-delete-prompt {
    display: block;
    margin: 0 0 var(--od-s3);
    font-size: var(--od-fs-20);
    line-height: 28px;
    font-weight: 700;
    color: var(--od-text);
}

.od-delete-target {
    color: var(--od-red);
}

/* 900x60 on the soft surface with the standard border, at the card radius
   rather than the input one (1:9742). */
.od-modal--delete .od-delete-field .form-control {
    min-height: 60px;
    background: var(--od-surface-soft);
    border: 1px solid var(--od-border);
    border-radius: var(--od-r-card);
    font-size: var(--od-fs-18);
}

/* Centring and the 20px gap now come from the shared .modal-footer rule, which
   both measured frames agree on. Only the deeper bottom inset is specific to
   this dialog: 55px from the actions to the modal's bottom edge (1:9715). */
.od-modal--delete .modal-footer {
    padding-bottom: 55px;
}

/* The frames show a ring-and-tick mark rather than a platform emoji. */
.od-modal-mark {
    display: block;
    width: 82px;
    height: 82px;
    margin: 0 auto var(--od-s5);
}

/* Modal actions are centred as a pair, not pushed to the right edge the way
   Bootstrap does it. Two independent frames put the pair's centre on the
   dialog's: the delete modal at 790/1010 in a 500-1500 dialog (1:9732, 1:9715)
   and the new-round popup at 760/980 (1:13941, 1:13936). 20px apart in both. */
.modal-footer {
    justify-content: center;
    padding: var(--od-s4) var(--od-s6) var(--od-s5);
    border-top: 1px solid var(--od-border-soft);
    gap: 20px;
}

/* Bootstrap puts `margin: .25rem` on every direct child of .modal-footer, which
   lands on top of the gap above and spaces the actions 28px apart instead of
   the frame's 20px. The gap property alone cannot win against it. */
.modal-footer > * {
    margin: 0;
}

/* Bootstrap's close button is a white-on-colour X in the default markup; on a
   white header it needs to be dark. */
.modal-header .btn-close,
.modal-header .btn-close-white {
    filter: none;
    opacity: 0.5;
}

.modal-header .btn-close:hover {
    opacity: 0.9;
}

/* --------------------------------------------------------------------------
   8. Tables
   -------------------------------------------------------------------------- */

.od-table-wrap {
    width: 100%;
    overflow-x: auto;
}

.od-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--od-fs-16);
}

/* Tables are 18px bold headers over 22px bold cells - every value in a data
   row is bold in the design, not just the labels. Three frames agree: the team
   dashboard's round history (1:13196), game master round results (1:11200) and
   the team links table (1:11069). */
.od-table th {
    padding: var(--od-s3) var(--od-s4);
    font-size: var(--od-fs-18);
    font-weight: 800;
    text-align: left;
    color: var(--od-text);
    border-bottom: 3px solid var(--od-rule);
    white-space: nowrap;
}

.od-table td {
    padding: var(--od-s4);
    font-size: var(--od-fs-22);
    font-weight: 700;
    border-bottom: 1px solid var(--od-border-soft);
    vertical-align: middle;
}

/* The all-decisions matrix (1:11987) is a different table from the others: a
   filled #4d4d4d header with white 22px type instead of the light header and
   dark underline, and cream rows separating the categories. Its data cells are
   regular weight, not the bold the summary tables use. */
.od-table--matrix thead th {
    background: var(--od-table-head);
    color: var(--od-text-on-dark);
    font-size: var(--od-fs-22);
    border-bottom: 0;
}

.od-table--matrix td {
    font-weight: 400;
}

/* Category rows: 20px bold on cream, spanning the width. */
.od-table--matrix tbody tr.od-rowgroup td,
.od-table--matrix tbody tr.od-rowgroup th {
    background: var(--od-cream);
    font-size: var(--od-fs-20);
    font-weight: 700;
}

.od-table tbody tr:last-child td {
    border-bottom: none;
}

.od-table--striped tbody tr:nth-child(odd) {
    background: var(--od-cream-alt);
}

.od-table__num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* The header has to follow its column. `.od-table th` sets text-align: left at
   (0,1,1), which outranks .od-table__num at (0,1,0), so every numeric heading
   sat at the left of a column whose figures were flush right - 販売製品数 /
   収益 / 費用 / 純利益 / 現金 on the round-results table, each one adrift of the
   number under it.
 *
 * Note the frames align these columns the other way: in 1:11200, 1:12301 and
 * 1:14759 the heading and the figures share the same LEFT edge (現金 at x=891
 * over 800 and 750 at x=891). Right is kept because figures that are compared
 * down a column - cash across teams - read better flush right, which is the one
 * place this file departs from the frames on purpose. Flipping it is two lines
 * if the design is meant literally. */
.od-table th.od-table__num {
    text-align: right;
}

.od-pos { color: var(--od-green); font-weight: 700; }
.od-neg { color: var(--od-red);   font-weight: 700; }

/* --------------------------------------------------------------------------
   8b. Tags, ranks, meta lists, progress
   -------------------------------------------------------------------------- */

.od-tags {
    display: flex;
    flex-wrap: wrap;
    gap: var(--od-s2);
}

/* A tag row standing on its own under a page title, rather than sitting beside
   the heading inside .od-heading-row. It needs to clear whatever follows it -
   the event card screen ran the tags straight into the top of the cream
   submitted band. The heading-row copy gets its spacing from the row and must
   not take this. */
.od-tags--row {
    margin-bottom: var(--od-s4);
}

/* Status tags. The Figma uses a filled red rectangle for live/current state and
   a white outlined chip for finished/inactive state - not the pill-shaped
   coloured badges Bootstrap defaults to. */
.od-tag {
    display: inline-block;
    padding: var(--od-s2) var(--od-s4);
    background: var(--od-red);
    color: var(--od-text-on-dark);
    border: 2px solid var(--od-red);
    border-radius: var(--od-r-tag);
    font-size: var(--od-fs-14);
    font-weight: 700;
    line-height: 1.3;
    white-space: nowrap;
}

/* The game/status strip under the team name: the game's own tab, then the two
   game states with the current one filled and the other outlined. 173x50 and
   100x50 at a 6px radius with 20px type on desktop (1:13689, 1:13694, 1:13695);
   41px at a 4px radius with 18px on mobile (14:1832-14:1837). */
.od-gametabs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--od-s3);
    margin-top: var(--od-s3);
}

/* The strip standing on its own between the title and the page's first block,
   as on the leaderboard, rather than tucked beside the heading inside
   .od-heading-row. Same reason as .od-tags--row: the tabs ran straight into the
   top of the results table. The heading-row copies must not take this, or the
   strip stops sitting level with the title. */
.od-gametabs--row {
    margin-bottom: var(--od-s4);
}

.od-gametab {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 50px;
    padding: 0 var(--od-s5);
    background: var(--od-red);
    color: var(--od-text-on-dark);
    border: 2px solid var(--od-red);
    border-radius: 6px;
    font-size: var(--od-fs-20);
    font-weight: 700;
    white-space: nowrap;
}

.od-gametab--muted {
    background: transparent;
    color: var(--od-grey-soft);
    border-color: var(--od-grey-soft);
}

/* Finished / not-currently-active state. */
.od-tag--inactive {
    background: var(--od-white);
    color: var(--od-text-faint);
    border-color: var(--od-border);
}

.od-tag--navy  { background: var(--od-navy);  color: var(--od-text-on-dark); border-color: var(--od-navy); }
.od-tag--green { background: var(--od-green); color: var(--od-text-on-dark); border-color: var(--od-green); }
.od-tag--gold  { background: var(--od-gold);  color: var(--od-navy);         border-color: var(--od-gold); }
.od-tag--grey  { background: var(--od-grey);  color: var(--od-text-on-dark); border-color: var(--od-grey); }
.od-tag--info  { background: var(--od-info-bg); color: var(--od-navy);       border-color: var(--od-info-bg); }
.od-tag--red   { background: var(--od-red);   color: var(--od-text-on-dark); border-color: var(--od-red); }

/* Compact variant, and the contexts that always want it: tags inside dense
   tables, meter headers and stat blocks would otherwise dominate the row. */
.od-tag--sm,
.od-table .od-tag,
.od-meter__head .od-tag,
.od-stat .od-tag,
.od-card__head .od-tag {
    padding: 1px var(--od-s2);
    font-size: var(--od-fs-12);
    border-width: 1px;
}

/* Full-bleed cream band used for status / completion notices. */
/* Sits inside the content column, not full bleed. The frames draw it the same
   width as the tables above and below it on desktop (1:12301) and inset to the
   content column on mobile (14:2159, 14:2213); it used to run edge to edge,
   which made it the widest thing on the page. */
.od-band {
    margin: var(--od-s6) 0;
    padding: var(--od-s5) var(--od-s6);
    background: var(--od-cream);
}

.od-band > :last-child {
    margin-bottom: 0;
}

.od-band .od-subheading {
    margin-top: 0;
}

/* Circled rank numeral, as used in the results tables. */
/* 18px bold, matching the circled numeral the leaderboard sets (14:2147,
   14:2183). See .od-step::before for why this stays a drawn ring. */
/* The ring follows the text colour rather than being fixed to the body ink, so
   the numeral and the circle around it always match. On the decisions matrix
   the rank row sits in the dark table head, where the digit is white and a
   var(--od-text) ring was drawing an all-but-invisible black circle around it.
   Everywhere else currentColor resolves to --od-text, so nothing moves. */
.od-rank {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.6em;
    height: 1.6em;
    border: 1px solid currentColor;
    border-radius: 50%;
    font-weight: 700;
    font-size: var(--od-fs-18);
}

.od-row--winner {
    background: var(--od-amber-bg);
}

.od-muted {
    color: var(--od-text-muted);
}

.od-card__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--od-s3);
    margin-bottom: var(--od-s4);
}

.od-card__head .od-card__title {
    margin-bottom: 0;
}

/* Detail lists are 18px on a 30px line (1:14253, 1:14279); they were 14px. The
   team summary in the sidebar is a step larger at 20px on 34px, so it is
   scoped below rather than sharing this size. */
.od-meta {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--od-s1) var(--od-s3);
    margin: 0 0 var(--od-s4);
    font-size: var(--od-fs-18);
    line-height: 30px;
}

.od-split--reverse aside .od-meta {
    font-size: var(--od-fs-20);
    line-height: 34px;
}

.od-meta dt {
    font-weight: 700;
    /* Black in the frames, like the values beside them - not a muted label. */
    color: var(--od-text);
}

.od-meta dd {
    margin: 0;
}

/* 32px tall on a #acaba3 track. Two independent bars on the dashboard frame
   agree on this: the round-progress bar (1:13524) and the investment-effect
   bars (1:13469, 1:13470), all 260x32 with a pill radius. It was 10px. */
.od-progress {
    display: flex;
    align-items: center;
    height: 32px;
    margin-bottom: var(--od-s4);
    background: var(--od-grey-soft);
    border-radius: var(--od-r-pill);
    overflow: hidden;
}

.od-progress__bar {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    background: var(--od-navy);
    border-radius: var(--od-r-pill);
    /* The round bar carries its label inside the fill, so the fill must not
       shrink below the text even at a low percentage. */
    min-width: fit-content;
}

/* The round-progress fill is red in the Figma (1:13540), unlike the navy
   investment bars. */
.od-progress__bar--red {
    background: var(--od-red);
}

.od-progress__label {
    padding: 0 var(--od-s3);
    font-size: var(--od-fs-14);
    color: var(--od-text-on-dark);
    white-space: nowrap;
}

.od-progress__bar--green { background: var(--od-green); }
.od-progress__bar--gold  { background: var(--od-gold); }

/* Game-state legend under the progress meter: three flat 300x50 bands, square
   corners, 14px regular black inset 18px (1:13864-1:13875, confirmed against
   the non-popup dashboard 1:13196).

   Not .od-alert--*, which is what these used to be. The legend needs blue for
   "round active" and grey for "game completed", while the page-level notice
   band is blue and calls itself info - one class cannot be both, and mapping
   the legend onto alert semantics is what forced the clash. */
.od-gamestate {
    display: block;
    padding: 0 18px;
    margin-bottom: var(--od-s3);
    font-size: var(--od-fs-14);
    line-height: 50px;
    color: var(--od-text);
}

.od-gamestate--active   { background: var(--od-notice-bg); }
.od-gamestate--event    { background: var(--od-yellow-band); }
.od-gamestate--complete { background: var(--od-grey-soft); }

/* Label row above a progress meter: name on the left, state tag on the right. */
.od-meter {
    margin-bottom: var(--od-s5);
}

.od-meter:last-child {
    margin-bottom: 0;
}

.od-meter__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--od-s2);
    margin-bottom: var(--od-s2);
    font-size: var(--od-fs-14);
}

.od-minihead {
    margin: 0 0 var(--od-s3);
    font-size: var(--od-fs-16);
    font-weight: 800;
    color: var(--od-text);
}

/* The line the frames close a card with, above its actions (1:8753, and
   25:1090 / 25:1103 for the card component).
 *
 * It was --od-border-soft (#e8e7e0) against a card background of #eae9e2 -
 * two shades apart, so the rule was in the markup, in the box model, and
 * invisible on screen. Every game card on the dashboard looked as though it had
 * no divider at all.
 *
 * --od-border, measured rather than picked: sampling the frame gives the line
 * at rgb(195,193,182) on a rgb(244,243,226) card, but that render is downscaled
 * 2000->1400, so the ink is spread over two rows. Summing the deviation back to
 * one source pixel gives about #a2a196, which is --od-border (#acaba3) to
 * within a shade. */
.od-rule {
    margin: var(--od-s5) 0;
    border: 0;
    border-top: 1px solid var(--od-border);
}


/* Sidebar-first ordering on desktop, used by the team dashboard: the narrow
   status column comes first in the markup, the wide content column second.
   Both classes are on the same element, so this needs the extra specificity to
   beat the base .od-split rule regardless of source order. */
.od-split.od-split--reverse {
    grid-template-columns: 300px minmax(0, 1fr);
}

/* Same shape as --reverse but with a tighter sidebar, for pages whose content
   column carries a form or wide table that needs the room. */
.od-split.od-split--narrow-aside {
    grid-template-columns: minmax(200px, 1fr) 3fr;
}

/* Two equal columns that collapse to one on narrow screens. */
.od-duo {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 0 var(--od-s6);
}

/* Cost breakdown. Each figure sits in its own white box under a small label -
   156x75 with a 34px figure on desktop (1:14314, 1:14324), 60x37 with 18px on
   mobile (30:837). It reads as a row of tiles rather than a list, which is why
   this replaced the definition list the page had. */
.od-costgrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(156px, 1fr));
    gap: var(--od-s5) var(--od-s4);
    margin-bottom: var(--od-s5);
}

.od-costgrid__item {
    display: flex;
    flex-direction: column;
    gap: var(--od-s2);
    min-width: 0;
}

.od-costgrid__label {
    font-size: var(--od-fs-16);
    color: var(--od-text);
}

.od-costgrid__value {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 75px;
    background: var(--od-white);
    font-size: 34px;
    font-weight: 700;
    color: var(--od-text);
}

/* The total is separated by a rule and set as its own large figure, underlined
   on mobile (1:14309, 1:14332, 30:871-30:874). */
/* The decisions form draws the same grid smaller than the results screen does:
   roughly 93x41 boxes with a 26px figure under a 14px label (1:13973), against
   156x75 with a 34px figure once the round has been processed (1:14314). */
/* Five across, so the nine costs wrap 5 + 4 as the frame lays them out rather
   than running out in a single line. The value box is pushed to the bottom of
   its cell so a label that wraps to two lines does not drop its box out of line
   with the rest of the row. */
.od-costgrid--compact {
    grid-template-columns: repeat(5, 1fr);
}

.od-costgrid--compact .od-costgrid__item {
    justify-content: flex-end;
}

.od-costgrid--compact .od-costgrid__label {
    font-size: var(--od-fs-14);
}

.od-costgrid--compact .od-costgrid__value {
    min-height: 41px;
    font-size: var(--od-fs-26);
}

.od-costtotal {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--od-s2);
    padding-top: var(--od-s5);
    border-top: 1px solid var(--od-grey-soft);
}

/* "/ 利用可能：3000" trails the total at body size rather than matching it. */
.od-costtotal__available {
    font-size: var(--od-fs-16);
    font-weight: 400;
}

.od-costtotal__label {
    font-size: var(--od-fs-18);
    font-weight: 700;
}

.od-costtotal__value {
    font-size: var(--od-fs-40);
    font-weight: 700;
    line-height: 1.2;
}

.od-duo > * {
    min-width: 0;
}

.od-duo .od-subheading:first-child,
.od-duo > div > .od-subheading:first-child {
    margin-top: var(--od-s5);
}

.od-actions--center {
    justify-content: center;
}

/* --------------------------------------------------------------------------
   8c. Long-form documentation (instruction pages)
   -------------------------------------------------------------------------- */

/* No measure cap: the guide sections are the full 1200px content width in the
   frames (1:9769 spans exactly the same x range as the tab strip 1:10060). The
   900px centred column left the panels visibly narrower than the tabs above
   them. */
.od-doc {
    margin: 0 auto;
}

/* Each guide section is one bordered panel with its title inside it - there is
   no filled header bar anywhere in either manual (pc 1:9769/1:9770/1:9771,
   sp 9:125).

   The border differs by breakpoint: #acaba3 on desktop, matching the product
   screens (1:9769), and the lighter #e8e7e0 on mobile (9:125, 13:854, 13:1012).
   The earlier note here had the soft border applying everywhere, which was the
   sp value read before the pc frames were. */
.od-doc-card {
    margin-bottom: var(--od-s6);
    background: var(--od-white);
    border: 1px solid var(--od-border);
    border-radius: var(--od-r-card);
    overflow: hidden;
}

.od-doc-card__head {
    padding: var(--od-s4) var(--od-s5);
    background: var(--od-cream);
    font-weight: 800;
    border-bottom: 1px solid var(--od-border);
}

.od-doc-card__head > * {
    margin: 0;
    font-size: var(--od-fs-18);
    color: inherit;
}

/* The five coloured head variants that used to sit here (navy, green, info,
   gold, danger) are gone. No template referenced any of them, and both manuals
   draw every section the same way - an outlined panel with a green triangle
   title inside - so a filled header bar in five colours had nothing to render
   from and nothing in the design to match. */

/* Accent borders used by the event-card explainer cards. */
/* The coloured border variants are gone. The manual distinguishes its card
   types by title colour alone - green positive, red negative, navy conditional
   (13:1013, 13:1017, 13:1021) - on a neutral #e8e7e0 border, so a coloured
   border was reinforcement the design does not use. Removed rather than left
   unused once the templates stopped referencing them. */

.od-card-text {
    margin-bottom: var(--od-s2);
    font-size: var(--od-fs-14);
}

/* The three event-card explainer cards in the player guide (1:10806-1:10808).
   Measured off 1:10081: a 25px title, 18px body on a 25px line, a 20px radius
   and a 1px #707070 hairline - darker than the #acaba3 the rest of the manual
   uses, and the only place either guide draws that grey, so it is written here
   rather than promoted to a token.
 *
 * The titles' colours were reaching the page as black. `.od-doc-card__body h6`
 * sets `color`, and at (0,1,1) it beat .od-pos / .od-neg / .od-warn at (0,1,0),
 * so the green, red and navy the frame gives these three were overridden on
 * every render. The :not(.od-card__title) on that rule is what lets them
 * through; the classes themselves were right all along. */
.od-doc-card--event-type {
    border-color: #707070;
    border-radius: 20px;
}

.od-doc-card--event-type .od-card__title {
    font-size: 25px;
}

.od-doc-card--event-type .od-card-text {
    font-size: var(--od-fs-18);
    line-height: 25px;
}

/* Amber ink is not in the Figma palette; warnings carry meaning via fill. */
/* Navy, matching the conditional event card title in the manual (13:1021),
   which sits beside the green positive and red negative ones. Its only use. */
.od-warn { color: var(--od-navy); font-weight: 700; }

/* Resource cards in the manual (13:854): a centred icon over a 16px label, a
   22px bold figure and a 13px grey note, on a white card with the manual's
   #e8e7e0 border. Replaces raw Bootstrap display-4 / fs-4 / small text-muted. */
.od-resource {
    text-align: center;
}

/* The frame draws these as vector illustrations, not emoji: a stack of ¥ coins
   (1:10935), a factory (1:10942) and a worker (1:10938), exported to
   resource-*.svg. font-size was sizing an emoji glyph and does nothing to an img.

   One height for all three rather than the frame's natural sizes (93.5 / 62.3 /
   120.9px). The frame bottom-aligns them so the labels beneath line up at a
   common baseline; a single height achieves the same alignment without a wrapper
   element, and each icon keeps its own aspect ratio. */
.od-resource__icon {
    display: block;
    height: 76px;
    width: auto;
    margin: 0 auto var(--od-s3);
}

/* Black, not #333/#989898. The frame draws the whole resource card in black -
   label, figure and the small line under it alike (player guide 1:10081,
   初期リソース). These three are only used by the manual, so the change is
   contained to it. */
.od-resource__label {
    margin: 0 0 var(--od-s2);
    font-size: var(--od-fs-16);
    color: var(--od-text);
}

.od-resource__value {
    margin: 0 0 var(--od-s3);
    font-size: var(--od-fs-22);
    font-weight: 700;
    color: var(--od-text);
}

.od-resource__desc {
    margin: 0;
    font-size: 13px;
    color: var(--od-text);
}

/* The manual's own type scale, measured off the pc frames (1:9761, 1:10081).
   Every text node in both guides is one of four sizes:

       26px / 36px line   numbered sub-title      "1. ログイン", "1. ゲーム状態"
       22px / 46px box    bold sub-heading        "ゲーム開始", "チームの監視"
       18px / 30px line   body copy and lists
       26px               green ▼ section title   (.od-doc-card__head--section)

   Ours ran the whole manual at the product's 16px body with Bootstrap's h5
   (20px) on top, so the four levels collapsed into two and the sentence under a
   numbered heading came out smaller than the frame draws it - the "font size of
   the lines after the numbered headers is too small" report.

   The two frames disagree about the numbered level: the game master guide sets
   it at 26px and the player guide at 22px. 26 here for both, so a numbered step
   heading looks the same wherever it appears; noted in
   docs/figma-conversion-notes.md rather than split into two rules. */
.od-doc-card__body {
    padding: var(--od-s5);
    font-size: var(--od-fs-18);
    line-height: 30px;
}

/* The クイックリンク row at the foot of each manual. It was a Bootstrap
   .btn-group, which butts the buttons edge to edge - three outlined links with
   no gap read as one bar divided by rules rather than as three buttons.

   Not .od-actions--center, which on mobile turns into column-reverse: that is
   right for a cancel/submit pair, but it would print these links bottom-up. */
.od-doc-links {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--od-s3);
    margin-top: var(--od-s5);
}

/* Sub-titles and their lead-in sentence are bold; the detail under them is not.
   The frames set every section up the same way - a bold numbered sub-title, one
   bold sentence introducing the step, then regular steps (the ログイン section
   is the clearest example). Ours had all three at the same weight, which is the
   "bold vs normal highlighting is missing" report: with nothing emphasised, the
   sub-title did not read as a heading at all.

   Structural rather than per-heading: the pattern holds for every h5 in both
   guides, so a rule keeps new sections consistent by default instead of relying
   on whoever adds one to remember the <strong>. */
/* The gaps are the frame's, not a guess. In 1:9761 the ログイン block runs
   heading box 517.4-553.4, lead-in box 576.4-622.4, first list line from 626 -
   so ~40px of air between the heading's ink and the lead-in's, and ~22px
   between the lead-in's and the list's. Subtracting the half-leading each line
   box carries at these sizes leaves the two margins below.

   The templates used to hang a Bootstrap `mt-4` on every heading after the
   first, which is 24px !important and would win over this; those are removed,
   so the rhythm lives here and a new section gets it for free. */
.od-doc-card__body h5 {
    margin: var(--od-s6) 0 30px;
    font-size: var(--od-fs-26);
    line-height: 36px;
    font-weight: 700;
}

.od-doc-card__body > h5:first-child {
    margin-top: 0;
}

.od-doc-card__body h5 + p {
    margin-bottom: 10px;
    font-size: var(--od-fs-22);
    line-height: 33px;
    font-weight: 700;
}

/* Manual body copy is black throughout the frames - there is no muted grey
   anywhere in either guide. .od-help is shared with the product screens, where
   grey helper text is correct, so this is scoped to the manual rather than
   changed at the source. */
.od-doc-card__body .od-help,
.od-doc-card__body .od-help strong {
    color: var(--od-text);
}

/* A third heading level inside a section: ゲーム開始 / 進捗の監視 / ラウンド処理
   / ラウンド進行 and their equivalents. The frames set these as plain bold body
   text, and - unlike an h5 - the paragraph under them stays regular, so this is
   its own class rather than another heading tag that the h5 rules would catch. */
/* A card whose first line is a lead-in rather than a heading - the event card
   section (1:10758) opens straight into one. The frame leaves no gap between
   the green section title and it. */
.od-doc-card__body > .od-doc-sub:first-child {
    margin-top: 0;
}

.od-doc-sub {
    margin: var(--od-s5) 0 var(--od-s2);
    font-size: var(--od-fs-22);
    line-height: 33px;
    font-weight: 700;
    color: var(--od-text);
}

/* h6 is the numbered category heading in the player guide (1. 生産と価格設定,
   2. 増設, ...). Bootstrap renders h6 at body size and weight 500, so these read
   as ordinary paragraphs; the frame sets them bold and a step up from the body
   (1:10081). */
/* :not(.od-card__title) - the event-card explainer cards in the player guide
   use h6 for their own titles, and they carry .od-card__title's own size and
   margins. Without this they would pick up a 24px top margin inside their
   card head. */
.od-doc-card__body h6:not(.od-card__title) {
    margin: var(--od-s5) 0 var(--od-s2);
    font-size: var(--od-fs-22);
    line-height: 33px;
    font-weight: 700;
    color: var(--od-text);
}

/* Manual lists are not browser lists. The frames run every step flush with the
   body text and write its own marker inline - "1.すべてのチームが..." with no
   space after the dot, and "・各チームの現在の現金" for unordered ones. Default
   ol/ul markers sit in a hanging indent with a period-and-space or a bullet
   glyph, which is the reported difference in "bullet glyphs and indentation".

   The counter is on the list rather than hardcoded in the markup so the numbers
   stay right when a step is inserted.

   Applied to bare ol/ul inside a manual section as well as to the explicit
   classes, because the frames use these two forms for every list in both
   guides - so a section written without the class still comes out right. */
.od-doc-card__body ol,
.od-doc-card__body ul,
.od-doc-steps,
.od-doc-bullets {
    margin: 0 0 var(--od-s4);
    padding: 0;
    list-style: none;
    line-height: 30px;
}

.od-doc-card__body ol,
.od-doc-steps {
    counter-reset: od-step;
}

.od-doc-card__body ol > li,
.od-doc-steps > li {
    counter-increment: od-step;
}

.od-doc-card__body ol > li::before,
.od-doc-steps > li::before {
    content: counter(od-step) ".";
}

.od-doc-card__body ul > li::before,
.od-doc-bullets > li::before {
    content: "\30fb";
}

.od-doc-card__body > :last-child {
    margin-bottom: 0;
}

/* Manual callouts are one colour. Every 重要 / 注意 / 戦略 / ヒント bar in both
   guides is #d6e1ff with 18px bold black type on a 30px line - including the
   security warning, which is the one most likely to have been red (1:9895,
   1:10030, 1:10220, 1:10707, text 1:10222).

   Ours were colour-coded amber/red/green off Bootstrap's alert semantics. The
   product screens keep those variants; only the manual flattens, so this is a
   separate class rather than a change to .od-alert.

   Mobile size is not measured - the sp manual shows the same blue box but no
   frame gives its type scale, so it inherits. */
.od-doc-note {
    padding: var(--od-s3) 20px;
    margin-bottom: var(--od-s5);
    background: var(--od-notice-bg);
    color: var(--od-text);
    font-size: var(--od-fs-18);
    font-weight: 700;
    line-height: 30px;
}

.od-doc-note > :last-child {
    margin-bottom: 0;
}

/* The one manual section the frames do not flatten: ゲームの削除 is drawn inside
   a red box with a red heading, and its 重要な注意事項 callout is red on pink
   rather than the shared blue. Deleting a game is the only irreversible thing
   in the product, so the manual marks it the way the product does - the same
   #b42626 as the delete modal's border (1:9237). */
.od-doc-card--danger {
    border: 2px solid var(--od-red);
}

.od-doc-card--danger .od-doc-card__head--section > *,
.od-doc-card--danger .od-doc-card__head > * {
    color: var(--od-red);
}

.od-doc-note--danger {
    background: var(--od-red-bg);
    color: var(--od-red);
}

/* Natural size, never stretched. `width: 100%` made every screenshot fill its
   column whatever it had been captured at, so a 700px-wide shot was blown up
   1.86x against a 1300px one and the manual read as though its pictures came
   from different machines. With `width: auto` each renders 1:1, and because
   scripts/capture_instructions.py now captures everything at a single width
   (CAPTURE_WIDTH, chosen to fit this column), 1:1 is the same zoom for all of
   them. max-width keeps a wide one from overflowing on a narrow window. */
.od-doc-img {
    display: block;
    width: auto;
    max-width: 100%;
    height: auto;
    border: 1px solid var(--od-border);
    border-radius: var(--od-r-input);
    box-shadow: var(--od-shadow);
}

/* Definition tables in the manual (ゲーム状態, 投資オプション). Both were
   `dl.row` grids, which draw no rules at all, so the term and its definition
   were held together only by proximity - the reported "should be a table".
   Ruled on both axes: the label column is a heading, so it needs a vertical
   edge, not just the row lines.

   Fixed label column at 240px so the terms in both tables line up with each
   other; the description column takes the rest. */
.od-doc-table {
    width: 100%;
    margin: 0 0 var(--od-s5);
    border-collapse: collapse;
    font-size: var(--od-fs-16);
    line-height: 28px;
    color: var(--od-text);
}

.od-doc-table th,
.od-doc-table td {
    padding: var(--od-s3) var(--od-s4);
    border: 1px solid var(--od-border);
    vertical-align: top;
    text-align: left;
}

.od-doc-table th {
    width: 240px;
    background: var(--od-cream-alt);
    font-weight: 700;
    white-space: nowrap;
}

.od-caption {
    margin-top: var(--od-s2);
    text-align: center;
    font-size: var(--od-fs-14);
    color: var(--od-text-muted);
}

/* Instruction tabs. The Figma fills the ACTIVE tab cream and leaves inactive
   tabs white with an underlined navy label - the inverse of Bootstrap's
   default, which showed inactive tabs in link-blue. */
.od-doc-tabs {
    display: flex;
    gap: var(--od-s3);
    margin-bottom: 0;
    padding: 0;
    list-style: none;
    border: 0;
}

.od-doc-tabs .nav-item {
    flex: 1 1 0;
    min-width: 0;
}

.od-doc-tabs .nav-link {
    display: block;
    width: 100%;
    padding: var(--od-s4) var(--od-s4);
    background: var(--od-white);
    color: var(--od-navy);
    font-weight: 700;
    font-size: var(--od-fs-16);
    text-align: center;
    text-decoration: underline;
    border: 2px solid var(--od-beige);
    border-bottom: 0;
    border-radius: var(--od-r-bar) var(--od-r-bar) 0 0;
    cursor: pointer;
}

.od-doc-tabs .nav-link:hover {
    background: var(--od-cream-alt);
    color: var(--od-navy);
}

/* The active tab is filled beige with black type and no underline; the inactive
   one is an outline with navy underlined type, which is what marks it as the
   link (1:10060). Both take a 20px top corner, not the 10px card radius. */
.od-doc-tabs .nav-link.active {
    background: var(--od-beige);
    color: var(--od-text);
    text-decoration: none;
    border-color: var(--od-beige);
}

/* A 2px rule under the tab strip, not a bordered box (1:10060, 線 742). The
   sections below are themselves outlined panels, so a container border around
   them drew a box inside a box that the design does not have. */
.od-doc-panel {
    padding: var(--od-s6) 0 0;
    border: 0;
    border-top: 2px solid var(--od-beige);
    border-radius: 0;
}

/* Section headings inside the guides are green triangle text in the Figma,
   not a filled colour bar. */
/* Spacing measured off the pc guides (1:10081, 1:9761) rather than inherited.
 * Three things were wrong and the first is the one you notice:
 *
 *   padding-left was 0, so the green title sat hard against the card border
 *   while the body beneath it was inset 24px - the heading and its own text did
 *   not line up. The frame insets both by the same amount.
 *
 *   The vertical rhythm was inverted. The frame leaves a generous gap above the
 *   title (37-53px across the three sections measured) and a tight one below it
 *   (12.6px: title box ends 482.4, next element starts 495). We had 17px above
 *   and 40px below - head padding-bottom plus the body's own padding-top.
 *
 * Sides use the body's 24px so the two align. The frame insets its manual
 * content by ~45px, wider than our card padding throughout; matching that is a
 * layout change across both guides rather than a spacing fix, so it is noted in
 * docs/figma-conversion-notes.md instead of done here.
 */
.od-doc-card__head--section {
    background: transparent;
    border-bottom: 0;
    padding: var(--od-s6) var(--od-s5) var(--od-s3);
    color: var(--od-green);
}

/* The body supplies the gap under a normal filled header, but a section title
   wants only the 12px above. */
.od-doc-card__head--section + .od-doc-card__body {
    padding-top: 0;
}

/* 26px on desktop (1:9772), 20px on mobile (9:126) - the section title is the
   one piece of manual type that is larger than the product screens, not
   smaller. It had been rendering at the shared 18px. */
.od-doc-card__head--section > * {
    color: var(--od-green);
    font-weight: 800;
    font-size: var(--od-fs-26);
}

.od-doc-card__head--section > *::before {
    content: "\25bc";
    margin-right: var(--od-s2);
    font-size: 0.85em;
}

/* --------------------------------------------------------------------------
   9. Alerts
   -------------------------------------------------------------------------- */

/* Block rather than flex so it lays out correctly whether the caller wraps its
   content in a single child or drops headings and paragraphs in directly. */
.od-alert {
    display: block;
    padding: var(--od-s4) var(--od-s5);
    margin-bottom: var(--od-s4);
    border-radius: var(--od-r-input);
    font-size: var(--od-fs-16);
}

.od-alert > :last-child {
    margin-bottom: 0;
}

.od-alert--dismissible {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--od-s3);
}

.od-alert__close {
    flex: 0 0 auto;
    padding: 0 var(--od-s2);
    background: none;
    border: none;
    font-size: 1.4em;
    line-height: 1;
    color: inherit;
    opacity: 0.6;
    cursor: pointer;
}

.od-alert__close:hover {
    opacity: 1;
}

.od-alert--danger {
    background: var(--od-red-bg);
    border: 2px solid var(--od-red);
    color: var(--od-red);
    font-weight: 700;
}

/* Page-level notice bands are a paler blue than the info panels elsewhere.
   Measured at #d6e1ff on both the team dashboard (1:13196) and the round
   decisions screen (1:13973); --od-info-bg stays on the deeper #bdd7ee used by
   info tags and document card heads, which these frames do not contradict. */
.od-alert--info {
    background: var(--od-notice-bg);
    color: var(--od-navy);
}

/* Success and warning carry their meaning in the tint and border. The darker
   inks I had used here (#06702a, #7a5d00) appear nowhere in the Figma palette,
   so the text falls back to normal body ink. Danger keeps red text because the
   frames do show red copy on the pink alert. */
.od-alert--success {
    background: #e3f7e8;
    border: 1px solid var(--od-green);
    color: var(--od-text);
}

/* Cream section band, used for completion notices rather than a status tint -
   the event card's submitted band (1:11390) and the dashboard's game-complete
   band (1:13496) are both #f4f3e2. */
.od-alert--note {
    background: var(--od-cream);
    color: var(--od-text);
}

/* Flat grey band, for a state that is over rather than one that needs
   attention: ゲーム完了 on the game status panel (1:14822, #acaba3). The blue
   notice band was doing this job, which read as news when the panel above it
   uses blue for the round that is live. */
.od-alert--muted {
    background: var(--od-grey-soft);
    color: var(--od-text);
}

.od-alert--warning {
    background: var(--od-amber-bg);
    border: 1px solid var(--od-gold-dark);
    color: var(--od-text);
}

/* The event-round strip on the game master's game screen: flat #faf6ac with no
   border, unlike the amber warning above it (1:12513). The two yellows are
   genuinely different - see the note on --od-yellow-band. */
.od-alert--event {
    background: var(--od-yellow-band);
    border: 0;
    color: var(--od-text);
}

/* Boxed callout with a heavy red rule, used for round/game state notices. */
/* The dashboard's round-state cards. Both states are drawn the same in the
   frames - a plain bordered card, left aligned, with a black 24px title carrying
   a hollow bullet (1:13586 "○アクション必要", 1:13587 "○決定事項提出済み", and
   the same pair on 1:13196).

   The action-required state used to be .od-callout: a red 2px box with centred
   red type. Nothing in the design draws it that way; it is an ordinary card that
   happens to be the one asking for a decision. */
.od-roundcard {
    padding: var(--od-s5) var(--od-s6);
    margin-bottom: var(--od-s5);
    background: var(--od-white);
    border: 1px solid var(--od-border);
    border-radius: var(--od-r-card);
}

.od-roundcard__title {
    margin: 0 0 var(--od-s2);
    font-size: var(--od-fs-24);
    font-weight: 800;
    color: var(--od-text);
}

.od-roundcard__title::before {
    content: "\25cb";
    margin-right: var(--od-s2);
}

.od-roundcard > p {
    margin-bottom: var(--od-s4);
    font-size: var(--od-fs-18);
}

.od-callout {
    border: 2px solid var(--od-red);
    border-radius: var(--od-r-input);
    padding: var(--od-s5);
    margin-bottom: var(--od-s5);
    text-align: center;
}

.od-callout__title {
    margin: 0 0 var(--od-s3);
    font-weight: 800;
    color: var(--od-red);
    font-size: var(--od-fs-18);
}

.od-text-center {
    text-align: center;
}

/* Large event-card numeral. */
/* The draw-card prompt (1:11341, 1:11349): an 18px bold lead over a 22px bold
   call to action. Both are plain bold lines, not marked subheadings. */
.od-drawcard__lead {
    margin: 0 0 var(--od-s2);
    font-size: var(--od-fs-18);
    font-weight: 700;
}

.od-drawcard__cta {
    margin: 0 0 var(--od-s5);
    font-size: var(--od-fs-22);
    font-weight: 700;
}

/* 100px navy in the Figma (1:11440) - by far the largest type in the product.
   Kept fluid at the bottom so it still fits a phone. */
/* The drawn card: the tilted card-back art, then the number. 1:11440 sets the
   number at 100px in a 436x150 box and puts the art (1:11986, 105.7x143.1)
   immediately to its left, the two centred on each other - hence a centred flex
   row rather than the block this was.
 *
 * The art is 106px wide in the frame and scales with the number below the
 * clamp's ceiling, so the pairing holds on a phone as well; it never grows past
 * the frame's own size. */
.od-cardnum {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--od-s2);
    margin: var(--od-s4) 0;
    font-size: clamp(3rem, 10vw, 100px);
    font-weight: 800;
    line-height: 1.1;
    color: var(--od-navy);
}

.od-cardnum__art {
    flex: 0 0 auto;
    width: min(1.06em, 106px);
    height: auto;
}

/* --------------------------------------------------------------------------
   10. Stats
   -------------------------------------------------------------------------- */

/* Main column beside a narrower sidebar; stacks below the desktop breakpoint. */
/* 860px content beside a fixed 300px sidebar with a 40px gutter - 1200 in
   total, the content column. Confirmed on two frames: the team dashboard
   (1:13196, sidebar 300 at x=400, cards 860 at x=740) and round decisions
   (1:13973, same geometry). The sidebar is a fixed width in the design, not a
   fraction, so it does not grow with the viewport. */
.od-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 40px;
    align-items: start;
}

.od-split > * {
    min-width: 0;
}

/* Whatever follows the two columns needs its own separation. .od-split has no
   bottom margin, and the blocks placed after it carry no top margin either -
   the game master's 次のステップ card and the team dashboard's game-state band
   both butted straight against the column above them. 32px is what
   .od-subheading opens between sections inside the columns, so the gap after
   the grid matches the gaps within it. */
.od-split + * {
    margin-top: var(--od-s6);
}

.od-split .od-subheading:first-child {
    margin-top: 0;
}

.od-stat-row {
    display: flex;
    gap: var(--od-s4);
    margin-top: var(--od-s4);
}

.od-stat-row--wrap {
    flex-wrap: wrap;
    margin-top: 0;
}

/* The dashboard status panel lays its figures out two per row with a rule
   between the columns (1:13393, a 288px vertical line down the panel centre)
   and rules between the rows. Kept as a modifier because other pages put three
   or more figures on a row. */
.od-stat-row--pairs {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0;
}

.od-stat-row--pairs .od-stat + .od-stat {
    border-left: 1px solid var(--od-border-soft);
}

.od-stat-row--wrap .od-stat {
    flex: 1 1 120px;
}

/* Padding inside a --flush card, for content that is not a table. */
.od-card__inner {
    padding: var(--od-s4);
}

.od-stat-row .od-stat {
    flex: 1 1 0;
}

.od-stat {
    text-align: center;
}

/* Headline figure - the cash total on the team dashboard - is 40px. Its colour
   comes from .od-pos / .od-neg on the element, which already resolve to the
   #00b237 and #b42626 the frames use, so none is set here. */
.od-stat__value {
    font-size: var(--od-fs-40);
    font-weight: 800;
    line-height: 1.2;
}

/* Supporting figures are 30px black, with 12px captions on a 16px line.
 *
 * :not() so a semantic colour still wins. .od-pos and .od-neg are single-class
 * selectors declared earlier in the sheet, so a bare `color` here overrode them
 * on equal specificity and the cash figure on the all-decisions page rendered
 * black however healthy the balance was - the template has always asked for
 * green. Same shape as the h6 rule that was blanking the event-card titles. */
.od-stat__value--sm:not(.od-pos):not(.od-neg) {
    color: var(--od-text-strong);
}

.od-stat__value--sm {
    font-size: var(--od-fs-30);
}

/* The game-statistics pair is the largest figure in the product at 60px, on the
   game master game screen (1:12749) and standings (1:14914), and 60px again on
   mobile standings (14:1743).

   Not clamped. It was written as clamp(40px, 9vw, 60px) to protect a phone, but
   9vw is 35px at 390 - below the floor - so mobile rendered 40px against a
   designed 60px, and the ceiling was only reached past a 667px viewport. The
   design already sizes these for 390: two of them sit in a 174.75px cell, which
   works because the values are one or two digits. */
.od-stat__value--xl {
    font-size: var(--od-fs-60);
    color: var(--od-text-strong);
}

.od-stat__label {
    font-size: var(--od-fs-12);
    line-height: 16px;
    color: var(--od-text-strong);
}

/* --------------------------------------------------------------------------
   11. Footer
   -------------------------------------------------------------------------- */

/* Black in the Figma, not grey. Size confirmed correct - the rendered string
   measures 335px in the frame and 334px here. */
.od-footer {
    flex-shrink: 0;
    padding: var(--od-s6) var(--od-s4);
    text-align: center;
    font-size: var(--od-fs-14);
    color: var(--od-text-strong);
}

/* Keeps the break in the footer before "Studies" instead of after it - see the
   comment on the element in base.html. */
.od-footer__unit {
    white-space: nowrap;
}

/* --------------------------------------------------------------------------
   12. Bootstrap bridge
   Templates still carry ~1,400 Bootstrap classes. Rather than rewrite every
   one, map the common ones onto the design tokens so untouched markup
   inherits the new look.
   -------------------------------------------------------------------------- */

.btn-primary {
    background-color: var(--od-navy);
    border-color: var(--od-navy);
}

.btn-primary:hover,
.btn-primary:focus {
    background-color: var(--od-navy-dark);
    border-color: var(--od-navy-dark);
}

.btn-secondary {
    background-color: var(--od-grey);
    border-color: var(--od-grey);
}

.btn-success {
    background-color: var(--od-green);
    border-color: var(--od-green);
}

.btn-danger {
    background-color: var(--od-red);
    border-color: var(--od-red);
}

/* Interactive states. Bootstrap ships its own hover/focus/disabled colours
   (#0a58ca, #565e64, #146c43, #86b7fe, #6c757d, #e9ecef...) which apply to any
   selector not overridden here. They are invisible in a resting-state audit,
   so they are set explicitly rather than left to chance. */
.btn:hover,
.btn:focus {
    color: inherit;
}

.btn-secondary:hover,
.btn-secondary:focus {
    background-color: var(--od-grey-dark);
    border-color: var(--od-grey-dark);
    color: var(--od-text-on-dark);
}

/* The Figma does not specify hover colours, so rather than invent new hexes
   these darken the palette colour systematically. */
.btn-success:hover,
.btn-success:focus,
.btn-danger:hover,
.btn-danger:focus,
.od-btn--success:hover,
.od-btn--danger:hover,
.od-btn--gold:hover {
    filter: brightness(0.9);
    color: var(--od-text-on-dark);
}

.btn-warning:hover,
.btn-warning:focus {
    background-color: var(--od-gold-dark);
    border-color: var(--od-gold-dark);
    color: var(--od-navy);
}

/* One focus ring for every control, replacing Bootstrap's blue glow. */
.btn:focus,
.btn:focus-visible,
.form-control:focus,
.form-select:focus,
.form-check-input:focus,
.dropdown-toggle:focus {
    box-shadow: 0 0 0 3px rgba(25, 42, 84, 0.12) !important;
    outline: none;
}

.btn-close:focus {
    box-shadow: 0 0 0 3px rgba(25, 42, 84, 0.12);
    outline: none;
}

/* Validation states: Bootstrap tints these #dc3545 / #198754. */
.form-control.is-invalid,
.was-validated .form-control:invalid {
    border-color: var(--od-red);
}

.form-control.is-invalid:focus,
.was-validated .form-control:invalid:focus {
    border-color: var(--od-red);
    box-shadow: 0 0 0 3px rgba(180, 38, 38, 0.15) !important;
}

.form-control.is-valid,
.was-validated .form-control:valid {
    border-color: var(--od-green);
}

.form-control.is-valid:focus,
.was-validated .form-control:valid:focus {
    border-color: var(--od-green);
    box-shadow: 0 0 0 3px rgba(0, 178, 55, 0.15) !important;
}

/* Disabled controls. */
.btn:disabled,
.btn.disabled {
    background-color: var(--od-grey);
    border-color: var(--od-grey);
    color: var(--od-text-on-dark);
    opacity: 0.65;
}

.form-control:disabled,
.form-control[readonly],
.form-select:disabled {
    background-color: var(--od-cream-alt);
    color: var(--od-text-muted);
}

.form-check-input:checked {
    background-color: var(--od-navy);
    border-color: var(--od-navy);
}

.btn-warning {
    background-color: var(--od-gold);
    border-color: var(--od-gold);
    color: var(--od-navy);
}

/* Buttons and form controls carry different radii in the Figma - 10px for
   buttons, 6px for inputs - so they cannot share one rule. */
.btn {
    border-radius: var(--od-r-btn);
}

.form-control,
.form-select {
    border-radius: var(--od-r-input);
}

/* Bootstrap form controls keep their own ink (#212529) and border (#ced4da).
   These are only visible inside hidden states - the delete-confirmation inputs
   in each dashboard modal - which is why they escaped the page-level audits. */
.form-control,
.form-select {
    color: var(--od-text);
    border-color: var(--od-border);
}

/* The focus ring itself is set once, with !important, in the shared focus rule
   above; repeating it here had no effect. */
.form-control:focus,
.form-select:focus {
    color: var(--od-text);
    border-color: var(--od-navy);
}

.form-control::placeholder {
    color: var(--od-text-faint);
}

.card {
    border: 1px solid var(--od-border);
    border-radius: var(--od-r-card);
}

.card-header {
    background: var(--od-cream);
    border-bottom: 1px solid var(--od-border);
    font-weight: 700;
}

/* Bootstrap drives table cell colour through its own custom properties, and
   .table-striped's row selector outranks a plain colour override - so set the
   variables rather than fighting specificity. Without this the decisions
   history page rendered every cell in Bootstrap's #212529. */
.table {
    color: var(--od-text);
    --bs-table-color: var(--od-text);
    --bs-table-striped-color: var(--od-text);
    --bs-table-active-color: var(--od-text);
    --bs-table-hover-color: var(--od-text);
}

.table > :not(caption) > * > * {
    padding: var(--od-s3) var(--od-s4);
}

.table > thead th {
    border-bottom: 3px solid var(--od-rule);
    font-weight: 800;
}

.alert-danger {
    background: var(--od-red-bg);
    border-color: var(--od-red);
    color: var(--od-red);
}

.alert-success {
    background: #e3f7e8;
    border-color: var(--od-green);
    color: #06702a;
}

.alert-info {
    background: var(--od-info-bg);
    border-color: var(--od-info-bg);
    color: var(--od-navy);
}

.alert-warning {
    background: var(--od-amber-bg);
    border-color: var(--od-gold-dark);
    color: var(--od-text);
}

/* Bootstrap's semantic text helpers ship their own greens/reds/ambers
   (#198754, #dc3545, #ffc107). The decisions history table uses them heavily,
   which put three off-palette colours on that page. Map them onto the design
   palette rather than rewriting ~40 call sites in a table whose markup is
   deliberately left alone. */
.text-success { color: var(--od-green) !important; }
.text-danger  { color: var(--od-red) !important; }
.text-warning { color: var(--od-gold-dark) !important; }
.text-muted   { color: var(--od-text-muted) !important; }
.text-primary { color: var(--od-navy) !important; }
.text-info    { color: var(--od-navy) !important; }

/* Same for the legacy cash helpers, which still resolve through legacy.css. */
.cash-positive { color: var(--od-green) !important; }
.cash-negative { color: var(--od-red) !important; }

.badge.bg-primary { background-color: var(--od-navy) !important; }
.badge.bg-success { background-color: var(--od-green) !important; }
.badge.bg-danger  { background-color: var(--od-red) !important; }
.badge.bg-warning { background-color: var(--od-gold) !important; color: var(--od-navy) !important; }

/* --------------------------------------------------------------------------
   13. Responsive
   The masthead stacks below the desktop breakpoint, matching OODASHIP_sp.
   -------------------------------------------------------------------------- */

@media (max-width: 767.98px) {
    .od-masthead__brand,
    .od-masthead__actions {
        flex: 1 1 100%;
        border-bottom-left-radius: 0;
        border-bottom-right-radius: 0;
    }

    /* The build hash was hidden here on the grounds that it widens the page.
       It is wanted on mobile too - identifying which build a phone is looking
       at is most of what it is for - so it is shown, one step smaller, and the
       masthead is allowed to wrap rather than be pushed wider. Measured at
       390px and at 320px: no horizontal overflow at either. */
    .od-version {
        font-size: 10px;
    }

    .od-masthead__link {
        font-size: var(--od-fs-12);
    }

    /* Masthead, measured from the sp file's shared login component (24:20).
       The mobile header is 83px - a 34px navy strip over a 49px gold one - not
       two 100px bands. Both min-heights come from the desktop rules and have to
       be released explicitly, or the header stands 200px tall on a phone.

       One rule each: these previously carried a second, earlier padding that
       the 5px measurement silently overrode. */
    .od-masthead__brand {
        gap: var(--od-s3);
        min-height: 34px;
        padding: 5px var(--od-s2);
    }

    .od-masthead__actions {
        justify-content: space-between;
        gap: var(--od-s2);
        min-height: 49px;
        padding: 5px var(--od-s2);
    }

    /* 96x39 at a 50px radius with 14px navy type (24:15). */
    .od-pill {
        min-width: 96px;
        min-height: 39px;
        padding: var(--od-s2) var(--od-s3);
        font-size: var(--od-fs-14);
        letter-spacing: 1px;
    }

    /* The lockup is 24px here against 48px on desktop, and the (c) drops to
       8.4px rather than tracking the base size (24:6, 24:7).

       font-size, not height: this is a text span since the wordmark moved to
       Bebas Neue, and a height does not shrink glyphs - the 48px lockup
       overflowed its box and was clipped by the shell's overflow guard. */
    .od-wordmark {
        font-size: var(--od-fs-14);
    }

    .od-wordmark__art {
        font-size: 24px;
        letter-spacing: 1px;
    }

    /* Half the desktop sizes, because the art is: 24px against 48. The mark
       keeps its 0.414 ratio to the wordmark's ink rather than the 8.4px it had
       when it sat on the baseline, so the two breakpoints agree about where the
       (c) is and how big it is. The margin is a length, so it halves here too;
       the translate is in em and does not need to. */
    .od-wordmark__mark {
        margin-left: 1.95px;
        font-size: 14.5px;
    }

    /* The mobile lockup is OODASHIP and the (c) only - the kana is dropped.
       That predates the stacked lockup and survives it: the sp frame drops the
       kana, and at 24px art the customer's ratio would set it 7px tall inside a
       bar that is already tight. So mobile stacks nothing - it is one row. */
    .od-wordmark__kana {
        display: none;
    }

    /* 11px navy between the two pills on the gold band (26:439), not the black
       the desktop header uses. */
    .od-masthead__welcome {
        font-size: 11px;
        color: var(--od-navy);
        /* Lets the ellipsis actually engage: a flex item defaults to
           min-width: auto and will not shrink below its own text. */
        min-width: 0;
    }

    /* Footer is 16px in the faint grey on mobile, against 14px black on desktop
       (24:379). */
    .od-footer {
        font-size: var(--od-fs-16);
        color: var(--od-text-faint);
    }

    /* The page title bar is a 4px black rule beside 20px type, not the desktop
       navy 10px bar beside 26px (24:434, 24:435). */
    .od-heading {
        font-size: var(--od-fs-20);
        gap: var(--od-s3);
    }

    .od-heading::before {
        width: 4px;
        background: var(--od-text-strong);
    }

    /* The sp file re-specifies the whole scale, not just the layout. These are
       the values that repeat across its screens (measured on 14:1689, the
       standings frame, and consistent with 12:186 and 21:212).

       Section headings step down 22 -> 18, card corners 10 -> 4, and tables drop
       from 18/22 to a uniform 13 - the mobile tables are far quieter than their
       desktop counterparts rather than simply narrower. */
    .od-subheading {
        font-size: var(--od-fs-18);
    }

    .od-panel,
    .od-card {
        border-radius: 4px;
    }

    /* Tags are 41px at a 4px radius with 18px type, against 50px / 6px / 20px
       on desktop (14:1715-14:1722). */
    .od-tag {
        min-height: 41px;
        border-radius: 4px;
        font-size: var(--od-fs-18);
    }

    /* ...but not inside a table. The 41px above is for the full-size state
       tags; a tag in a dense table has its own rule further up precisely
       because it "would otherwise dominate the row". That rule sets padding and
       font but no min-height, so this one applied to it unopposed: the 勝者！
       badge on the leaderboard came out 41px tall from a 12px font with 1px of
       padding, taking the winner's row to 90px against 54px for everyone else.
       Listed here rather than merged into the rule above, so source order
       cannot flip which of the two wins.

       Deliberately only the table and the explicit --sm opt-in. The compact
       rule up there also covers .od-stat and .od-meter__head, but those tags
       are 41px on mobile today and nobody has said they are wrong - widening
       this would resize the team dashboard as a side effect of a leaderboard
       fix. */
    .od-tag--sm,
    .od-table .od-tag {
        min-height: 0;
    }

    /* The round-preparation modal has to fit a phone, and did not.
     *
     * .od-timelimit__note is 26px/38px centred, which is two lines on desktop
     * and *six* on a 390px screen - 228px of one paragraph. That pushed the
     * dialog to 986px against an 860px viewport and left 投資決定に進む below
     * the fold on load: the player saw a wall of red and no way forward, on the
     * one screen in the product with a clock running against it.
     *
     * The sp frame (30:707) sets this text smaller and ranged left rather than
     * centred, which is also what stops it fragmenting into six lines. */
    .od-timelimit__note {
        font-size: var(--od-fs-16);
        line-height: 24px;
        text-align: left;
    }

    .od-gametab {
        min-height: 41px;
        padding: 0 var(--od-s4);
        border-radius: 4px;
        border-width: 1px;
        font-size: var(--od-fs-18);
    }

    /* Every full-size button collapses to one mobile size: 39px tall with 14px
       tracked type. --sm keeps its own 28px row size, and --modal keeps its
       200x50 at 20px - the mobile delete modal (30:558, 30:563) draws its
       actions at exactly the desktop size, so modals do not shrink. */
    .od-btn--lg,
    .od-btn--column,
    .od-btn--compact,
    .od-btn--card {
        min-height: 39px;
        font-size: var(--od-fs-14);
        letter-spacing: 1px;
    }

    /* The game cards are the exception: on a phone their actions are 30px at
       12px on a 3px radius, and they share the row rather than holding the
       desktop 194/78 widths (25:1084, 25:1086). Restated at the same two-class
       depth as the desktop rule above it, which would otherwise win here. */
    .od-card--game .od-btn--card {
        min-width: 0;
        min-height: 30px;
        padding: var(--od-s2) var(--od-s4);
        font-size: var(--od-fs-12);
        border-radius: 3px;
        letter-spacing: 1px;
    }

    .od-card--game .od-btn--danger {
        min-width: 0;
    }

    /* The event round is the exception to that collapse. Both its actions stay
       55px with 18px type - the draw button (14:2088) and the acknowledge button
       (14:2043) - so the one screen that asks for a single deliberate tap keeps
       a bigger target than the rest of the mobile site. */
    .od-btn--event {
        min-height: 55px;
        font-size: var(--od-fs-18);
        /* .od-btn--lg pins min-width: 360px, which is the desktop frame's
           button. The event panel is outlined and padded 24px, so its content
           box is 310px at 390px wide and 240px at 320px - the button broke out
           through the right border by 51px and 121px respectively. Every other
           large button on a phone escapes this with .od-btn--auto; these two
           never got it. Full width of the panel is the mobile equivalent of the
           frame's wide button. */
        min-width: 0;
        width: 100%;
    }

    .od-table th,
    .od-table td {
        font-size: 13px;
    }

    .od-code {
        font-size: 13px;
    }

    /* Manual body copy is 16px on a 28.8px line in the softer #333, not the
       black the rest of the site uses (9:129-9:135). The manual is the one
       place in the product that is read rather than scanned, which is
       presumably why it is set looser and quieter. Its cards take the mobile
       4px corner like every other card. */
    .od-card-text {
        font-size: var(--od-fs-16);
        line-height: 28.8px;
        color: var(--od-text-soft);
    }

    /* Softer border and a 20px section title on a phone, against #acaba3 and
       26px on desktop (9:125, 9:126). */
    .od-steps--grid,
    .od-steps--grid-3,
    .od-form-grid {
        grid-template-columns: 1fr;
    }

    .od-doc-card {
        border-radius: 4px;
        border-color: var(--od-border-soft);
    }

    .od-doc-card__head--section > * {
        font-size: var(--od-fs-20);
    }

    /* Mobile cells are regular weight, unlike desktop where every value is bold
       (12:150, 12:152). Money keeps its weight because .od-pos / .od-neg carry
       their own 700. The rules also change: a 2px black underline beneath the
       header and #acaba3 between rows (12:133, 12:149). */
    .od-table td {
        font-weight: 400;
        border-bottom: 1px solid var(--od-grey-soft);
    }

    .od-table th {
        border-bottom: 2px solid var(--od-text-strong);
    }

    /* The dialog is 360px inside 15px margins on a phone (30:554), against the
       1000px it takes on desktop. */
    .modal-dialog {
        max-width: 360px;
        margin: 15px auto;
    }

    /* Modal actions stack at their 200px width rather than filling the dialog.

       column, not column-reverse: modals invert the page-form convention. Both
       mobile modals put the dismissive action on top and the affirmative below
       - close over process (30:642, 30:640) and cancel over delete (30:563,
       30:558) - where create game puts its primary on top. The markup already
       lists dismiss first, so plain column matches and tab order is untouched.

       The two disagree on alignment: round complete centres, delete sits left.
       Centred here, matching the modal that appears during play.  */
    .modal-footer {
        flex-direction: column;
        align-items: center;
    }

    /* The cost grid is three across on mobile with much smaller tiles: a 12px
       label over a 60x37 bordered box holding an 18px figure (30:835-30:838).
       The total keeps its rule but the figure drops to 30px and gains the 2px
       underline the frame draws (30:873). */
    .od-costgrid {
        grid-template-columns: repeat(3, 1fr);
        gap: var(--od-s4) var(--od-s2);
    }

    .od-costgrid__label {
        font-size: var(--od-fs-12);
        color: var(--od-text-soft);
    }

    .od-costgrid__value {
        min-height: 37px;
        border: 1px solid var(--od-border-soft);
        font-size: var(--od-fs-18);
    }

    .od-costtotal__label {
        font-size: var(--od-fs-16);
    }

    .od-costtotal__value {
        font-size: var(--od-fs-30);
        border-bottom: 2px solid var(--od-text-soft);
        padding: 0 var(--od-s5);
    }

    /* The team summary card is 16px rows on mobile against 20px on desktop
       (30:764-30:769); its detail lists stay at the shared 18px/30. */
    .od-split--reverse aside .od-meta {
        font-size: var(--od-fs-16);
        line-height: 32px;
    }

    /* The legend restates itself on mobile rather than just reflowing: 45px on
       a 2px radius, 17px bold inset 16px, and a different colour in all three
       states - the deeper #bdd7ee blue, gold rather than yellow, and the soft
       border grey rather than #acaba3 (14:1893-14:1898). */
    .od-gamestate {
        padding: 0 16px;
        font-size: 17px;
        font-weight: 700;
        line-height: 45px;
        border-radius: 2px;
        color: var(--od-navy);
    }

    .od-gamestate--active   { background: var(--od-info-bg); }
    .od-gamestate--event    { background: var(--od-gold); }

    .od-gamestate--complete {
        background: var(--od-border-soft);
        color: var(--od-text-soft);
    }

    /* The cream status block sits inside the content column on a phone rather
       than running full bleed - 352.5px in a 375px main, drawn that way for both
       the in-progress and completed states (14:2159, 14:2213). Its title is 17px
       bold and its body 15px in the soft grey. */
    .od-band {
        margin: var(--od-s5) 0;
        padding: 16px 20px;
    }

    .od-band .od-subheading {
        font-size: 17px;
    }

    .od-band p {
        font-size: 15px;
        color: var(--od-text-soft);
    }

    /* The progress bar is a 3px-radius bar here, not a pill, on a lighter
       track (14:1731). */
    .od-progress {
        border-radius: 3px;
        background: var(--od-border-soft);
    }

    .od-progress__bar {
        border-radius: 3px;
    }

    .od-progress__label {
        font-size: var(--od-fs-16);
    }

    /* The game master screen shrinks its statistics to 22px on mobile (13:440)
       while standings keeps 60px (14:1743) - same size on desktop, different on
       a phone, so it takes a modifier rather than a separate size. */
    .od-stat__value--shrink {
        font-size: var(--od-fs-22);
    }

    /* Statistics keep their 60px figure but the caption drops to 12px grey. */
    .od-stat__label {
        font-size: var(--od-fs-12);
        color: var(--od-text-faint);
    }

    /* The 1fr/300px split and the 300px button floor are desktop measurements;
       both have to give way on a phone. */
    .od-home-panel {
        grid-template-columns: 1fr;
        gap: 0;
        padding: var(--od-s5) var(--od-s4);
    }

    /* The mobile submit spans its card rather than shrinking to its label - the
       frame draws it 292px wide inside a 350px card (24:556). The scoped
       .od-login__submit .od-btn rule carries two selectors, so it has to be
       matched here or it keeps the button at auto width. */
    .od-btn--compact {
        min-width: 0;
        width: 100%;
    }

    .od-login__submit .od-btn {
        width: 100%;
    }

    /* Fields are 54px with a 2px radius and the faint grey border throughout
       mobile - login (12:214) and create game (13:642) agree, so this is the
       shared mobile field rather than a login exception as the desktop 60px/5px
       is. Values are 16px and the placeholder lightens to #757575.

       The scoped .od-login__panel rules carry two selectors, so they have to be
       matched here or the login form keeps its desktop sizing. */
    .od-input,
    .od-select,
    .od-textarea,
    .od-field input[type="text"],
    .od-field input[type="password"],
    .od-field input[type="number"],
    .od-field select,
    .od-login__panel .od-field input[type="text"],
    .od-login__panel .od-field input[type="password"] {
        min-height: 54px;
        font-size: var(--od-fs-16);
        border-color: var(--od-text-faint);
        border-radius: 2px;
    }

    .od-input::placeholder,
    .od-textarea::placeholder {
        color: #757575;
    }

    /* Field labels are 16px here against 22px on desktop, and help text is 16px
       grey rather than 18px black (13:641, 13:649). */
    .od-label,
    .od-label--section,
    .od-login__panel .od-label,
    #decisions-form .od-label {
        font-size: var(--od-fs-16);
    }

    .od-help,
    #decisions-form .od-help {
        font-size: var(--od-fs-16);
        color: var(--od-text-faint);
    }

    /* Actions stack full-width rather than sitting on one row (25:931, 25:934).
       column-reverse because the frame puts the primary action on top while the
       markup keeps it last for tab order - reversing visually leaves the DOM
       order, and so the keyboard order, alone. */
    .od-actions--end,
    .od-actions--center {
        flex-direction: column-reverse;
        align-items: stretch;
    }

    .od-main {
        padding: var(--od-s5) var(--od-s4) var(--od-s7);
    }

    /* The modifiers carry two classes so they beat the base rule at any source
       order, which means this override has to match their specificity - a bare
       .od-split here loses to them and the split never collapses. That left the
       dashboard and decisions screens holding a 300px sidebar on a 390px phone,
       squeezing the content column to about 50px. */
    .od-split,
    .od-split.od-split--reverse,
    .od-split.od-split--narrow-aside {
        grid-template-columns: 1fr;
        gap: var(--od-s5);
    }

    /* Full bleed on mobile: the sp frame runs the illustration the whole 390px
       (image 1 and the artwork rectangle both sit at x=0, w=390), while the
       side padding here held it to 366 and left a 12px white gutter down each
       edge - read as a border around the image rather than as breathing room. */
    .od-hero {
        margin-top: calc(var(--od-s5) * -1);
        /* Back to the 32px the base rule used to carry, NOT to zero. The
           desktop rule now ends in -102px, which is the pc frame's overlap;
           mobile takes its own from .od-home-panel below, and that -86px is
           calibrated against a 48px gap here - 16px of padding plus this 32.
           Zeroing it silently deepened the sp overlap to 70px and pulled the
           puzzle pieces down over ▼アクティブなゲーム. */
        margin-bottom: var(--od-s6);
        padding: var(--od-s4) 0;
    }

    /* The tagline is not rendered in Japanese - the artwork carries it - but it
       is the one thing in the hero that still needs a margin when it is. */
    .od-hero__tagline {
        padding: 0 var(--od-s3);
    }

    /* The illustration sits ON the content panel, it does not stop above it.
     *
     * In the sp frame the artwork (21:217) runs y=83..378 while the cream
     * container (21:221) starts at y=340 - so its lower 38px overlap, with the
     * puzzle pieces breaking over the panel's top edge. We had a 48px *gap*
     * instead, which reads as two stacked blocks rather than one composition.
     *
     * 86px closes the 48px gap and then takes the 38px overlap the frame
     * draws. The hero is lifted above the panel so the artwork paints over the
     * cream rather than being clipped by it; the illustration's background is
     * transparent, so only the pieces themselves land on it. */
    .od-hero {
        position: relative;
        z-index: 1;
    }

    .od-home-panel {
        margin-top: -86px;
        /* The overlap is with the panel, NOT with the heading. In the frame the
           artwork ends at 378 and ▼アクティブなゲーム starts at 393, so the
           text clears it by 15px. Pulling the panel up without this left the
           heading 14px *inside* the illustration, with a puzzle piece sitting
           over the type. 53px is the frame's own container-top-to-text
           distance (340 -> 393), which restores that clearance exactly. */
        padding-top: 53px;
    }

    /* Stack the guide tables into label-above-description blocks.
     *
     * `.od-doc-table th` is `width: 240px` with `white-space: nowrap`, which is
     * right on a wide screen and refuses to yield on a narrow one: measured at
     * 390px, the label column held 218px and left the description **65px** -
     * a ribbon of two or three characters per line that ran the ゲーム状態
     * table to 1912px tall.
     *
     * Stacking gives the description the full column width. The rows keep a
     * border so they still read as a table rather than as loose paragraphs. */
    .od-doc-table,
    .od-doc-table tbody,
    .od-doc-table tr,
    .od-doc-table th,
    .od-doc-table td {
        display: block;
        width: auto;
    }

    .od-doc-table tr {
        margin-bottom: var(--od-s4);
        border: 1px solid var(--od-border);
    }

    .od-doc-table th {
        white-space: normal;
        border: 0;
        border-bottom: 1px solid var(--od-border);
    }

    .od-doc-table td {
        border: 0;
    }

    /* The sp file draws a DIFFERENT grid, not a smaller one.
     *
     * pc (1:14954) is a bounded diamond mesh, densest in the middle and
     * trailing off at the edges, exported 4096 wide for a 2000 frame. sp
     * (28:390) is a uniform lattice that fills its box edge to edge - a
     * separate export, 390x521 at the node, from a 750x1000 source.
     *
     * Reusing the pc artwork here was the bug: on a 390px screen its fixed
     * 4096px background-size shows one corner of the diamond magnified about
     * five times, so the phone got three enormous lines running the whole
     * length of the page instead of a fine mesh behind the hero.
     *
     * Figma's own export, like the pc one - not a recreation. Crossed
     * repeating-linear-gradients were already tried for the pc grid and were
     * wrong; they are not revisited here just because this lattice happens to
     * be uniform enough to fake.
     *
     * Geometry is the frame's: the mesh sits directly under the 83px masthead
     * (the sp frame puts image 1 at y=83, and our mobile masthead measures 83)
     * and spans the full width, so `100% auto` on a 750x1000 source lands it
     * at 390x520 against the frame's 521. It stops there rather than running
     * behind the footer, which is what the frame draws.
     */
    .od-shell--top {
        background-image: url("../images/hero/hero-grid-sp.3a8113accf1b.webp");
        background-size: 100% auto;
        background-position: center 83px;
    }

    .od-stat__value {
        font-size: var(--od-fs-28);
    }

    /* Padding only. This rule used to close with font-size: 14px, which sat
       after the sp layer's 13px and quietly won; the sp file sets 13 on both
       the standings table (12:150, 12:152) and the dashboard round history
       (14:1928-14:1984), so 13 is the measured value and this is the stale one.
       The matching .od-heading rule here (18px) beat the 20px page-title-bar
       component (24:435) the same way and has been removed outright. */
    .od-table th,
    .od-table td {
        padding: var(--od-s3);
    }

    .od-btn {
        padding: var(--od-s3) var(--od-s5);
    }
}

/* The ≤400px block that used to live here hid .od-masthead__welcome, to buy
   space for the two pills on the narrowest phones. Removed on 16 Aug 2026: on a
   390px phone - which is most of them - a team was left with no indication of
   which team they were signed in as, and that matters more here than in most
   apps, because opening a team link silently switches the browser's identity.
   Knowing whose session you are in is worth more than the space it costs.

   It truncates rather than pushing: the span already carried nowrap and
   text-overflow, and the min-width below is what lets a flex item honour them
   instead of refusing to shrink below its content. */

