/* ==========================================================================
   Finance app — base stylesheet (plain CSS, served via AssetMapper)

   Layout: fixed left sidebar + scrolling content area. On small screens the
   sidebar slides in over a backdrop, toggled by the topbar hamburger.
   Theme: light by default; [data-theme="dark"] on <html> flips the tokens.
   ========================================================================== */

:root,
:root[data-theme="light"] {
    --bg:            #f5f6f8;
    --surface:       #ffffff;
    --surface-alt:   #f8fafc;
    --sidebar-bg:    #ffffff;
    --hover:         #f1f5f9;
    --border:        #e5e8ec;
    --border-strong: #d5dae1;
    --text:          #1f2430;
    --text-muted:    #6b7280;
    --primary:       #4f46e5;
    --primary-hover: #4338ca;
    --primary-soft:  #eef2ff;
    --on-primary:    #ffffff;
    --danger:        #dc2626;
    --danger-soft:   #fef2f2;
    --danger-border: #fecaca;
    --success:       #059669;
    --success-soft:  #ecfdf5;
    --success-border:#a7f3d0;
    --warning:       #d97706;
    --warning-soft:  #fffbeb;
    --warning-border:#fde68a;
    --info:          #2563eb;
    --info-soft:     #eff6ff;
    --info-border:   #bfdbfe;
    --chart-grid:    rgba(15, 23, 42, .08);
    --chart-zero:    #9ca3af;

    --radius:        12px;
    --radius-sm:     8px;
    --shadow:        0 1px 2px rgba(16, 24, 40, .04), 0 1px 3px rgba(16, 24, 40, .06);
    --shadow-md:     0 4px 16px rgba(16, 24, 40, .08);
    --shadow-lg:     0 12px 32px rgba(16, 24, 40, .16);
    --font:          -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    --sidebar-w:     248px;
}

:root[data-theme="dark"] {
    --bg:            #0e1015;
    --surface:       #171a21;
    --surface-alt:   #1c202a;
    --sidebar-bg:    #12141a;
    --hover:         #1f2430;
    --border:        #272c37;
    --border-strong: #333a48;
    --text:          #e6e8ec;
    --text-muted:    #9aa2b1;
    --primary:       #6366f1;
    --primary-hover: #7c7ff5;
    --primary-soft:  #21243a;
    --on-primary:    #ffffff;
    --danger:        #f87171;
    --danger-soft:   #2a1a1c;
    --danger-border: #5b2a2c;
    --success:       #34d399;
    --success-soft:  #12241f;
    --success-border:#1f4d3f;
    --warning:       #fbbf24;
    --warning-soft:  #2a2313;
    --warning-border:#5a4a1f;
    --info:          #60a5fa;
    --info-soft:     #14233a;
    --info-border:   #274a6b;
    --chart-grid:    rgba(226, 232, 240, .10);
    --chart-zero:    #6b7280;
    --shadow:        0 1px 2px rgba(0, 0, 0, .3), 0 1px 3px rgba(0, 0, 0, .4);
    --shadow-md:     0 4px 16px rgba(0, 0, 0, .4);
    --shadow-lg:     0 12px 32px rgba(0, 0, 0, .55);
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    font-family: var(--font);
    font-size: 15px;
    line-height: 1.5;
    color: var(--text);
    background: var(--bg);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

/* ---- Headings -------------------------------------------------------------- */
h1 { font-size: 1.7rem; font-weight: 700; margin: 0 0 1.25rem; letter-spacing: -.02em; }
h2 { font-size: 1.2rem; font-weight: 650; margin: 0 0 .9rem; letter-spacing: -.01em; }
h3 { font-size: 1.02rem; font-weight: 600; margin: 1.25rem 0 .6rem; }

a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ==========================================================================
   App shell: sidebar + content
   ========================================================================== */
.app-shell { display: flex; min-height: 100vh; }

.sidebar {
    width: var(--sidebar-w);
    flex: none;
    position: sticky;
    top: 0;
    align-self: flex-start;
    height: 100vh;
    display: flex;
    flex-direction: column;
    background: var(--sidebar-bg);
    border-right: 1px solid var(--border);
    z-index: 50;
}

.sidebar-brand {
    display: flex;
    align-items: center;
    gap: .65rem;
    height: 64px;
    padding: 0 1.25rem;
    font-weight: 700;
    font-size: 1.1rem;
    letter-spacing: -.01em;
    color: var(--text);
    border-bottom: 1px solid var(--border);
}
.sidebar-logo {
    display: inline-grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border-radius: 9px;
    background: var(--primary);
    color: #fff;
    font-weight: 700;
    font-size: 1rem;
}

.sidebar-nav {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 1rem .75rem;
    overflow-y: auto;
    flex: 1 1 auto;
}

.nav-link {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .6rem .75rem;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    font-weight: 550;
    font-size: .93rem;
    text-decoration: none;
    transition: background .12s ease, color .12s ease;
}
.nav-link:hover { background: var(--hover); color: var(--text); text-decoration: none; }
.nav-link.is-active { background: var(--primary-soft); color: var(--primary); }
.nav-link.is-active .nav-icon { color: var(--primary); }

.nav-icon { width: 20px; height: 20px; flex: none; color: var(--text-muted); }
.nav-link:hover .nav-icon { color: var(--text); }

.sidebar-footer {
    padding: .75rem;
    border-top: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.sidebar-user { padding: 0 .75rem .5rem; font-size: .8rem; color: var(--text-muted); overflow-wrap: anywhere; }

.nav-link-logout:hover { background: var(--danger-soft); color: var(--danger); }
.nav-link-logout:hover .nav-icon { color: var(--danger); }

/* Logout is a POST form (CSRF-protected), so its trigger is a <button> that has to sit in
   the footer flex column and read exactly like the <a class="nav-link"> siblings above it. */
.logout-form { margin: 0; }
button.nav-link {
    width: 100%;
    background: transparent;
    border: none;
    font: inherit;
    font-weight: 550;
    font-size: .93rem;
    text-align: left;
    cursor: pointer;
}

/* Theme toggle button in the sidebar footer */
.theme-toggle {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .6rem .75rem;
    width: 100%;
    background: transparent;
    border: none;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    font: inherit;
    font-weight: 550;
    font-size: .93rem;
    cursor: pointer;
    text-align: left;
    transition: background .12s ease, color .12s ease;
}
.theme-toggle:hover { background: var(--hover); color: var(--text); }
.theme-toggle svg { width: 20px; height: 20px; flex: none; }
.theme-toggle .icon-moon { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-sun  { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: inline; }

/* Content column */
.app-body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
}
.app-main { flex: 1 1 auto; }

/* Mobile topbar (hidden on desktop) */
.topbar {
    display: none;
    align-items: center;
    gap: .75rem;
    height: 56px;
    padding: 0 1rem;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    position: sticky;
    top: 0;
    z-index: 40;
}
.topbar-brand { display: flex; align-items: center; gap: .5rem; font-weight: 700; }
.menu-btn {
    display: inline-grid;
    place-items: center;
    width: 40px;
    height: 40px;
    padding: 0;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text);
    cursor: pointer;
}
.menu-btn:hover { background: var(--hover); }
.menu-btn svg { width: 22px; height: 22px; }

.sidebar-backdrop {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, .45);
    z-index: 45;
}

