/* webapp/public/assets/css/style.css
 *
 * Shared base stylesheet for every LIFF page in this project. Keep
 * page-specific styling out of inline <style>/attributes where possible —
 * add new shared classes here instead, so all screens (create-bill,
 * pay-bill, monthly-summary, ...) read as one visual system.
 *
 * Palette/tokens are lifted directly from design/monthly-summary-template.js
 * (graphic's "Clean & Confident" system, also used by design/rich-menu-*.html)
 * so the interactive LIFF pages and the pushed PNG summary image read as one
 * product, not two divergent designs.
 *
 * Accessibility minimums baked in here per docs/design-requirements.md —
 * these are base styles, not opt-in utilities, because LIFF's in-app webview
 * may not honor OS-level text scaling and the household's real end users
 * include ~70-year-old parents:
 *   - body text >= 18px, primary amounts >= 22-24px bold
 *   - tap targets >= 48x48px, 8px+ spacing (12-16px around primary buttons)
 *   - AA contrast minimum / AAA target for primary numbers + CTAs
 *   - status is never color-only — every status pill also carries a glyph
 *     + Thai text label (see .pill rules)
 */

:root {
  /* ทิศทางที่ 2 — ชัดตรงประเด็น (Clean & Confident) — same tokens as
     design/monthly-summary-template.js and design/rich-menu-*.html */
  --bg: #F5F7FA;
  --surface: #FFFFFF;
  --surface-alt: #EEF1F5;
  --ink: #131B2E;
  --ink-2: #57606F;
  --line: #DEE3EA;
  --accent: #0A4DA6;
  --accent-tint: #E7EFFA;
  --on-accent: #FFFFFF;

  /* Secondary brand color, sampled directly from the ทองคำ mascot
     (design/logo.png) — a deep amber gold from the mane, chosen over the
     brighter mid-mane tone for its ~3:1 contrast against white surfaces.
     Used sparingly as a decorative accent only (thin rules, the
     first-impression hero on index.html, a small celebratory flourish on
     create-bill's success state) — never for primary actions/status, which
     stay on --accent blue per the existing convention shared with the LINE
     Flex Message cards in design/flex-messages.js. */
  --gold: #C7831E;
  --gold-tint: #F8F0E2;

  --status-unpaid-bg: #FBE4E2;   --status-unpaid-ink: #C22B1F;
  --status-pending-bg: #FCEBCF;  --status-pending-ink: #946200;
  --status-paid-bg: #DEF2E6;     --status-paid-ink: #157F45;

  --radius-lg: 14px;
  --radius-md: 12px;
  --radius-sm: 10px;
  --radius-pill: 999px;

  --tap-min: 48px;
  --space-tap-gap: 8px;
  --space-cta-gap: 16px;

  /* Shadows — subtle depth for cards/header/primary button (the
     beautification pass this file's header comment refers to). Kept light
     enough not to read as a new visual language: same neutral --ink tint at
     low alpha everywhere, applied uniformly across every screen including
     /pay-bill and /confirm-payment (a soft lift is polish, not added
     complexity — it doesn't change information density or add new controls,
     so it's safe on the parents' low-friction screens too). */
  --shadow-sm: 0 1px 2px rgba(19, 27, 46, 0.06), 0 2px 6px rgba(19, 27, 46, 0.05);
  --shadow-md: 0 4px 14px rgba(19, 27, 46, 0.10), 0 1px 3px rgba(19, 27, 46, 0.06);
  --shadow-accent: 0 3px 10px rgba(10, 77, 166, 0.24);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: -apple-system, 'Helvetica Neue', Arial, 'Segoe UI', Roboto, 'Noto Sans Thai', sans-serif;
  font-size: 18px; /* baseline minimum body text size, project-wide */
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
}

h1, h2, h3 {
  margin: 0;
  color: var(--ink);
  font-weight: 800;
}

p { margin: 0; }

button {
  font-family: inherit;
  font-size: 18px;
  color: inherit;
  cursor: pointer;
}

select, input {
  font-family: inherit;
  font-size: 18px;
  color: var(--ink);
}

a { color: var(--accent); }

/* Never rely on the browser's default focus ring alone (it's often too
   thin/low-contrast) — every interactive element gets a clearly visible
   focus outline so this also works for anyone navigating without precise
   touch. */
a:focus-visible,
button:focus-visible,
select:focus-visible,
input:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

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

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

