/* Billkeep — the shared foundation.

   Everything in here is used by both the marketing pages and the app itself:
   the colour tokens, the type ramp, buttons, focus, the page container. The
   tokens mirror lib/app/theme.dart in the Android app, so the site and the
   product look like one thing rather than two.

   Rules carried over from the app and worth not undoing:
   - no outlines on cards, chips or fields — a soft shadow instead
   - large corners (26px is the signature one)
   - tabular figures everywhere, because every screen is a column of money
   - colour is never the only carrier of meaning */

@font-face {
  font-family: 'Plus Jakarta Sans';
  src: url('../fonts/PlusJakartaSans.ttf') format('truetype-variations');
  font-weight: 200 800;
  font-display: swap;
}

/* Tokens ------------------------------------------------------------------ */

:root {
  --brand: #6C5CE7;
  --brand-alt: #9B6BFF;
  --brand-deep: #4B32C3;
  /* Brand colour as *text*. The pills and eyebrows set brand-coloured words on
     a 12-13% tint of the same hue, where --brand itself lands at about 3.9:1. */
  --brand-ink: #4B32C3;
  --on-brand: #FFFFFF;
  --paid: #12B886;
  --late: #FF5A76;
  --draft: #FFA62B;
  --quote: #4EA8FF;
  --people: #FF7A9A;

  /* The same hues, dark enough to be text. A status colour is a fill; setting
     a label in it on a light ground gives 1.9:1 for the amber and 2.5:1 for
     the green. Fills use the pair above, words use these. */
  --paid-ink: #0C7A5A;
  --late-ink: #C42340;
  --draft-ink: #9A5B00;

  --page: #FBF7FF;
  --card: #FFFFFF;
  --sunken: #F3EEFC;
  --ink: #1F1A2E;
  --ink-soft: #3B3450;
  --muted: #6A6280;
  --line: #EBE4F5;
  --hairline: rgba(255, 255, 255, .75);

  /* The gradient panel carries white text in both themes, so it keeps its own
     deep violets rather than following --brand, which lightens in the dark
     theme and would drop the text to about 1.9:1. */
  --panel-a: #5B45D9;
  --panel-b: #7B4FE4;

  /* Atmosphere behind the page: two soft washes of brand colour. Weak enough
     that nothing sits on a coloured field, strong enough that the page is not
     a flat sheet of white. */
  --glow-a: rgba(108, 92, 231, .16);
  --glow-b: rgba(255, 122, 154, .11);

  --shadow-sm: 0 2px 10px rgba(31, 26, 46, .06);
  --shadow-md: 0 14px 40px rgba(31, 26, 46, .10);
  --shadow-lg: 0 30px 80px rgba(76, 56, 160, .18);
  --shadow-brand: 0 12px 30px rgba(108, 92, 231, .28);

  --r-sm: 14px;
  --r-md: 20px;
  --r-lg: 26px;
  --r-xl: 34px;
  --r-pill: 999px;

  /* One spacing ramp, so vertical rhythm is a choice rather than a habit. */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 22px;
  --s-6: 30px;
  --s-7: 40px;
  --s-8: 56px;
  --s-9: 76px;
  --s-10: 104px;

  --wrap: 1180px;
  --wrap-narrow: 780px;

  --ease-in: cubic-bezier(.23, 1, .32, 1);      /* things arriving */
  --ease-move: cubic-bezier(.77, 0, .175, 1);   /* things already here */
  --press: cubic-bezier(.34, 1.56, .64, 1);     /* the small overshoot on a tap */
}

/* Dark. Three declarations rather than one, so the toggle wins in both
   directions and the untouched default still follows the system. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --brand: #A79BFF;
    --brand-alt: #C6B6FF;
    --brand-ink: #C6B6FF;
    --on-brand: #17122A;
    --paid: #4FE3B0;
    --late: #FF8FA3;
    --draft: #FFC46B;
    --quote: #7BC8FF;
    --people: #FFA3B8;

    --paid-ink: #4FE3B0;
    --late-ink: #FF8FA3;
    --draft-ink: #FFC46B;

    --page: #141021;
    --card: #1E1830;
    --sunken: #241D3A;
    --ink: #F2EEFF;
    --ink-soft: #D9D2F2;
    --muted: #A79FC0;
    --line: #2E2745;
    --hairline: rgba(255, 255, 255, .06);

    --glow-a: rgba(122, 104, 255, .20);
    --glow-b: rgba(255, 122, 154, .10);

    /* Tinted with the page's own hue rather than pure black — a neutral
       shadow over a violet-tinted dark ground reads as a hole in the page. */
    --shadow-sm: 0 2px 10px rgba(8, 4, 24, .45);
    --shadow-md: 0 14px 40px rgba(8, 4, 24, .55);
    --shadow-lg: 0 30px 80px rgba(6, 3, 20, .66);
    --shadow-brand: 0 12px 30px rgba(28, 12, 78, .55);
  }
}

