/* ==========================================================================
   @ACP O DESIGN_TOKENS
   app.css — the entire visual system for the merchant gateway.
   Owned by plans/02-design-system.md. There is no second stylesheet.

   RULES, in order of how much damage breaking them does:
     1. A component rule may reference a token. It may NOT contain a colour
        literal, a raw font stack, a pixel radius or a numeric font-weight.
     2. If a component class exists, it is the ONLY way to build that thing.
        The reference app defined .app-card and then hand-rolled 168 cards.
     3. Dark mode is three :root blocks in section 3. NOTHING below section 3
        mentions dark mode, ever.
     4. Every class in this file starts with `app-` (plans/00-conventions.md
        section 5.7). Pages may use no other class.
   ========================================================================== */

/* ==========================================================================
   1. FONTS
   Self-hosted from pb_public/fonts/. See plans/02-design-system.md section 1.
   All three are VARIABLE fonts: one file carries the whole weight range, so
   every weight token in section 2 is a genuinely different drawing.
   DO NOT add a webfont CDN link. The Content-Security-Policy blocks it
   (default-src 'self', with no font-src override) and `wons-engine review`
   reports SEC-SRI.
   ========================================================================== */

@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-var.woff2") format("woff2");
  /* Verified fvar axis: wght 100 - 900, default 400. */
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Manrope";
  src: url("/fonts/manrope-var.woff2") format("woff2");
  /* Verified fvar axis: wght 200 - 800, DEFAULT 200.
     DO NOT delete this range. Without it every Manrope element falls back to
     the 200 default instance and renders hairline-thin. */
  font-weight: 200 800;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "JetBrains Mono";
  src: url("/fonts/jetbrains-mono-var.woff2") format("woff2");
  /* Verified fvar axis: wght 400 - 800, default 400. */
  font-weight: 400 800;
  font-style: normal;
  font-display: swap;
}

/* ==========================================================================
   2. TOKENS - light mode is the default declaration.
   ========================================================================== */

:root {
  color-scheme: light;

  /* ---- 2.1 The ONE neutral ramp. There is no second ramp. ---------------
     CHANGED 2026-08-12: Slate out, a TRUE neutral in. Slate carries a blue
     cast (#f1f5f9 is measurably blue), and against it the teal accent made
     every screen read as "a blue app with a green thing on it". The reference
     the user asked for — the shadcn admin dashboard — is monochrome: the
     colour in it comes from data, never from chrome. Same eleven steps, same
     names, so every rule below follows without being touched. */
  --app-n-50:  #fafafa;
  --app-n-100: #f5f5f5;
  --app-n-200: #e5e5e5;
  --app-n-300: #d4d4d4;
  --app-n-400: #a3a3a3;
  --app-n-500: #737373;
  --app-n-600: #525252;
  --app-n-700: #404040;
  --app-n-800: #262626;
  --app-n-900: #171717;
  --app-n-950: #0a0a0a;

  /* ---- 2.2 Raw palette. Pages never name these; section 2.3 maps them. -- */
  --app-t-50:  #f0fdfa;
  --app-t-300: #5eead4;
  --app-t-400: #2dd4bf;
  --app-t-600: #0d9488;
  --app-t-700: #0f766e;
  --app-t-800: #115e59;

  --app-ok-50:  #ecfdf5;
  --app-ok-300: #6ee7b7;
  --app-ok-400: #34d399;
  --app-ok-600: #059669;
  --app-ok-700: #047857;
  --app-ok-900: #022c22;

  --app-warn-50:  #fffbeb;
  --app-warn-300: #fcd34d;
  --app-warn-400: #fbbf24;
  --app-warn-600: #d97706;
  --app-warn-800: #92400e;

  --app-bad-50:  #fef2f2;
  --app-bad-300: #fca5a5;
  --app-bad-400: #f87171;
  --app-bad-600: #dc2626;
  --app-bad-700: #b91c1c;
  --app-bad-950: #450a0a;

  --app-info-50:  #f0f9ff;
  --app-info-300: #7dd3fc;
  --app-info-400: #38bdf8;
  --app-info-600: #0284c7;
  --app-info-700: #0369a1;

  /* ---- 2.3 Surfaces and ink. Ratios measured against the pairs shipped. - */
  /* CHANGED 2026-08-12. The desk was n-100 and cards floated on it in a
     visibly grey trough. In the reference the page is nearly white, the
     sidebar is the only tinted plane, and a card is told apart by ONE
     hairline. Less contrast between surfaces, more between ink and paper. */
  --app-bg:         var(--app-n-50);    /* the desk the app floats on       */
  --app-surface:    #ffffff;            /* cards, panels, table rows        */
  --app-surface-2:  var(--app-n-50);    /* table head, inset areas          */
  --app-surface-3:  var(--app-n-100);   /* hover fill                       */
  /* WHERE YOU ARE, as opposed to what you are pointing at. It has to be a
     shade stronger than the hover fill or the active row disappears the moment
     the pointer is over it, and it has to be a token so section 3 can flip it. */
  --app-selected:   var(--app-n-200);

  --app-ink:        var(--app-n-900);   /* 17.85:1 on surface, 16.30 on bg  */
  --app-ink-muted:  var(--app-n-600);   /*  7.58:1 on surface,  6.92 on bg  */
  --app-ink-faint:  var(--app-n-500);   /*  4.76:1 on surface,  4.55 on s-2 */
  --app-ink-invert: #ffffff;

  /* ONE border colour, like the reference: n-200 everywhere a hairline is
     drawn. line-strong survives as a name because ~40 rules use it, but it
     is the same value now — two greys a shade apart read as a mistake.
     field-line stays DARKER than the rest on purpose: an input boundary is
     information, not decoration, and n-200 on white measures 1.3:1. */
  --app-line:        var(--app-n-200);  /* decorative hairline only         */
  --app-line-strong: var(--app-n-200);  /* card ring, decorative only       */
  --app-field-line:  var(--app-n-300);  /*  every input boundary            */

  /* ---- 2.4 Brand ------------------------------------------------------- */
  /* CHANGED 2026-08-12: the brand is INK. The teal ramp (2.2) is still here
     and is still what a chart series or a chosen state is drawn in, but the
     chrome — links, avatars, chips, focus — is monochrome now. A product
     whose every surface is branded has nothing left to emphasise with. */
  --app-brand:           var(--app-n-900);  /* 16.1:1 on surface            */
  --app-brand-strong:    var(--app-n-950);  /* hover                        */
  --app-brand-ink:       #ffffff;
  --app-brand-soft:      var(--app-n-100);

  /* The primary ACTION. ADDED 2026-08-09: the public site's call to action is
     near-black and the signed-in app's was teal, so the same product asked
     twice in two different voices. Ink is the action colour on both now, and
     teal stays what it always was — the brand accent on chips, avatars, links
     and status. Section 3 inverts these two for dark mode; nothing else needs
     to know. */
  --app-action:      var(--app-n-900);
  --app-action-hover: var(--app-n-800);
  --app-action-ink:  #ffffff;
  --app-brand-soft-ink:  var(--app-n-900);  /* 15.3:1 on brand-soft         */
  --app-ring:            var(--app-n-900);  /* the focus ring is ink too    */

  /* ---- 2.5 Status. ink / fill / edge for each of five meanings. --------
     Consumed by .app-status-* in section 9, which is the ONLY place that
     maps an invoice status onto one of these five.                        */
  --app-neutral-ink:  var(--app-n-600);   /* 6.92:1 on neutral-fill        */
  --app-neutral-fill: var(--app-n-100);
  --app-neutral-edge: var(--app-n-500);   /* 4.76:1 on surface             */

  --app-info-ink:  var(--app-info-700);   /* 5.57:1 on info-fill           */
  --app-info-fill: var(--app-info-50);
  --app-info-edge: var(--app-info-600);   /* 4.10:1 on surface             */

  --app-warn-ink:  var(--app-warn-800);   /* 6.84:1 on warn-fill           */
  --app-warn-fill: var(--app-warn-50);
  --app-warn-edge: var(--app-warn-600);   /* 3.19:1 on surface             */

  --app-ok-ink:  var(--app-ok-700);       /* 5.21:1 on ok-fill             */
  --app-ok-fill: var(--app-ok-50);
  --app-ok-edge: var(--app-ok-600);       /* 3.77:1 on surface             */

  --app-bad-ink:  var(--app-bad-700);     /* 5.91:1 on bad-fill            */
  --app-bad-fill: var(--app-bad-50);
  --app-bad-edge: var(--app-bad-600);     /* 4.83:1 on surface             */

  /* ---- 2.6 The floating shell ------------------------------------------ */
  --app-glass:       rgba(255, 255, 255, 0.72);
  --app-glass-line:  rgba(10, 10, 10, 0.08);
  --app-scrim:       rgba(10, 10, 10, 0.45);

  /* ---- 2.7 Type -------------------------------------------------------- */
  --app-font-sans:    "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  /* CHANGED 2026-08-12: THE DISPLAY FACE IS THE TEXT FACE. Manrope set the
     headings and Inter set everything else, and at 14-16px — which is every
     heading in an admin screen — the difference reads as an accident rather
     than a decision. The reference uses one family at every size. Manrope is
     still loaded for the public site, which is where a display face earns
     its keep. */
  --app-font-display: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --app-font-mono:    "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;

  --app-fs-2xs:  0.6875rem;
  --app-fs-xs:   0.75rem;
  --app-fs-sm:   0.8125rem;
  --app-fs-base: 0.875rem;
  --app-fs-md:   1rem;
  --app-fs-lg:   1.125rem;
  --app-fs-xl:   1.375rem;
  --app-fs-2xl:  1.75rem;
  --app-fs-3xl:  2.25rem;

  --app-lh-tight:  1.2;
  --app-lh-snug:   1.35;
  --app-lh-normal: 1.55;

  --app-track-eyebrow: 0.08em;
  --app-track-tight:  -0.011em;

  /* Six weights. Never write a numeric font-weight outside this block. */
  --app-w-regular:        400;
  --app-w-medium:         500;
  --app-w-semibold:       600;
  --app-w-bold:           700;
  /* 700/800 was a poster, not an interface. The reference sets titles at 500
     and the page heading at 600, and reserves weight for the one number that
     matters on the screen. */
  --app-w-display:        600;
  --app-w-display-strong: 600;

  /* ---- 2.8 Radii ------------------------------------------------------- */
  /* Names kept, values matched to the reference (--radius 0.625rem, so a
     control is 8px and a card is 14px). 16px cards next to 12px buttons was
     the roundest thing on the screen being the biggest. */
  --app-r-8:    8px;
  --app-r-12:  10px;
  --app-r-16:  14px;   /* the card default */
  --app-r-20:  18px;
  --app-r-pill: 999px;

  /* ---- 2.9 Shadows. Near-flat: the hairline ring does the work. -------- */
  /* Neutral, not slate-tinted, and shallower: a card in the reference has NO
     drop shadow at all, only a hairline. Depth is kept for what actually
     floats — menus (2) and modals (3). */
  --app-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.04);
  --app-shadow-2: 0 4px 16px -6px rgba(0, 0, 0, 0.14);
  --app-shadow-3: 0 24px 60px -20px rgba(0, 0, 0, 0.30);
  --app-focus:    0 0 0 3px color-mix(in srgb, var(--app-ring) 18%, transparent);

  /* ---- 2.10 Spacing, on a 4px grid ------------------------------------- */
  --app-s-1:  0.25rem;
  --app-s-2:  0.5rem;
  --app-s-3:  0.75rem;
  --app-s-4:  1rem;
  --app-s-5:  1.25rem;
  --app-s-6:  1.5rem;
  --app-s-8:  2rem;
  --app-s-10: 2.5rem;
  --app-s-12: 3rem;

  /* ---- 2.11 Layout ----------------------------------------------------- */
  --app-inset:       1rem;      /* ARCHITECTURE.md section 10: 1rem inset   */
  --app-gap:         0.5rem;
  /* CHANGED 2026-08-09: --app-rail-w and --app-nav-w described two permanent
     bars. There is one navigation now, at two widths. */
  --app-side-w:      16rem;
  --app-side-mini-w:  4.25rem;
  /* WIDENED 2026-08-12. 76rem centred left ~100px of dead gutter on either
     side of a 1440 screen while the content inside it was crowded — the worst
     of both. The reference fills the width it is given and controls density
     with padding instead. */
  --app-content-max: 88rem;
  --app-blur:        12px;      /* ARCHITECTURE.md section 10: blur(12px)   */

  /* ---- 2.12 Motion ----------------------------------------------------- */
  --app-fast: 120ms;
  --app-slow: 220ms;
  --app-ease: cubic-bezier(0.2, 0.6, 0.3, 1);

  /* ---- 2.13 Stacking --------------------------------------------------- */
  --app-z-topbar: 20;
  --app-z-scrim:  30;
  --app-z-drawer: 40;
  --app-z-modal:  60;
  --app-z-flash:  70;

  /* ---- 2.14 The public site (section 19) --------------------------------
     ADDED 2026-08-09 with the public landing page. Not in
     plans/02-design-system.md — no plan describes a public site.

     These are SEPARATE tokens, not aliases of the app's, and section 3 does
     NOT override them. That is the whole point: the signed-in application has
     a dark mode and a theme toggle, and the public site is light-only, so a
     visitor whose OS is dark still sees the page as it was designed. Section
     2.9d of the design system forbids a colour literal below section 3, which
     is why they live here rather than next to the rules that use them.
     ---------------------------------------------------------------------- */
  --mk-ink:        #050505;   /* near-black: text, dark panels, dark buttons */
  --mk-paper:      #ffffff;
  /* The field fill on the public side. Warm-neutral rather than pure grey, so
     it sits on the white card as a considered surface rather than as dirt. */
  --mk-wash:       #f4f4f3;
  --mk-tint:       #f4f4f4;   /* the quiet panel between two white ones      */
  --mk-ghost:      #ededed;   /* the half of a split heading that recedes    */
  --mk-line:       #dedede;   /* every hairline on the light side            */
  --mk-mute:       #929292;   /* secondary prose                             */
  --mk-quiet:      #6b6b6b;   /* tertiary: fine print                        */
  --mk-ink-soft:   #0f0f0f;   /* a card on the dark side                     */
  --mk-line-dark:  #262626;
  --mk-mute-dark:  #a3a3a3;
  --mk-accent:     #8f6afd;   /* used once, on the paid state. Not a brand   */
  --mk-radius:     1.25rem;
  --mk-radius-sm:  0.75rem;
  --mk-wrap:       72rem;
}

/* ==========================================================================
   3. DARK MODE - the whole mechanism, in one place.

   Nothing below this section mentions dark mode. Not one rule. The reference
   app wrote `dark:` inline 3,845 times and its dark mode was still
   inconsistent; here dark mode reassigns the SAME semantic tokens and every
   component follows for free.

   Three blocks:
     A. follow the operating system, unless the user forced light
     B. the user forced dark
     C. the user forced light, overriding a dark operating system

   Block A and block B are IDENTICAL and must stay identical.
   DO NOT edit one without editing the other. There is a verification command
   for this in plans/02-design-system.md section 2.9.
   ========================================================================== */

/* ---- A. operating-system dark ----------------------------------------- */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --app-bg:        var(--app-n-950);
    --app-surface:   var(--app-n-900);
    --app-surface-2: var(--app-n-800);
    --app-surface-3: var(--app-n-800);
    --app-selected:  var(--app-n-700);

    --app-ink:        var(--app-n-200);  /* 14.48:1 on surface             */
    --app-ink-muted:  var(--app-n-300);  /* 12.02:1 on surface             */
    --app-ink-faint:  var(--app-n-400);  /*  6.96:1 on surface             */
    --app-ink-invert: var(--app-n-950);

    --app-line:        var(--app-n-800);
    --app-line-strong: var(--app-n-700);
    --app-field-line:  var(--app-n-500);  /* 3.75:1 on surface            */

    /* THE CONTRAST FIX. The reference app used teal-700 here and measured
       2.67:1. teal-400 measures 9.59:1 on this surface. */
    --app-brand:          var(--app-n-200);
    --app-brand-strong:   #ffffff;
    --app-brand-ink:      var(--app-n-950);
    --app-brand-soft:     color-mix(in srgb, var(--app-n-400) 16%, transparent);
    /* Ink on paper becomes paper on ink. A near-black button on a near-black
       page would be invisible. */
    --app-action:       var(--app-n-200);
    --app-action-hover: #ffffff;
    --app-action-ink:   var(--app-n-950);
    --app-brand-soft-ink: var(--app-n-200);
    --app-ring:           var(--app-n-300);

    --app-neutral-ink:  var(--app-n-300);   /* 8.88:1 on neutral-fill     */
    --app-neutral-fill: color-mix(in srgb, var(--app-n-400) 18%, transparent);
    --app-neutral-edge: var(--app-n-400);   /* 6.96:1 on surface          */

    --app-info-ink:  var(--app-info-300);   /* 7.60:1 on info-fill        */
    --app-info-fill: color-mix(in srgb, var(--app-info-400) 18%, transparent);
    --app-info-edge: var(--app-info-400);   /* 8.33:1 on surface          */

    --app-warn-ink:  var(--app-warn-300);   /* 8.49:1 on warn-fill        */
    --app-warn-fill: color-mix(in srgb, var(--app-warn-400) 18%, transparent);
    --app-warn-edge: var(--app-warn-400);   /* 10.69:1 on surface         */

    --app-ok-ink:  var(--app-ok-300);       /* 8.16:1 on ok-fill          */
    --app-ok-fill: color-mix(in srgb, var(--app-ok-400) 18%, transparent);
    --app-ok-edge: var(--app-ok-400);       /* 9.29:1 on surface          */

    --app-bad-ink:  var(--app-bad-300);     /* 7.28:1 on bad-fill         */
    --app-bad-fill: color-mix(in srgb, var(--app-bad-400) 18%, transparent);
    --app-bad-edge: var(--app-bad-400);     /* 6.45:1 on surface          */

    --app-glass:      rgba(10, 10, 10, 0.72);
    --app-glass-line: rgba(229, 229, 229, 0.10);
    --app-scrim:      rgba(0, 0, 0, 0.60);

    --app-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.30);
    --app-shadow-2: 0 4px 16px -6px rgba(0, 0, 0, 0.55);
    --app-shadow-3: 0 24px 60px -20px rgba(0, 0, 0, 0.70);
  }
}

