/* CSS Variables for theming.
   All colors in this file must go through these tokens — see CLAUDE.md. */
:root {
    --primary-color: #2563eb;
    --primary-hover: #1d4ed8;
    --secondary-color: #64748b;
    --secondary-hover: #475569;
    --success-color: #10b981;
    --error-color: #ef4444;
    --error-btn-hover: #dc2626;
    --warning-color: #f59e0b;

    --bg-color: #eef2f6;
    --card-bg: #ffffff;
    /* Item cards listed inside a section card (rows, upload cards, notes):
       the middle of the three surfaces — page, item, section. */
    --item-bg: #f8fafc;
    --bg-white: #ffffff;
    --text-color: #1e293b;
    --text-muted: #64748b;
    --border-color: #d3dbe5;

    --on-primary: #ffffff;
    --on-primary-translucent: rgba(255, 255, 255, 0.25);

    --success-bg: #d1fae5;
    --success-text: #065f46;
    --success-border: #6ee7b7;
    --error-bg: #fee2e2;
    --error-text: #991b1b;
    --error-border: #fca5a5;
    --warning-bg: #fef3c7;
    --warning-text: #92400e;
    --info-bg: #dbeafe;
    --info-text: #1e40af;
    --pink-bg: #fce7f3;
    --pink-text: #9d174d;

    --success-pill-bg: rgba(34, 197, 94, 0.15);
    --success-pill-text: #166534;
    --success-pill-border: rgba(34, 197, 94, 0.3);
    --warn-pill-bg: rgba(234, 88, 12, 0.15);
    --warn-pill-text: #9a3412;
    --warn-pill-border: rgba(234, 88, 12, 0.3);

    /* Account-type identity (the project-wide convention): faded blue =
       balance-sheet accounts/statements, faded green = income-statement. Reuse
       these wherever an account or statement is tagged by type. */
    --acct-balance-bg: rgba(37, 99, 235, 0.12);
    --acct-balance-text: #1e40af;
    --acct-income-bg: rgba(34, 197, 94, 0.14);
    --acct-income-text: #166534;

    /* A row the app wrote into a statement's transcription by itself. Amber
       rather than the blue a hand-repaired row carries: the two must never
       read as the same kind of row. */
    --row-auto-added-bg: rgba(217, 119, 6, 0.12);
    /* A row the document's own line disagrees with. Red where the two above
       are amber and blue: those mark a row somebody wrote, this one marks a
       row nobody has checked yet. */
    --row-disputed-bg: rgba(239, 68, 68, 0.10);

    --row-hover-bg: #f0f7ff;
    --row-hover-blank: #eaf3fd;

    --card-bg-subtle: #eef2f6;
    --surface-locked: #f1f5f9;
    --muted-pill-bg: #e2e8f0;
    --muted-pill-text: #475569;
    --amount-positive: #047857;
    --amount-negative: #b91c1c;
    --action-needed-bg: #fef3c7;
    --action-needed-text: #92400e;
    --action-needed-border: #fcd34d;
    --action-ready-bg: #ecfdf5;
    --action-ready-text: #047857;
    --action-ready-border: #6ee7b7;

    /* Transaction notes: a distinct violet identity, so a note never reads as
       one of the ledger states (blue = informational/transfer, amber = needs
       you, green = ready). A note is a MEMORY, not a to-do. `-soft` is the
       merely-suggested variant. */
    --note-bg: #f3e8ff;
    --note-text: #6b21a8;
    --note-border: #d8b4fe;
    --note-soft-bg: #faf5ff;
    --note-soft-text: #7e22ce;

    /* A partner's message on a shared row: teal, so somebody TALKING never
       reads as one of the ledger states (violet = a note you took, amber =
       needs you, green = ready, blue = a transfer the engine spotted). */
    --message-bg: #ecfeff;
    --message-text: #0e7490;
    --message-border: #a5f3fc;

    --focus-ring: 0 0 0 3px rgba(37, 99, 235, 0.1);
    --overlay-bg: rgba(0, 0, 0, 0.5);
    --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
    --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
    --shadow-login: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
    --login-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

[data-theme="dark"] {
    --primary-color: #3b82f6;
    --primary-hover: #60a5fa;
    --secondary-color: #94a3b8;
    --secondary-hover: #cbd5e1;
    --success-color: #34d399;
    --error-color: #f87171;
    --error-btn-hover: #ef4444;
    --warning-color: #fbbf24;

    --bg-color: #0b1220;
    --card-bg: #1e293b;
    --item-bg: #172033;
    --bg-white: #1e293b;
    --text-color: #f1f5f9;
    --text-muted: #94a3b8;
    --border-color: #3a4960;

    --success-bg: #064e3b;
    --success-text: #6ee7b7;
    --success-border: #065f46;
    --error-bg: #7f1d1d;
    --error-text: #fca5a5;
    --error-border: #991b1b;
    --warning-bg: #78350f;
    --warning-text: #fcd34d;
    --info-bg: #1e3a8a;
    --info-text: #93c5fd;
    --pink-bg: #831843;
    --pink-text: #fbcfe8;

    --success-pill-bg: rgba(34, 197, 94, 0.18);
    --success-pill-text: #86efac;
    --success-pill-border: rgba(34, 197, 94, 0.4);
    --warn-pill-bg: rgba(234, 88, 12, 0.2);
    --warn-pill-text: #fdba74;
    --warn-pill-border: rgba(234, 88, 12, 0.45);

    --acct-balance-bg: rgba(96, 165, 250, 0.18);
    --acct-balance-text: #bfdbfe;
    --acct-income-bg: rgba(34, 197, 94, 0.2);
    --acct-income-text: #86efac;

    --row-auto-added-bg: rgba(251, 191, 36, 0.22);
    --row-disputed-bg: rgba(248, 113, 113, 0.20);

    --row-hover-bg: #1e3a5f;
    --row-hover-blank: #1a2e4a;

    --card-bg-subtle: #0f172a;
    --surface-locked: #172033;
    --muted-pill-bg: #1e293b;
    --muted-pill-text: #94a3b8;
    --amount-positive: #6ee7b7;
    --amount-negative: #fca5a5;
    --action-needed-bg: #78350f;
    --action-needed-text: #fde68a;
    --action-needed-border: #b45309;
    --action-ready-bg: rgba(16, 185, 129, 0.12);
    --action-ready-text: #6ee7b7;
    --action-ready-border: #047857;

    --note-bg: rgba(168, 85, 247, 0.18);
    --note-text: #e9d5ff;
    --note-border: rgba(168, 85, 247, 0.45);
    --note-soft-bg: rgba(168, 85, 247, 0.10);
    --note-soft-text: #d8b4fe;

    --message-bg: rgba(6, 182, 212, 0.16);
    --message-text: #a5f3fc;
    --message-border: rgba(6, 182, 212, 0.45);

    /* Bump info-text for 4.5:1 contrast on --card-bg in dark mode. */
    --info-text: #bfdbfe;

    --focus-ring: 0 0 0 3px rgba(96, 165, 250, 0.3);
    --overlay-bg: rgba(0, 0, 0, 0.7);
    --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.4), 0 1px 2px -1px rgba(0, 0, 0, 0.4);
    --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.5);
    --shadow-login: 0 25px 50px -12px rgba(0, 0, 0, 0.6);
    --login-gradient: linear-gradient(135deg, #1e293b 0%, #312e81 100%);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --primary-color: #3b82f6;
        --primary-hover: #60a5fa;
        --secondary-color: #94a3b8;
        --secondary-hover: #cbd5e1;
        --success-color: #34d399;
        --error-color: #f87171;
        --error-btn-hover: #ef4444;
        --warning-color: #fbbf24;

        --bg-color: #0b1220;
        --card-bg: #1e293b;
        --item-bg: #172033;
        --bg-white: #1e293b;
        --text-color: #f1f5f9;
        --text-muted: #94a3b8;
        --border-color: #3a4960;

        --success-bg: #064e3b;
        --success-text: #6ee7b7;
        --success-border: #065f46;
        --error-bg: #7f1d1d;
        --error-text: #fca5a5;
        --error-border: #991b1b;
        --warning-bg: #78350f;
        --warning-text: #fcd34d;
        --info-bg: #1e3a8a;
        --info-text: #93c5fd;
        --pink-bg: #831843;
        --pink-text: #fbcfe8;

        --success-pill-bg: rgba(34, 197, 94, 0.18);
        --success-pill-text: #86efac;
        --success-pill-border: rgba(34, 197, 94, 0.4);
        --warn-pill-bg: rgba(234, 88, 12, 0.2);
        --warn-pill-text: #fdba74;
        --warn-pill-border: rgba(234, 88, 12, 0.45);

        --acct-balance-bg: rgba(96, 165, 250, 0.18);
        --acct-balance-text: #bfdbfe;
        --acct-income-bg: rgba(34, 197, 94, 0.2);
        --acct-income-text: #86efac;

        --row-auto-added-bg: rgba(251, 191, 36, 0.22);

        --row-hover-bg: #1e3a5f;
        --row-hover-blank: #1a2e4a;

        --card-bg-subtle: #0f172a;
        --surface-locked: #172033;
        --muted-pill-bg: #1e293b;
        --muted-pill-text: #94a3b8;
        --amount-positive: #6ee7b7;
        --amount-negative: #fca5a5;
        --action-needed-bg: #78350f;
        --action-needed-text: #fde68a;
        --action-needed-border: #b45309;
        --action-ready-bg: rgba(16, 185, 129, 0.12);
        --action-ready-text: #6ee7b7;
        --action-ready-border: #047857;

        --note-bg: rgba(168, 85, 247, 0.18);
        --note-text: #e9d5ff;
        --note-border: rgba(168, 85, 247, 0.45);
        --note-soft-bg: rgba(168, 85, 247, 0.10);
        --note-soft-text: #d8b4fe;

        --message-bg: rgba(6, 182, 212, 0.16);
        --message-text: #a5f3fc;
        --message-border: rgba(6, 182, 212, 0.45);

        --info-text: #bfdbfe;

        --focus-ring: 0 0 0 3px rgba(96, 165, 250, 0.3);
        --overlay-bg: rgba(0, 0, 0, 0.7);
        --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.4), 0 1px 2px -1px rgba(0, 0, 0, 0.4);
        --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.5);
        --shadow-login: 0 25px 50px -12px rgba(0, 0, 0, 0.6);
        --login-gradient: linear-gradient(135deg, #1e293b 0%, #312e81 100%);
    }
}

/* Login Screen */
.login-screen {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: var(--login-gradient);
    z-index: 2000;
}

.login-card {
    background: var(--card-bg);
    border-radius: 20px;
    padding: 48px 40px;
    box-shadow: var(--shadow-login);
    text-align: center;
    max-width: 400px;
    width: 90%;
}

.login-logo {
    width: 72px;
    height: 72px;
    background: var(--primary-color);
    color: var(--on-primary);
    font-size: 2rem;
    font-weight: 700;
    border-radius: 18px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 auto 20px;
}

.login-title {
    font-size: 1.75rem;
    color: var(--text-color);
    margin-bottom: 4px;
}

.login-subtitle {
    color: var(--text-muted);
    font-size: 0.95rem;
    margin-bottom: 32px;
}

.login-card .g_id_signin {
    display: flex;
    justify-content: center;
}

.login-error {
    color: var(--error-color);
    font-size: 0.875rem;
    margin-top: 16px;
}

.dev-user-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.dev-user-button {
    width: 100%;
    justify-content: center;
}

.dev-user-switcher {
    background: var(--bg-color);
    color: var(--text-color);
    border: 1px solid var(--border-color);
    border-radius: 8px;
    padding: 6px 10px;
    font-size: 0.85rem;
    max-width: 200px;
    cursor: pointer;
}

.dev-user-switcher:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: var(--focus-ring);
}

/* Header with user menu */
.header-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
}

.header-brand h1 {
    margin: 0;
}

.header-book {
    display: flex;
    gap: 8px;
    align-items: center;
    flex: 1;
    min-width: 220px;
    max-width: 420px;
}

.header-book select {
    flex: 1;
    padding: 8px 12px;
    border: 1px solid var(--border-color);
    border-radius: 8px;
    font-size: 0.95rem;
    background: var(--bg-white);
    color: var(--text-color);
    cursor: pointer;
}

.header-book select:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: var(--focus-ring);
}

.user-menu {
    display: flex;
    align-items: center;
    gap: 10px;
}

/* Visually hidden but available to assistive tech and kept updatable by JS. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Compact API status — dot + optional pill badges in the header. The
   descriptive text lives in a tooltip + sr-only span. */
.status-compact {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 8px;
    border-radius: 999px;
    background: transparent;
    cursor: help;
}

.status-compact .status-dot {
    width: 8px;
    height: 8px;
}

.status-compact .service-badges {
    margin-left: 0;
    gap: 4px;
}

.status-compact .service-badge {
    font-size: 0.7rem;
    padding: 2px 6px;
    border-radius: 8px;
}

.user-avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    object-fit: cover;
}

/* Avatar menu — name, theme, dev switcher and sign-out behind one handle, so
   the header stays a single row. The panel itself is the shared .menu. */
.avatar-btn {
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    display: block;
}

.user-initial {
    align-items: center;
    justify-content: center;
    background: var(--primary-color);
    color: var(--on-primary);
    font-size: 0.9rem;
    font-weight: 600;
}

.avatar-menu .menu {
    min-width: 200px;
}

.avatar-menu .menu-name {
    padding: 7px 10px;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-muted);
    border-bottom: 1px solid var(--border-color);
    margin-bottom: 4px;
    word-break: break-word;
}

.avatar-menu .dev-user-switcher {
    width: calc(100% - 8px);
    max-width: none;
    margin: 4px;
}

/* Reset & Base Styles */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
    background-color: var(--bg-color);
    color: var(--text-color);
    line-height: 1.6;
    padding: 20px;
}

/* Container */
.container {
    max-width: 2000px;
    margin: 0 auto;
}

/* Header */
header {
    margin-bottom: 20px;
    padding: 16px 0 20px;
    border-bottom: 1px solid var(--border-color);
}

header h1 {
    font-size: 1.5rem;
    color: var(--primary-color);
    margin-bottom: 0;
    white-space: nowrap;
}

.subtitle {
    color: var(--text-muted);
    font-size: 1.1rem;
}

/* Cards */
.card {
    background: var(--card-bg);
    border-radius: 12px;
    padding: 24px;
    margin-bottom: 24px;
    box-shadow: var(--shadow);
    border: 1px solid var(--border-color);
}

.card h2 {
    font-size: 1.5rem;
    margin-bottom: 16px;
    color: var(--text-color);
}

/* Status Indicator */
.status-indicator {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px;
    background: var(--bg-color);
    border-radius: 8px;
}

.status-dot {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--secondary-color);
    animation: pulse 2s ease-in-out infinite;
}

.status-indicator.online .status-dot,
#api-status.online .status-dot {
    background: var(--success-color);
}

.status-indicator.offline .status-dot,
#api-status.offline .status-dot {
    background: var(--error-color);
    animation: none;
}

@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.5; }
}

.service-badges {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
    margin-left: auto;
}

.service-badge {
    font-size: 0.75rem;
    padding: 3px 8px;
    border-radius: 10px;
    font-weight: 500;
    white-space: nowrap;
    cursor: help;
}

.service-badge.service-ok {
    background: var(--success-pill-bg);
    color: var(--success-pill-text);
    border: 1px solid var(--success-pill-border);
}

.service-badge.service-warn {
    background: var(--warn-pill-bg);
    color: var(--warn-pill-text);
    border: 1px solid var(--warn-pill-border);
}

/* Forms */
.form-group {
    margin-bottom: 20px;
}

.form-group label {
    display: block;
    margin-bottom: 8px;
    font-weight: 500;
    color: var(--text-color);
}

.form-group input[type="file"] {
    display: block;
    width: 100%;
    padding: 12px;
    border: 2px dashed var(--border-color);
    border-radius: 8px;
    background: var(--bg-color);
    cursor: pointer;
    transition: all 0.2s;
}

.form-group input[type="file"]:hover {
    border-color: var(--primary-color);
    background: var(--bg-white);
}

.form-group small {
    display: block;
    margin-top: 6px;
    color: var(--text-muted);
    font-size: 0.875rem;
}

/* Buttons */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 12px 24px;
    border: none;
    border-radius: 8px;
    font-size: 1rem;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.2s;
    text-decoration: none;
}

