/* TM-FIN — "Royal FinTech". Aturan lengkapnya ada di DESIGN.md. */

*,
*::before,
*::after { box-sizing: border-box; }

/*
 * Colour is two independent choices, and they are kept that way.
 *
 * The MODE owns everything a background touches: the four surfaces, the
 * text on them, the dividers, and the meanings that must stay readable
 * either way — success, error, the shadows. The ACCENT owns the brand:
 * buttons, chips, active markers, chart bars.
 *
 * They used to be one attribute, so "dark" was two particular themes and
 * nobody could have a dark green one. Splitting them turns 13 fixed
 * combinations into 11 accents that each work in both modes.
 *
 * Order matters. :root carries light mode plus the default accent, then
 * the light accents override it, then dark mode overrides those, then the
 * dark accents override that. Every accent has a dark twin, so a chosen
 * accent always wins on its three-attribute selector.
 */

:root,
:root[data-accent="lavender"] {
    --surface: #f2f3f5;
    --surface-card: #ffffff;
    --surface-sunken: #eef0f3;
    --surface-container: #e7e9ee;
    --outline-variant: #dcdfe4;

    --on-surface: #16181d;
    --on-surface-variant: #454952;
    --on-surface-muted: #6b707a;

    --success: #0f7a4a;
    --error: #ba1a1a;
    --error-container: #ffdad6;
    --shadow-1: 0 4px 20px rgba(15, 23, 42, .05);
    --shadow-2: 0 12px 32px rgba(15, 23, 42, .12);

    --color-scheme: light;

    --primary: #5b53c9;
    --primary-strong: #3d35b4;
    --primary-container: #dfdeed;
    --on-primary: #ffffff;
    --secondary: #7670cd;
    --shadow-brand: 0 8px 24px rgba(91, 83, 201, .26);
}

/* ---------- Light accents ---------- */

:root[data-accent="royal"] {
    --primary: #253bb1;
    --primary-strong: #1a2b89;
    --primary-container: #dcdfef;
    --on-primary: #ffffff;
    --secondary: #4b60d2;
    --shadow-brand: 0 8px 24px rgba(37, 59, 177, .26);
}

:root[data-accent="teal"] {
    --primary: #0f766e;
    --primary-strong: #084a45;
    --primary-container: #dbf0ef;
    --on-primary: #ffffff;
    --secondary: #1dafa4;
    --shadow-brand: 0 8px 24px rgba(15, 118, 110, .26);
}

:root[data-accent="rose"] {
    --primary: #b83b6b;
    --primary-strong: #942c54;
    --primary-container: #eddee4;
    --on-primary: #ffffff;
    --secondary: #cb6f92;
    --shadow-brand: 0 8px 24px rgba(184, 59, 107, .26);
}

:root[data-accent="amber"] {
    --primary: #a15c07;
    --primary-strong: #734002;
    --primary-container: #f2e7d9;
    --on-primary: #ffffff;
    --secondary: #dd8213;
    --shadow-brand: 0 8px 24px rgba(161, 92, 7, .26);
}

:root[data-accent="slate"] {
    --primary: #3f4756;
    --primary-strong: #292f39;
    --primary-container: #e3e5e8;
    --on-primary: #ffffff;
    --secondary: #5f697e;
    --shadow-brand: 0 8px 24px rgba(63, 71, 86, .26);
}

:root[data-accent="hutan"] {
    --primary: #1f6b3a;
    --primary-strong: #124524;
    --primary-container: #deede3;
    --on-primary: #ffffff;
    --secondary: #349e59;
    --shadow-brand: 0 8px 24px rgba(31, 107, 58, .26);
}

:root[data-accent="samudra"] {
    --primary: #0b5f80;
    --primary-strong: #053d53;
    --primary-container: #daebf1;
    --on-primary: #ffffff;
    --secondary: #188dbb;
    --shadow-brand: 0 8px 24px rgba(11, 95, 128, .26);
}

:root[data-accent="anggur"] {
    --primary: #7b2d6b;
    --primary-strong: #571e4b;
    --primary-container: #ecdfe9;
    --on-primary: #ffffff;
    --secondary: #ab4596;
    --shadow-brand: 0 8px 24px rgba(123, 45, 107, .26);
}

:root[data-accent="terakota"] {
    --primary: #9c4221;
    --primary-strong: #742f16;
    --primary-container: #efe1dc;
    --on-primary: #ffffff;
    --secondary: #cd6037;
    --shadow-brand: 0 8px 24px rgba(156, 66, 33, .26);
}

:root[data-accent="kopi"] {
    --primary: #6b4423;
    --primary-strong: #462b15;
    --primary-container: #ede5de;
    --on-primary: #ffffff;
    --secondary: #9c6739;
    --shadow-brand: 0 8px 24px rgba(107, 68, 35, .26);
}

/* ---------- Dark modes ---------- */

/*
 * One selector, not two. The second used to name lavender explicitly, and
 * being one attribute more specific it beat the black-background block below —
 * so "hitam pekat" quietly stayed grey on the default accent, and only the
 * default. Lavender still lands here as the accent nobody chose, because this
 * block matches whether data-accent is absent or set to lavender.
 */
:root:is([data-mode="dark"], [data-mode="oled"]) {
    --surface: #0f1115;
    --surface-card: #1b1e26;
    --surface-sunken: #14171d;
    --surface-container: #242832;
    --outline-variant: #333844;

    --on-surface: #eef1f7;
    --on-surface-variant: #c3c9d4;
    --on-surface-muted: #949aa6;

    --success: #4ade80;
    --error: #ff8a80;
    --error-container: #4a1d1d;
    --shadow-1: 0 4px 20px rgba(0, 0, 0, .35);
    --shadow-2: 0 12px 32px rgba(0, 0, 0, .5);

    --color-scheme: dark;

    --primary: #9792d3;
    --primary-strong: #b9b7e1;
    --primary-container: #2e2c4f;
    --on-primary: #110f2f;
    --secondary: #7b76bc;
    --shadow-brand: 0 8px 24px rgba(151, 146, 211, .26);
}

/*
 * Hitam pekat. Di layar OLED piksel hitam benar-benar padam, jadi ini lebih
 * hemat daya dan kontrasnya lebih tajam — bukan sekadar gelap yang digelapkan.
 * Kartunya tetap sedikit di atas hitam supaya tepiannya masih terbaca; kartu
 * hitam di atas halaman hitam menghilangkan seluruh hierarki halamannya.
 */
:root[data-mode="oled"] {
    --surface: #000000;
    --surface-card: #0b0d11;
    --surface-sunken: #05070a;
    --surface-container: #14171d;
    --outline-variant: #262b34;
    --shadow-1: 0 4px 20px rgba(0, 0, 0, .6);
    --shadow-2: 0 12px 32px rgba(0, 0, 0, .75);
}

:root:is([data-mode="dark"], [data-mode="oled"])[data-accent="royal"] {
    --primary: #8b98da;
    --primary-strong: #b5bce3;
    --primary-container: #272e53;
    --on-primary: #0c1231;
    --secondary: #6e7bc4;
    --shadow-brand: 0 8px 24px rgba(139, 152, 218, .26);
}

:root:is([data-mode="dark"], [data-mode="oled"])[data-accent="teal"] {
    --primary: #84e1da;
    --primary-strong: #b0e8e3;
    --primary-container: #235753;
    --on-primary: #0c312e;
    --secondary: #66ccc4;
    --shadow-brand: 0 8px 24px rgba(132, 225, 218, .26);
}

:root:is([data-mode="dark"], [data-mode="oled"])[data-accent="rose"] {
    --primary: #d392ab;
    --primary-strong: #e1b7c7;
    --primary-container: #4f2c39;
    --on-primary: #2e0f1b;
    --secondary: #bb7791;
    --shadow-brand: 0 8px 24px rgba(211, 146, 171, .26);
}

:root:is([data-mode="dark"], [data-mode="oled"])[data-accent="amber"] {
    --primary: #e9b87c;
    --primary-strong: #edcfab;
    --primary-container: #5c401e;
    --on-primary: #31210c;
    --secondary: #d69f5c;
    --shadow-brand: 0 8px 24px rgba(233, 184, 124, .26);
}

:root:is([data-mode="dark"], [data-mode="oled"])[data-accent="slate"] {
    --primary: #92a9d3;
    --primary-strong: #b7c5e1;
    --primary-container: #383c42;
    --on-primary: #1a1d23;
    --secondary: #8f96a3;
    --shadow-brand: 0 8px 24px rgba(146, 169, 211, .26);
}

:root:is([data-mode="dark"], [data-mode="oled"])[data-accent="hutan"] {
    --primary: #92d3a9;
    --primary-strong: #b7e1c6;
    --primary-container: #2b5038;
    --on-primary: #0e2f1a;
    --secondary: #74be8e;
    --shadow-brand: 0 8px 24px rgba(146, 211, 169, .26);
}

:root:is([data-mode="dark"], [data-mode="oled"])[data-accent="samudra"] {
    --primary: #80c8e5;
    --primary-strong: #aed9ea;
    --primary-container: #214a5a;
    --on-primary: #0c2731;
    --secondary: #61b1d1;
    --shadow-brand: 0 8px 24px rgba(128, 200, 229, .26);
}