/* ---- B. user forced dark. IDENTICAL to block A. ------------------------ */
:root[data-theme="dark"] {
  color-scheme: dark;

  --app-bg:        var(--app-n-950);
  --app-surface:   var(--app-n-900);
  --app-surface-2: var(--app-n-800);
  --app-surface-3: var(--app-n-800);
  --app-selected:  var(--app-n-700);

  --app-ink:        var(--app-n-200);
  --app-ink-muted:  var(--app-n-300);
  --app-ink-faint:  var(--app-n-400);
  --app-ink-invert: var(--app-n-950);

  --app-line:        var(--app-n-800);
  --app-line-strong: var(--app-n-700);
  --app-field-line:  var(--app-n-500);

  --app-brand:          var(--app-n-200);
  --app-brand-strong:   #ffffff;
  --app-brand-ink:      var(--app-n-950);
  --app-brand-soft:     color-mix(in srgb, var(--app-n-400) 16%, transparent);
  /* FIXED 2026-08-12. These three were in block A and NOT in block B, so the
     rule at the head of section 3 — "block A and block B are IDENTICAL" — was
     false, and the consequence was invisible: a user whose OS is dark got a
     light action button, and a user who pressed the theme TOGGLE got the light
     mode's near-black button on a near-black page. Every primary button and
     every active filter chip disappeared, but only on forced dark, which is
     the path a screenshot of the default never covers. */
  --app-action:       var(--app-n-200);
  --app-action-hover: #ffffff;
  --app-action-ink:   var(--app-n-950);
  --app-brand-soft-ink: var(--app-n-200);
  --app-ring:           var(--app-n-300);

  --app-neutral-ink:  var(--app-n-300);
  --app-neutral-fill: color-mix(in srgb, var(--app-n-400) 18%, transparent);
  --app-neutral-edge: var(--app-n-400);

  --app-info-ink:  var(--app-info-300);
  --app-info-fill: color-mix(in srgb, var(--app-info-400) 18%, transparent);
  --app-info-edge: var(--app-info-400);

  --app-warn-ink:  var(--app-warn-300);
  --app-warn-fill: color-mix(in srgb, var(--app-warn-400) 18%, transparent);
  --app-warn-edge: var(--app-warn-400);

  --app-ok-ink:  var(--app-ok-300);
  --app-ok-fill: color-mix(in srgb, var(--app-ok-400) 18%, transparent);
  --app-ok-edge: var(--app-ok-400);

  --app-bad-ink:  var(--app-bad-300);
  --app-bad-fill: color-mix(in srgb, var(--app-bad-400) 18%, transparent);
  --app-bad-edge: var(--app-bad-400);

  --app-glass:      rgba(10, 10, 10, 0.72);
  --app-glass-line: rgba(229, 229, 229, 0.10);
  --app-scrim:      rgba(0, 0, 0, 0.60);

  --app-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.30);
  --app-shadow-2: 0 4px 16px -6px rgba(0, 0, 0, 0.55);
  --app-shadow-3: 0 24px 60px -20px rgba(0, 0, 0, 0.70);
}

/* ---- C. user forced light on a dark operating system.
     Nothing to redeclare: block A already excludes [data-theme="light"], so
     the :root defaults in section 2 apply unchanged. This rule exists only to
     set color-scheme so the browser paints its own controls light. ------- */
:root[data-theme="light"] {
  color-scheme: light;
}

/* ==========================================================================
   4. RESET AND BASE
   ========================================================================== */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100vh;
  background-color: var(--app-bg);
  /* A single, very soft brand wash so the desk reads as a surface rather than
     as empty chrome. ARCHITECTURE.md section 10: the app sits ON something. */
  background-image:
    radial-gradient(60rem 40rem at 12% -10%, color-mix(in srgb, var(--app-brand) 9%, transparent), transparent 60%);
  background-attachment: fixed;
  color: var(--app-ink);
  font-family: var(--app-font-sans);
  font-size: var(--app-fs-base);
  font-weight: var(--app-w-regular);
  line-height: var(--app-lh-normal);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, p, figure, blockquote, dl, dd { margin: 0; }
ul, ol { margin: 0; padding: 0; }

a {
  color: var(--app-brand);
  text-decoration: none;
}
a:hover { color: var(--app-brand-strong); text-decoration: underline; }

img, svg { max-width: 100%; }
svg { display: block; }

/* One focus treatment for the whole application. */
:focus-visible {
  outline: 2px solid var(--app-ring);
  outline-offset: 2px;
  border-radius: var(--app-r-8);
}

/* Screen-reader-only, and the skip link that becomes visible on focus. */
.app-sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%);
  white-space: nowrap;
}

.app-skip {
  position: absolute;
  left: var(--app-s-4);
  top: -4rem;
  z-index: var(--app-z-flash);
  padding: var(--app-s-2) var(--app-s-4);
  border-radius: var(--app-r-12);
  background: var(--app-surface);
  color: var(--app-ink);
  box-shadow: var(--app-shadow-2);
  transition: top var(--app-fast) var(--app-ease);
}
.app-skip:focus { top: var(--app-s-4); text-decoration: none; }

/* HIDDEN MEANS HIDDEN.
   -------------------------------------------------------------------------
   The browser's own [hidden] { display: none } is specificity (0,1,0) and so
   is .app-btn, and .app-btn sets display: inline-flex LATER in this file — so
   every button the scripts hide with the `hidden` attribute stayed on screen.
   That is how "Discard this one" came to be visible on a cart it refuses to
   discard: the control was showing, the guard behind it was correct, and the
   click did nothing.

   Doubled attribute selector, not !important: (0,2,0) beats any single class,
   and rule 1 of this file has no exception for convenience.
   ========================================================================= */
[hidden][hidden] { display: none; }

/* ==========================================================================
   5. THE SHELL — ONE navigation, two widths

   REWRITTEN 2026-08-09. What was here before: a 3.5rem pill-shaped icon rail
   AND a 14.5rem labelled nav panel, both permanent, both listing the same six
   destinations. Overview, Invoices, Counter, Payments, Settlement and Settings
   each appeared TWICE on screen at once, four rem apart. That is not a rail and
   a nav; that is one nav rendered twice.

   Now there is one sidebar with two widths:

     wide (default)                  mini  (:root[data-nav="mini"])
     ├─ logo + platform name         ├─ logo
     ├─ workspace + role             ├─ workspace initial
     ├─ section headings + labels    ├─ icons only, with native tooltips
     └─ user + sign out + collapse   └─ the same, iconised

   Mini is not a different navigation. It is this one with the labels hidden —
   which is what the rail was imitating. The state lives on the root element so
   the head script can apply it before first paint, and it survives navigation
   through localStorage.

   Below 1024px neither width applies: the sidebar becomes an off-canvas drawer
   driven by the checkbox at the top of body. No JavaScript is involved there,
   so it cannot break under a strict CSP and it resets on every navigation.
   ========================================================================== */

.app-drawer-toggle { position: absolute; opacity: 0; pointer-events: none; }

/* The icon component's own class. It lives here rather than in the sidebar
   rules because the SAME component is used in buttons, empty states and page
   heads — and an <svg> with no intrinsic size falls back to 300x150, so losing
   this one line does not shrink an icon, it explodes the layout around it. */
.app-nav-icon { width: 1rem; height: 1rem; flex: none; }

.app-shell {
  min-height: 100vh;
  display: grid;
  grid-template-columns: var(--app-side-w) minmax(0, 1fr);
  background: var(--app-bg);
}

:root[data-nav="mini"] .app-shell { grid-template-columns: var(--app-side-mini-w) minmax(0, 1fr); }

/* ---- 5.1 The sidebar ---------------------------------------------------- */

.app-side {
  position: sticky;
  top: 0;
  align-self: start;
  height: 100vh;
  display: flex;
  flex-direction: column;
  gap: var(--app-s-2);
  padding: var(--app-s-4) var(--app-s-3);
  /* CHANGED 2026-08-12: the sidebar is the tinted plane and the page is the
     white one — the reference's arrangement, and the reason its content area
     reads as the subject rather than as another panel. */
  background: var(--app-bg);
  border-right: 1px solid var(--app-line);
  overflow: hidden;
}

/* ---- 5.2 The brand block ------------------------------------------------
   The platform's own identity, from the platform_settings record. The mark is
   always visible; the words go when the sidebar narrows. */

.app-side-brand {
  display: flex;
  align-items: center;
  gap: var(--app-s-3);
  padding: var(--app-s-2) var(--app-s-2) var(--app-s-3);
  min-height: 3rem;
}

.app-side-logo {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--app-r-12);
  background: var(--app-ink);
  color: var(--app-ink-invert);
  overflow: hidden;
  text-decoration: none;
}
.app-side-logo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.app-side-mark {
  font-family: var(--app-font-display);
  font-weight: var(--app-w-display-strong);
  font-size: var(--app-fs-md);
  line-height: 1;
}

.app-side-brand-text { display: grid; min-width: 0; }
.app-side-platform {
  font-family: var(--app-font-display);
  font-weight: var(--app-w-bold);
  font-size: var(--app-fs-base);
  letter-spacing: var(--app-track-tight);
  color: var(--app-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.app-side-platform-sub {
  font-size: var(--app-fs-2xs);
  color: var(--app-ink-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---- 5.3 The workspace chip --------------------------------------------
   Which workspace you are in, and the way to leave it. Its own block, because
   "which company am I looking at" is the question a person with three
   workspaces asks most often and it deserves better than a line of small text. */

.app-side-tenant {
  display: flex;
  align-items: center;
  gap: var(--app-s-3);
  padding: var(--app-s-2) var(--app-s-3);
  border: 1px solid var(--app-line);
  border-radius: var(--app-r-12);
  background: var(--app-surface-2);
  text-decoration: none;
  color: inherit;
  transition: border-color var(--app-fast) var(--app-ease),
              background var(--app-fast) var(--app-ease);
}
.app-side-tenant:hover { border-color: var(--app-line-strong); background: var(--app-surface-3); }

.app-side-tenant-initial {
  flex: none;
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--app-r-8);
  background: var(--app-brand);
  color: var(--app-brand-ink);
  font-size: var(--app-fs-xs);
  font-weight: var(--app-w-bold);
}
.app-side-tenant-text { display: grid; min-width: 0; flex: 1; }
.app-side-tenant-name {
  font-size: var(--app-fs-sm);
  font-weight: var(--app-w-semibold);
  color: var(--app-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.app-side-tenant-role {
  font-size: var(--app-fs-xs);
  color: var(--app-ink-faint);
  text-transform: capitalize;
}
.app-side-tenant-switch { flex: none; color: var(--app-ink-faint); display: grid; }
.app-side-tenant-switch svg { width: 0.875rem; height: 0.875rem; }

/* ---- 5.4 The items ------------------------------------------------------ */

.app-side-nav {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding-top: var(--app-s-2);
}

/* SAY THAT IT SCROLLS. Added 2026-08-22 after watching a first-run merchant.
   The list is twenty-two destinations and the panel is one screen tall, so on
   a 1366x768 laptop it is cut somewhere in the middle of "Money" — and it was
   cut SILENTLY: macOS hides overlay scrollbars until something moves, so the
   last row you can see sits flush against the Collapse footer and the panel
   reads as complete. Members, Settings, Registers, Branding, Plan & usage and
   Shop were all below the fold with nothing on screen saying so.

   The fix is a real scrollbar instead of an overlay one: declaring a width on
   ::-webkit-scrollbar takes the panel off macOS's overlay scrollbars, so the
   track occupies layout and is on screen before anybody touches the trackpad.
   It is honest by construction — it exists only while the list overflows, so a
   window tall enough to show all twenty-two rows still shows none of this. No
   edge fade: a permanent gradient at the top row would claim there is
   something above the first item when the panel is already at rest there.

   THE SCROLLBAR IS NOT ENOUGH ON ITS OWN. ::-webkit-scrollbar is styled below
   and it is worth having, but it cannot be relied on as THE signal: on macOS
   the platform draws overlay scrollbars that occupy no layout and stay hidden
   at rest, and measuring it here confirmed the panel's gutter is still zero.
   (Setting the standard scrollbar-width/scrollbar-color instead makes Chromium
   drop the ::-webkit rules altogether and changes nothing, so they are
   deliberately absent.)

   So the load-bearing signal is the fade on the bottom edge: the last row
   visible dissolves instead of ending, which is the oldest "there is more
   below" there is, and it needs no scrollbar, no script and no measurement.
   Bottom only — a fade at the top would claim there is something above the
   first item when the panel is at rest there, which is the same kind of lie
   this is fixing. When the list fits, the bottom of the box is empty padding
   and the fade falls on nothing, so a tall window shows none of it. */
.app-side-nav {
  mask-image: linear-gradient(to bottom, #000 calc(100% - 1.5rem), transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 1.5rem), transparent 100%);
}
.app-side-nav::-webkit-scrollbar { width: 6px; }
.app-side-nav::-webkit-scrollbar-track { background: transparent; }
.app-side-nav::-webkit-scrollbar-thumb {
  background: var(--app-line-strong);
  border-radius: 999px;
}
.app-side-nav::-webkit-scrollbar-thumb:hover { background: var(--app-ink-faint); }

/* Sentence case, body size, muted — a group label, not a banner. */
.app-side-section {
  margin: var(--app-s-4) 0 var(--app-s-1);
  padding-inline: var(--app-s-3);
  font-size: var(--app-fs-xs);
  font-weight: var(--app-w-medium);
  color: var(--app-ink-faint);
}
.app-side-nav > .app-side-section:first-child { margin-top: 0; }

.app-side-item {
  display: flex;
  align-items: center;
  gap: var(--app-s-3);
  padding: var(--app-s-2) var(--app-s-3);
  border-radius: var(--app-r-8);
  color: var(--app-ink-muted);
  text-decoration: none;
  font-size: var(--app-fs-base);
  font-weight: var(--app-w-medium);
  white-space: nowrap;
  border: 0;
  background: none;
  width: 100%;
  text-align: left;
  font-family: inherit;
  cursor: pointer;
  transition: background var(--app-fast) var(--app-ease),
              color var(--app-fast) var(--app-ease);
}
/* FIXED 2026-08-13. This said var(--app-n-100) — a RAMP value, which is
   #f5f5f5 in every theme because the ramp is the raw palette and does not
   flip. In dark mode it painted a near-white block under near-white text, so
   hovering anything made it vanish. Rule 1 at the head of this file exists for
   exactly this: a component rule references a SEMANTIC token, and
   --app-surface-3 is the one that means "hover fill". */
.app-side-item:hover { background: var(--app-surface-3); color: var(--app-ink); }

/* The active item. CHANGED 2026-08-12: it was a near-black filled block, so
   the heaviest object on every screen was the navigation telling you where
   you already are. A quiet fill and full-strength ink says the same thing and
   lets the page win — which is what the reference does. */
.app-side-item[aria-current="page"] {
  background: var(--app-selected);
  color: var(--app-ink);
  font-weight: var(--app-w-medium);
}
.app-side-item[aria-current="page"]:hover { background: var(--app-selected); }

.app-side-icon { flex: none; display: grid; place-items: center; width: 1.125rem; height: 1.125rem; }
.app-side-icon svg { width: 1.125rem; height: 1.125rem; }
.app-side-label { overflow: hidden; text-overflow: ellipsis; }

/* ---- 5.5 The foot ------------------------------------------------------- */

.app-side-foot {
  border-top: 1px solid var(--app-line);
  padding-top: var(--app-s-2);
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.app-side-quiet { color: var(--app-ink-faint); font-weight: var(--app-w-regular); }

.app-side-user { align-items: center; }
.app-side-avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--app-r-pill);
  background: var(--app-surface-3);
  color: var(--app-ink);
  font-size: var(--app-fs-xs);
  font-weight: var(--app-w-bold);
}
.app-side-user-text { display: grid; min-width: 0; line-height: 1.25; }
.app-side-user-email {
  font-size: var(--app-fs-sm);
  color: var(--app-ink);
  overflow: hidden;
  text-overflow: ellipsis;
}
.app-side-user-role {
  font-size: var(--app-fs-xs);
  color: var(--app-ink-faint);
  text-transform: capitalize;
}

.app-side-collapse .app-side-icon {
  transition: transform var(--app-slow) var(--app-ease);
  transform: rotate(180deg);   /* points left: the direction it is about to go */
}
:root[data-nav="mini"] .app-side-collapse .app-side-icon { transform: none; }

/* ---- 5.6 MINI — the same navigation, labels off ------------------------- */

:root[data-nav="mini"] .app-side { padding-inline: var(--app-s-2); align-items: stretch; }
:root[data-nav="mini"] .app-side-brand { justify-content: center; padding-inline: 0; }
:root[data-nav="mini"] .app-side-brand-text,
:root[data-nav="mini"] .app-side-tenant-text,
:root[data-nav="mini"] .app-side-tenant-switch,
:root[data-nav="mini"] .app-side-user-text,
:root[data-nav="mini"] .app-side-label { display: none; }

:root[data-nav="mini"] .app-side-tenant { justify-content: center; padding-inline: 0; }
:root[data-nav="mini"] .app-side-item { justify-content: center; padding-inline: 0; }

/* A heading with no words would be a stray gap, so it becomes the rule that
   separates one group from the next — the grouping survives, the label does
   not. */
:root[data-nav="mini"] .app-side-section { padding: 0; margin: var(--app-s-3) var(--app-s-2); }
:root[data-nav="mini"] .app-side-section span { display: none; }
:root[data-nav="mini"] .app-side-section::after {
  content: "";
  display: block;
  height: 1px;
  background: var(--app-line);
}
:root[data-nav="mini"] .app-side-nav > .app-side-section:first-child { display: none; }

/* ---- 5.7 The main column ------------------------------------------------ */

.app-main { min-width: 0; display: flex; flex-direction: column; }

.app-topbar {
  position: sticky;
  top: 0;
  z-index: var(--app-z-topbar);
  display: flex;
  align-items: center;
  gap: var(--app-s-3);
  min-height: 3.5rem;
  padding: 0 var(--app-s-6);
  /* Solid. The frosted bar over a white page was blur with nothing to blur. */
  background: var(--app-surface);
  border-bottom: 1px solid var(--app-line);
}

/* The trail, not the title. The page prints its own h1 immediately below. */
.app-crumb {
  display: flex;
  align-items: center;
  gap: var(--app-s-2);
  font-size: var(--app-fs-sm);
  min-width: 0;
}
.app-crumb-section { color: var(--app-ink-faint); white-space: nowrap; }
.app-crumb-sep { color: var(--app-line-strong); }
.app-crumb-here {
  color: var(--app-ink);
  font-weight: var(--app-w-medium);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.app-topbar-actions { margin-left: auto; display: flex; align-items: center; gap: var(--app-s-2); }

.app-burger {
  display: none;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  margin-left: calc(var(--app-s-2) * -1);
  border-radius: var(--app-r-8);
  color: var(--app-ink-muted);
  cursor: pointer;
}
.app-burger:hover { background: var(--app-surface-3); color: var(--app-ink); }

.app-content {
  flex: 1;
  width: 100%;
  max-width: var(--app-content-max);
  margin-inline: auto;
  padding: var(--app-s-6) var(--app-s-8) var(--app-s-12);
}

/* The page plane. The sidebar carries the tint (5.1), so the working area is
   paper — cards on it are told apart by their hairline, not by a trough. */
.app-main { background: var(--app-surface); }

.app-scrim { display: none; }

/* ---- 5.8 Below 1024px: one off-canvas drawer ---------------------------- */

@media (max-width: 1024px) {
  .app-shell,
  :root[data-nav="mini"] .app-shell { grid-template-columns: minmax(0, 1fr); }

  .app-side,
  :root[data-nav="mini"] .app-side {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: var(--app-z-drawer);
    width: min(var(--app-side-w), 86vw);
    padding-inline: var(--app-s-3);
    transform: translateX(-102%);
    transition: transform var(--app-slow) var(--app-ease);
    box-shadow: var(--app-shadow-3);
  }
  /* On a phone the drawer is always the wide one: it is opened deliberately,
     it covers the page, and icons with no labels would be a worse answer to
     "where do I go" than the words. */
  :root[data-nav="mini"] .app-side-brand-text,
  :root[data-nav="mini"] .app-side-tenant-text,
  :root[data-nav="mini"] .app-side-tenant-switch,
  :root[data-nav="mini"] .app-side-user-text,
  :root[data-nav="mini"] .app-side-label { display: block; }
  :root[data-nav="mini"] .app-side-item,
  :root[data-nav="mini"] .app-side-tenant,
  :root[data-nav="mini"] .app-side-brand { justify-content: flex-start; padding-inline: var(--app-s-3); }
  :root[data-nav="mini"] .app-side-section { padding-inline: var(--app-s-3); margin: var(--app-s-4) 0 var(--app-s-1); }
  :root[data-nav="mini"] .app-side-section span { display: inline; }
  :root[data-nav="mini"] .app-side-section::after { display: none; }
  :root[data-nav="mini"] .app-side-nav > .app-side-section:first-child { display: block; }

  .app-drawer-toggle:checked ~ .app-shell .app-side { transform: none; }

  .app-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: var(--app-z-scrim);
    background: color-mix(in srgb, var(--app-n-950) 45%, transparent);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--app-slow) var(--app-ease);
  }
  .app-drawer-toggle:checked ~ .app-shell .app-scrim { opacity: 1; pointer-events: auto; }

  .app-burger { display: grid; }
  .app-side-collapse { display: none; }
  .app-content { padding: var(--app-s-6) var(--app-s-4) var(--app-s-10); }
  .app-topbar { padding-inline: var(--app-s-4); }
}


/* ==========================================================================
   6. PAGE HEADER - eyebrow + title
   ARCHITECTURE.md section 10, item 2: a muted uppercase tracking-widest
   section label above a bold page title, actions right-aligned in the same
   row. Used 228 times in the reference app because it orients you instantly
   without a heavy toolbar.
   ========================================================================== */

/* MEASURED 2026-08-12: the gap between this block and whatever followed it
   was ZERO — the actions row and the first card touched, which is most of what
   "cramped" was. The reference leaves 24px between every top-level block. */
.app-page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--app-s-4);
  margin-bottom: var(--app-s-6);
}

.app-eyebrow {
  font-family: var(--app-font-display);
  font-size: var(--app-fs-2xs);
  font-weight: var(--app-w-medium);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--app-ink-muted);
  line-height: var(--app-lh-tight);
}

.app-title {
  margin-top: var(--app-s-1);
  font-family: var(--app-font-display);
  font-size: var(--app-fs-xl);
  font-weight: var(--app-w-semibold);
  letter-spacing: var(--app-track-tight);
  line-height: var(--app-lh-tight);
  color: var(--app-ink);
}

.app-subtitle {
  margin-top: var(--app-s-2);
  max-width: 46rem;
  font-size: var(--app-fs-base);
  color: var(--app-ink-muted);
}

.app-page-actions {
  margin-left: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--app-s-2);
}