.btn-primary {
    background: var(--primary-color);
    color: var(--on-primary);
}

.btn-primary:hover {
    background: var(--primary-hover);
    box-shadow: var(--shadow-lg);
}

.btn-primary:disabled {
    background: var(--secondary-color);
    cursor: not-allowed;
}

.btn-secondary {
    background: var(--secondary-color);
    color: var(--on-primary);
}

.btn-secondary:hover {
    background: var(--secondary-hover);
}

/* Keyframes for the scoped .spinner-sm rules further down. */
@keyframes spin {
    to { transform: rotate(360deg); }
}

.loading {
    text-align: center;
    color: var(--text-muted);
    padding: 20px;
}

.btn-delete {
    background: var(--error-color);
    color: var(--on-primary);
    border: none;
    cursor: pointer;
}

.btn-delete:hover {
    background: var(--error-btn-hover);
}

.btn-parse {
    background: var(--primary-color);
    color: var(--on-primary);
    border: none;
    cursor: pointer;
}

.btn-parse:hover {
    background: var(--primary-hover);
}

/* Parse-result modal content */
.parse-result-table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 12px;
    font-size: 0.9rem;
}

.parse-result-table th,
.parse-result-table td {
    padding: 8px 10px;
    text-align: left;
    border-bottom: 1px solid var(--border-color);
}

.parse-result-table th {
    background: var(--bg-color);
    font-weight: 600;
}

.parse-result-table td.amount,
.parse-result-table th.amount {
    text-align: right;
    font-family: 'Courier New', monospace;
}

.status-badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
}

.status-badge.status-staged {
    background: var(--warning-bg);
    color: var(--warning-text);
}

.status-badge.status-confirmed {
    background: var(--success-bg);
    color: var(--success-text);
}

.empty-state {
    text-align: center;
    padding: 40px 20px;
    color: var(--text-muted);
}

/* Footer */
/* Work to do tab */
.section-hint {
    color: var(--text-muted);
    font-size: 0.875rem;
    margin: 0 0 12px 0;
}

/* Upload card */
.bank-upload-row {
    display: flex;
    gap: 10px;
    align-items: center;
    flex-wrap: wrap;
}
.bank-upload-row input[type="file"] {
    flex: 1;
    min-width: 260px;
}
.bank-upload-foot {
    margin-top: 8px;
    color: var(--text-muted);
    font-size: 12px;
}

/* Expected accounts in the bank list. One with no file for the month on screen
   is the same card as an upload, with no file attached yet
   (expectedPlaceholderHtml). */
.bfi-expected.is-skipped .fname {
    color: var(--text-muted);
}
/* A missing one is a drop target; highlight it while a file is dragged over. */
.bank-file-item.bfi-expected.dragging {
    border-color: var(--primary-color);
    background: var(--card-bg-subtle);
}
.ec-remove,
.ec-skip {
    border: none;
    background: none;
    color: var(--text-muted);
    font-size: 12px;
    cursor: pointer;
    padding: 2px 4px;
}
.ec-skip {
    color: var(--primary-color);
}
.ec-remove:hover {
    color: var(--error-color);
    text-decoration: underline;
}
.ec-skip:hover {
    text-decoration: underline;
}
/* The list is a flex column; the button keeps its own width. */
.bank-add-expected {
    align-self: flex-start;
}
/* Nomination cards — "make this expected?" suggestions below the list. */
.ec-noms {
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px dashed var(--border-color);
}
.ec-noms-head {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    margin-bottom: 6px;
}
.ec-nom {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    border: 1px solid var(--border-color);
    border-radius: 8px;
    background: var(--card-bg);
    margin-bottom: 6px;
}
.ec-nom-text {
    flex: 1;
    font-size: 13px;
    color: var(--text-color);
}

/* Drag-and-drop target — the upload row highlights while a file is dragged over it. */
.dropzone {
    border: 1px dashed transparent;
    border-radius: 10px;
    padding: 6px;
    margin: -6px;
    transition: border-color 0.15s, background 0.15s;
}
.dropzone.dragging {
    border-color: var(--primary-color);
    background: var(--card-bg-subtle);
}

/* Single "Stage transactions" action — replaces the old per-file stage buttons. */
.bank-stage-bar {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px solid var(--border-color);
}
.bank-stage-bar .spacer {
    flex: 1;
}

/* File cards */
.bank-files-container {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 14px;
}
.bank-file-item {
    border: 1px solid var(--border-color);
    border-radius: 10px;
    background: var(--item-bg);
    overflow: hidden;
    transition: border-color 0.15s;
}
.bank-file-item:hover {
    border-color: var(--secondary-color);
}
.bank-file-item.needs-action {
    border-color: var(--primary-color);
    box-shadow: 0 0 0 1px var(--primary-color) inset;
}
.bank-file-item.state-failed {
    border-color: var(--error-border);
}
.bank-file-item.state-done,
/* Staged: the month on screen is done here, so the card steps back beside the
   ones with work left; its ✓ line keeps its colour. */
.bank-file-item.state-staged {
    opacity: 0.88;
}
/* Scoped to outrank `.bfi-action .prompt`, which it is also. */
.bfi-action .prompt.bfi-staged {
    color: var(--action-ready-text);
    font-weight: 600;
}
/* The card clips its overflow, which would cut the open ⋯ menu off. */
.bank-file-item:has(.menu.open) {
    overflow: visible;
}

/* Cards glide to their new place on a paint that ends the user's own click
   (renderBankList). Names exist only while html.bank-vt is set: a
   view-transition-name makes its element a stacking context for as long as it
   is set, which would trap the ⋯ menu under the next card. Everything that
   moves when a card changes height is named, or it would ghost — cross-fade
   between its old and its new place. */
.bank-vt #bank-files-list .bank-file-item {
    view-transition-name: var(--vt);
}
.bank-vt .bank-add-expected {
    view-transition-name: bank-add-expected;
}
.bank-vt .ec-noms {
    view-transition-name: bank-noms;
}
.bank-vt .not-this-month {
    view-transition-name: bank-not-this-month;
}
.bank-vt #bank-files-list > .empty-state {
    view-transition-name: bank-empty;
}
.bank-vt #bank-stage-bar {
    view-transition-name: bank-stage-bar;
}
.bank-vt #bank-upload-card {
    view-transition-name: bank-upload-card;
}
.bank-vt #period-notes-card {
    view-transition-name: bank-notes-card;
}
.bank-vt #valuation-card {
    view-transition-name: bank-valuation-card;
}
.bank-vt #staged-card {
    view-transition-name: bank-staged-card;
}
/* A growing card unrolls instead of spilling over the one below. */
::view-transition-group(*) {
    animation-duration: 220ms;
    overflow: clip;
}

/* Collapsed "Not <month>'s statement" section — uploads holding rows in the
   banner month that are not its statement. */
.not-this-month {
    margin-top: 6px;
    border: 1px dashed var(--border-color);
    border-radius: 10px;
    padding: 4px 10px;
    background: var(--card-bg-subtle);
}
.not-this-month > summary {
    cursor: pointer;
    color: var(--text-muted);
    font-size: 0.9rem;
    padding: 6px 4px;
    list-style: none;
}
.not-this-month > summary::-webkit-details-marker {
    display: none;
}
.not-this-month > summary::before {
    content: '▸ ';
    color: var(--text-muted);
}
.not-this-month[open] > summary::before {
    content: '▾ ';
}
.not-this-month-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 8px 0 4px;
}

.bfi-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
}
.bfi-head .fname {
    font-weight: 600;
    max-width: 340px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bfi-head .fmeta {
    color: var(--text-muted);
    font-size: 12.5px;
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
    align-items: center;
}
.bfi-head .fmeta .sep {
    color: var(--border-color);
}
.bfi-head .spacer {
    flex: 1;
}

.bfi-action {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 0 14px 14px;
}
.bfi-action .prompt {
    color: var(--text-color);
    font-weight: 500;
    font-size: 13px;
}
.bfi-action .spacer {
    flex: 1;
}
.bfi-action .period-preview {
    color: var(--text-muted);
    font-size: 12.5px;
    font-style: italic;
}
.bfi-action .period-mismatch {
    color: var(--warning-text);
    background: var(--warning-bg);
    border: 1px solid var(--border-color);
    padding: 4px 10px;
    border-radius: 8px;
    font-size: 12.5px;
}

/* Pending sign-convention review: the one card state where the machine stops
   and asks (classifier vs extracted signs disagree). Warning-toned prompt +
   the Flip/Keep decision pair. */
.bfi-sign-review .prompt {
    color: var(--warning-text);
}
.bfi-sign-review .sign-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

/* Unanimity auto-flip note: informational, not a warning — the flip already
   happened and is undoable inline. Overrides .bfi-note's warning tone. */
.bfi-note.bfi-autoflip {
    background: var(--card-bg-subtle);
    color: var(--text-muted);
}
.btn-link {
    background: none;
    border: none;
    padding: 0;
    margin-left: 6px;
    color: var(--primary-color);
    cursor: pointer;
    font-size: inherit;
    text-decoration: underline;
}

.bfi-field {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12.5px;
    color: var(--text-muted);
}
.bfi-field > span {
    font-weight: 500;
}
.bfi-field select {
    min-width: 180px;
}

.seg {
    display: inline-flex;
    border: 1px solid var(--border-color);
    border-radius: 8px;
    overflow: hidden;
    background: var(--bg-white);
}
.seg button {
    background: transparent;
    border: none;
    padding: 7px 12px;
    font: inherit;
    font-size: 12.5px;
    color: var(--text-color);
    cursor: pointer;
    border-right: 1px solid var(--border-color);
}
.seg button:last-child {
    border-right: none;
}
.seg button[aria-pressed="true"] {
    background: var(--primary-color);
    color: var(--on-primary);
}
.seg-help {
    margin-left: 6px;
    font-size: 12px;
    color: var(--text-muted);
    cursor: help;
    border: 1px solid var(--border-color);
    border-radius: 999px;
    width: 18px;
    height: 18px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    background: transparent;
}

.inline-hint {
    display: none;
    margin: 0 14px 12px;
    padding: 10px 12px;
    border-radius: 8px;
    background: var(--info-bg);
    color: var(--info-text);
    font-size: 12.5px;
    line-height: 1.5;
}
.inline-hint.open {
    display: block;
}
/* Warn-toned variant — used for the contextual "why did linking fail?" hint. */
.inline-hint.hint-warn {
    background: var(--warning-bg);
    color: var(--warning-text);
    margin: 12px 0 0;
}

/* Shared-accounts help affordance (docs/plans/shared-accounts-help.md). */
.shared-help {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-wrap: wrap;
    margin-bottom: 10px;
}
.shared-help .inline-hint {
    flex-basis: 100%;
    margin: 6px 0 0;
}
.shared-help-link {
    font-size: 12px;
}
.shared-help-steps {
    margin: 6px 0 6px 18px;
    padding: 0;
}
.shared-help-steps li {
    margin: 2px 0;
}

.bfi-preview {
    padding: 8px 14px 14px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.bank-file-preview-row {
    display: grid;
    grid-template-columns: 110px 1fr 110px;
    gap: 16px;
    align-items: center;
    padding: 6px 8px;
    background: var(--card-bg-subtle);
    border-radius: 6px;
    font-size: 0.9rem;
}
.bank-file-preview-more {
    color: var(--text-muted);
    font-size: 0.85rem;
    padding: 2px 8px;
}

/* Preview thumbnails derived from an upload's own embedded images, shown before
   the slow parse finishes so the user can recognize/assign the file. */
.bfi-thumbs {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 8px 14px;
}
.bfi-thumb {
    height: 48px;
    max-width: 120px;
    object-fit: contain;
    border: 1px solid var(--border-color);
    border-radius: 4px;
    background: var(--card-bg-subtle);
}

.bfi-note {
    padding: 8px 14px;
    background: var(--warning-bg);
    color: var(--warning-text);
    font-size: 12.5px;
}
.bfi-note.error {
    background: var(--error-bg);
    color: var(--error-text);
}

/* The coverage question and its two answers, on the file's own card: "is this
   upload complete and covers all transactions for May 2026?" Warning-toned
   because it gates the month, not because anything is wrong with the file. */
.bfi-coverage {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 10px 14px;
    background: var(--warning-bg);
    color: var(--warning-text);
    font-size: 12.5px;
}
.bfi-coverage .prompt {
    font-weight: 600;
}
/* The reason under the question. Same ink as the question — a muted grey on
   the warning ground fell under 4.5:1 in both themes (review, 2026-09-09) —
   set apart by weight and size instead. */
.bfi-coverage .why {
    color: var(--warning-text);
    font-size: 12px;
}
.bfi-coverage-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}
/* Answered: a settled fact, not an open question — and a No is what keeps the
   month reading "not yet uploaded", so it stays visible and changeable. */
.bfi-coverage.answered,
.bfi-coverage.ignorable {
    flex-direction: row;
    align-items: center;
    gap: 10px;
    background: var(--card-bg-subtle);
    color: var(--text-muted);
}
.bfi-coverage.answered span,
.bfi-coverage.ignorable span {
    flex: 1;
}

.menu-wrap {
    position: relative;
}
.menu {
    position: absolute;
    right: 0;
    top: calc(100% + 4px);
    z-index: 20;
    background: var(--card-bg);
    border: 1px solid var(--border-color);
    border-radius: 8px;
    box-shadow: var(--shadow-lg);
    min-width: 160px;
    padding: 4px;
    display: none;
}
.menu.open {
    display: block;
}
.menu button {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: none;
    font: inherit;
    padding: 7px 10px;
    border-radius: 6px;
    color: var(--text-color);
    cursor: pointer;
    font-size: 13px;
}
.menu button:hover {
    background: var(--card-bg-subtle);
}
.menu button.danger {
    color: var(--error-color);
}
.menu button.danger:hover {
    background: var(--error-bg);
}
.btn-iconish {
    background: transparent;
    border: 1px solid var(--border-color);
    border-radius: 8px;
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
    cursor: pointer;
}

.linkish {
    background: none;
    border: none;
    padding: 2px 4px;
    font: inherit;
    font-size: 13px;
    color: var(--primary-color);
    cursor: pointer;
}
.linkish:hover {
    text-decoration: underline;
}

.btn-xs {
    padding: 2px 8px;
    font-size: 0.78rem;
    line-height: 1.4;
}

.btn.btn-danger {
    background: transparent;
    color: var(--error-color);
    border: 1px solid var(--border-color);
}
.btn.btn-danger:hover {
    background: var(--error-bg);
    border-color: var(--error-border);
}

/* Drafts list */
.staged-head {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}
.staged-head .spacer {
    flex: 1;
}
.staged-scope {
    color: var(--text-muted);
    font-size: 13px;
    margin: 0 0 14px;
}
.staged-scope b {
    color: var(--text-color);
}
/* One line per state of the month, adding up to the total. The numbers sit in a
   column of their own so the sum reads straight down them. */
.scope-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: inline-flex;
    flex-direction: column;
    gap: 2px;
}
.scope-list li {
    display: flex;
    gap: 8px;
}
.scope-n {
    min-width: 4ch;
    text-align: right;
    font-variant-numeric: tabular-nums;
}
/* What a bucket lost to a group row — indented under the line it qualifies. */
.scope-sub {
    padding-left: calc(4ch + 8px);
    font-size: 12px;
    opacity: 0.8;
}
.scope-total {
    border-top: 1px solid var(--border-color);
    padding-top: 3px;
    margin-top: 2px;
}
/* Below the total because it is outside it. */
.scope-extra {
    margin-top: 4px;
}
.staged-actions {
    margin-bottom: 10px;
}
.staging-controls {
    display: flex;
    gap: 8px;
    align-items: center;
}
/* A display filter beside Refresh — it takes rows out of the list and nothing
   else, so it reads as a control on the card, not as a book setting. */
.staged-toggle {
    align-items: center;
    gap: 6px;
    color: var(--text-muted);
    font-size: 13px;
    cursor: pointer;
}