:root:is([data-mode="dark"], [data-mode="oled"])[data-accent="anggur"] {
    --primary: #d392c5;
    --primary-strong: #e1b7d9;
    --primary-container: #4d2e46;
    --on-primary: #2d1027;
    --secondary: #b87aab;
    --shadow-brand: 0 8px 24px rgba(211, 146, 197, .26);
}

:root:is([data-mode="dark"], [data-mode="oled"])[data-accent="terakota"] {
    --primary: #d9a18c;
    --primary-strong: #e3c1b5;
    --primary-container: #533327;
    --on-primary: #31160c;
    --secondary: #c4856e;
    --shadow-brand: 0 8px 24px rgba(217, 161, 140, .26);
}

:root:is([data-mode="dark"], [data-mode="oled"])[data-accent="kopi"] {
    --primary: #d3b092;
    --primary-strong: #e1cab7;
    --primary-container: #4e3c2c;
    --on-primary: #2e1d0f;
    --secondary: #bb9677;
    --shadow-brand: 0 8px 24px rgba(211, 176, 146, .26);
}

/*
 * Everything below is the same in both modes and under every accent.
 *
 * --success, --error and the two elevation shadows used to live here too. They
 * belong to the mode now — this block sits after the mode blocks, so leaving
 * them here would have quietly overridden dark mode's versions of them.
 */
:root {
    /*
     * Categorical chart colours. Fixed across every theme, for the same reason
     * --success and --error are: they carry meaning by being told apart from
     * each other, not by matching the brand. Ordered so neighbouring slices
     * never sit close on the colour wheel.
     */
    --chart-1: #4f6df5;
    --chart-2: #00a37a;
    --chart-3: #f0a202;
    --chart-4: #e0538a;
    --chart-5: #8b5cf6;
    --chart-rest: #94a3b8;

    /* Spacing — 8px rhythm */
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 20px;
    --space-6: 24px;
    --space-7: 32px;
    --space-8: 40px;

    /* Shape */
    --radius-card: 24px;
    --radius-control: 12px;
    --radius-pill: 9999px;
    --radius-tiny: 4px;

    /* Elevation lives with the mode; --shadow-brand with the accent. */

    --content-width: 560px;
    --admin-width: 1120px;
    --pad-edge: var(--space-5);
}

@media (min-width: 768px) {
    :root { --pad-edge: var(--space-8); }
}

html { height: 100%; }

/*
 * The whole interface renders at 80%. Done with `zoom` on one element rather
 * than by shrinking every token, so the 8px rhythm and the type scale stay
 * exactly as documented — only their rendered size changes. Adjust --ui-scale
 * here and the entire app follows.
 *
 * Lengths inside a zoomed subtree still resolve against the real viewport, so
 * anything sized to fill the screen has to be divided by the scale first.
 */
:root {
    --ui-scale: .8;

    /* 44px as it actually lands on screen. Touch targets are an accessibility
       floor, so they are held there rather than allowed to shrink with the
       scale — see DESIGN.md §8. */
    --touch: calc(44px / var(--ui-scale));
}

body {
    zoom: var(--ui-scale);
    min-height: calc(100% / var(--ui-scale));
    margin: 0;
    font-family: var(--font-family);
    font-size: 16px;
    line-height: 24px;
    color: var(--on-surface-variant);
    background: var(--surface);
    -webkit-font-smoothing: antialiased;
}

/*
 * svh, not vh. On a phone, 100vh is the viewport with the address bar retracted
 * — the tallest it ever gets — so a page that fits the screen was still made
 * taller than what you can see, and scrolled by exactly the height of that bar.
 * svh is the viewport with the bar showing, so the floor never exceeds what is
 * actually visible. vh stays first as the fallback for browsers without svh.
 */
.page {
    display: flex;
    flex-direction: column;
    min-height: calc(100vh / var(--ui-scale));
    min-height: calc(100svh / var(--ui-scale));
}
.page--centered { align-items: center; justify-content: center; padding: var(--space-7) var(--pad-edge); }

/*
 * On a short screen the 32px above and below the card is the first thing worth
 * giving up: it buys back 32px of the budget, which is often the difference
 * between the sign-in card fitting and the page scrolling for two lines.
 */
@media (max-height: 700px) {
    .page--centered { padding-top: var(--space-4); padding-bottom: var(--space-4); }
}

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

/* ---------- Brand lockup ---------- */

/*
 * Sits above the card on every auth page, so signing in, registering, and
 * resetting a password all start from the same place. Its colour comes from
 * the accent tokens — the mark is inline SVG for exactly that reason.
 */
.brand {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-bottom: var(--space-6);
    text-align: center;
}

/*
 * No tinted plate behind it. The logo carries its own colours, and a plate in
 * the accent colour would put a second brand hue right beside the real one.
 */
.brand__mark {
    display: block;
    width: auto;
    height: 80px;
}

.brand__name {
    margin-top: var(--space-3);
    font-size: 20px;
    line-height: 28px;
    font-weight: 700;
    letter-spacing: .06em;
    color: var(--on-surface);
}

/*
 * --on-surface-variant, not --on-surface-muted: this line sits on the page
 * itself rather than inside a card, and muted on --surface measures 4.48:1 —
 * just under the bar. Everywhere else muted text has a white card behind it.
 */
.brand__tagline {
    margin-top: 2px;
    font-size: 13px;
    line-height: 18px;
    color: var(--on-surface-variant);
}

/* ---------- Typography ---------- */

h1 {
    margin: 0;
    font-size: 28px;
    line-height: 36px;
    font-weight: 700;
    letter-spacing: -.01em;
    color: var(--on-surface);
}

@media (min-width: 768px) {
    h1 { font-size: 32px; line-height: 40px; letter-spacing: -.02em; }
}

h2 {
    margin: 0;
    font-size: 14px;
    line-height: 20px;
    font-weight: 600;
    letter-spacing: .01em;
    color: var(--on-surface);
}

.muted { color: var(--on-surface-variant); font-size: 16px; line-height: 24px; margin: var(--space-2) 0 0; }
.tiny { color: var(--on-surface-muted); font-size: 12px; line-height: 16px; font-weight: 500; margin: 0; }

.error {
    margin: 0;
    padding: var(--space-3) var(--space-4);
    font-size: 14px;
    font-weight: 600;
    color: var(--error);
    background: var(--error-container);
    border-radius: var(--radius-control);
}

.notice {
    margin: 0;
    padding: var(--space-3) var(--space-4);
    font-size: 14px;
    font-weight: 600;
    color: var(--success);
    background: rgba(15, 122, 74, .12);
    border-radius: var(--radius-control);
}

/* ---------- Cards ---------- */

.card {
    background: var(--surface-card);
    border-radius: var(--radius-card);
    padding: var(--space-6);
    box-shadow: var(--shadow-1);
}

.card--tight { padding: var(--space-5); }

/* ---------- Forms ---------- */

/* Our own display rules outrank the browser's default for [hidden], so a field
   told to hide would otherwise stay on screen. Stated once, here, rather than
   remembered at every component that ever gets toggled. */
[hidden] { display: none !important; }

.field { display: block; margin-bottom: var(--space-4); }

/*
 * File picker. The browser draws its own control here and reads none of the
 * tokens in this file, so the button half is restyled to match the outline
 * button and the rest is made to sit in a normal sunken field.
 */
input[type="file"] {
    display: block;
    width: 100%;
    padding: var(--space-2);
    font: inherit;
    font-size: calc(16px / var(--ui-scale));
    color: var(--on-surface-muted);
    background: var(--surface-sunken);
    border: 1px solid var(--outline-variant);
    border-radius: var(--radius-control);
    cursor: pointer;
}

input[type="file"]::file-selector-button {
    min-height: var(--touch);
    margin-right: var(--space-3);
    padding: 0 var(--space-5);
    font: inherit;
    font-weight: 600;
    color: var(--primary);
    background: var(--surface-card);
    border: none;
    border-radius: var(--radius-control);
    cursor: pointer;
    transition: background .15s, color .15s;
}

input[type="file"]::file-selector-button:hover {
    background: var(--primary-container);
    color: var(--primary-strong);
}

input[type="file"]:focus-visible {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgba(37, 59, 177, .16);
}

.field__label {
    display: block;
    font-size: 14px;
    line-height: 20px;
    font-weight: 600;
    letter-spacing: .01em;
    color: var(--on-surface);
    margin-bottom: var(--space-2);
}

/*
 * Form controls are the one exception to --ui-scale. iOS zooms the page in on
 * its own whenever a focused field computes below 16px, so these are sized so
 * that 16px is what actually lands on screen after the scale is applied.
 */
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="date"],
input[type="month"],
input[type="datetime-local"],
input[type="time"],
select,
textarea {
    width: 100%;
    padding: 13px var(--space-4);
    font-size: calc(16px / var(--ui-scale));
    line-height: 24px;
    font-family: inherit;
    color: var(--on-surface);
    background: var(--surface-sunken);
    border: 1px solid var(--outline-variant);
    border-radius: var(--radius-control);
    outline: none;
    transition: border-color .15s, box-shadow .15s, background .15s;
}

input::placeholder { color: var(--on-surface-muted); }

/*
 * Date and time fields carry a piece of browser chrome inside them — the picker
 * icon, and the popup it opens. Neither reads our tokens, so they are told
 * which side of the theme they are on, and the icon is toned down to sit at the
 * same weight as the text next to it instead of shouting in pure black.
 */
