/*
 * Shared application stylesheet — UI Redesign Phase 1 (Foundation + Shared Shell Prototype,
 * 2026-08-18 product owner decision). Single source of truth for the brand palette, layout
 * shell, and reusable page-pattern classes — every template links this file instead of
 * repeating <style> blocks. Loaded AFTER tabler.min.css on every page, so these rules can
 * safely assume Tabler's own component classes (.card, .btn, .badge, .alert, .nav-link, ...)
 * already exist and are already styled; this file only adds brand tokens, the app-specific
 * shell (header/sidebar/main), and the handful of composite patterns (.app-page-header,
 * .app-stat-card, etc.) Tabler doesn't ship a ready-made equivalent for.
 *
 * Asset strategy (see completion report for the full verified rationale): Tabler Core replaces
 * Bootstrap's own stylesheet entirely — never load bootstrap.min.css alongside tabler.min.css,
 * they compile from the same base and would double-define every component. This file therefore
 * never redefines anything Tabler already provides (spacing utilities, grid, form controls,
 * table basics) — Bootstrap-first/Tabler-first still applies, this is strictly the layer on top.
 *
 * Theming approach: --tblr-primary (Tabler's own documented, safe runtime override point) is set
 * once, below, so every stock Tabler component (buttons, links, active nav state, focus rings)
 * re-themes to the brand navy automatically. Every custom class in this file is additionally
 * built from the --app-* tokens directly, never a Tabler internal variable that isn't part of
 * its documented public theming surface — those aren't guaranteed stable across a Tabler version
 * bump, the four documented/verified ones are.
 */

:root {
    /* ---- Brand / semantic tokens ---- */
    --app-primary: #0b3d78;         /* Deep community navy — continuation of the pre-existing #003d82 brand color, tuned slightly for AA contrast on Tabler's default light surfaces. */
    --app-primary-hover: #082c58;
    --app-primary-soft: #e7f0fb;
    --app-bg: #f4f6f9;
    --app-surface: #ffffff;
    --app-surface-sunken: #eef1f5;
    --app-border: #e1e5eb;
    --app-text: #1c2733;
    --app-muted: #66707c;
    --app-danger: #c0392b;
    --app-danger-soft: #fbeae8;
    --app-success: #2f9e44;
    --app-success-soft: #e7f6ea;
    --app-warning: #a86a06;
    --app-warning-soft: #fbf0dc;
    --app-info-soft: var(--app-primary-soft);

    /* Soft-badge text pairs (Phase 3, 2026-08-18 §八) — deliberately darker than the equivalent
       -icon/-accent color above, chosen for WCAG AA (4.5:1) legibility as small badge text sitting
       on that color's own -soft background, not for use as icon/border/accent color. */
    --app-success-text: #146c2e;
    --app-warning-text: #664d03;
    --app-danger-text: #842029;
    --app-info-text: var(--app-primary-hover);
    --app-muted-text: #4a5568;
    --app-radius: 10px;
    --app-radius-sm: 6px;
    --app-shadow: 0 1px 2px rgba(16, 24, 32, 0.06), 0 6px 16px rgba(16, 24, 32, 0.05);

    /* Tabler's own documented override point — re-themes every stock Tabler component at once. */
    --tblr-primary: var(--app-primary);
}

body {
    background-color: var(--app-bg);
    color: var(--app-text);
}

/* ---------- App shell: top header ---------- */
/* A slim, clean top bar (2026-08-18 decision §十一) — deliberately NOT a full-width saturated
   navy band. Brand mark carries the color instead of the whole bar. */

.app-header {
    background-color: var(--app-surface);
    border-bottom: 1px solid var(--app-border);
    color: var(--app-text);
    padding: 0.6rem 1rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}

