/* ════════════════════════════════════════════════════════════════════════
   LEDGER LIGHT — the ONE palette. Every page links this file.

   WHY THIS FILE EXISTS
   Before it, four pages carried four private palettes and had drifted into
   four different brands: the pay page was GREEN (#0b7a4b), Learn was lavender
   on cream (#7c6fff / #faf8f3), legal was purple (#5548d9) and sign-up was
   indigo (#5b5ef0). A shopkeeper signed up on one brand, paid on another and
   learned on a third. Colour is defined here, once, and nowhere else.

   THE RULE
   No page stylesheet may declare a colour literal (#hex, rgb(), hsl()). It
   references a token from this file. `tests/test_design_tokens_ssot.py`
   fails the build otherwise — the same shape as the balance / party-matcher
   / entry-side SSOT guards, and for the same reason: a rule nobody can
   accidentally break is worth more than a rule everybody agrees with.

   OPACITY TINTS
   Some surfaces need a translucent wash of a semantic colour. A literal
   rgba() would freeze one theme's colour into a rule, so every tintable
   colour also ships a comma-separated CHANNEL TRIPLET, used as
   `rgba(var(--accent-rgb), .15)`. Add a token → add its triplet if it will
   ever be tinted.

   THEMES
   `:root` is light and is the default. `[data-theme="dark"]` overrides ONLY
   the tokens that change. Never give a colour its sole definition inside the
   dark block — a token that exists in one theme and not the other renders as
   nothing in the other.
   ════════════════════════════════════════════════════════════════════════ */