input[type="date"],
input[type="month"],
input[type="datetime-local"],
input[type="time"] {
    color-scheme: var(--color-scheme);
    accent-color: var(--primary);
}

input::-webkit-calendar-picker-indicator {
    opacity: .55;
    cursor: pointer;
}
input::-webkit-calendar-picker-indicator:hover,
input:focus::-webkit-calendar-picker-indicator { opacity: 1; }

input:focus, select:focus {
    border-color: var(--primary);
    background: var(--surface-card);
    box-shadow: 0 0 0 3px rgba(37, 59, 177, .16);
}

.btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    padding: var(--space-4) var(--space-6);
    font-size: 16px;
    line-height: 24px;
    font-weight: 600;
    font-family: inherit;
    color: var(--on-primary);
    background: var(--primary);
    border: none;
    border-radius: var(--radius-control);
    cursor: pointer;
    box-shadow: var(--shadow-brand);
    /* Links wear this too, and an underline under a solid button reads as a
       mistake. Stated here so no variant has to remember it. */
    text-decoration: none;
    transition: transform .15s, box-shadow .15s, background .15s;
}
.btn:hover { background: var(--primary-strong); transform: translateY(-2px); box-shadow: 0 10px 28px rgba(37, 59, 177, .34); }
.btn:active { transform: translateY(0); }
.btn:disabled { opacity: .5; cursor: default; box-shadow: none; transform: none; }

.btn--outline {
    background: var(--surface-sunken);
    color: var(--primary);
    box-shadow: none;
}
.btn--outline:hover { background: var(--surface-container); color: var(--primary-strong); box-shadow: none; }

.btn--google {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    background: var(--surface-card);
    color: var(--on-surface);
    border: 1px solid var(--outline-variant);
    box-shadow: none;
}
.btn--google:hover { background: var(--surface-sunken); color: var(--on-surface); box-shadow: none; }

.btn--compact { width: auto; padding: var(--space-3) var(--space-5); }

.btn-ghost {
    /* Centred rather than block: the touch-target floor makes these boxes
       taller than their text, and a block would leave the label pinned to the
       top — which is what made the Admin link sit above Keluar. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 10px var(--space-4);
    font-size: 14px;
    line-height: 20px;
    font-family: inherit;
    font-weight: 600;
    color: var(--on-surface-variant);
    background: transparent;
    border: none;
    border-radius: var(--radius-pill);
    cursor: pointer;
    transition: background .15s, color .15s;
}
.btn-ghost:hover { background: var(--surface-container); color: var(--on-surface); }

.btn-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    line-height: 16px;
    font-weight: 600;
    color: var(--error);
    background: none;
    border: none;
    padding: var(--space-2) var(--space-2);
    cursor: pointer;
    font-family: inherit;
    border-radius: var(--space-2);
}
.btn-link:hover { background: var(--error-container); }

.btn-link--neutral { color: var(--primary); }
.btn-link--neutral:hover { background: var(--surface-container); }

.segmented {
    display: flex;
    gap: var(--space-1);
    padding: var(--space-1);
    background: var(--surface-sunken);
    border: 1px solid var(--outline-variant);
    border-radius: var(--radius-control);
    margin-bottom: var(--space-4);
}
.segmented label {
    flex: 1;
    text-align: center;
    padding: 10px;
    font-size: 14px;
    line-height: 20px;
    font-weight: 600;
    color: var(--on-surface-variant);
    border-radius: var(--space-2);
    cursor: pointer;
    transition: background .15s, color .15s;
}
/* Link variant, for filters that reload the page instead of posting a form. */
.segmented__link {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: var(--touch);
    padding: 10px;
    text-align: center;
    text-decoration: none;
    font-size: 14px;
    line-height: 20px;
    font-weight: 600;
    color: var(--on-surface-variant);
    border-radius: var(--space-2);
    transition: background .15s, color .15s;
}
.segmented__link.is-active { background: var(--primary); color: var(--on-primary); }

.segmented input { position: absolute; opacity: 0; pointer-events: none; }
.segmented label:has(input:checked) { background: var(--primary); color: var(--on-primary); }

/* Locked control — the choice is shown but cannot be changed. */
.segmented label:has(input:disabled) { cursor: not-allowed; opacity: .45; }
.segmented label:has(input:disabled:checked) { opacity: 1; }

.divider {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    color: var(--on-surface-muted);
    font-size: 12px;
    font-weight: 500;
    margin: var(--space-6) 0 var(--space-4);
}
.divider::before, .divider::after { content: ""; flex: 1; height: 1px; background: var(--outline-variant); }

.form-footer { margin-top: var(--space-5); text-align: center; font-size: 14px; color: var(--on-surface-variant); }
.form-footer a { color: var(--primary); font-weight: 600; text-decoration: none; }
.form-footer a:hover { text-decoration: underline; }

.inline-fields { display: flex; gap: var(--space-2); }
.inline-fields > *:first-child { flex: 1; }
.inline-fields > *:last-child { width: 136px; }
.inline-fields--auto > *:last-child { width: auto; }

/* Two controls of equal standing — neither one is the main field. */
.inline-fields--even > * { flex: 1 1 0; min-width: 0; }
.inline-fields--even > *:last-child { width: auto; }

/*
 * Kolom tanggal dan jam punya lebar minimum yang digambar browser, bukan kita.
 * Di layar sempit, tiga kolom sejajar berarti isinya terpotong di tengah —
 * "08 / 23 / 2…" bukan tanggal yang bisa dibaca siapa pun. Barisnya dibiarkan
 * membungkus; dua kolom masih muat berdampingan, yang ketiga turun.
 */
@media (max-width: 560px) {
    .inline-fields--even { flex-wrap: wrap; }

    /* `:first-child` ikut disebut karena aturan di atas menyebutnya juga, dan
       satu pseudo-class lebih spesifik daripada satu kelas. Tanpa baris kedua
       ini, kolom pertama tetap berlantai nol sementara sisanya 170px — dan
       dua kolom yang seharusnya kembar jadi 105px berbanding 241px. */
    .inline-fields--even > *,
    .inline-fields--even > *:first-child { flex: 1 1 170px; }
}

/* One quiet line under the topbar for members who can only look. The message
   repeats what every hidden button already implies, so it stays small. */
.viewer-bar {
    max-width: var(--content-width);
    margin: var(--space-4) auto 0;
    padding: 0 var(--pad-edge);
    font-size: 12px;
    line-height: 16px;
    text-align: center;
    color: var(--on-surface-muted);
}

/* ---------- App shell ---------- */

.topbar {
    position: sticky;
    top: 0;
    z-index: 20;
    background: var(--surface-card);
    border-bottom: 1px solid var(--outline-variant);
}
.topbar__inner {
    width: 100%;
    max-width: var(--content-width);
    margin: 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-3) var(--pad-edge);
}
/* The book name doubles as the way into the book list. */
.topbar__book {
    display: block;
    min-width: 0;
    min-height: var(--touch);
    padding: var(--space-1) 0;
    text-decoration: none;
    border-radius: var(--radius-control);
}
.topbar__book:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

.topbar__label {
    display: block;
    margin: 0;
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    color: var(--on-surface-muted);
}
.topbar__name {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    margin: 2px 0 0;
    font-size: 20px;
    line-height: 28px;
    font-weight: 700;
    letter-spacing: -.01em;
    color: var(--on-surface);
}
.topbar__name svg { width: 18px; height: 18px; flex-shrink: 0; color: var(--on-surface-muted); }
.topbar__right { display: flex; align-items: center; gap: var(--space-2); }

.live-dot {
    width: 8px;
    height: 8px;
    border-radius: var(--radius-pill);
    background: var(--success);
    opacity: .5;
    transition: opacity .3s;
}
.live-dot.is-pulsing { opacity: 1; }

main {
    flex: 1;
    width: 100%;
    max-width: var(--content-width);
    margin: 0 auto;
    padding: var(--space-6) var(--pad-edge) var(--space-8);
}

.stack { display: flex; flex-direction: column; gap: var(--space-7); }
.stack--sm { gap: var(--space-4); }

/* ---------- Bottom navigation ---------- */

.bottomnav {
    position: sticky;
    bottom: 0;
    z-index: 20;
    display: flex;
    justify-content: center;
    background: var(--surface-card);
    border-top: 1px solid var(--outline-variant);
    padding-bottom: env(safe-area-inset-bottom);
}
.bottomnav__inner { display: flex; width: 100%; max-width: var(--content-width); }
.bottomnav__item {
    position: relative;
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
    min-height: 56px;
    padding: var(--space-2) 0;
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    color: var(--on-surface-muted);
    text-decoration: none;
}
.bottomnav__item svg { width: 24px; height: 24px; stroke-width: 1.75; }
.bottomnav__item.is-active { color: var(--primary); font-weight: 600; }
.bottomnav__item.is-active::before {
    content: "";
    position: absolute;
    top: 0;
    width: 24px;
    height: 3px;
    border-radius: 0 0 var(--space-1) var(--space-1);
    background: var(--primary);
}

/* ---------- Dashboard ---------- */