.app-header .brand {
    color: var(--app-primary);
    font-weight: 700;
    font-size: 1.15rem;
    text-decoration: none;
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.app-header .brand:hover { color: var(--app-primary-hover); }

.app-header .header-right {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    white-space: nowrap;
}

.app-header .principal-name {
    font-size: 0.92rem;
    color: var(--app-text);
}

.app-header .principal-household {
    font-size: 0.8rem;
    color: var(--app-muted);
}

.app-header .sidebar-toggle {
    color: var(--app-text);
    border-color: var(--app-border);
}

/* Mobile header decluttering (Phase 2, 2026-08-18 §一-1): principal name, role badge, and logout
   move into the offcanvas user section below 992px — the top bar itself keeps only the hamburger
   and brand, so it never crowds or wraps on a 375-430px viewport. The same markup renders both
   places; only visibility toggles per breakpoint, so there is exactly one source of truth for
   "who is logged in" in the DOM, never two divergent copies. */
@media (max-width: 991.98px) {
    .app-header .header-right { display: none; }
}

.offcanvas-user-section {
    display: none;
}

@media (max-width: 991.98px) {
    .offcanvas-user-section {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 0.75rem;
        padding: 0.85rem 1rem;
        border-bottom: 1px solid var(--app-border);
        background: var(--app-surface-sunken);
    }
}

/* ---------- App shell: sidebar + main ---------- */

.app-shell {
    display: flex;
    align-items: flex-start;
    min-height: calc(100vh - 57px);
}

.app-sidebar {
    width: 240px;
    flex-shrink: 0;
    background-color: var(--app-surface);
    border-right: 1px solid var(--app-border);
    min-height: calc(100vh - 57px);
    padding: 1rem 0;
}

.app-main {
    flex-grow: 1;
    min-width: 0;
    padding: 1.5rem;
}

/* Shared horizontal page gutter (Dashboard / Shared UI Polish, 2026-08-20 product owner
   decision §10): a single, central rule for every page's left/right breathing room around the
   breadcrumb/title/content — never a per-page hardcoded margin-left. Desktop keeps the existing
   1.5rem (already within the requested 1.25–1.5rem range); mobile explicitly steps down to 1rem
   rather than relying on the same flat value at every width. */
@media (max-width: 767.98px) {
    .app-main {
        padding: 1rem;
    }
}

/* Content width strategy (Phase 3, 2026-08-18 §一): one page shape does not fit every page kind.
   A dashboard/table wants the full 1360px to lay out columns; a detail/form page read at that
   width leaves lines of text and a handful of fields stretched edge-to-edge, which is what manual
   review flagged. Three named widths, chosen per page by content shape — never a per-page inline
   style:
     .app-content-wide      (~1360px) — dashboard / list / table pages, where columns need room.
     .app-content-standard  (~1180px) — general management pages (e.g. category admin) that are
                                         more than a single record but not a wide data table.
     .app-content-narrow    (~1040px) — detail/form pages: a single record's fields or a reading
                                         column, matched roughly to a comfortable form/paragraph
                                         measure rather than the viewport.
   `.app-main-container` itself only centers; the width modifier is a second class the page adds
   alongside it, so no page ever hardcodes its own max-width. */
.app-main-container {
    margin: 0 auto;
}

.app-main-container.app-content-wide { max-width: 1360px; }
.app-main-container.app-content-standard { max-width: 1180px; }
.app-main-container.app-content-narrow { max-width: 1040px; }

.sidebar-nav {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
}

.sidebar-nav .nav-link {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.55rem 1.25rem;
    color: var(--app-text);
    text-decoration: none;
    border-left: 3px solid transparent;
    font-size: 0.92rem;
    border-radius: 0;
}

.sidebar-nav .nav-link .nav-link-icon {
    color: var(--app-muted);
    display: inline-flex;
    font-size: 1.05rem;
    line-height: 1;
}

.sidebar-nav .nav-link:hover { background-color: var(--app-surface-sunken); color: var(--app-text); }

.sidebar-nav .nav-link.active {
    background-color: var(--app-primary-soft);
    border-left-color: var(--app-primary);
    color: var(--app-primary);
    font-weight: 600;
}

.sidebar-nav .nav-link.active .nav-link-icon { color: var(--app-primary); }

.sidebar-heading {
    padding: 0.9rem 1.25rem 0.3rem;
    font-size: 0.7rem;
    letter-spacing: 0.06em;
    color: var(--app-muted);
    text-transform: uppercase;
    font-weight: 700;
}

.sidebar-divider {
    border: none;
    border-top: 1px solid var(--app-border);
    margin: 0.75rem 1.25rem;
}

/* Dual-role "return to resident mode" — always the first thing in the admin sidebar when present. */
.sidebar-return-link {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0 1rem 0.75rem;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius-sm);
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--app-primary);
    text-decoration: none;
    background: var(--app-primary-soft);
}