.staged-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 8px;
    min-height: 60px;
}
.staged-list .txn-row {
    border: 1px solid var(--border-color);
    border-radius: 10px;
    background: var(--item-bg);
    padding: 10px 14px;
    display: grid;
    /* Three sections, in the order the card is read: what this row IS (date +
       description), what it books (accounts + amounts), and what the software
       makes of it plus what you must do about it. Both right tracks are fixed so
       every card's columns start at the same x. 320px fits a typical account
       name + amount; 300px holds a wrapped chip sentence. */
    grid-template-columns: minmax(0, 1fr) 320px 300px;
    gap: 14px;
    align-items: start;
}
/* Under ~1100px the description track is squeezed to nothing between the two
   fixed ones, so the three sections stack in the same reading order instead. */
@media (max-width: 1100px) {
    .staged-list .txn-row,
    .awaiting-row.txn-row {
        grid-template-columns: 1fr;
    }
}
.staged-list .txn-row.clickable {
    cursor: pointer;
}
.staged-list .txn-row.clickable:hover {
    border-color: var(--primary-color);
    background: var(--row-hover-bg);
}
/* Ready-to-post cards: clear green fill + accent border (same tokens as the
   "Ready" badge, so both themes work for free). Placed after :hover so the
   fill persists on hover; the border still brightens to primary on hover. */
.staged-list .txn-row.is-ready {
    background: var(--action-ready-bg);
    border-color: var(--action-ready-border);
}
.staged-list .txn-row.is-ready.clickable:hover {
    border-color: var(--primary-color);
}

/* Already in the books — nothing here is work to do. The fill recedes and the
   two content columns dim, while the status column (the POSTED stamp) keeps full
   contrast: dimming the card itself would mute the one word that explains why
   the rest is muted. */
.staged-list .txn-row.is-posted {
    background: var(--card-bg-subtle);
}
.staged-list .txn-row.is-posted .txn-left,
.staged-list .txn-row.is-posted .txn-entries {
    opacity: 0.55;
}
.txn-posted-stamp {
    display: inline-block;
    padding: 4px 12px;
    border: 2px solid var(--border-color);
    border-radius: 6px;
    color: var(--text-muted);
    font-size: 15px;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
}
/* What the card still offers, under the stamp. Not a button: the click target
   is the whole row, as it is on every other card in the list. */
.txn-posted-open {
    color: var(--text-muted);
    font-size: 12px;
}

/* One folded run of broker rows, standing in for its members in the staged list
   (docs/plans/investment-row-volume.md). Dashed, so it reads as a fold rather
   than a transaction; everything inside it is ready, hence the green fill the
   Ready cards already use. */
.staged-list .txn-row.investment-group-row {
    border-style: dashed;
    background: var(--action-ready-bg);
}
/* Nothing in it is ready any more — it is in the books. Drops the green the
   Ready cards wear so the fill never outlives what it claimed, for the same
   receded fill as any other posted row. Needs its own rule: the dashed green
   above is the more specific selector. */
.staged-list .txn-row.investment-group-row.is-posted {
    background: var(--card-bg-subtle);
}
.investment-group-row .txn-head-line {
    flex-direction: row;
    align-items: baseline;
    gap: 8px;
}
.inv-group-caret {
    color: var(--text-muted);
    font-size: 11px;
}
.inv-group-note {
    display: block;
    margin-top: 4px;
    font-size: 12px;
    color: var(--text-muted);
}
.inv-group-toggle {
    color: var(--primary-color);
    font-size: 13px;
    text-align: right;
}

.txn-left {
    min-width: 0;
}
.txn-head-line {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}
.txn-date {
    color: var(--text-muted);
    font-size: 12.5px;
    font-variant-numeric: tabular-nums;
    flex-shrink: 0;
}
.txn-description {
    font-weight: 500;
    min-width: 0;
    /* Wraps to multiple lines for long statement descriptions instead of
       truncating — the full text is the most useful thing on the card. */
    overflow-wrap: anywhere;
    /* `display_description` joins the statement's words and the human's comment
       with a newline; honour it (docs/plans/description-comment-split.md). */
    white-space: pre-line;
}
/* Column 3: everything the software has to say about this row, and every button
   that answers it. One `.txn-mark` per thing it has to say. */
.txn-status-col {
    color: var(--text-muted);
    font-size: 12px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
    min-width: 0;
}
/* A chip and the buttons that answer it. One item per line: a chip is a
   statement and a button is an answer to it, and neither reads as the other's
   continuation. Stacked rather than wrapped, so a chip stays the width of its own
   text — sharing a line with a button stretched the pill to the column. */
.txn-mark {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    width: 100%;
}
/* These chips carry sentences, not one-word statuses — the duplicate chip names
   a row, an amount, a date and a filename. `.badge`'s nowrap would run them
   straight out of the column, so here they wrap, and the dot aligns with the
   first line rather than the middle of the block (same fix as `.badge.message`). */
.txn-mark > .badge,
.txn-mark > .awaiting-reason {
    max-width: 100%;
    white-space: normal;
    align-items: flex-start;
}
.txn-mark > .badge .dot {
    flex: none;
    margin-top: 5px;
}
/* The buttons sit under their chip, left-aligned — they are answers to it, not
   a right-hand action rail (which is what flex-end gave them under the legs). */
.txn-mark > button {
    align-self: flex-start;
    margin-top: 0;
}
.txn-entries {
    display: flex;
    flex-direction: column;
    gap: 2px;
    /* Fills its fixed grid track (see .txn-row); no justify-self/min-width so
       the column edge is the same on every card. */
    min-width: 0;
}
.txn-entry {
    display: flex;
    justify-content: space-between;
    gap: 14px;
    font-size: 13px;
    align-items: baseline;
}
.txn-entry .te-acct {
    color: var(--text-color);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.txn-entry.is-bankleg .te-acct {
    color: var(--primary-color);
    font-weight: 600;
}
.txn-entry.is-placeholder .te-acct {
    color: var(--text-muted);
    font-style: italic;
}

/* Autobalanced entry legs — engine-written counter-legs the user hasn't
   approved yet. Tinted with the "needs review" (amber) action tokens so the
   eye is drawn to them; "(Autobalanced)" tag + "enter amount" placeholder
   read at a glance. */
.txn-entry.is-autobalanced .te-acct {
    color: var(--action-needed-text);
}
/* A leg an attached note wrote — the user's own share, projected by their
   attach click. Violet (the note identity), never the engine's amber: it is
   not awaiting anyone's review. */
.txn-entry.is-note-leg .te-acct {
    color: var(--note-soft-text);
}
.txn-entry .te-auto {
    margin-left: 6px;
    font-size: 11px;
    font-style: italic;
    opacity: 0.75;
}
.txn-entry .te-amt-missing {
    font-size: 12px;
    font-style: italic;
    color: var(--action-needed-text);
}

/* Approve-autobalance button shown inline on an autobalanced staged row. */
.btn-approve-auto {
    align-self: flex-end;
    margin-top: 4px;
    padding: 4px 12px;
    font-size: 12px;
    font-weight: 600;
    border-radius: 6px;
    border: 1px solid var(--action-ready-border);
    background: var(--action-ready-text);
    color: var(--card-bg);
    cursor: pointer;
}
.btn-approve-auto:hover {
    opacity: 0.9;
}
.btn-approve-auto:disabled {
    cursor: default;
    opacity: 0.85;
}
/* In-flight spinner sized for this small button (the 16px `.spinner` is meant
   for the full-size primary button). Foreground is the button's own text color
   (`--card-bg`, which sits on the `--action-ready-text` background) so it reads
   in both themes; the track is the same color at low alpha via border-style. */
.btn-approve-auto .spinner-sm {
    display: inline-block;
    width: 11px;
    height: 11px;
    border: 2px solid var(--action-ready-border);
    border-top-color: var(--card-bg);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
    vertical-align: -1px;
}
/* In-flight spinner for bank-file card buttons (Re-parse / Delete). Unlike the
   approve button these sit on varied backgrounds (secondary, danger), so the
   spinner draws in `currentColor` — it inherits each button's own text color
   and reads correctly in both themes without a per-button rule. The track is
   the same color faded via the border vs. border-top contrast. */
.bank-file-item .spinner-sm {
    display: inline-block;
    width: 11px;
    height: 11px;
    margin-left: 6px;
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: 50%;
    opacity: 0.85;
    animation: spin 0.8s linear infinite;
    vertical-align: -1px;
}
.txn-entry .te-amt {
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-family: 'SF Mono', Menlo, Consolas, monospace;
    font-weight: 600;
    white-space: nowrap;
}
.txn-entry .te-amt.pos {
    color: var(--amount-positive);
}
.txn-entry .te-amt.neg {
    color: var(--amount-negative);
}
/* Superseded remainder amount under an incoming partner split-change: the old
   local value, muted + struck through, sitting just left of the proposed new
   amount (rendered in the normal pos/neg color). */
.txn-entry .te-amt-old {
    margin-right: 10px;
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-family: 'SF Mono', Menlo, Consolas, monospace;
    font-weight: 600;
    white-space: nowrap;
    color: var(--text-muted);
    text-decoration: line-through;
}

/* Badges */
.badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 11.5px;
    font-weight: 600;
    border: 1px solid transparent;
    white-space: nowrap;  /* a status chip is one short phrase — never wrap into a blob */
}
.badge .dot {
    width: 6px;
    height: 6px;
    border-radius: 999px;
    background: currentColor;
}
.badge.ready {
    background: var(--action-ready-bg);
    color: var(--action-ready-text);
    border-color: var(--action-ready-border);
}
.badge.needs {
    background: var(--action-needed-bg);
    color: var(--action-needed-text);
    border-color: var(--action-needed-border);
}
.badge.muted {
    background: var(--muted-pill-bg);
    color: var(--muted-pill-text);
}
.badge.warn {
    background: var(--warning-bg);
    color: var(--warning-text);
}
/* A row the last posting attempt refused (bulk or single) — the strongest
   "stop" chip, so it's the one in error-red. Reason on hover; the full text
   renders in the edit modal. Reuses the --error-* tokens (both themes). */
.badge.error {
    background: var(--error-bg);
    color: var(--error-text);
    border-color: var(--error-border);
}
/* Transfer-merge hint: informational (blue), deliberately distinct from the
   orange .warn duplicate badge — opposite meaning. Reuses the existing --info-*
   tokens (both themes). */
.badge.transfer {
    background: var(--info-bg);
    color: var(--info-text);
}
.btn-merge-transfer {
    align-self: flex-end;
    margin-top: 4px;
    padding: 4px 12px;
    font-size: 12px;
    font-weight: 600;
    border-radius: 6px;
    border: 1px solid var(--primary-color);
    background: var(--primary-color);
    color: var(--card-bg);
    cursor: pointer;
}
.btn-merge-transfer:hover {
    opacity: 0.9;
}
/* Step 2's "don't merge" — the quiet sibling of the primary merge action.
   Muted-pill palette (both themes) so the eye lands on the merge first;
   dismissal is deliberate, not competing. */
.btn-dismiss-merge {
    align-self: flex-end;
    margin-top: 4px;
    padding: 4px 12px;
    font-size: 12px;
    font-weight: 600;
    border-radius: 6px;
    border: 1px solid var(--border-color);
    background: var(--muted-pill-bg);
    color: var(--muted-pill-text);
    cursor: pointer;
}
.btn-dismiss-merge:hover {
    opacity: 0.85;
}
/* The undo link inside a dismissed-pair chip inherits the muted pill text. */
.badge.muted a {
    color: inherit;
    text-decoration: underline;
}

/* Review button on a shared-card row whose partner changed the split
   (shared_review.awaiting === 'mine'). It opens the review modal (Accept/Reject
   the proposed split). Amber "needs attention" fill — deliberately NOT the green
   .btn-approve-auto: a review is expected, the row isn't done. Matches the
   .badge.needs language the accompanying review badge already speaks. */
.btn-review {
    align-self: flex-end;
    margin-top: 4px;
    padding: 4px 12px;
    font-size: 12px;
    font-weight: 600;
    border-radius: 6px;
    border: 1px solid var(--action-needed-border);
    background: var(--action-needed-bg);
    color: var(--action-needed-text);
    cursor: pointer;
}
.btn-review:hover {
    opacity: 0.9;
}

/* Modal: locked bank-leg + "Where did it go?" layout */
.locked-leg {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 14px 16px;
    border-radius: 10px;
    background: var(--surface-locked);
    border: 1px solid var(--border-color);
}
.locked-leg .ll-topline {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
}
.locked-leg .ll-tag {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    font-weight: 600;
    color: var(--text-muted);
    letter-spacing: 0.02em;
    text-transform: uppercase;
}
.locked-leg .ll-tag .dot {
    width: 5px;
    height: 5px;
    border-radius: 999px;
    background: var(--primary-color);
}
.locked-leg .ll-sub {
    color: var(--text-muted);
    font-size: 12px;
    font-weight: 500;
}
/* Source statement file — provenance, shown only here in the modal (not on the
   card). Spread to the far right of the topline by the flex space-between. */
.locked-leg .ll-source {
    color: var(--text-muted);
    font-size: 12px;
    font-style: italic;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 50%;
}
.locked-leg .ll-row {
    display: grid;
    grid-template-columns: 1fr 160px;
    gap: 10px;
}
.locked-leg .ll-acct-sel {
    width: 100%;
    padding: 8px 10px;
    background: var(--card-bg);
    border: 1px solid var(--border-color);
    border-radius: 6px;
    color: var(--text-color);
    font-size: 13px;
}
.locked-leg .ll-amt-input {
    padding: 8px 10px;
    background: var(--card-bg);
    border: 1px solid var(--border-color);
    border-radius: 6px;
    color: var(--text-color);
    font-family: 'SF Mono', Menlo, Consolas, monospace;
    font-weight: 600;
    font-size: 15px;
    text-align: right;
}
.locked-note {
    color: var(--text-muted);
    font-size: 12px;
    margin: 6px 0 18px;
    display: flex;
    gap: 6px;
    line-height: 1.5;
}

/* "Do not post" toggle + reveal-on-check reason, at the foot of the edit modal. */
.do-not-post-block {
    margin-top: 18px;
    padding-top: 14px;
    border-top: 1px solid var(--border-color);
}
.dnp-toggle {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    color: var(--text-color);
    cursor: pointer;
}
.dnp-toggle input[type="checkbox"] {
    accent-color: var(--primary-color);
    width: 15px;
    height: 15px;
    cursor: pointer;
}
.do-not-post-block .modal-field {
    margin-top: 10px;
}

/* The statement's own words in the transaction modal — a value, not an input:
   `description_from_source` is machine-owned and no human write path reaches it
   (docs/plans/description-comment-split.md). Shared by both modal layouts, so
   it carries its own label styling rather than borrowing either one's. */
.src-desc {
    margin-bottom: 16px;
}
.src-desc label {
    display: block;
    font-size: 12.5px;
    font-weight: 500;
    color: var(--text-muted);
    margin-bottom: 4px;
}
.src-desc-text {
    padding: 8px 10px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    background: var(--surface-locked);
    color: var(--text-color);
    font-size: 13px;
}

.modal-row {
    display: grid;
    grid-template-columns: 160px 1fr;
    gap: 12px;
    margin-bottom: 18px;
}
.modal-row label {
    display: block;
    font-size: 12.5px;
    font-weight: 500;
    color: var(--text-muted);
    margin-bottom: 4px;
}
.modal-row input,
.modal-row textarea {
    width: 100%;
    padding: 8px 10px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    font-size: 13px;
    font-family: inherit;
    background: var(--bg-white);
    color: var(--text-color);
}
.modal-row input:focus,
.modal-row textarea:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: var(--focus-ring);
}

.cats {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.cats-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
}
.cats-header h3 {
    margin: 0;
    font-size: 14px;
    font-weight: 600;
}
.cats-header .hint {
    color: var(--text-muted);
    font-size: 12.5px;
}
.cat-row {
    display: grid;
    grid-template-columns: 1fr 140px 32px;
    gap: 8px;
    align-items: center;
}
.cat-row select.entry-account,
.cat-row input.cat-amount {
    padding: 8px 10px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    font-size: 13px;
    background: var(--bg-white);
    color: var(--text-color);
}
.cat-row input.cat-amount {
    width: 100%;
    text-align: right;
    font-family: 'SF Mono', Menlo, Consolas, monospace;
}
.cat-row select.entry-account:focus,
.cat-row input.cat-amount:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: var(--focus-ring);
}

/* Searchable account combobox (AccountPicker) — drop-in replacement for the
   native account <select>s. The .acct-picker wrapper occupies the same grid
   cell a <select> did; the menu floats above. */