/* ==========================================================================
   7. BUTTONS
   .app-btn on its own is the SECONDARY button and is complete and valid -
   plans/00-conventions.md section 3.2 uses it bare. Add .app-btn-primary
   when the action is the page's primary action.
   ========================================================================== */

.app-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--app-s-2);
  /* 2.5rem = 40px, and 44px under a coarse pointer (below). It was 2.25rem,
     which is 36px: under the 44px target every touch guideline asks for, on a
     product whose busiest screen is a till operated with a finger. */
  min-height: 2.25rem;
  padding: 0 var(--app-s-3);
  border: 1px solid var(--app-line);
  border-radius: var(--app-r-8);
  background: var(--app-surface);
  color: var(--app-ink);
  font-family: var(--app-font-sans);
  font-size: var(--app-fs-base);
  font-weight: var(--app-w-medium);
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--app-fast) var(--app-ease),
              border-color var(--app-fast) var(--app-ease),
              color var(--app-fast) var(--app-ease);
}
.app-btn:hover {
  background: var(--app-surface-3);
  color: var(--app-ink);
  text-decoration: none;
}
.app-btn:focus-visible { outline: none; box-shadow: var(--app-focus); }
.app-btn:disabled,
.app-btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

.app-btn-primary {
  border-color: var(--app-action);
  background: var(--app-action);
  color: var(--app-action-ink);
}
.app-btn-primary:hover {
  border-color: var(--app-action-hover);
  background: var(--app-action-hover);
  color: var(--app-action-ink);
}

.app-btn-danger {
  border-color: var(--app-bad-edge);
  background: var(--app-bad-edge);
  color: var(--app-ink-invert);
}
.app-btn-danger:hover {
  border-color: var(--app-bad-ink);
  background: var(--app-bad-ink);
  color: var(--app-ink-invert);
}

.app-btn-ghost {
  border-color: transparent;
  background: transparent;
  color: var(--app-ink-muted);
}
.app-btn-ghost:hover {
  border-color: transparent;
  background: var(--app-surface-3);
  color: var(--app-ink);
}

/* The small variant. The reference app had none and overrode button sizing
   with !important everywhere instead. There is no !important in this file. */
.app-btn-sm {
  min-height: 1.75rem;
  padding: 0 var(--app-s-3);
  border-radius: var(--app-r-8);
  font-size: var(--app-fs-xs);
}

/* Square icon-only button. Always needs an aria-label. */
.app-btn-icon {
  width: 2.25rem;
  min-height: 2.25rem;
  padding: 0;
}
.app-btn-icon.app-btn-sm { width: 1.75rem; min-height: 1.75rem; }

.app-btn-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--app-s-2);
}

/* ==========================================================================
   8. FORMS
   ========================================================================== */

.app-form {
  display: flex;
  flex-direction: column;
  gap: var(--app-s-5);
}

.app-field {
  display: flex;
  flex-direction: column;
  gap: var(--app-s-2);
}

.app-field-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--app-s-5);
}

.app-label {
  font-size: var(--app-fs-base);
  font-weight: var(--app-w-medium);
  color: var(--app-ink);
}
.app-required { color: var(--app-bad-ink); font-weight: var(--app-w-regular); }
.app-optional { color: var(--app-ink-faint); font-weight: var(--app-w-regular); }

.app-input,
.app-select,
.app-textarea {
  width: 100%;
  min-height: 2.25rem;
  padding: var(--app-s-2) var(--app-s-3);
  border: 1px solid var(--app-field-line);
  border-radius: var(--app-r-8);
  background: var(--app-surface);
  color: var(--app-ink);
  font-family: var(--app-font-sans);
  font-size: var(--app-fs-base);
  line-height: var(--app-lh-snug);
  transition: border-color var(--app-fast) var(--app-ease),
              box-shadow var(--app-fast) var(--app-ease);
}
.app-input::placeholder,
.app-textarea::placeholder { color: var(--app-ink-faint); }

.app-input:focus,
.app-select:focus,
.app-textarea:focus {
  outline: none;
  border-color: var(--app-ring);
  box-shadow: var(--app-focus);
}

.app-input:disabled,
.app-select:disabled,
.app-textarea:disabled {
  background: var(--app-surface-2);
  color: var(--app-ink-faint);
  cursor: not-allowed;
}

.app-textarea { min-height: 6rem; resize: vertical; }

.app-select {
  /* The chevron is a data: URI, which the CSP permits (img-src 'self' data:).
     It is monochrome and inherits nothing, so it is drawn in the neutral
     ramp's mid grey, which reads correctly on both surfaces. */
  appearance: none;
  padding-right: var(--app-s-8);
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2364748b' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--app-s-3) center;
  background-size: 1rem 1rem;
}

/* The invalid state. Set it from the server: add app-input-invalid whenever
   the field has an error, and point aria-describedby at the error element. */
.app-input-invalid,
.app-input-invalid:focus {
  border-color: var(--app-bad-edge);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--app-bad-edge) 26%, transparent);
}

.app-error {
  display: flex;
  align-items: center;
  gap: var(--app-s-1);
  font-size: var(--app-fs-xs);
  font-weight: var(--app-w-medium);
  color: var(--app-bad-ink);
}

.app-hint {
  font-size: var(--app-fs-xs);
  color: var(--app-ink-muted);
}

/* Prefix add-on, for a currency code sitting inside a money input. */
.app-input-group {
  display: flex;
  align-items: stretch;
}
.app-input-group .app-input {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  border-left: none;
}
.app-input-prefix {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--app-s-3);
  border: 1px solid var(--app-field-line);
  border-radius: var(--app-r-12) 0 0 var(--app-r-12);
  background: var(--app-surface-2);
  color: var(--app-ink-muted);
  font-size: var(--app-fs-sm);
  font-weight: var(--app-w-medium);
  white-space: nowrap;
}

/* Checkbox and radio. The native control is kept and only recoloured, so
   keyboard behaviour and screen-reader semantics are untouched. */
.app-check {
  display: flex;
  align-items: flex-start;
  gap: var(--app-s-3);
  padding: var(--app-s-2) 0;
  cursor: pointer;
}
.app-check input[type="checkbox"],
.app-check input[type="radio"] {
  flex: none;
  width: 1.125rem;
  height: 1.125rem;
  margin: 0.125rem 0 0;
  accent-color: var(--app-brand);
  cursor: pointer;
}
.app-check-body { display: flex; flex-direction: column; gap: var(--app-s-1); }
.app-check-label {
  font-size: var(--app-fs-base);
  font-weight: var(--app-w-medium);
  color: var(--app-ink);
}
.app-check-hint { font-size: var(--app-fs-xs); color: var(--app-ink-muted); }

/* ==========================================================================
   9. CARDS, STATS
   ARCHITECTURE.md section 10: near-flat. A hairline ring does the work, not
   a drop shadow. 16px is the card radius.
   ========================================================================== */

/* CHANGED 2026-08-12. An inset ring PLUS a drop shadow is two devices doing
   one job, and the ring drawn inside the radius left a visible seam where the
   card met a table. One real border, no shadow — the reference's card is a
   white plane with a hairline and nothing else. */
.app-card {
  border-radius: var(--app-r-16);
  background: var(--app-surface);
  border: 1px solid var(--app-line);
  margin-block: var(--app-s-4);
}

/* 24px, not 16/20. Measured against the reference: its card is py-6 px-6 and
   ours was py-4 px-5, which is why the same content read as crowded inside the
   same width. */
.app-card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--app-s-4);
  padding: var(--app-s-5) var(--app-s-6);
  border-bottom: 1px solid var(--app-line);
}
.app-card-title {
  font-family: var(--app-font-display);
  font-size: var(--app-fs-md);
  font-weight: var(--app-w-semibold);
  color: var(--app-ink);
  line-height: var(--app-lh-tight);
}
.app-card-meta { margin-top: var(--app-s-1); font-size: var(--app-fs-sm); color: var(--app-ink-muted); }
/* flex-wrap, like .app-page-actions above. Without it a card head with two
   buttons overflowed its own card on a narrow screen instead of wrapping —
   which is most of what "the buttons appear out of place" was. */
.app-card-actions { margin-left: auto; display: flex; flex-wrap: wrap; gap: var(--app-s-2); }

.app-card-body { padding: var(--app-s-6); }

.app-card-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--app-s-3);
  padding: var(--app-s-5) var(--app-s-6);
  border-top: 1px solid var(--app-line);
  background: var(--app-surface-2);
  border-radius: 0 0 var(--app-r-16) var(--app-r-16);
}

.app-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  gap: var(--app-s-5);
}

/* ---- stat cards ------------------------------------------------------- */
.app-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: var(--app-s-4);
  margin-block: var(--app-s-4);
}

.app-stat {
  display: flex;
  flex-direction: column;
  gap: var(--app-s-2);
  padding: var(--app-s-6);
  border-radius: var(--app-r-16);
  background: var(--app-surface);
  border: 1px solid var(--app-line);
}
/* CHANGED 2026-08-12: no longer 11px UPPERCASE LETTERSPACED. Four of those
   across the top of a dashboard is four labels shouting over the four numbers
   they describe. The reference prints "Total Revenue" in the same face and
   size as body text, muted, and lets the figure below it be the loud one. */
.app-stat-label {
  font-family: var(--app-font-sans);
  font-size: var(--app-fs-base);
  font-weight: var(--app-w-medium);
  color: var(--app-ink-muted);
}
/* The value is SANS, not display, and always tabular. Numbers are the one
   place a display face must not be trusted; see section 5 of the plan. */
.app-stat-value {
  font-family: var(--app-font-sans);
  font-size: var(--app-fs-2xl);
  font-weight: var(--app-w-semibold);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--app-track-tight);
  line-height: var(--app-lh-tight);
  color: var(--app-ink);
}
.app-stat-meta { font-size: var(--app-fs-sm); color: var(--app-ink-muted); }

/* ==========================================================================
   10. DATA TABLE + STATUS AS STRUCTURE
   ARCHITECTURE.md section 10, item 3: a 4px status-coloured left border on
   the row plus a matching soft-tinted pill, so a list is scannable without
   being read.

   The mapping from an invoice status onto a colour happens EXACTLY ONCE, in
   the .app-status-* rules below. Each one sets three private properties that
   the row border and the pill both consume. plans/00-conventions.md section
   3.2 puts the same `app-status-<status>` class on the <tr> and on the pill,
   and that is the intended usage.
   ========================================================================== */

.app-table-wrap {
  border-radius: var(--app-r-16);
  background: var(--app-surface);
  border: 1px solid var(--app-line);
  overflow-x: auto;
  margin-block: var(--app-s-4);
}

.app-table {
  width: 100%;
  min-width: 40rem;
  border-collapse: collapse;
  /* Body size, not small print. A table IS the page on most screens here, and
     the reference sets its cells at the same 14px as everything else. */
  font-size: var(--app-fs-base);
}

/* CHANGED 2026-08-12: the header row was 11px uppercase letterspaced on a
   grey band — a second, louder heading directly under the card's own. It is
   now the same size as the data, medium weight, muted, on the same white. */
.app-table-head th {
  padding: var(--app-s-4);
  border-bottom: 1px solid var(--app-line);
  background: var(--app-surface);
  text-align: left;
  font-family: var(--app-font-sans);
  font-size: var(--app-fs-base);
  font-weight: var(--app-w-medium);
  color: var(--app-ink-muted);
  white-space: nowrap;
}
.app-table-head th:first-child { border-top-left-radius: var(--app-r-16); }
.app-table-head th:last-child  { border-top-right-radius: var(--app-r-16); }

.app-table tbody tr {
  border-bottom: 1px solid var(--app-line);
  /* The 4px status border. --app-st-edge is set by an .app-status-* class;
     when no status class is present it falls back to transparent, so a plain
     table looks right without one. */
  /* NARROWED 2026-08-12 from 4px. ARCHITECTURE.md section 10 item 3 keeps
     the status border — it is what makes a list scannable without reading —
     but 4px of saturated colour per row fought the pill next to it. */
  border-left: 3px solid var(--app-st-edge, transparent);
  transition: background var(--app-fast) var(--app-ease);
}
.app-table tbody tr:last-child { border-bottom: none; }
/* A draft and a cancelled invoice get NO stripe. The stripe exists to make a
   state findable without reading, and "nothing has happened yet" is not a
   state worth finding — a dark bar down the left of every draft was the
   loudest thing in the list and it meant the least. The pill still says it. */
.app-table tbody tr.app-status-draft,
.app-table tbody tr.app-status-cancelled { border-left-color: transparent; }
.app-table tbody tr:hover { background: var(--app-surface-2); }