.sidebar-return-link:hover { background: var(--app-border); color: var(--app-primary-hover); }

@media (max-width: 991.98px) {
    .app-sidebar { display: none; }
}

.app-main .table-responsive { width: 100%; }

/* ---------- Reusable page patterns ---------- */

/* Breadcrumb / title / description / actions header, used at the top of every content page. */
.app-page-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1.5rem;
}

.app-page-header .breadcrumb { margin-bottom: 0.35rem; font-size: 0.8rem; }
.app-page-header h1 { font-size: 1.4rem; font-weight: 700; margin: 0 0 0.2rem; }
.app-page-header .app-page-description { color: var(--app-muted); font-size: 0.9rem; margin: 0; max-width: 60ch; }
.app-page-header .app-page-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }

/* Compact metric tile — dashboard summary row + 財務公開's own balance/income/expense/net row
   (finance/list.html reuses this same component). */
.app-stat-card {
    background: var(--app-surface);
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius);
    padding: 1rem 1.1rem;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    box-shadow: var(--app-shadow);
    /* Lets this card shrink below its content's natural width inside a narrow grid column
       (Dashboard / Shared UI Polish §8/§9) — without this, a flex/grid item's default
       min-width:auto can force the column, and everything beside it, wider than the viewport. */
    min-width: 0;
}

/* Dashboard / Shared UI Polish (2026-08-20 product owner decision §5): the label row (icon +
   caption, e.g. "帳面餘額"/"本月收入") was too small/unemphasized — bumped into the requested
   1–1.1rem / 600–700 range. The number itself (.app-stat-value below) is deliberately left at its
   existing size — the product owner explicitly did not want the summary figures themselves any
   larger, only their captions. */
.app-stat-card .app-stat-label {
    font-size: 1rem;
    font-weight: 600;
    color: var(--app-muted);
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.app-stat-card .app-stat-value {
    font-size: 1.6rem;
    font-weight: 700;
    color: var(--app-text);
    font-variant-numeric: tabular-nums;
    /* Mobile amount overflow fix (Dashboard / Shared UI Polish §8/§9): a two-column mobile stat
       grid (col-6) leaves noticeably less room per card than desktop's four-across layout. clamp()
       lets the figure shrink only as far as the viewport actually forces it to, never below a
       floor that becomes unreadable, and never above the original 1.6rem on desktop (the clamp
       ceiling matches the previous fixed size exactly, so desktop is visually unchanged). Digits
       never get clipped via a CSS overflow-hiding trick — the card shrinks the text instead. */
    font-size: clamp(1.05rem, 4.4vw, 1.6rem);
    white-space: nowrap;
    max-width: 100%;
    min-width: 0;
}

.app-stat-card .app-stat-icon {
    color: var(--app-primary);
    font-size: 1.1rem;
}

/* Finance's own 4-across balance/income/expense/net row (finance/list.html) — a dedicated class
   on that row's wrapper only, so this very-narrow-viewport single-column fallback never affects
   any other .row/.col-6 grid elsewhere in the app. Specificity note: ".app-stat-row > .col-6"
   (two classes) already outranks the plain ".col-6" Bootstrap utility (one class), so no
   !important is needed. Applied only below 360px — comfortably narrower than the smallest
   viewport this batch was asked to verify (320px) needs it, and Tabler's own two-column layout
   already reads fine at 375px/390px/430px with the clamp() fix above alone. */
@media (max-width: 359.98px) {
    .app-stat-row > .col-6 {
        flex: 0 0 100%;
        max-width: 100%;
    }
}

/* General content surface — a slightly more opinionated .card for list/detail bodies. */
.app-content-card {
    background: var(--app-surface);
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius);
    box-shadow: var(--app-shadow);
}