/* ---- Responsive: collapse the sidebar into an off-canvas drawer ------------ */
@media (max-width: 860px) {
    .topbar { display: flex; }

    .sidebar {
        position: fixed;
        top: 0;
        left: 0;
        height: 100vh;
        transform: translateX(-100%);
        transition: transform .22s ease;
        box-shadow: var(--shadow-lg);
    }
    .app-shell.is-open .sidebar { transform: translateX(0); }
    .app-shell.is-open .sidebar-backdrop { display: block; }
}

/* ==========================================================================
   Page containers & cards
   ========================================================================== */
/* Every page's outermost wrapper opts in by name — a new screen that forgets to join this
   list renders edge-to-edge with no padding. */
.dashboard,
.review,
.transactions,
.import,
.accounts,
.categories,
.category-rules,
.runway,
.runway-places,
.runway-form,
.runway-place-form,
.account-form,
.category-form,
.category-rule-form {
    max-width: 1200px;
    margin: 0 auto;
    padding: 2rem 2rem 3rem;
}

@media (max-width: 640px) {
    .dashboard, .review, .transactions, .import, .accounts, .categories,
    .category-rules, .runway, .runway-places, .runway-form, .runway-place-form,
    .account-form, .category-form, .category-rule-form {
        padding: 1.25rem 1rem 2rem;
    }
}

/* Surfaces: content blocks rendered as cards */
.accounts > table,
.categories > table,
.category-rules > table,
.review-table,
.transactions-table,
.import-summary,
.dashboard-filters,
.chart-bar,
.chart-donut,
.chart-savings,
.runway-places > table,
.runway-hero,
.runway-controls,
.runway-panel,
.runway-form form,
.account-form form,
.category-form form,
.category-rule-form form,
.login-container form {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}

/* Checkbox fields: input inline with its label, not stacked above it */
.form-check {
    display: flex;
    align-items: center;
    gap: .1rem;
    margin-bottom: 1rem;
}
.form-check label {
    display: inline;
    margin-bottom: 0;
    font-weight: 550;
}

/* Inline info tooltip icon next to a field label (native title tooltip) */
.field-info {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1rem;
    height: 1rem;
    margin-left: .3rem;
    font-size: .68rem;
    font-style: italic;
    font-weight: 700;
    line-height: 1;
    color: var(--text-muted);
    border: 1px solid var(--border-strong);
    border-radius: 50%;
    cursor: help;
    vertical-align: middle;
    user-select: none;
}

/* Form field help text: supportive, not attention-grabbing */
.help-text {
    font-size: .8rem;
    line-height: 1.4;
    color: var(--text-muted);
    margin: .3rem 0 0;
}

/* Categories list rendered as a parent → children tree */
.categories .category-name {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
}
.categories .category-swatch {
    width: 14px;
    height: 14px;
    border-radius: 3px;
    border: 1px solid var(--border-strong);
    flex: 0 0 auto;
}
.categories .category-row-parent > td:first-child {
    font-weight: 650;
}
.categories .category-row-child > td:first-child {
    padding-left: 1.75rem;
    color: var(--text-muted);
}
.categories .category-tree-branch {
    color: var(--text-muted);
    font-weight: 400;
}

/* "New …" links on index pages act as buttons */
.accounts > a[href],
.categories > a[href],
.category-rules > a[href],
.runway-places > a[href] {
    display: inline-flex;
    align-items: center;
    margin-bottom: 1rem;
    padding: .55rem 1rem;
    background: var(--primary);
    color: var(--on-primary);
    font-weight: 600;
    font-size: .9rem;
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow);
    text-decoration: none;
    transition: background .12s ease;
}
.accounts > a[href]:hover,
.categories > a[href]:hover,
.category-rules > a[href]:hover,
.runway-places > a[href]:hover { background: var(--primary-hover); text-decoration: none; }

/* ---- Tables ---------------------------------------------------------------- */
table {
    width: 100%;
    border-collapse: collapse;
    overflow: hidden;
    font-size: .92rem;
}

thead th {
    text-align: left;
    font-weight: 600;
    font-size: .76rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--text-muted);
    background: var(--surface-alt);
    padding: .7rem 1rem;
    border-bottom: 1px solid var(--border);
}

/* Sortable column headers (sortable-table Stimulus controller) */
thead th[data-action*="sortable-table#sort"] {
    cursor: pointer;
    user-select: none;
    position: relative;
    padding-right: 1.4rem;
}
thead th[data-action*="sortable-table#sort"]:hover { color: var(--text); background: var(--hover); }
thead th[data-action*="sortable-table#sort"]::after {
    content: '\2195';
    position: absolute;
    right: .5rem;
    opacity: .35;
    font-size: .85em;
}
thead th.sort-asc::after { content: '\2191'; opacity: 1; color: var(--primary); }
thead th.sort-desc::after { content: '\2193'; opacity: 1; color: var(--primary); }

tbody td { padding: .7rem 1rem; border-bottom: 1px solid var(--border); vertical-align: middle; }
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--surface-alt); }

/* Numeric amounts in the review table */
.review-table td:nth-child(3) { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Compact inline "assign category" control in the review table */
.assign-cell {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .6rem;
}
.assign-cell select { max-width: 200px; min-width: 150px; margin: 0; }
.assign-rule {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    font-weight: 500;
    font-size: .85rem;
    color: var(--text-muted);
    white-space: nowrap;
    cursor: pointer;
}
.assign-rule input { margin: 0; }
.btn-assign {
    padding: .55rem 1.15rem;
    font-size: .9rem;
    background: var(--primary);
    color: var(--on-primary);
}
.btn-assign:hover { background: var(--primary-hover); }

/* The batch's submit bars: one above the table, one below, same form. */
.bulk-assign-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .75rem;
    margin-bottom: .9rem;
}
.bulk-assign-bar-bottom { margin: .9rem 0 0; }
.bulk-assign-hint { font-size: .85rem; color: var(--text-muted); }

/* The payer/payee a statement named, shown under the description on every list that renders
   one. Its own line, because it is a second column of the transaction and not a continuation
   of the description — which is exactly what the importers stopped pretending it was. */
.tx-counterparty {
    display: block;
    font-size: .82rem;
    color: var(--text-muted);
}

/* Rows the app prefilled from an existing rule — tinted so they're scannable, badged so the
   inference is checkable (the badge's title carries the pattern, match type and account scope). */
.review-table tbody tr.row-auto > td { background: var(--info-soft); }
.review-table tbody tr.row-auto > td:first-child { box-shadow: inset 3px 0 0 var(--info); }
.auto-badge {
    display: inline-block;
    margin-left: .5rem;
    padding: .05rem .45rem;
    border-radius: 999px;
    background: var(--info);
    color: var(--on-primary);
    font-size: .7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .03em;
    vertical-align: middle;
    cursor: help;
}
/* The class's display would otherwise win over the UA's [hidden] rule when auto-match hides it. */
.auto-badge[hidden] { display: none; }

/* Let wide tables scroll instead of breaking the layout on small screens */
.accounts > table,
.categories > table,
.category-rules > table,
.review-table,
.transactions-table { display: table; }