.app-table td {
  padding: var(--app-s-3) var(--app-s-4);
  /* The reference's rows are 63px because its cells carry two lines; ours
     carry one, so 52px is the same rhythm without the padding being dead
     space. 48px was a spreadsheet. */
  height: 3.25rem;
  vertical-align: middle;
  color: var(--app-ink);
}
.app-table td a { font-weight: var(--app-w-medium); }

/* A table INSIDE a card lines its first column up with the card's own title
   rather than starting 8px to the left of it. Two vertical rules that nearly
   agree are worse than either — measured 313px for the title, 307px for the
   first header cell. */
.app-card .app-table th:first-child,
.app-card .app-table td:first-child { padding-left: var(--app-s-6); }
.app-card .app-table th:last-child,
.app-card .app-table td:last-child { padding-right: var(--app-s-6); }

/* Right-align a column of numbers. Pair it with .app-num on the cell. */
.app-table .app-num,
.app-table th.app-num { text-align: right; }

.app-table-meta {
  display: block;
  font-size: var(--app-fs-sm);
  color: var(--app-ink-muted);
}

/* ---- the five status colour sets -------------------------------------- */
.app-status-draft,
.app-status-cancelled {
  --app-st-ink:  var(--app-neutral-ink);
  --app-st-fill: var(--app-neutral-fill);
  --app-st-edge: var(--app-neutral-edge);
}
.app-status-issued {
  --app-st-ink:  var(--app-info-ink);
  --app-st-fill: var(--app-info-fill);
  --app-st-edge: var(--app-info-edge);
}
.app-status-part_paid {
  --app-st-ink:  var(--app-warn-ink);
  --app-st-fill: var(--app-warn-fill);
  --app-st-edge: var(--app-warn-edge);
}
.app-status-paid {
  --app-st-ink:  var(--app-ok-ink);
  --app-st-fill: var(--app-ok-fill);
  --app-st-edge: var(--app-ok-edge);
}
.app-status-overdue {
  --app-st-ink:  var(--app-bad-ink);
  --app-st-fill: var(--app-bad-fill);
  --app-st-edge: var(--app-bad-edge);
}

/* ---- the pill --------------------------------------------------------- */
.app-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--app-s-1);
  padding: 0.125rem var(--app-s-2);
  border-radius: var(--app-r-pill);
  background: var(--app-st-fill, var(--app-neutral-fill));
  color: var(--app-st-ink, var(--app-neutral-ink));
  font-size: var(--app-fs-xs);
  font-weight: var(--app-w-medium);
  line-height: 1.4;
  white-space: nowrap;
}

/* A cancelled invoice is inert, not merely neutral. Same neutral ink, but an
   outline instead of a fill, so it is distinguishable from `draft` at a
   glance without adding a second grey to the ramp. */
.app-pill.app-status-cancelled {
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--app-neutral-edge);
}

/* ==========================================================================
   11. FILTER CHIPS
   ========================================================================== */

.app-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--app-s-2);
}

.app-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--app-s-2);
  min-height: 1.875rem;
  padding: 0 var(--app-s-3);
  border: 1px solid var(--app-field-line);
  border-radius: var(--app-r-pill);
  background: var(--app-surface);
  color: var(--app-ink-muted);
  font-size: var(--app-fs-xs);
  font-weight: var(--app-w-medium);
  text-decoration: none;
  cursor: pointer;
  transition: background var(--app-fast) var(--app-ease),
              color var(--app-fast) var(--app-ease),
              border-color var(--app-fast) var(--app-ease);
}
.app-chip:hover {
  background: var(--app-surface-3);
  color: var(--app-ink);
  text-decoration: none;
}
.app-chip:focus-visible { outline: none; box-shadow: var(--app-focus); }

.app-chip[aria-pressed="true"],
.app-chip-active {
  border-color: var(--app-action);
  background: var(--app-action);
  color: var(--app-action-ink);
  font-weight: var(--app-w-semibold);
}

.app-chip-count {
  font-variant-numeric: tabular-nums;
  color: var(--app-ink-faint);
}
.app-chip[aria-pressed="true"] .app-chip-count,
.app-chip-active .app-chip-count { color: inherit; }

/* WHICH PILE AM I IN. Added 2026-08-23 with the invoice list's source split.
   Deliberately NOT another row of chips: chips here are a filter WITHIN a set,
   and this chooses the set. Two rows of identical pills would read as one
   seven-way choice where it is really a three-way choice and then a seven-way
   one, which is the ambiguity the underline removes. */
.app-tabs {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: var(--app-s-4);
  border-bottom: 1px solid var(--app-line);
  margin-bottom: var(--app-s-3);
}

.app-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--app-s-2);
  padding: var(--app-s-2) 0 calc(var(--app-s-2) + 1px);
  margin-bottom: -1px;
  border-bottom: 2px solid transparent;
  color: var(--app-ink-muted);
  font-size: var(--app-fs-sm);
  font-weight: var(--app-w-medium);
  text-decoration: none;
  white-space: nowrap;
}
.app-tab:hover { color: var(--app-ink); text-decoration: none; }
.app-tab:focus-visible { outline: none; box-shadow: var(--app-focus); }

.app-tab-on {
  color: var(--app-ink);
  border-bottom-color: var(--app-action);
  font-weight: var(--app-w-semibold);
}

.app-tab-count {
  font-variant-numeric: tabular-nums;
  font-size: var(--app-fs-xs);
  color: var(--app-ink-faint);
}
.app-tab-on .app-tab-count { color: var(--app-ink-muted); }

/* FIND ONE. The search sits between the tabs and the status chips because that
   is the order the narrowing happens in. */
.app-findbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--app-s-2);
  margin: var(--app-s-3) 0;
}

.app-findbar-input {
  flex: 1 1 22rem;
  min-width: 0;
}

/* A form that is still there but is no longer the headline. Added 2026-08-23
   with the customer account panel. Native <details>, so it works with no
   JavaScript and the browser keeps the keyboard behaviour. */
.app-details {
  border: 1px solid var(--app-line);
  border-radius: var(--app-r-16);
  background: var(--app-surface);
  margin-top: var(--app-s-5);
}

.app-details-summary {
  padding: var(--app-s-3) var(--app-s-4);
  cursor: pointer;
  font-size: var(--app-fs-sm);
  font-weight: var(--app-w-semibold);
  color: var(--app-ink);
  list-style-position: inside;
}
.app-details-summary:focus-visible { outline: none; box-shadow: var(--app-focus); border-radius: var(--app-r-16); }
.app-details[open] .app-details-summary { border-bottom: 1px solid var(--app-line); }
.app-details > .app-form { padding: var(--app-s-4); }

/* A long URL and the button that copies it, on one line that wraps sanely on a
   phone. Added 2026-08-23 with the pay-link share. */
.app-copyrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--app-s-2);
}
.app-copyrow-link {
  overflow-wrap: anywhere;
  min-width: 0;
}

/* A hint that ends a block rather than labelling the next field. Without it the
   WhatsApp note butted straight against the "Email it to" label and read as
   part of it. */
.app-hint-block { margin-bottom: var(--app-s-5); }

/* A counter charge nobody finished. Added 2026-08-26 with the till's recovery
   panel: the amount and the way out on one line, wrapping on a small till. */
.app-pending-charge {
  display: flex;
  flex-wrap: wrap;
  gap: var(--app-s-3);
  align-items: center;
  justify-content: space-between;
  padding: var(--app-s-3) 0;
  border-bottom: 1px solid var(--app-line);
}
.app-pending-charge:last-child { border-bottom: 0; padding-bottom: 0; }

/* THE CART'S OWN FIELDS. Added 2026-08-26: quantity and price are typed at a
   till, not only stepped. Narrow, right-aligned and tabular, because they sit
   in a column of figures a cashier reads down. */
.app-pos-qty {
  width: 4rem;
  text-align: center;
  padding: .25rem .35rem;
  margin: 0 .35rem;
  font-variant-numeric: tabular-nums;
  display: inline-block;
}
.app-pos-price {
  width: 7rem;
  text-align: right;
  padding: .25rem .5rem;
  font-variant-numeric: tabular-nums;
}
/* A price that is not the shelf price says so without a second line of text —
   the cashier who typed it knows, and the one who takes over the till does not. */
.app-pos-price-changed {
  border-color: var(--app-warn-edge);
  font-weight: var(--app-w-semibold);
}

/* The two bases of a tax return, side by side on a desk and stacked on a
   phone. Added 2026-08-26 with /tax-report. */
.app-taxpair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  gap: var(--app-s-4);
  margin-bottom: var(--app-s-3);
}
/* A short label-and-figure summary. Deliberately not a table: .app-table sets
   min-width:40rem for horizontal scrolling, which is right for a wide document
   list and wrong inside a narrow grid column. */
.app-taxlines { margin: 0; }
.app-taxlines > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--app-s-4);
  padding: var(--app-s-2) 0;
  border-bottom: 1px solid var(--app-line);
}
.app-taxlines > div:last-child { border-bottom: 0; padding-bottom: 0; }
.app-taxlines dt { color: var(--app-ink-muted); margin: 0; }
.app-taxlines dd { margin: 0; font-variant-numeric: tabular-nums; white-space: nowrap; }
.app-taxlines-strong dt, .app-taxlines-strong dd {
  color: var(--app-ink);
  font-weight: var(--app-w-semibold);
}

/* A QR a customer is about to scan. Bordered and on white whatever the theme,
   because a scanner reads contrast, and dark mode inverting a QR is how a code
   stops working at a counter. */
.app-qr {
  display: block;
  width: 13.75rem;
  height: 13.75rem;
  padding: var(--app-s-3);
  border: 1px solid var(--app-line);
  border-radius: var(--app-r-12);
  background: #ffffff;
}

/* The logo a merchant has uploaded, shown at the size it will print at so the
   preview is the answer to "will this look right", not a thumbnail. */
.app-logo-preview {
  max-height: 3.5rem;
  max-width: 12.5rem;
  display: block;
  padding: var(--app-s-2);
  border: 1px solid var(--app-line);
  border-radius: var(--app-r-8);
  background: var(--app-surface);
}

/* ==========================================================================
   12. EMPTY STATE
   DESIGN.md 1 and 3: an empty state carries the outcome CTA and, where there
   is one, the value the user already has. Never a bare "No records found".
   ========================================================================== */

.app-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--app-s-3);
  padding: var(--app-s-12) var(--app-s-6);
  text-align: center;
}
.app-empty-icon {
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  border-radius: var(--app-r-pill);
  background: var(--app-brand-soft);
  color: var(--app-brand-soft-ink);
}
.app-empty-title {
  font-family: var(--app-font-display);
  font-size: var(--app-fs-md);
  font-weight: var(--app-w-display);
  color: var(--app-ink);
}
.app-empty-body {
  max-width: 32rem;
  font-size: var(--app-fs-base);
  color: var(--app-ink-muted);
}
.app-empty-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--app-s-2);
  margin-top: var(--app-s-2);
}

/* ==========================================================================
   13. MODAL
   Built on the native <dialog> element, so Escape, focus trapping and the
   top layer are the browser's job rather than ours. Opened by a form POST
   that re-renders the page with the dialog `open`, or by a nonce'd script.
   ========================================================================== */

.app-modal {
  width: min(34rem, calc(100vw - var(--app-s-8)));
  padding: 0;
  border: none;
  border-radius: var(--app-r-16);
  background: var(--app-surface);
  color: var(--app-ink);
  box-shadow: var(--app-shadow-3), inset 0 0 0 1px var(--app-line-strong);
}
.app-modal::backdrop {
  background: var(--app-scrim);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}

.app-modal-head {
  display: flex;
  align-items: flex-start;
  gap: var(--app-s-3);
  padding: var(--app-s-5) var(--app-s-5) var(--app-s-3);
}
.app-modal-title {
  font-family: var(--app-font-display);
  font-size: var(--app-fs-lg);
  font-weight: var(--app-w-display-strong);
  letter-spacing: var(--app-track-tight);
  line-height: var(--app-lh-tight);
  color: var(--app-ink);
}
.app-modal-body {
  padding: 0 var(--app-s-5) var(--app-s-5);
  font-size: var(--app-fs-base);
  color: var(--app-ink-muted);
  display: flex;
  flex-direction: column;
  gap: var(--app-s-4);
}
.app-modal-foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--app-s-2);
  padding: var(--app-s-5) var(--app-s-6);
  border-top: 1px solid var(--app-line);
  background: var(--app-surface-2);
  border-radius: 0 0 var(--app-r-16) var(--app-r-16);
}

/* ==========================================================================
   14. FLASH MESSAGES
   Rendered by layout.html from the engine's $flash list. $flash is
   TEMPLATE-ONLY - write with flash("...") in a server block, read only in
   the template (SKILL.md section 6).
   ========================================================================== */

.app-flashes {
  position: fixed;
  right: var(--app-inset);
  bottom: var(--app-inset);
  z-index: var(--app-z-flash);
  display: flex;
  flex-direction: column;
  gap: var(--app-s-2);
  width: min(24rem, calc(100vw - var(--app-inset) * 2));
}

.app-flash {
  display: flex;
  align-items: flex-start;
  gap: var(--app-s-3);
  padding: var(--app-s-3) var(--app-s-4);
  border-radius: var(--app-r-12);
  background: var(--app-surface);
  color: var(--app-ink);
  font-size: var(--app-fs-base);
  box-shadow: var(--app-shadow-2), inset 0 0 0 1px var(--app-line-strong);
  border-left: 4px solid var(--app-st-edge, var(--app-ok-edge));
}
.app-flash-ok   { --app-st-edge: var(--app-ok-edge); }
.app-flash-bad  { --app-st-edge: var(--app-bad-edge); }
.app-flash-info { --app-st-edge: var(--app-info-edge); }

/* ==========================================================================
   15. PAGINATION
   ========================================================================== */

.app-pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--app-s-2);
}
.app-pager-info {
  margin-right: auto;
  font-size: var(--app-fs-xs);
  font-variant-numeric: tabular-nums;
  color: var(--app-ink-muted);
}
.app-pager-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2rem;
  min-height: 2rem;
  padding: 0 var(--app-s-2);
  border: 1px solid var(--app-field-line);
  border-radius: var(--app-r-8);
  background: var(--app-surface);
  color: var(--app-ink);
  font-size: var(--app-fs-xs);
  font-weight: var(--app-w-medium);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
}
.app-pager-link:hover {
  background: var(--app-surface-3);
  color: var(--app-ink);
  text-decoration: none;
}
.app-pager-link[aria-current="page"] {
  border-color: var(--app-brand);
  background: var(--app-brand);
  color: var(--app-brand-ink);
  font-weight: var(--app-w-semibold);
}
.app-pager-link[aria-disabled="true"] {
  opacity: 0.45;
  pointer-events: none;
}

/* ==========================================================================
   16. MONEY AND NUMBERS
   plans/02-design-system.md section 5 is the rule. This is the rendering.
   Every number in this application is tabular. No exceptions.
   ========================================================================== */

.app-num {
  font-family: var(--app-font-sans);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  white-space: nowrap;
}

.app-money {
  font-family: var(--app-font-sans);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  font-weight: var(--app-w-semibold);
  white-space: nowrap;
  color: var(--app-ink);
}
/* The currency code / symbol, always lighter than the figure it qualifies. */
.app-money-cur {
  margin-right: 0.35em;
  font-weight: var(--app-w-medium);
  color: var(--app-ink-muted);
}
/* A debit, a fee, a reversal. Never used for "less than expected". */
.app-money-neg { color: var(--app-bad-ink); }
.app-money-pos { color: var(--app-ok-ink); }

/* The one big number on an invoice or a statement. */
.app-amount {
  font-family: var(--app-font-sans);
  font-size: var(--app-fs-3xl);
  font-weight: var(--app-w-bold);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  letter-spacing: var(--app-track-tight);
  line-height: var(--app-lh-tight);
  color: var(--app-ink);
}
.app-amount .app-money-cur { font-size: var(--app-fs-lg); }

/* An identifier a human will read aloud or type: invoice number, payment
   reference, ledger entry id. Mono, so 0/O and 1/l cannot be confused. */
.app-ref {
  font-family: var(--app-font-mono);
  font-size: 0.9375em;
  font-weight: var(--app-w-medium);
  letter-spacing: 0.01em;
  color: var(--app-ink);
  /* A REFERENCE IS READ ALOUD DOWN A PHONE, so it does not wrap. Added
     2026-08-23: once the invoice list defaulted to the documents, the customer
     column filled with real names, the number column narrowed to suit, and
     DH-00011 broke across two lines as "DH-" and "00011". A merchant reading
     that to a customer reads two things. */
  white-space: nowrap;
}

/* ==========================================================================
   17. SMALL SHARED PIECES
   ========================================================================== */

.app-meta { font-size: var(--app-fs-xs); color: var(--app-ink-muted); }

.app-divider {
  height: 1px;
  border: none;
  margin: 0;
  background: var(--app-line);
}

.app-stack {
  display: flex;
  flex-direction: column;
  gap: var(--app-s-4);
}

.app-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--app-s-3);
}

/* A definition list used for read-only detail panels. */
.app-dl {
  display: grid;
  grid-template-columns: minmax(8rem, max-content) 1fr;
  gap: var(--app-s-3) var(--app-s-5);
  font-size: var(--app-fs-base);
}
.app-dl dt { color: var(--app-ink-muted); }
.app-dl dd { margin: 0; color: var(--app-ink); }

/* Goal Gradient (DESIGN.md 2). Never rendered at 0 - the server must count
   what the user already did. See plans/02-design-system.md section 7. */
.app-progress {
  display: flex;
  flex-direction: column;
  gap: var(--app-s-2);
}
.app-progress-track {
  height: 0.5rem;
  border-radius: var(--app-r-pill);
  background: var(--app-surface-3);
  overflow: hidden;
}
.app-progress-fill {
  height: 100%;
  border-radius: var(--app-r-pill);
  background: var(--app-brand);
  transition: width var(--app-slow) var(--app-ease);
}
.app-progress-label {
  display: flex;
  gap: var(--app-s-3);
  font-size: var(--app-fs-xs);
  font-variant-numeric: tabular-nums;
  color: var(--app-ink-muted);
}
.app-progress-remaining { margin-left: auto; }