/* Empty-state block for list pages with no rows. */
.app-empty-state {
    text-align: center;
    padding: 2.5rem 1.5rem;
    color: var(--app-muted);
}

.app-empty-state .app-empty-icon {
    font-size: 2rem;
    color: var(--app-border);
    margin-bottom: 0.5rem;
    display: block;
}

.app-empty-state p { margin: 0 auto; max-width: 40ch; }

/* Filter toolbar above a list table. */
.app-filter-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
    margin-bottom: 1rem;
}

/* Labeled filter grid (Phase 3, 2026-08-18 §九): every control gets a visible label — the
   previous unlabeled month <input> read as an empty box, especially on mobile where there's no
   neighboring context to imply what it is. 2 columns on mobile (still touch-friendly, and reads
   better than a single very-wide column of tiny controls), reflowing to one row on desktop. */
.app-filter-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0.75rem 0.75rem;
    align-items: end;
    margin-bottom: 1rem;
}

.app-filter-grid .app-filter-field label {
    display: block;
    font-size: 0.78rem;
    color: var(--app-muted);
    margin-bottom: 0.25rem;
}

.app-filter-grid .app-filter-submit { grid-column: span 2; }

@media (min-width: 768px) {
    .app-filter-grid {
        grid-template-columns: repeat(4, minmax(0, auto));
    }
    .app-filter-grid .app-filter-submit { grid-column: auto; }
}

/* ---------- Shared spacing system: detail card body / filter card body ----------
   Global Resident UI Spacing Cleanup (2026-08-20 product owner decision, iPhone manual
   acceptance): two semantic classes, applied only to the specific .card-body elements that need
   them — deliberately NOT a blanket ".card-body { padding: ... }" rule, which would double-pad
   every card that intentionally already uses the existing "card-body.p-0 + list-group-flush"
   edge-to-edge pattern (e.g. the dashboard's 最新公告/進行中的投票 cards) or a plain table/
   list-group row that already carries its own reasonable padding. Every page in this codebase
   already falls into exactly one of three buckets:
     1. Edge-to-edge list/table cards (card-body.p-0 + list-group-flush, or a bare table inside
        .table-responsive) — never touched by either class below.
     2. Detail/info cards (a <dl class="row"> label/value block and/or free-text paragraphs) —
        get .app-detail-card-body.
     3. Filter/search panels (.app-filter-bar or .app-filter-grid) — get .app-filter-card-body.
   Both classes target the same horizontal starting point as the card's own header
   (.app-content-card > .card-header, 1.25rem mobile / 1.25–1.5rem desktop — see above), which is
   the actual root cause this batch fixes: the header's padding was made explicit in an earlier
   batch, but body content was left to whatever padding a plain Bootstrap .card-body (or, worse,
   a <dl class="row">'s own negative horizontal margin — see below) happened to produce, so the
   two edges drifted out of alignment. */
.app-detail-card-body {
    padding-left: 1.25rem;
    padding-right: 1.25rem;
    padding-top: 1rem;
    padding-bottom: 1.1rem;
}

@media (min-width: 768px) {
    .app-detail-card-body {
        padding-left: 1.5rem;
        padding-right: 1.5rem;
    }
}

/* Bootstrap's .row always carries a negative horizontal margin (half its --bs-gutter-x, 0.75rem
   by default) meant to be cancelled out by a *parent* .container's own matching padding — a
   <dl class="row"> dropped directly into a .card-body (which has no such compensating padding of
   its own) instead gets pulled outward, undoing most of .app-detail-card-body's padding above and
   landing the label/value text almost flush with the card border. Neutralizing just the row's own
   margin (never its .col-* children's padding, which becomes the — correct, and now consistent —
   small additional indent between the card edge and the label text) fixes this with no effect on
   any .row used anywhere else (admin filter grids, Bootstrap layout rows, etc.), since the
   selector only ever matches a .row that is a direct child of .app-detail-card-body. */