:root[data-theme="dark"] {
  --brand: #A79BFF;
  --brand-alt: #C6B6FF;
  --brand-ink: #C6B6FF;
  --on-brand: #17122A;
  --paid: #4FE3B0;
  --late: #FF8FA3;
  --draft: #FFC46B;
  --quote: #7BC8FF;
  --people: #FFA3B8;

  --paid-ink: #4FE3B0;
  --late-ink: #FF8FA3;
  --draft-ink: #FFC46B;

  --page: #141021;
  --card: #1E1830;
  --sunken: #241D3A;
  --ink: #F2EEFF;
  --ink-soft: #D9D2F2;
  --muted: #A79FC0;
  --line: #2E2745;
  --hairline: rgba(255, 255, 255, .06);

  --glow-a: rgba(122, 104, 255, .20);
  --glow-b: rgba(255, 122, 154, .10);

  --shadow-sm: 0 2px 10px rgba(8, 4, 24, .45);
  --shadow-md: 0 14px 40px rgba(8, 4, 24, .55);
  --shadow-lg: 0 30px 80px rgba(6, 3, 20, .66);
  --shadow-brand: 0 12px 30px rgba(28, 12, 78, .55);
}

/* Reset ------------------------------------------------------------------- */

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

html {
  scroll-behavior: smooth;
  /* The sticky header is 72px; an anchor landing under it reads as a broken
     link rather than a scroll. */
  scroll-padding-top: 92px;
  -webkit-text-size-adjust: 100%;
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, Arial, sans-serif;
  font-size: 17px;
  line-height: 1.65;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img, svg, video { max-width: 100%; display: block; }
/* `max-width` without `height: auto` leaves the width/height attributes acting
   as a presentational height, so a 1080x2400 screenshot drawn 248px wide keeps
   its 2400px height and stretches. The attributes stay on the markup — they are
   what reserves the right space before the file arrives — and this is what lets
   the height follow the width. */
img { height: auto; }
a { color: var(--brand); text-decoration: none; }
a:hover { text-decoration: underline; }
button { font: inherit; }

/* Type -------------------------------------------------------------------- */

h1, h2, h3, h4 {
  margin: 0 0 .4em;
  font-weight: 800;
  line-height: 1.12;
  letter-spacing: -.028em;
  text-wrap: balance;
}
h1 { font-size: clamp(2.4rem, 1.5rem + 3.6vw, 4.1rem); letter-spacing: -.038em; }
h2 { font-size: clamp(1.75rem, 1.2rem + 2.2vw, 2.75rem); letter-spacing: -.032em; }

/* ⚠ A heading that breaks one word onto its own last line reads as an accident,
   and on a page of ten headings it happens to two or three of them. `balance`
   evens the lines; `pretty` on running text only prevents the widow, which is
   all body copy needs. */
h1, h2, h3, h4 { text-wrap: balance; }
p, .lede, li { text-wrap: pretty; }
h3 { font-size: 1.2rem; font-weight: 700; letter-spacing: -.018em; }
h4 { font-size: 1rem; font-weight: 700; letter-spacing: -.01em; }
p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

.lede {
  font-size: clamp(1.06rem, .98rem + .45vw, 1.3rem);
  line-height: 1.6;
  color: var(--muted);
  max-width: 62ch;
  text-wrap: pretty;
}
.muted { color: var(--muted); }
.small { font-size: .92rem; }
.tiny { font-size: .82rem; }
strong { font-weight: 700; }

/* A word or phrase lifted out of a headline. Underlined by a soft brand
   sweep rather than a rule, so it reads as emphasis and not as a link. */
.mark-brand {
  color: var(--brand);
  background: linear-gradient(transparent 62%, color-mix(in srgb, var(--brand) 20%, transparent) 62%);
  /* The padding only exists so the sweep runs a little past the first and last
     letter. Cancelling it in the margin keeps that bleed without spending
     layout width — at hero size .06em is ~4px, and it was visibly detaching
     the full stop in "never takes a cut ." */
  padding: 0 .06em; margin: 0 -.06em;
}

/* Containers -------------------------------------------------------------- */

.wrap { width: 100%; max-width: var(--wrap); margin: 0 auto; padding: 0 24px; }
.narrow { max-width: var(--wrap-narrow); }
.mid { max-width: 960px; }

/* Shared components ------------------------------------------------------- */
/* These three are used by both the marketing pages and the app, so they live
   here rather than in either sheet. The app loads base.css + app.css and never
   downloads the marketing styles. */

/* ⚠ Scoped to the anchor on purpose. As a bare `.brand` this rule also claimed
   `class="callout brand"` — the brand-tinted callout used on about, contact,
   delete-account, books and six help guides — and laid its heading and its
   paragraph out side by side in 1.18rem/800 logo type. It also made the
   callout shrink-to-fit, which pushed /contact 43px wider than a 360px phone.
   Every logo in the site and the app is an `<a class="brand">`; the app's own
   `.side .brand` / `.app-bar .brand` overrides still outrank this. */
a.brand {
  display: inline-flex; align-items: center; gap: 10px;
  font-weight: 800; letter-spacing: -.035em; font-size: 1.18rem;
  color: var(--ink); text-decoration: none;
}
a.brand:hover { text-decoration: none; }
a.brand .mark { width: 34px; height: 34px; border-radius: 10px; box-shadow: var(--shadow-sm); }

/* Shared: the marketing pages use these lists everywhere, and so does the app's
   front door (`showFrontDoor` in ui.js emits `<ul class="ticks">`, and app.css
   styles `.gate-intro .ticks li`). While this lived in site.css — which the app
   deliberately never loads — the gate's three selling points rendered as an
   unstyled browser bullet list. The `.crosses` and `.panel` variants are
   marketing-only and stay in site.css. */
.ticks { list-style: none; padding: 0; margin: 0; display: grid; gap: 11px; }
/* ⚠ The dot is positioned, not a flex item. As a flex row the item's own
   contents became flex items too, so `<strong>Credit notes</strong> — copied
   from…` was laid out as two columns: the term wrapped inside a narrow column
   of its own and the sentence continued in a second one, with the em dash
   dangling at a different offset on every row. 53 bullets across features,
   pricing, books, tax, your-records, contact and the landing page read that
   way. Positioned, the bullet is one ordinary sentence again.
   25px = the 14px dot + the 11px that used to be the flex gap. */
.ticks li { position: relative; padding-left: 25px; }
.ticks li::before {
  content: ""; position: absolute; left: 0; top: .52em;
  width: 14px; height: 14px; border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--paid) 22%, transparent);
  box-shadow: inset 0 0 0 4px var(--paid);
}