.app {
  max-width: 480px; /* phone-viewport-first; this opens inside LINE's
                        in-app webview, never assume desktop width */
  margin: 0 auto;
  min-height: 100vh;
  background: var(--bg);
  display: flex;
  flex-direction: column;
}

.app-header {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  background: var(--surface);
  border-bottom: 1.5px solid var(--line);
  box-shadow: var(--shadow-sm);
}

/* Groups the brand mark + page title so the back button's gap stays fixed
   while this pair shares the remaining header width and can wrap onto a
   second line for a long Thai title without disturbing the back button or
   overflowing the 480px app shell. */
.app-header-title {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  flex: 1 1 auto;
}

.app-header-title h1 {
  min-width: 0;
}

.app-header h1 {
  font-size: 22px;
  line-height: 1.3;
}

/* Brand mark (design/logo.png, processed to webapp/public/assets/img/logo-icon.png)
   — same small size in every header so the product reads as one system.
   Decorative only: the page title text already names the screen, so this
   stays out of the accessibility tree. */
.app-header-logo {
  width: 28px;
  height: 28px;
  flex: 0 0 auto;
  object-fit: contain;
  display: block;
}

/* Every screen needs an obvious on-screen back/cancel control — never rely
   on the OS back gesture (docs/design-requirements.md #6). Icon + visible
   Thai label, not icon-only. */
.back-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: var(--tap-min);
  min-height: var(--tap-min);
  padding: 8px 14px 8px 10px;
  background: var(--surface-alt);
  border: 1.5px solid var(--line);
  border-radius: var(--radius-md);
  font-weight: 700;
  font-size: 18px;
  color: var(--ink);
  flex: 0 0 auto;
}

.back-btn .icon {
  font-size: 22px;
  line-height: 1;
}

.app-main {
  flex: 1 1 auto;
  padding: 16px;
  padding-bottom: 40px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

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

.card {
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 16px;
  box-shadow: var(--shadow-sm);
}

.section-label {
  display: block;
  font-size: 18px;
  font-weight: 700;
  color: var(--ink-2);
  margin-bottom: 8px;
}

/* --- Form controls ------------------------------------------------------- */

.select-field {
  width: 100%;
  min-height: var(--tap-min);
  padding: 10px 14px;
  font-size: 18px;
  font-weight: 600;
  color: var(--ink);
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--radius-md);
}

/* --- Buttons --------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: var(--tap-min);
  min-width: var(--tap-min);
  padding: 12px 20px;
  border-radius: var(--radius-md);
  border: none;
  font-weight: 700;
  font-size: 18px;
  text-decoration: none; /* .btn is also used on <a> elements, e.g. the
                             all-bills link on monthly-summary.html */
}

.btn-primary {
  width: 100%;
  min-height: 56px;
  font-size: 20px;
  font-weight: 800;
  background: var(--accent);
  color: var(--on-accent);
  margin: var(--space-cta-gap) 0;
  box-shadow: var(--shadow-accent);
}

.btn-primary:disabled {
  background: var(--ink-2);
  opacity: 0.7;
  cursor: not-allowed;
  box-shadow: none;
}

/* Shown above a disabled .btn-primary submit button to explain WHY it's
   disabled (e.g. "กรุณากรอกให้ครบ: รายการ") - a disabled button alone gives
   no indication of what's still missing, which was a real source of user
   confusion (create-bill.js's updateSubmitEnabled() populates this). */
.submit-hint {
  font-size: 15px;
  font-weight: 600;
  color: var(--status-unpaid-ink);
  text-align: center;
  margin: 8px 0 0;
}

.btn-secondary {
  background: var(--surface);
  color: var(--accent);
  border: 1.5px solid var(--accent);
}

/* Negative/reject action (e.g. "ยังไม่ได้รับ" on pay-bill's Approvals
   section) — same outline shape as .btn-secondary but in the shared
   status-unpaid tone, so a reject action reads as distinct from both the
   primary accent action and a neutral secondary one. */
.btn-danger {
  background: var(--surface);
  color: var(--status-unpaid-ink);
  border: 1.5px solid var(--status-unpaid-ink);
}

/* --- Household total line --------------------------------------------- */

.total-line {
  background: var(--accent-tint);
  border: 1.5px solid var(--accent);
  border-radius: var(--radius-lg);
  padding: 16px;
  font-size: 20px;
  font-weight: 700;
  color: var(--ink);
  box-shadow: var(--shadow-sm);
}