.app-detail-card-body > .row {
    margin-left: 0;
    margin-right: 0;
}

.app-filter-card-body {
    padding: 1rem 1.25rem;
}

@media (min-width: 768px) {
    .app-filter-card-body {
        padding: 1.1rem 1.5rem;
    }
}

/* Danger zone panel for irreversible actions (permanent delete, revoke admin, ...). */
.app-danger-zone {
    border: 1px solid var(--app-danger-soft);
    background: var(--app-danger-soft);
    border-radius: var(--app-radius);
    padding: 1rem 1.2rem;
}

.app-danger-zone h4 {
    color: var(--app-danger);
    font-size: 0.9rem;
    margin: 0 0 0.35rem;
}

.app-danger-zone p { font-size: 0.85rem; color: var(--app-text); margin: 0 0 0.75rem; }

/* Status badge — a thin wrapper so status color mapping lives in one place per module,
   never a bare bg-success/bg-danger scattered ad hoc per template. */
.app-status-badge { font-weight: 600; }

/* ---------- Auth (login) card ---------- */

/* Phase 2 (2026-08-18 §一-2) foundation fix: `align-items: center` on a shell taller than a
   short mobile viewport (browser chrome visible) pushed the brand mark above the fold with no
   scroll affordance, so it read as "clipped". flex-start + a safe-area-aware top gap (never a
   hardcoded magic-number margin) fixes this: the shell is always top-anchored and the page
   simply scrolls if content is taller than the viewport, exactly like every other page here. */
.auth-shell {
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: max(2rem, calc(env(safe-area-inset-top, 0px) + 1.5rem)) 1rem 2rem;
}

@media (min-height: 640px) {
    .auth-shell { align-items: center; }
}

/* Modifier for long forms (registration) — always top-anchored, regardless of viewport height;
   vertical centering only suits the short single-card auth screens (login/reset/activation). */
.auth-shell-wide { align-items: flex-start !important; }

.auth-card { width: 100%; max-width: 400px; }

.auth-card .brand-mark {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    color: var(--app-primary);
    font-weight: 700;
    font-size: 1.3rem;
    margin-bottom: 1.5rem;
}

/* ---------- Dashboard list cards (home.html) ---------- */

.dashboard-card-list a.list-group-item {
    border-left: none;
    border-right: none;
}

.dashboard-card-list a.list-group-item:first-child { border-top: none; }
.dashboard-card-list a.list-group-item:last-child { border-bottom: none; }

/* ---------- Admin module launcher (admin/index.html) ---------- */

.module-card {
    display: flex;
    align-items: flex-start;
    gap: 0.9rem;
    padding: 1.1rem 1.2rem;
    text-decoration: none;
    color: var(--app-text);
    height: 100%;
}

.module-card .module-icon {
    flex: none;
    width: 2.6rem;
    height: 2.6rem;
    border-radius: var(--app-radius-sm);
    background: var(--app-primary-soft);
    color: var(--app-primary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.3rem;
}

.module-card h3 { font-size: 1rem; font-weight: 700; margin: 0 0 0.2rem; color: var(--app-text); }
.module-card p { font-size: 0.83rem; color: var(--app-muted); margin: 0 0 0.4rem; }
.module-card .module-enter { font-size: 0.82rem; font-weight: 600; color: var(--app-primary); }
.module-card:hover { color: var(--app-text); }
.module-card:hover .module-enter { color: var(--app-primary-hover); }

/* ---------- Desktop table <-> mobile record card (Phase 2, 2026-08-18 §七) ---------- */
/*
 * The reusable answer to "a wide admin table becomes unreadable on a phone": render the SAME
 * th:each loop TWICE from the same Model data — once as a <table> (shown md and up), once as a
 * stack of .record-card blocks (shown below md) — never duplicate business state into JS, never
 * rely on horizontal scroll as the only mobile behavior for a primary list. Bootstrap's own
 * .d-none/.d-md-block utility classes decide which one is visible at any given width; both exist
 * in the DOM, so there's exactly one server-rendered source of truth either way.
 *
 *   <div class="table-responsive d-none d-md-block"> ... table ... </div>
 *   <div class="d-md-none record-card-list"> <div class="record-card" th:each="..."> ... </div> </div>
 */
.record-card-list { display: flex; flex-direction: column; gap: 0.65rem; }

.record-card {
    background: var(--app-surface);
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius);
    padding: 0.9rem 1rem;
}