/* ════════════════════════════════════════════════════════════════════════
   THE FACES. Served from this origin, not from a CDN.

   Each file is one VARIABLE font, so a single rule per file covers every
   weight the product uses and the browser interpolates between them —
   Google's stylesheet writes one rule per weight instead. The ranges are the
   weights that actually appear in the product; a rule that claims more than
   its file carries is clamped, and one that claims less makes the browser
   synthesise a fake bold.

   Google splits each face into pieces by character range, and each rule's
   `unicode-range` is copied from Google's own rule for that piece: `latin`,
   `latin-ext` (where the rupee sign lives, with most other currency signs)
   and, for Inter and JetBrains Mono, `cyrillic` (the numero sign on the
   onboarding sheet; Fraunces publishes no cyrillic). The range is
   load-bearing both ways: it is how the browser knows to fetch the latin-ext
   file for a rupee sign, and it stops a download for a Devanagari character
   none of these faces contain. Until 10/09/2026 only `latin` was here, and
   every rupee sign in the product was drawn by the phone's own font; see
   tests/test_vendored_fonts.py. `swap` so text is readable while the file
   arrives — on a local file that is a formality, and it is the right
   formality.

   Provenance, licence and how to update: static/fonts/LICENSE.md.
   ════════════════════════════════════════════════════════════════════════ */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 500 600;
  font-display: swap;
  src: url('/static/fonts/fraunces-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 500 600;
  font-display: swap;
  src: url('/static/fonts/fraunces-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/inter-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500 700;
  font-display: swap;
  src: url('/static/fonts/jetbrains-mono-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500 700;
  font-display: swap;
  src: url('/static/fonts/jetbrains-mono-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500 700;
  font-display: swap;
  src: url('/static/fonts/jetbrains-mono-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

:root {
  /* Native widgets — scrollbars, date pickers, the checkbox tick — are painted
     by the browser, not by us. color-scheme is the only way to tell it which
     way round they go, and it is declared per THEME rather than as
     `light dark`: `light dark` makes those widgets follow the operating
     system, which would leave a dashboard user's chosen light theme with dark
     scrollbars on a dark phone. */
  color-scheme: light;

  /* ── surfaces: cool near-whites, "lifted paper" ── */
  --canvas:      #f5f6fa;
  --canvas-2:    #eef0f6;
  --paper:       #ffffff;
  --paper-2:     #fbfcfe;
  --raise:       #ffffff;
  --line:        #e8eaf0;
  --line-soft:   #f0f2f6;
  --line-strong: #dadde6;

  /* ── ink ── */
  --ink:   #15171d;
  --ink-2: #3a3f4b;
  --muted: #5d6573;
  --faint: #939bab;
  --ghost: #b9c0cc;

  /* ── indigo primary ── */
  --accent:      #5b5ef0;
  --accent-2:    #4a4ce0;
  --accent-ink:  #3d34c9;
  --accent-soft: #edecfe;
  --accent-line: #d9d8fb;
  --on-accent:   #ffffff;

  /* ── money semantics.
     --pos is money owed TO the shop (a receivable), --neg is money the shop
     owes out (a payable). Never re-map these to "up/down" or "profit/loss";
     the ledger's only question is which way the money points. ── */
  --pos:       #0b8f5b;
  --pos-soft:  #e3f6ec;
  --pos-line:  #bce8cf;
  --neg:       #e0214a;
  --neg-soft:  #fde7ec;
  --neg-line:  #f7c0cc;
  --amber:     #c4790a;   /* cash sale */
  --amber-soft:#fcf0db;
  --sky:       #1f74d8;   /* payment / informational */
  --sky-soft:  #e4f0fd;
  --on-amber:  #1a1205;
  --on-pos:    #ffffff;   /* words on a solid --pos button */

  /* ── channel triplets, for rgba() tints (see header) ── */
  --accent-rgb: 91, 94, 240;
  --pos-rgb:    11, 143, 91;
  --neg-rgb:    224, 33, 74;
  --amber-rgb:  196, 121, 10;
  --sky-rgb:    31, 116, 216;
  --ink-rgb:    21, 23, 29;
  --muted-rgb:  93, 101, 115;
  --paper-rgb:  255, 255, 255;
  --canvas-rgb: 245, 246, 250;
  --shadow-rgb: 20, 24, 36;
  --scrim-rgb:  20, 23, 33;
  /* The specular highlight on frosted "glass" edges is a LIGHT source, not a
     surface, so it stays white in both themes and only its alpha changes. */
  --glass-hi-rgb: 255, 255, 255;

  /* ── type. The three faces, served from this origin — see the @font-face
     block at the top of this file and static/fonts/LICENSE.md. ── */
  --f-sans:  'Inter', system-ui, -apple-system, sans-serif;
  --f-serif: 'Fraunces', Georgia, serif;
  --f-mono:  'JetBrains Mono', ui-monospace, monospace;

  /* ── radius ── */
  --r-xs: 8px;
  --r-sm: 11px;
  --r:    14px;
  --r-lg: 20px;
  --r-xl: 26px;

  /* ── shadow: layered, soft on light ── */
  --sh-1: 0 1px 2px rgba(var(--shadow-rgb), .05), 0 1px 1px rgba(var(--shadow-rgb), .03);
  --sh-2: 0 2px 4px rgba(var(--shadow-rgb), .04), 0 10px 24px -14px rgba(var(--shadow-rgb), .18);
  --sh-3: 0 4px 8px rgba(var(--shadow-rgb), .05), 0 24px 48px -20px rgba(var(--shadow-rgb), .24);
  --sh-pop: 0 10px 20px -6px rgba(var(--shadow-rgb), .12), 0 40px 80px -24px rgba(var(--shadow-rgb), .30);
  --ring: 0 0 0 4px rgba(var(--accent-rgb), .16);

  /* ── motion ── */
  --spring: cubic-bezier(.22,.78,.28,1);
  --ease:   cubic-bezier(.4,0,.2,1);

  /* The brand glyph itself, as a file rather than a drawing of it. Pages
     paint it with a CSS mask, so one asset serves every surface in whatever
     colour that surface needs — see .logo-mk::before. Theme-invariant: it is
     artwork, not a colour. */
  --logo-mark: url("/static/favicon/mark.png");

  /* ── brand-constant colours: the SAME in both themes, deliberately.
     These are logos and printed-icon artwork, not UI surfaces — WhatsApp's
     green is WhatsApp's green on a dark screen too. ── */
  --wa: #25d366;
  --wa-rgb: 37, 211, 102;
  /* A QR code is read by a camera, not by a person. It must stay dark modules
     on a white quiet zone in BOTH themes — tinting it to the dark surface is
     how you ship a payment page whose QR will not scan. */
  --qr-paper: #ffffff;

  /* Telegram, reproduced in the onboarding sheet so a shopkeeper recognises
     the app they are about to open. Telegram's blue is Telegram's blue on a
     page of ours, the same as the WhatsApp green above — these are a picture
     OF another product, not our own surfaces, so they never re-theme. */
  --tg-blue:        #2aabee;
  --tg-blue-soft:   #e0f2fb;
  --tg-blue-deep:   #1a7fb6;
  --tg-header:      #517da2;
  --tg-wall-top:    #d8e1e8;
  --tg-wall-bottom: #cad3da;
  --tg-sent:        #effdde;
  --tg-sent-time:   #5fab85;
  --tg-recv-time:   #92a0a8;

  /* The soft indigo wash in the top-right corner of the app canvas, and the
     surface a toast floats on. Both are the two tokens that genuinely have no
     sibling above — a glow is not a surface and a toast deliberately inverts
     against the page so it reads as "over" it rather than "in" it. */
  --glow:      #ececfd;
  --toast-bg:  #15171d;
  --toast-ink: #ffffff;

  /* ── categorical series ramp, for CHART DATA only.
     These are not UI surfaces and carry no money meaning — never use one to
     say "owed" or "paid", that is what --pos / --neg are for. Chart.js paints
     to a <canvas> and cannot read a custom property, so the operator cost
     chart resolves these through getComputedStyle at render time. ── */
  --series-1: #7c6fff;
  --series-2: #f472b6;
  --series-3: #a78bfa;
  --series-4: #818cf8;
  --series-5: #94a3b8;
  --series-6: #cbd5e1;
  --series-7: #fbbf24;
  --series-8: #34d399;

  /* ── layout metrics ── */
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bot: env(safe-area-inset-bottom, 0px);
  --safe-l:   env(safe-area-inset-left, 0px);
  --safe-r:   env(safe-area-inset-right, 0px);
  --topbar-h: 58px;
  --botnav-h: 66px;
  /* Capped at 1680px, then centred (Karan, 20/09/2026). The history: capped
     at 1240px until 12/09/2026, when it went full-bleed (none) because 1240
     stranded the card grids with a wide empty margin on a desktop monitor.
     Full-bleed then let the wide DATA TABLES (the ledger / activity statement)
     fling their columns out to the screen edges. 1680 is the middle ground,
     and the SMALLEST cap that still draws the three-column card grid: three
     520px columns + two 8px gaps + the .wrap's 28px side padding = 1632px, so
     1680 holds exactly three columns with headroom and never a stranded fourth.
     The grids keep their shape, the table stays readable, every section stays
     the same width (aligned), and the side margin on a normal desktop is
     small. */
  --maxw:     1680px;
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --canvas:      #0f1117;
  --canvas-2:    #181b23;
  --paper:       #181b22;
  --paper-2:     #1e222b;
  --raise:       #20242e;
  --line:        #282d39;
  --line-soft:   #1f2330;
  --line-strong: #363c4b;

  --ink:   #f0f2f7;
  --ink-2: #d3d8e2;
  --muted: #99a1b2;
  --faint: #6b7385;
  --ghost: #4a5161;

  --accent:      #7e80f5;
  --accent-2:    #9092f8;
  --accent-ink:  #c2c3fb;
  --accent-soft: #20223c;
  --accent-line: #34376a;
  --on-accent:   #ffffff;

  --pos:       #34d399;
  --pos-soft:  #112a22;
  --pos-line:  #1d4d3c;
  --neg:       #fb6f8a;
  --neg-soft:  #2e1620;
  --neg-line:  #5a2738;
  --amber:     #e7a948;
  --amber-soft:#2c2213;
  --sky:       #5aa9f5;
  --sky-soft:  #14263a;
  --on-amber:  #1a1205;
  --on-pos:    #06251a;   /* dark --pos is a light green: dark words on it */

  --accent-rgb: 126, 128, 245;
  --pos-rgb:    52, 211, 153;
  --neg-rgb:    251, 111, 138;
  --amber-rgb:  231, 169, 72;
  --sky-rgb:    90, 169, 245;
  --ink-rgb:    240, 242, 247;
  --muted-rgb:  153, 161, 178;
  --paper-rgb:  24, 27, 34;
  --canvas-rgb: 15, 17, 23;
  --shadow-rgb: 0, 0, 0;
  --scrim-rgb:  0, 0, 0;

  --glow:      #1a1c34;
  --toast-bg:  #2a2f3b;
  --toast-ink: #f0f2f7;

  --sh-1: 0 1px 2px rgba(var(--shadow-rgb), .4);
  --sh-2: 0 2px 6px rgba(var(--shadow-rgb), .4), 0 12px 28px -16px rgba(var(--shadow-rgb), .7);
  --sh-3: 0 6px 14px rgba(var(--shadow-rgb), .45), 0 26px 52px -22px rgba(var(--shadow-rgb), .8);
  --sh-pop: 0 12px 28px -8px rgba(var(--shadow-rgb), .6), 0 44px 84px -28px rgba(var(--shadow-rgb), .9);
  --ring: 0 0 0 4px rgba(var(--accent-rgb), .26);

  /* Only the two pale slates change: they vanish on a near-black canvas. */
  --series-5: #8892a4;
  --series-6: #aab4c4;
}

/* ════════════════════════════════════════════════════════════════════════
   NO DARK MODE BEFORE THE SIGN-IN DOOR.

   Karan, 08/09/2026: "dark mode is only after sign in door is crossed, never
   before". The dark palette above is reached ONLY through data-theme, which
   the dashboard writes from the shopkeeper's own saved choice. A page with no
   data-theme (pay, privacy, Learn, the onboarding sheet) gets the light
   palette whatever the phone is set to, so there is deliberately no
   prefers-color-scheme block in this file. tests/test_design_tokens_ssot.py
   refuses one on every page: a rule here reaches every page that links it.
   ════════════════════════════════════════════════════════════════════════ */