.total-line .amount {
  font-size: 28px;
  font-weight: 800;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

/* --- Member list --------------------------------------------------------- */

.member-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-tap-gap);
}

.member-item {
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}

.member-row {
  width: 100%;
  min-height: var(--tap-min);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 16px;
  background: var(--surface);
  border: none;
  text-align: left;
}

.member-row .member-main {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.member-row .member-name {
  font-size: 20px;
  font-weight: 800;
  color: var(--ink);
}

.member-row .member-status-summary {
  font-size: 18px;
  font-weight: 600;
  color: var(--ink-2);
}

.member-row .member-amount-wrap {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
}

.member-row .member-amount {
  font-size: 24px;
  font-weight: 800;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The chevron is the explicit, visible control indicating "tap to expand" -
   per docs/feature-monthly-summary.md 3.1.4 it must exist as a control on
   its own, distinct from the row just being tappable. It is presented as a
   round badge so it reads as a button even though (to keep this valid,
   single-control-per-row markup - no nested <button>s) the whole row is the
   one real <button> element; its accessible name already includes the
   member's name/amount/status, so this glyph stays aria-hidden. */
.chevron-badge {
  width: 36px;
  height: 36px;
  min-width: 36px;
  border-radius: 50%;
  background: var(--surface-alt);
  border: 1.5px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  color: var(--ink-2);
  transition: transform 0.15s ease;
}

.member-row[aria-expanded="true"] .chevron-badge {
  transform: rotate(180deg);
  background: var(--accent-tint);
  border-color: var(--accent);
  color: var(--accent);
}

.member-detail {
  padding: 4px 16px 16px;
  background: var(--surface-alt);
  border-top: 1.5px solid var(--line);
}

.member-detail[hidden] { display: none; }

.detail-subtotal {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 18px;
  padding: 12px 0;
  font-size: 18px;
  font-weight: 600;
  color: var(--ink);
}

.detail-subtotal .item {
  white-space: nowrap;
}

.detail-subtotal .value {
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.item-card {
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--radius-md);
  padding: 12px 14px;
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  box-shadow: var(--shadow-sm);
}

.item-name {
  font-size: 18px;
  font-weight: 800;
  color: var(--ink);
}

.item-card .item-date {
  font-size: 18px;
  color: var(--ink-2);
}

.item-card .item-amount-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}

.item-card .item-amount-owed {
  font-size: 22px;
  font-weight: 800;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.item-card .item-bill-total {
  font-size: 18px;
  color: var(--ink-2);
}

.item-card .item-payer {
  font-size: 18px;
  color: var(--ink);
}

.item-card .item-approver-note {
  font-size: 18px;
  color: var(--ink-2);
}

/* Action button row inside an .item-card (pay-bill's "แจ้งว่าจ่ายแล้ว" /
   "ยืนยันว่าได้รับแล้ว" / "ยังไม่ได้รับ" buttons) — buttons share the row
   instead of stacking full-width like standalone .btn-primary elsewhere,
   since up to two live side by side here. */
.item-card-actions {
  display: flex;
  gap: var(--space-tap-gap);
  margin-top: 4px;
}

.item-card-actions .btn {
  flex: 1 1 0;
  width: auto;
  min-height: var(--tap-min);
  margin: 0;
}

/* PromptPay QR (webapp/public/assets/js/pay-bill.js) - every unpaid
   "รอฉันจ่าย" row shows its own QR slot automatically, no button/toggle;
   .promptpay-qr-wrap is the slot's box and stays in place while its content
   swaps between a loading placeholder, the fetched QR, or an inline error
   (see renderQrSlotLoading/SuccessInto/ErrorInto). Sized for scanning by a
   phone camera, not tapping, so it doesn't follow --tap-min; 240px (matching
   the scale of .hero-logo's 120px brand mark, just larger since a QR needs
   real scan resolution) stays comfortably legible without dominating the
   480px app shell. */
.promptpay-qr-wrap {
  margin-top: 10px;
  padding: 14px;
  background: var(--surface-alt);
  border: 1.5px solid var(--line);
  border-radius: var(--radius-md);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.promptpay-qr-img {
  display: block;
  width: 100%;
  max-width: 240px;
  height: auto;
  background: var(--surface);
  border-radius: var(--radius-sm);
}

.promptpay-qr-caption {
  font-size: 18px;
  font-weight: 700;
  color: var(--ink);
  text-align: center;
}

/* Loading placeholder ("กำลังโหลด QR พร้อมเพย์...") and inline per-row error
   copy shown in a .promptpay-qr-wrap before/if the fetch resolves - muted
   secondary-note weight, same convention as .item-approver-note, distinct
   from the bold primary .promptpay-qr-caption used once a QR actually
   loads. */
.promptpay-qr-status {
  font-size: 18px;
  color: var(--ink-2);
  text-align: center;
}

/* Row content-block tap target (webapp/public/assets/js/all-bills.js) - wraps
   everything above a row's status pill in a link to /bill-detail?billId=...
   (docs/feature-view-all-bills.md 2026-08-09 update). Deliberately not the
   whole .item-card (avoids nested-interactive-element markup); the subtle
   background shift on press is the tap affordance called for in that doc,
   no extra "ดูรายละเอียด" label/chevron needed. text-decoration/color reset
   so the link doesn't read as blue underlined text - every child already
   carries its own explicit color via .item-name/.item-date/.item-payer/etc. */
.item-card-link {
  display: block;
  text-decoration: none;
  color: inherit;
  margin: -2px;
  padding: 2px;
  border-radius: var(--radius-sm);
}

.item-card-link:active {
  background: var(--surface-alt);
}

/* --- Pay-bill split lists (Section 1 "รอฉันจ่าย" / Section 2 "รอฉันอนุมัติ") - */

.split-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-tap-gap);
}

.split-list[hidden] { display: none; }

/* --- Status pill: color + glyph + text, never color alone --------------- */

.pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 18px;
  font-weight: 700;
  padding: 6px 14px;
  border-radius: var(--radius-pill);
  width: fit-content;
}