.hero {
    position: relative;
    overflow: hidden;
    background: linear-gradient(135deg, var(--primary-strong), var(--secondary));
    color: var(--on-primary);
    border-radius: var(--radius-card);
    padding: var(--space-6);
    box-shadow: var(--shadow-brand);
}
.hero::after {
    content: "";
    position: absolute;
    top: -70px;
    right: -50px;
    width: 190px;
    height: 190px;
    border-radius: var(--radius-pill);
    background: rgba(255, 255, 255, .1);
}
.hero__label {
    margin: 0;
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    color: rgba(255, 255, 255, .82);
}
.hero__amount {
    margin: var(--space-2) 0 0;
    font-size: 32px;
    line-height: 40px;
    font-weight: 700;
    letter-spacing: -.02em;
    font-variant-numeric: tabular-nums;
}
.hero__split { position: relative; display: flex; gap: var(--space-6); margin-top: var(--space-6); font-size: 14px; }
.hero__split strong {
    display: block;
    margin-top: var(--space-1);
    font-size: 16px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); }

.list { list-style: none; margin: 0; padding: 0; }
.list--divided > li { padding: var(--space-4) 0; border-top: 1px solid var(--outline-variant); }
.list--divided > li:first-child { border-top: none; padding-top: var(--space-2); }

.list-card {
    background: var(--surface-card);
    border-radius: var(--radius-card);
    padding: var(--space-4) var(--space-5);
    box-shadow: var(--shadow-1);
    margin-bottom: var(--space-4);
}
.list-card:last-child { margin-bottom: 0; }

.item__title { margin: 0; font-size: 16px; line-height: 24px; font-weight: 600; color: var(--on-surface); }
.item__meta { margin: var(--space-1) 0 0; font-size: 12px; line-height: 16px; font-weight: 500; color: var(--on-surface-muted); }

.amount {
    font-size: 16px;
    font-weight: 700;
    white-space: nowrap;
    color: var(--on-surface);
    font-variant-numeric: tabular-nums;
}
.amount--income { color: var(--success); }

.badge {
    display: inline-block;
    padding: var(--space-1) var(--space-3);
    font-size: 12px;
    line-height: 16px;
    font-weight: 600;
    color: var(--primary-strong);
    background: var(--primary-container);
    border-radius: var(--radius-pill);
}
.badge--muted { color: var(--on-surface-muted); background: var(--surface-container); }
.badge--danger { color: var(--error); background: var(--error-container); }

/* Lunas memakai warna pemasukan: di aplikasi ini hijau selalu berarti uang
   masuk, dan invoice yang dibayar adalah uang yang masuk. */
.badge--paid { color: var(--success); background: var(--surface-container); }

/* ---------- Dialog ---------- */

/*
 * Native <dialog>, so focus trapping, Esc to close and the backdrop all come
 * from the browser rather than from script we would have to maintain.
 */
.modal {
    width: min(420px, calc(100vw / var(--ui-scale) - var(--space-6) * 2));
    padding: 0;
    color: var(--on-surface-variant);
    background: var(--surface-card);
    border: none;
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-2);
}

.modal::backdrop {
    background: rgba(15, 23, 42, .45);
}

.modal__card { padding: var(--space-6); }

.modal__title {
    margin: 0;
    font-size: 18px;
    line-height: 26px;
    font-weight: 700;
    color: var(--on-surface);
}

.modal__text {
    margin: var(--space-3) 0 0;
    font-size: 15px;
    line-height: 22px;
    color: var(--on-surface-variant);
}

.modal__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-2);
    margin-top: var(--space-6);
}

.modal__btn {
    min-height: var(--touch);
    padding: var(--space-3) var(--space-5);
    font-family: inherit;
    font-size: 15px;
    font-weight: 600;
    color: var(--on-surface-variant);
    background: var(--surface-sunken);
    border: 1px solid var(--outline-variant);
    border-radius: var(--radius-control);
    cursor: pointer;
    transition: background .15s, color .15s;
}
.modal__btn:hover { background: var(--surface-container); color: var(--on-surface); }

.modal__btn--save {
    color: var(--on-primary);
    background: var(--primary);
    border-color: var(--primary);
}
.modal__btn--save:hover { color: var(--on-primary); background: var(--primary-strong); }

.modal__btn--danger {
    color: var(--on-primary);
    background: var(--error);
    border-color: var(--error);
}
.modal__btn--danger:hover { color: var(--on-primary); background: var(--error); filter: brightness(.92); }

/* ---------- Bulk tick list ---------- */

.check-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--outline-variant);
}
.check-row:last-child { border-bottom: none; }

.check-row--head { font-weight: 600; color: var(--on-surface); }

.check-row__pick {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
    min-height: var(--touch);
    cursor: pointer;
}

.check-row input[type="checkbox"] {
    flex-shrink: 0;
    width: calc(22px / var(--ui-scale));
    height: calc(22px / var(--ui-scale));
    accent-color: var(--primary);
}

.check-row__who { min-width: 0; }

.check-row__name {
    display: block;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: 15px;
    color: var(--on-surface);
}

.check-row__meta {
    display: block;
    margin-top: 2px;
    font-size: 12px;
    line-height: 16px;
    color: var(--on-surface-muted);
}
.check-row__meta.is-overdue { color: var(--error); font-weight: 600; }

/*
 * Written as input.check-row__amount on purpose: the shared field rule is
 * input[type="text"], which outranks a bare class and would otherwise stretch
 * this to 100% and squeeze the name out of the row entirely.
 */
input.check-row__amount {
    flex: 0 0 104px;
    width: 104px;
    padding: var(--space-2) var(--space-3);
    font-size: calc(15px / var(--ui-scale));
    text-align: right;
}

.bulk-total {
    margin: var(--space-4) 0 0;
    padding: var(--space-3) var(--space-4);
    font-size: 15px;
    font-weight: 600;
    color: var(--on-surface);
    background: var(--surface-container);
    border-radius: var(--radius-control);
    font-variant-numeric: tabular-nums;
}

/* ---------- Inline edit row ---------- */

/*
 * Tucks a rarely-used edit form inside a data card without a second page.
 * Built on <details> so it survives a live-update swap untouched.
 */
.edit-row { margin-top: var(--space-3); }

.edit-row__summary {
    display: flex;
    align-items: center;
    min-height: var(--touch);
    font-size: 13px;
    font-weight: 600;
    color: var(--primary);
    cursor: pointer;
    list-style: none;
}
.edit-row__summary::-webkit-details-marker { display: none; }
.edit-row__summary:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

.edit-row__form {
    padding: var(--space-3) 0 var(--space-2);
    border-top: 1px solid var(--outline-variant);
}

/* ---------- Person row ---------- */

.person-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-1) 0;
    border-bottom: 1px solid var(--outline-variant);
}
.person-row:last-child { border-bottom: none; }

.person-row__actions {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

/* A form is a block by default and would drop the second action onto its own
   line. Stated on the form itself so the pair survives even if the flex rule
   above is ever missed. */
.person-row__actions form { display: inline-flex; }

.person-row__who { min-width: 0; }

.person-row__name,
.person-row__email {
    display: block;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.person-row__name {
    font-size: 15px;
    line-height: 22px;
    font-weight: 600;
    color: var(--on-surface);
}

.person-row__email {
    margin-top: 2px;
    font-size: 12px;
    line-height: 16px;
    color: var(--on-surface-muted);
}

/* ---------- Scope switch ---------- */

.scope {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-4) var(--space-5);
    background: var(--surface-card);
    border-radius: var(--radius-control);
    box-shadow: var(--shadow-1);
}

.scope__text { min-width: 0; }
.scope__title {
    display: block;
    font-size: 15px;
    line-height: 22px;
    font-weight: 600;
    color: var(--on-surface);
}
.scope__desc {
    display: block;
    margin-top: 2px;
    font-size: 12px;
    line-height: 16px;
    color: var(--on-surface-muted);
}

.scope__toggle {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    min-width: var(--touch);
    min-height: var(--touch);
    padding: 0;
    background: none;
    border: none;
    cursor: pointer;
}

.scope__track {
    position: relative;
    width: 52px;
    height: 32px;
    background: var(--outline-variant);
    border-radius: var(--radius-pill);
    transition: background .15s;
}
.scope__toggle.is-on .scope__track { background: var(--primary); }

.scope__knob {
    position: absolute;
    top: 4px;
    left: 4px;
    width: 24px;
    height: 24px;
    background: var(--on-primary);
    border-radius: var(--radius-pill);
    box-shadow: var(--shadow-1);
    transition: transform .15s;
}
.scope__toggle.is-on .scope__knob { transform: translateX(20px); }

.scope__toggle:focus-visible .scope__track {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

/*
 * The wallet owner sits in a data-card row, so it has to read like the plain
 * values above it rather than like a form control dropped into the middle of
 * the card. Hence no visible box until it is focused.
 */
.owner-form {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-2);
}

.owner-form select {
    width: auto;
    max-width: 100%;
    padding: var(--space-1) var(--space-2);
    /* Renders at 15px. Kept above the 16px computed threshold so iOS still has
       no reason to zoom the page in when the picker opens. */
    font-size: calc(15px / var(--ui-scale));
    font-weight: 400;
    color: var(--on-surface-variant);
    text-align: right;
    background: transparent;
    border-color: transparent;
}

.owner-form select:focus {
    color: var(--on-surface);
    background: var(--surface-sunken);
    border-color: var(--primary);
}

.code-box {
    margin: var(--space-4) 0 0;
    padding: var(--space-4);
    text-align: center;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 20px;
    font-weight: 700;
    letter-spacing: .2em;
    color: var(--primary-strong);
    background: var(--primary-container);
    border-radius: var(--radius-control);
}

.hint-box {
    margin-top: var(--space-4);
    padding: var(--space-4);
    font-size: 14px;
    line-height: 22px;
    color: var(--on-surface-variant);
    background: var(--surface-container);
    border-radius: var(--radius-control);
}
.hint-box p { margin: 0 0 var(--space-2); }
.hint-box p:last-child { margin-bottom: 0; }
.hint-box code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-weight: 700;
    color: var(--primary-strong);
}