/* ---- Forms ----------------------------------------------------------------- */
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="date"],
input[type="file"],
select,
textarea {
    width: 100%;
    max-width: 380px;
    padding: .5rem .65rem;
    font: inherit;
    font-size: .92rem;
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    transition: border-color .12s ease, box-shadow .12s ease;
}
input:focus, select:focus, textarea:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--primary-soft);
}

input[type="checkbox"], input[type="radio"] { width: auto; accent-color: var(--primary); margin-right: .4rem; }

label { font-weight: 550; font-size: .9rem; color: var(--text); }

/* Symfony form_row wraps each field in a bare <div>; give them spacing */
form > div, .form-group { margin-bottom: 1rem; }
form > div > label, .form-group label { display: block; margin-bottom: .35rem; }

/* Standalone forms get card padding */
.account-form form,
.category-form form,
.category-rule-form form,
.login-container form { padding: 1.5rem; max-width: 480px; }

/* Buttons */
button, .btn {
    display: inline-block;
    padding: .55rem 1.1rem;
    font: inherit;
    font-size: .9rem;
    font-weight: 600;
    color: var(--on-primary);
    background: var(--primary);
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background .12s ease;
}
button:hover, .btn:hover { background: var(--primary-hover); }
button:active { transform: translateY(1px); }

/* Delete buttons inside table rows read as destructive/secondary */
td form button {
    background: transparent;
    color: var(--danger);
    padding: .3rem .6rem;
    font-size: .85rem;
}
td form button:hover { background: var(--danger-soft); }

/* Edit links next to delete in tables */
td a[href] { font-size: .85rem; font-weight: 550; margin-right: .5rem; }

/* Cancel links on forms */
.account-form > a,
.category-form > a,
.category-rule-form > a {
    display: inline-block;
    margin-top: 1rem;
    color: var(--text-muted);
    font-size: .9rem;
}

/* ---- Dashboard filters ----------------------------------------------------- */
/* A single compact toolbar row: label + control side by side, not stacked. */
.dashboard-filters {
    padding: .8rem 1.1rem;
    margin-bottom: 1.75rem;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .75rem 1.25rem;
}

.filter-field {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: .6rem;
    min-width: 0;
    margin-bottom: 0; /* overrides the generic `form > div` spacing */
}
.filter-field-grow { flex: 1 1 auto; }

/* Uppercase micro-label shared by the year select and the accounts group */
.filter-field > label,
.filter-label {
    display: inline-block;
    margin-bottom: 0; /* overrides the generic `form > div > label` spacing */
    font-size: .7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-muted);
    white-space: nowrap;
}

.filter-divider {
    width: 1px;
    align-self: stretch;
    margin: .1rem 0;
    background: var(--border);
}

/* Year select: native chrome replaced by a themed chevron on a wrapper. */
.select-wrap { position: relative; display: inline-flex; align-items: center; }
.select-wrap::after {
    content: '';
    position: absolute;
    right: .75rem;
    width: .38rem;
    height: .38rem;
    border-right: 1.5px solid var(--text-muted);
    border-bottom: 1.5px solid var(--text-muted);
    transform: translateY(-25%) rotate(45deg);
    pointer-events: none;
}
.dashboard-filters select {
    appearance: none;
    -webkit-appearance: none;
    width: auto;
    min-width: 7rem;
    padding: .4rem 2rem .4rem .7rem;
    font-weight: 550;
    border-radius: 999px;
    cursor: pointer;
}
.dashboard-filters select:hover { border-color: var(--primary); }

/* Accounts as selectable chips. The native checkbox is visually hidden (not removed —
   it still submits and takes focus); the chip's ::before is the checkbox people see. */
.account-chips { display: flex; flex-wrap: wrap; gap: .4rem; }
.account-chip {
    position: relative; /* contains the visually-hidden input below */
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    padding: .35rem .75rem .35rem .6rem;
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    background: var(--surface);
    color: var(--text-muted);
    font-weight: 550;
    font-size: .85rem;
    white-space: nowrap;
    cursor: pointer;
    user-select: none;
    transition: border-color .12s ease, background .12s ease, color .12s ease;
}
.account-chip::before {
    content: '';
    width: .9rem;
    height: .9rem;
    flex: none;
    border: 1.5px solid var(--border-strong);
    border-radius: 4px;
    background-repeat: no-repeat;
    background-position: center;
    background-size: .8rem;
    transition: border-color .12s ease, background-color .12s ease;
}
.account-chip:hover { border-color: var(--primary); color: var(--text); }
.account-chip:has(input:checked) {
    border-color: var(--primary);
    background: var(--primary-soft);
    color: var(--primary);
}
.account-chip:has(input:checked)::before {
    border-color: var(--primary);
    background-color: var(--primary);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23fff' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round' d='M3.5 8.5l3 3 6-6'/%3E%3C/svg%3E");
}
/* The hidden input still owns focus, so mirror its focus ring onto the chip. */
.account-chip:has(input:focus-visible) {
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--primary-soft);
}
.account-chip input {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    opacity: 0;
    pointer-events: none;
}
/* Review's chips carry how many rows each account still has queued — the number is the
   point of the chip there, so it stays legible rather than fading with the label. */
.account-chip-count {
    padding: 0 .35rem;
    border-radius: 999px;
    background: var(--border);
    color: var(--text-muted);
    font-size: .75rem;
    font-variant-numeric: tabular-nums;
}
.account-chip:has(input:checked) .account-chip-count {
    background: var(--surface);
    color: var(--primary);
}

@media (max-width: 700px) {
    .filter-divider { display: none; }
}

/* Category rules: bulk-assign-accounts toolbar above the table */
.bulk-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .75rem;
    padding: .8rem 1.1rem;
    margin-bottom: 1.75rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}
.bulk-toolbar select { width: auto; min-width: 220px; max-width: 320px; }
.category-rules table th:first-child,
.category-rules table td:first-child { width: 2rem; }

/* How many transactions each rule categorizes. A zero recedes rather than shouts: it is the
   one number worth acting on, but it is also the resting state of a rule created moments ago.
   "shadowed" beside it says the pattern does match rows — an earlier rule just gets them. */
.rule-usage { white-space: nowrap; }
.rule-usage-none { color: var(--text-muted); }
.rule-usage-shadowed {
    margin-left: .4rem;
    font-size: .8rem;
    font-weight: 600;
    color: var(--warning);
}

/* How many transactions each category holds — same column, same restraint as the Rules screen's.
   "N direct" marks the rows filed on a parent instead of one of its subcategories: the pickers
   only offer leaves, so nothing else on screen would ever show them. */
.category-usage { white-space: nowrap; }
.category-usage-none { color: var(--text-muted); }
.category-usage-direct {
    margin-left: .4rem;
    font-size: .8rem;
    font-weight: 600;
    color: var(--warning);
}

/* ---- Charts ---------------------------------------------------------------- */
.charts { display: grid; grid-template-columns: 1fr; gap: 1.75rem; }
@media (min-width: 900px) {
    .charts { grid-template-columns: 2fr 1fr; }
}