/* ==========================================================================
   18. MOTION AND PRINT
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* The only !important declarations in this file are the two above, which is
   what the reduced-motion query requires, and the print overrides below.
   DO NOT add another one. */

@media print {
  .app-side, .app-topbar, .app-scrim, .app-flashes, .app-skip { display: none !important; }
  body { background: #ffffff; }
  .app-main { padding: 0 !important; }
  .app-card, .app-table-wrap { box-shadow: none; border: 1px solid var(--app-line-strong); }
  .mk-nav-wrap, .mk-footer, .mk-skip { display: none !important; }
}

/* ==========================================================================
   19. THE PUBLIC SITE

   Everything a signed-out visitor sees: the landing page, sign in, sign up.
   ADDED 2026-08-09. Not in plans/02-design-system.md, which describes the
   signed-in application only.

   TWO RULES HOLD THIS SECTION TOGETHER.

   1. Every colour is a var() from section 2.14. Section 2.9d of the design
      system forbids a colour literal below section 3 and the gate that checks
      it is real, so a new shade means a new token up there, not a hex here.

   2. Nothing in here reads an --app- token, and section 3 (dark mode) does not
      mention a single .mk- class. The public site is light-only by design: a
      marketing page that changes colour with the visitor's OS setting is two
      designs to keep honest instead of one.

   The look: white paper, near-black ink, hairline outlined cards with a large
   radius, and headings split into a ghost half and a solid half. One accent,
   used once, on a paid state.
   ========================================================================== */

.mk-page {
  background: var(--mk-paper);
  color: var(--mk-ink);
  font-family: var(--app-font-sans);
  -webkit-font-smoothing: antialiased;
}

.mk-skip {
  position: absolute;
  left: -9999px;
}
.mk-skip:focus {
  left: 1rem;
  top: 1rem;
  z-index: var(--app-z-flash);
  background: var(--mk-ink);
  color: var(--mk-paper);
  padding: 0.6rem 1rem;
  border-radius: var(--mk-radius-sm);
}

.mk-wrap {
  max-width: var(--mk-wrap);
  margin-inline: auto;
  padding-inline: 1.5rem;
}

/* ---- 19.1 The floating navbar -------------------------------------------
   A pill inset from the viewport edge, the same idea as the application's
   rail, so the two halves of the product feel related without sharing code. */

.mk-nav-wrap {
  position: sticky;
  top: 0;
  z-index: var(--app-z-topbar);
  padding: 1rem 1.5rem 0;
  background: var(--mk-paper);
}

.mk-nav {
  max-width: var(--mk-wrap);
  margin-inline: auto;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.7rem 0.7rem 0.7rem 1.1rem;
  border: 1px solid var(--mk-line);
  border-radius: 999px;
  background: var(--mk-paper);
}

.mk-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  text-decoration: none;
  color: var(--mk-ink);
}
.mk-mark { display: block; flex: none; }

.mk-brand-word {
  font-family: var(--app-font-display);
  font-weight: 800;
  font-size: 0.95rem;
  letter-spacing: -0.01em;
}
.mk-brand-thin { font-weight: 400; opacity: 0.55; }

.mk-nav-links {
  display: flex;
  gap: 1.6rem;
  margin-inline: auto;
}
.mk-nav-links a {
  color: var(--mk-ink);
  text-decoration: none;
  font-size: 0.95rem;
  padding-block: 0.25rem;
  border-bottom: 1px solid transparent;
}
.mk-nav-links a:hover { border-bottom-color: var(--mk-ink); }

.mk-nav-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.mk-link-quiet {
  color: var(--mk-ink);
  text-decoration: none;
  font-size: 0.95rem;
  padding: 0.4rem 0.2rem;
}
.mk-link-quiet:hover { text-decoration: underline; }

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

.mk-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.7rem 1.4rem;
  border-radius: 999px;
  border: 1px solid var(--mk-ink);
  font: inherit;
  font-weight: 600;
  font-size: 0.95rem;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--app-fast) var(--app-ease),
              color var(--app-fast) var(--app-ease);
}
.mk-btn-dark { background: var(--mk-ink); color: var(--mk-paper); }
.mk-btn-dark:hover { background: var(--mk-ink-soft); }
.mk-btn-ghost { background: transparent; color: var(--mk-ink); }
.mk-btn-ghost:hover { background: var(--mk-ink); color: var(--mk-paper); }
.mk-btn-lg { padding: 0.9rem 1.8rem; font-size: 1rem; }
.mk-btn-block { display: flex; width: 100%; margin-top: auto; }

.mk-page a:focus-visible,
.mk-page button:focus-visible,
.mk-page input:focus-visible {
  outline: 2px solid var(--mk-ink);
  outline-offset: 2px;
}

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

.mk-eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.75rem;
  color: var(--mk-mute);
  margin-bottom: 1.5rem;
}

/* The hero wordmark. clamp() rather than a media query because the whole
   point of it is to run edge to edge at every width. */
.mk-display {
  font-family: var(--app-font-display);
  font-weight: 800;
  text-transform: uppercase;
  font-size: clamp(2.75rem, 11vw, 8.5rem);
  line-height: 0.92;
  letter-spacing: -0.03em;
  margin: 0;
}
.mk-display-2 { font-size: clamp(2.25rem, 8vw, 6rem); }

.mk-lede {
  max-width: 34rem;
  margin: 1.5rem auto 0;
  font-size: clamp(1rem, 1.6vw, 1.25rem);
  line-height: 1.55;
  color: var(--mk-mute);
}

/* A split heading: the first half recedes, the second lands. */
.mk-split {
  font-family: var(--app-font-display);
  font-weight: 800;
  text-transform: uppercase;
  font-size: clamp(2rem, 7vw, 5rem);
  line-height: 0.95;
  letter-spacing: -0.03em;
  margin: 0 0 2rem;
}
.mk-split .mk-ghost { color: var(--mk-ghost); }
.mk-split-dark .mk-ghost { color: var(--mk-line-dark); }

.mk-section-lede {
  max-width: 40rem;
  color: var(--mk-mute);
  font-size: 1.05rem;
  line-height: 1.6;
  margin: 0 0 2.5rem;
}

.mk-fineprint { color: var(--mk-quiet); font-size: 0.85rem; margin-top: 1rem; }
.mk-center { text-align: center; }

/* ---- 19.4 Sections ------------------------------------------------------ */

.mk-section { padding: clamp(4rem, 9vw, 8rem) 0; }
.mk-tint { background: var(--mk-tint); }
.mk-dark { background: var(--mk-ink); color: var(--mk-paper); }
.mk-dark .mk-section-lede { color: var(--mk-mute-dark); }

.mk-hero {
  padding: clamp(3rem, 7vw, 6rem) 0 0;
  text-align: center;
}
.mk-cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  justify-content: center;
  margin-top: 2rem;
}
.mk-close {
  padding: clamp(4rem, 9vw, 8rem) 0;
  text-align: center;
}

/* ---- 19.5 The hero stage ------------------------------------------------
   A drawn product shot with two outlined notes floating beside it. On a
   narrow screen the notes stack under it rather than overlapping. */

.mk-stage {
  position: relative;
  max-width: var(--mk-wrap);
  margin: clamp(2.5rem, 6vw, 4.5rem) auto 0;
  padding: 0 1.5rem clamp(3rem, 7vw, 6rem);
  display: grid;
  gap: 1.25rem;
  justify-items: center;
}

.mk-float {
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius);
  background: var(--mk-paper);
  padding: 1.25rem 1.4rem;
  max-width: 19rem;
  text-align: center;
}
.mk-float-title { font-weight: 700; margin: 0 0 0.4rem; }
.mk-float-body { margin: 0; color: var(--mk-mute); font-size: 0.9rem; line-height: 1.5; }

.mk-device {
  width: min(30rem, 100%);
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius);
  background: var(--mk-paper);
  overflow: hidden;
  text-align: left;
}
.mk-device-bar {
  display: flex;
  gap: 0.4rem;
  padding: 0.8rem 1rem;
  border-bottom: 1px solid var(--mk-line);
}
.mk-device-bar span {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--mk-line);
}
.mk-device-body { padding: 1.4rem 1.5rem 1.6rem; }

.mk-inv-head { display: flex; align-items: flex-start; justify-content: space-between; }
.mk-inv-label { margin: 0; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.12em; color: var(--mk-mute); }
.mk-inv-number { margin: 0.2rem 0 0; font-weight: 700; font-family: var(--app-font-mono); }

.mk-chip {
  font-size: 0.75rem;
  font-weight: 700;
  padding: 0.25rem 0.7rem;
  border-radius: 999px;
  border: 1px solid var(--mk-line);
}
.mk-chip-paid { color: var(--mk-accent); border-color: var(--mk-accent); }

.mk-inv-total {
  display: flex;
  align-items: baseline;
  gap: 0.2rem;
  margin: 1.2rem 0 1.4rem;
  font-family: var(--app-font-display);
  font-weight: 800;
  letter-spacing: -0.02em;
}
.mk-inv-cur { font-size: 1rem; color: var(--mk-mute); margin-right: 0.25rem; }
.mk-inv-amount { font-size: clamp(2.2rem, 6vw, 3rem); line-height: 1; }
.mk-inv-dec { font-size: 1.2rem; color: var(--mk-mute); }

.mk-inv-rows { border-top: 1px solid var(--mk-line); }
.mk-inv-row {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--mk-line);
  font-size: 0.9rem;
}
.mk-inv-row-quiet { color: var(--mk-mute); }
.mk-num { font-family: var(--app-font-mono); font-variant-numeric: tabular-nums; }

.mk-inv-foot { margin-top: 1.1rem; }
.mk-inv-foot p { margin: 0.6rem 0 0; font-size: 0.8rem; color: var(--mk-mute); }
.mk-bar { height: 0.35rem; border-radius: 999px; background: var(--mk-tint); overflow: hidden; }
.mk-bar i { display: block; height: 100%; background: var(--mk-ink); }
.mk-bar-full i { width: 100%; }

@media (min-width: 60rem) {
  .mk-stage {
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 2rem;
  }
  .mk-float-l { justify-self: end; }
  .mk-float-r { justify-self: start; }
  .mk-float { text-align: left; }
}

/* ---- 19.6 Two-column copy and the figures card -------------------------- */

.mk-two {
  display: grid;
  gap: 2rem;
  margin-bottom: 3rem;
}
.mk-copy p { margin: 0 0 1rem; font-size: 1.05rem; line-height: 1.65; color: var(--mk-mute); }
.mk-copy p:first-child { color: var(--mk-ink); }

.mk-figures {
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius);
  padding: 1.6rem;
  background: var(--mk-paper);
}
.mk-figure-row { display: flex; align-items: center; gap: 0.75rem; }
.mk-figure {
  font-family: var(--app-font-display);
  font-weight: 800;
  font-size: clamp(2rem, 5vw, 2.75rem);
  letter-spacing: -0.02em;
}
.mk-pill {
  font-size: 0.75rem;
  font-weight: 700;
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
}
.mk-pill-dark { background: var(--mk-ink); color: var(--mk-paper); }
.mk-figure-sub { margin: 0.75rem 0 0; color: var(--mk-mute); font-size: 0.9rem; line-height: 1.5; }
.mk-figure-rule { height: 1px; background: var(--mk-line); margin: 1.25rem 0; }
.mk-figure-line { display: flex; justify-content: space-between; gap: 1rem; margin: 0 0 0.5rem; font-size: 0.95rem; }
.mk-figure-line span { color: var(--mk-mute); }

@media (min-width: 60rem) {
  .mk-two { grid-template-columns: 1.2fr 1fr; gap: 4rem; align-items: start; }
}

/* ---- 19.7 Card grids ---------------------------------------------------- */

.mk-grid-4, .mk-grid-3 { display: grid; gap: 1rem; }
@media (min-width: 40rem) {
  .mk-grid-4, .mk-grid-3 { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 64rem) {
  .mk-grid-4 { grid-template-columns: repeat(4, 1fr); }
  .mk-grid-3 { grid-template-columns: repeat(3, 1fr); }
}

.mk-card {
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius);
  padding: 1.5rem;
  background: var(--mk-paper);
}
.mk-card h3 { margin: 0 0 0.5rem; font-size: 1.05rem; }
.mk-card p { margin: 0; color: var(--mk-mute); font-size: 0.9rem; line-height: 1.55; }
.mk-card-kicker {
  margin: 0 0 1.5rem;
  font-family: var(--app-font-mono);
  font-size: 0.8rem;
  color: var(--mk-mute);
}
.mk-card-dark {
  background: var(--mk-ink-soft);
  border-color: var(--mk-line-dark);
}
.mk-card-dark h3 { color: var(--mk-paper); }
.mk-card-dark p { color: var(--mk-mute-dark); }

/* ---- 19.8 Numbered steps ------------------------------------------------ */

.mk-steps { display: grid; gap: 1.5rem; }
@media (min-width: 48rem) { .mk-steps { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 72rem) { .mk-steps { grid-template-columns: repeat(4, 1fr); } }

.mk-step { border-top: 1px solid var(--mk-line-dark); padding-top: 1.25rem; }
.mk-step-no {
  margin: 0 0 1rem;
  font-family: var(--app-font-mono);
  font-size: 0.85rem;
  color: var(--mk-mute-dark);
}
.mk-step h3 { margin: 0 0 0.5rem; font-size: 1.1rem; }
.mk-step p { margin: 0; color: var(--mk-mute-dark); font-size: 0.92rem; line-height: 1.55; }

.mk-note-dark {
  margin-top: 3rem;
  border: 1px solid var(--mk-line-dark);
  border-radius: var(--mk-radius);
  padding: 1.5rem;
}
.mk-note-dark p { margin: 0; color: var(--mk-mute-dark); line-height: 1.6; }
.mk-note-dark strong { color: var(--mk-paper); }

/* ---- 19.9 Pricing ------------------------------------------------------- */

.mk-price-grid { display: grid; gap: 1rem; }
@media (min-width: 56rem) { .mk-price-grid { grid-template-columns: repeat(3, 1fr); } }

.mk-price {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius);
  background: var(--mk-paper);
  padding: 1.75rem;
}
.mk-price-lift { border-color: var(--mk-ink); }
.mk-price-name { margin: 0; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.8rem; color: var(--mk-mute); }
.mk-price-amount {
  margin: 0.75rem 0 0;
  font-family: var(--app-font-display);
  font-weight: 800;
  font-size: clamp(2rem, 4vw, 2.5rem);
  letter-spacing: -0.02em;
}
.mk-price-interval { margin: 0.2rem 0 0; color: var(--mk-mute); font-size: 0.9rem; }
.mk-price-desc { margin: 1rem 0 1.25rem; font-size: 0.92rem; line-height: 1.55; color: var(--mk-mute); }

.mk-price-list { list-style: none; margin: 0 0 1.75rem; padding: 0; display: grid; gap: 0.6rem; }
.mk-price-list li {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  font-size: 0.92rem;
  line-height: 1.45;
}
.mk-tick {
  flex: none;
  width: 1rem;
  height: 1rem;
  margin-top: 0.15rem;
  border-radius: 50%;
  border: 1px solid var(--mk-line);
}
.mk-yes .mk-tick { background: var(--mk-ink); border-color: var(--mk-ink); }
.mk-no { color: var(--mk-quiet); }

/* ---- 19.10 Footer ------------------------------------------------------- */

.mk-footer { background: var(--mk-ink); color: var(--mk-paper); padding: clamp(3rem, 7vw, 5rem) 0 2rem; }
.mk-footer-inner { max-width: var(--mk-wrap); margin-inline: auto; padding-inline: 1.5rem; }

.mk-footer-word {
  font-family: var(--app-font-display);
  font-weight: 800;
  text-transform: uppercase;
  font-size: clamp(2rem, 8vw, 5rem);
  line-height: 0.95;
  letter-spacing: -0.03em;
  margin: 0 0 3rem;
}

.mk-footer-cols { display: grid; gap: 2rem; }
@media (min-width: 48rem) { .mk-footer-cols { grid-template-columns: 1fr 1fr 2fr; gap: 3rem; } }

.mk-footer-head {
  margin: 0 0 1rem;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--mk-mute-dark);
}
.mk-footer-cols a {
  display: block;
  color: var(--mk-paper);
  text-decoration: none;
  padding: 0.3rem 0;
  font-size: 0.95rem;
}
.mk-footer-cols a:hover { text-decoration: underline; }
.mk-footer-note p { margin: 0; color: var(--mk-mute-dark); font-size: 0.9rem; line-height: 1.6; }

.mk-footer-rule { height: 1px; background: var(--mk-line-dark); margin: 3rem 0 1.25rem; }
.mk-footer-base {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 2rem;
  justify-content: space-between;
  color: var(--mk-mute-dark);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* ---- 19.11 The signed-out forms ----------------------------------------
   Sign in and sign up share this. One card, centred, nothing else on the
   screen to look at. */

.mk-auth { padding: clamp(2.5rem, 6vw, 5rem) 0 clamp(4rem, 8vw, 7rem); }
.mk-auth-wrap { max-width: 27rem; margin-inline: auto; padding-inline: 1.5rem; }
.mk-auth-head { text-align: center; margin-bottom: 2rem; }
.mk-auth-title {
  font-family: var(--app-font-display);
  font-weight: 800;
  text-transform: uppercase;
  font-size: clamp(1.75rem, 5vw, 2.75rem);
  letter-spacing: -0.02em;
  line-height: 1;
  margin: 0.75rem 0 0.75rem;
}
.mk-auth-sub { margin: 0; color: var(--mk-mute); font-size: 0.95rem; line-height: 1.55; }

.mk-auth-card {
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius);
  padding: 1.75rem;
  background: var(--mk-paper);
}