.cta {
    display: block;
    padding: var(--space-4);
    text-align: center;
    font-size: 16px;
    font-weight: 600;
    color: var(--primary);
    background: var(--surface-card);
    border-radius: var(--radius-control);
    box-shadow: var(--shadow-1);
    text-decoration: none;
    transition: transform .15s, box-shadow .15s;
}
.cta:hover { transform: translateY(-2px); box-shadow: var(--shadow-2); }

/* ---------- Typeface ---------- */

/*
 * One variable, set on the root, so every rule that says font-family:
 * inherit follows along. Only the chosen family is ever downloaded — the
 * boot script adds its stylesheet link and nothing else.
 */

:root,
:root[data-font="manrope"] {
    --font-family: "Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

:root[data-font="inter"] {
    --font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

:root[data-font="jakarta"] {
    --font-family: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

:root[data-font="plex"] {
    --font-family: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

:root[data-font="nunito"] {
    --font-family: "Nunito Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

:root[data-font="source"] {
    --font-family: "Source Sans 3", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

:root[data-font="sistem"] {
    --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

/* ---------- Legal pages ---------- */

/*
 * The privacy policy and terms. Wider than the app's 560px and rendered on the
 * blank layout, because they are read straight through rather than tapped at —
 * and a reviewer opens them signed out, with no topbar or navigation to sit in.
 */
.legal {
    max-width: 720px;
    margin: 0 auto;
    padding: var(--space-7) var(--pad-edge) var(--space-8);
}

.legal__back,
.legal__nav a {
    font-size: 14px;
    font-weight: 600;
    color: var(--primary);
    text-decoration: none;
}

.legal__back { display: inline-block; margin-bottom: var(--space-5); }
.legal__nav { margin-top: var(--space-6); text-align: center; }

/* Long-form text, which the app has nowhere else — every other page is rows
   and fields. Generous line height and real spacing between paragraphs. */
.prose { font-size: 16px; line-height: 26px; color: var(--on-surface-variant); }
.prose h2 {
    margin: var(--space-6) 0 var(--space-3);
    font-size: 18px;
    line-height: 26px;
    font-weight: 700;
    color: var(--on-surface);
}
.prose h2:first-child { margin-top: 0; }
.prose p { margin: 0 0 var(--space-4); }
.prose ul { margin: 0 0 var(--space-4); padding-left: var(--space-5); }
.prose li { margin-bottom: var(--space-3); }
.prose strong { color: var(--on-surface); font-weight: 700; }

/* Divides one card's contents, where .section-head divides the page. Quieter
   than a section head on purpose: it is a label on a group, not a heading of
   its own standing. */
.group-head {
    margin: var(--space-5) 0 var(--space-2);
    font-size: 11px;
    line-height: 16px;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--on-surface-muted);
}

.section-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); }
.section-head a { font-size: 12px; font-weight: 600; color: var(--primary); text-decoration: none; }

.empty { margin: var(--space-4) 0 0; font-size: 14px; color: var(--on-surface-muted); }

.owner-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-3);
    padding-top: var(--space-3);
    border-top: 1px solid var(--outline-variant);
}

/* ---------- Emoji picker ---------- */

.picker { margin-bottom: var(--space-4); }

/*
 * The chosen icon sits beside the name, because that is exactly how the pair
 * appears in the list afterwards — picking one becomes a preview of the row.
 */
.picker__head { display: flex; align-items: flex-end; gap: var(--space-3); }
.picker__name { flex: 1; min-width: 0; margin-bottom: 0; }

.picker__preview {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    font-size: 22px;
    line-height: 1;
    background: var(--surface-sunken);
    border: 1px solid var(--outline-variant);
    border-radius: var(--radius-control);
}

.picker__grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
    gap: var(--space-2);
    max-height: 168px;
    overflow-y: auto;
    margin-top: var(--space-3);
    padding: var(--space-2);
    background: var(--surface-sunken);
    border: 1px solid var(--outline-variant);
    border-radius: var(--radius-control);
}

.picker__option {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    height: 44px;
    font-size: 20px;
    line-height: 1;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--space-2);
    cursor: pointer;
    transition: background .12s, border-color .12s;
}
.picker__option:hover { background: var(--surface-card); }
.picker__option.is-selected {
    background: var(--primary-container);
    border-color: var(--primary);
}

/* ---------- Menu list ---------- */

.menu-link {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-4) var(--space-5);
    margin-bottom: var(--space-3);
    background: var(--surface-card);
    border-radius: var(--radius-control);
    box-shadow: var(--shadow-1);
    text-decoration: none;
    color: var(--on-surface);
    transition: transform .15s, box-shadow .15s;
}
.menu-link:last-child { margin-bottom: 0; }
.menu-link:hover { transform: translateY(-2px); box-shadow: var(--shadow-2); }

.menu-link__title { display: block; font-size: 16px; font-weight: 600; }
.menu-link__desc { display: block; margin-top: 2px; font-size: 12px; color: var(--on-surface-muted); }
.menu-link svg { width: 18px; height: 18px; flex-shrink: 0; color: var(--on-surface-muted); }

/* ---------- Filters ---------- */

.filter-toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: var(--space-4) var(--space-5);
    font-family: inherit;
    font-size: 15px;
    font-weight: 600;
    color: var(--on-surface);
    background: var(--surface-card);
    border: none;
    border-radius: var(--radius-control);
    box-shadow: var(--shadow-1);
    cursor: pointer;
}
.filter-toggle__count {
    font-size: 12px;
    font-weight: 600;
    color: var(--primary-strong);
    background: var(--primary-container);
    border-radius: var(--radius-pill);
    padding: 2px var(--space-3);
}

.filter-panel { margin-top: var(--space-3); }
.filter-panel[hidden] { display: none; }

.filter-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-3); }

/* ---------- Charts ---------- */

.chart { margin-top: var(--space-4); }

.chart__peak {
    margin: 0 0 var(--space-1);
    font-size: 12px;
    line-height: 16px;
    font-weight: 600;
    color: var(--on-surface-muted);
    font-variant-numeric: tabular-nums;
}

.chart__plot { display: block; width: 100%; height: 168px; }

.chart__bar { fill: var(--primary); }
.chart__bar--income { fill: var(--success); }

.chart__grid { stroke: var(--outline-variant); stroke-width: 1; stroke-dasharray: 2 4; }
.chart__axis { stroke: var(--outline-variant); stroke-width: 1; }

.chart__labels { display: flex; margin-top: var(--space-2); }
.chart__label {
    flex: 1;
    text-align: center;
    font-size: 12px;
    line-height: 16px;
    font-weight: 500;
    color: var(--on-surface-muted);
}
.chart__label--now { color: var(--on-surface); font-weight: 700; }

.chart__legend {
    display: flex;
    gap: var(--space-5);
    margin: var(--space-4) 0 0;
    font-size: 12px;
    line-height: 16px;
    color: var(--on-surface-muted);
}
.chart__key {
    display: inline-block;
    width: 10px;
    height: 10px;
    margin-right: var(--space-2);
    border-radius: var(--radius-tiny);
    background: var(--primary);
}
.chart__key--income { background: var(--success); }

/*
 * Donut: this month's spending split by category.
 *
 * The slices need colours that stay apart from each other, which a theme's ten
 * tokens cannot give — so this is a fixed set, like --success and --error are.
 * Picked to read on both the lightest card and the darkest one; they are only
 * ever fills next to a legend, never text.
 */
.donut {
    position: relative;
    width: 176px;
    height: 176px;
    margin: var(--space-4) auto 0;
    flex-shrink: 0;
}
.donut svg { display: block; width: 100%; height: 100%; }
.donut__slice { fill: none; stroke-width: 14; transition: opacity .15s; }
.donut__slice:hover { opacity: .8; }
.donut__hole { fill: none; stroke: var(--surface-sunken); stroke-width: 14; }

.donut__center {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    pointer-events: none;
}
.donut__label { font-size: 11px; letter-spacing: .04em; text-transform: uppercase; color: var(--on-surface-muted); }
.donut__total {
    font-size: 17px;
    font-weight: 700;
    color: var(--on-surface);
    font-variant-numeric: tabular-nums;
}

.slice-list { margin: var(--space-5) 0 0; padding: 0; list-style: none; }
.slice-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) 0;
}
.slice-row__key {
    flex-shrink: 0;
    width: 10px;
    height: 10px;
    border-radius: var(--radius-tiny);
}
.slice-row__name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: 14px;
    color: var(--on-surface-variant);
}
.slice-row__share {
    flex-shrink: 0;
    font-size: 12px;
    color: var(--on-surface-muted);
    font-variant-numeric: tabular-nums;
}
.slice-row__value {
    flex-shrink: 0;
    font-size: 14px;
    font-weight: 600;
    color: var(--on-surface);
    font-variant-numeric: tabular-nums;
}