.acct-picker {
    position: relative;
    min-width: 0;
}
/* In the flex .entry-row the account column grew via flex:1; the grid .cat-row
   sizes it by position. Keep the flex behaviour for the picker that replaced
   the <select>. */
.entry-row > .acct-picker.entry-account {
    flex: 1;
}
/* The bank-file picker sits in a wrapping flex strip; give it a comfortable
   width rather than shrinking to the placeholder text. */
.acct-picker.bank-file-account {
    flex: 1;
    min-width: 240px;
}
/* Multi-select mode: the picked accounts as removable chips above the search
   box. `:empty` keeps the strip from reserving space before anything is picked,
   so an unused filter is the same height as a plain input. */
.acct-picker-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-bottom: 4px;
}
.acct-picker-chips:empty {
    display: none;
}
.acct-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 4px 2px 8px;
    border-radius: 999px;
    font-size: 12px;
    background: var(--muted-pill-bg);
    color: var(--muted-pill-text);
    /* In dark, --muted-pill-bg equals the card it sits on, so the fill alone
       leaves no pill to see. The border is what makes it a chip in both themes. */
    border: 1px solid var(--border-color);
}
.acct-chip-x {
    border: none;
    background: none;
    padding: 0 3px;
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
    color: var(--text-muted);
}
.acct-chip-x:hover {
    color: var(--amount-negative);
}
/* Fixed width so names stay aligned whether or not a row is ticked. */
.acct-picker-tick {
    width: 12px;
    flex: none;
    color: var(--primary-color);
}

.acct-picker-input {
    width: 100%;
    box-sizing: border-box;
    padding: 8px 10px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    font-size: 13px;
    font-family: inherit;
    background: var(--bg-white);
    color: var(--text-color);
}
.acct-picker-input:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: var(--focus-ring);
}
.acct-picker-input:disabled {
    background: var(--bg-color);
    color: var(--text-muted);
    cursor: default;
}
.acct-picker-menu {
    /* position:fixed + appended to <body> in JS so no scrollable / overflow
       ancestor (modal body, entries-section card) can clip it. Coordinates and
       width are set inline by positionMenu(); z-index clears the modal (1000). */
    position: fixed;
    z-index: 1200;
    min-width: 360px;
    max-height: 280px;
    overflow-y: auto;
    background: var(--bg-white);
    border: 1px solid var(--border-color);
    border-radius: 8px;
    box-shadow: var(--shadow-lg);
    padding: 4px;
}
.acct-picker-group {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    padding: 8px 8px 4px;
}
.acct-picker-opt {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 7px 8px;
    border-radius: 6px;
    font-size: 13px;
    color: var(--text-color);
    cursor: pointer;
}
.acct-picker-opt.is-active,
.acct-picker-opt:hover {
    background: var(--muted-pill-bg);
}
.acct-picker-opt.is-selected .acct-picker-opt-name {
    font-weight: 600;
    color: var(--primary-color);
}
/* Name + type pill on one line; description dimmed beneath. */
.acct-picker-opt-main {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}
/* Linked-account badge — the two-person mark before any account name that
   involves the partner: card, remainder or settlement (linked-account-badge.md).
   currentColor, so it takes the colour of whatever name it prefixes. */
.linked-badge {
    display: inline-flex;
    flex-shrink: 0;
    vertical-align: -0.15em;
    margin-right: 0.3em;
    opacity: 0.85;
}
.linked-badge svg {
    width: 1em;
    height: 1em;
    display: block;
}
/* The picker's resting value is an <input>, so its badge overlays the left
   edge and the text is padded past it. */
.acct-picker-badge {
    position: absolute;
    left: 10px;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    pointer-events: none;
    color: var(--text-muted);
}
.acct-picker-badge:empty {
    display: none;
}
.acct-picker-badge .linked-badge {
    margin-right: 0;
}
.acct-picker.has-badge .acct-picker-input {
    padding-left: 30px;
}
.acct-picker-opt-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.acct-picker-opt-desc {
    font-size: 11.5px;
    color: var(--text-muted);
    opacity: 0.85;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Account-type pill — the shared faded-blue (balance) / faded-green (income)
   convention. Reusable anywhere an account is tagged by type. */
.acct-type-pill {
    flex-shrink: 0;
    font-size: 10px;
    font-weight: 600;
    padding: 1px 7px;
    border-radius: 999px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    white-space: nowrap;
}
.acct-type-pill.is-balance {
    background: var(--acct-balance-bg);
    color: var(--acct-balance-text);
}
.acct-type-pill.is-income {
    background: var(--acct-income-bg);
    color: var(--acct-income-text);
}
.acct-picker-empty {
    padding: 8px;
    font-size: 13px;
    color: var(--text-muted);
}
.acct-picker-all {
    margin-top: 4px;
    padding: 8px;
    border-top: 1px solid var(--border-color);
    font-size: 12.5px;
    color: var(--primary-color);
    cursor: pointer;
}
.acct-picker-all:hover {
    background: var(--muted-pill-bg);
}

/* Shared-card remainder row — pinned account + ownership dropdown instead of
   a remove button (docs/plans/shared-accounts.md, Decision 16). */
.cat-row.remainder-row {
    /* Trailing 32px matches the cat-row's remove-button column so the amount
       (col 3) lines up vertically with the category-row amounts (col 2 + 32px
       gutter). The remainder row has no remove button — the column stays empty. */
    grid-template-columns: 1fr 180px 140px 32px;
}
/* A bill payment DRAINS the remainder, it does not split it — no ownership
   dropdown (settlement-cross-book.md Phase 4), so the row falls back to the plain
   cat-row template and the amount still lines up with the category amounts. */
.cat-row.remainder-row.no-split {
    grid-template-columns: 1fr 140px 32px;
}
.cat-row.remainder-row select.entry-account[disabled] {
    color: var(--text-muted);
    background: var(--bg-color);
}
.cat-row.remainder-row .ownership-select {
    padding: 8px 10px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    font-size: 13px;
    background: var(--bg-white);
    color: var(--text-color);
}
.cat-row.remainder-row .ownership-select:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: var(--focus-ring);
}
.cat-row.remainder-row input.cat-amount[readonly] {
    color: var(--text-muted);
    background: var(--bg-color);
}
/* Review mode (old→new on the split): a caption above the amount naming the
   proposal, and a read-only "was X" below it — both pinned to the amount column
   (col 3) so the row stays a clean account | split | amount layout. */
.cat-row.remainder-row .remainder-suggestion {
    grid-column: 3;
    grid-row: 1;
    align-self: end;
    font-size: 11px;
    font-weight: 600;
    color: var(--primary-color);
    line-height: 1.2;
    margin-bottom: 2px;
}
.cat-row.remainder-row .remainder-old {
    grid-column: 3;
    grid-row: 3;
}
/* "was X" under any amount changed since the modal opened — the review's
   old-value look, so the two read as the same thing. Placed per row layout. */
.cat-row.remainder-row .remainder-old,
.amount-was {
    font-size: 11px;
    color: var(--text-muted);
    line-height: 1.2;
    margin-top: 2px;
}
.cat-row > .amount-was,
.locked-leg .ll-row > .amount-was {
    grid-column: 2;
    grid-row: 2;
}
.cat-row.remainder-row:not(.no-split) > .amount-was {
    grid-column: 3;
}
/* Category rows stack with no gap, so at the row's 8px gap the note sat nearer
   the NEXT row's amount and read as its caption. Hug our own amount instead. */
.cat-row:has(> .amount-was) {
    row-gap: 0;
    margin-bottom: 6px;
}
/* A plain row is flex; while it shows "was X" it becomes the equivalent grid so
   the note can sit under the amount column. */
.entry-row:has(> .amount-was) {
    display: grid;
    grid-template-columns: 1fr 130px auto;
}
.entry-row > .amount-was {
    grid-column: 2;
    grid-row: 2;
}
.entry-row.manual-remainder-row:has(> .amount-was) {
    grid-template-columns: 1fr auto 130px;
}
.entry-row.manual-remainder-row > .amount-was {
    grid-column: 3;
}
/* Amounts coloured like the transaction cards (syncAmountTones). The ids lift the
   tone over each layout's own input colour, the muted read-only partner's share
   included — its grey background still marks it read-only. */
#entries-list .entry-amount.pos,
#ll-amt-input.pos {
    color: var(--amount-positive);
}
#entries-list .entry-amount.neg,
#ll-amt-input.neg {
    color: var(--amount-negative);
}
/* With a suggestion caption the row grows to 3 implicit rows; keep the first two
   real controls on row 2 so the caption sits above them. */
.cat-row.remainder-row:has(.remainder-suggestion) select.entry-account,
.cat-row.remainder-row:has(.remainder-suggestion) .ownership-select,
.cat-row.remainder-row:has(.remainder-suggestion) input.cat-amount {
    grid-row: 2;
}

/* "Awaiting your review" banner in the edit modal of a shared-card copy. */
.shared-review-note,
.posted-cover-note {
    background: var(--warning-bg);
    color: var(--warning-text);
    border-radius: 6px;
    padding: 10px 12px;
    font-size: 13px;
    line-height: 1.5;
    margin: 0 0 14px;
}

/* What a staged merged row was made from, above its Unmerge button. Nobody is
   waiting on anything, so info-blue rather than the amber above. */
.merged-from-note {
    background: var(--info-bg);
    color: var(--info-text);
    border-radius: 6px;
    padding: 10px 12px;
    font-size: 13px;
    line-height: 1.5;
    margin: 0 0 14px;
}

/* The shape question (E3) and its answer, both inside the edit modal:
   docs/plans/card-refund-double-claim.md. The two answers carry EQUAL weight —
   a visual preselect would read as endorsing amounts this question never
   checks. */
.shape-assertion-dialog {
    background: var(--info-bg);
    color: var(--info-text);
    border-radius: 6px;
    padding: 12px 14px;
    font-size: 13px;
    line-height: 1.5;
    margin: 0 0 14px;
}

.shape-assertion-dialog h4 {
    margin: 0 0 6px;
    font-size: 14px;
}

.shape-assertion-dialog p {
    margin: 0 0 10px;
}

.shape-assertion-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* The rest of the modal while the question is up: visibly and actually inert,
   because the request that will carry the answer was built before it appeared. */
.shape-assertion-frozen {
    opacity: 0.5;
    pointer-events: none;
}

.shape-assertion-sibling {
    background: var(--warning-bg);
    color: var(--warning-text);
    border-radius: 6px;
    padding: 8px 10px;
    margin: 0 0 10px;
}

.shape-assertion-refusal {
    background: var(--error-bg);
    color: var(--error-text);
    border-radius: 6px;
    padding: 10px 12px;
    font-size: 13px;
    line-height: 1.5;
    margin: 0 0 14px;
}

.cat-remove {
    background: transparent;
    border: none;
    color: var(--text-muted);
    font-size: 18px;
    cursor: pointer;
    line-height: 1;
    border-radius: 6px;
    width: 32px;
    height: 32px;
}
.cat-remove:hover {
    background: var(--error-bg);
    color: var(--error-color);
}
.cat-split-link {
    align-self: flex-start;
    margin-top: 4px;
}

/* Row tools under the leg list, on the rows' own columns: "+ Add account" under
   the accounts, Clear / Distribute stacked under the amounts. Quiet on purpose:
   shortcuts for typing, not the modal's action. */
.leg-tools {
    display: grid;
    grid-template-columns: 1fr 140px 32px;     /* = .cat-row */
    gap: 8px;
    align-items: start;
}
.entries-section .leg-tools {
    grid-template-columns: 1fr 130px auto;     /* = .entry-row: amount 130px, × auto */
    padding: 8px 16px;
    border-top: 1px solid var(--border-color);
}
.leg-split {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
/* A plain row's × is as wide as its glyph, so a hidden copy sizes that column. */
.leg-gutter {
    visibility: hidden;
}
.leg-tool {
    padding: 4px 10px;
    font-size: 12px;
    font-weight: 500;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    background: var(--bg-white);
    color: var(--text-color);
    cursor: pointer;
}
.leg-tool:hover:not(:disabled) {
    border-color: var(--primary-color);
    color: var(--primary-color);
}
.leg-tool:disabled {
    color: var(--text-muted);
    cursor: default;
    opacity: 0.6;
}
.leg-add {
    justify-self: start;
}

.status-line {
    display: flex;
    align-items: center;
    gap: 10px;
    font-weight: 600;
    font-size: 13px;
    margin-top: 16px;
    padding-top: 14px;
    border-top: 1px solid var(--border-color);
}
.status-line .dot {
    width: 8px;
    height: 8px;
    border-radius: 999px;
    display: inline-block;
    background: var(--text-muted);
}
.status-line.ready {
    color: var(--success-color);
}
.status-line.ready .dot {
    background: var(--success-color);
}
.status-line.needs {
    color: var(--warning-color);
}
.status-line.needs .dot {
    background: var(--warning-color);
}
.status-line .amt {
    font-family: 'SF Mono', Menlo, Consolas, monospace;
}

/* Legacy balance footer — hidden in statement mode; still present so
   any code path that only touches #entries-balance-amount keeps working. */
.entries-balance[hidden] {
    display: none !important;
}

/* Navigation Tabs */
.nav-tabs {
    display: flex;
    gap: 4px;
    margin-bottom: 24px;
    background: var(--card-bg);
    border-radius: 12px;
    padding: 4px;
    box-shadow: var(--shadow);
    border: 1px solid var(--border-color);
    overflow-x: auto;
}

.nav-tab {
    flex: 1;
    padding: 12px 20px;
    border: none;
    border-radius: 8px;
    background: transparent;
    font-size: 1rem;
    font-weight: 500;
    color: var(--text-muted);
    cursor: pointer;
    transition: all 0.2s;
}

.nav-tab:hover {
    color: var(--text-color);
    background: var(--bg-color);
}

.nav-tab.active {
    background: var(--primary-color);
    color: var(--on-primary);
}

/* On a phone the five labels don't fit one row; wrapping keeps all of them
   visible where scrolling hid whichever fell off the right edge. */
@media (max-width: 480px) {
    .nav-tabs {
        flex-wrap: wrap;
        overflow-x: visible;
    }

    .nav-tab {
        flex: 1 1 auto;
        padding: 10px 12px;
        font-size: 0.9rem;
    }
}

/* Count chip inside a nav-tab label — reuses the "needs attention" action
   tokens so it reads as a to-do badge in both themes. Hidden when empty. The
   awaiting one is its own click target (it jumps to the earliest period), so
   it keeps the pointer cursor. */
.nav-count {
    display: inline-block;
    cursor: pointer;
    margin-left: 6px;
    min-width: 18px;
    padding: 0 6px;
    border-radius: 9px;
    background: var(--action-needed-bg);
    color: var(--action-needed-text);
    font-size: 0.8rem;
    font-weight: 600;
    line-height: 18px;
    text-align: center;
}
.nav-count:empty { display: none; }

/* Awaiting-approval router rows. These are shared-card rows that need a decision
   but have no local draft of their own (fileless/manual origin) — they now live
   IN the staged list (docs/plans/shared-accounts-worktodo.md §5), so they need
   their own grid rather than .staged-list's card layout (their markup differs).
   Same three tracks as a staged card, so the columns line up across the list. */
.awaiting-row.txn-row {
    border: 1px solid var(--border-color);
    border-radius: 10px;
    background: var(--item-bg);
    padding: 10px 14px;
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px 300px;
    gap: 14px;
    align-items: start;
}
.awaiting-row.txn-row.clickable {
    cursor: pointer;
}
.awaiting-row.txn-row.clickable:hover {
    border-color: var(--primary-color);
}

/* Inbox router row's reason line — a plain descriptive sentence ("Sigve shared
   this — review & post your copy"), deliberately NOT a pill badge (a wrapped
   sentence-in-a-pill reads as a blob). Sits beside the short status pill. */
.awaiting-reason {
    color: var(--text-muted);
    font-size: 12px;
}

/* Period nav — global period selector. Today only the Work to do tab
   reads its value; other tabs keep their own period controls. Chips, the
   "Annen periode" picker and the selected period's header stack here. */
.period-other {
    position: relative;
}
/* A bubble over the page (same layer as .menu), its arrow pointing at the pill. */
.period-picker {
    position: absolute;
    top: calc(100% + 10px);
    left: 0;
    z-index: 20;
    display: flex;
    gap: 16px;
    align-items: center;
    padding: 12px 14px;
    background: var(--card-bg);
    border: 1px solid var(--border-color);
    border-radius: 12px;
    box-shadow: var(--shadow-lg);
}
.period-picker[hidden] {
    display: none;
}
.period-picker::before {
    content: '';
    position: absolute;
    top: -6px;
    left: 48px;
    width: 10px;
    height: 10px;
    background: var(--card-bg);
    border-top: 1px solid var(--border-color);
    border-left: 1px solid var(--border-color);
    transform: rotate(45deg);
}
/* Period-close toggle: subtle until closed, then success-tinted. */
#period-close-btn.is-closed {
    background: var(--success-bg);
    color: var(--success-text);
    border-color: var(--success-border);
}
#period-close-btn.is-closed:hover {
    border-color: var(--success-color);
}
.period-field {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--text-muted);
    font-size: 0.9rem;
    font-weight: 500;
}
.period-field select {
    padding: 6px 10px;
    border: 1px solid var(--border-color);
    border-radius: 8px;
    background: var(--bg-white);
    color: var(--text-color);
    font-size: 0.9rem;
    cursor: pointer;
}
.period-field select:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: var(--focus-ring);
}

