/* ==========================================================================================
   VD20 補習班 ERP — app.css v2 (docs/ui/DESIGN-SPEC-v2.md · design B "Refined Professional"
   with the Comfort-First type scale of design C; v1 docs/ui/DESIGN-SPEC.md stays binding where v2 is silent)
   Sections: 1 tokens (light · dark · contrast · v1 aliases) → 2 base → 3 legacy elements & classes →
             4 shell (sidebar · rail · drawer · sheet · topbar) → 5 components → 6 utilities →
             7 MOTION (keyframes · stagger · reveal · view transitions · preference levels) →
             8 responsive → 9 print
   Operator rulings (binding): the page background is pure WHITE (no grey canvas — surfaces separate with
   hairlines and soft elevation); LIGHT is the default on every computer (the OS dark setting is NOT
   followed — dark only via html[data-theme="dark"] from the 外觀 toggle); prints are always light.
   Rules: views never contain a hex colour — everything below reads tokens. Animate transform / opacity
   only (the two contained disclosure heights are named in §7). CSP: no style="" anywhere — geometry comes
   from classes, SVG attributes, <progress>, or JS CSSOM custom properties (el.style.setProperty).
   Legacy class names (.btn.sec, .btn.sm, .pill, .grid, .muted, .flash, .tabs a.active, .fsec > .hd/.bd,
   .fform …) stay mapped until the page sweep ends.
   ========================================================================================== */