.card {
  position: relative;
  background: var(--card); border-radius: var(--r-lg);
  padding: 28px; box-shadow: var(--shadow-sm);
}
/* The hairline that makes a card look lit rather than pasted on. */
.card::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  box-shadow: inset 0 1px 0 var(--hairline); pointer-events: none;
}
.card h3 { margin-bottom: .38em; }

/* The ⓘ ------------------------------------------------------------------- */
/* Here rather than in app.css because the sign-in panel is drawn on both the
   app and the public page, and only this sheet is loaded by both. */

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

.icon-link {
  border: 0; background: transparent; color: var(--muted); cursor: pointer;
  width: 32px; height: 32px; border-radius: var(--r-sm);
  display: inline-grid; place-items: center; vertical-align: middle; flex: 0 0 auto;
}
.icon-link:hover { background: var(--sunken); color: var(--ink); }
.icon-link[hidden] { display: none; }

/* The panel the ⓘ opens. app.css restyles it as one of the app's own sheets
   where that stylesheet is loaded; this is what the public page gets. */
dialog.info-sheet {
  border: 0; padding: 0; background: transparent; color: var(--ink);
  max-width: 460px; width: calc(100% - 28px);
}
dialog.info-sheet::backdrop { background: rgba(12, 8, 26, .55); }
dialog.info-sheet .sheet-body {
  background: var(--card); border-radius: var(--r-xl); padding: 24px;
  box-shadow: var(--shadow-lg); max-height: min(86vh, 900px); overflow: auto;
}
dialog.info-sheet h2 { font-size: 1.2rem; letter-spacing: -.02em; margin: 0 0 .6em; }
dialog.info-sheet p { margin: 0 0 .8em; }
dialog.info-sheet .actions { display: flex; justify-content: flex-end; margin-top: 18px; }