.chart-bar, .chart-donut, .chart-savings { padding: 1.5rem; }
.savings-section { margin-top: 1.75rem; }
.chart-bar canvas, .chart-donut canvas, .chart-savings canvas { max-width: 100%; }

.chart-hint { margin: -.5rem 0 .9rem; font-size: .82rem; color: var(--text-muted); }

/* Category-breakdown stat strip (total / avg / vs last year) */
.breakdown-stats {
    display: flex;
    flex-wrap: wrap;
    gap: 1.75rem;
    margin: .25rem 0 1rem;
}
.stat { display: flex; flex-direction: column; gap: .15rem; }
.stat-label {
    font-size: .7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-muted);
}
.stat-value {
    font-size: 1.35rem;
    font-weight: 700;
    letter-spacing: -.01em;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.stat-delta {
    display: inline-flex;
    align-items: baseline;
    gap: .2rem;
    font-size: 1.05rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
/* Expenses: spending up = worse (red), down = better (green) */
.stat-delta.is-up   { color: var(--danger); }
.stat-delta.is-down { color: var(--success); }

/* While subcategories are filtered out, this delta still covers the whole year — receded so
   it doesn't read as describing the Avg / Total next to it. */
.stat.is-out-of-scope .stat-delta { opacity: .4; }
.stat-scope { font-weight: 500; text-transform: none; letter-spacing: 0; }

/* Running-month toggle, sitting with the doughnut rather than in the page toolbar because
   that is the only chart it changes. */
.donut-scope { margin: -.25rem 0 .5rem; }
.donut-scope label {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    color: var(--text-muted);
    font-size: .88rem;
    /* Lighter than the surrounding labels on purpose: it must not compete with the chart
       heading it sits under. */
    font-weight: 500;
    cursor: pointer;
}

/* ---- Subcategory filter panel ----------------------------------------------- */
/* A full-width row above both charts inside the .charts grid. */
.category-filter {
    grid-column: 1 / -1;
    padding: 1.25rem 1.5rem 1.5rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}
.category-filter-head h3 { margin: 0; font-size: 1.05rem; }
.category-filter-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: .35rem 1rem;
    margin-bottom: 1rem;
}
.category-filter-head .chart-hint { margin: 0; flex: 1 1 16rem; }
.filter-reset {
    padding: .3rem .7rem;
    font: inherit;
    font-size: .8rem;
    color: var(--text-muted);
    background: none;
    border: 1px solid var(--border);
    border-radius: 999px;
    cursor: pointer;
}
.filter-reset:hover { border-color: var(--primary); color: var(--primary); }

.filter-groups {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
    gap: 1rem 1.5rem;
    margin: 0;
    padding: 0;
    list-style: none;
}
.filter-group { min-width: 0; }
.filter-leaves { margin: .2rem 0 0; padding: 0 0 0 1.1rem; list-style: none; }

.filter-row {
    display: flex;
    align-items: center;
    gap: .35rem;
    min-width: 0;
}
.filter-check {
    display: flex;
    align-items: center;
    gap: .45rem;
    min-width: 0;
    padding: .15rem 0;
    font-size: .85rem;
    cursor: pointer;
}
.filter-row-parent .filter-check { font-weight: 600; font-size: .9rem; }
.filter-check input { margin: 0; flex: none; }
/* An unchecked row is out of the numbers — mute it, but keep the swatch legible. */
.filter-check:has(input:not(:checked)) .filter-name {
    color: var(--text-muted);
    text-decoration: line-through;
}
.filter-check:has(input:not(:checked)) .filter-swatch { opacity: .35; }
.filter-swatch {
    width: .75rem;
    height: .75rem;
    border-radius: 3px;
    flex: none;
}
.filter-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.filter-only {
    flex: none;
    padding: .05rem .4rem;
    font: inherit;
    font-size: .68rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--text-muted);
    background: none;
    border: 1px solid transparent;
    border-radius: 999px;
    cursor: pointer;
    /* Kept quiet by size, weight and case rather than by transparency: at opacity .25 this
       sat around 1.3:1 against the panel, i.e. invisible until you knew to look for it. */
    /* A click can leave the label text selected, and a stray selection highlight on a pill
       this small reads as a stuck "active" state. */
    user-select: none;
    transition: color .12s ease, background .12s ease, border-color .12s ease;
}
/* Hovering anywhere in the row offers the button; hovering the button itself commits to it.
   The row-hover selector is wrapped in :where() to keep it at .filter-only's specificity —
   written plainly it is (0,3,0) and beats the (0,2,0) button-hover rule below on `color`,
   which leaves --text on a --primary fill: unreadable in light mode, and accidentally fine
   in dark mode only because --text happens to be near-white there. */
:where(.filter-row:hover) .filter-only { color: var(--text); border-color: var(--border-strong); }
.filter-only:hover,
.filter-only:focus-visible {
    color: var(--on-primary);
    background: var(--primary);
    border-color: var(--primary);
}

/* ---- Dashboard drill-down panel --------------------------------------------- */
.dashboard-detail { display: block; }

.detail-panel {
    margin-top: 1.75rem;
    padding: 1.5rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}

.detail-header { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem 1.25rem; }
.detail-header h2 { margin-bottom: .35rem; display: flex; align-items: center; gap: .5rem; }
.detail-swatch {
    display: inline-block;
    width: .85em;
    height: .85em;
    border-radius: 3px;
    flex: none;
}
.detail-total { margin: 0 0 1rem; color: var(--text-muted); font-size: .92rem; }
.detail-total strong { color: var(--text); font-variant-numeric: tabular-nums; }
/* Says the count and total beside it describe a subset of the cell, so the header can't be
   read as the whole of "March — Food" while the chips are narrowing it. */
.detail-scope { margin-left: .4rem; font-size: .85rem; opacity: .8; }

.detail-note { margin-bottom: 1.25rem; }
.detail-note label { display: block; margin-bottom: .35rem; }
.detail-note-row { display: flex; align-items: flex-start; gap: .75rem; }
.detail-note-row textarea { max-width: 560px; flex: 1 1 auto; resize: vertical; }

/* Auto-save status next to the note label ("Saving…" / "Saved" / "Failed to save") */
.detail-note-status {
    margin-left: .5rem;
    font-size: .8rem;
    font-weight: 500;
    text-transform: none;
    letter-spacing: normal;
    color: var(--text-muted);
}
.detail-note-status:not(:empty)::before { content: '· '; }

/* Subcategory chips: the open cell's colour key and its row filter. Built like the account
   chips in the page toolbar (pill, native checkbox visually hidden) so the two read as the
   same control, but tinted with each subcategory's own chart shade — the chip, its share-bar
   segment and the bar segment that was clicked to get here are then visibly one thing.
   `--chip-color` is set inline per chip; every colour below derives from it. */
.detail-subs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .4rem;
    margin: 0 0 .85rem;
}
.detail-subs-label {
    margin-right: .35rem;
    font-size: .7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-muted);
}
.sub-chip {
    position: relative; /* contains the visually-hidden input */
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    padding: .3rem .8rem;
    font-size: .85rem;
    font-weight: 550;
    color: var(--text-muted);
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    white-space: nowrap;
    cursor: pointer;
    user-select: none;
    transition: border-color .12s ease, background .12s ease, color .12s ease;
}
/* The native box is made invisible but kept exactly the size of the chip, rather than shrunk
   to a corner: the whole pill is then the hit area for a pointer *and* for anything driving
   the input itself (Playwright's check(), an AT click), which a 1px input would silently miss. */