.record-card .record-card-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 0.6rem;
    margin-bottom: 0.5rem;
}

.record-card .record-card-title { font-weight: 700; font-size: 0.95rem; color: var(--app-text); }

.record-card dl.record-card-fields {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.2rem 0.6rem;
    margin: 0 0 0.65rem;
    font-size: 0.85rem;
}

.record-card dl.record-card-fields dt { color: var(--app-muted); font-weight: 500; }
.record-card dl.record-card-fields dd { margin: 0; color: var(--app-text); word-break: break-word; }

.record-card .record-card-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--app-border);
}

.record-card .record-card-actions .btn { flex: 1 1 auto; min-height: 2.4rem; }

/* ---------- Status badges ---------- */
/*
 * Semantic mapping is fixed system-wide (Phase 2, 2026-08-18 §十) — every module reuses the same
 * four buckets rather than inventing its own palette per screen. Built on Tabler's own "-lt"
 * (light) badge background utilities, never a bespoke color:
 *   success  -> bg-success-lt   (ACTIVE / 顯示中 / 已投票 / RESIDENT visible)
 *   warning  -> bg-warning-lt   (PENDING / 待審核 / 未讀 / ADMIN_ONLY)
 *   secondary-> bg-secondary-lt (DISABLED / 已隱藏 / 已停用)
 *   danger   -> bg-danger-lt    (DELETED / 永久操作)
 * Every badge always carries a text label alongside its color — never color-only status.
 *
 * Phase 3 (2026-08-18 §八) polish: manual review found Tabler's stock "-lt" pairing readable but
 * inconsistent in contrast across hues. These overrides replace only the background/text pair for
 * the badge classes this app actually uses, on top of the same tokens everywhere else, each text
 * color picked to clear WCAG AA (4.5:1) against its own soft background — never color alone, the
 * text label is unconditional (see every template's badge markup).
 */
.badge.bg-success-lt { background-color: var(--app-success-soft) !important; color: var(--app-success-text) !important; }
.badge.bg-warning-lt { background-color: var(--app-warning-soft) !important; color: var(--app-warning-text) !important; }
.badge.bg-danger-lt { background-color: var(--app-danger-soft) !important; color: var(--app-danger-text) !important; }
.badge.bg-info-lt { background-color: var(--app-info-soft) !important; color: var(--app-info-text) !important; }
.badge.bg-secondary-lt { background-color: var(--app-surface-sunken) !important; color: var(--app-muted-text) !important; }

/* ---------- Form card width (Phase 3, 2026-08-18 §六) ---------- */
/*
 * Replaces ~21 inline `style="max-width: NNNpx"` literals (500/560/640/700/760/860, one
 * hand-picked number per page) with a single shared width for every single-record create/edit/
 * confirm form card. Sits inside the page's own .app-content-narrow (1040px) container, so the
 * page itself is already capped — this narrows the individual card a bit further to a width where
 * a labeled input/select doesn't stretch edge-to-edge on desktop.
 */
.app-form-card { max-width: 760px; }

/* Two-column field grid inside a form card, for short paired fields (分類/可見性、收支類型/分類, ...)
   that don't need the full card width each. Falls back to a single column below md, same as every
   other responsive grid in this app — never a page-specific breakpoint. */
.app-form-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0 1rem;
}

@media (min-width: 768px) {
    .app-form-grid.app-form-grid-2col {
        grid-template-columns: 1fr 1fr;
    }
}