/* Sign-in panel ----------------------------------------------------------- */
/* Rendered by assets/js/auth-panel.js into both the app's Account screen and
   the public /sign-in page, so it lives here rather than in either sheet. */

.auth-card { max-width: 440px; margin: 0 auto; }
/* An h2 for the document outline — these pages have no other section heading —
   sized like the card title it actually is. */
.auth-heading { font-size: 1.2rem; font-weight: 700; letter-spacing: -.018em; margin: 0; }
.auth-card .card { display: grid; gap: 14px; }
.or {
  display: flex; align-items: center; gap: 12px; color: var(--muted);
  font-size: .84rem; margin: 4px 0;
}
.or::before, .or::after {
  content: ''; flex: 1 1 auto; height: 1px; background: var(--line);
}
.google-button { display: flex; justify-content: center; min-height: 44px; }
/* The button and its ⓘ on one line. The button takes what is left, so the
   width handed to Google still fits the card it is sitting in. */
.google-row { display: flex; align-items: center; gap: 6px; }
.google-row .google-button { flex: 1 1 auto; min-width: 0; }
.avatar-photo { width: 44px; height: 44px; border-radius: 999px; flex: 0 0 auto; }

.linkish {
  border: 0; background: none; color: var(--brand-ink); font: inherit;
  font-weight: 700; cursor: pointer; text-align: left;
  /* Padded to a comfortable target rather than a bare line of text: it is the
     control somebody reaches for on a phone, having already mistyped once. */
  padding: 8px 2px; margin: -8px -2px; align-self: start;
}
.linkish:hover { text-decoration: underline; }

/* The one message slot the panel uses for every outcome, good or bad. */
.auth-note {
  margin: 0; padding: 12px 14px; border-radius: var(--r-sm);
  font-size: .92rem; font-weight: 600;
  background: color-mix(in srgb, var(--paid) 14%, transparent); color: var(--paid-ink);
}
.auth-note.bad { background: color-mix(in srgb, var(--late) 14%, transparent); color: var(--late-ink); }
.auth-note[hidden] { display: none; }

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

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: 13px 24px; border: 0; border-radius: var(--r-pill);
  font: inherit; font-weight: 700; font-size: .98rem; letter-spacing: -.01em;
  cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: transform .12s var(--press), box-shadow .22s ease, background .2s ease, color .2s ease;
}
.btn:hover { text-decoration: none; }
.btn:active { transform: scale(.978); }
.btn svg { width: 19px; height: 19px; flex: 0 0 auto; }

.btn-primary { background: var(--brand); color: var(--on-brand); box-shadow: var(--shadow-brand); }
.btn-primary:hover { background: color-mix(in srgb, var(--brand) 88%, #000); box-shadow: var(--shadow-md); }
.btn-ghost { background: var(--sunken); color: var(--ink); }
.btn-ghost:hover { background: color-mix(in srgb, var(--brand) 12%, var(--sunken)); }
.btn-quiet { background: transparent; color: var(--ink); box-shadow: inset 0 0 0 1.5px var(--line); }
.btn-quiet:hover { box-shadow: inset 0 0 0 1.5px var(--brand); color: var(--brand); }
/* The secondary button on a gradient panel. A translucent white *fill* lightens
   the ground under white text — 3.4:1 at best — so this is an outline instead,
   which leaves the label sitting on the panel itself at about 5.2:1. */
.btn-on-brand {
  background: transparent; color: #fff;
  box-shadow: inset 0 0 0 1.6px rgba(255, 255, 255, .55);
}
.btn-on-brand:hover { box-shadow: inset 0 0 0 1.6px #fff; background: transparent; }
.btn-lg { padding: 16px 32px; font-size: 1.05rem; }
.btn-sm { padding: 9px 17px; font-size: .9rem; }
.btn-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }

.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* Focus ------------------------------------------------------------------- */

:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 4px; }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--brand); color: var(--on-brand);
  padding: 12px 18px; border-radius: 0 0 var(--r-sm) 0; font-weight: 700;
}
.skip:focus { left: 0; }