.sub-chip input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    appearance: none;
    background: none;
    border: 0;
    opacity: 0;
    cursor: pointer;
}
/* The swatch is the checkbox: filled in the subcategory's shade while it counts, hollow once
   it doesn't. One glyph doing both jobs keeps a strip of six chips readable. */
.sub-swatch {
    width: .7rem;
    height: .7rem;
    flex: none;
    border-radius: 3px;
    background: var(--chip-color);
}
.sub-chip:has(input:checked) {
    color: var(--text);
    border-color: var(--chip-color);
    background: color-mix(in srgb, var(--chip-color) 14%, var(--surface));
}
.sub-chip:has(input:not(:checked)) .sub-swatch {
    background: transparent;
    box-shadow: inset 0 0 0 1.5px var(--border-strong);
}
/* Out of the figures — struck through, matching the filter panel's convention. */
.sub-chip:has(input:not(:checked)) .sub-chip-name { text-decoration: line-through; }
.sub-chip:hover { border-color: var(--chip-color); color: var(--text); }
/* The hidden input still owns focus, so mirror its ring onto the chip. */
.sub-chip:has(input:focus-visible) { box-shadow: 0 0 0 3px var(--primary-soft); }
/* Nothing in this cell to filter: present as the colour key, quiet as a control. */
.sub-chip-empty { opacity: .45; }

/* Ratio between the cell's subcategories — a 100% bar in the same shades as the chips above
   it, over the rows currently visible. Deliberately not a second doughnut: the panel is
   already tall, and this answers "how is this cell split" in one line. */
.sub-share { margin: 0 0 1.1rem; }
.sub-share-bar {
    display: flex;
    height: .5rem;
    overflow: hidden;
    background: var(--border);
    border-radius: 999px;
}
/* A sliver still has to be visible; the transition makes a chip toggle read as a resize
   rather than a redraw. Siblings are graded shades of one parent colour, so neighbouring
   segments need a hairline between them to be countable at all — drawn as a shadow over the
   segment to its left, since a gap or a border would push the widths past 100%. */
.sub-share-seg { min-width: 2px; transition: width .18s ease; }
.sub-share-seg + .sub-share-seg { box-shadow: -1.5px 0 0 var(--surface); }
.sub-share-legend {
    display: flex;
    flex-wrap: wrap;
    gap: .25rem 1.5rem;
    margin: .55rem 0 0;
    padding: 0;
    list-style: none;
    font-size: .82rem;
    color: var(--text-muted);
}
.sub-share-item { display: inline-flex; align-items: center; gap: .4rem; }
/* A class rule setting `display` outranks the UA's `[hidden] { display: none }`, so a
   subcategory the chips have hidden would otherwise sit in the legend reading "0%". */
.sub-share-item[hidden] { display: none; }
.sub-share-name { color: var(--text); }
.sub-share-pct { font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.sub-share-amount { font-variant-numeric: tabular-nums; }

.detail-table .detail-amount { font-variant-numeric: tabular-nums; white-space: nowrap; }

.detail-recat { display: flex; align-items: center; gap: .5rem; }
.detail-recat select { width: auto; max-width: 200px; }

.detail-empty { color: var(--text-muted); margin: 0; }

/* Badge on transactions attributed to a different month than their real date */
.detail-moved {
    display: inline-block;
    margin-left: .35rem;
    padding: .05rem .4rem;
    font-size: .72rem;
    font-weight: 600;
    color: var(--info);
    background: var(--info-soft);
    border-radius: 999px;
    vertical-align: middle;
    cursor: help;
}

/* ---- Transactions list ------------------------------------------------------ */
/* Match the pill selects it sits beside, so the strip reads as one toolbar. */
.transactions .dashboard-filters input[type="search"] {
    width: 100%;
    min-width: 12rem;
    max-width: 22rem;
    padding: .4rem .9rem;
    font-weight: 550;
    border-radius: 999px;
}
.transactions .dashboard-filters input[type="search"]:hover { border-color: var(--primary); }
.transactions-table .detail-amount { font-variant-numeric: tabular-nums; white-space: nowrap; }
.transactions-table td:last-child { min-width: 16rem; }

.tx-recat { display: inline-flex; align-items: center; gap: .5rem; }
.tx-recat select { width: auto; max-width: 220px; }

/* How the row's category got there. "rule" is the quiet default — "manual" is the one
   worth spotting, because a rule re-apply will step over it. */
.tx-origin {
    display: inline-block;
    padding: .05rem .45rem;
    font-size: .72rem;
    font-weight: 600;
    color: var(--text-muted);
    background: var(--primary-soft);
    border-radius: 999px;
    cursor: help;
}
.tx-origin-manual { color: var(--info); background: var(--info-soft); }

/* An edited row keeps its place and says what happened, instead of vanishing and making
   the rows below jump up under the cursor mid-sweep. */
.tx-change-marker {
    margin-left: .5rem;
    font-size: .8rem;
    font-weight: 600;
    color: var(--success);
    white-space: nowrap;
}

/* The edit pushed this row out of the current filters. It stays until the next reload or
   filter change — receded, but still fully editable. Recede via colour, not opacity: an
   opacity on the <tr> would take the marker and the select down with it.

   The drill-down panel reuses all of this for a row an in-panel edit moved out of the open
   cell; only the form's class differs (.detail-recat). */
.tx-row-stale > td { color: var(--text-muted); }
.tx-row-stale .tx-recat select,
.tx-row-stale .detail-recat select { opacity: .65; }
.tx-row-stale .tx-change-marker { color: var(--text-muted); }

/* ---- Import summary -------------------------------------------------------- */
.import-summary { padding: 1.25rem 1.5rem; margin-bottom: 1.75rem; }
.import-summary ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: .75rem 2rem; }
.import-summary li { color: var(--text-muted); }
.import-summary li strong { color: var(--text); }

/* ---- Pagination ------------------------------------------------------------ */
/* Numbered pages on the left, row-range summary pinned to the right. The summary is
   always rendered (it answers "how much is there?"); the page list only when there's
   more than one page — hence `margin-left: auto` on the summary rather than
   space-between, which would drift the summary left on a single-page list. */
.pagination {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .75rem 1.5rem;
    margin-top: 1.25rem;
    font-size: .9rem;
}
.pagination-summary { margin: 0 0 0 auto; color: var(--text-muted); }

.pagination-pages {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .3rem;
    margin: 0;
    padding: 0;
    list-style: none;
}
.pagination-pages a,
.pagination-pages .is-current,
.pagination-pages .is-disabled {
    display: block;
    min-width: 2.25rem;
    padding: .4rem .7rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    text-align: center;
    font-weight: 550;
    font-variant-numeric: tabular-nums;
}
.pagination-pages a { background: var(--surface); }
.pagination-pages a:hover { background: var(--primary-soft); border-color: var(--primary); text-decoration: none; }
.pagination-pages .is-current {
    background: var(--primary);
    border-color: var(--primary);
    color: #fff;
}
/* Kept in the flow rather than hidden, so the row doesn't reflow at the ends. */
.pagination-pages .is-disabled { color: var(--text-muted); opacity: .5; }
.pagination-pages .pagination-gap {
    padding: .4rem .2rem;
    border: 0;
    color: var(--text-muted);
}