.mk-field { margin-bottom: 1.1rem; }
.mk-label { display: block; font-size: 0.85rem; font-weight: 600; margin-bottom: 0.4rem; }
.mk-input {
  width: 100%;
  padding: 0.75rem 0.9rem;
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius-sm);
  background: var(--mk-paper);
  color: var(--mk-ink);
  font: inherit;
}
.mk-input:focus { outline: 2px solid var(--mk-ink); outline-offset: 1px; border-color: var(--mk-ink); }
.mk-input-invalid { border-color: var(--mk-ink); border-width: 2px; }
.mk-hint { margin: 0.4rem 0 0; font-size: 0.8rem; color: var(--mk-quiet); line-height: 1.45; }
.mk-error { margin: 0.4rem 0 0; font-size: 0.85rem; font-weight: 600; color: var(--mk-ink); }
.mk-required { color: var(--mk-mute); }

.mk-auth-actions { margin-top: 1.5rem; display: grid; gap: 0.75rem; }
.mk-auth-alt { text-align: center; margin: 1.25rem 0 0; font-size: 0.9rem; color: var(--mk-mute); }
.mk-auth-alt a { color: var(--mk-ink); }

.mk-steps-tiny {
  display: flex;
  gap: 0.4rem;
  justify-content: center;
  margin-bottom: 0.5rem;
}
.mk-steps-tiny i {
  width: 1.75rem;
  height: 0.25rem;
  border-radius: 999px;
  background: var(--mk-line);
}
.mk-steps-tiny i.mk-on { background: var(--mk-ink); }

@media (max-width: 52rem) {
  .mk-nav-links { display: none; }
  .mk-nav { padding-left: 0.9rem; }
}

/* At 375px the brand wordmark, "Sign in" and "Get started" together were 19px
   wider than the viewport, which put a sideways scrollbar on the whole page
   (measured: scrollWidth 394 against innerWidth 375). The wordmark goes and the
   mark stays — it is still the link home — so both actions survive, which
   matters more: a returning visitor needs Sign in to be in the navigation. */
@media (max-width: 34rem) {
  .mk-brand-word { display: none; }
  .mk-btn { padding: 0.6rem 1.1rem; font-size: 0.9rem; }
  .mk-nav-actions { gap: 0.5rem; }
  .mk-wrap, .mk-auth-wrap, .mk-footer-inner { padding-inline: 1.15rem; }
}

/* ==========================================================================
   20. THE INVOICE COMPOSER

   ADDED 2026-08-09. /invoices/new used to be a four-question form with no
   amount on it, followed by a second screen where the prices lived. It is one
   screen now, and it is shaped like the document it produces: letterhead,
   bill-to, line table, totals bottom-right, notes and terms.

   The rule for this section: an input should look like the value it will
   become. Fields are underlines and transparent fills rather than boxes, so
   the page reads as an invoice being filled in rather than as a form about an
   invoice. They gain a visible boundary on hover and focus, which is where a
   person needs to know what is editable.
   ========================================================================== */

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

.doc-error-top {
  margin-bottom: var(--app-s-4);
  padding: var(--app-s-3) var(--app-s-4);
  border: 1px solid var(--app-bad-edge);
  border-radius: var(--app-r-12);
  background: var(--app-bad-fill);
}

.doc-paper {
  background: var(--app-surface);
  border: 1px solid var(--app-line);
  border-radius: var(--app-r-16);
  padding: clamp(1.25rem, 3vw, 2.5rem);
  box-shadow: var(--app-shadow-1);
}

/* ---- 20.1 Letterhead ---------------------------------------------------- */

.doc-head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--app-s-4);
  justify-content: space-between;
  align-items: flex-start;
  padding-bottom: var(--app-s-5);
  border-bottom: 1px solid var(--app-line);
}
.doc-from-name {
  margin: 0;
  font-family: var(--app-font-display);
  font-weight: var(--app-w-display-strong);
  font-size: var(--app-fs-lg);
  letter-spacing: var(--app-track-tight);
}
.doc-from-line { margin: 0.15rem 0 0; color: var(--app-ink-faint); font-size: var(--app-fs-sm); }

.doc-title-block { text-align: right; }
.doc-kind {
  margin: 0;
  font-family: var(--app-font-display);
  font-weight: var(--app-w-display-strong);
  font-size: var(--app-fs-2xl);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  line-height: 1;
}
.doc-status {
  display: inline-block;
  margin-top: var(--app-s-2);
  padding: 0.15rem 0.6rem;
  border-radius: var(--app-r-pill);
  background: var(--app-surface-3);
  color: var(--app-ink-muted);
  font-size: var(--app-fs-2xs);
  font-weight: var(--app-w-bold);
  text-transform: uppercase;
  letter-spacing: var(--app-track-eyebrow);
}
.doc-number { margin: var(--app-s-2) 0 0; color: var(--app-ink-faint); font-size: var(--app-fs-xs); }

/* ---- 20.2 Meta row ------------------------------------------------------ */

.doc-meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--app-s-4);
  padding: var(--app-s-5) 0;
  border-bottom: 1px solid var(--app-line);
}
.doc-meta-bill { grid-column: span 2; min-width: 0; }
.doc-meta-label {
  display: block;
  margin: 0 0 var(--app-s-1);
  font-size: var(--app-fs-2xs);
  font-weight: var(--app-w-semibold);
  text-transform: uppercase;
  letter-spacing: var(--app-track-eyebrow);
  color: var(--app-ink-faint);
}
.doc-hint { margin: var(--app-s-1) 0 0; font-size: var(--app-fs-xs); color: var(--app-ink-faint); }

/* ---- 20.3 Fields that look like values ---------------------------------- */

.doc-input,
.doc-select {
  width: 100%;
  padding: 0.4rem 0.5rem;
  border: 1px solid transparent;
  border-bottom-color: var(--app-line-strong);
  border-radius: var(--app-r-8);
  background: transparent;
  color: var(--app-ink);
  font: inherit;
  font-size: var(--app-fs-base);
  transition: border-color var(--app-fast) var(--app-ease),
              background var(--app-fast) var(--app-ease);
}
.doc-input:hover,
.doc-select:hover { background: var(--app-surface-2); border-color: var(--app-line-strong); }
.doc-input:focus,
.doc-select:focus {
  outline: none;
  background: var(--app-surface);
  border-color: var(--app-ring);
  box-shadow: var(--app-focus);
}
.doc-input::placeholder { color: var(--app-ink-faint); }
.doc-area { resize: vertical; line-height: var(--app-lh-normal); }
.doc-num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--app-font-mono); }

/* ---- 20.4 The line table ------------------------------------------------ */

.doc-table-wrap { overflow-x: auto; padding-top: var(--app-s-4); }
.doc-table { width: 100%; border-collapse: collapse; min-width: 46rem; }
.doc-table thead th {
  padding: var(--app-s-2) var(--app-s-2);
  border-bottom: 1px solid var(--app-line-strong);
  font-size: var(--app-fs-2xs);
  font-weight: var(--app-w-semibold);
  text-transform: uppercase;
  letter-spacing: var(--app-track-eyebrow);
  color: var(--app-ink-faint);
  text-align: left;
}
.doc-col-qty, .doc-col-price, .doc-col-disc, .doc-col-amt { text-align: right; }
.doc-col-desc  { width: 40%; }
.doc-col-qty   { width: 7rem; }
.doc-col-price { width: 9rem; }
.doc-col-disc  { width: 8rem; }
.doc-col-tax   { width: 11rem; }
.doc-col-amt   { width: 8rem; }
.doc-col-x     { width: 2.5rem; }

.doc-row td { padding: 0.15rem var(--app-s-1); border-bottom: 1px solid var(--app-line); vertical-align: middle; }
.doc-row:hover td { background: var(--app-surface-2); }
.doc-amount {
  display: block;
  padding-right: var(--app-s-2);
  text-align: right;
  font-family: var(--app-font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: var(--app-w-semibold);
}

.doc-x {
  width: 1.75rem; height: 1.75rem;
  display: grid; place-items: center;
  border: 0;
  border-radius: var(--app-r-8);
  background: none;
  color: var(--app-ink-faint);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  transition: background var(--app-fast) var(--app-ease), color var(--app-fast) var(--app-ease);
}
.doc-x:hover { background: var(--app-bad-fill); color: var(--app-bad-edge); }
.doc-x:disabled { opacity: 0.25; cursor: not-allowed; }

/* Feedback, not decoration: a new row says "here, this one is yours" and a
   leaving row shows the table closing over it rather than blinking. */
@keyframes doc-row-in {
  from { background: var(--app-brand-soft); transform: translateY(-2px); }
  to   { background: transparent; transform: none; }
}
.doc-row-new td { animation: doc-row-in 400ms var(--app-ease); }
.doc-row-going { opacity: 0; transform: translateX(0.5rem); transition: opacity 160ms var(--app-ease), transform 160ms var(--app-ease); }

.doc-add-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--app-s-3);
  padding: var(--app-s-4) 0;
  border-bottom: 1px solid var(--app-line);
}
.doc-add-sep { color: var(--app-ink-faint); font-size: var(--app-fs-sm); }
.doc-add-item { max-width: 18rem; }

/* ---- 20.5 Totals -------------------------------------------------------- */

.doc-totals { display: flex; justify-content: flex-end; padding: var(--app-s-5) 0; }
.doc-totals-inner { width: min(24rem, 100%); }
.doc-total-line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--app-s-4);
  padding: var(--app-s-2) 0;
  font-size: var(--app-fs-base);
  color: var(--app-ink-muted);
}
.doc-total-line .doc-num { font-weight: var(--app-w-medium); color: var(--app-ink); }
.doc-total-input { display: flex; align-items: center; gap: var(--app-s-1); width: 9rem; }
.doc-total-input .doc-input { padding-block: 0.25rem; }
.doc-marker { color: var(--app-ink-faint); font-size: var(--app-fs-sm); }

.doc-total-grand {
  margin-top: var(--app-s-2);
  padding-top: var(--app-s-3);
  border-top: 1px solid var(--app-line-strong);
  font-family: var(--app-font-display);
  font-weight: var(--app-w-display-strong);
  font-size: var(--app-fs-xl);
  color: var(--app-ink);
}
.doc-total-grand .doc-num { font-size: var(--app-fs-xl); font-weight: var(--app-w-display-strong); }
.doc-total-note { margin: var(--app-s-2) 0 0; font-size: var(--app-fs-xs); color: var(--app-ink-faint); }

.doc-notes {
  display: grid;
  gap: var(--app-s-5);
  padding-top: var(--app-s-5);
  border-top: 1px solid var(--app-line);
}
@media (min-width: 48rem) { .doc-notes { grid-template-columns: 1fr 1fr; } }

/* ---- 20.6 The action bar -----------------------------------------------
   Sticks to the bottom of the viewport: on a long invoice the save button and
   the total should never be somewhere you have to go looking for. */

.doc-actions {
  position: sticky;
  bottom: 0;
  z-index: var(--app-z-topbar);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--app-s-4);
  margin-top: var(--app-s-5);
  padding: var(--app-s-4) var(--app-s-5);
  border: 1px solid var(--app-line);
  border-radius: var(--app-r-16);
  background: color-mix(in srgb, var(--app-surface) 92%, transparent);
  backdrop-filter: blur(var(--app-blur));
  box-shadow: var(--app-shadow-2);
}
.doc-actions-sum { display: flex; align-items: baseline; gap: var(--app-s-3); }
.doc-actions-label {
  font-size: var(--app-fs-2xs);
  text-transform: uppercase;
  letter-spacing: var(--app-track-eyebrow);
  color: var(--app-ink-faint);
}
.doc-actions-total {
  font-family: var(--app-font-display);
  font-weight: var(--app-w-display-strong);
  font-size: var(--app-fs-xl);
  font-variant-numeric: tabular-nums;
}
.doc-actions-btns { display: flex; gap: var(--app-s-2); margin-left: auto; }

@media (max-width: 40rem) {
  .doc-meta-bill { grid-column: span 2; }
  .doc-actions { padding: var(--app-s-3) var(--app-s-4); }
  .doc-actions-total { font-size: var(--app-fs-lg); }
}

/* The running total beside "Add another line" on the draft editor. Hidden in
   the markup and revealed by the script, so a browser with JavaScript off is
   not shown a total that would never update. */
.ed-total { margin-left: auto; align-self: center; color: var(--app-ink-muted); font-size: var(--app-fs-sm); }
.ed-total strong { font-family: var(--app-font-mono); font-variant-numeric: tabular-nums; color: var(--app-ink); }

/* The style previews under the template form. Each opens the real renderer with
   sample figures, in a new tab, so choosing a style is looking at it rather
   than reading three adjectives about it. */
.app-style-preview { display: flex; flex-wrap: wrap; gap: var(--app-s-2); margin-top: var(--app-s-2); }

/* ==========================================================================
   21. THE OVERVIEW DASHBOARD

   ADDED 2026-08-09. The overview was a P0 artefact — a setup checklist and a
   count of colleagues — so the one thing a merchant opens an invoicing product
   to find out, what am I owed and what is late, was on no screen at all.

   The stat tile is the pattern every good admin template converges on, and for
   a reason: a coloured icon to find the row by, a small label, one large figure,
   and a sentence that makes the figure mean something.
   ========================================================================== */

.app-kpis {
  display: grid;
  gap: var(--app-s-4);
  grid-template-columns: 1fr;
  margin-bottom: var(--app-s-6);
}
@media (min-width: 40rem) { .app-kpis { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 68rem) { .app-kpis { grid-template-columns: repeat(4, 1fr); } }

.app-kpi {
  display: flex;
  align-items: flex-start;
  gap: var(--app-s-4);
  padding: var(--app-s-5);
  border: 1px solid var(--app-line);
  border-radius: var(--app-r-16);
  background: var(--app-surface);
}
/* Only when something is actually late. An alert that is always on is decoration. */
.app-kpi-alert { border-color: var(--app-bad-edge); }

.app-kpi-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--app-r-12);
  background: var(--app-surface-3);
  color: var(--app-ink-muted);
}
.app-kpi-icon-open { background: var(--app-surface-3); color: var(--app-ink-muted); }
.app-kpi-icon-late { background: var(--app-bad-fill); color: var(--app-bad-ink); }
.app-kpi-icon-paid { background: var(--app-ok-fill); color: var(--app-ok-ink); }

/* CHANGED 2026-08-12 with the rest of the re-skin: sentence case at body
   size. Four uppercase letterspaced labels across the top of the dashboard
   competed with the four figures they label. */
.app-kpi-label {
  margin: 0;
  font-size: var(--app-fs-base);
  font-weight: var(--app-w-medium);
  color: var(--app-ink-muted);
}
.app-kpi-figure {
  margin: 0.15rem 0 0;
  font-family: var(--app-font-sans);
  font-weight: var(--app-w-semibold);
  font-size: var(--app-fs-2xl);
  letter-spacing: var(--app-track-tight);
  font-variant-numeric: tabular-nums;
  color: var(--app-ink);
}
.app-kpi-meta { margin: 0.25rem 0 0; font-size: var(--app-fs-sm); color: var(--app-ink-muted); line-height: var(--app-lh-snug); }

/* The split. One bar, three shares, honest proportions. */
.app-split-bar {
  display: flex;
  height: 0.6rem;
  border-radius: var(--app-r-pill);
  overflow: hidden;
  background: var(--app-surface-3);
}
.app-split-bar i { display: block; height: 100%; }
.app-split-paid { background: var(--app-ok-edge); }
.app-split-open { background: var(--app-brand); }
.app-split-late { background: var(--app-bad-edge); }

.app-split-key {
  display: flex;
  flex-wrap: wrap;
  gap: var(--app-s-2) var(--app-s-5);
  margin-top: var(--app-s-3);
  font-size: var(--app-fs-xs);
  color: var(--app-ink-muted);
}
.app-split-key span { display: inline-flex; align-items: center; gap: var(--app-s-2); }
.app-split-key i { width: 0.6rem; height: 0.6rem; border-radius: 50%; display: inline-block; }

/* ==========================================================================
   22. THE FOCUS LAYOUT

   The third layout this stylesheet serves, after the public site (19) and the
   application shell (5). No sidebar, no breadcrumb: a slim bar with the way
   out, and the full width of the screen for the work.

   ADDED 2026-08-09 for the counter. A till is used standing up, on whatever
   screen the shop has, by somebody with a queue in front of them.
   ========================================================================== */

.app-focus { min-height: 100vh; background: var(--app-bg); display: flex; flex-direction: column; }

.app-focus-bar {
  position: sticky;
  top: 0;
  z-index: var(--app-z-topbar);
  display: flex;
  align-items: center;
  gap: var(--app-s-4);
  min-height: 3.25rem;
  padding: 0 var(--app-s-5);
  background: var(--app-surface);
  border-bottom: 1px solid var(--app-line);
}
.app-focus-back {
  display: inline-flex;
  align-items: center;
  gap: var(--app-s-2);
  color: var(--app-ink-muted);
  text-decoration: none;
  font-size: var(--app-fs-base);
  font-weight: var(--app-w-medium);
  padding: var(--app-s-2) var(--app-s-2) var(--app-s-2) 0;
}
.app-focus-back:hover { color: var(--app-ink); }
/* The chevron points back the way it goes. */
.app-focus-back svg { transform: rotate(180deg); }