/* ── 1. tokens ─────────────────────────────────────────────────────────────────────────── */
:root {
    color-scheme: light;
    /* surfaces — WHITE page: canvas = sheet = sidebar; hairlines + soft elevation do the separation */
    --canvas:#ffffff; --sheet:#ffffff; --sb-bg:#ffffff; --subtle:#f8f9fb; --raised:#ffffff;
    --hover:#f2f4f7;  --active:#e9edf2; --inset:#f2f4f7; --thumb:#ffffff; --nav-active:#ffffff;
    --scrim:rgba(15,20,30,.32); --glass:rgba(255,255,255,.82);
    /* gradients — key surfaces only (primary/danger button, brand mark, dashboard glow, stat tile).
       高竿 brand layer (docs/ui/BRAND-SPEC.md §3): the primary / brand gradients sit on the darkened brand blue
       (white on them ≥ 4.57:1); the dashboard glow is a faint blue + green bloom */
    --grad-glow:radial-gradient(1000px 280px at 80% -140px, rgba(0,146,216,.08), transparent 70%),
                radial-gradient(520px 200px at 100% -40px, rgba(67,177,73,.06), transparent 70%);
    --grad-tile:linear-gradient(180deg,#ffffff 0%,#fafbfd 100%);
    --grad-primary:linear-gradient(180deg,#037CBA 0%,#006FAB 100%);
    --grad-primary-hover:linear-gradient(180deg,#0477B4 0%,#00679F 100%);
    --grad-danger:linear-gradient(180deg,#d23a2f 0%,#b42318 100%);
    --grad-brand:linear-gradient(140deg,#1AA3E2 0%,#0092D8 50%,#0077B6 100%);
    /* lines */
    --border:#e4e7ec; --border-soft:#eef0f3; --border-strong:#d3d8e0;
    --border-control:#7f8795; --border-control-hover:#5b6372;
    --ring:rgba(15,23,42,.09);
    /* ink */
    --text-1:#111620; --text-2:#3b4351; --text-3:#596171; --text-4:#8a919d; --placeholder:#687080;
    /* 高竿 brand colours (manual p.06, BRAND-SPEC §2) — DECORATION, the logo, icons ≥ 20 px, charts and display text
       ≥ 24 px only. Brand blue is 3.44:1 and green 2.75:1 on white, so neither is ever body text, a border, the only
       state signal or a fill behind white text in light mode: UI text and controls use the --accent family below. */
    --gg-blue:#0092D8; --gg-green:#43B149; --gg-ink:#4C4948; --gg-beige:#F8F7F0;
    --gg-rule:linear-gradient(90deg, var(--gg-blue) 0 50%, var(--gg-green) 50% 100%);   /* hard split, not a blend */
    --gg-spectrum:linear-gradient(90deg, #1E72B1 0%, #849CC5 25%, #E8E7E4 54%, #A6C6A7 75%, #4BA665 100%);
    --gg-ribbon-a:.22; --gg-ribbon-a-green:.26;      /* brand stages (login · auth · error) — the ceiling */
    --gg-ribbon-a-soft:.10;                          /* inside the dashboard welcome card — the ceiling */
    --gg-disc:#F0F8FD;                               /* empty-state glyph disc */
    --font-brand:"Segoe UI Variable Display","Segoe UI","Helvetica Neue",Helvetica,Arial,"Noto Sans TC","PingFang TC","Microsoft JhengHei UI","Microsoft JhengHei",sans-serif;
    /* accent — the ONE decorative hue: the brand blue darkened to AA (white on it 4.87, links 5.96) */
    --accent:#0077B6; --accent-hover:#006BA3; --accent-press:#005E8F;
    --accent-soft:#E6F4FB; --accent-softer:#F0F8FD; --accent-text:#00699F; --accent-border:#BDE3F5; --on-accent:#ffffff;
    --focus:#0077B6; --focus-glow:rgba(0,146,216,.22);
    /* the focus ring on the (legacy) dark surfaces — kept ≥3:1 on #0f172a (AUD-116) */
    --focus-dark:#4CB3E4;
    /* status: text / soft bg / dot / border — success sits on the brand green's hue (text 5.60, dot 3.66) */
    --success:#2D7631; --success-soft:#ECF7ED; --success-dot:#39983F; --success-border:#C3E6C5;
    --warning:#a14b06; --warning-soft:#fff5e1; --warning-dot:#e08a0b; --warning-border:#f5d9a4;
    --danger:#b42318;  --danger-soft:#fef0ee;  --danger-dot:#e5484d;  --danger-border:#f6c4bf;
    --danger-solid:#b42318; --danger-solid-hover:#9a1d14; --success-solid:#318135;
    --neutral-soft:#eef1f5; --neutral-text:#3b4351;
    /* money: neutral by default; colour marks an exception (red stays the danger colour — never brand-coloured) */
    --money-neg:#b42318; --money-pos:#2D7631;
    /* chart — series 1 is the brand blue (a ≥ 3:1 graphic); series 2 stays orange (green would read as "good") */
    --series-1:#0092D8; --series-1-soft:rgba(0,146,216,.14); --series-2:#e8692e;
    --grid:#eceff3; --axis:#d3d8e0; --band:rgba(0,146,216,.06);
    /* skeleton */
    --skel:#eef1f5; --skel-hi:rgba(255,255,255,.7);
    /* elevation — ring + ambient + key light (cards draw their edge with the ring, not a border) */
    --e-0:0 0 0 1px var(--ring);
    --e-1:0 0 0 1px var(--ring), 0 1px 2px rgba(15,23,42,.05);
    --e-2:0 0 0 1px rgba(15,23,42,.10), 0 2px 4px -1px rgba(15,23,42,.06), 0 10px 24px -8px rgba(15,23,42,.16);
    --e-3:0 0 0 1px rgba(15,23,42,.10), 0 4px 10px -2px rgba(15,23,42,.08), 0 18px 40px -10px rgba(15,23,42,.24);
    --e-4:0 0 0 1px rgba(15,23,42,.09), 0 8px 18px -4px rgba(15,23,42,.10), 0 32px 72px -16px rgba(15,23,42,.34);
    --e-accent:0 1px 2px rgba(0,94,143,.30), 0 6px 16px -4px rgba(0,146,216,.42);
    --hl-top:inset 0 1px 0 rgba(255,255,255,.20);
    --hl-ring:inset 0 0 0 1px rgba(0,45,80,.20);

    /* type — system fonts only; Latin face first (digits, English names), CJK falls through */
    --font:"Segoe UI Variable Text","Segoe UI",-apple-system,BlinkMacSystemFont,"PingFang TC","Noto Sans TC","Microsoft JhengHei UI","Microsoft JhengHei","Heiti TC",sans-serif;
    --font-display:"Segoe UI Variable Display","Segoe UI",-apple-system,BlinkMacSystemFont,"PingFang TC","Noto Sans TC","Microsoft JhengHei UI","Microsoft JhengHei",sans-serif;
    --font-mono:ui-monospace,"Cascadia Mono","SF Mono",Consolas,monospace;
    /* the Comfort-First scale (design C, binding): 15 px body + cells at 1.65, 24 px titles, 30 px stats */
    --fs-xs:12.5px; --fs-sm:13.5px; --fs-label:14px; --fs-nav:14.5px; --fs-md:15px; --fs-lg:16.5px; --fs-xl:19px; --fs-2xl:24px; --fs-3xl:30px;
    --lh-tight:1.25; --lh-head:1.4; --lh-ui:1.5; --lh-body:1.65; --lh-prose:1.8;
    /* B names kept as aliases of the C scale */
    --t-display:var(--fs-3xl); --t-h1:var(--fs-2xl); --t-h2:var(--fs-lg); --t-h3:var(--fs-lg); --t-body:var(--fs-md);
    --t-prose:14.5px; --t-label:var(--fs-label); --t-meta:var(--fs-sm); --t-cap:var(--fs-xs);

    /* space · radius · size */
    --s-1:4px; --s-2:8px; --s-3:12px; --s-4:16px; --s-5:20px; --s-6:24px; --s-7:28px; --s-8:32px; --s-10:40px; --s-12:48px; --s-16:64px;
    --r-xs:6px; --r-sm:8px; --r-md:10px; --r-lg:12px; --r-xl:16px; --r-2xl:20px; --r-full:999px;
    --h-sm:32px; --h-md:38px; --h-lg:44px; --h-touch:44px;
    --row:48px; --row-compact:40px; --row-phone:64px;
    --sb-w:256px; --rail-w:64px; --tb-h:60px; --inset-gap:0px; --gutter:32px;
    /* z-index */
    --z-sticky:10; --z-sidebar:30; --z-topbar:40; --z-dropdown:50; --z-drawer:60; --z-toast:100; --z-tooltip:110; --z-progress:120;

    /* MOTION — durations · easings · distances · stagger (all motion reads these; §7) */
    --dur-0:80ms;    /* press feedback */
    --dur-1:120ms;   /* colour, hover fills, exits */
    --dur-2:160ms;   /* menus, tooltips, focus glow, small enters */
    --dur-3:220ms;   /* dialogs, toasts out, page/row enter, tab indicator */
    --dur-4:320ms;   /* drawer, view transition, toast spring-in, disclosure */
    --dur-5:480ms;   /* count-up, chart grow, progress fill — the data-growth ceiling */
    --dur-burst:640ms;
    --ease-out:cubic-bezier(.16,1,.3,1);          /* expo-out: fast start, soft settle — the house curve */
    --ease-std:cubic-bezier(.2,0,0,1);            /* standard */
    --ease-in:cubic-bezier(.4,0,1,1);             /* exits */
    --ease-in-out:cubic-bezier(.65,0,.35,1);      /* line draws, shimmer */
    --ease-spring:cubic-bezier(.3,1.3,.6,1);      /* fallback; real spring below (≈4 % overshoot) */
    --stagger:32ms;
    /* DISTANCE tokens — every keyframe reads them, so "reduce / off" turns each effect into a plain fade */
    --m-rise:6px; --m-rise-sm:4px; --m-pop:.96; --m-slide:18px; --m-lift:-2px; --m-lift-btn:-1px; --m-press:.97; --m-shake:3px;

    /* v1 → v2 aliases: untouched views + page CSS keep compiling (delete each when grep finds no user).
       --soft maps to --subtle (NOT the canvas): a legacy "soft" panel stays a visible tint on the white page */
    --shadow-1:var(--e-1); --shadow-2:var(--e-2); --shadow-3:var(--e-3); --shadow-accent:var(--e-accent);
    --tb-bg:var(--glass);
    --ink:var(--text-1); --muted:var(--text-3); --line:var(--border); --soft:var(--subtle); --panel:var(--sheet);
    --brand:var(--accent); --brand2:var(--accent-hover); --brand-soft:var(--accent-soft); --bg:var(--canvas);
    --r:var(--r-md); --shadow:var(--e-1); --shadow-md:var(--e-2);
}
@supports (transition-timing-function: linear(0, 1)) {
    :root { --ease-spring:linear(0, .067 2.2%, .24 5.1%, .62 11.4%, .86 17.2%, .98 23%, 1.035 28.6%, 1.045 33.8%, 1.03 40.5%, 1.008 52%, .998 64%, 1); }
}
/* DARK — ONLY when the user picks it (html[data-theme="dark"], 外觀 toggle). There is deliberately NO
   OS-dark media block: light is the default on every computer (operator 2026-10-10). Chosen, not inverted. */
:root[data-theme="dark"] {
    color-scheme: dark;
    --canvas:#13161a; --sheet:#13161a; --sb-bg:#0f1114; --subtle:#171a1f; --raised:#1b1e24;
    --hover:#1d2027;  --active:#252a33; --inset:#0f1115; --thumb:#2a2f38; --nav-active:#1b1e24;
    --scrim:rgba(0,0,0,.56); --glass:rgba(19,22,26,.82);
    --grad-glow:radial-gradient(1000px 280px at 80% -140px, rgba(0,146,216,.12), transparent 70%),
                radial-gradient(520px 200px at 100% -40px, rgba(67,177,73,.07), transparent 70%);
    --grad-tile:linear-gradient(180deg,#171a20 0%,#13161a 100%);
    --grad-primary:linear-gradient(180deg,#037CBA 0%,#006FAB 100%);
    --grad-primary-hover:linear-gradient(180deg,#037CBA 0%,#0477B4 100%);   /* lighter on hover in dark */
    --grad-danger:linear-gradient(180deg,#d23a2f 0%,#b42318 100%);
    --grad-brand:linear-gradient(140deg,#1AA3E2 0%,#0092D8 50%,#0077B6 100%);
    /* brand layer: dimmer ribbons on the dark canvas; the glyph disc is the brand blue mixed 8 % over #13161a */
    --gg-ribbon-a:.14; --gg-ribbon-a-green:.16; --gg-ribbon-a-soft:.08; --gg-disc:#112029;
    --border:#262a31; --border-soft:#1f2329; --border-strong:#323741; --border-control:#6f7683; --border-control-hover:#8f96a2;
    --ring:rgba(255,255,255,.08);
    --text-1:#eceef2; --text-2:#bdc3cc; --text-3:#949ba7; --text-4:#646b77; --placeholder:#868d99;
    --accent:#0077B6; --accent-hover:#037CBA; --accent-press:#006BA3;
    --accent-soft:#102A38; --accent-softer:#112029; --accent-text:#66BEE8; --accent-border:#0C4562; --on-accent:#ffffff;
    --focus:#4CB3E4; --focus-glow:rgba(76,179,228,.26);
    --success:#7BC880; --success-soft:#1A2C21; --success-dot:#43B149; --success-border:#214428;
    --warning:#fbbf24; --warning-soft:#2a2110; --warning-dot:#f59e0b; --warning-border:#4d3b14;
    --danger:#fb7f7f;  --danger-soft:#2c1719;  --danger-dot:#f0565a;  --danger-border:#5a2a2d;
    --danger-solid:#b42318; --danger-solid-hover:#c22b20; --success-solid:#318135;
    --neutral-soft:#20242b; --neutral-text:#bdc3cc;
    --money-neg:#fb7f7f; --money-pos:#7BC880;
    --series-1:#0092D8; --series-1-soft:rgba(0,146,216,.20); --series-2:#e0703a; --grid:#22262d; --axis:#323741; --band:rgba(76,179,228,.08);
    --skel:#22262d; --skel-hi:rgba(255,255,255,.06);
    --e-0:0 0 0 1px var(--ring);
    --e-1:0 0 0 1px var(--ring), 0 1px 2px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.03);
    --e-2:0 0 0 1px rgba(255,255,255,.11), 0 2px 4px rgba(0,0,0,.45), 0 12px 28px -8px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.04);
    --e-3:0 0 0 1px rgba(255,255,255,.11), 0 6px 14px rgba(0,0,0,.5), 0 24px 48px -12px rgba(0,0,0,.7), inset 0 1px 0 rgba(255,255,255,.05);
    --e-4:0 0 0 1px rgba(255,255,255,.11), 0 10px 24px rgba(0,0,0,.55), 0 40px 80px -16px rgba(0,0,0,.75), inset 0 1px 0 rgba(255,255,255,.06);
    --e-accent:0 0 0 1px rgba(76,179,228,.20), 0 6px 18px -4px rgba(0,119,182,.60);
    --hl-top:inset 0 1px 0 rgba(255,255,255,.16);
}
@media (prefers-contrast: more) {
    :root { --border:#c4cad3; --border-soft:#d3d8e0; --ring:rgba(15,23,42,.24); --border-control:#5b6372; --text-3:#474e5b; }
    :root[data-theme="dark"] { --border:#3c424c; --border-soft:#323741; --ring:rgba(255,255,255,.22); --border-control:#9aa1ac; --text-3:#b3b9c3; }
}
@media (forced-colors: active) {   /* rings are shadows, and forced colours drop shadows → real borders */
    .card, .stat, .menu, .dlg, .toast, .pal, .fsec-b, .fsec > .bd, .actionbar, .bulk, .nav-i[aria-current="page"] { border:1px solid CanvasText; }
    .badge, .btn, .seg-thumb, .tabs-ind, input, select, textarea { border:1px solid CanvasText; }
    :focus-visible { outline:2px solid Highlight !important; }
}
@media (prefers-reduced-transparency: reduce) { :root { --glass:var(--sheet); } }   /* glass → solid */

/* ── 2. base ───────────────────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing:border-box; }
html { -webkit-text-size-adjust:100%; text-size-adjust:100%; interpolate-size:allow-keywords; scroll-padding-top:calc(var(--tb-h) + 16px); }
body { margin:0; font:var(--t-body)/var(--lh-body) var(--font); color:var(--text-1); background:var(--canvas);
       -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility; font-kerning:normal; line-break:strict; }
a { color:var(--accent-text); text-decoration:none; transition:color var(--dur-1); }
a:hover { text-decoration:underline; text-underline-offset:3px; text-decoration-thickness:1px; }
h1, h2, h3, h4 { margin:0; font-weight:600; color:var(--text-1); line-height:var(--lh-head); }
h2 { font-size:var(--fs-lg); }
h3 { font-size:var(--fs-md); }
p { margin:0 0 8px; }
hr { border:0; border-top:1px solid var(--border); margin:16px 0; }
img { max-width:100%; }
:focus-visible { outline:2px solid var(--focus); outline-offset:2px; }
::placeholder { color:var(--placeholder); opacity:1; }
::selection { background:var(--accent-soft); color:var(--text-1); }
.sprite { position:absolute; width:0; height:0; overflow:hidden; }
svg.i { width:16px; height:16px; flex:none; fill:none; stroke:currentColor; stroke-width:1.75; stroke-linecap:round; stroke-linejoin:round; vertical-align:-3px; }
svg.i--sm { width:14px; height:14px; }
svg.i--lg { width:20px; height:20px; }
.num, .money, .roc, .tnum { font-variant-numeric:tabular-nums; }
kbd { display:inline-flex; align-items:center; justify-content:center; min-width:20px; height:20px; padding:0 5px;
      border-radius:var(--r-xs); background:var(--sheet); box-shadow:inset 0 0 0 1px var(--border-strong), inset 0 -1px 0 var(--border-strong);
      font:600 11.5px/1 var(--font-mono); color:var(--text-3); }
.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; }
.skip { position:absolute; left:8px; top:-48px; z-index:200; padding:8px 12px; border-radius:var(--r-sm); background:var(--accent); color:var(--on-accent); }
.skip:focus { top:8px; }
* { scrollbar-width:thin; scrollbar-color:var(--border-strong) transparent; }
*::-webkit-scrollbar { width:10px; height:10px; }
*::-webkit-scrollbar-thumb { background:var(--border-strong); border-radius:999px; border:3px solid transparent; background-clip:content-box; }
[hidden] { display:none !important; }

/* ── 3. legacy elements & classes (un-rebuilt pages stay coherent in light AND dark) ──────── */
.card { position:relative; background:var(--sheet); border-radius:var(--r-lg); box-shadow:var(--e-1); min-width:0; }
table { border-collapse:collapse; width:100%; }
th, td { text-align:left; padding:10px 14px; border-bottom:1px solid var(--border); font-size:var(--fs-md); line-height:var(--lh-ui); color:var(--text-2); }
th { background:var(--subtle); font-weight:600; color:var(--text-3); font-size:var(--fs-sm); }
td b, td strong { color:var(--text-1); }
tbody tr:last-child td { border-bottom:0; }
tr:hover td { background:var(--hover); }
.pill { display:inline-flex; align-items:center; background:var(--accent-soft); color:var(--accent-text); border-radius:var(--r-full);
        padding:2px 11px; font-size:13px; font-weight:600; line-height:1.5; box-shadow:inset 0 0 0 1px var(--accent-border); }
.muted { color:var(--text-3); font-size:var(--fs-sm); }

/* buttons (§6.1) — primary is the gradient fill; .btn.sec / .btn.sm are the legacy names */
.btn { --h:var(--h-md); position:relative; display:inline-flex; align-items:center; justify-content:center; gap:8px; min-height:var(--h);
       padding:0 16px; border:0; border-radius:var(--r-sm); background:var(--grad-primary); color:var(--on-accent);
       font:600 14.5px/1.2 var(--font); white-space:nowrap; cursor:pointer; text-decoration:none !important; user-select:none;
       box-shadow:var(--hl-top), var(--hl-ring), var(--e-1);
       transition:box-shadow var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out), background-color var(--dur-1), color var(--dur-1), opacity var(--dur-1), filter var(--dur-1); }
.btn:hover { background:var(--grad-primary-hover); color:var(--on-accent); transform:translateY(var(--m-lift-btn)); box-shadow:var(--hl-top), var(--hl-ring), var(--e-accent); }
.btn:active { transform:translateY(0) scale(var(--m-press)); background:var(--accent-press); box-shadow:var(--hl-ring); transition-duration:var(--dur-0); }
.btn:focus-visible { outline:2px solid var(--focus); outline-offset:2px; }
.btn .i { width:17px; height:17px; }
.btn:disabled, .btn[aria-disabled="true"] { opacity:.5; cursor:not-allowed; transform:none; }
.btn.sec, .btn--secondary { background:var(--sheet); color:var(--text-1); box-shadow:inset 0 0 0 1px var(--border-strong), 0 1px 2px rgba(15,23,42,.05); }
.btn.sec:hover, .btn--secondary:hover { background:var(--sheet); color:var(--text-1); box-shadow:inset 0 0 0 1px var(--border-control), var(--e-2); }
.btn.sec:active, .btn--secondary:active { background:var(--hover); box-shadow:inset 0 0 0 1px var(--border-control); }
.btn.sec .i, .btn--secondary .i { color:var(--text-2); }
.btn--ghost { background:transparent; color:var(--text-2); box-shadow:none; }
.btn--ghost:hover { background:var(--hover); color:var(--text-1); box-shadow:none; transform:none; }
.btn--ghost:active { background:var(--active); box-shadow:none; }
.btn--danger { background:var(--grad-danger); color:#fff; }
.btn--danger:hover { background:var(--grad-danger); filter:brightness(1.06); color:#fff; box-shadow:var(--hl-top), var(--e-2); }
.btn--danger:active { background:var(--danger-solid-hover); filter:none; }
.btn.sm, .btn--sm { --h:var(--h-sm); padding:0 12px; font-size:13.5px; gap:6px; }
.btn.sm .i, .btn--sm .i { width:16px; height:16px; }
.btn--lg { --h:var(--h-lg); padding:0 20px; font-size:15px; }
.btn--icon { width:var(--h); padding:0; }
.btn kbd { height:19px; min-width:19px; margin-left:2px; font-size:11px; }
.btn:not(.btn--secondary):not(.btn--ghost):not(.sec) kbd { background:rgba(255,255,255,.16); box-shadow:inset 0 0 0 1px rgba(255,255,255,.28); color:#fff; }
/* busy: the label hides (its width kept), a spinner shows; .is-done: the async-save success morph (VD20.motion.done) */
.btn[aria-busy="true"] { color:transparent !important; pointer-events:none; }
.btn[aria-busy="true"] > * { visibility:hidden; }
.btn[aria-busy="true"]::after { content:""; position:absolute; left:50%; top:50%; width:16px; height:16px; margin:-8px 0 0 -8px; border-radius:50%;
                                border:2px solid rgba(255,255,255,.4); border-top-color:#fff; animation:spin .7s linear infinite; }
.btn--secondary[aria-busy="true"]::after, .btn--ghost[aria-busy="true"]::after, .btn.sec[aria-busy="true"]::after { border-color:var(--border-strong); border-top-color:var(--text-2); }
.btn.is-done { background:var(--success-solid); box-shadow:var(--hl-top); color:transparent !important; }
.btn.is-done > * { visibility:hidden; }
.btn.is-done::after { content:""; position:absolute; left:50%; top:50%; width:12px; height:7px; margin:-6px 0 0 -6px; border:solid #fff; border-width:0 0 2.5px 2.5px;
                      transform:rotate(-45deg) scale(0); animation:check-pop var(--dur-4) var(--ease-spring) forwards; }

/* controls (§6.10) — 38 px, a real ≥3:1 edge drawn as an inset ring so it can grow into the focus edge */
input[type=text],input[type=email],input[type=date],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=month],input[type=time],input[type=url],input[type=datetime-local],input[type=week],select,textarea,.input {
    width:100%; min-height:var(--h-md); padding:6px 12px; border:0; border-radius:var(--r-sm); background-color:var(--sheet);
    box-shadow:inset 0 0 0 1px var(--border-control); color:var(--text-1); font:var(--fs-md)/1.45 var(--font);
    transition:box-shadow var(--dur-2) var(--ease-out), background-color var(--dur-1); }
/* select: the native arrow by default (it keeps clear of any page-set padding — compact table / toolbar selects);
   the shared field contexts (x-field, legacy form grids, filter pills) draw the v2 chevron with room reserved for it */
select { cursor:pointer; }
.fld select:not([multiple]):not([size]), .fgrid select:not([multiple]):not([size]), .fform select:not([multiple]):not([size]), .fsel select {
    -webkit-appearance:none; appearance:none; padding-right:34px;
    background-image:linear-gradient(45deg, transparent 50%, var(--text-3) 50%), linear-gradient(135deg, var(--text-3) 50%, transparent 50%);
    background-position:calc(100% - 17px) 52%, calc(100% - 12px) 52%; background-size:5px 5px; background-repeat:no-repeat; }
select[multiple], select[size] { padding:6px; }
textarea { min-height:96px; padding:10px 12px; resize:vertical; line-height:var(--lh-body); }
input[type=file] { font:14px var(--font); color:var(--text-2); max-width:100%; }
input[type=file]::file-selector-button { height:var(--h-sm); margin-right:10px; padding:0 12px; border:0; border-radius:var(--r-sm);
                                        background:var(--sheet); color:var(--text-1); font:600 13.5px var(--font); cursor:pointer;
                                        box-shadow:inset 0 0 0 1px var(--border-strong), 0 1px 2px rgba(15,23,42,.05); transition:background-color var(--dur-1); }
input[type=file]::file-selector-button:hover { background:var(--hover); }
input[type=file]:focus-visible { outline:2px solid var(--focus); outline-offset:2px; border-radius:var(--r-sm); }
/* numbers in a form line up like the table columns they feed (rates, counts, qty / price) */
.fld input[type=number].num, .dt input[type=number].num { text-align:right; font-variant-numeric:tabular-nums; }   /* quantities / rates, not codes or phones */
input:hover, select:hover, textarea:hover { box-shadow:inset 0 0 0 1px var(--border-control-hover); }
/* focus (M12): a solid 2 px edge straddling the control's edge (≥3:1, WCAG 2.4.13) + a soft decorative glow that eases in */
input:focus,select:focus,textarea:focus { outline:2px solid var(--focus); outline-offset:-1px; box-shadow:inset 0 0 0 1px var(--focus), 0 0 0 5px var(--focus-glow); }
input[type=radio] { accent-color:var(--accent); width:18px; height:18px; min-height:0; margin:0; vertical-align:middle; flex:none; }
input[readonly], textarea[readonly] { background-color:var(--inset); color:var(--text-2); }
input:disabled, select:disabled, textarea:disabled { opacity:.6; cursor:not-allowed; }
[aria-invalid="true"] { background-color:var(--danger-soft) !important; box-shadow:inset 0 0 0 2px var(--danger-dot) !important; }
[aria-invalid="true"]:focus { box-shadow:inset 0 0 0 2px var(--danger-dot), 0 0 0 5px rgba(229,72,77,.18) !important; }
/* checkbox — a custom box; the tick scales in with the spring (M13, transform only); the native `required` is kept */
input[type=checkbox]:not(.switch) { -webkit-appearance:none; appearance:none; position:relative; display:inline-block; width:18px; height:18px; min-height:0; margin:0; padding:0; flex:none; vertical-align:middle;
    border:0; border-radius:5px; background:var(--sheet); box-shadow:inset 0 0 0 1.5px var(--border-control); cursor:pointer;
    transition:background-color var(--dur-1), box-shadow var(--dur-2), transform var(--dur-2) var(--ease-out); }
input[type=checkbox]:not(.switch):hover { box-shadow:inset 0 0 0 1.5px var(--border-control-hover); }
input[type=checkbox]:not(.switch)::after { content:""; position:absolute; left:6px; top:2.5px; width:5px; height:9.5px; border:solid #fff; border-width:0 2px 2px 0;
    transform:rotate(45deg) scale(0); transition:transform var(--dur-2) var(--ease-in); }
input[type=checkbox]:not(.switch):checked { background:var(--accent); box-shadow:inset 0 0 0 1px var(--accent-press); }
input[type=checkbox]:not(.switch):checked::after { transform:rotate(45deg) scale(1); transition:transform var(--dur-4) var(--ease-spring); }
input[type=checkbox]:not(.switch):indeterminate { background:var(--accent); box-shadow:none; }
input[type=checkbox]:not(.switch):indeterminate::after { left:4px; top:7.5px; width:10px; height:0; border-width:0 0 2px 0; transform:none; }
input[type=checkbox]:not(.switch):active { transform:scale(.9); transition-duration:var(--dur-0); }
input[type=checkbox]:not(.switch):disabled { background:var(--inset); }
input[type=checkbox]:not(.switch):disabled:checked { background:var(--text-4); box-shadow:none; }
input[type=checkbox]:focus-visible { outline:2px solid var(--focus); outline-offset:2px; }
input[type=checkbox]:not(.switch):focus { box-shadow:inset 0 0 0 1.5px var(--focus); }
input[type=checkbox]:not(.switch):checked:focus { box-shadow:inset 0 0 0 1px var(--accent-press); }
input[type=checkbox]:focus:not(:focus-visible), input[type=radio]:focus:not(:focus-visible) { outline-color:transparent; }
/* switch — the knob springs; it stretches while pressed (M14) */
input.switch { -webkit-appearance:none; appearance:none; position:relative; display:inline-block; width:42px; height:24px; min-height:0; margin:0; padding:0; flex:none; border:0; border-radius:var(--r-full);
               background:var(--border-control); box-shadow:none; cursor:pointer; vertical-align:middle; transition:background-color var(--dur-2) var(--ease-std); }
input.switch::after { content:""; position:absolute; left:3px; top:3px; width:18px; height:18px; border-radius:var(--r-full); background:#fff;
                      box-shadow:0 1px 3px rgba(0,0,0,.25); transform-origin:left center; transition:transform var(--dur-4) var(--ease-spring); }
input.switch:checked { background:var(--accent); }
input.switch:checked::after { transform:translateX(18px); }
input.switch:active::after { transform:scaleX(1.18); }
input.switch:checked:active::after { transform:translateX(18px) scaleX(1.18); transform-origin:right center; }
input.switch:focus { box-shadow:none; }
input.switch:focus-visible { outline:2px solid var(--focus); outline-offset:2px; }
input.switch:focus:not(:focus-visible) { outline-color:transparent; }
input.switch:disabled { opacity:.5; }
label { display:block; font-size:var(--fs-label); color:var(--text-2); margin-bottom:6px; }
.grid { display:grid; gap:16px; }

/* tabs (§6.8) — the underline is ONE indicator element that slides (transform), never a per-tab border.
   Without JS the active tab draws its own 2 px bar; app.js (.has-ind) swaps it for the sliding .tabs-ind */
.tabs { position:relative; display:flex; gap:2px; border-bottom:1px solid var(--border); margin-bottom:20px; overflow-x:auto; scrollbar-width:none; }
/* a strip that scrolls fades at the edge that hides more tabs (app.js toggles .is-fade-s / .is-fade-e; alpha-only mask, no colour) */
.tabs.is-fade-e { -webkit-mask-image:linear-gradient(90deg, #000 calc(100% - 32px), transparent); mask-image:linear-gradient(90deg, #000 calc(100% - 32px), transparent); }
.tabs.is-fade-s { -webkit-mask-image:linear-gradient(90deg, transparent, #000 32px); mask-image:linear-gradient(90deg, transparent, #000 32px); }
.tabs.is-fade-s.is-fade-e { -webkit-mask-image:linear-gradient(90deg, transparent, #000 32px, #000 calc(100% - 32px), transparent);
                            mask-image:linear-gradient(90deg, transparent, #000 32px, #000 calc(100% - 32px), transparent); }
.tabs a, .tabs .tab { position:relative; display:inline-flex; align-items:center; gap:8px; min-height:46px; padding:0 14px; border:0; background:none;
                      font:var(--fs-md)/1 var(--font); color:var(--text-2); white-space:nowrap; cursor:pointer; border-radius:var(--r-sm) var(--r-sm) 0 0;
                      transition:color var(--dur-1), background-color var(--dur-1); }
.tabs a:hover, .tabs .tab:hover { color:var(--text-1); background:var(--hover); text-decoration:none; }
.tabs a.active, .tabs .is-active, .tabs [aria-current="page"], .tabs [aria-selected="true"] { color:var(--text-1); font-weight:600; }
.tabs a.active::after, .tabs .is-active::after, .tabs [aria-current="page"]::after, .tabs [aria-selected="true"]::after {
    content:""; position:absolute; left:10px; right:10px; bottom:0; height:2px; border-radius:2px; background:var(--accent); }
.tabs.has-ind a.active::after, .tabs.has-ind .is-active::after, .tabs.has-ind [aria-current="page"]::after, .tabs.has-ind [aria-selected="true"]::after { display:none; }
.tabs-ind { position:absolute; left:0; bottom:0; width:100px; height:2px; border-radius:2px; background:var(--accent); pointer-events:none;
            transform-origin:left center; transform:translateX(var(--ind-x, 0px)) scaleX(var(--ind-s, 0));
            transition:transform var(--dur-3) var(--ease-out); }
.tabs--page .tabs-ind { view-transition-name:vt-tab-ind; }   /* ONE named strip per page (a duplicate name aborts a VT) */
.tabs-ind.no-anim { transition:none; }
.tab-n { min-width:26px; height:22px; padding:0 8px; display:inline-grid; place-items:center; border-radius:var(--r-full); background:var(--neutral-soft);
         font-size:12.5px; font-weight:600; color:var(--text-3); font-variant-numeric:tabular-nums; transition:background-color var(--dur-1), color var(--dur-1); }
.tabs a.active .tab-n, .tabs .is-active .tab-n, .tabs [aria-current="page"] .tab-n, .tabs [aria-selected="true"] .tab-n { background:var(--accent-soft); color:var(--accent-text); }
[role="tabpanel"]:not([hidden]) { animation:panel-in var(--dur-3) var(--ease-out) backwards; }
.flash { display:flex; gap:10px; padding:12px 16px; margin-bottom:16px; border-radius:var(--r-md); box-shadow:inset 0 0 0 1px var(--success-border);
         background:var(--success-soft); color:var(--text-1); font-size:14.5px; line-height:var(--lh-body); }
.flash.err { background:var(--danger-soft); color:var(--text-1); box-shadow:inset 0 0 0 1px var(--danger-border); }
.money-pos { color:var(--money-pos); }
.money-neg { color:var(--money-neg); }
/* <x-field> (AUD-115): the field's own validation message, linked via aria-describedby; it rises in (err-in) */
.field-err { display:flex; gap:6px; align-items:flex-start; color:var(--danger); font-size:var(--fs-sm); line-height:1.55; margin-top:8px;
             animation:err-in var(--dur-3) var(--ease-out) backwards; }
.field-err::before { content:""; flex:none; width:15px; height:15px; margin-top:2px; background:currentColor;
    -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 7.5v5M12 16.2h.01'/%3E%3C/svg%3E") center/contain no-repeat;
            mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 7.5v5M12 16.2h.01'/%3E%3C/svg%3E") center/contain no-repeat; }
label.inline { display:inline-flex; align-items:center; gap:8px; margin:0; font-size:14.5px; color:var(--text-1); }
td label.inline { font-size:inherit; color:inherit; }
label.inline.nowrap { white-space:nowrap; }
label.inline.nowrap input[type=number] { width:120px; }
fieldset.plain { border:0; margin:0; padding:0; min-width:0; }
/* toolbars wrap instead of overflowing a 375px phone (AUD-117) */
.toolbar { display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
.toolbar.between { justify-content:space-between; }
/* sectioned master-record forms (legacy .fform + .fsec > .hd / .bd share the panel section) */
.fform { max-width:1200px; }
.fform-head { display:flex; align-items:flex-end; justify-content:space-between; gap:12px; flex-wrap:wrap; margin-bottom:20px; }
.fsec > .hd { display:flex; gap:12px; align-items:flex-start; padding-top:6px; }
.fsec > .hd .ic { width:34px; height:34px; flex:none; border-radius:var(--r-md); display:grid; place-items:center; background:var(--subtle); color:var(--text-2);
                  box-shadow:inset 0 0 0 1px var(--border); }
.fsec > .hd .ti { font-weight:600; font-size:var(--fs-lg); line-height:1.4; color:var(--text-1); }
.fsec > .hd .sub { color:var(--text-3); font-size:14px; line-height:var(--lh-body); margin-top:8px; }
.fform label { font-weight:600; }
.fform input, .fform select { width:100%; }
.fform .hint { margin-top:8px; }
.fcheck { display:flex; align-items:center; gap:10px; padding:8px 12px; border-radius:var(--r-sm); background:var(--subtle); box-shadow:inset 0 0 0 1px var(--border); height:100%; }
.fcheck input { width:auto; flex:none; margin:0; }
.fcheck label { margin:0; font-weight:400; }
.fsubhd { display:flex; align-items:center; gap:8px; margin:4px 0 -2px; font-size:var(--fs-sm); font-weight:600; color:var(--text-2); grid-column:1 / -1; }
.factions { display:flex; gap:10px; align-items:center; position:sticky; bottom:0; padding:14px 0 4px; margin-top:4px;
            background:linear-gradient(transparent, var(--sheet) 55%); z-index:var(--z-sticky); }

/* ── 4. shell (§5) ─────────────────────────────────────────────────────────────────────── */
.app { min-height:100vh; padding-left:var(--sb-w); display:flow-root; }
.sb { position:fixed; top:0; bottom:0; left:0; z-index:var(--z-sidebar); width:var(--sb-w); display:flex; flex-direction:column;
      padding:8px 12px 10px; background:var(--sb-bg); box-shadow:inset -1px 0 0 var(--border); view-transition-name:vt-sidebar; }
.sb-brand { display:flex; align-items:center; gap:4px; height:52px; margin-bottom:10px; flex:none; }
.brand { display:flex; align-items:center; gap:10px; min-width:0; flex:1; padding:6px; border-radius:var(--r-md); color:var(--text-1);
         transition:background-color var(--dur-1); }
.brand:hover { background:var(--hover); text-decoration:none; }
/* the 高竿 C-mark (brand/mark.png 38 × 32, @2x/@4x — BRAND-SPEC §4.1): the sidebar brand in both widths and both themes
   (blue 5.27 / green 6.59 on the dark sidebar, no wordmark to recolour). Never recoloured, stretched or shadowed. */
.brand-logo { display:block; width:38px; height:32px; flex:none; object-fit:contain; }
/* the school's one-letter mark (<x-brand-mark>): a gradient square, the letter in white (decorative) — kept as the
   letter fallback of the sign-in / error shells until their brand stage lands */
.brand-mark { position:relative; width:32px; height:32px; flex:none; display:grid; place-items:center; border-radius:9px; background:var(--grad-brand);
              color:#fff; font:700 16px/1 var(--font); box-shadow:var(--hl-top), 0 1px 2px rgba(0,94,143,.35), 0 4px 10px -3px rgba(0,146,216,.45); }
.brand-text { min-width:0; line-height:1.25; }
.brand-name { display:block; font-size:15px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.brand-sub { display:block; font-size:12.5px; color:var(--text-3); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.sb-nav { flex:1; min-height:0; overflow-y:auto; overscroll-behavior:contain; margin:0 -12px; padding:0 12px 12px; scrollbar-gutter:stable; }
.nav-pin { display:grid; gap:2px; padding-bottom:12px; margin-bottom:12px; border-bottom:1px solid var(--border); }
.nav-grp + .nav-grp { margin-top:4px; }
.nav-g { display:flex; align-items:center; gap:10px; width:100%; height:40px; padding:0 10px; border:0; background:none; border-radius:var(--r-sm);
         font:600 var(--fs-nav)/1 var(--font); color:var(--text-1); cursor:pointer; text-align:left; transition:background-color var(--dur-1); }
.nav-g:hover { background:var(--hover); }
.nav-g .nav-gi { color:var(--text-3); width:17px; height:17px; transition:color var(--dur-1); }
.nav-grp.has-active > .nav-g .nav-gi { color:var(--accent-text); }
.nav-gl { flex:1; min-width:0; }
.nav-caret { width:15px; height:15px; color:var(--text-4); transition:transform var(--dur-3) var(--ease-out); }
.nav-grp:not(.is-open) > .nav-g .nav-caret { transform:rotate(-90deg); }
/* group disclosure (M16): the height animates via interpolate-size — contained, one of the two layout animations;
   the padding lives on the inner wrapper so height:0 really is 0; a closed group leaves the tab order (visibility) */
/* the paint clip is pushed 6 px out on both sides (margin/padding-inline) so the active chip's ring, the accent bar
   and the 2 px focus outline (offset 2) of a full-width item are not cut flat at the group's edges */
.nav-gp { height:0; overflow:clip; contain:layout paint; visibility:hidden; margin-inline:-6px; padding-inline:6px;
          transition:height var(--dur-4) var(--ease-out), visibility 0s linear var(--dur-4); }
.nav-grp.is-open > .nav-gp { height:auto; visibility:visible; transition:height var(--dur-4) var(--ease-out), visibility 0s; }
.nav-gp-in { padding:2px 0 10px; }
.nav-fly-t { display:none; }
.nav-sub { padding:12px 10px 4px 37px; font-size:12.5px; font-weight:600; line-height:1.3; color:var(--text-3); }
.nav-i { position:relative; display:flex; align-items:center; gap:10px; height:36px; padding:0 10px 0 37px; border-radius:var(--r-sm);
         font-size:var(--fs-nav); color:var(--text-2); white-space:nowrap;
         transition:background-color var(--dur-1), color var(--dur-1), box-shadow var(--dur-2); }
.nav-pin .nav-i { padding-left:10px; height:38px; }
.nav-i .i { width:17px; height:17px; color:var(--text-3); transition:color var(--dur-1); }
.nav-i:hover { background:var(--hover); color:var(--text-1); text-decoration:none; }
.nav-i:active { background:var(--active); }
/* active = a raised chip + an accent bar that GLIDES between items on navigation (view transition, M02) */
.nav-i.is-active, .nav-i[aria-current="page"] { background:var(--nav-active); color:var(--text-1); font-weight:600; box-shadow:var(--e-1); }
.nav-i.is-active .i, .nav-i[aria-current="page"] .i { color:var(--accent-text); }
.nav-ind { position:absolute; left:0; top:10px; bottom:10px; width:3px; border-radius:0 3px 3px 0; background:var(--accent); view-transition-name:vt-nav-ind; }
.nav-l { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; }
.nav-c { margin-left:auto; min-width:24px; height:22px; padding:0 7px; display:inline-grid; place-items:center; border-radius:var(--r-full);
         font-size:12.5px; font-weight:600; line-height:1; color:var(--text-3); background:var(--neutral-soft); font-variant-numeric:tabular-nums; }
.nav-c.is-hot { color:var(--danger); background:var(--danger-soft); }
.nav-fold { display:flex; align-items:center; gap:8px; width:100%; height:32px; padding:0 10px 0 33px; border:0; background:none; border-radius:var(--r-sm);
            font:600 12.5px/1 var(--font); color:var(--text-3); cursor:pointer; text-align:left; transition:background-color var(--dur-1), color var(--dur-1); }
.nav-fold:hover { color:var(--text-1); background:var(--hover); }
.nav-fold .nav-caret { width:13px; height:13px; }
.nav-fold > span:not(.nav-c) { flex:1; min-width:0; }
.nav-fg:not(.is-open) .nav-fold .nav-caret { transform:rotate(-90deg); }
.nav-fg:not(.is-open) .nav-fi { display:none; }
.nav-fold .nav-c { font-weight:600; }
/* JS off: the disclosure buttons cannot toggle, so every group (and fold) is simply open — the whole menu stays reachable */
@media (scripting: none) {
    .nav-gp { height:auto; visibility:visible; }
    .nav-fg:not(.is-open) .nav-fi { display:block; }
    .nav-grp > .nav-g .nav-caret, .nav-fold .nav-caret { display:none; }
}
.sb-foot { flex:none; display:grid; gap:2px; padding-top:10px; margin:0 -12px; padding-left:12px; padding-right:12px; border-top:1px solid var(--border); }
.sb-help { display:flex; align-items:center; gap:10px; width:100%; height:36px; padding:0 10px; border:0; background:none; border-radius:var(--r-sm);
           font:14px/1 var(--font); color:var(--text-3); cursor:pointer; transition:background-color var(--dur-1), color var(--dur-1); }
.sb-help:hover { background:var(--hover); color:var(--text-1); }
.sb-help kbd { margin-left:auto; }
.sb-scrim { display:none; }
.sb-collapse .i { transition:transform var(--dur-3) var(--ease-out); }

/* collapsed rail (64 px) + flyout — toggled inside a same-document view transition (M17), never a width transition */
html.rail .app { padding-left:var(--rail-w); }
html.rail .sb { width:var(--rail-w); padding-left:12px; padding-right:12px; align-items:center; }
html.rail .sb-brand { flex-direction:column; height:auto; gap:8px; }
html.rail .brand { flex:none; padding:6px 1px; }   /* the 38 px mark centred in the 64 px rail (13 px each side) */
html.rail .brand-text, html.rail .nav-gl, html.rail .nav-grp > .nav-g .nav-caret, html.rail .nav-l, html.rail .nav-pin .nav-c,
html.rail .sb-help-t, html.rail .sb-help kbd { display:none; }
html.rail .sb-nav { width:100%; overflow:visible; margin:0; padding:0; }
html.rail .nav-pin { display:flex; flex-direction:column; align-items:center; gap:2px; }
html.rail .nav-i, html.rail .nav-g, html.rail .sb-help { width:40px; height:40px; padding:0; justify-content:center; margin:0 auto; }
html.rail .nav-grp.has-active > .nav-g { background:var(--nav-active); box-shadow:var(--e-1); }
html.rail .nav-grp > .nav-gp { display:none; }
html.rail .nav-grp.is-fly > .nav-gp { display:block; position:fixed; left:calc(var(--rail-w) + 6px); top:60px; width:264px; height:auto; max-height:calc(100vh - 80px);
    visibility:visible; overflow-y:auto; contain:none; margin:0; padding:6px; background:var(--raised); border-radius:var(--r-lg); box-shadow:var(--e-3);
    z-index:var(--z-dropdown); animation:pop-in var(--dur-2) var(--ease-out) backwards; transform-origin:left top; }
html.rail .nav-grp.is-fly .nav-gp-in { padding:0; }
html.rail .nav-grp.is-fly .nav-fly-t { display:block; padding:8px 10px 6px; font-size:var(--fs-nav); font-weight:600; color:var(--text-1); }
html.rail .nav-grp.is-fly .nav-sub { padding-left:10px; }
html.rail .nav-grp.is-fly .nav-i { width:auto; height:36px; padding:0 10px; justify-content:flex-start; margin:0; }
html.rail .nav-grp.is-fly .nav-l { display:block; }
html.rail .nav-grp.is-fly .nav-fold { padding-left:6px; }
html.rail .sb-foot { border-top:0; }
html.rail .sb-collapse .i { transform:rotate(180deg); }

/* the sheet is FLUSH and white (no inset gap, radius or ring); the dashboard adds a soft accent glow (.has-glow) */
.sheet { position:relative; margin:0; min-width:0; min-height:100vh;
         background:var(--sheet); border-radius:0; box-shadow:none; display:flex; flex-direction:column; isolation:isolate; }
.sheet::before { content:""; position:absolute; inset:0 0 auto 0; height:320px; border-radius:0; background:var(--grad-glow);
                 pointer-events:none; z-index:-1; opacity:0; transition:opacity var(--dur-4); }
.sheet.has-glow::before { opacity:1; }
/* the glass topbar: 60 px, bottom hairline, left padding = the gutter */
.tb { position:sticky; top:0; z-index:var(--z-topbar); display:flex; align-items:center; gap:6px; min-height:var(--tb-h);
      padding:0 16px 0 var(--gutter); background:var(--glass); -webkit-backdrop-filter:saturate(1.6) blur(14px); backdrop-filter:saturate(1.6) blur(14px);
      border-bottom:1px solid var(--border); border-radius:0; view-transition-name:vt-topbar; }
/* the top bar's brand accent: the manual's 色彩光譜 as a 2 px band along its top edge (BRAND-SPEC §5.1 "a soft wide
   bar only") — decorative chrome, never behind data; dimmed on the dark canvas, gone in forced colours and print */
.tb::before { content:""; position:absolute; left:0; right:0; top:0; height:2px; background:var(--gg-spectrum); pointer-events:none; }
:root[data-theme="dark"] .tb::before { opacity:.5; }
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))) { .tb { background:var(--sheet); } }
@media (prefers-reduced-transparency: reduce) { .tb { background:var(--sheet); -webkit-backdrop-filter:none; backdrop-filter:none; } }
.btn.tb-burger { display:none; }
.crumbs { min-width:0; }
.crumbs ol { display:flex; align-items:center; list-style:none; margin:0; padding:0; min-width:0; font-size:14.5px; white-space:nowrap; }
.crumbs li { display:flex; align-items:center; min-width:0; }
.crumbs li + li::before { content:""; width:6px; height:6px; margin:0 12px 0 8px; border-top:1.5px solid var(--text-4); border-right:1.5px solid var(--text-4); transform:rotate(45deg); flex:none; }
.crumbs .crumb-g { color:var(--text-3); }
.crumbs a { color:var(--text-2); border-radius:var(--r-xs); }
.crumbs a:hover { color:var(--text-1); text-decoration:none; }
.crumbs .crumb-back { display:none; }
.crumbs [aria-current="page"] { color:var(--text-1); font-weight:600; overflow:hidden; text-overflow:ellipsis; }
.crumbs a .crumb-back { width:16px; height:16px; margin:0 2px 0 -4px; }
.tb-sp { flex:1; }
.tb-search { display:flex; align-items:center; gap:10px; width:340px; height:40px; padding:0 8px 0 14px; margin-right:6px;
             border:0; border-radius:var(--r-md); background:var(--subtle); box-shadow:inset 0 0 0 1px var(--border); color:var(--text-3);
             font:14px/1 var(--font); cursor:pointer; transition:box-shadow var(--dur-2), color var(--dur-1), background-color var(--dur-1); }
.tb-search:hover { box-shadow:inset 0 0 0 1px var(--border-strong), var(--e-1); color:var(--text-2); background:var(--sheet); }
.tb-search .i { width:17px; height:17px; }
.tb-search-t { flex:1; text-align:left; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.tb-kbd { display:flex; gap:3px; }
.tb-div { width:1px; height:22px; background:var(--border); margin:0 6px; }
.tb-user { display:flex; align-items:center; gap:10px; height:44px; padding:0 8px 0 4px; border:0; background:none; border-radius:var(--r-md);
           color:var(--text-1); font:14px/1.25 var(--font); cursor:pointer; text-align:left; transition:background-color var(--dur-1); }
.tb-user:hover { background:var(--hover); }
.tb-user-n { min-width:0; max-width:180px; }
.tb-user-n b { display:block; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.tb-user-n span { display:block; font-size:13px; color:var(--text-3); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.tb-caret { width:14px; height:14px; color:var(--text-4); }
.tb-theme .i { transition:transform var(--dur-4) var(--ease-out); }
.tb-theme:active .i { transform:rotate(-30deg) scale(.9); }
.banners { padding:16px var(--gutter) 0; max-width:1440px; width:100%; margin:0 auto; display:grid; gap:8px; }

/* page scaffolding */
.page { width:100%; max-width:1440px; margin:0 auto; padding:32px var(--gutter) 56px; view-transition-name:vt-page; }
.page:focus { outline:2px solid transparent; }
.page--form { max-width:1200px; }
.page--prose { max-width:880px; }
.page--fluid { max-width:none; }

/* page header (§6.2) */
.ph { display:flex; align-items:flex-end; justify-content:space-between; gap:16px 24px; margin-bottom:28px; flex-wrap:wrap; }
.ph-text { min-width:0; }
.ph-eyebrow { display:flex; align-items:center; flex-wrap:wrap; gap:4px 8px; font-size:var(--fs-sm); color:var(--text-3); margin-bottom:8px; }
.ph-eyebrow .i { width:14px; height:14px; }
.ph-eyebrow-sep { width:3px; height:3px; border-radius:50%; background:var(--text-4); flex:none; }
.ph-title { font:600 var(--t-h1)/1.35 var(--font-display); color:var(--text-1); }
.ph-meta { margin:8px 0 0; font-size:14.5px; line-height:var(--lh-body); color:var(--text-3); max-width:68ch; }
.ph-meta b { color:var(--text-1); font-weight:600; }
.ph-actions { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.ph--legacy { margin-bottom:20px; }
.ph-title-row { display:flex; align-items:center; flex-wrap:wrap; gap:4px 12px; }
.ph-title-row .ph-status { display:inline-flex; align-items:center; }

/* ── 5. components ─────────────────────────────────────────────────────────────────────── */
/* card (§6.4) — the ring is the edge (no border); 58 px header */
.card-h { display:flex; align-items:center; gap:10px 12px; min-height:58px; padding:10px 16px 10px 20px; border-bottom:1px solid var(--border); flex-wrap:wrap; }
.card-t { font:600 var(--t-h2)/1.45 var(--font); color:var(--text-1); }
.card-sub { font-size:var(--fs-sm); color:var(--text-3); }
.card-x { margin-left:auto; display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.card-b { padding:20px; }
.card--flush > .card-b { padding:0; }
.card-f { display:flex; align-items:center; justify-content:space-between; gap:8px; padding:12px 20px; border-top:1px solid var(--border);
          background:var(--subtle); font-size:var(--fs-sm); color:var(--text-3); border-radius:0 0 var(--r-lg) var(--r-lg); }
.card--link { display:block; color:inherit; transition:transform var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out); }
.card--link:hover, .card--link:focus-visible { text-decoration:none; transform:translateY(var(--m-lift)); box-shadow:var(--e-2); }
/* link-more (M36): a tinted pill whose chevron nudges on hover */
.link-more { display:inline-flex; align-items:center; gap:2px; height:32px; padding:0 6px 0 10px; border-radius:var(--r-sm); font-size:var(--fs-sm); font-weight:600; color:var(--text-2);
             transition:background-color var(--dur-1), color var(--dur-1); }
.link-more .i { width:14px; height:14px; transition:transform var(--dur-2) var(--ease-out); }
.link-more:hover { color:var(--accent-text); background:var(--accent-softer); text-decoration:none; }
.link-more:hover .i { transform:translateX(2px); }

/* stat tiles (§6.3) — gradient tile, 34 px icon tile, 30 px value, optional sparkline */
.stats { display:grid; grid-template-columns:repeat(4, minmax(0,1fr)); gap:16px; margin-bottom:20px; }
.stat { position:relative; display:flex; flex-direction:column; padding:18px 20px; background:var(--grad-tile); border-radius:var(--r-lg); color:inherit;
        box-shadow:var(--e-1); min-width:0; overflow:hidden; isolation:isolate; }
.stat--link { transition:transform var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out); }
.stat--link:hover, .stat--link:focus-visible { text-decoration:none; transform:translateY(var(--m-lift)); box-shadow:var(--e-2); }
.stat-l { display:flex; align-items:center; gap:10px; font-size:14px; color:var(--text-2); min-width:0; }
.stat-l > span:not(.stat-ic) { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.stat-l > .i { width:16px; height:16px; color:var(--text-3); }
.stat-ic { width:34px; height:34px; flex:none; display:grid; place-items:center; border-radius:var(--r-sm); background:var(--sheet); color:var(--text-2);
           box-shadow:inset 0 0 0 1px var(--border), 0 1px 2px rgba(15,23,42,.04); }
.stat-ic .i { width:18px; height:18px; }
.stat-go { margin-left:auto; color:var(--text-4); opacity:0; transform:translateX(-4px); transition:opacity var(--dur-2), transform var(--dur-2) var(--ease-out); }
.stat--link:hover .stat-go, .stat--link:focus-visible .stat-go { opacity:1; transform:none; color:var(--accent-text); }
.stat-v { margin-top:14px; font:600 var(--t-display)/1.1 var(--font-display); color:var(--text-1); font-variant-numeric:tabular-nums; letter-spacing:-.01em; overflow-wrap:break-word; }
.stat-v.is-neg { color:var(--money-neg); }
.stat-v.is-pos { color:var(--money-pos); }
.stat-v.is-warn { color:var(--warning); }
.stat-v.is-zero { color:var(--text-3); }   /* a real 0 (x-stat) reads quiet, like x-money 0 */
.stat-u { font:400 15px/1 var(--font); color:var(--text-3); margin-right:4px; letter-spacing:0; }
.stat-u--after { margin:0 0 0 5px; }
.stat-f { display:flex; flex-wrap:wrap; align-items:center; gap:8px; margin-top:12px; font-size:var(--fs-sm); color:var(--text-3); }
.delta { display:inline-flex; align-items:center; gap:2px; height:22px; padding:0 8px; border-radius:var(--r-full); font-size:12.5px; font-weight:600; font-variant-numeric:tabular-nums; }
.delta--good { color:var(--success); background:var(--success-soft); }
.delta--bad { color:var(--danger); background:var(--danger-soft); }
.spark { position:absolute; right:18px; top:64px; width:96px; height:36px; overflow:visible; z-index:-1; pointer-events:none; }
.spark .spark-a { fill:var(--series-1-soft); stroke:none; }
.spark .spark-l { fill:none; stroke:var(--series-1); stroke-width:1.75; stroke-linecap:round; stroke-linejoin:round; vector-effect:non-scaling-stroke; }
.spark .spark-d { fill:var(--sheet); stroke:var(--series-1); stroke-width:1.75; vector-effect:non-scaling-stroke; }
.stat--neg .spark .spark-l, .stat--neg .spark .spark-d { stroke:var(--money-neg); }
.stat--neg .spark .spark-a { fill:var(--danger-soft); }
/* stat variants + tile rows */
.stats--3 { grid-template-columns:repeat(3, minmax(0,1fr)); }
.stats--2 { grid-template-columns:repeat(2, minmax(0,1fr)); }
.stat--compact { padding:12px 16px; }
.stat--compact .stat-v { margin-top:6px; font-size:22px; }
.stat--compact .stat-f { margin-top:6px; }
.stat--plain { box-shadow:none; border-radius:0; background:transparent; }
.stats--strip { display:flex; gap:0; padding:0; background:var(--sheet); border-radius:var(--r-lg); box-shadow:var(--e-1); }
.stats--strip > .stat { flex:1 1 0; min-width:0; }
.stats--strip > .stat + .stat { box-shadow:inset 1px 0 0 var(--border); }
/* meters: native <progress> (no style="") — the fill grows on load (M09) */
progress.meter { -webkit-appearance:none; appearance:none; display:inline-block; width:64px; height:6px; border:0; border-radius:var(--r-full); background:var(--active);
                 vertical-align:middle; overflow:hidden; }
progress.meter::-webkit-progress-bar { background:var(--active); border-radius:var(--r-full); }
progress.meter::-webkit-progress-value { background:var(--accent); border-radius:var(--r-full); transform-origin:left center; }
progress.meter::-moz-progress-bar { background:var(--accent); border-radius:var(--r-full); transform-origin:left center; }
progress.meter.is-low::-webkit-progress-value { background:var(--warning-dot); }
progress.meter.is-low::-moz-progress-bar { background:var(--warning-dot); }
progress.meter--wide { width:100%; height:8px; }
.meter-row { display:inline-flex; align-items:center; gap:10px; }
.meter-t { font-size:var(--fs-sm); color:var(--text-2); font-variant-numeric:tabular-nums; min-width:44px; }

/* data table (§6.5) */
.card--table { overflow:clip; }
.dt-wrap { min-width:0; }
.card--table.is-contained .dt-wrap { overflow:auto; max-height:calc(100dvh - 220px); }
.dt { width:100%; border-collapse:separate; border-spacing:0; font-size:var(--t-body); }
.dt th { position:sticky; top:var(--tb-h); z-index:var(--z-sticky); height:44px; padding:0 14px; text-align:left; white-space:nowrap;
         background:var(--subtle); border-bottom:1px solid var(--border); font-size:var(--fs-sm); font-weight:600; color:var(--text-3); }
.card--table.is-contained .dt th, .card .card .dt th, .dt.is-static th, .chart .dt th, .card--flush .dt th { top:0; }
.card--flush .dt th { position:static; }
/* a titled table: <x-table> inside <x-card title="…" flush> (report output, a record's tab) — the table's own
   card frame folds into the host card (also on a phone, where a bare table bleeds to the page edge) */
.card-b > .card--table { margin:0; border:0; border-radius:0; box-shadow:none; background:transparent; }
.dt td { height:var(--row); padding:8px 14px; border-bottom:1px solid var(--border); color:var(--text-2); line-height:var(--lh-ui); font-size:var(--fs-md); }
.dt th:first-child, .dt td:first-child { padding-left:20px; }
.dt th:last-child, .dt td:last-child { padding-right:20px; }
.dt tbody tr:last-child td { border-bottom:0; }
.dt tbody tr { transition:background-color var(--dur-1); }
.dt tbody tr:hover td { background:var(--hover); }
.dt tbody tr.is-selected td { background:var(--accent-softer); }
.dt tbody tr.is-selected td:first-child { box-shadow:inset 3px 0 0 var(--accent); }
.dt tr[data-href] { cursor:pointer; }
.dt td.num, .dt th.num, .dt td.money { text-align:right; }
.dt .num, .dt .money { font-variant-numeric:tabular-nums; }
.dt .roc, .dt .code-t { color:var(--text-3); font-variant-numeric:tabular-nums; }
.dt .dt-name { color:var(--text-1); font-weight:600; }
.dt .en { margin-left:8px; font-size:var(--fs-sm); color:var(--text-3); }
.dt .sub { display:none; margin-top:2px; font-size:12.5px; color:var(--text-3); }
.dt .nil { color:var(--text-3); }   /* AA: .nil also carries words (（無）, 未設定, 不限 …), not just the decorative dash */
/* trap 3: .dt td{color} must not beat a negative amount */
.dt .money--neg, .dt td.money--neg { color:var(--money-neg); } .dt .money--pos, .dt td.money--pos { color:var(--money-pos); } .dt .money--zero, .dt td.money--zero { color:var(--text-3); }
.dt .c-chk { width:44px; }
.dt .c-act { width:56px; padding-left:4px; padding-right:12px; text-align:right; }
/* row actions reveal (M37): ⋯ fades and slides in on row hover / focus-within; always visible on touch */
.dt .c-act .menu-trigger { opacity:0; transform:translateX(4px); transition:opacity var(--dur-2), transform var(--dur-2) var(--ease-out), background-color var(--dur-1); }
.dt tr:hover .c-act .menu-trigger, .dt tr:focus-within .c-act .menu-trigger, .dt .c-act .menu-trigger[aria-expanded="true"] { opacity:1; transform:none; }
@media (hover:none) { .dt .c-act .menu-trigger { opacity:1; transform:none; } }
.dt th[aria-sort] { color:var(--text-1); }
.dt tfoot td { border-top:1px solid var(--border-strong); border-bottom:0; font-weight:600; color:var(--text-1); background:var(--subtle); }
.dt.is-compact td, html[data-density="compact"] .dt td { height:var(--row-compact); padding-top:4px; padding-bottom:4px; font-size:14px; }
.sort { display:inline-flex; align-items:center; gap:4px; color:inherit; }
.sort .i { width:12px; height:12px; }
.sort:hover { color:var(--text-1); text-decoration:none; }
.dt-pager { padding:10px 16px 10px 20px; }
.phone-mask { display:inline-flex; align-items:center; gap:6px; color:var(--text-2); font-variant-numeric:tabular-nums; }

/* pager (resources/views/pagination/*, §6.6) */
.pager { display:flex; align-items:center; justify-content:space-between; gap:8px 14px; flex-wrap:wrap; width:100%; font-size:var(--fs-sm); color:var(--text-3); }
.pager-info { color:var(--text-3); font-variant-numeric:tabular-nums; }
.pager-links { display:flex; flex-wrap:wrap; gap:4px; list-style:none; margin:0; padding:0; }
.pager .pg { display:inline-flex; align-items:center; justify-content:center; min-width:34px; height:34px; padding:0 10px; border-radius:var(--r-sm);
             color:var(--text-2); white-space:nowrap; font-variant-numeric:tabular-nums; transition:background-color var(--dur-1), color var(--dur-1); }
.pager a.pg:hover { background:var(--hover); color:var(--text-1); text-decoration:none; }
.pager .pg.cur { background:var(--sheet); color:var(--text-1); font-weight:600; box-shadow:inset 0 0 0 1px var(--border-strong), 0 1px 2px rgba(15,23,42,.06); }
.pager .pg.disabled { color:var(--text-4); }
.pager .pg.gap { min-width:20px; padding:0 2px; color:var(--text-4); }
.pager-links li.pg-of { display:none; }
.pager-links li.pg-of .pg { color:var(--text-2); }

/* badge + chip (§6.11) */
.badge { display:inline-flex; align-items:center; gap:7px; height:26px; padding:0 11px 0 10px; border-radius:var(--r-full); font-size:13px; font-weight:600;
         line-height:1; white-space:nowrap; background:var(--neutral-soft); color:var(--neutral-text); box-shadow:inset 0 0 0 1px var(--ring); vertical-align:middle; }
.badge::before { content:""; width:7px; height:7px; border-radius:50%; background:var(--text-4); flex:none; }
.badge--plain::before { display:none; }
.badge--plain { padding:0 11px; }
.badge--ok { background:var(--success-soft); color:var(--success); box-shadow:inset 0 0 0 1px var(--success-border); } .badge--ok::before { background:var(--success-dot); }
.badge--info { background:var(--accent-soft); color:var(--accent-text); box-shadow:inset 0 0 0 1px var(--accent-border); } .badge--info::before { background:var(--accent); }
.badge--warn { background:var(--warning-soft); color:var(--warning); box-shadow:inset 0 0 0 1px var(--warning-border); } .badge--warn::before { background:var(--warning-dot); }
.badge--bad { background:var(--danger-soft); color:var(--danger); box-shadow:inset 0 0 0 1px var(--danger-border); } .badge--bad::before { background:var(--danger-dot); }
.badge--neutral { color:var(--text-3); }
.badge--live::before { animation:dot-live 1.6s var(--ease-in-out) 3; }  /* 上課中: three soft pulses, then rests (M31) */
.chip { display:inline-flex; align-items:center; height:24px; padding:0 8px; border-radius:var(--r-xs); background:var(--subtle); vertical-align:middle;
        color:var(--text-2); font:600 12.5px/1 var(--font); box-shadow:inset 0 0 0 1px var(--border-strong); white-space:nowrap; font-variant-numeric:tabular-nums; }
.chip + .chip { margin-left:4px; }
.chip--more { color:var(--text-3); }

/* money + roc */
.money--neg { color:var(--money-neg); }
.money--pos { color:var(--money-pos); }
.money--zero, .roc.is-null { color:var(--text-3); }
.money-u { font-size:.86em; color:var(--text-3); margin-right:2px; }

/* avatar + skeleton */
.avatar { width:32px; height:32px; flex:none; border-radius:50%; display:inline-grid; place-items:center; background:var(--accent-soft);
          color:var(--accent-text); font-size:13.5px; font-weight:600; box-shadow:inset 0 0 0 1px var(--accent-border); }
.avatar--sm { width:28px; height:28px; font-size:12.5px; }
.avatar--photo { object-fit:cover; box-shadow:inset 0 0 0 1px var(--border); }
/* skeleton (§6.18) — the ::after sweep runs only while something is loading (an aria-busy container) */
.skel, .skeleton { position:relative; display:block; height:12px; border-radius:var(--r-xs); background:var(--skel); overflow:hidden; isolation:isolate; }
.skel::after { content:""; position:absolute; inset:0; transform:translateX(-100%); background:linear-gradient(90deg, transparent, var(--skel-hi), transparent); }
[aria-busy="true"] .skel::after { animation:shimmer 1.3s var(--ease-in-out) infinite; }
.skel--circle { width:32px; height:32px; border-radius:50%; flex:none; }
.skel--t { width:60%; height:12px; }
.skel--s { width:36%; height:10px; margin-top:7px; }

/* alert (§6.19) */
.alert { display:flex; align-items:flex-start; gap:12px; padding:14px 16px; border-radius:var(--r-md); font-size:14.5px; line-height:var(--lh-body); color:var(--text-1);
         box-shadow:inset 0 0 0 1px var(--border); }
.alert-ic { margin-top:3px; }
.alert-m { flex:1; min-width:0; }
.alert-t { font-weight:600; }
.alert-b a, .alert-m a { text-decoration:underline; text-underline-offset:3px; }
.alert-x { margin:-6px -6px -6px 0; }
.alert--info { background:var(--accent-softer); box-shadow:inset 0 0 0 1px var(--accent-border); } .alert--info .alert-ic { color:var(--accent-text); }
.alert--ok { background:var(--success-soft); box-shadow:inset 0 0 0 1px var(--success-border); } .alert--ok .alert-ic { color:var(--success); }
.alert--warn { background:var(--warning-soft); box-shadow:inset 0 0 0 1px var(--warning-border); } .alert--warn .alert-ic { color:var(--warning); }
.alert--bad { background:var(--danger-soft); box-shadow:inset 0 0 0 1px var(--danger-border); } .alert--bad .alert-ic { color:var(--danger); }
.alert--bad a { color:var(--danger); font-weight:600; }
.form-errors { margin-bottom:20px; }
.form-errors:focus { outline:2px solid var(--focus); outline-offset:2px; }
.form-errors-list { margin:6px 0 0; padding-left:20px; }

/* empty state (§6.12) — line-art illustration coloured by classes; it floats three times, then rests (M32) */
.empty { padding:56px 24px; text-align:center; }
.empty-ic { width:44px; height:44px; margin:0 auto 14px; border-radius:var(--r-md); display:grid; place-items:center; background:var(--subtle); color:var(--text-3);
            box-shadow:inset 0 0 0 1px var(--border); }
.empty-ic .i { width:20px; height:20px; }
.empty-art { display:block; width:132px; height:96px; margin:0 auto 18px; overflow:visible; }
.empty-art .ea-card { fill:var(--sheet); stroke:var(--border-strong); stroke-width:1.5; }
.empty-art .ea-line { stroke:var(--border-strong); stroke-width:3; stroke-linecap:round; fill:none; }
.empty-art .ea-acc { fill:var(--accent-soft); stroke:var(--accent); stroke-width:1.5; }
.empty-art .ea-glass { fill:none; stroke:var(--accent); stroke-width:3; stroke-linecap:round; stroke-linejoin:round; }
.empty-art .ea-ok { fill:none; stroke:var(--success-dot); stroke-width:3; stroke-linecap:round; stroke-linejoin:round; }
.empty-art .ea-okbg { fill:var(--success-soft); stroke:var(--success-dot); stroke-width:1.5; }
.empty-art .ea-shadow { fill:var(--active); stroke:none; }
.empty-art .ea-float { animation:float 3.2s var(--ease-in-out) 3 both; transform-box:fill-box; transform-origin:center; }
.empty-art .ea-shadow { animation:float-shadow 3.2s var(--ease-in-out) 3 both; transform-box:fill-box; transform-origin:center; }
.empty-t { font-size:var(--fs-lg); }
.empty-d { margin:6px auto 18px; max-width:460px; font-size:14.5px; line-height:var(--lh-prose); color:var(--text-3); }

/* filter bar (§6.7) — search with a leading icon, pill selects that show label + value, removable chips */
.fbar { display:flex; align-items:center; gap:10px; margin-bottom:16px; flex-wrap:wrap; }
.fbar-sp { flex:1; }
.fbar-meta { font-size:var(--fs-sm); color:var(--text-3); }
.fsearch { position:relative; display:flex; align-items:center; width:340px; max-width:100%; margin:0; }
.fsearch-i { position:absolute; left:14px; color:var(--text-3); pointer-events:none; transition:color var(--dur-1); }
/* trap 2: beats the generic control padding */
.fsearch input[type=search], .fsearch input[type=text] { padding-left:42px; padding-right:40px; }
.fsearch:focus-within .fsearch-i { color:var(--accent-text); }
.fsearch kbd { position:absolute; right:10px; }
.fsel { position:relative; display:inline-flex; align-items:center; min-height:var(--h-md); margin:0; border-radius:var(--r-sm); background:var(--sheet);
        box-shadow:inset 0 0 0 1px var(--border-control); transition:box-shadow var(--dur-2), background-color var(--dur-1); }
.fsel:hover { box-shadow:inset 0 0 0 1px var(--border-control-hover); }
.fsel:focus-within { box-shadow:inset 0 0 0 1px var(--focus), 0 0 0 1px var(--focus), 0 0 0 5px var(--focus-glow); }
.fsel-l { padding:0 0 0 14px; font-size:14px; color:var(--text-3); white-space:nowrap; }
/* trap 5: background-color only — the background shorthand would wipe the drawn chevron */
.fsel select { width:auto; min-height:36px; border:0; background-color:transparent; box-shadow:none !important; font-size:14px; font-weight:600; padding:0 32px 0 6px; }
.fsel select:focus { outline:2px solid transparent; }
.fsel.is-on { background:var(--accent-soft); box-shadow:inset 0 0 0 1px var(--accent-border); }
.fsel.is-on .fsel-l, .fsel.is-on select { color:var(--accent-text); }
.fsel.is-on:focus-within { box-shadow:inset 0 0 0 1px var(--focus), 0 0 0 1px var(--focus), 0 0 0 5px var(--focus-glow); }
.fbar-go { display:none; }
/* filter bar: the phone bottom sheet + the active-filter summary (§5.5) — the sheet is a <dialog> that a
   desktop lays out inline (display:contents), so the same fields serve both */
.btn.fbar-open { display:none; }
.fbar-n { display:inline-grid; place-items:center; min-width:22px; height:22px; padding:0 7px; margin-left:2px; border-radius:var(--r-full);
          background:var(--accent); color:var(--on-accent); font-size:12.5px; font-weight:600; }
.fbar-sheet { display:contents; }
.fbar-sheet-h, .fbar-sheet-f { display:none; }
.fbar-fields { display:contents; }
.fsel--date input { width:auto; min-height:36px; border:0; background-color:transparent; box-shadow:none !important; font-size:14px; padding:0 10px 0 6px; }
.fsel--date input:focus { outline:2px solid transparent; }
.fbar--manual .fbar-go { display:inline-flex; }
.fbar-sum { display:flex; align-items:center; flex-wrap:wrap; gap:6px 8px; margin:-4px 0 16px; font-size:var(--fs-sm); color:var(--text-3); }
.fbar-chip { display:inline-flex; align-items:center; gap:4px; height:30px; padding:0 8px 0 12px; border-radius:var(--r-full);
             background:var(--accent-soft); color:var(--accent-text); font-size:var(--fs-sm); box-shadow:inset 0 0 0 1px var(--accent-border);
             animation:chip-in var(--dur-3) var(--ease-spring) backwards; transition:box-shadow var(--dur-1); }
.fbar-chip:hover { text-decoration:none; box-shadow:inset 0 0 0 1px var(--accent); }
.fbar-chip-k { color:var(--text-3); }
.fbar-chip-k::after { content:"："; }
.fbar-chip .i { width:14px; height:14px; }
.fbar-sum-x { margin-left:4px; font-size:var(--fs-sm); }
.fbar .fsel select { max-width:240px; text-overflow:ellipsis; }   /* a long option label never widens the filter bar */
/* an auto-submitting bar whose filters include dates: dates never auto-submit, so its 查詢 stays visible */
.fbar:has(.fsel--date) .fbar-go { display:inline-flex; }
/* <x-filter-text>: a short text / number filter in the pill look; a month filter's 民國 reading */
.fsel--text input { width:100px; min-height:36px; border:0; background-color:transparent; box-shadow:none !important; font-size:14px; padding:0 10px 0 6px; }
.fsel--text input:focus { outline:2px solid transparent; }
.fsel.is-on .fsel--text input, .fsel--text.is-on input { color:var(--accent-text); }
.fsel-roc { padding-right:12px; font-size:12.5px; color:var(--text-3); white-space:nowrap; }
/* <x-period-stepper>: ‹ 115/09 › */
.stepper { display:inline-flex; align-items:center; gap:2px; }
.stepper-t { min-width:72px; text-align:center; font-size:14px; font-weight:600; color:var(--text-1); font-variant-numeric:tabular-nums; }

/* segmented (§6.9) — a sliding thumb (JS inserts .seg-thumb + .has-thumb and sets --seg-x / width by CSSOM);
   without JS the pressed / checked option draws its own chip. No scaleX (it would squash the r6 corners). */
.seg { position:relative; display:inline-flex; padding:3px; gap:2px; background:var(--inset); border-radius:var(--r-sm); box-shadow:inset 0 0 0 1px var(--border); isolation:isolate; vertical-align:middle; }
.seg button { position:relative; display:inline-flex; align-items:center; justify-content:center; gap:6px; height:32px; padding:0 12px; border:0; background:none; border-radius:6px;
              font:14px/1 var(--font); color:var(--text-3); cursor:pointer; white-space:nowrap; transition:color var(--dur-1); }
.seg button:hover { color:var(--text-1); }
.seg button[aria-pressed="true"] { color:var(--text-1); font-weight:600; }
.seg:not(.has-thumb) button[aria-pressed="true"] { background:var(--thumb); box-shadow:var(--e-1); }
.seg button .i { width:15px; height:15px; }
.seg-thumb { position:absolute; left:0; top:3px; bottom:3px; width:0; border-radius:6px; background:var(--thumb); box-shadow:var(--e-1); z-index:-1; pointer-events:none;
             transform:translateX(var(--seg-x, 3px)); transition:transform var(--dur-3) var(--ease-out); }

/* forms (§6.10) — B's panel layout: a 240 px title column + a panel card holding the field grid */
.form { display:grid; gap:20px; }
.form > * { min-width:0; }
.fsec { display:grid; grid-template-columns:240px minmax(0,1fr); gap:32px; }
.fsec + .fsec { margin-top:20px; }
.form > .fsec + .fsec { margin-top:0; }
.fsec-h { padding-top:6px; min-width:0; }
.fsec-t { display:flex; align-items:center; gap:10px; font-size:var(--t-h3); line-height:1.4; }
.fsec-n { width:24px; height:24px; flex:none; display:inline-grid; place-items:center; border-radius:var(--r-full); background:var(--accent-soft);
          color:var(--accent-text); font-size:12.5px; font-weight:600; box-shadow:inset 0 0 0 1px var(--accent-border); }
.fsec-d { margin:8px 0 0; font-size:14px; line-height:var(--lh-body); color:var(--text-3); }
.fsec-b, .fsec > .bd { min-width:0; padding:24px; background:var(--sheet); border-radius:var(--r-lg); box-shadow:var(--e-1); }
.fgrid { display:grid; grid-template-columns:repeat(auto-fill, minmax(220px,1fr)); gap:22px 20px; }
.fgrid .full { grid-column:1 / -1; }
.fgrid .span2, .fgrid .wide { grid-column:span 2; }
.fld { min-width:0; }
.fld > label, .fld > .lbl { display:flex; align-items:center; gap:4px; margin-bottom:8px; font-size:var(--t-label); font-weight:600; line-height:1.5; color:var(--text-2); }
.req { color:var(--danger); }
.opt { margin-left:auto; font-size:12.5px; font-weight:400; color:var(--text-3); }
.hint { margin-top:8px; font-size:var(--fs-sm); line-height:1.55; color:var(--text-3); }
.hint b { color:var(--text-2); font-weight:600; }
.field-fix { margin-top:8px; }
.field-fix a, .field-fix button { font-size:var(--fs-sm); }
/* the fix chip 「改用 …」 (a data-fill link / button in <x-slot:fix>) */
.field-fix > [data-fill] { display:inline-flex; align-items:center; gap:4px; height:30px; padding:0 12px; border:0; border-radius:var(--r-full);
                           background:var(--accent-soft); color:var(--accent-text); font:600 13px/1 var(--font); cursor:pointer; text-decoration:none;
                           box-shadow:inset 0 0 0 1px var(--accent-border); transition:transform var(--dur-2) var(--ease-out), box-shadow var(--dur-1); }
.field-fix > [data-fill]:hover { transform:translateY(-1px); box-shadow:inset 0 0 0 1px var(--accent); text-decoration:none; }
/* a read-only (permission-locked) control: inset fill, a strong edge and a lock glyph */
.ctl-lock { position:relative; display:block; }
.ctl-lock input { padding-right:36px; background-color:var(--inset); box-shadow:inset 0 0 0 1px var(--border-strong); color:var(--text-2); }
.ctl-lock-i { position:absolute; right:12px; top:50%; margin-top:-7px; width:14px; height:14px; color:var(--text-3); pointer-events:none; }
/* a permission-locked money field: the same inset fill + lock glyph */
.fld.is-locked .affix { position:relative; }
.fld.is-locked .affix input { padding-right:36px; background-color:var(--inset); box-shadow:inset 0 0 0 1px var(--border-strong); color:var(--text-2); }
.affix { display:flex; align-items:stretch; }
.affix-p { display:inline-flex; align-items:center; padding:0 12px; border-radius:var(--r-sm) 0 0 var(--r-sm); background:var(--inset);
           box-shadow:inset 0 0 0 1px var(--border-control); font-size:14px; color:var(--text-3); }
.affix input { border-radius:0 var(--r-sm) var(--r-sm) 0; text-align:right; margin-left:-1px; }
/* segmented field (radios) — the selected chip SLIDES (JS thumb); no-JS uses the :checked chip.
   trap 4: .segf label > span never matches the thumb (a direct child of .segf, not inside a label) */
.segf { position:relative; display:flex; width:100%; min-height:var(--h-md); padding:3px; gap:2px; background:var(--inset); border:0 none; border-radius:var(--r-sm);
        box-shadow:inset 0 0 0 1px var(--border-control); isolation:isolate; }
.segf label { position:relative; flex:1; margin:0; font-weight:400; min-width:0; }
/* the option's own radio / checkbox stays out of the layout (beats the custom checkbox box) */
.segf input, .segf input[type=checkbox], .segf input[type=radio] { position:absolute; width:1px; height:1px; opacity:0; pointer-events:none; }
/* locked (permission / schedule-driven): the inset fill + a strong edge, like .ctl-lock */
.segf.is-locked, .segf:has(input:disabled) { background:var(--inset); box-shadow:inset 0 0 0 1px var(--border-strong); }
.segf label > span { display:grid; place-items:center; height:100%; min-height:30px; padding:0 6px; border-radius:6px; font-size:var(--fs-md); color:var(--text-3); cursor:pointer;
                     white-space:nowrap; transition:color var(--dur-1); }
.segf input:checked + span { color:var(--text-1); font-weight:600; }
.segf:not(.has-thumb) input:checked + span { background:var(--thumb); box-shadow:var(--e-1); }
.segf input:focus-visible + span { outline:2px solid var(--focus); outline-offset:1px; }
.segf input:disabled + span { cursor:not-allowed; opacity:.6; }
.segf .seg-thumb { top:3px; bottom:3px; }
.switch-row { display:flex; align-items:center; gap:10px; min-height:var(--h-md); font-size:14.5px; color:var(--text-2); }
/* pill radios (a long list of choices): groupAttrs ['class' => 'radios--pills'] */
.radios { display:flex; flex-wrap:wrap; gap:12px; align-items:center; }
.radios--stack { flex-direction:column; align-items:flex-start; gap:8px; }
.fld--radios .radios label { margin:0; }
.radios.radios--pills { flex-direction:row; flex-wrap:wrap; gap:8px; }
.radios--pills label { position:relative; display:inline-flex; align-items:center; gap:6px; min-height:var(--h-md); margin:0; padding:0 14px; border-radius:var(--r-full);
                       background:var(--sheet); box-shadow:inset 0 0 0 1px var(--border-strong); font-size:14.5px; font-weight:400; color:var(--text-2); cursor:pointer;
                       transition:box-shadow var(--dur-2), background-color var(--dur-1), color var(--dur-1), transform var(--dur-0); }
.radios--pills label:hover { box-shadow:inset 0 0 0 1px var(--border-control); }
.radios--pills label:active { transform:scale(.97); }
.radios--pills input, .radios--pills input[type=radio], .radios--pills input[type=checkbox] { position:absolute; width:1px; height:1px; opacity:0; pointer-events:none; }
.radios--pills label:has(input:checked) { background:var(--accent-soft); color:var(--accent-text); font-weight:600; box-shadow:inset 0 0 0 1.5px var(--accent); }
.radios--pills label:has(input:focus-visible) { outline:2px solid var(--focus); outline-offset:2px; }
/* a short radio list laid out in one row: groupAttrs ['class' => 'radios--row'] */
.radios.radios--row { flex-direction:row; align-items:center; gap:8px 20px; min-height:var(--h-md); }
/* x-field: checkbox + checkbox list — the box lines up with the controls of its grid row (label 14 × 1.5 + 8 px) */
.fld--check { padding-top:29px; }
.chk { display:flex; align-items:center; gap:9px; min-height:var(--h-md); margin:0; padding:0 12px; border:1px solid var(--border); border-radius:var(--r-sm);
       background:var(--subtle); font-size:var(--fs-md); font-weight:400; color:var(--text-1); cursor:pointer; transition:border-color var(--dur-1); }
.chk:hover { border-color:var(--border-strong); }
.fld > .chk { margin:0; gap:9px; font-size:var(--fs-md); font-weight:400; color:var(--text-1); }
.chk input { flex:none; margin:0; }
/* its hint / error line up with the label text (12 px padding + 18 px box + 9 px gap + 1 px border) */
.fld--check .hint, .fld--check .field-err { margin-top:8px; }
.fld--check > .hint, .fld--check > .field-err { padding-left:40px; }
/* a checkbox on a row of its own needs no alignment padding (full width, or .fld--flat) */
.fgrid > .fld--check.full, .fld--check.fld--flat { padding-top:0; }
.checks { display:flex; flex-wrap:wrap; gap:8px; }
.checks .chk { font-weight:400; }
.fld--checks.has-error .chk { border-color:var(--danger-dot); }
.checks--stack { flex-direction:column; align-items:stretch; }
/* a field that only matters in another state (page JS toggles it; stays enabled — visual cue only) */
.fld.is-dim { opacity:.55; transition:opacity var(--dur-1); }
.fld.is-dim:focus-within { opacity:1; }
/* photo row (students / employees): a 92 px drop zone that lifts on hover / drag-over (M38) */
.photo-row { display:flex; align-items:center; gap:18px; padding-bottom:22px; margin-bottom:22px; border-bottom:1px dashed var(--border-strong); flex-wrap:wrap; }
.photo-box { width:92px; height:92px; flex:none; border-radius:var(--r-xl); display:grid; place-items:center; background:var(--subtle); color:var(--text-3); overflow:hidden; font-size:12.5px;
             box-shadow:inset 0 0 0 1.5px var(--border-strong); transition:box-shadow var(--dur-2), transform var(--dur-2) var(--ease-out); }
.photo-box:hover, .photo-box.is-drag, .photo-row:has(input[type=file]:hover) .photo-box { box-shadow:inset 0 0 0 1.5px var(--accent); transform:scale(1.02); }
.photo-box img { width:100%; height:100%; object-fit:cover; }
.photo-box .i { width:26px; height:26px; }
.photo-row .fld { flex:1; min-width:220px; }
/* form sub-heading inside a section's field grid */
.fgrid > .fsubhd, .fgrid > h3 { grid-column:1 / -1; margin:6px 0 -6px; font-size:var(--fs-sm); font-weight:600; color:var(--text-2); }
.fgrid > p.hint { margin:0; }
/* a stored PII value on an edit form: shown masked, 「修改」 opens an empty field (students/form 身分證號) */
.pp-nid-row { display:flex; align-items:center; gap:8px; min-height:var(--h-md); }

/* wizard step indicator (<x-steps>) — the current dot is the primary gradient with a soft halo */
.steps-nav { margin-bottom:24px; }
.steps-m { display:none; margin:0 0 6px; font-size:var(--fs-sm); color:var(--text-3); }
.steps { display:flex; align-items:center; gap:10px; list-style:none; margin:0; padding:0; counter-reset:none; }
.step { display:flex; align-items:center; gap:10px; min-width:0; font-size:14.5px; color:var(--text-3); }
.step + .step::before { content:""; width:40px; height:2px; flex:none; border-radius:2px; background:var(--border-strong); }
.step-n { width:28px; height:28px; flex:none; border-radius:50%; display:inline-grid; place-items:center; font-size:13px; font-weight:600;
          background:var(--sheet); color:var(--text-3); box-shadow:inset 0 0 0 1.5px var(--border-strong); }
.step-n .i { width:14px; height:14px; }
.step.is-done .step-n { background:var(--accent-soft); color:var(--accent-text); box-shadow:inset 0 0 0 1px var(--accent-border); }
.step.is-done { color:var(--text-2); }
.step.is-current .step-n { background:var(--grad-primary); color:var(--on-accent); box-shadow:0 0 0 4px var(--focus-glow); }
.step.is-current, .step.is-current .step-l { color:var(--text-1); font-weight:600; }
.step-l { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }

/* sticky glass action bar (§6.10) — rises on load (M30); the dirty dot pops; aligned to the panel column when the
   form uses the section layout */
.actionbar { position:sticky; bottom:16px; z-index:var(--z-sticky); display:flex; align-items:center; justify-content:space-between; gap:12px;
             margin:8px 0 0; padding:10px 10px 10px 18px; background:var(--glass); -webkit-backdrop-filter:saturate(1.6) blur(14px); backdrop-filter:saturate(1.6) blur(14px);
             border-radius:var(--r-xl); box-shadow:var(--e-3); animation:bar-in var(--dur-4) var(--ease-out) backwards; }
form:has(> .fsec) > .actionbar { margin-left:272px; }
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))) { .actionbar { background:var(--raised); } }
.ab-dirty { display:flex; align-items:center; gap:8px; font-size:14px; color:var(--text-2); }
.ab-dirty i { width:8px; height:8px; border-radius:50%; background:var(--warning-dot); box-shadow:0 0 0 3px var(--warning-soft); }
.ab-dirty:not([hidden]) i { animation:dot-pop var(--dur-4) var(--ease-spring) backwards; }
.ab-btns { display:flex; gap:8px; margin-left:auto; flex-wrap:wrap; }
.ab-note { display:inline-flex; align-items:center; gap:6px; min-width:0; font-size:14px; color:var(--text-3); font-variant-numeric:tabular-nums; }
.ab-note b { color:var(--text-1); font-weight:600; }
/* an action bar that is a card's own footer (a form embedded in a list page) */
.card .actionbar--card, .actionbar--card { position:sticky; bottom:0; margin:20px -20px -20px; padding:12px 20px; border-top:1px solid var(--border);
                                           border-radius:0 0 var(--r-lg) var(--r-lg); background:var(--subtle); box-shadow:none; animation:none;
                                           -webkit-backdrop-filter:none; backdrop-filter:none; }
/* the busy note of a long submit (data-busy-note) */
.busy-note { display:flex; align-items:center; gap:8px; margin-top:12px; font-size:14px; color:var(--text-2); }
.busy-note::before { content:""; width:15px; height:15px; flex:none; border-radius:50%; border:2px solid var(--border-strong); border-right-color:var(--accent); animation:spin .8s linear infinite; }

/* toasts (§6.14) — spring in from below; a countdown hairline owns the dismissal (animationend) and pauses on
   hover / focus; siblings FLIP into their new slots (JS, WAAPI) */
.toasts { position:fixed; right:24px; bottom:24px; z-index:var(--z-toast); display:flex; flex-direction:column; gap:10px; width:384px; max-width:calc(100vw - 24px); }
body:has(.bulk:not([hidden])) .toasts, body:has(.actionbar:not(.actionbar--card)) .toasts { bottom:88px; }
.toast { position:relative; display:flex; align-items:flex-start; gap:12px; padding:14px 12px 16px 16px; background:var(--raised); border-radius:var(--r-lg);
         box-shadow:var(--e-3); color:var(--text-1); overflow:hidden; animation:toast-in var(--dur-4) var(--ease-spring) backwards; }
.toast.is-out { animation:toast-out var(--dur-3) var(--ease-in) both; }
.toast-ic { width:24px; height:24px; flex:none; display:grid; place-items:center; margin-top:1px; }
.toast-ic .i { width:20px; height:20px; }
.toast--ok .toast-ic { color:var(--success-dot); }
.toast--info .toast-ic { color:var(--accent-text); }
.toast--bad .toast-ic { color:var(--danger); }
.toast-m { flex:1; min-width:0; }
.toast-t { font-size:var(--fs-md); font-weight:600; line-height:1.5; color:var(--text-1); }
.toast-b { font-size:14px; line-height:1.6; color:var(--text-2); margin-top:2px; }
.toast-meta { font-size:13px; color:var(--text-3); margin-top:4px; font-variant-numeric:tabular-nums; }
.toast-acts { display:flex; flex-wrap:wrap; gap:6px; margin-top:10px; }
.toast-a { flex:none; }
.toast-x { flex:none; margin:-4px -2px 0 0; }
.toast-timer { position:absolute; left:0; right:0; bottom:0; height:2px; background:var(--accent); opacity:.55; transform-origin:left center; pointer-events:none;
               animation:countdown 5s linear both; }
.toast--ok .toast-timer { background:var(--success-dot); }
.toast:hover .toast-timer, .toast:focus-within .toast-timer { animation-play-state:paused; }
.toast--bad .toast-timer { display:none; }
/* the checkmark burst (M24, completions only: 收費, 新增, 儲存, 報名) */
.burst { position:relative; display:block; width:22px; height:22px; color:var(--success-dot); }
.burst svg { position:absolute; inset:0; width:22px; height:22px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; overflow:visible; }
.burst-ring { stroke-dasharray:63; stroke-dashoffset:63; animation:ck-draw 300ms var(--ease-out) 60ms forwards; }
.burst-tick { stroke-dasharray:14; stroke-dashoffset:14; animation:ck-draw 200ms var(--ease-out) 260ms forwards; }
.burst-fill { fill:var(--success-soft); stroke:none; transform-box:fill-box; transform-origin:center; transform:scale(0); animation:burst-fill 360ms var(--ease-spring) 200ms forwards; }
.burst i { position:absolute; left:50%; top:50%; width:3px; height:3px; margin:-1.5px 0 0 -1.5px; border-radius:50%; background:currentColor; opacity:0;
           animation:ray 460ms var(--ease-out) 280ms forwards; }
.burst i:nth-of-type(1) { --a:0deg; } .burst i:nth-of-type(2) { --a:60deg; } .burst i:nth-of-type(3) { --a:120deg; }
.burst i:nth-of-type(4) { --a:180deg; } .burst i:nth-of-type(5) { --a:240deg; } .burst i:nth-of-type(6) { --a:300deg; }

/* dialogs (§6.13) — enter AND exit in pure CSS (@starting-style + allow-discrete); JS only calls showModal()/close() */
dialog { color:var(--text-1); }
dialog::backdrop { background:var(--scrim); }
.dlg { width:min(480px, calc(100vw - 32px)); padding:0; margin:16vh auto auto; border:0; border-radius:var(--r-xl); background:var(--raised); box-shadow:var(--e-4);
       opacity:0; transform:translateY(var(--m-rise)) scale(var(--m-pop));
       transition:opacity var(--dur-2) var(--ease-in), transform var(--dur-2) var(--ease-in), overlay var(--dur-2) allow-discrete, display var(--dur-2) allow-discrete; }
.dlg[open] { opacity:1; transform:none; transition:opacity var(--dur-3) var(--ease-out), transform var(--dur-4) var(--ease-out), overlay var(--dur-3) allow-discrete, display var(--dur-3) allow-discrete; }
@starting-style { .dlg[open] { opacity:0; transform:translateY(var(--m-rise)) scale(var(--m-pop)); } }
.dlg::backdrop { background:var(--scrim); opacity:0; transition:opacity var(--dur-2), overlay var(--dur-2) allow-discrete, display var(--dur-2) allow-discrete; }
.dlg[open]::backdrop { opacity:1; transition:opacity var(--dur-3); -webkit-backdrop-filter:blur(2px); backdrop-filter:blur(2px); }
@starting-style { .dlg[open]::backdrop { opacity:0; } }
.dlg:focus, .dlg:focus-visible { outline:2px solid transparent; }   /* the box never shows a ring (its first field / button does) */
.dlg-b { display:flex; gap:16px; padding:24px 24px 18px; }
.dlg-ic { width:44px; height:44px; flex:none; border-radius:var(--r-md); display:grid; place-items:center; background:var(--accent-soft); color:var(--accent-text); box-shadow:inset 0 0 0 1px var(--accent-border); }
.dlg-ic .i { width:20px; height:20px; }
.dlg--danger .dlg-ic { background:var(--danger-soft); color:var(--danger); box-shadow:inset 0 0 0 1px var(--danger-border); }
.dlg--warn .dlg-ic { background:var(--warning-soft); color:var(--warning); box-shadow:inset 0 0 0 1px var(--warning-border); }
.dlg-m { flex:1; min-width:0; }
.dlg-t { font-size:var(--fs-xl); line-height:1.4; margin-top:8px; }
.dlg-d, .dlg-body { margin:8px 0 0; font-size:14.5px; line-height:var(--lh-prose); color:var(--text-2); white-space:pre-line; }
.dlg-body { white-space:normal; }
.dlg-facts { margin-top:14px; padding:10px 14px; border-radius:var(--r-md); background:var(--subtle); box-shadow:inset 0 0 0 1px var(--border); font-size:14px; }
.dlg-fact { display:flex; gap:12px; padding:3px 0; }
.dlg-fk { width:72px; flex:none; color:var(--text-3); }
.dlg-fv { color:var(--text-1); font-variant-numeric:tabular-nums; min-width:0; overflow-wrap:anywhere; }
.dlg-note { display:flex; gap:8px; align-items:flex-start; margin-top:12px; padding:10px 12px; border-radius:var(--r-md); background:var(--warning-soft);
            box-shadow:inset 0 0 0 1px var(--warning-border); font-size:14px; line-height:var(--lh-body); color:var(--text-1); }
.dlg-note .i { color:var(--warning); margin-top:3px; }
.dlg-type { margin-top:14px; }
.dlg-type label { font-size:14px; margin-bottom:8px; }
.dlg-f { display:flex; justify-content:flex-end; align-items:center; gap:8px; padding:14px 24px; border-top:1px solid var(--border); background:var(--subtle);
         border-radius:0 0 var(--r-xl) var(--r-xl); }
.dlg-hint { margin-right:auto; display:flex; align-items:center; gap:6px; font-size:13px; color:var(--text-3); }
.keys-dlg { width:min(520px, calc(100vw - 32px)); }
.keys { display:grid; grid-template-columns:max-content 1fr; gap:10px 16px; margin:14px 0; font-size:14px; }
.keys dt { white-space:nowrap; }
.keys dd { margin:0; color:var(--text-2); }
/* <x-modal size="wide|xl"> and the form dialog (<x-modal :action>): a scrolling body, the footer always visible */
.dlg--wide { width:min(600px, calc(100vw - 32px)); }
.dlg--xl { width:min(800px, calc(100vw - 32px)); }
.dlg--form { max-height:calc(100dvh - 48px); margin-top:8vh; }
.dlg--form[open] { display:flex; flex-direction:column; }
.dlg--form > form { display:contents; }
.dlg--form .dlg-b { min-height:0; overflow-y:auto; }
.dlg--form .dlg-f { flex:none; }
.dlg-fields { display:grid; gap:16px; margin-top:16px; }
.dlg-fields > .fgrid { margin:0; }

/* menu (§6.16) — opens with pop-in from the trigger corner (M19); close is instant */
.menu-wrap { position:relative; display:inline-block; }
.menu { position:absolute; right:0; top:calc(100% + 8px); min-width:240px; padding:6px; background:var(--raised); border-radius:var(--r-lg); box-shadow:var(--e-3);
        z-index:var(--z-dropdown); text-align:left; transform-origin:top right; animation:pop-in var(--dur-2) var(--ease-out) backwards; }
.menu--start { right:auto; left:0; transform-origin:top left; }
.menu.is-up { top:auto; bottom:calc(100% + 8px); transform-origin:bottom right; }
.menu-h { padding:8px 10px 12px; border-bottom:1px solid var(--border); margin-bottom:6px; }
.menu-h b { display:block; font-size:var(--fs-md); font-weight:600; }
.menu-h span { font-size:13px; color:var(--text-3); overflow-wrap:anywhere; }
.menu-i, .menu [role="menuitem"] { display:flex; align-items:center; gap:10px; width:100%; min-height:40px; padding:0 10px; border:0; border-radius:var(--r-sm);
         background:none; color:var(--text-2); font:14.5px/1.2 var(--font); text-align:left; cursor:pointer; white-space:nowrap; transition:background-color var(--dur-1), color var(--dur-1); }
.menu-i .i, .menu [role="menuitem"] .i { width:17px; height:17px; color:var(--text-3); }
.menu-i:hover, .menu-i:focus-visible, .menu [role="menuitem"]:hover, .menu [role="menuitem"]:focus-visible { background:var(--hover); color:var(--text-1); text-decoration:none; }
.menu .is-danger, .menu .is-danger .i { color:var(--danger); }
.menu-sep { height:1px; background:var(--border); margin:6px 4px; }
.menu-row { display:flex; align-items:center; justify-content:space-between; gap:16px; min-height:46px; padding:0 10px; font-size:14px; color:var(--text-2); }
.menu-row > span:first-child { white-space:nowrap; }
.menu-form { margin:0; }
.user-menu { min-width:320px; }

/* palette (Ctrl+K, §6.15) — drops in from the top; results rise in 22 ms steps (CSSOM delay); a skeleton only after 150 ms */
.pal { width:min(680px, calc(100vw - 32px)); padding:0; margin:12vh auto auto; border:0; border-radius:var(--r-xl); background:var(--raised); box-shadow:var(--e-4); overflow:hidden;
       opacity:0; transform:translateY(calc(-1 * var(--m-rise))) scale(.98);
       transition:opacity var(--dur-1) var(--ease-in), transform var(--dur-1) var(--ease-in), overlay var(--dur-1) allow-discrete, display var(--dur-1) allow-discrete; }
.pal[open] { opacity:1; transform:none; transition:opacity var(--dur-2) var(--ease-out), transform var(--dur-3) var(--ease-out), overlay var(--dur-2) allow-discrete, display var(--dur-2) allow-discrete; }
@starting-style { .pal[open] { opacity:0; transform:translateY(calc(-1 * var(--m-rise))) scale(.98); } }
.pal::backdrop { background:var(--scrim); opacity:0; transition:opacity var(--dur-2), overlay var(--dur-2) allow-discrete, display var(--dur-2) allow-discrete; }
.pal[open]::backdrop { opacity:1; -webkit-backdrop-filter:blur(2px); backdrop-filter:blur(2px); }
@starting-style { .pal[open]::backdrop { opacity:0; } }
@media (prefers-reduced-transparency: reduce) { .dlg[open]::backdrop, .pal[open]::backdrop { -webkit-backdrop-filter:none; backdrop-filter:none; } }
.pal-in { display:flex; align-items:center; gap:12px; height:60px; padding:0 16px 0 18px; border-bottom:1px solid var(--border); }
.pal-in:focus-within { box-shadow:inset 0 -2px 0 var(--focus); }
.pal-in .i { width:19px; height:19px; color:var(--text-3); }
.pal-in input[type=text] { flex:1; height:100%; min-height:0; border:0; background:none; box-shadow:none !important; padding:0; font:17px var(--font); color:var(--text-1); }
.pal-in input[type=text]:focus { outline:2px solid transparent; }
.pal-list { max-height:min(440px, 56vh); overflow-y:auto; padding:8px; }
.pal-g { padding:10px 10px 6px; font-size:12.5px; font-weight:600; color:var(--text-3); }
.pal-o { display:flex; align-items:center; gap:12px; min-height:48px; padding:0 12px; border-radius:var(--r-md); color:var(--text-2); font-size:var(--fs-md); cursor:pointer;
         transition:background-color var(--dur-0); }
.pal-o .i { width:17px; height:17px; color:var(--text-3); }
.pal-o[aria-selected="true"] { background:var(--accent-softer); color:var(--text-1); box-shadow:inset 0 0 0 1px var(--accent-border); }
.pal-o[aria-selected="true"] .pal-enter { opacity:1; }
.pal-t { color:var(--text-1); min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.pal-t mark { background:none; color:inherit; font-weight:600; text-decoration:underline 2px var(--accent); text-underline-offset:4px; }
.pal-path { margin-left:auto; font-size:13px; color:var(--text-3); white-space:nowrap; }
.pal-o kbd { margin-left:auto; }
.pal-path + kbd, .pal-path + .pal-enter { margin-left:10px; }
.pal-enter { opacity:0; transition:opacity var(--dur-1); }
.pal-empty { padding:24px 12px; text-align:center; font-size:var(--fs-sm); color:var(--text-3); animation:fade-in var(--dur-2) var(--ease-out) backwards; }
.pal-f { display:flex; align-items:center; gap:16px; min-height:44px; padding:0 16px; border-top:1px solid var(--border); background:var(--subtle); font-size:13px; color:var(--text-3); flex-wrap:wrap; }
.pal-f span { display:inline-flex; align-items:center; gap:5px; }
.pal-priv { margin-left:auto; }
.pal-list .pal-o, .pal-list .pal-g { animation:rise var(--dur-3) var(--ease-out) backwards; }
.pal-o--skel { cursor:default; }
.pal-sl { flex:1; min-width:0; display:block; }
.pal-o--skel .skel--circle { width:28px; height:28px; }

/* bulk bar (§6.19) — glass, springs up when the first row is ticked (M29) */
.bulk { position:fixed; left:var(--sb-w); right:0; bottom:24px; z-index:var(--z-toast); display:flex; align-items:center; gap:8px; width:max-content; max-width:calc(100vw - 24px);
        margin:0 auto; padding:8px 8px 8px 18px; background:var(--glass); -webkit-backdrop-filter:saturate(1.6) blur(14px); backdrop-filter:saturate(1.6) blur(14px);
        border-radius:var(--r-xl); box-shadow:var(--e-3); animation:bar-in var(--dur-4) var(--ease-spring) backwards; }
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))) { .bulk { background:var(--raised); } }
html.rail .bulk { left:var(--rail-w); }
.bulk-n { font-size:14.5px; color:var(--text-2); white-space:nowrap; }
.bulk-n b { color:var(--text-1); }
.bulk-acts { display:flex; align-items:center; gap:6px; flex-wrap:wrap; padding-left:10px; border-left:1px solid var(--border); }
.bulk-x { margin-left:4px; }

/* tooltip [data-tip] (M33) — inverse chip; fades + rises 4 px after a 300 ms intent delay (charts keep their own hover card) */
[data-tip]:not(.hit) { position:relative; }
[data-tip]:not(.hit)::after { content:attr(data-tip); position:absolute; left:50%; bottom:calc(100% + 8px); z-index:var(--z-tooltip); padding:6px 9px; border-radius:var(--r-sm);
    background:var(--text-1); color:var(--sheet); font:400 13px/1.35 var(--font); white-space:nowrap; pointer-events:none;
    opacity:0; transform:translate(-50%, 4px); transition:opacity var(--dur-1) var(--ease-in), transform var(--dur-1) var(--ease-in); }
[data-tip]:not(.hit):hover::after, [data-tip]:not(.hit):focus-visible::after { opacity:1; transform:translate(-50%, 0); transition:opacity var(--dur-2) var(--ease-out) 300ms, transform var(--dur-2) var(--ease-out) 300ms; }

/* page header: tertiary actions (inline on a desktop, in the ⋯ menu on a phone) */
.ph-tert { display:flex; align-items:center; gap:8px; }
.ph-tert-m, .ph-more--phone { display:none; }
.ph-tert-m .btn, .ph-tert-m a, .ph-tert-m button { display:flex; width:100%; min-height:40px; justify-content:flex-start; gap:10px; padding:0 10px;
    border:0; border-radius:var(--r-sm); background:none; box-shadow:none; transform:none; color:var(--text-2); font:14.5px/1.2 var(--font); text-align:left; }
.ph-tert-m .btn:hover, .ph-tert-m a:hover, .ph-tert-m button:hover { background:var(--hover); color:var(--text-1); text-decoration:none; transform:none; box-shadow:none; }
.ph-tert-m .btn .i { color:var(--text-3); }
.ph-tert-m form { margin:0; }

/* server-rendered bar chart (§6.17) — bars grow from the baseline (M08), the 結餘 line draws */
.chart { padding:16px 20px 18px; }
.chart-head { display:flex; align-items:center; gap:10px 18px; flex-wrap:wrap; margin-bottom:6px; }
.legend { display:flex; gap:16px; font-size:var(--fs-sm); color:var(--text-2); flex-wrap:wrap; }
.legend span { display:inline-flex; align-items:center; gap:7px; }
.sw { width:10px; height:10px; border-radius:3px; display:inline-block; }
.sw--s1 { background:var(--series-1); } .sw--s2 { background:var(--series-2); } .sw--s3 { background:var(--text-3); }
.sw--line { width:14px; height:3px; border-radius:2px; background:var(--text-2); }
.chart-sum { font-size:var(--fs-sm); color:var(--text-3); }
.chart-sum b { color:var(--text-1); font-weight:600; font-variant-numeric:tabular-nums; }
.chart-seg { margin-left:auto; }
.chart-plot { display:grid; grid-template-columns:auto minmax(0,1fr); column-gap:10px; margin-top:16px; }
.chart-y { display:flex; flex-direction:column; justify-content:space-between; height:214px; margin:-7px 0; font-size:12.5px; line-height:14px; color:var(--text-3); text-align:right; font-variant-numeric:tabular-nums; }
.chart-area { position:relative; height:200px; }
.chart-area svg { display:block; width:100%; height:100%; overflow:visible; }
.chart .grid { stroke:var(--grid); stroke-width:1; vector-effect:non-scaling-stroke; }
.chart .axis { stroke:var(--axis); stroke-width:1; vector-effect:non-scaling-stroke; }
.chart .bar { transform-box:fill-box; transform-origin:50% 100%; }
.chart .bar.s1 { fill:var(--series-1); } .chart .bar.s2 { fill:var(--series-2); } .chart .bar.s3 { fill:var(--text-3); }
.chart .net { fill:none; stroke:var(--text-2); stroke-width:2; stroke-linecap:round; stroke-linejoin:round; vector-effect:non-scaling-stroke; }
.chart .net-d { fill:var(--sheet); stroke:var(--text-2); stroke-width:2; vector-effect:non-scaling-stroke; }
.chart .hit { fill:transparent; }
.chart .hit.is-hover { fill:var(--band); }
.chart-x { display:grid; grid-auto-flow:column; grid-auto-columns:minmax(0,1fr); padding-top:8px; font-size:12.5px; color:var(--text-3); text-align:center; }
.chart-x .is-cur { color:var(--text-1); font-weight:600; }
.chart-tip { position:absolute; top:12px; left:0; z-index:var(--z-tooltip); min-width:180px; padding:10px 12px; border-radius:var(--r-md); pointer-events:none;
             background:var(--raised); box-shadow:var(--e-3); font-size:13.5px; color:var(--text-2);
             opacity:0; transform:translateY(4px); transition:opacity var(--dur-1), transform var(--dur-1) var(--ease-out); white-space:nowrap; }
.chart-tip.is-on { opacity:1; transform:none; }
.chart-tip-h { color:var(--text-1); font-weight:600; margin-bottom:2px; }
/* direct value labels — positioned by classes only (column = grid order, height = .vy-n) */
.chart-vals { position:absolute; inset:0; display:grid; grid-auto-flow:column; grid-auto-columns:minmax(0,1fr); pointer-events:none; }
.cv-col { position:relative; }
.cv { position:absolute; margin-bottom:6px; padding:2px 6px; border-radius:var(--r-xs); background:var(--sheet); box-shadow:var(--e-1);
      font-size:12.5px; font-weight:600; line-height:1.3; color:var(--text-1); white-space:nowrap; font-variant-numeric:tabular-nums; }
.chart-vals--1 .cv { left:50%; transform:translateX(-50%); }
.chart-vals--2 .cv--s1, .chart-vals--3 .cv--s1 { right:calc(50% + 1px); }
.chart-vals--2 .cv--s2, .chart-vals--3 .cv--s3 { left:calc(50% + 1px); }
.chart-vals--3 .cv--s2 { left:50%; transform:translateX(-50%); margin-bottom:26px; }
.vy-0{bottom:0} .vy-2{bottom:2%} .vy-4{bottom:4%} .vy-6{bottom:6%} .vy-8{bottom:8%} .vy-10{bottom:10%} .vy-12{bottom:12%} .vy-14{bottom:14%}
.vy-16{bottom:16%} .vy-18{bottom:18%} .vy-20{bottom:20%} .vy-22{bottom:22%} .vy-24{bottom:24%} .vy-26{bottom:26%} .vy-28{bottom:28%} .vy-30{bottom:30%}
.vy-32{bottom:32%} .vy-34{bottom:34%} .vy-36{bottom:36%} .vy-38{bottom:38%} .vy-40{bottom:40%} .vy-42{bottom:42%} .vy-44{bottom:44%} .vy-46{bottom:46%}
.vy-48{bottom:48%} .vy-50{bottom:50%} .vy-52{bottom:52%} .vy-54{bottom:54%} .vy-56{bottom:56%} .vy-58{bottom:58%} .vy-60{bottom:60%} .vy-62{bottom:62%}
.vy-64{bottom:64%} .vy-66{bottom:66%} .vy-68{bottom:68%} .vy-70{bottom:70%} .vy-72{bottom:72%} .vy-74{bottom:74%} .vy-76{bottom:76%} .vy-78{bottom:78%}
.vy-80{bottom:80%} .vy-82{bottom:82%} .vy-84{bottom:84%} .vy-86{bottom:86%} .vy-88{bottom:88%} .vy-90{bottom:90%} .vy-92{bottom:92%} .vy-94{bottom:94%}
.vy-96{bottom:96%} .vy-98{bottom:98%} .vy-100{bottom:100%}

/* dashboard lists (尚欠名單 / 待處理 / 財務摘要) + the two-column dashboard grid */
.dgrid { display:grid; grid-template-columns:minmax(0,2fr) minmax(0,1fr); gap:20px; align-items:start; }
.dcol { display:grid; gap:20px; min-width:0; }
.lrows { list-style:none; margin:0; padding:6px 0; }
.lrow { display:flex; align-items:center; gap:12px; padding:10px 20px; color:inherit; transition:background-color var(--dur-1); }
a.lrow:hover { background:var(--hover); text-decoration:none; }
a.lrow:hover .chev { transform:translateX(3px); color:var(--text-2); }
.lrow-m { min-width:0; flex:1; }
.lrow-t { display:flex; align-items:center; flex-wrap:wrap; gap:4px 8px; font-size:var(--fs-md); color:var(--text-1); font-weight:600; line-height:1.45; }
.lrow-s { display:block; font-size:var(--fs-sm); color:var(--text-3); margin-top:2px; }
.lrow-ic { width:38px; height:38px; flex:none; border-radius:var(--r-md); display:grid; place-items:center; background:var(--subtle); color:var(--text-2); box-shadow:inset 0 0 0 1px var(--border); }
.lrow-ic .i { width:18px; height:18px; }
.lrow-n { min-width:30px; height:26px; padding:0 8px; border-radius:var(--r-full); display:inline-grid; place-items:center; background:var(--neutral-soft);
          font-size:13px; font-weight:600; color:var(--text-1); font-variant-numeric:tabular-nums; }
.lrow-n.is-hot { background:var(--warning-soft); color:var(--warning); box-shadow:inset 0 0 0 1px var(--warning-border); }
.lrow .chev { width:15px; height:15px; color:var(--text-4); transition:transform var(--dur-2) var(--ease-out), color var(--dur-1); }
.lrow .money { font-weight:600; font-size:var(--fs-md); white-space:nowrap; }
.cls { display:flex; align-items:center; gap:10px; min-width:0; }
.cls-t { color:var(--text-1); font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* 總覽 財務摘要: the amount is the emphasis, the label reads like a stat label */
.dash-kpi .lrow-t { font-weight:400; color:var(--text-2); }
.dash-kpi .lrow .money { font-weight:600; }

/* 收費 live calculation card (payment/create) */
.paygrid { display:grid; grid-template-columns:minmax(0,1fr) 360px; gap:24px; align-items:start; }
.paycard { position:sticky; top:calc(var(--tb-h) + 16px); }
.paymain { min-width:0; }
.fld-grow { flex:1; min-width:150px; }
.calc { margin:0; }
.calc-row { display:flex; justify-content:space-between; align-items:baseline; gap:12px; padding:7px 0; font-size:var(--fs-md); color:var(--text-2); }
.calc-row dd { margin:0; font-variant-numeric:tabular-nums; color:var(--text-1); }
.calc-row.is-sep { border-top:1px solid var(--border); margin-top:6px; padding-top:12px; }
.calc-row.is-total { border-top:1px solid var(--border-strong); margin-top:8px; padding-top:14px; font-weight:600; color:var(--text-1); }
.calc-row.is-total dd, .calc-row.is-big dd { font:600 24px/1.2 var(--font-display); letter-spacing:-.01em; }
.calc-row .money--neg, .calc-row dd.money--neg { color:var(--money-neg); }
.calc-row dd.money--pos { color:var(--money-pos); }
.calc-row dd.money--zero { color:var(--text-3); }
.calc-warn { margin-top:10px; }
.who { display:flex; align-items:center; gap:14px; }
.who .avatar { width:40px; height:40px; font-size:15px; }
.who-t { font-size:var(--fs-lg); font-weight:600; color:var(--text-1); }
.who-s { font-size:var(--fs-sm); color:var(--text-3); margin-top:2px; }

/* ui-kit page helpers */
.kit-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(260px,1fr)); gap:16px; }
#kit-tokens .kit-grid { grid-template-columns:repeat(auto-fill, minmax(120px,1fr)); gap:12px; }
.kit-row { display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
.kit-swatch { height:44px; border-radius:var(--r-sm); box-shadow:inset 0 0 0 1px var(--border); }
.kit-sec { margin-bottom:32px; }
.kit-sec > h2 { font-size:var(--fs-lg); margin-bottom:14px; }
.kit-toasts { display:grid; gap:10px; max-width:420px; }
.kit-toasts .toast { animation:none; }
.kit-toasts .toast-timer { animation-play-state:paused; }
.sw-canvas { background:var(--canvas); } .sw-sheet { background:var(--sheet); } .sw-subtle { background:var(--subtle); } .sw-raised { background:var(--raised); }
.sw-accent { background:var(--accent); } .sw-accent-soft { background:var(--accent-soft); } .sw-success { background:var(--success-soft); }
.sw-warning { background:var(--warning-soft); } .sw-danger { background:var(--danger-soft); } .sw-s1 { background:var(--series-1); } .sw-s2 { background:var(--series-2); }
.sw-text-1 { background:var(--text-1); } .sw-text-3 { background:var(--text-3); } .sw-border { background:var(--border-strong); }
.sw-hover { background:var(--hover); } .sw-inset { background:var(--inset); } .sw-glow { background:var(--grad-glow), var(--sheet); } .sw-tile { background:var(--grad-tile); }
.sw-primary { background:var(--grad-primary); } .sw-brand { background:var(--grad-brand); }

/* description list (detail pages) */
.dl { display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:16px 24px; margin:0; }
.dl--1 { grid-template-columns:minmax(0,1fr); }
.dl--3 { grid-template-columns:repeat(3, minmax(0,1fr)); }
.dl--4 { grid-template-columns:repeat(4, minmax(0,1fr)); }
.dl-i { min-width:0; }
.dl-i--wide { grid-column:1 / -1; }
.dl dt { font-size:var(--fs-sm); color:var(--text-3); margin-bottom:2px; }
.dl dd { margin:0; font-size:var(--fs-md); color:var(--text-1); overflow-wrap:anywhere; }
.dl .nil { color:var(--text-3); }

/* timeline (history / audit) */
.tl { list-style:none; margin:0; padding:0; }
.tl-i { position:relative; display:flex; gap:12px; padding:0 0 18px; }
.tl-i:not(:last-child)::before { content:""; position:absolute; left:11px; top:26px; bottom:0; width:1px; background:var(--border-strong); }
.tl-dot { position:relative; width:24px; height:24px; flex:none; border-radius:50%; display:grid; place-items:center; background:var(--neutral-soft);
          color:var(--text-3); box-shadow:inset 0 0 0 1px var(--border-strong); }
.tl-dot:empty::after { content:""; width:7px; height:7px; border-radius:50%; background:var(--text-4); }
.tl-i--ok .tl-dot { background:var(--success-soft); color:var(--success); box-shadow:inset 0 0 0 1px var(--success-border); } .tl-i--ok .tl-dot:empty::after { background:var(--success-dot); }
.tl-i--info .tl-dot { background:var(--accent-soft); color:var(--accent-text); box-shadow:inset 0 0 0 1px var(--accent-border); } .tl-i--info .tl-dot:empty::after { background:var(--accent); }
.tl-i--warn .tl-dot { background:var(--warning-soft); color:var(--warning); box-shadow:inset 0 0 0 1px var(--warning-border); } .tl-i--warn .tl-dot:empty::after { background:var(--warning-dot); }
.tl-i--bad .tl-dot { background:var(--danger-soft); color:var(--danger); box-shadow:inset 0 0 0 1px var(--danger-border); } .tl-i--bad .tl-dot:empty::after { background:var(--danger-dot); }
.tl-dot .i { width:13px; height:13px; }
.tl-m { flex:1; min-width:0; padding-top:1px; }
.tl-h { display:flex; align-items:baseline; gap:8px; flex-wrap:wrap; }
.tl-t { font-size:var(--fs-md); font-weight:600; color:var(--text-1); }
.tl-at { margin-left:auto; font-size:13px; color:var(--text-3); white-space:nowrap; }
.tl-who { font-size:var(--fs-sm); color:var(--text-3); margin-top:2px; }
.tl-b { font-size:14.5px; line-height:var(--lh-body); color:var(--text-2); margin-top:4px; }
.tl-chg { margin:8px 0 0; padding:8px 12px; border-radius:var(--r-md); background:var(--subtle); box-shadow:inset 0 0 0 1px var(--border); font-size:var(--fs-sm); }
.tl-chg-i { display:flex; gap:10px; padding:2px 0; }
.tl-chg dt { width:104px; flex:none; color:var(--text-3); }
.tl-chg dd { margin:0; min-width:0; color:var(--text-2); overflow-wrap:anywhere; }
.tl-chg del { color:var(--text-3); text-decoration-color:var(--text-4); }
.tl-chg ins { color:var(--text-1); text-decoration:none; font-weight:600; }
.tl-arrow { color:var(--text-4); }
.tl-chg .nil { color:var(--text-3); }

/* a record's danger zone (the LAST card of a detail / settings page): a danger ring */
.card.card--danger { box-shadow:0 0 0 1px var(--danger-border), 0 1px 2px rgba(15,23,42,.05); }
.card--danger > .card-h .card-t { color:var(--danger); }
/* logos / letterhead previews are designed for paper: a white chip in both themes */
.paper-chip { display:inline-grid; place-items:center; padding:8px 12px; border-radius:var(--r-md); background:#fff; box-shadow:inset 0 0 0 1px var(--border-strong); }
.paper-chip img { display:block; max-height:64px; }

/* ── 5b. shared requests of the page-rebuild wave (integration) ──────────────────────────
   Each rule replaced a page-local stop-gap; the pages now use these instead. */
/* report parameter pieces (components/report/*): 年度範圍 ── and the 課程範圍 radios */
.rpt-yr { display:flex; align-items:flex-end; gap:8px; flex-wrap:wrap; }
.rpt-yr .fld { width:140px; }
.rpt-yr-sep { padding-bottom:10px; color:var(--text-3); }
.rpt-scope { display:flex; align-items:flex-end; gap:16px; flex-wrap:wrap; }
/* a scrolling contained table keeps its absolutely-placed .sr-only text inside the scroll box; is-short = a picker
   inside a form (fixed height instead of the viewport-based one) */
.card--table.is-contained .dt-wrap { position:relative; }
.card--table.is-contained.is-short .dt-wrap { max-height:420px; }
/* a contained table's header still sticks inside a titled flush card (the flush rule un-sticks page tables) */
.card--flush .card--table.is-contained .dt th { position:sticky; top:0; }
/* a titled x-table folds its frame into a flush card even with a <form> between them */
.card--flush .card-b > form > .card--table, .card--flush .card-b > form > .dt-wrap { margin:0; border:0; border-radius:0; box-shadow:none; background:transparent; }
/* a checkbox column that stays on a phone (picker tables; .c-chk is bulk selection and hides) */
.dt th.c-pick, .dt td.c-pick { width:44px; padding-right:0; }
/* shrink-to-fit column (a verb, a short status) */
.dt th.c-fit, .dt td.c-fit { width:1%; white-space:nowrap; }
/* a two-column grid of cards (pairs of titled tables); one column below 1024 */
.split-2 { display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:20px; align-items:start; margin-bottom:20px; }
.split-2 > .card, .split-2 > .card--table { margin:0; }
/* the form + live card layout (.paygrid): below 1600 the fields beside the 360px card are too narrow for the 240px
   section-title column — stack each section's title above its fields */
@media (max-width:1599px) { .paymain .fsec { grid-template-columns:minmax(0,1fr); gap:14px; } }
/* print helpers for app pages printed with Ctrl+P (print.css has its own for layouts.print) */
.print-only { display:none; }
/* stat values: shrink before they break mid-word on a 4-up row at 1024–1279 */
@media (min-width:1024px) and (max-width:1279px) { .stats:not(.stats--2):not(.stats--3):not(.stats--strip) .stat-v { font-size:26px; } }

/* ── 6. utilities ──────────────────────────────────────────────────────────────────────── */
.stack { display:grid; gap:16px; }
.row { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.mt-0 { margin-top:0; } .mb-0 { margin-bottom:0; } .mb-16 { margin-bottom:16px; } .mt-16 { margin-top:16px; }
.text-3 { color:var(--text-3); }
.prose { font-size:14.5px; line-height:1.75; color:var(--text-2); }
.nowrap { white-space:nowrap; }
.form > .form-errors { margin-bottom:0; }

/* accordion <details class="acc"> (M16): a contained 320 ms height disclosure (::details-content + interpolate-size)
   with a rotating caret; browsers without ::details-content simply snap open */
details.acc { background:var(--sheet); border-radius:var(--r-lg); box-shadow:var(--e-1); }
details.acc + details.acc { margin-top:10px; }
details.acc > summary { display:flex; align-items:center; gap:10px; min-height:50px; padding:0 18px; border-radius:var(--r-lg); cursor:pointer; list-style:none;
                        font-weight:600; color:var(--text-1); transition:background-color var(--dur-1); }
details.acc > summary::-webkit-details-marker { display:none; }
details.acc > summary:hover { background:var(--hover); }
details.acc > summary::after { content:""; margin-left:auto; width:7px; height:7px; border-right:1.5px solid var(--text-3); border-bottom:1.5px solid var(--text-3);
                               transform:translateY(-2px) rotate(45deg); transition:transform var(--dur-3) var(--ease-out); flex:none; }
details.acc[open] > summary::after { transform:translateY(2px) rotate(-135deg); }
details.acc::details-content { height:0; overflow:clip; transition:height var(--dur-4) var(--ease-out), content-visibility var(--dur-4) allow-discrete; }
details.acc[open]::details-content { height:auto; }
.acc-b { padding:2px 18px 18px; color:var(--text-2); }
/* ── 7. MOTION (§7) ─────────────────────────────────────────────────────────────────────── */
/* 7.1 keyframes — every one reads the distance tokens, so "reduce / off" turns them into pure fades.
   No keyframe animates width / height / top / left / margin / padding (MotionContractTest). */
@keyframes rise        { from { opacity:0; transform:translateY(var(--m-rise)); } to { opacity:1; transform:none; } }
@keyframes rise-row    { from { opacity:0; transform:translateY(var(--m-rise-sm)); } to { opacity:1; transform:none; } }
@keyframes fade-in     { from { opacity:0; } to { opacity:1; } }
@keyframes fade-out    { to { opacity:0; } }
@keyframes pop-in      { from { opacity:0; transform:translateY(calc(-1 * var(--m-rise-sm))) scale(var(--m-pop)); } to { opacity:1; transform:none; } }
@keyframes panel-in    { from { opacity:0; transform:translateY(var(--m-rise-sm)); } to { opacity:1; transform:none; } }
@keyframes page-in     { from { opacity:0; transform:translateY(var(--m-rise-sm)); } to { opacity:1; transform:none; } }   /* v1 name (page CSS) */
@keyframes modal-in    { from { opacity:0; transform:translateY(calc(-1 * var(--m-rise))) scale(.98); } to { opacity:1; transform:none; } }   /* v1 name */
@keyframes toast-in    { from { opacity:0; transform:translateY(var(--m-slide)) scale(var(--m-pop)); } to { opacity:1; transform:none; } }
@keyframes toast-out   { to { opacity:0; transform:translateX(var(--m-slide)) scale(.98); } }
@keyframes bar-in      { from { opacity:0; transform:translateY(var(--m-slide)); } to { opacity:1; transform:none; } }
@keyframes sheet-in    { from { transform:translateY(100%); } to { transform:none; } }
@keyframes chip-in     { from { opacity:0; transform:scale(.88); } to { opacity:1; transform:none; } }
@keyframes err-in      { from { opacity:0; transform:translateY(calc(-1 * var(--m-rise-sm))); } to { opacity:1; transform:none; } }
@keyframes grow-y      { from { transform:scaleY(0); } to { transform:none; } }
@keyframes grow-x      { from { transform:scaleX(0); } to { transform:none; } }
@keyframes draw        { from { stroke-dashoffset:1; } to { stroke-dashoffset:0; } }
@keyframes countdown   { from { transform:scaleX(1); } to { transform:scaleX(0); } }
@keyframes ck-draw     { to { stroke-dashoffset:0; } }
@keyframes burst-fill  { to { transform:scale(1); } }
@keyframes ray         { 0% { opacity:1; transform:rotate(var(--a)) translateY(-9px); } 100% { opacity:0; transform:rotate(var(--a)) translateY(-24px) scale(.4); } }
@keyframes check-pop   { to { transform:rotate(-45deg) scale(1); } }
@keyframes spin        { to { transform:rotate(360deg); } }
@keyframes shimmer     { to { transform:translateX(100%); } }
@keyframes float       { 0%,100% { transform:translateY(0); } 50% { transform:translateY(calc(-1 * var(--m-rise))); } }
@keyframes float-shadow{ 0%,100% { transform:scaleX(1); opacity:1; } 50% { transform:scaleX(.86); opacity:.6; } }
@keyframes row-flash   { 0% { opacity:0; } 12% { opacity:1; } 70% { opacity:1; } 100% { opacity:0; } }
@keyframes row-hold    { 0% { opacity:.6; } 100% { opacity:0; } }   /* with step-end: a static tint, then gone (reduce / off) */
@keyframes shake       { 0%,100% { transform:none; } 20% { transform:translateX(calc(-1 * var(--m-shake))); } 40% { transform:translateX(var(--m-shake)); }
                         60% { transform:translateX(calc(-.6 * var(--m-shake))); } 80% { transform:translateX(calc(.4 * var(--m-shake))); } }
@keyframes dot-live    { 0%,100% { box-shadow:0 0 0 0 transparent; } 50% { box-shadow:0 0 0 4px var(--accent-soft); } }
@keyframes dot-pop     { from { transform:scale(0); } to { transform:scale(1); } }
@keyframes ring-pulse  { from { box-shadow:inset 0 0 0 2px var(--danger-dot), 0 0 0 0 rgba(229,72,77,.35); } to { box-shadow:inset 0 0 0 2px var(--danger-dot), 0 0 0 8px rgba(229,72,77,0); } }
@keyframes vt-out      { to { opacity:0; transform:translateY(calc(-1 * var(--m-rise-sm))); } }
@keyframes vt-in       { from { opacity:0; transform:translateY(var(--m-rise)); } }
@keyframes navprog     { from { transform:scaleX(0); } to { transform:scaleX(.9); } }

/* 7.2 page cascade (M04) + stagger (M05) — CSS-only indices (never style="--i"): the first 12 children step by
   --stagger; the rest appear at once. Entrances use `backwards` (never `both`): a finished entrance never
   overrides a :hover / :active transform, and nothing is ever held hidden. */
.page-in > :not(.bulk, .toasts, dialog) { animation:rise var(--dur-3) var(--ease-out) backwards; }
.page-in > :nth-child(2):not(.bulk, .toasts, dialog) { animation-delay:40ms; }
.page-in > :nth-child(3):not(.bulk, .toasts, dialog) { animation-delay:80ms; }
.page-in > :nth-child(n+4):not(.bulk, .toasts, dialog) { animation-delay:120ms; }
.st > * { animation:rise var(--dur-3) var(--ease-out) backwards; animation-delay:calc(var(--i, 0) * var(--stagger)); }
.st--rows > tr { animation-name:rise-row; }
.st > :nth-child(2){--i:1} .st > :nth-child(3){--i:2} .st > :nth-child(4){--i:3} .st > :nth-child(5){--i:4} .st > :nth-child(6){--i:5}
.st > :nth-child(7){--i:6} .st > :nth-child(8){--i:7} .st > :nth-child(9){--i:8} .st > :nth-child(10){--i:9} .st > :nth-child(11){--i:10} .st > :nth-child(12){--i:11}
.st > :nth-child(n+13) { animation:none; }
.st--slow { --stagger:60ms; }
/* a tile row / card grid inside the cascade starts after its own block (header 0 → tiles 40 → cards 80–120) */
.page-in > .st:nth-child(2) > * { animation-delay:calc(40ms + var(--i, 0) * var(--stagger)); }
.page-in > .st:nth-child(3) > * { animation-delay:calc(80ms + var(--i, 0) * var(--stagger)); }
.page-in > .st:nth-child(n+4) > * { animation-delay:calc(120ms + var(--i, 0) * var(--stagger)); }
.page-in > .st:nth-child(n) > :nth-child(n+13) { animation:none; }
/* progressive: one rule instead of the nth-child ladder where sibling-index() exists (same cap) */
@supports (z-index: sibling-index()) {
    .st > * { --i:min(sibling-index() - 1, 11); }
}
/* never replay: bfcache restore (app.js adds html.no-enter on pageshow.persisted) and a validation re-render
   (the server adds .no-enter to <main> when $errors->any()) show the final state at once */
html.no-enter .page-in > *, html.no-enter .st > *, .page-in.no-enter > *, .no-enter .st > *, .no-enter .st > * > * { animation:none; }
/* the first frame never runs shell transitions (theme-init adds .is-booting, app.js drops it after 2 rAF) */
html.is-booting *, html.is-booting *::before, html.is-booting *::after { transition:none !important; }
/* navigation progress (M39) — a 2 px hairline while a same-origin page load is pending: the top bar's brand accent,
   brand blue rising into brand green (上升感; a non-text graphic, decoration only — BRAND-SPEC §5) */
.navprog { position:fixed; left:0; right:0; top:0; z-index:var(--z-progress); height:2px;
           background:linear-gradient(90deg, var(--gg-blue) 0%, var(--gg-blue) 55%, var(--gg-green) 100%); transform-origin:left center;
           transform:scaleX(0); opacity:0; pointer-events:none; }
html.is-nav .navprog { opacity:1; animation:navprog 2400ms var(--ease-out) forwards; }

/* 7.3 data — charts grow (M08), sparklines draw (M07), meters fill (M09) */
.chart .bar { animation:grow-y var(--dur-5) var(--ease-out) backwards; }
.chart svg > g:nth-of-type(2) .bar, .chart g.m:nth-child(2) .bar { animation-delay:40ms; } .chart svg > g:nth-of-type(3) .bar, .chart g.m:nth-child(3) .bar { animation-delay:80ms; }
.chart svg > g:nth-of-type(4) .bar, .chart g.m:nth-child(4) .bar { animation-delay:120ms; } .chart svg > g:nth-of-type(5) .bar, .chart g.m:nth-child(5) .bar { animation-delay:160ms; }
.chart svg > g:nth-of-type(6) .bar, .chart g.m:nth-child(6) .bar { animation-delay:200ms; } .chart svg > g:nth-of-type(n+7) .bar, .chart g.m:nth-child(n+7) .bar { animation-delay:240ms; }
.chart .bar.s2 { animation-duration:calc(var(--dur-5) + 40ms); }
.chart .net { stroke-dasharray:1; animation:draw 600ms var(--ease-in-out) 300ms backwards; }
.chart .net-d, .chart-vals { animation:fade-in var(--dur-3) var(--ease-out) 700ms backwards; }
.spark .spark-l { stroke-dasharray:1; animation:draw 700ms var(--ease-in-out) 200ms backwards; }
.spark .spark-a { animation:fade-in 400ms var(--ease-out) 600ms backwards; }
.spark .spark-d { animation:chip-in var(--dur-3) var(--ease-spring) 850ms backwards; transform-box:fill-box; transform-origin:center; }
progress.meter::-webkit-progress-value { animation:grow-x var(--dur-5) var(--ease-out) 200ms backwards; }
progress.meter::-moz-progress-bar { animation:grow-x var(--dur-5) var(--ease-out) 200ms backwards; }
/* below-the-fold reveal: app.js adds html.io and marks .reveal.is-in when it enters the viewport;
   without JS (or before) nothing is held — every animation simply runs on load */
html.io .reveal:not(.is-in), html.io .reveal:not(.is-in) * { animation-play-state:paused !important; }
html.io .reveal:not(.is-in) progress.meter::-webkit-progress-value { animation-play-state:paused !important; }
/* after a save (M26): the saved row glows once — the opacity of an overlay, never the cell background */
.dt tr.is-saved td { position:relative; isolation:isolate; }
.dt tr.is-saved td::before { content:""; position:absolute; inset:0; background:var(--accent-soft); box-shadow:inset 0 1px 0 var(--accent-border), inset 0 -1px 0 var(--accent-border);
                             z-index:-1; animation:row-flash 2400ms var(--ease-std) 300ms both; pointer-events:none; }
.dt tr.is-saved td:first-child::after { content:""; position:absolute; left:0; top:0; bottom:0; width:3px; background:var(--accent); animation:row-flash 2400ms var(--ease-std) 300ms both; pointer-events:none; }
/* scroll-to-error (M27): smooth scroll (JS) + one gentle shake + a ring pulse */
.is-shake { animation:shake 360ms var(--ease-std) 1; }
[aria-invalid="true"].is-pulse { animation:ring-pulse 700ms var(--ease-out) 2; }
[aria-invalid="true"].is-shake.is-pulse { animation:shake 360ms var(--ease-std) 1, ring-pulse 700ms var(--ease-out) 360ms 2; }

/* 7.4 view transitions — cross-document (MPA) progressive enhancement; the shell parts are named so they stay put */
@view-transition { navigation:auto; types:nav; }
::view-transition-group(*) { animation-duration:var(--dur-4); animation-timing-function:var(--ease-out); }
/* indicators glide with the spring: the nav accent bar and the active tab underline */
::view-transition-group(vt-nav-ind), ::view-transition-group(vt-tab-ind) { animation-duration:var(--dur-4); animation-timing-function:var(--ease-spring); }
::view-transition-group(vt-sidebar), ::view-transition-group(vt-topbar) { overflow:clip; }
::view-transition-old(vt-sidebar), ::view-transition-new(vt-sidebar) { inline-size:auto; block-size:100%; object-fit:none; object-position:left top; }
::view-transition-old(vt-topbar), ::view-transition-new(vt-topbar) { inline-size:auto; block-size:100%; object-fit:none; object-position:right top; }
/* type nav (link navigation): content leaves up in 120 ms, the new page rises in; the box never morphs */
html:active-view-transition-type(nav)::view-transition-group(vt-page) { animation-duration:0s; }
html:active-view-transition-type(nav)::view-transition-old(vt-page) { animation:vt-out var(--dur-1) var(--ease-in) both; }
html:active-view-transition-type(nav)::view-transition-new(vt-page) { animation:vt-in var(--dur-3) var(--ease-out) 60ms both; }
/* type rail (sidebar collapse / expand, M17): the sidebar's clip box narrows, the content cross-fades — snapshots, no reflow per frame */
html:active-view-transition-type(rail)::view-transition-group(vt-page) { overflow:clip; }
html:active-view-transition-type(rail)::view-transition-old(vt-page),
html:active-view-transition-type(rail)::view-transition-new(vt-page) { inline-size:auto; block-size:auto; object-position:left top; }
html:active-view-transition-type(rail)::view-transition-old(vt-page) { animation:fade-out var(--dur-2) var(--ease-in) both; }
html:active-view-transition-type(rail)::view-transition-new(vt-page) { animation:fade-in var(--dur-3) var(--ease-out) 60ms backwards; }
/* type theme (M34): one calm 240 ms cross-fade of the whole page — the shell parts lose their names so nothing slides */
html:active-view-transition-type(theme) .sb, html:active-view-transition-type(theme) .tb, html:active-view-transition-type(theme) .page,
html:active-view-transition-type(theme) .nav-ind, html:active-view-transition-type(theme) .tabs-ind { view-transition-name:none; }
html:active-view-transition-type(theme)::view-transition-group(root) { animation-duration:240ms; animation-timing-function:var(--ease-std); }

/* 7.5 preference levels — html[data-motion="calm"|"off"] (user menu 動態效果, applied pre-paint) + the OS setting.
   calm = feedback survives (hover, press, focus, toggles, overlays, indicators); orientation and decoration
          (cascade, stagger, count-up, chart / sparkline / meter growth, empty float, live dot) do not.
   off / OS reduce = every distance → 0, overlays become ≤ 120 ms fades; no stagger, loops, shimmer, count-up,
          shake, burst rays or page view transition. Content is always the server-rendered final state. */
html[data-motion="calm"] { --stagger:0ms; }
html[data-motion="calm"] .st > *, html[data-motion="calm"] .page-in > *, html[data-motion="calm"] .chart .bar, html[data-motion="calm"] .chart .net,
html[data-motion="calm"] .chart .net-d, html[data-motion="calm"] .chart-vals, html[data-motion="calm"] .spark *, html[data-motion="calm"] .empty-art *,
html[data-motion="calm"] .badge--live::before, html[data-motion="calm"] [role="tabpanel"] { animation:none; }
html[data-motion="calm"] progress.meter::-webkit-progress-value { animation:none; }
html[data-motion="calm"] progress.meter::-moz-progress-bar { animation:none; }
html[data-motion="calm"]::view-transition-old(vt-page) { animation:fade-out var(--dur-1) linear both; }
html[data-motion="calm"]::view-transition-new(vt-page) { animation:fade-in var(--dur-1) linear backwards; }
html[data-motion="calm"]::view-transition-group(vt-nav-ind), html[data-motion="calm"]::view-transition-group(vt-tab-ind) { animation-timing-function:var(--ease-out); }
@media (prefers-reduced-motion: reduce) {
    :root { --m-rise:0px; --m-rise-sm:0px; --m-pop:1; --m-slide:0px; --m-lift:0px; --m-lift-btn:0px; --m-press:1; --m-shake:0px; --stagger:0ms;
            --dur-3:120ms; --dur-4:120ms; --dur-5:0ms; --ease-spring:var(--ease-std); }
    @view-transition { navigation:none; }
    html { scroll-behavior:auto; }
    .st > *, .page-in > *, .chart .bar, .chart .net, .chart .net-d, .chart-vals, .spark *, .empty-art *, .badge--live::before,
    .is-shake, .is-pulse, .toast-timer, .burst i, [role="tabpanel"], .fbar-chip, .field-err, .pal-list .pal-o, .pal-list .pal-g, .ab-dirty i { animation:none !important; }
    /* M26 reduce: a static tint for 2.4 s, then gone — an opacity step, no fade (not a permanent "selected" look) */
    .dt tr.is-saved td::before, .dt tr.is-saved td:first-child::after { animation:row-hold 2400ms step-end 300ms both !important; }
    .skel::after, [aria-busy="true"] .skel::after { animation:none !important; }
    progress.meter::-webkit-progress-value { animation:none !important; }
    progress.meter::-moz-progress-bar { animation:none !important; }
    .burst-ring, .burst-tick { animation:none; stroke-dashoffset:0; }
    .burst-fill { animation:none; transform:none; }
    .btn[aria-busy="true"]::after, .busy-note::before { animation:none; border-right-color:transparent; }
    .tabs-ind, .seg-thumb, .nav-gp, .nav-caret, details.acc::details-content, details.acc > summary::after { transition:none !important; }
    html.is-nav .navprog { animation:none; transform:scaleX(.6); }
}
html[data-motion="off"] { --m-rise:0px; --m-rise-sm:0px; --m-pop:1; --m-slide:0px; --m-lift:0px; --m-lift-btn:0px; --m-press:1; --m-shake:0px; --stagger:0ms;
                          --dur-3:120ms; --dur-4:120ms; --dur-5:0ms; --ease-spring:var(--ease-std); scroll-behavior:auto; }
html[data-motion="off"] .st > *, html[data-motion="off"] .page-in > *, html[data-motion="off"] .chart .bar, html[data-motion="off"] .chart .net,
html[data-motion="off"] .chart .net-d, html[data-motion="off"] .chart-vals, html[data-motion="off"] .spark *, html[data-motion="off"] .empty-art *,
html[data-motion="off"] .skel::after, html[data-motion="off"] .badge--live::before, html[data-motion="off"] .is-shake, html[data-motion="off"] .is-pulse,
html[data-motion="off"] .toast-timer, html[data-motion="off"] .burst i, html[data-motion="off"] [role="tabpanel"], html[data-motion="off"] .fbar-chip,
html[data-motion="off"] .field-err, html[data-motion="off"] .pal-list .pal-o, html[data-motion="off"] .pal-list .pal-g, html[data-motion="off"] .ab-dirty i { animation:none !important; }
html[data-motion="off"] progress.meter::-webkit-progress-value { animation:none !important; }
html[data-motion="off"] progress.meter::-moz-progress-bar { animation:none !important; }
html[data-motion="off"] .dt tr.is-saved td::before, html[data-motion="off"] .dt tr.is-saved td:first-child::after { animation:row-hold 2400ms step-end 300ms both !important; }
html[data-motion="off"] .burst-ring, html[data-motion="off"] .burst-tick { animation:none; stroke-dashoffset:0; }
html[data-motion="off"] .burst-fill { animation:none; transform:none; }
html[data-motion="off"] .tabs-ind, html[data-motion="off"] .seg-thumb, html[data-motion="off"] .nav-gp, html[data-motion="off"] .nav-caret,
html[data-motion="off"] details.acc::details-content, html[data-motion="off"] details.acc > summary::after { transition:none !important; }
html[data-motion="off"] .navprog { display:none; }
html[data-motion="off"]::view-transition-group(*), html[data-motion="off"]::view-transition-old(*), html[data-motion="off"]::view-transition-new(*) { animation:none !important; }
/* the theme switch never animates tokens themselves (one frame of transition:none) */
html.theme-swap *, html.theme-swap *::before, html.theme-swap *::after { transition:none !important; }

/* ── 8. responsive — 1280 · 1024 · 768 · 390 ───────────────────────────────────────────── */
@media (max-width:1279px) {
    :root { --gutter:24px; }
    .tb-search { width:240px; }
    .tb-user-n { display:none; }
    .dt .c-lg { display:none; }
    form:has(> .fsec) > .actionbar { margin-left:0; }
}
@media (max-width:1023px) {
    .dgrid { grid-template-columns:minmax(0,1fr); }
    .dcol { display:contents; }
    .dash-today { order:1; } .dash-arrears { order:2; } .dash-todo { order:3; } .dash-chart { order:4; } .dash-kpi { order:5; }
    .fsec { grid-template-columns:minmax(0,1fr); gap:14px; }
    .paygrid { grid-template-columns:minmax(0,1fr); }
    .paycard { position:static; }
    .stats { grid-template-columns:repeat(2, minmax(0,1fr)); }
    .dl--3, .dl--4 { grid-template-columns:repeat(2, minmax(0,1fr)); }
    /* an odd tile count on the 2-column grid: the last tile takes the whole row instead of leaving a hole */
    .stats > .stat:last-child:nth-child(odd) { grid-column:1 / -1; }
    .split-2 { grid-template-columns:minmax(0,1fr); }
}
@media (max-width:767px) {
    /* the off-canvas menu is out of the tab order / a11y tree until opened (app.js also sets inert — AUD-117) */
    :root { --gutter:16px; --fs-3xl:24px; --fs-2xl:22px; --row:var(--row-phone); --tb-h:56px; }
    .app, html.rail .app { padding-left:0; }
    .sb, html.rail .sb { position:fixed; inset:0 auto 0 0; width:300px; align-items:stretch; padding:10px; background:var(--sb-bg); z-index:var(--z-drawer);
          box-shadow:var(--e-4); transform:translateX(-102%); visibility:hidden; view-transition-name:none;
          transition:transform var(--dur-3) var(--ease-in), visibility 0s linear var(--dur-3); }
    html.nav-open .sb { transform:none; visibility:visible; transition:transform var(--dur-4) var(--ease-out); }
    html.nav-open .sb-scrim { display:block; position:fixed; inset:0; background:var(--scrim); z-index:calc(var(--z-drawer) - 1); animation:fade-in var(--dur-3) linear backwards; }
    .nav-ind { view-transition-name:none; }
    /* the drawer always shows the FULL menu — ignore a saved desktop rail */
    html.rail .sb-brand { flex-direction:row; height:52px; }
    html.rail .brand { flex:1; }
    html.rail .brand-text, html.rail .nav-gl, html.rail .nav-grp > .nav-g .nav-caret, html.rail .nav-l, html.rail .nav-pin .nav-c, html.rail .sb-help-t, html.rail .sb-help kbd { display:revert; }
    html.rail .nav-l { display:block; }
    html.rail .sb-nav { overflow-y:auto; margin:0 -10px; padding:0 10px 12px; }
    html.rail .nav-pin { display:grid; }
    html.rail .nav-pin .nav-i { width:auto; padding:0 10px; justify-content:flex-start; }
    html.rail .nav-i { width:auto; padding:0 10px 0 37px; justify-content:flex-start; margin:0; }
    html.rail .nav-g { justify-content:flex-start; width:100%; padding:0 10px; margin:0; }
    html.rail .nav-grp > .nav-gp { display:block; }
    html.rail .nav-grp.has-active > .nav-g { background:none; box-shadow:none; }
    html.rail .sb-help { width:100%; justify-content:flex-start; padding:0 10px; margin:0; }
    .nav-i, .nav-g, .nav-pin .nav-i, html.rail .nav-i, html.rail .nav-g, html.rail .nav-pin .nav-i { height:var(--h-touch); }
    .sb-collapse { display:none; }
    .sheet { margin:0; border-radius:0; box-shadow:none; min-height:100vh; }
    .tb { border-radius:0; padding:0 8px 0 6px; gap:2px; min-height:56px; }
    .btn.tb-burger { display:inline-flex; }
    .crumbs li:not(.is-m) { display:none; }
    .crumbs li + li::before { display:none; }
    .crumbs li.is-m { font-size:16px; }
    .crumbs li.is-m .crumb-g { color:var(--text-1); font-weight:600; }
    .crumbs li.is-m a { display:inline-flex; align-items:center; gap:2px; color:var(--text-1); font-weight:600; }
    .crumbs li.is-m .crumb-back { display:inline-block; }
    .tb-search { width:44px; height:44px; padding:0; margin:0; justify-content:center; background:none; box-shadow:none; color:var(--text-2); }
    .tb-search-t, .tb-kbd, .tb-div, .tb-caret { display:none; }
    .tb-user-n { display:none; }
    .btn, .tb-user { --h:var(--h-touch); }
    .btn--sm, .btn.sm { --h:36px; }
    .btn kbd, kbd.hint-k { display:none; }
    input[type=text],input[type=email],input[type=date],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=month],input[type=time],input[type=url],select,textarea { min-height:var(--h-touch); font-size:16px; }
    .page { padding:18px var(--gutter) 96px; }
    .banners { padding:12px var(--gutter) 0; }
    .ph { align-items:flex-start; margin-bottom:16px; gap:12px; }
    .ph-actions { width:100%; }
    .ph-meta { font-size:14.5px; }
    /* dashboard 今日課程 on a phone: name + attendance text + status (the bar and the class chip drop) */
    .dash-today .cls .chip, .dash-today .meter-row progress { display:none; }
    .dash-today .dt-name { display:block; margin-top:2px; }
    .stats { gap:10px; }
    .stats.stats--strip { gap:0; }                 /* the strip's hairlines are its gaps */
    .stat { padding:14px; }
    .stat-ic { width:30px; height:30px; }
    .spark { display:none; }
    .segf { min-height:var(--h-touch); }           /* 44 px touch targets like every other phone control */
    .segf label > span { min-height:38px; }
    .seg button { height:38px; }
    .fgrid > .fld--check.full-sm { padding-top:0; }
    .actionbar--card .ab-cancel { display:inline-flex; }   /* a card-embedded editor keeps its way out */
    .actionbar--card { padding-bottom:calc(12px + env(safe-area-inset-bottom)); }
    .tabs { margin-left:calc(-1 * var(--gutter)); margin-right:calc(-1 * var(--gutter)); padding:0 var(--gutter); }
    .tabs a, .tabs .tab { min-height:44px; padding:0 12px; }
    .fbar { gap:8px; }
    .fsearch { width:auto; flex:1 1 100%; }
    .fsearch kbd { display:none; }
    .fbar-sp, .fbar-meta { display:none; }
    .dt .c-opt, .dt .c-chk { display:none; }
    .dt .c-act .menu-trigger { opacity:1; transform:none; }
    .dt td { height:auto; min-height:var(--row-phone); padding:12px 10px; }
    .dt th { padding:0 10px; }
    .dt .c-act { width:48px; padding-left:0; padding-right:8px; }
    /* the first VISIBLE cell (a phone hides .c-chk / .c-opt) lines up with the page gutter (trap 8) */
    .dt tr > :not(.c-chk, .c-opt):not(:not(.c-chk, .c-opt) ~ *) { padding-left:var(--gutter); }
    .dt .sub { display:block; max-width:44vw; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
    .dt .dt-name { font-size:16px; }
    .dt .en { display:none; }
    .card--table { margin-left:calc(-1 * var(--gutter)); margin-right:calc(-1 * var(--gutter)); border-radius:0; box-shadow:0 -1px 0 var(--border), 0 1px 0 var(--border); }
    .card-b > .card--table { margin-left:0; margin-right:0; box-shadow:none; }
    /* a table still wider than the phone after its priority columns hide (C's larger type) scrolls inside its card —
       never clipped; its header then sticks to the table's own top */
    .card--table .dt-wrap { overflow-x:auto; overscroll-behavior-x:contain; }
    .card--table .dt-wrap .dt th { top:0; }
    .card-f { border-radius:0; padding:12px 16px; }
    .fgrid { grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:16px 12px; }
    .fgrid .span2, .fgrid .full, .fgrid .full-sm, .fgrid .wide { grid-column:1 / -1; }
    .fsec-b, .fsec > .bd { padding:16px; }
    .actionbar, form:has(> .fsec) > .actionbar { bottom:0; margin:16px calc(-1 * var(--gutter)) 0; padding:10px var(--gutter) calc(10px + env(safe-area-inset-bottom));
                 border-radius:var(--r-xl) var(--r-xl) 0 0; }
    .ab-dirty, .ab-second, .ab-cancel, .ab-note { display:none; }
    .ab-btns { width:100%; } .ab-btns .ab-primary { flex:1; }
    .toasts { left:12px; right:12px; bottom:calc(12px + env(safe-area-inset-bottom)); width:auto; }
    body:has(.bulk:not([hidden])) .toasts, body:has(.actionbar:not(.actionbar--card)) .toasts { bottom:calc(88px + env(safe-area-inset-bottom)); }
    .pager-links li:not(:first-child):not(:last-child):not(.pg-of) { display:none; }
    .pager-links li.pg-of { display:list-item; }
    .pager-info { display:none; }
    .pager { justify-content:center; }
    .pager .pg { min-width:44px; height:44px; }
    /* a short form (確認密碼 / 變更密碼): its bottom action bar docks at the screen foot, not mid-page */
    main.page.page--form.pg-account { min-height:calc(100dvh - 56px); display:flex; flex-direction:column; }
    main.page.page--form.pg-account > form:last-of-type { flex:1; display:flex; flex-direction:column; }
    main.page.page--form.pg-account > form:last-of-type > .actionbar { margin-top:auto; }
    /* a line-item repeater row on a phone: the 品項 cell keeps its room, qty / price stay compact */
    tbody[data-repeat] > tr > td:first-child { min-width:45%; }
    tbody[data-repeat] input.num { max-width:72px; }
    /* page header (§6.2): secondary buttons with an icon go icon-only (label = accessible name + title);
       tertiary actions collapse into the ⋯ menu; the primary stays labelled and takes the room */
    /* (never inside the ⋯ menu: the tertiary copies there are labelled menu items) */
    .ph-actions .btn--secondary:has(> svg.i):not(.menu .btn) { width:var(--h); padding:0; gap:0; font-size:0; }
    .ph-actions .btn--secondary:has(> svg.i):not(.menu .btn) > svg.i { width:18px; height:18px; }
    .ph-actions > .btn--primary { flex:1; }
    .ph-tert { display:none; }
    .ph-more--phone { display:inline-block; }
    .ph-tert-m { display:block; }
    /* bulk bar: full width above the bottom edge */
    .bulk, html.rail .bulk { left:12px; right:12px; bottom:12px; width:auto; max-width:none; flex-wrap:wrap; padding:8px 8px 8px 12px; }
    .bulk-acts { padding-left:0; border-left:0; }
    .bulk-x { margin-left:auto; }
    /* description list + summary strip */
    .dl, .dl--3, .dl--4 { grid-template-columns:minmax(0,1fr); gap:12px; }
    .stats--strip { display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); }
    .stats--strip > .stat + .stat { box-shadow:none; }
    .stats--strip > .stat:nth-child(even) { box-shadow:inset 1px 0 0 var(--border); }
    .stats--strip > .stat:nth-child(n+3) { box-shadow:inset 0 1px 0 var(--border); }
    .stats--strip > .stat:nth-child(even):nth-child(n+3) { box-shadow:inset 1px 0 0 var(--border), inset 0 1px 0 var(--border); }
    .tl-at { margin-left:0; flex-basis:100%; }
    .chk { min-height:var(--h-touch); }
    /* wizard steps: numbers only + the current label, with 「步驟 n／N」 above */
    .steps-m { display:block; }
    .step:not(.is-current) .step-l { position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; }
    .step:not(.is-current) a.step-l:focus { position:static; width:auto; height:auto; clip:auto; }
    .step + .step::before { width:18px; }
    .who .avatar { width:36px; height:36px; }
}
@media (max-width:640px) {
    .wrap input, .wrap select, .wrap textarea { max-width:100%; }
    .btn { white-space:nowrap; }
    .grid { grid-template-columns:1fr !important; }
    .card { overflow-x:auto; }                  /* wide legacy tables scroll inside their card */
    .card.card--table { overflow:clip; }        /* x-table keeps priority columns + the sticky header instead */
    /* filter bar (§6.7): search (flex) + 「篩選 n」 on one line; the filters move into a bottom sheet */
    .btn.fbar-open { display:inline-flex; flex:none; }
    .fbar .fsearch { flex:1 1 160px; min-width:0; }      /* search + 「篩選 n」 share one line */
    .fbar-sheet { display:none; }
    .fbar-sheet[open] { display:flex; flex-direction:column; position:fixed; inset:auto 0 0 0; width:100%; max-width:none; max-height:85dvh; margin:0;
                        padding:0 0 env(safe-area-inset-bottom); border:0; border-radius:var(--r-xl) var(--r-xl) 0 0;
                        background:var(--raised); color:var(--text-1); box-shadow:var(--e-4); animation:sheet-in var(--dur-4) var(--ease-out) backwards; }
    .fbar-sheet::backdrop { background:var(--scrim); }
    .fbar-sheet-h { display:flex; align-items:center; justify-content:space-between; gap:8px; padding:12px 12px 8px 16px; border-bottom:1px solid var(--border); }
    .fbar-sheet-t { font-size:var(--fs-lg); }
    .fbar-fields { display:grid; gap:12px; padding:16px; overflow-y:auto; }
    .fbar-fields .fsel { display:flex; width:100%; min-height:var(--h-touch); }
    .fbar-fields .fsel select, .fbar-fields .fsel input { flex:1; min-width:0; min-height:42px; font-size:16px; }
    .fbar-sheet-f { display:flex; justify-content:flex-end; gap:8px; padding:12px 16px; border-top:1px solid var(--border); background:var(--subtle); }
    .fbar-sheet-f .btn--primary { flex:1; }
    /* form dialogs become a bottom sheet */
    .dlg--form, .dlg--sheet { width:100%; max-width:none; max-height:88dvh; margin:auto 0 0; border-radius:var(--r-xl) var(--r-xl) 0 0; }
    .dlg--form .dlg-ic, .dlg--sheet .dlg-ic { display:none; }
    .dlg--form .dlg-b, .dlg--sheet .dlg-b { padding:16px 16px 12px; }
    .dlg--form .dlg-hint { display:none; }
}
@media (min-width:641px) and (max-width:1120px) {
    .grid { grid-template-columns:repeat(2,1fr) !important; }
}
@media (hover:none) { .btn kbd, .tb-kbd, .fsearch kbd { display:none; } }

/* ── 9. print — always light, app chrome removed, no motion ───────────────────────────────── */
@media print {
    :root, :root[data-theme="dark"] {
        color-scheme:light;
        --canvas:#fff; --sheet:#fff; --sb-bg:#fff; --subtle:#fff; --raised:#fff; --hover:transparent; --active:transparent; --inset:#fff; --thumb:#fff; --nav-active:#fff;
        --border:#cfd3d9; --border-soft:#cfd3d9; --border-strong:#cfd3d9; --border-control:#999; --ring:#cfd3d9;
        --text-1:#000; --text-2:#222; --text-3:#444; --text-4:#666;
        --accent-soft:#fff; --accent-softer:#fff; --accent-text:#000; --success-soft:#fff; --warning-soft:#fff; --danger-soft:#fff; --neutral-soft:#fff;
        --success:#000; --warning:#000; --danger:#000;
        --money-neg:#000; --money-pos:#000;
        --e-0:none; --e-1:none; --e-2:none; --e-3:none; --e-4:none; --e-accent:none; --hl-top:inset 0 0 0 0 transparent; --hl-ring:inset 0 0 0 0 transparent;
        --grad-glow:none; --grad-tile:#fff;
        /* the rest of the palette back to its LIGHT values, so a page printed from dark mode keeps no dark ink */
        --placeholder:#687080; --border-control-hover:#5b6372; --glass:#fff; --scrim:transparent;
        --accent:#0077B6; --accent-hover:#006BA3; --accent-press:#005E8F; --accent-border:#BDE3F5; --on-accent:#fff; --focus:#0077B6;
        --success-dot:#39983F; --warning-dot:#e08a0b; --danger-dot:#e5484d; --neutral-text:#222;
        --success-border:#999; --warning-border:#999; --danger-border:#999;
        --series-1:#0092D8; --series-1-soft:rgba(0,146,216,.14); --series-2:#e8692e; --grid:#eceff3; --axis:#d3d8e0; --band:transparent;
        --skel:#eef1f5;
    }
    body { background:#fff; }
    .sb, .sb-scrim, .tb, .navprog, .toasts, .actionbar, .fbar, .fbar-sum, .tabs, .pager, .dt-pager, .c-chk, .c-act, .bulk, dialog, .skip, .ph-actions, .menu-wrap, .seg,
    .banners, .screen-only, .busy-note { display:none !important; }
    .print-only { display:block !important; }
    .app, html.rail .app { padding-left:0; }
    .sheet { margin:0; border:0; box-shadow:none; }
    .sheet::before { display:none; }
    .page { padding:0; max-width:none; }
    .card, .stat, .fsec-b, .fsec > .bd, .stats--strip { box-shadow:inset 0 0 0 1px #cfd3d9; }
    .stat--plain { box-shadow:none; }
    .dt th { position:static; }
    .badge { background:none !important; box-shadow:inset 0 0 0 1px #999; }
    .badge::before { display:none; }
    a { text-decoration:none; color:inherit; }
    * { animation:none !important; transition:none !important; view-transition-name:none !important; }
}