/* Server-sorted column header — a real link, unlike the client-side sortable-table headers,
   because a paged screen has to sort in SQL. That's an implementation difference the user
   should never see, so this deliberately reproduces the sortable-table header above: the
   link takes over the th's padding (whole cell clickable, same hover), and the caret is
   absolutely placed at the same offset with the same glyphs. */
th.sort-th { position: relative; padding: 0; }
.sort-link {
    display: block;
    padding: .7rem 1.4rem .7rem 1rem;
    color: inherit;
    font: inherit;
    white-space: nowrap;
}
.sort-link:hover { color: var(--text); background: var(--hover); text-decoration: none; }
.sort-caret {
    position: absolute;
    right: .5rem;
    top: 50%;
    transform: translateY(-50%);
    font-size: .85em;
    opacity: .35;
}
/* Only the caret takes the accent, not the label — matching thead th.sort-asc::after. */
.sort-link.is-sorted .sort-caret { opacity: 1; color: var(--primary); }

/* Row count above a list that isn't paged (Rules). */
.list-count { margin: 0 0 .75rem; color: var(--text-muted); font-size: .9rem; }

/* ---- Flash / alerts -------------------------------------------------------- */
.flash, .alert {
    max-width: 1200px;
    margin: 1rem auto 0;
    padding: .8rem 1.1rem;
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    font-size: .92rem;
    font-weight: 500;
}
.flash-success              { background: var(--success-soft); color: var(--success); border-color: var(--success-border); }
.flash-error, .alert-danger { background: var(--danger-soft);  color: var(--danger);  border-color: var(--danger-border); }
.flash-warning              { background: var(--warning-soft); color: var(--warning); border-color: var(--warning-border); }
.flash-info                 { background: var(--info-soft);    color: var(--info);    border-color: var(--info-border); }

/* ---- Login ----------------------------------------------------------------- */
.login-container {
    max-width: 400px;
    margin: 10vh auto 0;
    padding: 0 1.5rem;
}
.login-container h1 { text-align: center; }
.login-container form { padding: 1.75rem; box-shadow: var(--shadow-md); }
.login-container button, .login-container .card-center .btn { width: 100%; margin-top: .5rem; text-align: center; }
.login-container .alert { margin: 0 0 1rem; }

/* ---- Per-transaction notes -------------------------------------------------- */
/* One leading icon column on all three lists (Transactions, Review, drill-down). The note
   text is never rendered inline, so this icon's state is the only thing that says a row is
   annotated — which is why an empty one recedes rather than disappears (it still has to be
   findable to *add* a note) and a set one is always at full strength. */
.note-col { width: 1px; padding-right: .25rem; white-space: nowrap; }

.visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    clip-path: inset(50%);
    overflow: hidden;
    white-space: nowrap;
}

.tx-note { position: relative; display: inline-block; }
.tx-note-toggle {
    display: inline-flex;
    align-items: center;
    padding: .2rem;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    cursor: pointer;
    opacity: .28;
    transition: opacity .12s ease, color .12s ease, background .12s ease;
}
/* <details> draws a disclosure triangle by default in both engines. */
.tx-note-toggle { list-style: none; }
.tx-note-toggle::-webkit-details-marker { display: none; }

tr:hover .tx-note-toggle,
.tx-note-toggle:hover,
.tx-note-toggle:focus-visible,
.tx-note[open] .tx-note-toggle,
.tx-note-set .tx-note-toggle { opacity: 1; }

.tx-note-toggle:hover { background: var(--hover); }
.tx-note-set .tx-note-toggle { color: var(--primary); }

.tx-note-toggle svg {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.25;
    stroke-linecap: round;
    stroke-linejoin: round;
}
/* Filled body + visible ruled lines are what "this row has a note" looks like at a glance;
   an unnoted row shows the bare outline. */
.tx-note-set .tx-note-toggle svg > path:first-child { fill: var(--primary-soft); }
.tx-note-lines { opacity: 0; }
.tx-note-set .tx-note-lines { opacity: 1; }

.tx-note-editor {
    position: absolute;
    z-index: 30;
    top: calc(100% + .35rem);
    left: 0;
    width: 320px;
    padding: .75rem;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lg);
    text-align: left;
    white-space: normal;
}
.tx-note-label {
    display: block;
    margin-bottom: .35rem;
    font-size: .82rem;
    font-weight: 600;
    color: var(--text-muted);
}
.tx-note-editor textarea { width: 100%; resize: vertical; }
.tx-note-status { font-weight: 400; color: var(--text-muted); }
.tx-note-status:not(:empty)::before { content: '· '; }

/* ---- Misc ------------------------------------------------------------------ */
.review p,
.accounts td[colspan],
.categories td[colspan],
.category-rules td[colspan] { color: var(--text-muted); }

/* ==========================================================================
   Runway (/runway)
   ==========================================================================
   One page, four blocks down the column: the answer, the two dials that move it, what is
   being counted on each side of the division, then the history. The hero is the only place
   on the site with a display-sized number, which is the point — everything else here exists
   to explain that one figure. */

.runway { display: flex; flex-direction: column; gap: 1.25rem; }
.runway > h1 { margin: 0; }

.runway-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
}
.runway-head h1 { margin: 0; }
.runway-head-actions { display: flex; align-items: center; gap: 1rem; }

.runway-empty {
    padding: 2rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text-muted);
}

/* ---- The answer ------------------------------------------------------------ */
.runway-hero {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1.5rem 2rem;
    padding: 1.5rem 1.75rem;
    /* The one accent rail on the page: this block is the answer, the rest is its working. */
    border-left: 3px solid var(--primary);
}
.runway-hero-main { flex: 1 1 260px; display: flex; flex-direction: column; gap: .3rem; }
.runway-hero-label {
    font-size: .78rem;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--text-muted);
    font-weight: 600;
}
.runway-months {
    display: flex;
    align-items: baseline;
    gap: .4rem;
    font-size: 3rem;
    line-height: 1.05;
    font-weight: 700;
    letter-spacing: -.02em;
    font-variant-numeric: tabular-nums;
}
.runway-months-unit { font-size: 1.1rem; font-weight: 600; color: var(--text-muted); letter-spacing: 0; }
/* The class rule above sets `display: flex`, which outranks the UA's `[hidden]` — so hiding
   the number when the answer is a sentence needs saying explicitly. Same trap as
   `.sub-share-item[hidden]` on the drill-down panel. */
.runway-months[hidden] { display: none; }
/* The three non-numeric answers wear the same weight as the number they stand in for, so the
   block never looks like a chart that failed to load. */
.runway-message { margin: .2rem 0; font-size: 1.25rem; font-weight: 650; line-height: 1.3; }
.runway-hero-sub { margin: 0; font-size: .92rem; color: var(--text-muted); }
.runway-hero-sub strong { color: var(--text); }
.runway-hero-note { margin: .35rem 0 0; font-size: .82rem; color: var(--text-muted); }