/* Period-chip row — "unfinished business at a glance" (one chip per period with
   open work; click jumps the tab there). Leads with the "Annen periode" pill.
   A second row of the tab bar's card: tabs and chips are both "where to go",
   everything below the rail is the selected month's work. */
.period-chips {
    display: flex;
    gap: 8px;
    align-items: center;
    flex-wrap: wrap;
    padding: 10px 12px;
    background: var(--card-bg);
    border: 1px solid var(--border-color);
    border-top: none;
    border-radius: 0 0 12px 12px;
    box-shadow: var(--shadow);
}
/* Joined to the chip row only while it is shown (updatePeriodNavVisibility). */
.nav-tabs.has-period-chips {
    margin-bottom: 0;
    border-radius: 12px 12px 0 0;
}
/* The rail: from the month title down through every card of the tab. */
#work-to-do-section {
    margin: 16px 0 0 4px;
    padding-left: 20px;
    border-left: 3px solid var(--primary-color);
}
/* Stays under the top of the window so a long draft list never loses its
   month. Opaque, because the cards scroll beneath it; the spread hides their
   shadows, which bleed past the bar's edges. */
#work-to-do-section > .period-header {
    position: sticky;
    top: 0;
    z-index: 10;
    margin-bottom: 0;
    padding: 12px 0 16px;
    background: var(--bg-color);
    box-shadow: 0 0 0 4px var(--bg-color);
}
/* scrollIntoView({block: 'nearest'}) would park the card under the bar. */
#bank-files-list .bank-file-item {
    scroll-margin-top: 170px;
}
/* The month's tasks under its title: a ring until the task is finished, then a
   green pill with a ✓ (renderPeriodTasks). */
.period-header-main {
    min-width: 0;
}
.period-tasks-group {
    margin-top: 6px;
}
.period-tasks-label {
    margin-bottom: 3px;
    color: var(--text-muted);
    font-size: 0.65rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
.period-tasks {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 3px;
    margin: 0;
    padding: 0;
    list-style: none;
}
.period-task {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 1px 10px 1px 6px;
    border-radius: 999px;
    border: 1px solid var(--border-color);
    background: var(--muted-pill-bg);
    color: var(--muted-pill-text);
    font-size: 0.8rem;
    line-height: 1.25;
    font-weight: 500;
}
.period-task-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    border: 1.5px solid currentColor;
    font-size: 0.65rem;
    line-height: 1;
}
.period-task-count {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
.period-task.is-done {
    background: var(--success-pill-bg);
    color: var(--success-pill-text);
    border-color: var(--success-pill-border);
}
.period-task.is-done .period-task-mark {
    background: var(--success-pill-text);
    color: var(--card-bg);
}
/* Title and close button share a row; the pills take the full width below, so
   the sticky bar stays short and the button does not wrap. */
@media (max-width: 640px) {
    #work-to-do-section > .period-header {
        display: grid;
        grid-template-columns: 1fr auto;
        column-gap: 12px;
    }
    .period-header-main {
        display: contents;
    }
    .period-tasks-group {
        grid-column: 1 / -1;
    }
    #period-close-btn {
        grid-area: 1 / 2;
    }
}
/* The three task cards fold to their title. The title is the toggle, so it is
   a button dressed as the heading; a finished task carries a ✓ after it. */
.task-toggle {
    padding: 0;
    border: none;
    background: none;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
}
.task-toggle::before {
    content: '▾ ';
    color: var(--text-muted);
}
.task-toggle:focus-visible {
    outline: none;
    border-radius: 6px;
    box-shadow: var(--focus-ring);
}
.task-card.is-done .task-toggle::after {
    content: ' ✓';
    color: var(--success-pill-text);
}
.task-card.is-collapsed {
    padding-top: 16px;
    padding-bottom: 16px;
}
.task-card.is-collapsed .task-toggle::before {
    content: '▸ ';
}
.task-card.is-collapsed h2 {
    margin-bottom: 0;
}
/* Controls for a list that is not showing. */
.task-card.is-collapsed .staging-controls {
    display: none;
}
@media (max-width: 640px) {
    #work-to-do-section {
        padding-left: 12px;
    }
}
/* Chips are rendered into this wrapper; it must not be a flex item itself. */
.period-chips-list {
    display: contents;
}
.period-chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 5px 10px;
    border-radius: 999px;
    border: 1px solid var(--border-color);
    background: var(--muted-pill-bg);
    color: var(--muted-pill-text);
    font-size: 0.85rem;
    font-weight: 500;
    cursor: pointer;
    transition: border-color 0.12s, box-shadow 0.12s;
}
.period-chip:hover {
    border-color: var(--primary-color);
}
.period-chip:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}
/* Not a period — it opens the picker; dashed says "action", lit while open. */
.period-chip-other {
    border-style: dashed;
}
.period-chip-other[aria-expanded="true"] {
    border-color: var(--primary-color);
}
.period-chip-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: 999px;
    background: var(--card-bg);
    color: var(--text-muted);
    font-size: 0.75rem;
    font-weight: 600;
}
/* Current period — primary-tinted so you always see where you are. */
.period-chip.is-selected {
    background: var(--primary-color);
    color: var(--on-primary);
    border-color: var(--primary-color);
}
.period-chip.is-selected .period-chip-count {
    background: var(--on-primary-translucent);
    color: var(--on-primary);
}
/* Closed-but-work-returned (auto-re-opened, §2) — warn-tinted: "this month is
   back". Selected wins over reopened (the selected rule follows in source). */
.period-chip.is-reopened {
    background: var(--warn-pill-bg);
    color: var(--warn-pill-text);
    border-color: var(--warn-pill-border);
}
.period-chip.is-reopened .period-chip-count {
    background: var(--card-bg);
    color: var(--warn-pill-text);
}

/* Transactions is ONE level of cards: its section drops its chrome, so the
   heading and the filter row sit straight on the page and the rows are the only
   cards. "Where does the section end" stops being a question because there is
   nothing to end. Here only — Work to do's sections carry buttons that would
   float on the page, and Setup and Notes are short lists
   (docs/plans/card-surfaces.md, B). */
#tab-transactions .card {
    background: none;
    border: none;
    box-shadow: none;
    padding: 0;
}
#tab-transactions .txn-row {
    background: var(--card-bg);
    box-shadow: var(--shadow);
}
/* The row's usual hover lifts it to --bg-white, which IS its resting colour
   here, so it needs the tinted fill the staged cards use instead. */
#tab-transactions .txn-row:hover {
    background: var(--row-hover-bg);
}

/* Transactions-tab period filter (reuses .period-field for the selects). The
   view editor's account filter is the same bar. */
.txn-filters {
    display: flex;
    gap: 16px;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 16px;
}
.txn-filters .spacer {
    flex: 1;
}
/* The text box matches the period selects rather than the browser default, so
   the bar reads as one row of controls. */
.txn-search-field input {
    padding: 6px 10px;
    border: 1px solid var(--border-color);
    border-radius: 8px;
    background: var(--bg-white);
    color: var(--text-color);
    font-size: 0.9rem;
    font-family: inherit;
    min-width: 260px;
}
.txn-search-field input:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: var(--focus-ring);
}
/* Wide enough for a chip or two beside the search box before wrapping. */
.txn-account-field #txn-account-host,
.txn-account-field #ve-account-host {
    min-width: 220px;
}
.txn-scope {
    color: var(--text-muted);
    font-size: 0.85rem;
}

/* What the default hide subtracted, stated above the list it subtracted from —
   the scope line reports the same subtraction as "showing N of TOTAL"
   (docs/plans/investment-row-volume.md). */
.txn-hidden-notice {
    display: flex;
    align-items: baseline;
    gap: 4px;
    color: var(--text-muted);
    font-size: 0.85rem;
    padding: 6px 12px;
    margin-bottom: 6px;
    background: var(--item-bg);
    border: 1px dashed var(--border-color);
    border-radius: 8px;
}

/* Book Selector */
.book-selector {
    display: flex;
    gap: 12px;
    align-items: center;
}

.book-selector select {
    flex: 1;
    padding: 10px 12px;
    border: 1px solid var(--border-color);
    border-radius: 8px;
    font-size: 1rem;
    background: var(--bg-color);
    color: var(--text-color);
    cursor: pointer;
}

.book-selector select:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: var(--focus-ring);
}

/* Inline Forms */
.inline-form {
    display: flex;
    gap: 8px;
    align-items: center;
    margin-top: 12px;
    flex-wrap: wrap;
}

.inline-form input[type="text"],
.inline-form select {
    padding: 8px 12px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    font-size: 0.9rem;
    background: var(--bg-white);
    color: var(--text-color);
}

.inline-form input[type="text"]:focus,
.inline-form select:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: var(--focus-ring);
}

/* Share inside the account Edit form: not a form action, so it sits apart at
   the far end, carrying the same two-person mark as a shared account's name. */
.inline-form .btn-share {
    margin-left: auto;
}

/* Small buttons */
.btn-sm {
    padding: 8px 16px;
    font-size: 0.875rem;
}

/* A leading glyph that takes the button's text colour and size. */
.btn-with-icon svg {
    width: 1em;
    height: 1em;
    flex-shrink: 0;
}

/* Account Items */
.account-group-title {
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-muted);
    margin: 20px 0 8px;
    padding-bottom: 4px;
    border-bottom: 1px solid var(--border-color);
}

.account-group-title:first-child {
    margin-top: 0;
}

.accounts-container {
    min-height: 60px;
}

.account-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 10px 12px;
    margin-bottom: 4px;
    background: var(--item-bg);
    border-radius: 8px;
    border: 1px solid var(--border-color);
    transition: background 0.15s;
}

.account-item:hover {
    background: var(--bg-white);
    box-shadow: var(--shadow);
}

.account-info {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.account-name {
    font-size: 0.95rem;
}

.account-description {
    font-size: 0.85rem;
    color: var(--text-muted);
}

#new-account-starting-balance-fields {
    display: flex;
    gap: 8px;
}

.account-actions {
    display: flex;
    gap: 8px;
}

/* Section Header (title + action button side by side) */
.section-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 16px;
}

.section-header h2 {
    margin-bottom: 0;
}

/* Transaction List */
.transactions-container {
    min-height: 60px;
}

.txn-row {
    display: grid;
    grid-template-columns: 110px 1fr 220px 110px;
    column-gap: 16px;
    row-gap: 2px;
    align-items: center;
    padding: 12px;
    margin-bottom: 4px;
    background: var(--item-bg);
    border-radius: 8px;
    border: 1px solid var(--border-color);
    cursor: pointer;
    transition: background 0.15s;
}

/* A row awaiting THIS book's split review gets a trailing auto column for the
   Review-split button. The button pins to row 1 column 5 so it sits beside the
   amount even on multi-leg rows — and so the legs, which place themselves, do
   not find its cell taken. */
.txn-row--reviewable {
    grid-template-columns: 110px 1fr 220px 110px auto;
}
.txn-row--reviewable .btn-review {
    grid-row: 1;
    grid-column: 5;
    align-self: center;
    margin-top: 0;
    justify-self: end;
}

.txn-row .txn-date,
.txn-row .txn-desc {
    grid-row: 1;
}

/* Each leg names only its column; auto-placement walks the pairs down the rows,
   so a five-leg row needs no per-leg style from the renderer. Layout the row
   carries inline cannot be restacked by a media query, which is what kept this
   row wider than a phone (plans/transactions-phone-overflow.md). Child selector:
   both classes are shared with the bank-file preview row. */
.txn-row > .txn-account {
    grid-column: 3;
}
.txn-row > .txn-amount {
    grid-column: 4;
}

.txn-entry-secondary {
    color: var(--text-muted);
    font-size: 0.85rem;
}

.txn-row:hover {
    background: var(--bg-white);
    box-shadow: var(--shadow);
}

.txn-date {
    font-family: 'Courier New', monospace;
    font-size: 0.9rem;
    color: var(--text-muted);
    white-space: nowrap;
}

.txn-desc {
    font-size: 0.95rem;
    min-width: 0;
    overflow: hidden;
    /* The comment sits on its own line under the statement's words
       (`display_description`). Clamped at two so one long merchant string plus a
       long comment cannot make a ledger row grow without bound — this card used
       to be a single nowrap line with an ellipsis, and the clamp is that same
       bound one line lower. */
    white-space: pre-line;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
}

.txn-account {
    font-size: 0.875rem;
    color: var(--text-muted);
    white-space: nowrap;
}

.txn-amount {
    font-family: 'Courier New', monospace;
    font-size: 0.9rem;
    text-align: right;
    white-space: nowrap;
    min-width: 80px;
}
/* Green/red by whether the leg GREW net worth, not by sign (js
   `amountToneClass`). Outranks .txn-entry-secondary's mute, so every leg of a
   row is coloured — the row's colour pattern is the signal. */
.txn-amount.pos {
    color: var(--amount-positive);
}
.txn-amount.neg {
    color: var(--amount-negative);
}
/* Superseded remainder amount on the transactions tab under an incoming partner
   split-change: the old value, muted + struck through, just left of the proposed
   new amount (which keeps the normal .txn-amount color). Mirrors .te-amt-old on
   the work-to-do tab. */
.txn-amount-old {
    margin-right: 8px;
    color: var(--text-muted);
    text-decoration: line-through;
}

/* The four fixed tracks need 512px before the description gets a pixel, so a
   loaded month panned the whole page on a phone. Three tracks instead: the date
   and the amount keep their own edges, and the merchant and the account names
   each take the middle plus the edge track they do not need. */
@media (max-width: 640px) {
    .txn-row {
        grid-template-columns: auto minmax(0, 1fr) auto;
        column-gap: 10px;
        row-gap: 4px;
        align-items: start;
    }
    .txn-row > .txn-desc {
        grid-column: 2 / -1;
    }
    .txn-row > .txn-account {
        grid-column: 1 / 3;
    }
    .txn-row > .txn-amount {
        grid-column: 3;
    }
    /* Under the legs, at the margin: there is no fifth track to sit in. */
    .txn-row--reviewable .btn-review {
        grid-row: auto;
        grid-column: 1 / -1;
        justify-self: start;
    }
}

/* Modal */
.modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: var(--overlay-bg);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
    padding: 20px;
}

/* Modal.ask's question: above the modal and any picker menu (1200), below the
   login screen (2000). */
.question-overlay { z-index: 1500; }

.modal {
    background: var(--card-bg);
    border-radius: 16px;
    width: 100%;
    max-width: 640px;
    max-height: 90vh;
    display: flex;
    flex-direction: column;
    box-shadow: var(--shadow-lg);
}

.modal-sm { max-width: 400px; }
.modal-lg { max-width: 860px; }

/* Generic form layout inside modals */
.modal-form {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.modal-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.modal-field label {
    font-size: 0.85rem;
    font-weight: 500;
    color: var(--text-muted);
}

.modal-field input[type="text"],
.modal-field input[type="date"],
.modal-field input[type="number"],
.modal-field select,
.modal-field textarea {
    width: 100%;
    padding: 9px 12px;
    border: 1px solid var(--border-color);
    border-radius: 8px;
    font-size: 0.95rem;
    background: var(--bg-white);
    color: var(--text-color);
    font-family: inherit;
}

.modal-field input:focus,
.modal-field select:focus,
.modal-field textarea:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: var(--focus-ring);
}