@media (min-width: 560px) {
    .donut-wrap { display: flex; align-items: center; gap: var(--space-7); }
    .donut-wrap .slice-list { flex: 1; margin-top: 0; }
    .donut { margin: 0; }
}

.bar-row { margin-bottom: var(--space-4); }
.bar-row:last-child { margin-bottom: 0; }
.bar-row__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.bar-row__name { font-size: 14px; font-weight: 600; color: var(--on-surface); }
.bar-row__value { font-size: 13px; font-weight: 600; color: var(--on-surface-variant); font-variant-numeric: tabular-nums; }

/* display is stated because the budget card puts the bar inside a <summary>,
   where only inline elements are allowed. */
.bar-track {
    display: block;
    height: 8px;
    margin-top: var(--space-2);
    background: var(--surface-sunken);
    border-radius: var(--radius-pill);
    overflow: hidden;
}
.bar-fill { display: block; height: 100%; background: var(--primary); border-radius: var(--radius-pill); }
.bar-fill--warn { background: #d97706; }
.bar-fill--over { background: var(--error); }

/* Target menabung dibaca terbalik dari anggaran: penuh itu tujuannya, bukan
   peringatan. Karena itu warnanya bukan merah, dan statusnya juga ditulis. */
.bar-fill--near { background: var(--secondary); }
.bar-fill--done { background: var(--success); }
.bar-row__note { margin: var(--space-2) 0 0; font-size: 12px; color: var(--on-surface-muted); }
.bar-row__note--warn { color: #b45309; font-weight: 600; }
.bar-row__note--over { color: var(--error); font-weight: 600; }

/* ---------- Data cards ---------- */

.data-card {
    background: var(--surface-card);
    border-radius: var(--radius-card);
    padding: var(--space-2) var(--space-5) var(--space-4);
    box-shadow: var(--shadow-1);
    margin-bottom: var(--space-4);
}

/*
 * Collapsible variant. A month of transactions is a lot of scrolling when every
 * record shows six rows, so the card shows one summary line and keeps the full
 * table one tap away. Built on <details> so it survives a live-update swap
 * without any script needing to be re-attached.
 */
details.data-card { padding: 0; }

.data-card__summary {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-height: 44px;
    padding: var(--space-4) var(--space-5);
    cursor: pointer;
    list-style: none;
}
.data-card__summary::-webkit-details-marker { display: none; }
.data-card__summary:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: -2px;
    border-radius: var(--radius-card);
}

.data-card__head { flex: 1; min-width: 0; }

/*
 * Inline variant — head and actions on one line. Used where the card is not
 * foldable and carries a single button: stacking them would leave the button
 * alone on a line of its own, which reads as a second, heavier card.
 */
.data-card--inline {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding-top: var(--space-4);
}
.data-card--inline .data-card__actions { padding-top: 0; flex-wrap: nowrap; }

/*
 * Flush variant — a collapsible row living inside another card, as on the
 * dashboard. It keeps the tap-to-open behaviour but drops the surface and the
 * shadow, because a card sitting inside a card reads as a mistake.
 */
.data-card--flush {
    background: none;
    box-shadow: none;
    border-radius: 0;
    padding: 0;
    margin-bottom: 0;
    border-bottom: 1px solid var(--outline-variant);
}
.data-card--flush:last-of-type { border-bottom: none; }
.data-card--flush > .data-card__summary,
.data-card--flush .data-card__detail { padding-left: 0; padding-right: 0; }
.data-card--flush .data-card__detail { border-top: none; }

/* Flat variant — the book list keeps its action visible instead of folding.
   Its head sits straight in the card, so it brings its own breathing room. */
.data-card > .data-card__head { padding: var(--space-3) 0; }

.data-card__head-title {
    display: block;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: 16px;
    line-height: 24px;
    font-weight: 600;
    color: var(--on-surface);
}

/* Wraps rather than truncates: the date lives at the end of this line, and the
   date is the thing people scan a ledger for. */
.data-card__head-meta {
    display: block;
    margin-top: 2px;
    font-size: 12px;
    line-height: 16px;
    color: var(--on-surface-muted);
}

.data-card__chevron {
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    color: var(--on-surface-muted);
    transition: transform 150ms ease;
}
details[open] > .data-card__summary .data-card__chevron { transform: rotate(180deg); }

.data-card__detail {
    padding: 0 var(--space-5) var(--space-4);
    border-top: 1px solid var(--outline-variant);
}

.data-card__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--outline-variant);
}
.data-card__row:last-of-type { border-bottom: none; }

.data-card__label {
    flex-shrink: 0;
    font-size: 11px;
    line-height: 16px;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--on-surface-muted);
}

.data-card__value {
    text-align: right;
    font-size: 15px;
    line-height: 22px;
    font-weight: 600;
    color: var(--on-surface);
    overflow-wrap: anywhere;
}
.data-card__value--muted { font-weight: 400; color: var(--on-surface-variant); }

/* Wraps, because a card with several actions would otherwise squash them all
   onto one line on a phone. Centred, so a text button and a boxed one sitting
   in the same row line up on their labels rather than their edges. */
.data-card__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-2);
    padding-top: var(--space-4);
}

.btn-action {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4);
    font-family: inherit;
    font-size: 13px;
    line-height: 20px;
    font-weight: 600;
    color: var(--on-surface-variant);
    background: var(--surface-sunken);
    border: 1px solid var(--outline-variant);
    border-radius: var(--radius-control);
    cursor: pointer;
    transition: border-color .15s, color .15s, background .15s;
}
.btn-action:hover { border-color: var(--primary); color: var(--primary); }

.btn-action--danger { color: var(--error); }
.btn-action--danger:hover { border-color: var(--error); background: var(--error-container); color: var(--error); }

.star-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    background: transparent;
    border: none;
    border-radius: var(--radius-pill);
    color: var(--on-surface-muted);
    cursor: pointer;
    transition: color .15s, background .15s;
}
.star-btn:hover { background: var(--surface-container); color: var(--primary); }
.star-btn.is-on { color: var(--primary); }
.star-btn svg { width: 20px; height: 20px; }
.data-card__value .star-btn { margin: calc(var(--space-3) * -1) 0; }

/* ---------- Pagination ---------- */

.pager {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-4) 0 0;
}

.pager__info { font-size: 13px; color: var(--on-surface-muted); }

.pager__nav { display: flex; align-items: center; gap: var(--space-4); }

.pager__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    color: var(--on-surface-variant);
    background: var(--surface-card);
    border: 1px solid var(--outline-variant);
    border-radius: var(--radius-control);
    text-decoration: none;
    box-shadow: var(--shadow-1);
}
.pager__btn:hover { border-color: var(--primary); color: var(--primary); }
.pager__btn[aria-disabled="true"] {
    opacity: .4;
    pointer-events: none;
    box-shadow: none;
}
.pager__btn svg { width: 18px; height: 18px; }

.pager__page { font-size: 14px; font-weight: 600; color: var(--on-surface); }

/* ---------- Theme picker ---------- */

/*
 * The size picker. Same shape as the theme grid on purpose — they sit next to
 * each other and answer the same kind of question, so they should not look
 * like two different kinds of control.
 *
 * The sample letters differ in size between the buttons rather than showing an
 * absolute preview: the whole page is already zoomed by the current setting, so
 * what a reader can judge is which one is bigger, not how big it will end up.
 */
.scale-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
    gap: var(--space-2);
    margin: var(--space-4) 0;
}

.scale-option {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-2);
    font-family: inherit;
    font-size: 12px;
    line-height: 16px;
    font-weight: 600;
    color: var(--on-surface-variant);
    background: var(--surface-sunken);
    border: 1px solid var(--outline-variant);
    border-radius: var(--radius-control);
    cursor: pointer;
    transition: border-color .15s, color .15s;
}
.scale-option:hover { border-color: var(--primary); color: var(--on-surface); }
.scale-option.is-selected {
    border-color: var(--primary);
    background: var(--primary-container);
    color: var(--primary-strong);
}

.scale-option__sample {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 32px;
    font-weight: 700;
    color: var(--on-surface);
}
.theme-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
    gap: var(--space-2);
    margin-top: var(--space-4);
}

.theme-option {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-2);
    font-family: inherit;
    font-size: 12px;
    line-height: 16px;
    font-weight: 600;
    color: var(--on-surface-variant);
    background: var(--surface-sunken);
    border: 1px solid var(--outline-variant);
    border-radius: var(--radius-control);
    cursor: pointer;
    transition: border-color .15s, color .15s;
}
.theme-option:hover { border-color: var(--primary); color: var(--on-surface); }
.theme-option.is-selected {
    border-color: var(--primary);
    border-width: 2px;
    color: var(--on-surface);
}

/*
 * Each swatch carries both of its colours, and the mode picks which one shows.
 * That way switching to dark repaints the whole grid with no script involved,
 * and a chip always previews the colour that tapping it will actually give.
 */
.theme-option__chip {
    width: 40px;
    height: 40px;
    border-radius: var(--radius-pill);
    background: var(--swatch);
}
:root:is([data-mode="dark"], [data-mode="oled"]) .theme-option__chip { background: var(--swatch-dark); }

/* ---------- Typeface picker ---------- */

/*
 * A list rather than a grid: each row previews itself in its own face, and a
 * name set in the face it names is the only preview that tells the truth.
 */
.font-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin: var(--space-4) 0;
}