.runway-formula {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .75rem;
    padding: .8rem 1rem;
    background: var(--surface-alt);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-variant-numeric: tabular-nums;
}
.runway-term { display: flex; flex-direction: column; gap: .15rem; }
.runway-term-label {
    font-size: .7rem;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text-muted);
    white-space: nowrap;
}
.runway-term strong { font-size: .95rem; font-weight: 650; white-space: nowrap; }
.runway-op { color: var(--text-muted); padding-top: .85rem; }

/* ---- The dials ------------------------------------------------------------- */
.runway-controls {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 1.25rem;
    padding: .9rem 1.1rem;
}
.runway-controls form { margin: 0; }
.runway-input {
    display: flex;
    align-items: center;
    gap: .4rem;
    padding-right: .6rem;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
}
.runway-input input {
    width: 7rem;
    margin: 0;
    border: none;
    background: transparent;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}
.runway-input input:focus { outline: none; }
.runway-input:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); }
.runway-input-unit { color: var(--text-muted); }
.runway-controls-note { flex: 1 1 220px; margin: 0; text-align: right; font-size: .82rem; color: var(--text-muted); }

/* ---- What is counted ------------------------------------------------------- */
.runway-panels {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 1.25rem;
    align-items: start;
}
.runway-panel { display: flex; flex-direction: column; }
.runway-panel-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    padding: .85rem 1.1rem;
    border-bottom: 1px solid var(--border);
}
.runway-panel-head h2 { margin: 0; font-size: 1rem; font-weight: 650; }
.runway-panel-total { font-variant-numeric: tabular-nums; font-weight: 650; font-size: .95rem; }
.runway-panel-foot,
.runway-panel-empty {
    margin: 0;
    padding: .75rem 1.1rem;
    border-top: 1px solid var(--border);
    font-size: .82rem;
    color: var(--text-muted);
}
.runway-panel-empty { border-top: none; }

.runway-ticks { list-style: none; margin: 0; padding: .5rem .5rem; }
.runway-tick {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .3rem .6rem;
    border-radius: var(--radius-sm);
}
.runway-tick:hover { background: var(--hover); }
.runway-tick-label { display: flex; align-items: center; gap: .55rem; flex: 1 1 auto; min-width: 0; cursor: pointer; }
.runway-tick-label input { margin: 0; }
/* Keeps a place with no reading aligned with the ticked ones above it. */
.runway-tick-gap { width: 13px; flex: none; }
.runway-tick-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.runway-tick-amount { font-variant-numeric: tabular-nums; color: var(--text-muted); font-size: .88rem; white-space: nowrap; }
.runway-tick-empty .runway-tick-name,
.runway-tick-empty .runway-tick-amount { color: var(--text-muted); font-style: italic; }
/* An unticked leaf is out of the burn — muted exactly as the dashboard's filter panel mutes
   a hidden category, and for the same reason: the row stays readable and one click from being
   counted again. Leaves only: a parent box is indeterminate while some of its leaves are
   ticked, and an indeterminate box is not :checked. */
.runway-tick-leaf .runway-tick-label:has(input:not(:checked)) .runway-tick-name {
    color: var(--text-muted);
    text-decoration: line-through;
}
.runway-tick-parent { margin-top: .35rem; }
.runway-tick-parent .runway-tick-name { font-weight: 650; }
.runway-tick-leaf { padding-left: 1.9rem; }
.runway-ticks-tree .runway-tick-leaf .runway-tick-name { font-size: .92rem; }
/* A real category tree runs to thirty rows, which would leave the money panel beside it
   floating over a screen of whitespace and push the history chart out of reach. The list
   scrolls in place instead; the total in the header always describes the whole of it. */
.runway-ticks-tree { max-height: 24rem; overflow-y: auto; }

/* inline-block, not inline: outside a flex row (the Places table, the snapshot form) an
   inline span ignores width and height and the swatch simply does not appear. */
.runway-swatch { display: inline-block; width: 12px; height: 12px; border-radius: 3px; flex: none; }

/* ---- History --------------------------------------------------------------- */
.runway-chart { padding: 1rem; height: 340px; }
.runway-chart canvas { max-height: 100%; }

.runway-table-scroll { overflow-x: auto; }
.runway-table { width: 100%; border-collapse: collapse; }
.runway-table th, .runway-table td { white-space: nowrap; }
.runway-num { text-align: right; font-variant-numeric: tabular-nums; }
.runway-num-empty { color: var(--text-muted); }
.runway-num-total { font-weight: 650; }
.runway-row-actions { display: flex; align-items: center; gap: .25rem; }
.runway-row-actions form { margin: 0; }

/* ---- Places & the snapshot form -------------------------------------------- */
.runway-places td form { display: inline; }
.runway-place-retired td { color: var(--text-muted); }
.runway-place-hex { font-size: .82rem; color: var(--text-muted); margin-left: .4rem; }

.runway-form form { padding: 1.5rem; max-width: 720px; }
/* One column per place, wrapping — it is a grid of boxes to fill in, not a stack of
   questions, so a monthly update reads as one task rather than eight. */
.runway-form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 1rem;
    margin: 1.25rem 0;
}
.runway-form-field { position: relative; padding-left: 1.1rem; }
.runway-form-field .runway-swatch { position: absolute; left: 0; top: .3rem; }
.runway-form-field input { font-variant-numeric: tabular-nums; }
.runway-form-retired { display: block; margin-top: -.6rem; font-size: .75rem; color: var(--text-muted); }

.runway-form > a,
.runway-place-form > a {
    display: inline-block;
    margin-top: 1rem;
    color: var(--text-muted);
    font-size: .9rem;
}

@media (max-width: 640px) {
    .runway-months { font-size: 2.4rem; }
    .runway-controls-note { text-align: left; }
    .runway-chart { height: 280px; }
}

/* ---- Accounts & auth (PROJ-38) ---- */

/* Generic surface card: same recipe as .login-container form, for screens
   that show a message rather than a form (e.g. "check your inbox"). */
.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-md);
    padding: 1.75rem;
    max-width: 480px;
}
.card-center { text-align: center; }
.card-center p { margin: .25rem 0 1rem; font-size: .92rem; color: var(--text-muted); }
.card-center strong { color: var(--text); }

.auth-brand {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .6rem;
    font-weight: 700;
    font-size: 1.1rem;
    margin-bottom: 1.5rem;
}
.auth-lead { text-align: center; color: var(--text-muted); font-size: .92rem; margin: 0 0 1.25rem; }
.auth-switch { text-align: center; font-size: .9rem; color: var(--text-muted); margin: 1.25rem 0 0; }

.auth-icon {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    margin: 0 auto 1rem;
}
.auth-icon svg { width: 24px; height: 24px; }
.auth-icon.ok   { background: var(--success-soft); color: var(--success); }
.auth-icon.info { background: var(--info-soft);    color: var(--info); }
.auth-icon.bad  { background: var(--danger-soft);  color: var(--danger); }

.label-row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: .35rem; }
.label-row label { margin: 0 !important; }
.label-row a { font-size: .82rem; font-weight: 550; }