.modal-field input:disabled,
.modal-field select:disabled,
.modal-field textarea:disabled {
    background: var(--bg-color);
    color: var(--text-muted);
    cursor: default;
}

.modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 20px 24px;
    border-bottom: 1px solid var(--border-color);
}

.modal-header h2 {
    margin: 0;
    font-size: 1.25rem;
}

.modal-close {
    background: none;
    border: none;
    font-size: 1.5rem;
    color: var(--text-muted);
    cursor: pointer;
    padding: 4px 8px;
    border-radius: 6px;
    line-height: 1;
}

.modal-close:hover {
    background: var(--bg-color);
    color: var(--text-color);
}

.modal-body {
    padding: 24px;
    overflow-y: auto;
    flex: 1;
}

.modal-footer {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 16px 24px;
    border-top: 1px solid var(--border-color);
}

.modal-footer .btn {
    margin-bottom: 0;
}

/* A destructive footer action (the edit modal's Unmerge) stands apart from
   Cancel and Save, and fills once its first click has armed it. */
.modal-footer .btn-danger:first-child {
    margin-right: auto;
}
.modal-footer .btn-danger.is-armed {
    background: var(--error-bg);
    border-color: var(--error-border);
}

/* Repairing a statement that doesn't add up — the modal behind the file card's
   "Repair rows" (docs/plans/statement-balance-repair.md). It edits the file's
   transcription, so it reads as a document view: the rows as they were read,
   with the ledger's hold on them stated per row. */
.repair-summary p {
    margin: 0 0 10px;
    line-height: 1.5;
}
.repair-note {
    color: var(--text-muted);
    font-size: 0.85rem;
}
.repair-gap {
    font-weight: 600;
    padding: 8px 12px;
    border-radius: 8px;
    margin-bottom: 16px;
}
.repair-gap:empty { display: none; }
.repair-gap.off {
    background: var(--error-bg);
    color: var(--error-text);
}
.repair-gap.ok {
    background: var(--success-bg);
    color: var(--success-text);
}

.repair-restage {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 16px;
    font-size: 0.9rem;
    color: var(--text-muted);
}

.repair-sign {
    background: var(--warning-bg);
    color: var(--warning-text);
    border-radius: 8px;
    padding: 12px 14px;
    margin-bottom: 16px;
}
.repair-sign p { margin: 0 0 10px; line-height: 1.45; }
.repair-sign-actions { display: flex; gap: 8px; }

/* The printed lines no row matches, quoted so they can be found on the paper.
   Informational rather than alarming — the summary above already carries the
   gap, and a second red block reads as a second defect. */
.repair-missing {
    background: var(--info-bg);
    color: var(--info-text);
    border-radius: 8px;
    padding: 12px 14px;
    margin-bottom: 16px;
    font-size: 0.9rem;
}
.repair-missing p { margin: 0 0 8px; line-height: 1.45; }
.repair-missing .btn-link { color: inherit; }
.repair-line-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.repair-line-list li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px; }
.repair-line-quote { font-weight: 600; font-variant-numeric: tabular-nums; }
/* The line's own text, on a row of its own under its date and amount. */
.repair-line-whole { flex-basis: 100%; overflow-wrap: anywhere; }

/* Rows saying something the document's own line does not. Alarming, where
   `.repair-missing` above deliberately is not: that block sits under a
   balance summary that already carries the warning, and this one has none —
   a disputed bill foots perfectly. */
.repair-disputes {
    background: var(--error-bg);
    color: var(--error-text);
    border-radius: 8px;
    padding: 12px 14px;
    margin-bottom: 16px;
    font-size: 0.9rem;
}
.repair-disputes p { margin: 0; line-height: 1.45; }
.repair-disputes .btn-link { color: inherit; }

.repair-rows {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9rem;
}
.repair-rows th {
    text-align: left;
    font-weight: 600;
    color: var(--text-muted);
    border-bottom: 1px solid var(--border-color);
    padding: 6px 8px;
}
.repair-rows td {
    padding: 6px 8px;
    border-bottom: 1px solid var(--border-color);
    vertical-align: middle;
}
.repair-rows .num { text-align: right; white-space: nowrap; }
.repair-rows td.num.neg { color: var(--amount-negative); }
.repair-rows td.num.pos { color: var(--amount-positive); }
.repair-rows td.actions { text-align: right; white-space: nowrap; }
.repair-rows tr.locked td { background: var(--surface-locked); }
/* Hand-repaired: a tint that reads as "annotated", not as "selected" — the
   translucent account-identity token, which stays soft in both themes. */
.repair-rows tr.repaired td { background: var(--acct-balance-bg); }
.repair-rows tr.auto-added td { background: var(--row-auto-added-bg); }
.repair-rows tr.disputed td,
.repair-rows tr.repair-dispute td { background: var(--row-disputed-bg); }
/* The finding hangs off its row, so it keeps the row's bottom border to close
   the pair and only loses the gap between them. */
.repair-rows tr.repair-dispute td { padding-top: 0; font-size: 0.85rem; }
.repair-dispute-ask { margin: 2px 0 6px; line-height: 1.45; }
.repair-dispute-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}
.repair-rows tr.repair-provenance td {
    border-bottom: none;
    padding-top: 0;
    color: var(--text-muted);
    font-size: 0.8rem;
}
.repair-rows tr.repair-form input,
.repair-rows tr.repair-form textarea {
    width: 100%;
    padding: 5px 8px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    background: var(--bg-white);
    color: var(--text-color);
    font-family: inherit;
    font-size: 0.9rem;
}
.repair-where { color: var(--text-muted); font-size: 0.8rem; }
.repair-empty { color: var(--text-muted); }

.repair-add { margin-top: 18px; }
.repair-add h3 { margin: 0 0 10px; font-size: 1rem; }
.repair-add-fields {
    display: grid;
    grid-template-columns: minmax(140px, 1fr) 2fr minmax(120px, 1fr);
    gap: 12px;
}
.repair-add .repair-provenance {
    color: var(--text-muted);
    font-size: 0.8rem;
    min-height: 1.2em;
    margin-top: 6px;
}
.repair-add-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 10px;
}

/* Transaction Fields */
.txn-fields {
    display: flex;
    gap: 12px;
    margin-bottom: 24px;
}

.txn-field label {
    display: block;
    font-size: 0.85rem;
    font-weight: 500;
    color: var(--text-muted);
    margin-bottom: 4px;
}

.txn-field input[type="date"],
.txn-field input[type="text"],
.txn-field textarea {
    width: 100%;
    padding: 8px 12px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    font-size: 0.9rem;
    background: var(--bg-white);
    color: var(--text-color);
}

.txn-field input:disabled {
    background: var(--bg-color);
    color: var(--text-muted);
    cursor: default;
}

.txn-field input:focus,
.txn-field textarea:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: var(--focus-ring);
}

.txn-field-wide {
    flex: 1;
}

/* Entries Section */
.entries-section {
    border: 1px solid var(--border-color);
    border-radius: 10px;
    overflow: hidden;
}

.entries-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 12px 16px;
    background: var(--bg-color);
    border-bottom: 1px solid var(--border-color);
}

.entries-header h3 {
    font-size: 0.95rem;
    margin: 0;
}

.entries-header .btn {
    margin-bottom: 0;
}

/* Entry Rows */
.entry-row {
    display: flex;
    gap: 8px;
    align-items: center;
    padding: 10px 16px;
    border-bottom: 1px solid var(--border-color);
}

.entry-row:last-child {
    border-bottom: none;
}

.entry-row select.entry-account {
    flex: 1;
    padding: 8px 10px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    font-size: 0.85rem;
    background: var(--bg-white);
    color: var(--text-color);
}

.entry-row input.entry-amount {
    width: 130px;
    padding: 8px 10px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    font-size: 0.9rem;
    font-family: 'Courier New', monospace;
    text-align: right;
    background: var(--bg-white);
    color: var(--text-color);
}

.entry-row select:focus,
.entry-row input:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: var(--focus-ring);
}

.btn-remove-entry {
    background: none;
    border: none;
    font-size: 1.25rem;
    color: var(--text-muted);
    cursor: pointer;
    padding: 4px 8px;
    border-radius: 4px;
    line-height: 1;
}

.btn-remove-entry:hover {
    color: var(--error-color);
    background: var(--error-bg);
}

/* Readonly entry rows (view mode) */
.entry-row-readonly {
    justify-content: space-between;
}

.entry-account-label {
    font-size: 0.9rem;
}

.entry-amount-label {
    font-family: 'Courier New', monospace;
    font-size: 0.9rem;
    font-weight: 600;
    white-space: nowrap;
}

.amount-positive {
    color: var(--success-color);
}

.amount-negative {
    color: var(--error-color);
}

/* Balance Footer */
.entries-balance {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 12px 16px;
    background: var(--bg-color);
    border-top: 1px solid var(--border-color);
    font-weight: 600;
    font-size: 0.9rem;
}

.balance-amount {
    font-family: 'Courier New', monospace;
}

.balance-amount.balanced {
    color: var(--success-color);
}

.balance-amount.unbalanced {
    color: var(--error-color);
}

/* View Editor */
.view-category-block {
    border: 1px solid var(--border-color);
    border-radius: 10px;
    background: var(--item-bg);
    margin-bottom: 12px;
    overflow: hidden;
}

.view-category-header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    background: var(--bg-color);
    border-bottom: 1px solid var(--border-color);
}

.category-name-input {
    flex: 1;
    font-weight: 600;
    font-size: 0.95rem;
    border: 1px solid transparent;
    border-radius: 4px;
    padding: 2px 6px;
    background: transparent;
    color: var(--text-color);
    transition: border-color 0.15s, background 0.15s;
}

.category-name-input:hover {
    border-color: var(--border-color);
    background: var(--bg-white);
}

.category-name-input:focus {
    outline: none;
    border-color: var(--primary-color);
    background: var(--bg-white);
    box-shadow: var(--focus-ring);
}

.category-name-label {
    flex: 1;
    font-weight: 600;
    font-size: 0.95rem;
    color: var(--text-muted);
}

.category-count {
    font-size: 0.8rem;
    color: var(--text-muted);
    white-space: nowrap;
}

.btn-icon {
    background: none;
    border: none;
    cursor: pointer;
    font-size: 1.1rem;
    line-height: 1;
    padding: 2px 6px;
    border-radius: 4px;
    color: var(--text-muted);
}

.btn-icon.btn-danger:hover {
    background: var(--error-bg);
    color: var(--error-color);
}

/* Category Account Rows */
.category-accounts {
    padding: 4px 0;
}

.category-account-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 14px;
    border-bottom: 1px solid var(--border-color);
}

.category-account-row:last-child {
    border-bottom: none;
}

.category-account-row .account-name {
    flex: 1;
}

.move-account-select {
    font-size: 0.8rem;
    padding: 4px 8px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    background: var(--bg-white);
    color: var(--text-muted);
    cursor: pointer;
    max-width: 160px;
}

.move-account-select:focus {
    outline: none;
    border-color: var(--primary-color);
}

/* The description sits dimmed under the name: the search box matches it, so a
   hit on a word the name lacks still explains itself. */
.category-account-row .account-description {
    display: block;
}

/* Bulk move: the row checkboxes feed this bar. Sticky so it stays in reach while
   ticking down a long list; opaque because the rows scroll beneath it. */
.ve-bulk-bar {
    display: flex;
    align-items: center;
    gap: 12px;
    position: sticky;
    top: 0;
    z-index: 10;
    padding: 8px 14px;
    margin-bottom: 12px;
    background: var(--bg-color);
    border: 1px solid var(--border-color);
    border-radius: 10px;
}
.ve-bulk-bar .spacer {
    flex: 1;
}
.ve-bulk-bar .move-account-select {
    max-width: 220px;
}
.ve-select-all {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.9rem;
    cursor: pointer;
}
.ve-select-all input,
.ve-account-check {
    cursor: pointer;
}

.category-empty {
    padding: 12px 14px;
    color: var(--text-muted);
    font-size: 0.875rem;
    font-style: italic;
}

/* Toggle Button Group (view selector, year selector) */
.btn-group {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.btn-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 16px;
    border: 1px solid var(--border-color);
    border-radius: 8px;
    background: var(--bg-white);
    color: var(--text-color);
    font-size: 0.9rem;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.15s;
}

.btn-toggle:hover {
    border-color: var(--primary-color);
    color: var(--primary-color);
}

.btn-toggle.active {
    background: var(--primary-color);
    border-color: var(--primary-color);
    color: var(--on-primary);
}

/* Summary picker — one labelled row per view type, then the years. The label
   column is what tells you a view's type, so the buttons carry no pill. */
.summary-picker-row {
    display: flex;
    align-items: baseline;
    gap: 12px;
    margin-top: 12px;
}

.summary-picker-label {
    flex: 0 0 120px;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-muted);
}

/* Blue for balance sheet, green for income statement — the account-type colours
   every account name carries (.acct-type-pill), so the picker says a view's type
   the same way. The active fill is the type's TEXT token: it was chosen to
   contrast with the card in each theme, so the card colour reads on top of it. */
.summary-picker-row.is-balance .summary-picker-label { color: var(--acct-balance-text); }
.summary-picker-row.is-income .summary-picker-label { color: var(--acct-income-text); }
.summary-picker-row.is-balance .btn-toggle {
    background: var(--acct-balance-bg);
    color: var(--acct-balance-text);
    border-color: transparent;
}
.summary-picker-row.is-income .btn-toggle {
    background: var(--acct-income-bg);
    color: var(--acct-income-text);
    border-color: transparent;
}
.summary-picker-row.is-balance .btn-toggle:hover,
.summary-picker-row.is-income .btn-toggle:hover {
    border-color: currentColor;
}
.summary-picker-row.is-balance .btn-toggle.active {
    background: var(--acct-balance-text);
    color: var(--card-bg);
}
.summary-picker-row.is-income .btn-toggle.active {
    background: var(--acct-income-text);
    color: var(--card-bg);
}

/* Category filter — a dropdown, not a row of toggles like the years: a view can
   hold 100+ categories, so the list needs a search box and its own scroll. The
   picker card does not clip, so plain absolute positioning is enough (unlike
   .acct-picker-menu, which has modals and scroll containers to escape). */
.summary-cat-filter {
    position: relative;
}

.summary-cat-panel {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    z-index: 20;
    width: 280px;
    max-width: 90vw;
    padding: 8px;
    background: var(--bg-white);
    border: 1px solid var(--border-color);
    border-radius: 8px;
    box-shadow: var(--shadow-lg);
}

.summary-cat-panel-head {
    display: flex;
    gap: 6px;
    margin-bottom: 6px;
}

.summary-cat-search {
    flex: 1;
    min-width: 0;
    padding: 6px 8px;
    background: var(--bg-white);
    color: var(--text-color);
    border: 1px solid var(--border-color);
    border-radius: 6px;
    font-size: 0.85rem;
}

.summary-cat-search:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: var(--focus-ring);
}

.summary-cat-list {
    max-height: 260px;
    overflow-y: auto;
}

.summary-cat-opt {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px;
    border-radius: 6px;
    font-size: 0.85rem;
    color: var(--text-color);
    cursor: pointer;
}

.summary-cat-opt:hover {
    background: var(--row-hover-bg);
}

@media (max-width: 640px) {
    .summary-picker-row {
        flex-direction: column;
        gap: 6px;
    }

    /* Stacked, the 120px basis would size the label's HEIGHT. */
    .summary-picker-label {
        flex: 0 0 auto;
    }
}