.font-option {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-1);
    width: 100%;
    padding: var(--space-3) var(--space-4);
    text-align: left;
    color: var(--on-surface-variant);
    background: var(--surface-sunken);
    border: 1px solid var(--outline-variant);
    border-radius: var(--radius-control);
    cursor: pointer;
    transition: border-color .15s;
}
.font-option:hover { border-color: var(--primary); }
.font-option.is-selected {
    border-color: var(--primary);
    border-width: 2px;
    padding: calc(var(--space-3) - 1px) calc(var(--space-4) - 1px);
}

.font-option__name { font-size: 15px; font-weight: 700; color: var(--on-surface); }

/* The digits are the point: this app is mostly numbers. */
.font-option__sample {
    font-size: 17px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--on-surface);
}

.font-option__note { font-size: 12px; line-height: 16px; color: var(--on-surface-muted); }

/* ---------- Light or dark ---------- */

.mode-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    /* Empat pilihan, dua baris. Satu baris berisi empat membuat labelnya
       terpotong justru di kata yang membedakannya. */
    gap: var(--space-2);
    margin: var(--space-4) 0;
}

.mode-option {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-2);
    font-family: inherit;
    font-size: 12px;
    line-height: 16px;
    font-weight: 600;
    color: var(--on-surface-variant);
    background: var(--surface-sunken);
    border: 1px solid var(--outline-variant);
    border-radius: var(--radius-control);
    cursor: pointer;
    transition: border-color .15s, color .15s;
}
.mode-option:hover { border-color: var(--primary); color: var(--on-surface); }
.mode-option.is-selected {
    border-color: var(--primary);
    border-width: 2px;
    color: var(--on-surface);
}

/* Literal swatches, not token-driven: they must show what each mode looks
   like, including the one you are not currently in. */
.mode-option__sample {
    width: 100%;
    height: 34px;
    border-radius: var(--radius-tiny);
    border: 1px solid rgba(120, 128, 140, .35);
}
.mode-option__sample--light {
    background: linear-gradient(180deg, #ffffff 0 60%, #f2f3f5 60% 100%);
}
.mode-option__sample--dark {
    background: linear-gradient(180deg, #1b1e26 0 60%, #0f1115 60% 100%);
}
.mode-option__sample--oled {
    background: linear-gradient(180deg, #0b0d11 0 60%, #000000 60% 100%);
}
/* Separuh terang separuh gelap: contohnya sendiri yang menjelaskan artinya. */
.mode-option__sample--auto {
    background: linear-gradient(105deg, #ffffff 0 50%, #0f1115 50% 100%);
}

/*
 * Eye button. Icon only, because the topbar is the narrowest row in the app
 * and a word here would push the book name into an ellipsis.
 */
.eye-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--touch);
    height: var(--touch);
    padding: 0;
    background: none;
    border: none;
    border-radius: var(--radius-pill);
    color: var(--on-surface-muted);
    cursor: pointer;
    transition: color .15s, background .15s;
}

.eye-btn:hover { background: var(--surface-container); color: var(--on-surface); }
.eye-btn.is-on { color: var(--primary); }
.eye-btn svg { width: 20px; height: 20px; }

/* The slash only belongs on the icon while the amounts are actually covered. */
.icon-eye__slash { display: none; }
.eye-btn.is-on .icon-eye__slash { display: inline; }

/* ---------- Invoice ---------- */

/*
 * Kertas, bukan halaman aplikasi.
 *
 * Ini satu-satunya tempat di seluruh berkas ini yang menulis warna langsung
 * dan bukan lewat token — dan itu disengaja. Invoice dikirim ke luar lalu
 * dicetak; ia tidak boleh berubah rupa karena pembacanya memilih tema gelap
 * atau aksen Teal. Hitam di atas putih, apa pun temanya.
 */
.inv-page { max-width: 820px; margin: 0 auto; padding: var(--space-6) var(--pad-edge); }

.inv-bar { display: flex; gap: var(--space-3); margin-bottom: var(--space-5); }

.inv-paper {
    background: #ffffff;
    color: #111827;
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-1);
    padding: 40px;
}

.inv-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--space-5);
}

.inv-brand { display: inline-flex; align-items: center; gap: 10px; }
.inv-brand img { width: auto; height: 34px; }

.inv-brand__name {
    font-size: 15px;
    font-weight: 700;
    letter-spacing: .02em;
    color: #111827;
}