/* ---------- Form action bar (Phase 3, 2026-08-18 §七) ---------- */
/*
 * Every ordinary create/edit form ends with exactly this: a secondary Cancel and a primary
 * Save/Create, right-aligned on desktop, called out here as one class so the pattern is visibly
 * shared rather than copy-pasted utility classes per page. Danger actions (delete/purge/deactivate
 * confirmation) are deliberately NOT this class — those live in their own .app-danger-zone panel
 * as a standalone form, never mixed into a page's normal Save flow.
 */
.app-form-actions {
    display: flex;
    gap: 0.6rem;
    justify-content: flex-end;
    margin-top: 1.5rem;
}

@media (max-width: 575.98px) {
    .app-form-actions {
        flex-direction: column-reverse;
    }
    .app-form-actions .btn {
        width: 100%;
    }
}

/* ---------- Poll option rows (Phase 3, 2026-08-18 §五) ---------- */
/*
 * Turns a bare "radio + text in open whitespace" list into clearly delimited, clickable rows.
 * Radio name/value/id and the surrounding <form> are completely untouched — this only wraps the
 * existing <label> so the whole row (not just the small circle) is the click/tap target, and adds
 * a visible hover/focus/checked state.
 */
.poll-option-row {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius-sm);
    padding: 0.75rem 1rem;
    margin-bottom: 0.6rem;
    cursor: pointer;
    transition: border-color 0.15s ease, background-color 0.15s ease;
}

.poll-option-row:hover { border-color: var(--app-primary); background: var(--app-primary-soft); }

.poll-option-row:has(input:focus-visible) {
    outline: 2px solid var(--app-primary);
    outline-offset: 1px;
}

.poll-option-row:has(input:checked) {
    border-color: var(--app-primary);
    background: var(--app-primary-soft);
    font-weight: 600;
}

.poll-option-row input[type="radio"] { flex: none; margin: 0; }

/* ---------- Typography scale (Phase 3, 2026-08-18 §十五) ---------- */
/*
 * A small set of reusable heading/text classes so every page reaches for the same size instead of
 * an ad hoc h4/h5/h6 chosen per template. .app-page-header h1 (page title) is already sized above;
 * these cover the next tier down — section headings inside a content card, and small metadata
 * text — so no page invents its own font-size for either.
 */
.app-section-title {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--app-text);
    margin: 0;
}

.app-meta-text {
    font-size: 0.82rem;
    color: var(--app-muted);
}

/* Dashboard / Shared UI Polish (2026-08-20 product owner decision §11/§12): every content card's
   section heading across the app ("收支明細", "歷史紀錄", "附件", "修改歷史", ...) already used the
   exact same markup convention — <h2 class="h6 mb-0"> (or the bare Bootstrap ".h6" utility) inside
   a direct .card-header child of .app-content-card — but relied on Tabler's small default h6
   sizing, which read as unemphasized and sat too close to the card's left edge in product owner
   screenshot review. Fixed centrally here, once, rather than editing each of the ~17 templates
   that already share this markup: every one of them inherits the fix automatically. Table column
   headers (<th>) are a completely different element and are never matched by this selector — see
   §11's explicit "不要把 table column headers 一起放大" — and the separate uppercase/muted in-body
   section dividers (e.g. announcements/documents detail pages' "附件" heading, which additionally
   carries .text-uppercase.text-muted and is never inside a .card-header) are untouched too, since
   they serve a deliberately subtler, different visual role. */
.app-content-card > .card-header {
    padding-left: 1.25rem;
    padding-right: 1.25rem;
    /* Shared UI Spacing Polish (2026-08-20 product owner decision §1): the heading still sat too
       close to the card's top edge after the previous batch's font-size/weight bump alone —
       explicit vertical padding on the header row itself, never on the card, the body below, or
       any text's own margin. Applies identically on desktop and mobile (no breakpoint override
       needed — this is a fixed, modest nudge, not a responsive concern). */
    padding-top: 1.05rem;
    padding-bottom: 0.9rem;
}

.app-content-card > .card-header h2.h6,
.app-content-card > .card-header .h6 {
    font-size: 1.05rem;
    font-weight: 700;
}