/* Summary Table */
.summary-table-wrapper {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.summary-table {
    border-collapse: collapse;
    font-size: 0.85rem;
    min-width: 100%;
    white-space: nowrap;
}

.summary-cat-col {
    min-width: 180px;
    max-width: 240px;
    white-space: normal;
}

.summary-year-header {
    text-align: center;
    font-weight: 700;
    padding: 8px 4px;
    background: var(--primary-color);
    color: var(--on-primary);
    border-right: 2px solid var(--on-primary);
}

.summary-year-header:last-child {
    border-right: none;
}

.summary-month-header {
    text-align: right;
    padding: 6px 8px;
    color: var(--text-muted);
    font-weight: 500;
    background: var(--bg-color);
    border-bottom: 1px solid var(--border-color);
    border-right: 1px solid var(--border-color);
}

.summary-cat-name {
    padding: 7px 10px;
    font-weight: 500;
    border-right: 1px solid var(--border-color);
    border-bottom: 1px solid var(--border-color);
    background: var(--bg-white);
    position: sticky;
    left: 0;
    z-index: 1;
}

/* A category with accounts: its name is the button that opens them. */
.summary-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 0;
    border: none;
    background: none;
    cursor: pointer;
    font: inherit;
    color: inherit;
    text-align: left;
}
.summary-chevron {
    display: inline-block;
    font-size: 0.8em;
    color: var(--text-muted);
    transition: transform 0.15s;
}
.summary-toggle[aria-expanded="true"] .summary-chevron {
    transform: rotate(90deg);
}
.summary-account-row .summary-cat-name {
    padding-left: 32px;
    font-weight: 400;
    color: var(--text-muted);
}
/* Once any category is open the figures are two kinds — category sums and the
   accounts under them. Sums turn bold; account rows recede onto a tinted ground
   with an edge marking them as belonging to the row above. The :not(:hover)
   and :not(.summary-blank) keep the row hover and the blank-cell shade winning. */
.summary-table.has-expanded tbody tr:not(.summary-account-row) .summary-cat-name,
.summary-table.has-expanded tbody tr:not(.summary-account-row) .summary-cell {
    font-weight: 700;
}
.summary-table.has-expanded .summary-account-row:not(:hover) .summary-cat-name,
.summary-table.has-expanded .summary-account-row:not(:hover) .summary-cell:not(.summary-blank) {
    background: var(--item-bg);
}
.summary-table.has-expanded .summary-account-row .summary-cat-name {
    box-shadow: inset 3px 0 0 var(--border-color);
}

.summary-cell {
    text-align: right;
    padding: 7px 10px;
    border-right: 1px solid var(--border-color);
    border-bottom: 1px solid var(--border-color);
    font-family: 'Courier New', monospace;
    font-size: 0.82rem;
}

.summary-blank {
    background: var(--bg-color);
}

.summary-pos {
    color: var(--text-color);
}

.summary-neg {
    color: var(--error-color);
}

tbody tr:hover .summary-cat-name,
tbody tr:hover .summary-cell {
    background: var(--row-hover-bg);
}

tbody tr:hover .summary-blank {
    background: var(--row-hover-blank);
}

.summary-total-row .summary-cat-name,
.summary-total-row .summary-cell {
    font-weight: 700;
    background: var(--card-bg-subtle);
    border-top: 2px solid var(--border-color);
}

/* Responsive */
@media (max-width: 640px) {
    .header-row {
        gap: 8px;
    }

    .header-book {
        min-width: 0;
    }

    header h1 {
        font-size: 1.4rem;
        text-align: center;
    }

    .card {
        padding: 16px;
    }

    .book-selector {
        flex-direction: column;
    }

    .inline-form {
        flex-direction: column;
        align-items: stretch;
    }

    .inline-form input[type="text"],
    .inline-form select {
        width: 100%;
    }

    .txn-fields {
        flex-direction: column;
    }

    .entry-row {
        flex-wrap: wrap;
    }

    .entry-row select.entry-account {
        width: 100%;
    }

    .entry-row input.entry-amount {
        flex: 1;
    }

    .modal {
        max-height: 95vh;
    }

    .account-item {
        flex-direction: column;
        align-items: flex-start;
        gap: 8px;
    }

    .account-actions {
        width: 100%;
        justify-content: flex-end;
    }
}

/* Dev-only: model comparison card (temporary) */
.compare-models-card {
    border: 1px dashed var(--border-color);
}

.compare-status {
    margin-top: 0.75rem;
    color: var(--text-muted);
    font-size: 0.9rem;
}

.compare-results {
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
    margin-top: 1rem;
}

.compare-file {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.compare-file-name {
    font-family: monospace;
    font-size: 0.95rem;
    margin: 0;
    color: var(--text-color);
}

.compare-summary {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem 1.25rem;
    font-size: 0.85rem;
    color: var(--text-muted);
    padding: 0.4rem 0.6rem;
    border-left: 3px solid var(--border-color);
    background: var(--bg-color);
    border-radius: 0 4px 4px 0;
}

.compare-summary-match {
    color: var(--success-color);
    font-weight: 500;
}

.compare-summary-diff {
    color: var(--warning-color);
    font-weight: 500;
}

.compare-summary-partial {
    color: var(--text-muted);
    font-style: italic;
}

.compare-results-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
}

@media (max-width: 900px) {
    .compare-results-row {
        grid-template-columns: 1fr;
    }
}

.compare-result {
    border: 1px solid var(--border-color);
    border-radius: 6px;
    padding: 0.75rem;
    background: var(--bg-white);
    overflow-x: auto;
}

.compare-result-header {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
    border-bottom: 1px solid var(--border-color);
    padding-bottom: 0.4rem;
}

.compare-result-model {
    font-family: monospace;
    font-size: 0.9rem;
    color: var(--text-color);
}

.compare-result-meta {
    font-size: 0.85rem;
    color: var(--text-muted);
}

.compare-result-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.85rem;
}

.compare-result-table th,
.compare-result-table td {
    padding: 0.3rem 0.4rem;
    text-align: left;
    border-bottom: 1px solid var(--border-color);
    vertical-align: top;
}

.compare-result-table th {
    color: var(--text-muted);
    font-weight: 500;
}

.compare-result-table td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Toasts — non-blocking feedback. Stacked bottom-right, above the login screen
   (z-index 2000) and the modal overlay (z-index 1000). Colors resolve from the
   existing action/status tokens, so both themes are covered without new tokens. */
.toast-container {
    position: fixed;
    bottom: 20px;
    right: 20px;
    z-index: 2100;
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-width: min(420px, calc(100vw - 40px));
    pointer-events: none;
}
.toast {
    padding: 12px 16px;
    border-radius: 8px;
    border: 1px solid var(--border-color);
    background: var(--card-bg);
    color: var(--text-color);
    font-size: 13px;
    box-shadow: var(--shadow-lg);
    opacity: 0;
    transform: translateY(8px);
    transition: opacity 0.2s, transform 0.2s;
    pointer-events: auto;
}
.toast.show {
    opacity: 1;
    transform: translateY(0);
}
.toast-success {
    background: var(--success-bg);
    color: var(--success-text);
    border-color: var(--success-border);
}
.toast-error {
    background: var(--error-bg);
    color: var(--error-text);
    border-color: var(--error-border);
}
.toast-info {
    background: var(--info-bg);
    color: var(--info-text);
    border-color: var(--info-text);
}
.toast-close {
    float: right;
    margin: -4px -8px 0 8px;
    padding: 0 4px;
    background: none;
    border: none;
    color: inherit;
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
}

/* An error's headline with the server's own text one click away (errorHtml) —
   in a toast, the modal's refusal panel, or a list that failed to load.
   pre-wrap: some server refusals are lists. */
.error-headline {
    margin: 0;
}
.error-details {
    margin-top: 6px;
    font-size: 12px;
}
.error-details summary {
    cursor: pointer;
}
.error-details-text {
    margin-top: 4px;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* ── Shared accounts (handshake) ─────────────────────────────── */
.shared-accounts-block {
    margin-top: 24px;
    padding-top: 4px;
    border-top: 1px solid var(--border-color);
}

.shared-empty {
    font-size: 0.9rem;
    color: var(--text-muted);
    margin: 8px 0 0;
}

.shared-invite {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    padding: 12px;
    margin-bottom: 8px;
    background: var(--action-needed-bg);
    border: 1px solid var(--action-needed-border);
    border-radius: 8px;
}

.shared-invite-text { font-size: 0.9rem; }
.shared-invite-actions { display: flex; gap: 8px; }

.shared-list {
    list-style: none;
    margin: 8px 0 0;
    padding: 0;
}

.shared-list-item {
    display: flex;
    flex-wrap: wrap;    /* a partner's email is one unbreakable word; at phone width the pill wraps under it */
    align-items: center;
    gap: 12px;
    padding: 10px 12px;
    margin-bottom: 4px;
    background: var(--item-bg);
    border: 1px solid var(--border-color);
    border-radius: 8px;
}

.shared-list-name { font-weight: 600; font-size: 0.95rem; }
/* 160px basis: beside a long account name at phone width the whole "with …"
   drops to its own line instead of squeezing to a column of letters. */
.shared-list-partner { color: var(--text-muted); font-size: 0.85rem; flex: 1 1 160px; overflow-wrap: break-word; }

.shared-status {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--muted-pill-bg);
    color: var(--muted-pill-text);
}

.shared-status-active {
    background: var(--action-ready-bg);
    color: var(--action-ready-text);
}

.shared-actions {
    margin-top: 12px;
}

.modal-form .modal-label {
    display: block;
    margin-top: 12px;
    font-size: 0.85rem;
    color: var(--text-muted);
}

.modal-form .modal-label input,
.modal-form .modal-label select {
    display: block;
    width: 100%;
    margin-top: 4px;
    padding: 8px 10px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    background: var(--bg-white);
    color: inherit;
    font-size: 0.95rem;
    box-sizing: border-box;
}

/* ── Transaction notes ─────────────────────────────────────────────────────
   A note is a private, non-ledger memory-at-purchase record (see
   docs/transaction-notes.md). Its violet identity is deliberately outside the
   ledger palette — a note is never a to-do, never a state the books are in.
   Phone-first: the quick-add form collapses to one column below 640px, because
   the capture case IS standing at a till. */

.note-form {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.note-form-row {
    display: flex;
    gap: 14px;
    flex-wrap: wrap;
}
.note-form-row > .note-field {
    flex: 1 1 220px;
}

.note-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
    font-size: 0.85rem;
    color: var(--text-muted);
}
.note-field > span em {
    font-style: normal;
    opacity: 0.75;
}
.note-field input[type="text"],
.note-field input[type="date"],
.note-field input[type="number"],
.note-field textarea {
    padding: 10px 12px;
    border: 1px solid var(--border-color);
    border-radius: 8px;
    background: var(--bg-white);
    color: var(--text-color);
    font: inherit;
    font-size: 0.95rem;
    box-sizing: border-box;
    width: 100%;
}
.note-field input:focus,
.note-field textarea:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: var(--focus-ring);
}

.note-amount-row,
.note-custom-share {
    display: flex;
    gap: 8px;
    align-items: stretch;
    flex-wrap: wrap;
}
.note-amount-row input,
.note-custom-share input {
    flex: 1 1 120px;
    min-width: 0;
}
/* The seg groups sit beside a text input, so match its height rather than the
   compact bank-card sizing. */
.note-form .seg button,
.note-capture-card .seg button {
    padding: 9px 14px;
}

.note-form-actions {
    display: flex;
    justify-content: flex-end;
}
.note-form-actions .btn {
    min-width: 140px;
    justify-content: center;
}

/* ── Note cards (the Notes tab list + the work-to-do surfacing) ── */

/* ── Month-end values (docs/plans/investment-valuation.md) ────────────────── */
.valuation-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* One row per account, and the row IS the disclosure: its head line always
   reads, its body opens per row (docs/plans/month-end-card-rework.md, A). The
   cap keeps a label and its figure within a few centimetres of each other on a
   wide screen — the whole complaint behind this rework. */
.valuation-row {
    border: 1px solid var(--border-color);
    border-radius: 10px;
    padding: 12px 14px;
    background: var(--item-bg);
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-width: 560px;
}
.valuation-row.is-quiet {
    padding: 10px 14px;
}
/* A shut row is its head line and nothing else, so the body's gap must not
   print as padding under it. */
.valuation-row:not([open]) {
    gap: 0;
}

.valuation-row-head {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
    cursor: pointer;
    list-style: none;
}
.valuation-row-head::-webkit-details-marker {
    display: none;
}
.valuation-row-head::before {
    content: '▸ ';
    color: var(--text-muted);
}
.valuation-row[open] > .valuation-row-head::before {
    content: '▾ ';
}
/* Nothing to open: no marker, no pointer. */
.valuation-row.is-quiet .valuation-row-head {
    cursor: default;
}
.valuation-row.is-quiet .valuation-row-head::before {
    content: '';
}
.valuation-note,
.valuation-head-note {
    color: var(--text-muted);
    font-size: 0.85rem;
}
/* The figure the head line carries sits at the row's right edge — near its
   label, because the row is capped. */
.valuation-head-note {
    margin-left: auto;
}

/* Labels in a column, values right beside them: a figure belongs next to the
   word for it, and the column still lines the numbers up down the block.
   Stacks below the phone breakpoint, where a 2-column row would squeeze the
   input to nothing. */
.valuation-field {
    display: flex;
    align-items: center;
    gap: 12px;
}
.valuation-field label {
    color: var(--text-muted);
    font-size: 0.85rem;
    flex: 0 0 11em;
}
/* `hidden` is a UA rule, and an author `display` beats it whatever the
   specificity — the derived line sets the attribute and would still paint. */
.valuation-field[hidden] {
    display: none;
}
.valuation-static {
    font-variant-numeric: tabular-nums;
    color: var(--text-color);
}
.valuation-static.pos { color: var(--amount-positive); }
.valuation-static.neg { color: var(--amount-negative); }

.valuation-input {
    width: 160px;
    text-align: right;
    font-variant-numeric: tabular-nums;
    padding: 6px 8px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    background: var(--bg-white);
    color: var(--text-color);
}