/* Reveal on scroll -------------------------------------------------------- */
/* Armed only while the script is running, so content can never be trapped
   invisible by a browser that never fires the observer. */

html.js .reveal:not(.is-in) { opacity: 0; transform: translateY(16px); }
html.js .reveal.is-in {
  opacity: 1; transform: none;
  transition: opacity .6s ease, transform .6s var(--ease-in);
  transition-delay: var(--delay, 0ms);
}

@media (prefers-reduced-motion: reduce) {
  html.js .reveal:not(.is-in),
  html.js .reveal.is-in { opacity: 1; transform: none; transition: none; }
  .btn:active { transform: none; }
  * { animation-duration: .001ms !important; animation-iteration-count: 1 !important; }
}

/* The theme toggle ---------------------------------------------------------
   Lives here rather than in site.css because both the public header and the
   app shell mount one, and the app never downloads site.css. Moving it back
   would silently unstyle the app's button, not the site's. */
.theme-toggle {
  width: 40px; height: 40px; flex: 0 0 auto;
  display: grid; place-items: center;
  margin-left: 6px; border: 0; border-radius: var(--r-pill);
  background: var(--sunken); color: var(--ink-soft); cursor: pointer;
  transition: background .18s ease, color .18s ease, transform .12s var(--press);
}
.theme-toggle:hover { background: color-mix(in srgb, var(--brand) 14%, var(--sunken)); color: var(--brand); }
.theme-toggle:active { transform: scale(.94); }
.theme-toggle svg { width: 19px; height: 19px; }
.theme-toggle .icon-moon { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-sun { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .icon-sun { display: none; }
  :root:not([data-theme="light"]) .theme-toggle .icon-moon { display: block; }
}

/* WhatsApp — in reach on every page and in the app, dressed like the rest of
   Billkeep: a card pill with the soft shadow, and WhatsApp's own green kept to
   the disc, where it does the job of saying which app this opens. On a phone
   it folds to the disc alone, so it covers as little of the page as it can.
   Lives here because the app never downloads site.css. */
.wa-float {
  position: fixed; z-index: 50;
  right: max(18px, env(safe-area-inset-right));
  bottom: max(18px, env(safe-area-inset-bottom));
  display: inline-flex; align-items: center; gap: 11px;
  padding: 7px 20px 7px 7px; border-radius: var(--r-pill);
  background: var(--card); color: var(--ink);
  font-weight: 700; font-size: .95rem; letter-spacing: -.01em; text-decoration: none;
  box-shadow: var(--shadow-md);
  transition: transform .18s var(--press), box-shadow .22s ease;
  animation: wa-in .6s var(--ease-in) .6s backwards;
}
.wa-float:hover { text-decoration: none; }
@media (hover: hover) {
  .wa-float:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
}
.wa-float:active { transform: scale(.97); }
.wa-disc {
  width: 42px; height: 42px; border-radius: 50%; flex: 0 0 auto;
  display: grid; place-items: center;
  background: #25D366; color: #FFFFFF;
  box-shadow: 0 6px 16px rgba(37, 211, 102, .32);
}
.wa-disc svg { width: 24px; height: 24px; }
@keyframes wa-in { from { opacity: 0; transform: translateY(14px) scale(.96); } }

@media (max-width: 640px) {
  .wa-float { padding: 0; background: transparent; box-shadow: none; }
  .wa-label { display: none; }
  .wa-disc { width: 54px; height: 54px; box-shadow: 0 10px 26px rgba(37, 211, 102, .38), var(--shadow-md); }
  .wa-disc svg { width: 28px; height: 28px; }
}
@media (prefers-reduced-motion: reduce) {
  .wa-float { animation: none; transition: none; }
}