.pill .pill-glyph {
  font-size: 16px;
  line-height: 1;
}

.pill--unpaid    { background: var(--status-unpaid-bg);  color: var(--status-unpaid-ink); }
.pill--pending   { background: var(--status-pending-bg); color: var(--status-pending-ink); }
.pill--paid      { background: var(--status-paid-bg);    color: var(--status-paid-ink); }
/* Cancelled-bill pill (webapp/public/all-bills.js) - distinct grey/neutral
   tone, deliberately not reusing any of the 3 payment-status colors above
   so "cancelled" never gets mistaken for "unpaid"/"pending"/"paid". */
.pill--cancelled { background: var(--surface-alt);       color: var(--ink-2); }

/* --- Type badge: non-status label, e.g. "จำนวนเงินคงที่" on an auto-create
   recurring reminder row (webapp/public/assets/js/recurring-reminders.js).
   Deliberately a separate class from .pill above - .pill is reserved for
   payment/reminder *status* (paid/unpaid/pending/cancelled) per the
   accessibility note at the top of this file; this is a *type* tag instead,
   so it intentionally does not reuse any of the --status-* colors and does
   not need a glyph (nothing here communicates ok/warn/danger). */
.badge {
  display: inline-flex;
  align-items: center;
  font-size: 16px;
  font-weight: 700;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
  width: fit-content;
  background: var(--accent-tint);
  color: var(--accent);
}

/* --- Loading / error / empty states ------------------------------------- */

.state-panel {
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 28px 20px;
  text-align: center;
  font-size: 18px;
  color: var(--ink);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  box-shadow: var(--shadow-sm);
}

.state-panel.state-error {
  background: var(--status-unpaid-bg);
  border-color: var(--status-unpaid-ink);
  color: var(--status-unpaid-ink);
}

.state-panel[hidden] { display: none; }

/* --- Send-image feedback banner ------------------------------------------ */

.feedback-banner {
  border-radius: var(--radius-md);
  padding: 14px 16px;
  font-size: 18px;
  font-weight: 700;
  display: flex;
  align-items: flex-start;
  gap: 10px;
}

.feedback-banner[hidden] { display: none; }

.feedback-banner.success {
  background: var(--status-paid-bg);
  color: var(--status-paid-ink);
}

.feedback-banner.failure {
  background: var(--status-unpaid-bg);
  color: var(--status-unpaid-ink);
}

/* --- Create-bill form ----------------------------------------------------- */

.field-group {
  margin-bottom: var(--space-cta-gap);
}

.field-group:last-of-type {
  margin-bottom: 0;
}