.valuation-explained {
    border-left: 2px solid var(--border-color);
    padding-left: 12px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.valuation-legend {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.valuation-plug {
    border-top: 1px solid var(--border-color);
    padding-top: 8px;
    font-weight: 600;
    flex-wrap: wrap;
}
/* Where the plug lands, and the two acts available — both after the figure, so
   the column of numbers runs unbroken down the body. */
.valuation-plug .valuation-note {
    font-weight: 400;
}
.valuation-actions {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 12px;
}

/* The way back out of an answered month. The sentence beside the button is the
   one thing the undo does NOT touch — the draft it wrote
   (docs/plans/undo-answered-month.md) — so it wraps under the button rather
   than being cut off on a narrow card. */
.valuation-undo {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-top: 4px;
}
.valuation-undo .valuation-note {
    flex: 1 1 16em;
}

/* The month's movements the books already hold — read, never typed over. */
.valuation-movements {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.valuation-movement {
    display: flex;
    align-items: baseline;
    gap: 10px;
    font-size: 0.85rem;
}
.valuation-mv-date {
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    flex: 0 0 3.2em;
}
.valuation-mv-desc {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.valuation-mv-acct,
.valuation-mv-staged {
    color: var(--text-muted);
}
.valuation-mv-staged {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-left: 6px;
}
.valuation-mv-amt {
    font-variant-numeric: tabular-nums;
    flex: 0 0 auto;
}
.valuation-mv-amt.pos { color: var(--amount-positive); }
.valuation-mv-amt.neg { color: var(--amount-negative); }

/* Securities that arrived with no cash. A warning panel rather than a quiet
   list: the books have no entry for these, so the month-end plug takes the
   arrival for a gain until the user says otherwise. The rows reuse the
   movement layout above — same shape, different source. */
.valuation-transfers {
    display: flex;
    flex-direction: column;
    gap: 4px;
    color: var(--warning-text);
    background: var(--warning-bg);
    border-radius: 6px;
    padding: 8px 10px;
}
.valuation-transfers .valuation-legend,
.valuation-transfers .valuation-mv-date,
.valuation-transfers .valuation-mv-acct {
    color: inherit;
    opacity: 0.75;
}
.valuation-transfer-amt {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    flex: 0 0 auto;
}
.valuation-transfer-note {
    margin: 2px 0 0;
    font-size: 0.8rem;
    opacity: 0.85;
}

/* A line whose account the user picks here, rather than one registered on the
   investment: picker, amount, and a way to take it back off. */
.valuation-extra {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.valuation-extra-line {
    display: flex;
    align-items: center;
    gap: 8px;
}
.valuation-extra-line .valuation-line-account {
    flex: 1 1 auto;
    min-width: 0;
}
.valuation-line-remove {
    flex: 0 0 auto;
    border: none;
    background: none;
    cursor: pointer;
    color: var(--text-muted);
    font-size: 1.1rem;
    line-height: 1;
    padding: 2px 6px;
    border-radius: 4px;
}
.valuation-line-remove:hover {
    color: var(--amount-negative);
    background: var(--row-hover-bg);
}

/* "of which cash" sits under the market value it is part of. */
.valuation-sub label {
    padding-left: 16px;
}

.valuation-ack-line {
    display: flex;
    align-items: baseline;
    gap: 4px;
    flex-wrap: wrap;
}
.valuation-ack {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    font-size: 0.85rem;
    color: var(--text-color);
}

/* Which statements are still out — a fact about the MONTH, so one line at the
   top of the card. Whether a row is ready is a fact about the row, and that is
   its own head line. */
.valuation-incomplete {
    margin: 0;
    max-width: 560px;
    font-size: 0.85rem;
    color: var(--warning-text);
    background: var(--warning-bg);
    border-radius: 6px;
    padding: 6px 10px;
}

/* What the broker's own export already answers. The checkbox's replacement
   states a fact; the blocked notice asks for the one click that makes it one. */
.valuation-covered,
.valuation-blocked,
.valuation-check {
    margin: 0;
    font-size: 0.85rem;
}
.valuation-covered {
    color: var(--text-muted);
}
.valuation-blocked {
    color: var(--warning-text);
    background: var(--warning-bg);
    border-radius: 6px;
    padding: 6px 10px;
}
/* Sits under "of which cash", indented with it. */
.valuation-check {
    padding-left: 16px;
}
.valuation-check.is-match { color: var(--amount-positive); }
.valuation-check.is-differs { color: var(--amount-negative); }

/* A covered month's one remaining question: which figure the broker screen was
   read from. Two numbers sit side by side there and they are not the same. */
.valuation-reads {
    border: 1px solid var(--border-color);
    border-radius: 6px;
    margin: 0;
    padding: 8px 10px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.valuation-reads legend {
    padding: 0 4px;
    font-size: 0.8rem;
    color: var(--text-muted);
}
.valuation-reads label {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.85rem;
    color: var(--text-color);
}

/* What the typed figure works out to as securities, shown before the draft is
   written — where a total that does not net the credit line shows itself. */
.valuation-derived .valuation-static {
    color: var(--text-muted);
    font-size: 0.85rem;
}

/* Per-account config on the Setup tab. `.account-item` is a flex ROW and
   `.inline-form` a wrapping flex row inside it, so the edit body gets one
   container of its own — otherwise this block competes for space with the name,
   type and description instead of sitting under them. */
.account-edit {
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: 100%;
}
.valuation-config {
    max-width: 420px;
    border-top: 1px solid var(--border-color);
    padding-top: 10px;
    margin-top: 4px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.valuation-hint {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.8rem;
    line-height: 1.45;
}
.valuation-toggle {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.9rem;
    color: var(--text-color);
}
.valuation-fields {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
/* `display` beats the `hidden` attribute, so the untick has to say so itself —
   without this the market-value fields show on every unticked account. */
.valuation-fields[hidden] {
    display: none;
}
.valuation-label {
    color: var(--text-muted);
    font-size: 0.8rem;
}
.valuation-from {
    align-self: flex-start;
}
/* The broker-role block, outside the market-value toggle it used to live in. */
.valuation-section {
    display: flex;
    flex-direction: column;
    gap: 6px;
    border-top: 1px dashed var(--border-color);
    padding-top: 8px;
}
.valuation-section-title {
    margin: 0;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-color);
}

@media (max-width: 560px) {
    .valuation-field {
        flex-direction: column;
        align-items: stretch;
        gap: 4px;
    }
    /* A column flex container reads `flex-basis` as a HEIGHT, so the label's
       11em column has to go with the column layout that made it one. */
    .valuation-field label {
        flex: 0 0 auto;
    }
    /* The head line wraps here, and a reason pushed to the right edge of its
       own line reads as belonging to nothing. */
    .valuation-head-note {
        margin-left: 0;
    }
    .valuation-input {
        width: 100%;
    }
    .valuation-movement {
        flex-wrap: wrap;
    }
    .valuation-mv-desc {
        white-space: normal;
        flex-basis: 100%;
        order: 3;
    }
}

.notes-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.note-card {
    border: 1px solid var(--border-color);
    border-left: 3px solid var(--note-border);
    border-radius: 10px;
    padding: 12px 14px;
    background: var(--item-bg);
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.note-head {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
}
.note-title {
    font-weight: 600;
    color: var(--text-color);
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
}
.note-head .te-amt {
    margin-left: auto;
}

.note-meta {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    font-size: 12.5px;
    color: var(--text-muted);
}
.note-share {
    background: var(--note-soft-bg);
    color: var(--note-soft-text);
    border-radius: 999px;
    padding: 2px 10px;
    font-weight: 600;
}
.note-account {
    background: var(--acct-balance-bg);
    color: var(--acct-balance-text);
    border-radius: 999px;
    padding: 2px 10px;
}
.note-body {
    margin: 0;
    font-size: 13px;
    color: var(--text-color);
    overflow-wrap: anywhere;
}

.note-attached,
.note-dismissals,
.note-manage {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    font-size: 12.5px;
    color: var(--text-muted);
}
.note-manage {
    justify-content: flex-end;
}
.note-attached a {
    color: var(--primary-color);
}

/* One candidate row: the offer, plus the matcher's arithmetic. Showing WHY is
   what makes a deliberately loose threshold tolerable. */
.note-candidate {
    border: 1px solid var(--border-color);
    border-radius: 8px;
    padding: 8px 10px;
    background: var(--card-bg-subtle);
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.note-candidate-row {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
}
.note-candidate-row .txn-description {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
}
.note-candidate-row .te-amt {
    margin-left: auto;
}
.note-candidate-why {
    font-size: 11.5px;
    color: var(--text-muted);
}
.note-candidate-actions {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    justify-content: flex-end;
}
.note-candidate-actions button {
    align-self: auto;
    margin-top: 0;
}

/* The attach machinery — candidates, the month finder, the dismissal undos —
   folded behind one disclosure so the tab reads as the capture list it is. */
.note-attach {
    font-size: 12.5px;
}
.note-attach summary {
    cursor: pointer;
    font-weight: 600;
    color: var(--primary-color);
}
.note-attach-body {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 8px;
}

/* ── Note chips on a staged row ── */

.badge.note {
    background: var(--note-bg);
    color: var(--note-text);
    border-color: var(--note-border);
    /* An attached note carries the user's own words — let it wrap rather than
       truncate the one thing they came back for. */
    white-space: normal;
    max-width: 30em;
}
.badge.note-suggested {
    background: var(--note-soft-bg);
    color: var(--note-soft-text);
    border-color: var(--note-border);
    border-style: dashed;
    white-space: normal;
    max-width: 30em;
}

/* ── Partner messages on a shared row (flow #12) ── */

/* Somebody's sentence, not a status — so it wraps rather than truncating the one
   thing the row is there to carry, and clamps at three lines rather than growing
   without bound. This is the first row text the OTHER book writes, and the column
   is `db.Text`: unclamped, a pasted paragraph turned one ledger row 559px tall,
   which is exactly the bound `.txn-desc`'s own clamp exists to hold. The full
   text is in `title`, which is what makes the clamp honest.
   `overflow-wrap` for the same reason `.txn-description` has it — one unbroken
   token must not push the row's track wider than its column.

   The clamp is on the INNER span, not the chip. The chip has to stay the
   inline-flex row `.badge` defines, because phase 3 hangs two markers off it —
   an unread dot and a read receipt — and `-webkit-line-clamp` requires
   `display:-webkit-box`, which would swallow both: the flex `gap` stops
   applying, and the receipt (last in flow) is exactly what a third line of
   message text clamps away. */
.badge.message,
.badge.message-mine {
    align-items: flex-start;
    max-width: 34em;
    font-weight: 500;
}
.badge.message .msg-text,
.badge.message-mine .msg-text {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    white-space: normal;
    overflow-wrap: anywhere;
    min-width: 0;   /* or a long message refuses to shrink inside the chip */
}
/* Both markers keep their size against a long message (`flex: none`) and align
   with the message's FIRST line, not the top of a three-line box — the chip is
   flex-start so a short marker beside tall text would otherwise float. */
.badge.message .dot {
    flex: none;
    margin-top: 5px;
}
.badge.message-mine .msg-receipt {
    flex: none;
    margin-top: 1px;
    font-weight: 600;
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    opacity: 0.75;
}
.badge.message {
    background: var(--message-bg);
    color: var(--message-text);
    border-color: var(--message-border);
}
/* My own slot is an echo, not news — `.badge.muted` carries the colours; only the
   border differs. Never a read receipt: nothing clears a seen flag until the
   modal lands, so neither flag is rendered at all (see partnerMessageChips). */
.badge.message-mine {
    border-color: var(--border-color);
}
/* On a posted row the chips get their OWN full-width grid row beneath the legs
   (renderTxnRow places it explicitly), because a WRAPPING chip cannot live inside
   `.txn-desc`'s 2-line `-webkit-box` clamp. Measured against this stylesheet, it
   fails both ways depending on the description: short merchant → the cell grows
   from 25px to 87px, breaking the very bound the clamp exists to hold; merchant
   long enough to fill both lines → the chip is 0px visible, not truncated but
   gone, its own `title` unreachable with it. All that survives there is the
   clamp's ellipsis on the merchant string — which reads as a trimmed merchant,
   not as a message somebody wrote you.

   `align-items: flex-start` because a flex row stretches by default: a short "You"
   chip beside a 3-line partner chip became a 61px oval with its text at the top
   edge. The staged card's `.txn-mark` never showed this: its chips are stacked,
   so no chip is ever laid out beside a taller one. */
.txn-messages {
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 8px;
    margin-top: 4px;
}

/* ── The message box in the edit modal (flow #12, phase 3) ── */

/* A box carrying this class is exactly as tall as its text. No manual resize
   handle (the height is not the user's to set) and no scrollbar (the point is
   that nothing is hidden). One line to start; JS sets the height from
   scrollHeight on every keystroke. Which boxes: see `growTextareas`. */
textarea.autogrow {
    resize: none;
    overflow-y: hidden;
    line-height: 1.45;
}

/* The half-sentence beside a field label that says who can read the field.
   Deliberately quiet — it qualifies the label, it is not a second label. */
.field-note {
    margin-left: 6px;
    font-weight: 400;
    font-style: italic;
    opacity: 0.85;
}

/* Full-width, directly under Comment, so the two labels read as a pair: mine
   above, theirs below. */
.partner-message {
    margin-bottom: 18px;
}
/* Both of these set `display`, which beats the UA stylesheet's
   `[hidden] { display: none }` — without this the box renders on every row,
   shared or not, and an empty teal strip sits above it. */
.partner-message[hidden],
.partner-said[hidden] {
    display: none;
}

/* What the partner said, read-only, above the box that answers it. Opening this
   modal is what marks their message read, so this is the one surface that has to
   show it. Teal, like the row chip it echoes — same voice, same colour. */
.partner-said {
    display: flex;
    flex-direction: column;
    gap: 2px;
    background: var(--message-bg);
    color: var(--message-text);
    border: 1px solid var(--message-border);
    border-radius: 8px;
    padding: 8px 12px;
    font-size: 13px;
    line-height: 1.5;
}
.partner-said-who {
    font-weight: 600;
    font-size: 11.5px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    opacity: 0.85;
}
.partner-said-text {
    white-space: pre-wrap;      /* a message may be several lines; keep them */
    overflow-wrap: anywhere;
}

/* What the one Save button is about to do to the cross-book agreement. Last
   element in the modal body and STICKY to its bottom edge: `.modal-body` scrolls,
   and a categorize modal is easily tall enough to push a trailing line out of
   sight — a disclosure below the fold is not a disclosure, and the whole point is
   that it is read before the button is pressed. Opaque background and a border
   because content scrolls under it. Inside the body's padding box, not bled to
   its edges: negative side margins are 24px wider than `clientWidth` once a
   vertical scrollbar appears, which is exactly when this element exists.
   Info-blue, not warning-amber: a settle or a re-review is the normal course of
   the flow, and `.shared-review-note` above it already owns amber for "somebody
   is waiting on you". */
.save-effect-note {
    position: sticky;
    bottom: 0;
    z-index: 1;
    background: var(--info-bg);
    color: var(--info-text);
    border: 1px solid var(--border-color);
    border-radius: 6px;
    padding: 10px 12px;
    font-size: 12.5px;
    line-height: 1.5;
    margin: 14px 0 0;
}

/* ── Note panel in the edit modal ── */

.modal-note-box {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-bottom: 14px;
}
.modal-note {
    border: 1px solid var(--note-border);
    background: var(--note-bg);
    color: var(--note-text);
    border-radius: 10px;
    padding: 10px 12px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.modal-note.is-offer {
    background: var(--note-soft-bg);
    color: var(--note-soft-text);
    border-style: dashed;
}
.modal-note-head {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
    font-size: 12.5px;
}
.modal-note-head .te-amt {
    margin-left: auto;
}
.modal-note-body {
    font-size: 13px;
    overflow-wrap: anywhere;
}
.modal-note-actions {
    display: flex;
    gap: 8px;
    justify-content: flex-end;
    flex-wrap: wrap;
}
.modal-note button {
    align-self: flex-end;
    margin-top: 0;
}

/* Phone-first capture: one column, full-width controls, seg groups that wrap
   instead of overflowing. */
@media (max-width: 640px) {
    .note-form-row {
        flex-direction: column;
        gap: 14px;
    }
    /* Once these rows stack, `flex-basis` stops meaning "min column width" and
       starts meaning HEIGHT — which inflated every field to 220px of mostly
       whitespace (found on a 390px viewport). Height is the content's business
       when stacked. */
    .note-form-row > .note-field,
    .note-amount-row input,
    .note-custom-share input {
        flex: 0 0 auto;
    }
    .note-capture-card .seg,
    .note-form .seg {
        display: flex;
        flex-wrap: wrap;
        width: 100%;
    }
    .note-capture-card .seg button,
    .note-form .seg button {
        flex: 1 1 auto;
        padding: 11px 10px;
    }
    .note-amount-row,
    .note-custom-share {
        flex-direction: column;
    }
    .note-form-actions .btn {
        width: 100%;
    }
    .note-head .te-amt,
    .note-candidate-row .te-amt,
    .modal-note-head .te-amt {
        margin-left: 0;
    }
    .note-candidate-actions,
    .note-manage {
        justify-content: flex-start;
    }
}

/* The manual-attach escape hatch in the edit modal: collapsed by default so it
   never competes with the scored offers above it. */
.modal-note-picker {
    border: 1px dashed var(--note-border);
    border-radius: 10px;
    padding: 8px 12px;
    background: var(--note-soft-bg);
    color: var(--note-soft-text);
    font-size: 12.5px;
}
.modal-note-picker summary {
    cursor: pointer;
    font-weight: 600;
}
.modal-note-picker-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 8px;
    max-height: 240px;
    overflow-y: auto;
}
.modal-note-pick {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
    padding: 6px 8px;
    border-radius: 8px;
    background: var(--card-bg);
    color: var(--text-color);
}
.modal-note-pick-text {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
}
.modal-note-pick .te-amt {
    margin-left: auto;
}
.modal-note-pick button {
    align-self: center;
    margin-top: 0;
}

/* The notes tab's own manual-attach door: browse a month of rows and attach any
   of them, for the note the matcher missed entirely. Collapsed by default. */
.note-row-finder {
    border: 1px dashed var(--note-border);
    border-radius: 8px;
    padding: 8px 10px;
    background: var(--note-soft-bg);
    color: var(--note-soft-text);
    font-size: 12.5px;
}
.note-row-finder summary {
    cursor: pointer;
    font-weight: 600;
}
.note-finder-nav {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 8px 0;
}
.note-finder-label {
    font-weight: 600;
}
.note-finder-nav button {
    align-self: auto;
    margin-top: 0;
    min-width: 36px;
}
.note-finder-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    max-height: 300px;
    overflow-y: auto;
}
.note-finder-row {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
    padding: 6px 8px;
    border-radius: 8px;
    background: var(--card-bg);
    color: var(--text-color);
}
.note-finder-desc {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
    white-space: pre-line;
}
.note-finder-row .te-amt {
    margin-left: auto;
}
.note-finder-row button {
    align-self: center;
    margin-top: 0;
}
.note-finder-empty {
    margin: 0;
    color: var(--text-muted);
}

/* The capture form's scope gate: two long labels, so let it fill the row rather
   than crowd them into the compact bank-card segment width. */
.seg.seg-wide {
    display: flex;
    width: 100%;
}
.seg.seg-wide button {
    flex: 1 1 0;
}