input.is-invalid { border-color: var(--danger); }
.field-error { font-size: .8rem; color: var(--danger); margin: .3rem 0 0; font-weight: 500; }

.btn-secondary { background: var(--surface); color: var(--text); border: 1px solid var(--border-strong); }
.btn-danger { background: var(--danger); color: #fff; }
.btn-danger-ghost { background: transparent; color: var(--danger); border: 1px solid var(--danger-border); }

.code-input {
    font-size: 1.4rem !important;
    letter-spacing: .4em;
    text-align: center;
    font-variant-numeric: tabular-nums;
    max-width: none !important;
    padding: .6rem !important;
}

.callout { border-radius: var(--radius-sm); border: 1px solid; padding: .85rem 1rem; font-size: .88rem; margin-bottom: 1.1rem; }
.callout p { margin: .2rem 0; }
.callout ul { margin: .35rem 0 .2rem; padding-left: 1.1rem; }
.callout-title { font-weight: 700; display: flex; gap: .45rem; align-items: center; }
.callout-title svg { width: 18px; height: 18px; flex: none; }
.callout-warning { background: var(--warning-soft); border-color: var(--warning-border); color: var(--warning); }
.callout-danger  { background: var(--danger-soft);  border-color: var(--danger-border);  color: var(--danger); }
.callout-info    { background: var(--info-soft);    border-color: var(--info-border);    color: var(--info); }

/* Password strength meter: filled by password_strength_controller.js via data-level. */
.strength {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: .25rem;
    margin: .4rem 0 0;
    height: 6px;
}
.strength span { background: var(--border); border-radius: 3px; }
.strength[data-level="1"] span:nth-child(-n+1) { background: var(--danger); }
.strength[data-level="2"] span:nth-child(-n+2) { background: var(--warning); }
.strength[data-level="3"] span:nth-child(-n+3) { background: var(--info); }
.strength[data-level="4"] span:nth-child(-n+4) { background: var(--success); }
.strength-label { font-size: .8rem; color: var(--text-muted); margin-top: .25rem; }

/* Honeypot field: kept in the form and in the layout flow (off-screen, not display:none)
   so a bot that skips hidden fields still fills it in. */
.hp-field { position: absolute; left: -10000px; top: auto; width: 1px; height: 1px; overflow: hidden; }

.settings, .settings-form { max-width: 760px; padding: 2rem 2rem 3rem; }

.settings-section {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    margin-bottom: 1rem;
}
.settings-section > h2 {
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--text-muted);
    margin: 0;
    padding: .9rem 1.25rem .2rem;
}
.settings-section.is-danger { border-color: var(--danger-border); }
.settings-section.is-danger > h2 { color: var(--danger); }

.settings-row { display: flex; align-items: center; gap: 1rem; padding: .85rem 1.25rem; border-top: 1px solid var(--border); }
.settings-section > h2 + .settings-row { border-top: none; }
.settings-row-main { flex: 1; min-width: 0; }
.settings-row-title { font-weight: 600; font-size: .93rem; }
.settings-row-value { font-size: .85rem; color: var(--text-muted); overflow-wrap: anywhere; }
.settings-row .btn { padding: .4rem .9rem; font-size: .85rem; flex: none; }

.badge { display: inline-block; padding: .05rem .5rem; border-radius: 999px; font-size: .72rem; font-weight: 650; vertical-align: middle; margin-left: .35rem; }
.badge-on      { background: var(--success-soft); color: var(--success);    border: 1px solid var(--success-border); }
.badge-off     { background: var(--hover);         color: var(--text-muted); border: 1px solid var(--border); }
.badge-pending { background: var(--warning-soft);  color: var(--warning);    border: 1px solid var(--warning-border); }

.settings-links { font-size: .82rem; color: var(--text-muted); padding: .5rem .25rem; }
.settings-links a { color: var(--text-muted); text-decoration: underline; }

.back-link { display: inline-block; font-size: .88rem; color: var(--text-muted); margin-bottom: .75rem; }

.settings-form form {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: 1.5rem;
    max-width: 480px;
}
.settings-form h1 { margin-bottom: .4rem; }
.settings-form .lead { color: var(--text-muted); font-size: .92rem; margin: 0 0 1.25rem; max-width: 480px; }

.form-actions { display: flex; gap: .75rem; align-items: center; margin-top: .5rem; flex-wrap: wrap; }
.form-actions a { color: var(--text-muted); font-size: .9rem; }

.legal { max-width: 680px; margin: 0 auto; padding: 2rem 1.5rem 3rem; }
.legal .meta { color: var(--text-muted); font-size: .85rem; margin: -.9rem 0 1.5rem; }
.legal p { color: var(--text); }
.legal-top { display: flex; justify-content: space-between; align-items: center; padding: .9rem 1.5rem; border-bottom: 1px solid var(--border); background: var(--surface); }
.legal-top .auth-brand { margin: 0; }

/* Recovery key: the required "save your recovery key" screen */
.login-container.is-wide { max-width: 520px; }
.auth-icon.key { background: var(--primary-soft); color: var(--primary); }
.recovery-key {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: .45rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 1.18rem;
    font-weight: 600;
    letter-spacing: .08em;
    background: var(--surface-alt);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: .9rem;
    margin: 0 0 .75rem;
    text-align: center;
    user-select: all;
}
.recovery-key span { background: var(--surface); border: 1px solid var(--border); border-radius: 6px; padding: .35rem 0; }
.key-actions { display: grid; grid-template-columns: repeat(3, 1fr); gap: .5rem; margin: 0 0 .4rem; }
.key-actions .btn { width: auto; margin: 0; }
.key-status { font-size: .8rem; color: var(--success); font-weight: 600; min-height: 1.2em; margin: 0 0 1rem; text-align: center; }
.key-facts { font-size: .85rem; color: var(--text-muted); margin: 0 0 1.1rem; padding-left: 1.1rem; }
@media (max-width: 640px) {
    .recovery-key { font-size: 1.02rem; gap: .35rem; padding: .65rem; }
}
.recovery-print { display: none; }
@media print {
    body * { visibility: hidden; }
    .recovery-print, .recovery-print * { visibility: visible; }
    .recovery-print { display: block; position: absolute; inset: 0 auto auto 0; padding: 24px; color: #000; }
    .recovery-print .rk {
        font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
        font-size: 1.1rem;
        font-weight: 700;
        letter-spacing: .08em;
        border: 1.5px dashed #555;
        padding: .6rem;
        text-align: center;
        margin: .8rem 0;
    }
}

/* Recovery key: the emailed-link reset page */
.key-input{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:1.02rem;letter-spacing:.06em;text-transform:uppercase}
.or-divider{display:flex;align-items:center;gap:.75rem;color:var(--text-muted);font-size:.82rem;margin:1.25rem 0 .9rem}
.or-divider::before,.or-divider::after{content:"";flex:1;border-top:1px solid var(--border)}
.no-key{text-align:center;font-size:.88rem;color:var(--text-muted)}
.no-key a{color:var(--danger);font-weight:600}
.link-button{all:unset;cursor:pointer;color:var(--primary);font-size:.82rem;font-weight:550}
.login-container .link-button{width:auto;margin:0}