.app-focus-title {
  font-weight: var(--app-w-semibold);
  color: var(--app-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.app-focus-actions { margin-left: auto; display: flex; align-items: center; gap: var(--app-s-2); }

/* Wider than the app's reading column: this is a working surface, and the
   item grid and the cart want to sit side by side without either being cramped. */
.app-content-focus {
  flex: 1;
  width: 100%;
  max-width: 96rem;
  margin-inline: auto;
  padding: var(--app-s-6) var(--app-s-5) var(--app-s-10);
}

@media (max-width: 48rem) {
  .app-focus-bar { padding-inline: var(--app-s-4); gap: var(--app-s-3); }
  .app-focus-title { display: none; }
  .app-content-focus { padding: var(--app-s-4) var(--app-s-4) var(--app-s-8); }
}

/* Two tiles rather than four: the statement answers a shorter question than the
   overview does, and stretching two cards across four columns makes them read
   as though something is missing. */
@media (min-width: 40rem) { .app-kpis-2 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 68rem) { .app-kpis-2 { grid-template-columns: repeat(2, 1fr); } }

/* ---------------------------------------------------------------------------
   The customer screen (plans/60-p5-terminal.md Stage C).

   Read across a counter, by somebody standing up, often at arm's length and
   from an angle. So: no chrome, one number that dominates everything, and type
   that stays legible on a cheap second monitor. Every other screen in this app
   is worked ON; this one is only READ.
   --------------------------------------------------------------------------- */
.pos-cust {
  display: flex;
  flex-direction: column;
  min-height: 70vh;
  gap: var(--app-s-5);
}
.pos-cust-head { border-bottom: 1px solid var(--app-line); padding-bottom: var(--app-s-3); }
.pos-cust-merchant { font-size: 1.5rem; font-weight: 600; margin: 0; }
.pos-cust-meta { color: var(--app-ink-muted); margin: var(--app-s-1) 0 0; }
.pos-cust-body { flex: 1; }
.pos-cust-table { width: 100%; border-collapse: collapse; font-size: 1.35rem; }
.pos-cust-table td { padding: var(--app-s-3) 0; border-bottom: 1px solid var(--app-line); }
.pos-cust-desc { width: 60%; }
.pos-cust-qty { color: var(--app-ink-muted); text-align: center; width: 15%; }
.pos-cust-amt { text-align: right; font-variant-numeric: tabular-nums; width: 25%; }
.pos-cust-empty { color: var(--app-ink-muted); font-size: 1.2rem; padding: var(--app-s-6) 0; }
.pos-cust-foot { border-top: 2px solid var(--app-line); padding-top: var(--app-s-4); }
.pos-cust-subs p {
  display: flex; justify-content: space-between;
  color: var(--app-ink-muted); margin: 0 0 var(--app-s-2); font-size: 1.1rem;
  font-variant-numeric: tabular-nums;
}
.pos-cust-total-label { color: var(--app-ink-muted); margin: 0; text-transform: uppercase; letter-spacing: .08em; font-size: .9rem; }
/* The one figure the customer is actually checking. clamp() so it fills a
   20-inch display without overflowing a laptop propped on the counter. */
.pos-cust-total {
  font-size: clamp(2.75rem, 9vw, 5rem);
  font-weight: 700;
  margin: var(--app-s-1) 0 0;
  font-variant-numeric: tabular-nums;
  line-height: 1.05;
}
.pos-cust-stale { color: var(--app-ink-muted); margin: var(--app-s-2) 0 0; }

/* The display layout's only rule: fill the screen and stay out of the way. */
.app-display {
  padding: var(--app-s-6) var(--app-s-6) var(--app-s-8);
  max-width: 64rem;
  margin-inline: auto;
}
@media (max-width: 48rem) { .app-display { padding: var(--app-s-4); } }

/* The QR, on the screen the customer reads (Stage D). When somebody is being
   asked to scan something, the thing to scan is the whole screen. */
.pos-cust-pay { text-align: center; padding: var(--app-s-6) 0; }
.pos-cust-pay-head { font-size: 1.75rem; font-weight: 600; margin: 0 0 var(--app-s-4); }
.pos-cust-qr {
  width: clamp(12rem, 40vh, 20rem);
  height: auto;
  image-rendering: pixelated;   /* a QR is pixels; smoothing them costs scans */
  background: #ffffff;          /* scannable in dark mode: a code must be dark on light */
  padding: var(--app-s-3);
  border-radius: var(--app-r-12);
}
.pos-cust-pay-total {
  font-size: clamp(2rem, 6vw, 3.25rem);
  font-weight: 700;
  margin: var(--app-s-4) 0 0;
  font-variant-numeric: tabular-nums;
}
.pos-cust-pay-note { color: var(--app-ink-muted); margin: var(--app-s-3) auto 0; max-width: 32rem; }
.app-pos-qr img { image-rendering: pixelated; background: #ffffff; padding: var(--app-s-2); border-radius: var(--app-r-12); }

/* ---------------------------------------------------------------------------
   CHECKOUT — the page a customer lands on from a QR code or an invoice link.

   Read on a phone, once, by somebody who is standing at a counter or sitting
   with an invoice. It is one card, centred, with one thing to do. Nothing on
   this page navigates anywhere, so nothing on it looks like navigation.

   Light only, deliberately. The app has a theme toggle because merchants live
   in it all day; a customer sees this page for forty seconds and their OS
   preference should not decide whether a payment screen looks trustworthy.
   --------------------------------------------------------------------------- */
.co {
  min-height: 100vh;
  background: #f6f7f9;
  color: #0a0a0a;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding: clamp(1rem, 5vw, 3.5rem) 1rem 2rem;
  gap: 1rem;
}
.co-card {
  width: 100%;
  max-width: 30rem;
  background: #ffffff;
  border: 1px solid #e5e5e5;
  border-radius: 18px;
  box-shadow: 0 12px 40px -18px rgba(15, 23, 42, 0.35);
  padding: clamp(1.25rem, 4vw, 2rem);
}
.co-foot { color: #737373; font-size: .8125rem; text-align: center; margin: 0; max-width: 30rem; }

/* The checkout re-states the app's building blocks in its own light palette
   rather than inheriting them: the app's tokens follow the merchant's theme,
   and this page must look the same for every customer. */
.co .app-page-head { display: block; margin: 0 0 1.25rem; padding: 0; border: 0; }
.co .app-eyebrow { color: #737373; font-size: .8125rem; margin: 0 0 .25rem; letter-spacing: .02em; }
.co .app-title { font-size: 1.375rem; line-height: 1.25; margin: 0; color: #0a0a0a; }
.co .app-subtitle { color: #737373; margin: .375rem 0 0; font-size: .9375rem; }
/* The checkout is ONE sheet, so its sections are not cards: no background, no
   border, no radius. The radius is the one that mattered — it stayed on while
   the border was reduced to a single top rule, so the divider between two
   sections rendered as a rounded line curling up at both ends, like a box
   someone had rubbed out. Visible on a phone at the exact moment a customer is
   deciding whether to trust the page with a payment. */
.co .app-card { background: transparent; border: 0; border-radius: 0; box-shadow: none; margin: 0; }
.co .app-card + .app-card { border-top: 1px solid var(--app-line); margin-top: var(--app-s-5); padding-top: var(--app-s-5); }
.co .app-card-head { padding: 0 0 .75rem; border: 0; }
.co .app-card-title { font-size: var(--app-fs-lg); margin: 0; color: var(--app-ink); }
.co .app-card-meta { color: var(--app-ink-muted); margin: var(--app-s-1) 0 0; font-size: var(--app-fs-base); }
.co .app-card-body { padding: 0; }
.co .app-label { color: #404040; font-weight: 600; font-size: .875rem; }
.co .app-input, .co .app-select {
  background: #ffffff; border: 1px solid #d4d4d4; color: #0a0a0a;
  border-radius: 10px; min-height: 2.875rem; font-size: 1rem; width: 100%;
}
.co .app-input:focus, .co .app-select:focus { border-color: #171717; box-shadow: 0 0 0 3px rgba(15, 118, 110, .18); outline: none; }
.co .app-hint { color: #737373; font-size: .8125rem; }
.co .app-error { color: #b91c1c; font-size: .875rem; }
.co .app-btn {
  min-height: 2.875rem; border-radius: 10px; font-size: 1rem; font-weight: 600;
  background: #ffffff; border: 1px solid #d4d4d4; color: #0a0a0a;
}
.co .app-btn-primary { background: #0a0a0a; border-color: #0a0a0a; color: #ffffff; }
.co .app-btn-primary:hover { background: #262626; }
.co .app-btn-ghost { background: transparent; border-color: transparent; color: #525252; }
/* One action per row on a phone: a payment button that shares a line with
   another button is a payment button somebody taps by accident. */
.co .app-btn-row { display: flex; flex-direction: column; gap: .625rem; }
.co .app-btn-row .app-btn { width: 100%; }
@media (min-width: 26rem) {
  .co .app-btn-row { flex-direction: row; flex-wrap: wrap; }
  .co .app-btn-row .app-btn { width: auto; flex: 1 1 auto; }
}
.co .app-form { display: flex; flex-direction: column; gap: 1rem; }
.co .app-field { display: flex; flex-direction: column; gap: .375rem; }
/* REPORTED 2026-08-12: "the due table content doesn't have any paddings and
   the colors are way off. Can't even see invoice date."
   Measured on a 390px screen: `1fr auto` gave the value column whatever the
   label left, so "Wednesday, 12 August 2026" ran off the card — the date was
   not faint, it was outside the page. min-width:0 lets a grid child shrink
   (without it a grid item refuses to go below its content width, which is the
   whole bug), the value may wrap, and under 26rem the pair stacks. */
.co .app-dl {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  gap: var(--app-s-3) var(--app-s-4);
  margin: 0;
  padding: var(--app-s-1) 0;
}
.co .app-dl dt { color: var(--app-ink-muted); font-size: var(--app-fs-base); min-width: 0; }
.co .app-dl dd {
  margin: 0;
  min-width: 0;
  text-align: right;
  color: var(--app-ink);
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 26rem) {
  .co .app-dl { grid-template-columns: 1fr; gap: var(--app-s-1) 0; }
  .co .app-dl dt { font-size: var(--app-fs-sm); }
  .co .app-dl dd { text-align: left; padding-bottom: var(--app-s-2); }
}
.co-h3 { font-size: var(--app-fs-base); font-weight: var(--app-w-semibold); margin: 0 0 var(--app-s-2); }
.co .app-divider { border: 0; border-top: 1px solid #e5e5e5; margin: 1rem 0; }
/* min-width:0, against the app table's 40rem floor: that floor is right for a
   merchant's data grid on a laptop and wrong for four columns on a phone,
   where it produced a table the customer had to drag sideways to read. */
.co .app-table { width: 100%; min-width: 0; border-collapse: collapse; }
.co .app-table td, .co .app-table th {
  height: auto;
  padding: var(--app-s-3) 0;
  border-bottom: 1px solid var(--app-line);
  font-size: var(--app-fs-base);
}
.co .app-table tr:last-child td { border-bottom: 0; }

/* ---------------------------------------------------------------------------
   RECEIPT — /invoices/:id/receipt, opened in its own window from the till.

   Sized for an 80mm till roll, legible on A4, and printed WITHOUT the
   application around it: @media print removes every control, so what comes out
   is the receipt and nothing else.
   --------------------------------------------------------------------------- */
.rc {
  max-width: 22rem;
  margin: 0 auto;
  padding: 1.25rem;
  background: var(--app-surface);
  border: 1px solid var(--app-line);
  border-radius: var(--app-r-12);
  font-variant-numeric: tabular-nums;
}
.rc-head { text-align: center; margin-bottom: 1rem; }
.rc-merchant { font-size: 1.125rem; font-weight: 700; margin: 0; }
.rc-meta { color: var(--app-ink-muted); font-size: .8125rem; margin: .125rem 0 0; }
.rc-row { display: flex; justify-content: space-between; gap: 1rem; font-size: .875rem; padding: .1875rem 0; }
.rc-row span:first-child { color: var(--app-ink-muted); }
.rc-rule { border-top: 1px dashed var(--app-line-strong); margin: .75rem 0; }
.rc-lines { width: 100%; border-collapse: collapse; }
.rc-lines td { padding: .25rem 0; vertical-align: top; font-size: .875rem; }
.rc-desc { padding-right: .5rem; }
.rc-qty { display: block; color: var(--app-ink-muted); font-size: .75rem; }
.rc-amt { text-align: right; white-space: nowrap; }
.rc-total { display: flex; justify-content: space-between; font-size: 1.125rem; font-weight: 700; padding: .5rem 0; border-top: 1px solid var(--app-line-strong); margin-top: .25rem; }
.rc-paid { text-align: center; font-weight: 700; letter-spacing: .12em; margin: .75rem 0 0; }
.rc-foot { text-align: center; color: var(--app-ink-muted); font-size: .75rem; margin: 1rem 0 0; }
.rc-tools { max-width: 22rem; margin: 0 auto 1rem; display: flex; gap: .5rem; }
.rc-tools .app-btn { flex: 1 1 auto; }
.rc-email { margin: 1rem auto 0; display: block; }
.rc-email-row { display: flex; gap: .5rem; }
.rc-email-row .app-input { flex: 1 1 auto; }

@media print {
  /* One rule, doing the whole job: everything that is not the receipt goes. */
  .rc-tools, .app-flashes { display: none !important; }
  body { background: #ffffff; }
  .app-display { padding: 0; }
  .rc { border: 0; border-radius: 0; max-width: none; padding: 0; box-shadow: none; }
  .rc-merchant, .rc-total, .rc-paid { color: #000000; }
}

/* The customer screen, after a sale and during one (Stage C, extended
   2026-08-12). The change owed is the number a customer checks hardest, so it
   is the largest thing on the screen after the total itself. */
.pos-cust-paid { text-align: center; padding: var(--app-s-6) 0; }
.pos-cust-paid-head {
  font-size: 1.25rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--app-ink-muted); margin: 0 0 var(--app-s-2);
}
.pos-cust-paid-total { font-size: clamp(2.5rem, 8vw, 4.5rem); font-weight: 700; margin: 0; font-variant-numeric: tabular-nums; line-height: 1.05; }
.pos-cust-change { margin: var(--app-s-6) auto 0; padding-top: var(--app-s-4); border-top: 2px solid var(--app-line); max-width: 26rem; }
.pos-cust-change-label { color: var(--app-ink-muted); margin: 0; text-transform: uppercase; letter-spacing: .08em; font-size: .9rem; }
.pos-cust-change-amt { font-size: clamp(2rem, 7vw, 3.5rem); font-weight: 700; margin: var(--app-s-1) 0 0; font-variant-numeric: tabular-nums; }
.pos-cust-ref { color: var(--app-ink-muted); margin: var(--app-s-3) 0 0; font-size: 1rem; }
.pos-cust-live { margin-top: var(--app-s-4); border-top: 1px solid var(--app-line); padding-top: var(--app-s-3); }
.pos-cust-live p {
  display: flex; justify-content: space-between; gap: 1rem;
  font-size: 1.25rem; margin: 0 0 var(--app-s-2); font-variant-numeric: tabular-nums;
}
.pos-cust-live p span:first-child { color: var(--app-ink-muted); font-size: 1rem; }

/* Parked sales at the till: one tab per customer being served, the number on
   each being its Ctrl shortcut. Sized for a finger on a small touchscreen —
   a till is not operated with a trackpad. */
.app-pos-tabs { display: flex; flex-wrap: wrap; gap: var(--app-s-2); margin-bottom: var(--app-s-4); }
.app-pos-tab {
  display: inline-flex; align-items: baseline; gap: var(--app-s-2);
  min-height: 2.75rem; padding: var(--app-s-2) var(--app-s-3);
  border: 1px solid var(--app-field-line); border-radius: var(--app-r-12);
  background: var(--app-surface); color: var(--app-ink); cursor: pointer;
  font: inherit; line-height: 1.2;
}
.app-pos-tab:hover { background: var(--app-surface-3); }
.app-pos-tab.is-active { border-color: var(--app-action); box-shadow: inset 0 0 0 1px var(--app-action); font-weight: 600; }
.app-pos-tab.is-empty .app-pos-tab-amt { color: var(--app-ink-faint); }
.app-pos-tab-num {
  display: inline-grid; place-items: center; min-width: 1.5rem; height: 1.5rem;
  border-radius: 999px; background: var(--app-surface-2); color: var(--app-ink-muted);
  font-size: .8125rem; font-variant-numeric: tabular-nums;
}
.app-pos-tab.is-active .app-pos-tab-num { background: var(--app-action); color: var(--app-action-ink, #fff); }
.app-pos-tab-amt { font-variant-numeric: tabular-nums; }
.app-pos-tab-add { border-style: dashed; color: var(--app-ink-muted); }

/* A finger is not a mouse pointer. Every control that can be tapped gets the
   44px target on a touchscreen, which is what a counter actually runs on. */
@media (pointer: coarse) {
  .app-btn { min-height: 2.75rem; padding-inline: var(--app-s-5); }
  .app-input, .app-select { min-height: 2.75rem; }
  .app-pos-tab { min-height: 3rem; }
}

/* The invoice a customer sees after scanning, inside the checkout card. It is
   read on a phone, held in one hand, by somebody deciding whether to pay — so
   the amount leads, the action is full width under it, and the line items are
   reference material rather than the subject. */
.co .app-amount {
  font-size: clamp(2.25rem, 9vw, 3rem);
  font-weight: 700;
  margin: 0 0 var(--app-s-4);
  color: #0a0a0a;
  font-variant-numeric: tabular-nums;
}
.co .app-money-cur { font-size: .5em; font-weight: 600; color: #737373; margin-right: .2em; vertical-align: .35em; }
.co-pay { margin: 0 0 var(--app-s-2); }
.co-pay .app-btn { width: 100%; min-height: 3rem; font-size: 1.0625rem; }
.co .app-page-actions { margin-left: 0; }
.co .app-table-wrap { overflow-x: auto; margin: 0; border: 0; }
.co .app-table th, .co .app-table td { padding: .4375rem 0; font-size: .875rem; white-space: nowrap; }
.co .app-table th:first-child, .co .app-table td:first-child { white-space: normal; padding-right: .75rem; }
.co .app-table-head th { color: var(--app-ink-muted); font-weight: var(--app-w-medium); font-size: var(--app-fs-sm); }
.co .app-pill { font-size: .75rem; }
.co .app-meta { color: var(--app-ink-muted); font-size: var(--app-fs-base); margin: 0 0 .5rem; }
.co .app-ref { font-family: var(--app-font-mono, monospace); background: var(--app-surface-3); padding: .0625rem .3125rem; border-radius: 6px; }
.co a { color: var(--app-ink); }

/* ---- quick add, inside a form that is already a form -------------------
   plans/70 §2 item 2. A <details> in the invoice composer and at the till:
   closed it is a button, open it is three fields and a submit. It cannot be a
   nested <form> — HTML has none — so it is styled to read as its own block
   inside the one form the page is. */
.doc-quickadd {
  margin-block-start: var(--app-s-3);
  padding: var(--app-s-3);
  border: 1px dashed var(--app-edge);
  border-radius: var(--app-r-12);
  background: var(--app-surface-2);
}
.doc-quickadd > summary {
  cursor: pointer;
  list-style: none;
  display: inline-flex;
}
.doc-quickadd > summary::-webkit-details-marker { display: none; }
.doc-quickadd[open] > summary { margin-block-end: var(--app-s-3); }

/* ---- the account menu, top right (plans/70 §2 item 3) ------------------
   A <details>, so it opens and closes with no script at all. The sidebar foot
   used to hold this; the sidebar is navigation now and nothing else. */
.app-account { position: relative; }
.app-account > summary {
  cursor: pointer;
  list-style: none;
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
}
.app-account > summary::-webkit-details-marker { display: none; }
.app-account-avatar {
  display: inline-grid;
  place-items: center;
  inline-size: 2rem;
  block-size: 2rem;
  border-radius: 999px;
  background: var(--app-action);
  color: var(--app-action-ink);
  font-size: var(--app-fs-sm);
  font-weight: 600;
}
.app-account > summary:hover .app-account-avatar { filter: brightness(1.08); }
.app-account-panel {
  position: absolute;
  inset-inline-end: 0;
  inset-block-start: calc(100% + var(--app-s-2));
  min-inline-size: 15rem;
  padding: var(--app-s-2);
  background: var(--app-surface-1);
  border: 1px solid var(--app-edge);
  border-radius: var(--app-r-12);
  box-shadow: var(--app-shadow-2);
  z-index: 50;
}
.app-account-who {
  display: grid;
  gap: 2px;
  padding: var(--app-s-2);
  border-block-end: 1px solid var(--app-edge);
  margin-block-end: var(--app-s-2);
}
.app-account-email { font-size: var(--app-fs-sm); color: var(--app-ink); word-break: break-all; }
.app-account-role  { font-size: var(--app-fs-xs); color: var(--app-ink-muted); }
.app-account-item {
  display: block;
  padding: var(--app-s-2);
  border-radius: var(--app-r-8);
  color: var(--app-ink);
  font-size: var(--app-fs-sm);
  text-decoration: none;
}
.app-account-item:hover { background: var(--app-surface-3); color: var(--app-ink); }
.app-account-out { color: var(--app-bad-ink); }

/* ---- a merchant's own mark, where their customer pays ------------------
   plans/70 §2 item 5. Bounded rather than free: a logo uploaded at any size
   must not push the amount owed below the fold on a phone. */
.app-brand-logo {
  max-block-size: 40px;
  max-inline-size: 180px;
  object-fit: contain;
}
.app-brand-swatch {
  display: block;
  block-size: 3rem;
  border-radius: var(--app-r-12);
  border: 1px solid var(--app-edge);
}

/* The merchant's own colour, injected as a custom property so the RULES stay
   here (lint L12). --app-brand is set inline on the element; everything about
   what that colour does is written once, below. */
.app-brand-ink { color: var(--app-brand, var(--app-ink)); }
.app-brand-swatch { background: var(--app-brand, var(--app-surface-3)); }

/* =========================================================================
   12. THE CHECKOUT
   =========================================================================
   Added 2026-08-20 with the rebuilt payment page. Everything in this section
   is public-facing: it is seen by people who have never used this product and
   never will again, on a phone, once. That is a different job from the rest of
   the stylesheet, and the rules it follows are:

     * one decision per screen, and the decision is the biggest thing on it;
     * nothing that looks pressable unless it is;
     * a test rail is labelled as one everywhere it appears, in a colour that
       does not read as an error, because it is not one.
   ---------------------------------------------------------------------- */

/* ---- 12.1 How would you like to pay ---------------------------------- */

.pay-methods {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--app-s-4);
  margin-block: var(--app-s-5);
}

/* Each method is its own form, so the form must not introduce a box of its
   own — the BUTTON is the tile. */
.pay-method { display: flex; }

.pay-method-btn {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--app-s-1);
  width: 100%;
  padding: var(--app-s-5) var(--app-s-5);
  text-align: left;
  font: inherit;
  color: var(--app-ink);
  background: var(--app-surface);
  border: 1px solid var(--app-line);
  border-radius: var(--app-r-16);
  cursor: pointer;
  transition: border-color 120ms ease, box-shadow 120ms ease, transform 120ms ease;
}
.pay-method-btn:hover {
  border-color: var(--app-brand);
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.04), 0 8px 20px rgb(0 0 0 / 0.06);
}
/* The press is felt rather than announced. Kept tiny: a tile that leaps is a
   tile somebody mistrusts on a payment page. */
.pay-method-btn:active { transform: translateY(1px); }
.pay-method-btn:focus-visible {
  outline: 2px solid var(--app-brand);
  outline-offset: 2px;
}

.pay-method-label {
  font-family: var(--app-font-display);
  font-size: var(--app-fs-md);
  font-weight: var(--app-w-semibold);
}
.pay-method-blurb {
  font-size: var(--app-fs-sm);
  color: var(--app-ink-muted);
}

/* ---- 12.2 Saying "this is a test" without saying "something is wrong" -- */

/* Deliberately NOT the error colour. A test rail is working exactly as
   intended, and dressing it in red teaches people to ignore red. */
.pay-method-test,
.pay-test-note {
  display: inline-block;
  margin-top: var(--app-s-2);
  padding: var(--app-s-1) var(--app-s-2);
  font-size: var(--app-fs-xs);
  font-weight: var(--app-w-semibold);
  letter-spacing: 0.02em;
  color: var(--app-ink-muted);
  background: var(--app-surface-2);
  border: 1px dashed var(--app-line);
  border-radius: var(--app-r-8);
}
.pay-test-note { margin-bottom: var(--app-s-4); }

.pay-otp-demo {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--app-s-2);
  margin-bottom: var(--app-s-5);
  padding: var(--app-s-4) var(--app-s-5);
  background: var(--app-surface-2);
  border: 1px dashed var(--app-line);
  border-radius: var(--app-r-12);
  font-size: var(--app-fs-sm);
  color: var(--app-ink-muted);
}
.pay-otp-demo span { flex-basis: 100%; font-size: var(--app-fs-xs); }
.pay-otp-code {
  font-family: var(--app-font-mono, ui-monospace, monospace);
  font-size: var(--app-fs-lg);
  letter-spacing: 0.18em;
  color: var(--app-ink);
}

/* ---- 12.3 The card form ---------------------------------------------- */

/* A checkout column, not a page. 26rem is about as wide as a card form can be
   before the eye stops treating it as one object. */
.pay-card { max-width: 30rem; }
.pay-form { display: flex; flex-direction: column; gap: var(--app-s-5); }

/* Digits line up under digits. Without tabular figures the grouped number
   shifts sideways as it is typed, which reads as the field fighting back. */
.pay-card-number {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
}

.pay-testcards summary {
  cursor: pointer;
  font-size: var(--app-fs-sm);
  color: var(--app-ink-muted);
}
.pay-testcards code { font-variant-numeric: tabular-nums; }

.pay-receipt-actions { margin-block: var(--app-s-5); }

/* =========================================================================
   13. SPACING: WHO OWNS THE GAP
   =========================================================================
   Reported 2026-08-20: "spacings all over the platform are inconsistent, as
   highlighted in /shop and /plan".

   The cause is one line. `.app-card` carries `margin-block: var(--app-s-4)`,
   which is right when cards are stacked down a page and each must push the
   next away. It is wrong the moment cards are laid out by a container that
   already owns the spacing: inside `.app-card-grid` every tile added its own
   16px on top of the grid's 20px `gap`, so the vertical rhythm between rows
   (36px) stopped matching the horizontal rhythm between columns (20px) — and
   the first and last rows gained an outdent the container never asked for.

   The rule: THE CONTAINER OWNS THE GAP. A card inside a flex or grid layout
   contributes none of its own. Fixed here rather than by deleting the margin,
   because the stacked case is the common one and still needs it.
   ---------------------------------------------------------------------- */

.app-card-grid > .app-card,
.app-stats > .app-card {
  margin-block: 0;
}

/* CARDS IN A GRID ARE THE SAME HEIGHT, and their buttons line up.
   On /shop each product card carries a different number of optional lines —
   a monthly equivalent, what you already have, an expiry warning — so the buy
   buttons sat at four different heights across one row. The eye reads that as
   four unrelated boxes rather than one row of choices. The body becomes a
   column and the action is pushed to the bottom of it, so the buttons agree
   whatever the copy above them does. */
.app-card-grid > .app-card {
  display: flex;
  flex-direction: column;
}
.app-card-grid > .app-card > .app-card-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--app-s-3);
}
.app-card-grid > .app-card > .app-card-body > .app-btn-row {
  margin-top: auto;
  padding-top: var(--app-s-2);
}

/* The gap above owns the spacing between everything in a card body, so the
   individual paragraphs must stop adding their own on top of it. */
.app-card-grid > .app-card > .app-card-body > p { margin: 0; }

/* =========================================================================
   14. THE AUTH SCREENS — a split panel

   ☠️ THESE ARE .mk-authpanel-*, NOT .mk-split-*, AND THE NAME MATTERS.
   They were written as .mk-split first, which is ALREADY TAKEN: it is the
   landing page's split HEADING — display face, weight 800, uppercase,
   font-size clamp(2rem, 7vw, 5rem). Used as a layout container it handed every
   descendant a 70px uppercase display font, so the body copy rendered as
   squashed capitals and the inputs — which carry `font: inherit` — came out
   120px tall. Two rounds of "explicit typography" were spent treating the
   symptom before the cause was measured with getComputedStyle.
   Check a class name is free before reusing it.
   =========================================================================
   Asked for 2026-08-20 against a reference: form on the left, something worth
   looking at on the right.

   WHAT IS ON THE RIGHT, AND WHY IT IS NOT A PHOTOGRAPH. The reference used a
   stock photo of a meeting. A photo of strangers in an office says nothing
   about what this product does, costs a large asset to ship on the one page
   people load on the worst connection they own, and dates the moment the
   clothes do. What goes there instead is the product's OWN material — an
   invoice that has been paid, an amount, a settlement line — drawn in CSS. It
   is specific to this subject, it weighs nothing, and it is honest: what you
   see before signing in is what you get after.

   The panel is the dark side of the public palette, which already exists for
   the landing page. Nothing here invents a colour.
   ---------------------------------------------------------------------- */

.mk-authpanel {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
  max-width: 68rem;
  margin-inline: auto;
  padding: clamp(2rem, 5vw, 4rem) 1.5rem clamp(3rem, 7vw, 5rem);
}
/* ---- 14.1a How it responds -------------------------------------------
   Reported 2026-08-20: "not responsive either." It had ONE breakpoint — one
   column until 62rem, two after — so between about 62rem and 75rem the form
   column was pinned at 26rem while the panel took everything else, and on a
   laptop the whole thing sat in a narrow strip with the page's own margins
   around it.
   Now: one column while there is not room for two, then a ratio rather than a
   fixed width, so both halves grow with the window instead of the panel
   taking the slack. */
@media (min-width: 60rem) {
  .mk-authpanel {
    grid-template-columns: minmax(20rem, 0.9fr) minmax(0, 1.1fr);
    gap: clamp(2rem, 4vw, 3.5rem);
  }
}
@media (min-width: 90rem) {
  .mk-authpanel { grid-template-columns: minmax(24rem, 0.8fr) minmax(0, 1.2fr); }
}

/* The form column keeps the width it always had: a form wider than about 26rem
   makes the eye travel further between a label and its box for no gain. */
.mk-authpanel-form { min-width: 0; }

/* ---- 14.1b The fields ------------------------------------------------
   Closer to the reference than the shared .mk-input: filled rather than
   outlined, a larger radius, and more room. An outlined box on a white card
   is two lines describing the same edge; a soft fill says "type here" with
   one. The border stays, at very low contrast, because a fill alone
   disappears for anyone with a high-contrast display or a printed page.

   SCOPED TO THIS PANEL. The same .mk-input is used by the invite form and the
   old centred pages, and changing it globally would restyle screens nobody
   asked me to touch. */
.mk-authpanel-form .mk-field { margin-bottom: 1rem; }
.mk-authpanel-form .mk-label {
  display: block;
  margin-bottom: 0.4rem;
  font-size: 0.85rem;
  font-weight: 600;
}
.mk-authpanel-form .mk-input {
  padding: 0.85rem 1.05rem;
  border-radius: 999px;
  border-color: transparent;
  background: var(--mk-wash);
  /* ☠️ NOT `font: inherit`. That shorthand is what turned these into 120px
     boxes when a container above them carried a display-face font — it takes
     the size AND the line-height from whatever it happens to sit inside. The
     field states its own. */
  font-family: var(--app-font-sans);
  font-size: 1rem;
  line-height: 1.4;
}
.mk-authpanel-form .mk-input:focus {
  background: var(--mk-paper);
  border-color: var(--mk-ink);
  outline: 2px solid var(--mk-ink);
  outline-offset: 1px;
}
/* The submit matches the fields it sits under, rather than the site's
   general-purpose button. */
.mk-authpanel-form .mk-btn-block { border-radius: 999px; padding-block: 0.9rem; }

/* The card holding them loses its outline: the fields are the shapes now, and
   a box around boxes is one border too many. */
.mk-authpanel-form .mk-auth-card {
  border: 0;
  padding: 0;
  background: transparent;
}
.mk-authpanel-head { margin-bottom: 1.75rem; }
.mk-authpanel-title {
  margin: 0.35rem 0 0.5rem;
  font-size: clamp(1.75rem, 4vw, 2.25rem);
  line-height: 1.1;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
/* ☠️ EVERY TYPOGRAPHIC PROPERTY IS STATED, NOT INHERITED.
   The first version of these rules set only colour, size and line-height, and
   the body copy rendered as squashed uppercase with the word spaces closed up
   — while the same text in the OLD centred markup rendered correctly, in the
   same browser, on the same page. Something above these selectors is setting
   case and spacing for prose in this position, and a component that leaves
   those to the cascade is a component that changes shape depending on where it
   is dropped. So each one says what it is. */
.mk-authpanel-sub {
  margin: 0;
  color: var(--mk-mute);
  font-family: var(--app-font-sans);
  font-size: 0.95rem;
  font-weight: 400;
  line-height: 1.55;
}

/* ---- 14.2 The panel --------------------------------------------------- */

/* HIDDEN ON A PHONE. It is illustration, and a person signing in on a handset
   wants the form above the fold rather than a scroll past a picture of one. */
.mk-authpanel-aside { display: none; }
@media (min-width: 60rem) {
  .mk-authpanel-aside {
    /* A COLUMN, NOT A BLOCK. Reported 2026-08-20: "the right illustration
       looks unbalanced." Two things made it so. The first was a collision —
       .mk-float was already the landing page's WHITE card, complete with
       `max-width: 19rem`, so the cards sat squashed inside a pale rectangle
       nobody designed. The second was this: the panel was a block, so its
       content bunched at the top and left a slab of empty ink underneath.
       Now the words sit at the top, the cards sit on the floor, and the space
       between them is the panel's own breathing room rather than a gap. */
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 2.5rem;
    position: relative;
    min-height: 28rem;
    padding: 2.5rem;
    border-radius: calc(var(--mk-radius) * 1.5);
    background: var(--mk-ink);
    color: #ffffff;
    overflow: hidden;
  }
}

/* A single soft wash, so the flat ink has some depth without becoming a
   gradient anybody would notice. */
.mk-authpanel-aside::after {
  content: "";
  position: absolute;
  inset: auto -20% -35% -20%;
  height: 60%;
  background: radial-gradient(60% 100% at 50% 100%, rgba(143, 106, 253, 0.28), transparent 70%);
  pointer-events: none;
}

.mk-authpanel-aside-head { position: relative; z-index: 1; max-width: 22rem; }
.mk-authpanel-aside-eyebrow {
  margin: 0 0 0.6rem;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--mk-mute-dark);
}
.mk-authpanel-aside-title {
  margin: 0 0 0.75rem;
  font-size: 1.6rem;
  line-height: 1.2;
  letter-spacing: -0.015em;
  text-wrap: balance;
}
.mk-authpanel-aside-sub {
  margin: 0;
  color: var(--mk-mute-dark);
  font-family: var(--app-font-sans);
  font-size: 0.9rem;
  font-weight: 400;
  line-height: 1.6;
}

/* ---- 14.3 The floating cards ------------------------------------------ */

.mk-panelcard-stack {
  position: relative;
  z-index: 1;
  display: grid;
  gap: 0.85rem;
}
.mk-panelcard-card {
  width: 100%;
  background: var(--mk-ink-soft);
  border: 1px solid var(--mk-line-dark);
  border-radius: var(--mk-radius);
  padding: 1rem 1.15rem;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.35);
}

.mk-panelcard-row { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.mk-panelcard-label { font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--mk-mute-dark); }
.mk-panelcard-amount {
  font-size: 1.35rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}
.mk-panelcard-meta {
  margin: 0.35rem 0 0;
  font-family: var(--app-font-sans);
  font-size: 0.8rem;
  font-weight: 400;
  line-height: 1.5;
  color: var(--mk-mute-dark);
}

/* The accent earns its keep in exactly one place, as it does on the landing
   page: the state everybody is waiting for. */
.mk-panelcard-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #ffffff;
  background: var(--mk-accent);
}
.mk-panelcard-dot {
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.9;
}

/* ---- 14.4 Small print under the form ---------------------------------- */

.mk-auth-foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.75rem;
  margin-top: 1.25rem;
  font-family: var(--app-font-sans);
  font-size: 0.9rem;
  font-weight: 400;
  color: var(--mk-mute);
}
.mk-auth-foot a { color: var(--mk-ink); }