.text-field,
.number-field,
.date-field {
  width: 100%;
  min-height: var(--tap-min);
  padding: 10px 14px;
  font-size: 18px;
  font-weight: 600;
  color: var(--ink);
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--radius-md);
}

/* --- Participant checkbox list --------------------------------------------- */

.checkbox-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-tap-gap);
}

.checkbox-item {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: var(--tap-min);
  padding: 10px 14px;
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--radius-md);
  font-size: 18px;
  font-weight: 600;
  color: var(--ink);
}

.checkbox-item input[type="checkbox"] {
  width: 24px;
  height: 24px;
  flex: 0 0 auto;
  accent-color: var(--accent);
}

/* --- Split-type toggle ------------------------------------------------------ */

.toggle-group {
  display: flex;
  gap: var(--space-tap-gap);
}

.toggle-option {
  flex: 1 1 0;
  min-height: var(--tap-min);
  padding: 10px 12px;
  font-size: 18px;
  font-weight: 700;
  color: var(--ink-2);
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--radius-md);
  text-align: center;
}

.toggle-option[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* Disabled when only one participant is selected - splitting doesn't mean
   anything with a single person, so the choice itself is removed rather
   than left clickable-but-meaningless (create-bill.js forces splitType back
   to 'equal' at the same time this class of state is entered). */
.toggle-option:disabled {
  cursor: not-allowed;
  opacity: 0.55;
  background: var(--surface-alt);
  border-color: var(--line);
  color: var(--ink-2);
}

/* --- Percentage split inputs ------------------------------------------------ */

.percent-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-tap-gap);
}

.percent-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: var(--tap-min);
  padding: 10px 14px;
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--radius-md);
}

.percent-row .percent-name {
  font-size: 18px;
  font-weight: 600;
  color: var(--ink);
  flex: 1 1 auto;
  min-width: 0;
}

.percent-row .percent-input-wrap {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  font-size: 18px;
  font-weight: 700;
  color: var(--ink);
}

.percent-input {
  width: 72px;
  min-height: 40px;
  padding: 6px 8px;
  font-size: 18px;
  font-weight: 700;
  text-align: right;
  color: var(--ink);
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--radius-sm);
}

.percent-total {
  margin-top: 4px;
  font-size: 18px;
  font-weight: 700;
  text-align: right;
}

.percent-total.ok { color: var(--status-paid-ink); }
.percent-total.warn { color: var(--status-unpaid-ink); }

/* --- Breakdown table (create-bill success state) ---------------------------- */

.breakdown-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 12px 0 20px;
}

.breakdown-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 14px;
  background: var(--surface-alt);
  border: 1.5px solid var(--line);
  border-radius: var(--radius-md);
}

.breakdown-row .breakdown-name {
  font-size: 18px;
  font-weight: 700;
  color: var(--ink);
}

.breakdown-row .breakdown-amount {
  font-size: 18px;
  font-weight: 800;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.breakdown-row.is-payer .breakdown-name::after {
  content: " (ผู้จ่าย)";
  font-weight: 500;
  color: var(--ink-2);
}

/* --- Brand hero (index.html bootstrap/loading screen only) -------------- --
   The first thing anyone sees after tapping into the LIFF app from the Rich
   Menu, so it gets a slightly more prominent mascot treatment than the
   28px header mark used on every other screen — still restrained, just a
   soft gold-tinted halo behind the transparent-background logo (no card,
   no border, so it doesn't read as another surface to parse). */
.hero-logo-wrap {
  display: flex;
  justify-content: center;
  padding: 4px 0 0;
}

.hero-logo {
  width: 120px;
  height: 120px;
  object-fit: contain;
  padding: 16px;
  background: radial-gradient(circle, var(--gold-tint) 0%, rgba(248, 240, 226, 0) 70%);
  border-radius: 50%;
}

/* --- Gold celebration accent (create-bill success state only) ----------- --
   A single sparing use of --gold as a decorative flourish, not a status
   color — the pill above stays the shared --status-paid green so "paid"
   still means the same thing everywhere in the product. This is opt-in
   (added only to #success-state's markup in create-bill.html) precisely so
   it never reaches /pay-bill or /confirm-payment. */
.card-celebrate {
  position: relative;
  overflow: hidden;
}

.card-celebrate::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 4px;
  background: linear-gradient(90deg, var(--gold), #E9C377 50%, var(--gold));
}