.inv-meta { font-size: 12px; line-height: 1.6; color: #6b7280; text-align: right; }

/*
 * Penjumlahan di bawah rinciannya, rata kanan mengikuti kolom Jumlah.
 *
 * Sebelumnya total dicetak besar di atas sebelum ada yang dijumlahkan. Angkanya
 * benar, tapi tempatnya bukan tempat orang mencarinya — dan sebesar itu ia
 * mengambil perhatian dari rincian yang justru menjelaskan angkanya.
 */
.inv-sum {
    margin: 18px 0 0;
    margin-left: auto;
    max-width: 300px;
}

.inv-sum__row {
    display: flex;
    justify-content: space-between;
    gap: var(--space-4);
    margin: 0;
    padding: 5px 0;
    font-size: 13px;
    color: #6b7280;
    font-variant-numeric: tabular-nums;
}

.inv-sum__row--total {
    margin-top: 5px;
    padding-top: 9px;
    border-top: 1.5px solid #111827;
    font-size: 15px;
    font-weight: 700;
    color: #111827;
}

/* Penerima punya bloknya sendiri, bukan menempel di baris jatuh tempo. */
.inv-to { margin: 30px 0 26px; }
.inv-to__label {
    margin: 0 0 3px;
    font-size: 11px;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: #9ca3af;
}
.inv-to__name { margin: 0; font-size: 15px; font-weight: 700; color: #111827; }
.inv-to__detail { margin: 2px 0 0; font-size: 12.5px; color: #6b7280; }

.inv-table { width: 100%; border-collapse: collapse; font-size: 13px; table-layout: fixed; }

.inv-table th {
    padding: 0 0 8px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: #9ca3af;
    text-align: left;
    border-bottom: 1px solid #e5e7eb;
}

.inv-table td { padding: 12px 0; border-bottom: 1px solid #f3f4f6; vertical-align: top; }
.inv-table tr:last-child td { border-bottom: none; }

/*
 * Dua kelas di pemilihnya, bukan satu. `.inv-table th` sudah rata kiri dan
 * satu elemen membuatnya lebih spesifik daripada `.inv-qty` sendirian — judul
 * kolomnya tetap di kiri sementara angkanya rata kanan, dan keduanya tidak
 * pernah bertemu. Jebakan yang sama pernah membuat dua kolom tanggal berbeda
 * lebar di form.
 */
.inv-table .inv-qty { width: 64px; text-align: right; }
.inv-table .inv-num { width: 140px; text-align: right; font-variant-numeric: tabular-nums; }
.inv-tax td { color: #6b7280; }

.inv-foot {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    gap: var(--space-6);
    margin-top: 40px;
}

.inv-foot__left { font-size: 12px; line-height: 1.7; color: #6b7280; max-width: 340px; }
.inv-foot__left p { margin: 0; }
.inv-foot__title {
    margin: 0 0 2px;
    font-size: 11px;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: #9ca3af;
}

/*
 * Invoice yang sudah lunas dibedakan, bukan cuma diberi label.
 *
 * Yang dicari orang saat membuka daftar ini adalah mana yang BELUM dibayar,
 * jadi yang sudah selesai sengaja mundur satu langkah: judulnya diredupkan dan
 * nominalnya tidak lagi ditebalkan. Statusnya tetap ditulis sebagai teks —
 * warna saja tidak pernah cukup.
 */
.data-card--paid .data-card__head-title { color: var(--on-surface-muted); }
.data-card--paid > .data-card__summary .amount { color: var(--on-surface-muted); font-weight: 500; }

/* Baris isian tagihan di form: keterangan lebar, qty dan harga menyusul. */
.inv-row { display: grid; grid-template-columns: 1fr 72px 132px; gap: var(--space-3); }

@media (max-width: 560px) {
    .inv-row { grid-template-columns: 1fr; gap: 0; }
    .inv-paper { padding: 24px; }
    .inv-head { flex-direction: column; }
    .inv-meta { text-align: left; }
    .inv-foot { flex-direction: column; align-items: stretch; }
}

/*
 * Saat dicetak, yang tersisa hanya kertasnya: tanpa tombol, tanpa bayangan,
 * tanpa sudut membulat — dan tanpa zoom, karena --ui-scale mengecilkan
 * seluruh antarmuka ke 80% dan kertas A4 tidak ikut mengecil.
 */
@media print {
    .inv-bar, .topbar, .bottomnav { display: none !important; }
    body { zoom: 1; background: #ffffff; }
    .inv-page { max-width: none; padding: 0; }
    .inv-paper { box-shadow: none; border-radius: 0; padding: 0; }
    .inv-table td, .inv-table th { border-color: #d1d5db; }
    @page { size: A4; margin: 18mm; }
}

/* ---------- Calendar ---------- */

.cal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    margin-bottom: var(--space-4);
}

.cal-title { margin: 0; font-size: 18px; line-height: 26px; font-weight: 600; }

.cal-nav {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--touch);
    height: var(--touch);
    border-radius: var(--radius-pill);
    color: var(--primary);
    font-size: 18px;
    text-decoration: none;
}

.cal-nav:hover { background: var(--surface-container); }

/*
 * Seven equal columns, whatever the screen. minmax(0, 1fr) rather than 1fr:
 * a plain 1fr floors at the content width, and a two-digit date plus its dots
 * would push the grid past the card on a narrow phone.
 */
.cal-grid {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 2px;
}

.cal-dow {
    padding: var(--space-1) 0;
    font-size: 11px;
    font-weight: 600;
    text-align: center;
    color: var(--on-surface-muted);
}

.cal-day {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    gap: 3px;
    /* Square-ish cells keep the month readable as a shape, not a list. */
    min-height: calc(var(--touch) * 0.95);
    padding: 6px 0 4px;
    border-radius: var(--radius-tiny);
    color: var(--on-surface);
    font-variant-numeric: tabular-nums;
    text-decoration: none;
    transition: background .15s;
}

.cal-day:hover { background: var(--surface-container); }

.cal-day__num { font-size: 14px; line-height: 18px; }

/* The neighbouring months are shown so the weeks stay whole, not because
   anyone came to read them. */
.cal-day--outside { color: var(--on-surface-muted); opacity: .55; }

.cal-day--today .cal-day__num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 22px;
    border-radius: var(--radius-pill);
    background: var(--primary);
    color: var(--on-primary);
    font-weight: 700;
}

.cal-day--selected { background: var(--primary-container); }
.cal-day--selected:hover { background: var(--primary-container); }

.cal-day__dots { display: flex; gap: 3px; min-height: 6px; }

.cal-dot {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: var(--radius-pill);
    background: var(--on-surface-muted);
}

/* Three kinds, three chart colours — the same six that never change with the
   theme, because here too they exist to be told apart, not to match a brand. */
.cal-dot--agenda   { background: var(--chart-1); }
.cal-dot--hutang   { background: var(--chart-2); }
.cal-dot--berulang { background: var(--chart-3); }

/*
 * The calendar keeps its own legend rather than borrowing .chart__legend.
 * .chart__key is not a wrapper — it *is* the 10x10 swatch — so putting a label
 * inside one forced the words into a ten-pixel box and they piled on top of
 * each other.
 */
.cal-legend {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-4);
    margin: var(--space-4) 0 0;
    padding: 0;
    list-style: none;
    font-size: 12px;
    line-height: 16px;
    color: var(--on-surface-muted);
}

/* nowrap on the item, wrap on the row: "Jatuh tempo" moves to the next line
   whole rather than splitting away from its own dot. */
.cal-legend li {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
}

/*
 * One checkbox with its label.
 *
 * Deliberately not .segmented. That class hides its inputs and switches off
 * their pointer events, because there the label *is* the control — which for a
 * single box leaves a grey slab with nothing to tick and nothing that responds.
 * Sized like the dues checklist, which is the other place a real checkbox
 * appears.
 */
.check-field {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-height: var(--touch);
    margin-bottom: var(--space-4);
    cursor: pointer;
}

.check-field input[type="checkbox"] {
    flex-shrink: 0;
    width: calc(22px / var(--ui-scale));
    height: calc(22px / var(--ui-scale));
    accent-color: var(--primary);
}

.check-field span { font-size: 15px; line-height: 22px; color: var(--on-surface-variant); }

/* ---------- Hidden amounts ---------- */

/*
 * Menutupi nominal, bukan menghapusnya. Angkanya tetap ada di HTML dan tetap
 * terbaca oleh siapa pun yang membuka sumber halaman — ini untuk mata orang di
 * sebelah, bukan pengganti izin akses, dan halaman Profil mengatakannya.
 *
 * Lebarnya dikunci ke lebar aslinya lewat visibility, jadi tabel dan baris
 * label-nilai tidak melompat saat penutupnya dinyalakan.
 */
:root[data-amounts="hidden"] .amount,
:root[data-amounts="hidden"] .hero__amount,
:root[data-amounts="hidden"] .stat__value,
:root[data-amounts="hidden"] .donut__total,
:root[data-amounts="hidden"] .data-card__value.amount,
:root[data-amounts="hidden"] .hero__split strong,
:root[data-amounts="hidden"] .slice-row__value,
:root[data-amounts="hidden"] .bar-row__value {
    position: relative;
    visibility: hidden;
}

:root[data-amounts="hidden"] .amount::after,
:root[data-amounts="hidden"] .hero__amount::after,
:root[data-amounts="hidden"] .stat__value::after,
:root[data-amounts="hidden"] .donut__total::after,
:root[data-amounts="hidden"] .hero__split strong::after,
:root[data-amounts="hidden"] .slice-row__value::after,
:root[data-amounts="hidden"] .bar-row__value::after {
    content: "••••••";
    position: absolute;
    top: 0;
    right: 0;
    visibility: visible;
    letter-spacing: .08em;
    color: var(--on-surface-muted);
}

/* ---------- Install prompt ---------- */

.install-bar {
    position: sticky;
    bottom: var(--space-4);
    z-index: 30;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    width: 100%;
    max-width: var(--content-width);
    margin: 0 auto var(--space-4);
    padding: var(--space-3) var(--space-4);
    background: var(--surface-card);
    border: 1px solid var(--outline-variant);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-2);
}
.install-bar[hidden] { display: none; }

/* On the sign-in screen there is no bottom bar to hover above. */
.install-bar--inline {
    position: static;
    margin: var(--space-6) 0 0;
}

.install-bar__icon { width: 44px; height: 44px; border-radius: var(--radius-control); flex-shrink: 0; }
.install-bar__text { flex: 1; min-width: 0; }
.install-bar__title { margin: 0; font-size: 14px; line-height: 20px; font-weight: 600; color: var(--on-surface); }
.install-bar__sub { margin: 2px 0 0; font-size: 12px; line-height: 16px; color: var(--on-surface-muted); }

.install-bar__actions { display: flex; align-items: center; gap: var(--space-2); flex-shrink: 0; }

.install-bar__close {
    width: 32px;
    height: 32px;
    font-size: 18px;
    line-height: 1;
    font-family: inherit;
    color: var(--on-surface-muted);
    background: transparent;
    border: none;
    border-radius: var(--radius-pill);
    cursor: pointer;
}
.install-bar__close:hover { background: var(--surface-container); color: var(--on-surface); }

/* ---------- Admin panel ---------- */

.admin main { max-width: var(--admin-width); }
.admin .topbar__inner { max-width: var(--admin-width); }

/* Admin has room for more than one card per row. */
.data-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: var(--space-4);
    align-items: start;
}
.data-grid .data-card { margin-bottom: 0; }

.table-wrap { overflow-x: auto; margin-top: var(--space-4); }

table.data { width: 100%; border-collapse: collapse; font-size: 14px; }
table.data th,
table.data td {
    text-align: left;
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--outline-variant);
    white-space: nowrap;
}
table.data th {
    font-size: 12px;
    line-height: 16px;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--on-surface-muted);
    font-weight: 600;
}
table.data td { color: var(--on-surface-variant); }
table.data td:first-child { color: var(--on-surface); font-weight: 600; }
table.data tr:last-child td { border-bottom: none; }

.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-4); }
.stat {
    background: var(--surface-card);
    border-radius: var(--radius-card);
    padding: var(--space-5);
    box-shadow: var(--shadow-1);
}
.stat__label { margin: 0; font-size: 12px; line-height: 16px; font-weight: 500; color: var(--on-surface-muted); }
.stat__value {
    margin: var(--space-2) 0 0;
    font-size: 24px;
    line-height: 32px;
    font-weight: 700;
    letter-spacing: -.01em;
    color: var(--on-surface);
    font-variant-numeric: tabular-nums;
}

/* ---------- Utilities ---------- */

a.btn, a.cta, a.btn-ghost { text-decoration: none; text-align: center; }

.subhead { font-size: 18px; line-height: 26px; font-weight: 600; color: var(--on-surface); }

.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 700; color: var(--on-surface); }

.error-page { margin: auto; padding: var(--space-8) var(--pad-edge); text-align: center; max-width: 420px; }
.error-page__code { font-size: 48px; line-height: 56px; }

/* Visible to screen readers only — keeps every page anchored by a heading. */
.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;
}

.mt-2 { margin-top: var(--space-2); }
.mt-3 { margin-top: var(--space-3); }
.mt-4 { margin-top: var(--space-4); }
.mt-6 { margin-top: var(--space-6); }
.text-right { text-align: right; }
.code-input { text-transform: uppercase; letter-spacing: .15em; }
.actions { display: flex; align-items: center; gap: var(--space-4); }

/* ---------- Touch targets ---------- */

/*
 * Everything shrinks with --ui-scale except the area a finger has to hit.
 * Stated once here instead of inside each component so a future change to the
 * scale cannot quietly push any control under the 44px floor.
 */
.btn,
.btn-ghost,
.btn-link,
.btn-action,
.filter-toggle,
.pager__btn,
.picker__option,
.star-btn,
.bottomnav__item,
.data-card__summary,
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="date"],
input[type="month"],
input[type="datetime-local"],
input[type="time"],
select,
textarea {
    min-height: var(--touch);
}

.pager__btn,
.picker__option,
.star-btn {
    min-width: var(--touch);
}

@media (prefers-reduced-motion: reduce) {
    * { animation: none !important; transition: none !important; }
    .btn:hover, .cta:hover { transform: none; }
}