/* ---------- Detail page content body (Phase 3, 2026-08-18 §四) ---------- */
/*
 * Was a one-off inline <style> block copy-pasted into every detail page (announcement/poll/
 * comment); now a single shared class. `max-width` caps long free-text content to a comfortable
 * reading measure — the surrounding .app-content-narrow already keeps the whole page from
 * stretching edge-to-edge, this goes one step further for the paragraph text specifically, since
 * even a 1040px column is wider than ideal for continuous prose.
 */
.content-body {
    white-space: pre-wrap;
    max-width: 70ch;
}

/* ---------- Home dashboard cards (Phase 3, 2026-08-18 §三) ---------- */
/*
 * Home's two side-by-side "latest announcements / open polls" cards must NOT be forced to equal
 * height — a `row-cols` grid with `align-items: stretch` (Bootstrap's default) does exactly that,
 * so the short column gets padded with a big empty lower half. `align-items: start` lets each
 * card's height follow its own content while the two columns still share the same top edge/grid.
 */
.app-dashboard-row {
    align-items: start;
}

/* Manual Acceptance Fix Batch 1, item 11 (2026-08-19 product owner screenshot review): the
   dashboard cards' title and summary-metrics area sat too close to the card's left edge. Scoped
   narrowly to the heading and summary-metrics rows only — the transaction/list rows below (e.g.
   財務概況's list-group-flush) deliberately keep their existing spacing, unchanged. Applied
   consistently to all four dashboard cards (最新公告/進行中的投票/本期管理費/財務概況), not just
   the one the screenshot showed. */
/* Dashboard / Shared UI Polish (2026-08-20 product owner decision §5B): the section titles
   (最新公告/進行中的投票/本期管理費/財務概況) sat at Tabler's small default card-header text size
   — bumped into the requested 1–1.1rem / 600–700 range. Scoped to the header row itself (icon +
   title span + optional badge) — the card-body list rows underneath are untouched. Bootstrap's
   .badge already sets its own font-size relative to its parent (font-size: .75em), so it inherits
   this new baseline proportionally and stays visually a small badge, never list-item-sized. */
/* Specificity note (caught during Shared UI Spacing Polish, 2026-08-20): dashboard cards are
   ".app-content-card > .card-header.app-dashboard-card-header" — both this rule and the generic
   ".app-content-card > .card-header" rule above match the same element. A bare ".app-dashboard-
   card-header" selector (one class, specificity 0,1,0) loses its padding-left/right to the
   generic rule (two classes, 0,2,0) regardless of source order; scoping this selector to
   ".card-header.app-dashboard-card-header" (two classes, 0,2,0) ties it instead, and this rule's
   later position in the file then wins that tie — restoring the deliberately wider 1.5rem
   dashboard gutter. */
.card-header.app-dashboard-card-header {
    padding-left: 1.5rem;
    padding-right: 1.5rem;
    font-size: 1.05rem;
    font-weight: 700;
    /* Shared UI Spacing Polish (2026-08-20 product owner decision §1) — same vertical nudge as
       the generic .app-content-card > .card-header rule above, kept as an explicit duplicate
       here (rather than folded into that selector) since .app-dashboard-card-header is applied
       directly to the header div itself on dashboard cards, not via the h2.h6 markup convention
       the generic rule targets. */
    padding-top: 1.05rem;
    padding-bottom: 0.9rem;
}

/* Global Resident UI Spacing Cleanup (2026-08-20 product owner decision §16, Dashboard review):
   bumped from 0.75rem to align with the rest of this batch's card-body horizontal padding
   convention. Kept as its own class (rather than switched to .app-detail-card-body) because this
   card-body is mixed content — the 財務概況 card also has a list-group-flush transactions list
   below the metrics row, which must keep relying on its own list-group-item padding alone, not
   inherit a second layer of card-body padding (see home.html's own comment at that card). Margin
   and padding coexist safely on this same element — the row's own -0.25rem (.row.g-2) margin and
   this class's padding do not need a separate neutralizing rule the way a bare <dl class="row">
   dropped straight into an unpadded card-body does (see .app-detail-card-body > .row above). */
.app-dashboard-card-metrics {
    padding-left: 1rem;
    padding-right: 1rem;
}
