/* ==========================================================================
   @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, REWRITTEN 2026-10-04 as the
     "clear ledger" world (plans/92-public-site.md), replacing the cloth world.
     Not in plans/02-design-system.md.

     These are SEPARATE tokens, not aliases of the app's. Section 3 reassigns
     them for dark mode exactly as it reassigns the --app- ones, and the
     public site follows the same theme toggle as the application.

     TWO FAMILIES OF TOKEN, AND THE DIFFERENCE IS THE POINT.
       bg / bg-alt / surface / ink / ink-soft / mute / quiet / line / field-line
       accent / accent-soft / accent-ink / bad / shade
         ROLES. They swap in the dark: ink is the strong colour, bg is the
         ground. A rule that means "text on the page" uses these.
       deep / deep-2 / deep-ink / deep-mute / deep-line / deep-accent
         A SURFACE THAT IS DARK IN BOTH THEMES: the "how it works" field, the
         closing band and the sign-in panel. They do NOT swap, they are
         re-tuned (a shade lighter in the dark theme, so a deep field still
         reads as a field against a near-black page). Reported 2026-10-03: the
         sign-in panel and the landing's dark bands were built from ink/paper,
         so in the dark theme they turned pale grey while their text stayed
         white. A band that is meant to be dark must never be built from a
         role that flips.

     ONE ACCENT. Teal means "matched" and "paid" and nothing else: the tick, the
     Paid pill, a focus ring, the figure that arrives. It is never a
     decoration.

     Section 2.9d of the design system forbids a colour literal below section
     3, which is why every one lives here rather than next to its rule.
     ---------------------------------------------------------------------- */
  --mk-bg:          #ffffff;   /* the page                                   */
  --mk-bg-alt:      #f6f8f8;   /* a quiet band; the stage behind the product */
  --mk-surface:     #ffffff;   /* a window or card lying on bg or bg-alt     */
  --mk-ink:         #0f1c1b;   /* text, and the dark button                  */
  --mk-ink-soft:    #26403d;   /* the dark button on hover                   */
  --mk-mute:        #465654;   /* secondary prose                            */
  --mk-quiet:       #5b6c6a;   /* tertiary: captions, fine print             */
  --mk-line:        #dfe6e5;   /* every hairline                             */
  --mk-line-strong: #c5d0cf;   /* an outline you should notice               */
  --mk-field-line:  #82928f;   /* an input's boundary, 3:1 against its page  */
  --mk-shade:       #0f1c1b;   /* what a shadow is made of                   */

  --mk-accent:      #0f766e;   /* 5.5:1 on bg                                */
  --mk-on-accent:   #ffffff;
  --mk-accent-soft: #e3f3f0;   /* the Paid pill, the tick                    */
  --mk-accent-ink:  #0b5c56;   /* 6.9:1 on accent-soft                       */
  --mk-bad:         #b42318;   /* an error, 6.5:1 on bg                      */

  --mk-deep:        #06302d;
  --mk-deep-2:      #0c403b;   /* a panel lying on the deep field            */
  --mk-deep-ink:    #f1f8f7;   /* 13.3:1 on deep                             */
  --mk-deep-mute:   #aecbc7;   /* 8.3:1 on deep, 6.7:1 on deep-2             */
  --mk-deep-line:   #1f5a54;
  --mk-deep-line-strong: #2d6e67;
  --mk-deep-accent: #5eead4;   /* 9.7:1 on deep                              */

  --mk-shadow-sm: 0 1px 2px color-mix(in srgb, var(--mk-shade) 7%, transparent);
  --mk-shadow-md: 0 1px 2px color-mix(in srgb, var(--mk-shade) 6%, transparent),
                  0 10px 24px -10px color-mix(in srgb, var(--mk-shade) 18%, transparent);
  --mk-shadow-lg: 0 2px 4px -1px color-mix(in srgb, var(--mk-shade) 6%, transparent),
                  0 28px 56px -20px color-mix(in srgb, var(--mk-shade) 26%, transparent);

  --mk-radius:     0.625rem;
  --mk-radius-lg:  1rem;
  --mk-radius-xl:  1.5rem;
  --mk-wrap:       72rem;
  --mk-font-head:  "Manrope", var(--app-font-sans);
}

/* ==========================================================================
   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);

    /* ---- THE PUBLIC SITE IN THE DARK -----------------------------------
       The roles swap (ink becomes near-white, bg becomes night teal-black).
       The deep fields do not swap: they stay deep teal and get a shade lighter
       so a field still reads against the page. The accent is brightened for
       the dark ground. See section 2.14.

       The checkout (.co, section 12) is deliberately NOT here. It is a page a
       stranger sees once, on a phone, and it stays light in both themes. */
    --mk-bg:          #0a1110;
    --mk-bg-alt:      #0f1918;
    --mk-surface:     #121e1d;
    --mk-ink:         #ecf4f3;
    --mk-ink-soft:    #cfe0de;
    --mk-mute:        #a3b8b5;
    --mk-quiet:       #8ea5a2;
    --mk-line:        #1f2e2d;
    --mk-line-strong: #2f4543;
    --mk-field-line:  #5d7673;
    --mk-shade:       #000000;

    --mk-accent:      #2dd4bf;
    --mk-on-accent:   #04201d;
    --mk-accent-soft: #0f2f2c;
    --mk-accent-ink:  #7be9da;
    --mk-bad:         #ff8a80;

    --mk-deep:        #0b3733;
    --mk-deep-2:      #114743;
    --mk-deep-ink:    #f1f8f7;
    --mk-deep-mute:   #aecbc7;
    --mk-deep-line:   #1f5a54;
    --mk-deep-line-strong: #2d6e67;
    --mk-deep-accent: #5eead4;
  }
}

/* ---- 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);

    /* ---- THE PUBLIC SITE IN THE DARK -----------------------------------
       The roles swap (ink becomes near-white, bg becomes night teal-black).
       The deep fields do not swap: they stay deep teal and get a shade lighter
       so a field still reads against the page. The accent is brightened for
       the dark ground. See section 2.14.

       The checkout (.co, section 12) is deliberately NOT here. It is a page a
       stranger sees once, on a phone, and it stays light in both themes. */
    --mk-bg:          #0a1110;
    --mk-bg-alt:      #0f1918;
    --mk-surface:     #121e1d;
    --mk-ink:         #ecf4f3;
    --mk-ink-soft:    #cfe0de;
    --mk-mute:        #a3b8b5;
    --mk-quiet:       #8ea5a2;
    --mk-line:        #1f2e2d;
    --mk-line-strong: #2f4543;
    --mk-field-line:  #5d7673;
    --mk-shade:       #000000;

    --mk-accent:      #2dd4bf;
    --mk-on-accent:   #04201d;
    --mk-accent-soft: #0f2f2c;
    --mk-accent-ink:  #7be9da;
    --mk-bad:         #ff8a80;

    --mk-deep:        #0b3733;
    --mk-deep-2:      #114743;
    --mk-deep-ink:    #f1f8f7;
    --mk-deep-mute:   #aecbc7;
    --mk-deep-line:   #1f5a54;
    --mk-deep-line-strong: #2d6e67;
    --mk-deep-accent: #5eead4;
}

/* ---- 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; }

/* ☠️ AN ANCHOR THAT IS A BUTTON IS NOT A LINK, and the rule above does not know
   that. Its specificity is (0,1,1) — one element, one pseudo-class — which BEATS
   a single class like `.mk-btn-dark` at (0,1,0). So every `<a class="mk-btn">`
   took `--app-brand-strong` on hover, and on the light side that token is
   var(--app-n-950): a near-black word on a near-black button. Reported as "hover
   on the black button with white text makes the text black and making it
   invisible", and the underline the same rule adds was the other half of it.

   The .app-btn family never showed this because every one of its hover rules
   re-declares `color` and `text-decoration`. This says the same thing once, for
   both families, so a new button variant cannot forget it. `inherit` is
   deliberately not used: it would take the colour of whatever the button sits
   in, and a button's colour is its own. */
a.app-btn:hover,
a.mk-btn:hover { text-decoration: none; }

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: var(--app-s-4);
  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);
  transform: translateY(-6rem);
  transition: transform var(--app-fast) var(--app-ease);
}
.app-skip:focus { transform: none; 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;
}
/* ☠️ THE TILE IS A FALLBACK, NOT A FRAME. .app-side-logo paints a near-black
   rounded square so the one-letter mark below has something to sit on. When a
   real logo is uploaded that square became a black card behind it — reported as
   "the logo of the platform is in black background. Should be transparent to
   respect whatever logo color with any background" — and `cover` cropped the
   logo to a square on top of that.
   So the tile stands down whenever it is holding an image: no background, no
   rounding to fight, and `contain` so the whole mark is visible whatever shape
   the file is. */
/* A MODIFIER THE TEMPLATE SETS, not :has(). layout.html already branches on
   platformHasLogo, so the fact is known at render time and does not need to be
   inferred from the DOM — and this works in every browser that reaches this
   application rather than only the ones with :has(). */
.app-side-logo-img { background: none; border-radius: 0; }
.app-side-logo img { width: 100%; height: 100%; object-fit: contain; 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);
}
/* On a phone the actions drop under the subtitle. Right-aligned they read as stray text
   ("Back to items" alone at the far edge); left-aligned they read as the page's controls. */
@media (max-width: 40rem) {
  .app-page-head > .app-page-actions,
  .app-page-head > .app-btn { margin-left: 0; }
}

/* ==========================================================================
   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;
}

/* A button the one-submit guard in layout.html has just taken. It reads as
   WORKING rather than as broken: the same dimming a disabled button gets says
   "you may not", and what this one means is "already going". The cursor is the
   only difference that matters — nothing here moves, because a payment button
   that starts animating at the moment of payment is a payment button somebody
   presses again. */
.app-btn[aria-busy="true"] {
  opacity: 0.65;
  cursor: progress;
}

.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);
}
/* A button that belongs on the same row as the fields it submits (a filter bar):
   it sits on the fields' baseline instead of floating at the top of its cell. */
.app-field-row > .app-btn-row { align-self: end; }

.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. */
/* A file chooser in the app's own button, not the browser's grey one. */
input[type="file"]::file-selector-button {
  margin-right: var(--app-s-3);
  padding: var(--app-s-1) 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: inherit;
  font-weight: var(--app-w-medium);
  cursor: pointer;
}
input[type="file"]::file-selector-button:hover { background: var(--app-surface-3); }

.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);
}

/* margin-block, like .app-card and .app-stats above. Without it a grid of cards
   sat flush against whatever paragraph introduced it — measured on /shop, where
   "Issuing invoices is never metered" touched the top of the first product card
   and read as that card's own subtitle rather than as the page's line. */
.app-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  gap: var(--app-s-5);
  margin-block: var(--app-s-4);
}

/* TWO PANELS SIDE BY SIDE, one narrower screen and they stack. Used by
   /settings/branding (the form, and what it changes) and /admin/platform, both
   of which asked for it in markup before any rule existed — so both rendered as
   one full-width column and the second panel read as a separate page section.
   auto-fit rather than a hard `1fr 1fr` so the break happens on content width
   and needs no media query. */
.app-grid-2 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: var(--app-s-5);
  align-items: start;
  margin-block: var(--app-s-4);
}
/* The gap owns the spacing between the panels, so the cards inside stop adding
   their own — the same correction .app-card-grid makes below. */
.app-grid-2 > .app-card { margin-block: 0; }

/* ---- 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,
.app-table tbody tr.app-status-paid { 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 has no box of its own: the card is the box. Without this the
   wrap drew a second bordered, rounded rectangle inset inside the first. The wrap
   keeps its overflow-x, so a wide table still scrolls, and its bottom corners are
   clipped to the card's radius. */
.app-card > .app-table-wrap,
.app-card-body > .app-table-wrap:only-child {
  margin: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
}
.app-card > .app-table-wrap:last-child { border-radius: 0 0 var(--app-r-16) var(--app-r-16); }
/* A body follows the table in the same card: a hairline says where the table ends, since its
   last row carries no border of its own. (A footer brings its own top border.) */
.app-card > .app-table-wrap:has(+ .app-card-body) { border-bottom: 1px solid var(--app-line); }
.app-card-body:has(> .app-table-wrap:only-child) { padding: 0; }
.app-card > .app-card-body:last-child > .app-table-wrap:only-child { border-radius: 0 0 var(--app-r-16) var(--app-r-16); }
.app-card > .app-table-wrap .app-table-head th:first-child,
.app-card-body > .app-table-wrap .app-table-head th:first-child { border-top-left-radius: 0; }
.app-card > .app-table-wrap .app-table-head th:last-child,
.app-card-body > .app-table-wrap .app-table-head th:last-child { border-top-right-radius: 0; }

/* 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; }

/* A table of a few short columns (three figures a row) that must stay on a phone
   rather than scroll its numbers out of sight. */
.app-table-fit { min-width: 0; }
@media (max-width: 40rem) {
  .app-card .app-table.app-table-fit th, .app-card .app-table.app-table-fit td { padding-inline: var(--app-s-2); font-size: var(--app-fs-sm); }
}

.app-nowrap { white-space: nowrap; }

/* A list a merchant reads on a phone (plans/87 ruling 3). The table keeps its markup;
   below 40rem each row becomes a block of label / value pairs, so Total and Status are
   on the screen instead of a swipe away. The header row stays for screen readers. */
@media (max-width: 40rem) {
  .app-table.app-table-stack { min-width: 0; display: block; }
  .app-table-stack thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .app-table-stack tbody { display: block; }
  .app-table-stack tbody tr { display: block; padding: var(--app-s-4); }
  .app-table.app-table-stack td,
  .app-card .app-table.app-table-stack td:first-child,
  .app-card .app-table.app-table-stack td:last-child {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: baseline;
    gap: 0 var(--app-s-4);
    height: auto;
    padding: var(--app-s-1) 0;
    text-align: right;
  }
  /* The small line under a value (a reference, an email) goes on its own line, under it. */
  .app-table-stack td > .app-table-meta { flex: 1 1 100%; }
  /* A cell with nothing in it (no town on file) is not a label with a blank beside it. */
  .app-table-stack td:empty { display: none; }
  .app-table-stack td[data-label]::before {
    content: attr(data-label);
    flex: none;
    text-align: left;
    font-size: var(--app-fs-sm);
    color: var(--app-ink-muted);
  }
  .app-table.app-table-stack td.app-cell-title,
  .app-card .app-table.app-table-stack td.app-cell-title:first-child {
    display: block;
    padding-bottom: var(--app-s-2);
    font-size: var(--app-fs-md);
    font-weight: var(--app-w-semibold);
    text-align: left;
  }
  .app-table.app-table-stack td.app-cell-action,
  .app-card .app-table.app-table-stack td.app-cell-action:last-child {
    justify-content: flex-start;
    flex-wrap: wrap;
    padding-top: var(--app-s-3);
  }
  .app-table-stack td.app-col-secondary { display: none; }
}

.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 GENERIC GOOD/BAD PILL, added 2026-09-02 for the webhook delivery log.
   The .app-status-* pills below all name an INVOICE state, and a delivery is
   not an invoice — borrowing `app-status-paid` to mean "accepted" would be the
   kind of reuse that reads fine today and lies the moment either meaning
   moves. These set the same two custom properties .app-pill already reads, so
   they inherit its shape and only choose its colour. */
.app-pill-ok  { --app-st-fill: var(--app-ok-fill);  --app-st-ink: var(--app-ok-ink); }
.app-pill-bad { --app-st-fill: var(--app-bad-fill); --app-st-ink: var(--app-bad-ink); }

/* 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);
}
/* The tone is a dot, not a coloured slab down one side (plans/87 ruling 4). */
.app-flash:not(.co-testbanner)::before {
  content: "";
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  margin-top: 0.5rem;
  border-radius: 50%;
  background: 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);
}
/* A divider separates two things. A stack that ends on one — the shape a
   `{{ for }}` loop produces when it writes a rule after every item — is a line
   ruled under nothing, hard against the card's bottom padding. */
.app-stack > .app-divider:last-child { display: none; }

/* The gap above owns the spacing inside a stack, so the blocks that carry
   their own margin-block stop adding it on top — 16px of gap plus 16px of
   margin is 32px, and only one of the two was asked for. */
.app-stack > .app-card,
.app-stack > .app-card-grid,
.app-stack > .app-grid-2,
.app-stack > .app-stats,
.app-stack > .app-table-wrap { margin-block: 0; }

/* A BULLETED LIST THAT IS PROSE, not navigation. The reset in section 4 zeroes
   `ul` padding for the nav lists, which left the one list in the application
   that is actually body copy — /settings/branding, "where it shows up" — with
   its markers under the card's own padding and its lines run together. The
   indent and the leading are put back here, once.
   NOT display:flex. A flex item is not a list item, and the markers vanish. */
.app-list {
  margin: 0;
  padding-left: var(--app-s-5);
  font-size: var(--app-fs-base);
  line-height: var(--app-lh-normal);
  color: var(--app-ink-muted);
}
.app-list > li + li { margin-top: var(--app-s-2); }
.app-list > li::marker { color: var(--app-ink-faint); }

.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); }
/* A list of steps or facts that wants a rule between its rows. */
.app-dl-rows { row-gap: 0; }
.app-dl-rows dt,
.app-dl-rows dd {
  display: flex;
  align-items: center;
  gap: var(--app-s-3);
  padding: var(--app-s-3) 0;
  border-top: 1px solid var(--app-line);
}
.app-dl-rows dt:first-of-type,
.app-dl-rows dd:first-of-type { border-top: 0; }

/* 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);
}
.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: A CLEAR LEDGER

   Everything a signed-out visitor sees: the landing page, sign in, sign up,
   forgot, reset and confirm. Not in plans/02-design-system.md, which describes
   the signed-in application only; the rulings live in plans/92-public-site.md.

   REWRITTEN 2026-10-04, replacing the 2026-10-03 "cloth" world wholesale
   ("I feel like it went more uglier. I am looking for a trustworthy look that
   provides premium trust and ease of using feeling."). Cream paper, an indigo
   weave and a striped selvedge are gone. The standing bar is PRODUCT.md's:
   Stripe-grade finish, Wise's plain money language, Mercury's banker-calm.

   THE WORLD. A statement, read in daylight: a white page ruled with hairlines,
   one ink, one teal that means "this is matched, this is paid", and a single
   deep-teal field that appears where the page makes its two promises (how the
   money moves, and the call to act). Figures are tabular. Depth is a hairline
   and a soft two-layer shadow, never a glow, a texture or a stripe.

   FOUR RULES HOLD THIS SECTION TOGETHER.

   1. Every colour is a var() from section 2.14. Section 2.9d forbids a colour
      literal below section 3, and a new shade means a new token up there.

   2. Nothing in here reads an --app- token, and NO RULE IN THIS SECTION
      MENTIONS DARK MODE. Section 3 reassigns the --mk- tokens, so every rule
      below follows the theme without being touched.

   3. A DEEP FIELD IS BUILT FROM --mk-deep*, NEVER FROM --mk-ink / --mk-bg.
      Ink and bg swap in the dark theme; deep does not. The 2026-10-03 version
      built its dark bands and the sign-in panel from ink with a hardcoded
      white label, so in the dark theme they turned pale grey with white text
      on them (lint L33 is the guard). If a surface must stay dark, it takes a
      deep token.

   4. NO HEX, NO rgba(). A tint is color-mix() over a token.
   ========================================================================== */

.mk-page {
  background: var(--mk-bg);
  color: var(--mk-ink);
  font-family: var(--app-font-sans);
  -webkit-font-smoothing: antialiased;
  accent-color: var(--mk-accent);
  caret-color: var(--mk-accent);
}
/* The parts the page did not draw. The scrollbar belongs to the root, so the
   rule reaches it through :has(); the selection is the teal tint. */
:root:has(.mk-page) { scrollbar-color: var(--mk-line-strong) var(--mk-bg); }
.mk-page ::selection { background: var(--mk-accent-soft); color: var(--mk-ink); }
.mk-page a { text-underline-offset: 0.2em; }

.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-bg);
  padding: 0.6rem 1rem;
  border-radius: var(--mk-radius);
}

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

/* Screen-reader-only text. The pricing ticks say "included" in a shape and a
   colour; this says it in words. */
.mk-sr {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The glyphs from the icon component carry app-nav-icon's size; on this site
   they are sized by whoever places them. */
.mk-page svg.app-nav-icon { width: 1em; height: 1em; flex: none; }

/* ---- 19.1 The navigation -------------------------------------------------- */

.mk-nav-wrap {
  position: sticky;
  top: 0;
  z-index: var(--app-z-topbar);
  background: var(--mk-bg);
  border-bottom: 1px solid var(--mk-line);
}

.mk-nav {
  max-width: var(--mk-wrap);
  margin-inline: auto;
  display: flex;
  align-items: center;
  gap: 1rem;
  height: 4rem;
  padding-inline: 1.5rem;
}

.mk-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  text-decoration: none;
  color: var(--mk-ink);
}
.mk-mark { display: block; flex: none; color: var(--mk-ink); }
/* ☠️ AN UPLOADED LOGO IS WHATEVER SIZE ITS FILE IS. The reset's
   `img { max-width: 100% }` bounds it to the container's width and nothing
   bounds its HEIGHT, so a 1024px upload once made the header 1024px tall.
   Bounded on BOTH axes, and `contain` rather than `cover`: a logo is a drawing
   with edges that mean something, and cropping a wordmark cuts the word in
   half. The aspect ratio is the file's own. */
.mk-mark-img {
  display: block;
  flex: none;
  width: auto;
  height: 30px;
  max-width: 9rem;
  object-fit: contain;
}

.mk-brand-word {
  font-family: var(--mk-font-head);
  font-weight: 700;
  font-size: 1rem;
  letter-spacing: -0.015em;
}

.mk-nav-links {
  display: flex;
  gap: 0.25rem;
  margin-inline: auto;
}
.mk-nav-links a {
  color: var(--mk-mute);
  text-decoration: none;
  font-size: 0.925rem;
  font-weight: 500;
  padding: 0.45rem 0.8rem;
  border-radius: var(--mk-radius);
  transition: background var(--app-fast) var(--app-ease), color var(--app-fast) var(--app-ease);
}
.mk-nav-links a:hover { color: var(--mk-ink); background: var(--mk-bg-alt); }

.mk-nav-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.mk-link-quiet {
  color: var(--mk-ink);
  text-decoration: none;
  font-size: 0.925rem;
  font-weight: 500;
  padding: 0.45rem 0.8rem;
  border-radius: var(--mk-radius);
}
.mk-link-quiet:hover { background: var(--mk-bg-alt); }

/* The theme toggle in the public navbar. A <button>, so none of the anchor
   rules above reach it. */
.mk-icon-btn {
  display: inline-grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 0;
  border-radius: var(--mk-radius);
  background: transparent;
  color: var(--mk-mute);
  cursor: pointer;
  transition: background var(--app-fast) var(--app-ease), color var(--app-fast) var(--app-ease);
}
.mk-icon-btn:hover { background: var(--mk-bg-alt); color: var(--mk-ink); }
.mk-icon-btn svg { width: 1.05rem; height: 1.05rem; }

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

.mk-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 2.5rem;
  padding: 0.55rem 1.15rem;
  border-radius: var(--mk-radius);
  border: 1px solid transparent;
  font: inherit;
  font-weight: 600;
  font-size: 0.925rem;
  line-height: 1.2;
  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),
              box-shadow var(--app-fast) var(--app-ease);
}
.mk-btn-dark {
  background: var(--mk-ink);
  color: var(--mk-bg);
  box-shadow: 0 1px 2px color-mix(in srgb, var(--mk-ink) 22%, transparent);
}
/* `color` is repeated on purpose. Without it the reset's a:hover — (0,1,1),
   which outranks this single class — repaints the label the wrong colour. */
.mk-btn-dark:hover { background: var(--mk-ink-soft); color: var(--mk-bg); }
.mk-btn-ghost {
  background: var(--mk-bg);
  border-color: var(--mk-line-strong);
  color: var(--mk-ink);
}
.mk-btn-ghost:hover { background: var(--mk-bg-alt); color: var(--mk-ink); }
/* On a deep field: the light button, and the outlined one. */
.mk-btn-light { background: var(--mk-deep-ink); color: var(--mk-deep); }
.mk-btn-light:hover { background: var(--mk-deep-accent); color: var(--mk-deep); }
.mk-btn-line { background: transparent; border-color: var(--mk-deep-line-strong); color: var(--mk-deep-ink); }
.mk-btn-line:hover { background: var(--mk-deep-2); color: var(--mk-deep-ink); }
.mk-btn-lg { min-height: 3rem; padding: 0.7rem 1.5rem; 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-accent);
  outline-offset: 2px;
}
.mk-deep a:focus-visible,
.mk-authpanel-aside a:focus-visible {
  outline-color: var(--mk-deep-accent);
}

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

/* ☠️ THE FLUID TERM IS min(vw, vh), AND THE vh HALF IS THE WHOLE FIX.
   Reported 2026-09-03 against the old wordmark: "the landing page looks too
   zoomed in when viewed from a 13 inch screen." Sized on width alone, a 13"
   laptop (1440x812) and a 27" monitor (2560x1440) rendered the same size in
   a viewport 40% shorter, and the product fell off the bottom of the screen
   on the machine most visitors use. Height is the binding constraint on a
   laptop. Every size here that drives vertical layout carries the pairing. */
.mk-display {
  font-family: var(--mk-font-head);
  font-weight: 700;
  font-size: clamp(2.25rem, min(4.1vw, 6.6vh), 3.75rem);
  line-height: 1.06;
  letter-spacing: -0.035em;
  text-wrap: balance;
  margin: 0;
}

.mk-h2 {
  font-family: var(--mk-font-head);
  font-weight: 700;
  font-size: clamp(1.75rem, min(3vw, 5.2vh), 2.5rem);
  line-height: 1.12;
  letter-spacing: -0.03em;
  text-wrap: balance;
  margin: 0 0 1rem;
}

.mk-lede {
  max-width: 34rem;
  margin: 1.25rem 0 0;
  font-size: clamp(1.0625rem, min(1.3vw, 8vh), 1.2rem);
  line-height: 1.6;
  color: var(--mk-mute);
  text-wrap: pretty;
}

.mk-section-lede {
  max-width: 38rem;
  color: var(--mk-mute);
  font-size: 1.075rem;
  line-height: 1.6;
  margin: 0 0 clamp(2rem, min(4vw, 8vh), 3rem);
  text-wrap: pretty;
}

.mk-fineprint { color: var(--mk-quiet); font-size: 0.85rem; margin-top: 1.25rem; }
.mk-num { font-variant-numeric: tabular-nums; }

/* A figure marked as an illustration, wherever it could be mistaken for data. */
.mk-sample {
  display: inline-block;
  padding: 0.1rem 0.5rem;
  border: 1px solid var(--mk-line-strong);
  border-radius: 99px;
  color: var(--mk-quiet);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 1.5;
}

/* ---- 19.4 Sections and fields --------------------------------------------- */

.mk-section { padding: clamp(3.5rem, min(7vw, 9vh), 6rem) 0; }
.mk-alt { background: var(--mk-bg-alt); border-block: 1px solid var(--mk-line); }

/* THE NAVIGATION IS STICKY, SO AN ANCHOR MUST STOP SHORT OF IT. Without this a
   linked section arrives with its heading under the bar. */
.mk-section[id] { scroll-margin-top: 4rem; }

/* The deep field. It does not swap with the theme (rule 3). */
.mk-deep { background: var(--mk-deep); color: var(--mk-deep-ink); }
.mk-deep .mk-h2,
.mk-deep .mk-display { color: var(--mk-deep-ink); }
.mk-deep .mk-section-lede,
.mk-deep .mk-lede { color: var(--mk-deep-mute); }

.mk-cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 2rem;
}

/* ---- 19.5 The hero -------------------------------------------------------- */

.mk-hero { padding: clamp(2rem, min(5vw, 6.5vh), 4.5rem) 0 clamp(2.5rem, min(5vw, 6vh), 4rem); }

.mk-hero-grid {
  display: grid;
  gap: clamp(2.5rem, min(5vw, 8vh), 4.5rem);
  align-items: center;
}
@media (min-width: 62rem) {
  .mk-hero-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.02fr); }
}

.mk-assure {
  list-style: none;
  margin: 1.5rem 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  color: var(--mk-mute);
  font-size: 0.9rem;
}
.mk-assure li { display: inline-flex; align-items: center; gap: 0.45rem; }
.mk-assure svg { color: var(--mk-accent); font-size: 1rem; stroke-width: 2; }

/* The three plain promises under the hero: a hairline, three columns of text.
   No icons, no cards — they are captions to the product above them. */
.mk-notes {
  display: grid;
  margin-top: clamp(2.5rem, min(5vw, 8vh), 4rem);
  border-top: 1px solid var(--mk-line);
}
.mk-note { padding: 1.5rem 0 0; }
.mk-note-title { margin: 0 0 0.35rem; font-weight: 650; font-size: 0.975rem; }
.mk-note-body { margin: 0; color: var(--mk-mute); font-size: 0.925rem; line-height: 1.55; max-width: 26rem; }
@media (min-width: 48rem) {
  .mk-notes { grid-template-columns: repeat(3, 1fr); column-gap: 2.5rem; }
}

/* ---- 19.6 The statement window -----------------------------------------------
   THE HERO'S ONE SIGNATURE. One invoice followed from "issued" to "settled to
   your account", with the fee on its own line beside the gross. Drawn in CSS:
   no image file, nothing for the strict CSP to fetch. Its figures are made up
   and the window says so (Sample).

   Beside it, on a smaller slip, is what the customer sees: the payer's half of
   the same sale. The two together are the product.

   WITHOUT JAVASCRIPT it is the finished picture. The unfinished state exists
   only under .mk-anim, which the script in layout.html adds to <html>. */

.mk-stage {
  position: relative;
  padding: clamp(1.25rem, min(3vw, 8vh), 2rem);
  /* Room under the window for the customer's slip, which overlaps only the
     window's own bottom padding, never a row. */
  padding-bottom: 8.5rem;
  border-radius: var(--mk-radius-xl);
  background: var(--mk-bg-alt);
  border: 1px solid var(--mk-line);
}

.mk-window {
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius-lg);
  background: var(--mk-surface);
  box-shadow: var(--mk-shadow-lg);
  overflow: hidden;
}
.mk-window-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.7rem 1.25rem;
  border-bottom: 1px solid var(--mk-line);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--mk-mute);
}
.mk-window-body { padding: 1.25rem; }

.mk-win-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; }
.mk-win-label { margin: 0; font-size: 0.8rem; color: var(--mk-quiet); }
.mk-win-ref { margin: 0.15rem 0 0; font-family: var(--app-font-mono); font-size: 0.8rem; font-weight: 600; color: var(--mk-mute); }
.mk-win-total {
  display: flex;
  align-items: baseline;
  gap: 0.3rem;
  margin: 0.7rem 0 0;
  font-family: var(--mk-font-head);
  font-weight: 700;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}
.mk-win-cur { font-size: 1rem; color: var(--mk-quiet); font-weight: 600; }
.mk-win-amount { font-size: clamp(2rem, min(3.4vw, 5.4vh), 2.75rem); line-height: 1; }
.mk-win-dec { margin-left: -0.2rem; font-size: 1.1rem; color: var(--mk-quiet); }

.mk-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.2rem 0.6rem;
  border-radius: 99px;
  font-size: 0.75rem;
  font-weight: 650;
  line-height: 1.4;
  white-space: nowrap;
}
.mk-pill svg { font-size: 0.85rem; stroke-width: 2.4; }
.mk-pill-ok { background: var(--mk-accent-soft); color: var(--mk-accent-ink); }

/* The trail: four lines, a rule on the left that the checks sit on. */
.mk-trail {
  list-style: none;
  margin: 1.25rem 0 0;
  padding: 0;
  border-top: 1px solid var(--mk-line);
}
.mk-trail-row {
  position: relative;
  display: grid;
  grid-template-columns: 1.5rem 1fr auto;
  align-items: center;
  gap: 0.75rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--mk-line);
  font-size: 0.9rem;
}
.mk-trail-row:last-child { border-bottom: 0; padding-bottom: 0.1rem; }
.mk-trail-dot {
  display: grid;
  place-items: center;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 50%;
  background: var(--mk-accent-soft);
  color: var(--mk-accent-ink);
}
.mk-trail-dot svg { font-size: 0.8rem; stroke-width: 2.6; }
.mk-trail-what { color: var(--mk-mute); }
.mk-trail-fig { font-variant-numeric: tabular-nums; color: var(--mk-ink); font-weight: 500; }
.mk-trail-row-fee .mk-trail-what { color: var(--mk-quiet); }
.mk-trail-row-fee .mk-trail-fig { color: var(--mk-mute); }
.mk-trail-row-net .mk-trail-what { color: var(--mk-ink); font-weight: 650; }
.mk-trail-row-net .mk-trail-fig { font-weight: 700; }
.mk-trail-row-net .mk-trail-dot { background: var(--mk-accent); color: var(--mk-bg); }

/* What the customer sees. It overlaps the window's lower left corner. */
.mk-slip {
  position: absolute;
  left: clamp(-0.5rem, -1vw, 0rem);
  bottom: 1.1rem;
  width: min(17.5rem, 78%);
  padding: 0.9rem 1rem;
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius-lg);
  background: var(--mk-surface);
  box-shadow: var(--mk-shadow-lg);
}
.mk-slip-who { margin: 0; font-size: 0.75rem; color: var(--mk-quiet); }
.mk-slip-line { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; margin: 0.25rem 0 0.7rem; }
.mk-slip-title { margin: 0; font-weight: 650; font-size: 0.925rem; }
.mk-slip-amt { margin: 0; font-family: var(--mk-font-head); font-weight: 700; font-size: 1.05rem; font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
.mk-slip-btn {
  display: block;
  padding: 0.5rem 0.8rem;
  border-radius: var(--mk-radius);
  background: var(--mk-accent);
  color: var(--mk-on-accent);
  text-align: center;
  font-size: 0.85rem;
  font-weight: 650;
}

@media (min-width: 62rem) {
  .mk-stage { margin-right: -0.5rem; }
}

/* ---- 19.7 What it does ------------------------------------------------------ */

.mk-two { display: grid; gap: clamp(2rem, min(5vw, 8vh), 4rem); align-items: start; }
@media (min-width: 62rem) {
  .mk-two { grid-template-columns: minmax(0, 1fr) minmax(0, 0.9fr); }
  /* The words are taller than the card beside them; the card follows them down. */
  .mk-two > .mk-plate { position: sticky; top: 6rem; }
}
.mk-copy p { color: var(--mk-mute); font-size: 1.05rem; line-height: 1.65; max-width: 34rem; margin: 0 0 1rem; text-wrap: pretty; }

/* The fee, as a figure beside the facts that govern it. */
.mk-plate {
  padding: 1.5rem;
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius-lg);
  background: var(--mk-surface);
  box-shadow: var(--mk-shadow-sm);
}
.mk-plate-row { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; }
.mk-plate-label { font-weight: 650; font-size: 1.05rem; }
.mk-plate-figure {
  font-family: var(--mk-font-head);
  font-weight: 700;
  font-size: 1.75rem;
  letter-spacing: -0.03em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--mk-accent);
}
.mk-plate-sub { margin: 0.75rem 0 0; color: var(--mk-mute); font-size: 0.925rem; line-height: 1.55; }
.mk-plate-sample {
  margin-top: 1.25rem;
  padding: 0.9rem 1rem 0.5rem;
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius);
  background: var(--mk-bg-alt);
}
.mk-plate-cap { display: flex; align-items: center; gap: 0.6rem; margin: 0 0 0.25rem; font-size: 0.8rem; color: var(--mk-quiet); }
/* The three standing facts, ruled, under the words they qualify. */
.mk-facts { max-width: 34rem; margin-top: 1.75rem; border-top: 1px solid var(--mk-line); }
.mk-facts .mk-ledger-line:last-child { border-bottom: 1px solid var(--mk-line); padding-bottom: 0.7rem; }
.mk-ledger-line {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin: 0;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--mk-line);
  font-size: 0.925rem;
  color: var(--mk-mute);
}
.mk-ledger-line:last-child { border-bottom: 0; padding-bottom: 0; }
.mk-ledger-line strong { margin-left: auto; color: var(--mk-ink); font-weight: 650; }
.mk-ledger-net { color: var(--mk-ink); font-weight: 650; }
.mk-ledger-net strong { font-weight: 700; }

/* The four parts of the product: text on hairlines, two columns. */
.mk-bolts {
  list-style: none;
  margin: clamp(2.5rem, min(5vw, 8vh), 4rem) 0 0;
  padding: 0;
  display: grid;
  border-top: 1px solid var(--mk-line);
}
.mk-bolt { padding: 1.5rem 0; border-bottom: 1px solid var(--mk-line); }
.mk-bolt h3 { margin: 0 0 0.4rem; font-family: var(--mk-font-head); font-size: 1.1rem; font-weight: 700; letter-spacing: -0.02em; }
.mk-bolt p { margin: 0; color: var(--mk-mute); font-size: 0.95rem; line-height: 1.6; max-width: 30rem; }
@media (min-width: 48rem) {
  .mk-bolts { grid-template-columns: 1fr 1fr; column-gap: 3rem; }
}

/* ---- 19.8 How the money moves ------------------------------------------------
   Four stops on a line, on the deep field. The line is what the section says:
   one path, nothing off it. */

.mk-flow {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2rem;
}
.mk-flow-step { position: relative; padding-left: 2.25rem; display: flex; flex-direction: column; }
.mk-flow-node {
  position: absolute;
  left: 0;
  top: 0.25rem;
  width: 1.1rem;
  height: 1.1rem;
  border-radius: 50%;
  border: 2px solid var(--mk-deep-accent);
  background: var(--mk-deep);
}
.mk-flow-step::before {
  content: "";
  position: absolute;
  left: calc(0.55rem - 1px);
  top: 1.6rem;
  bottom: -1.8rem;
  width: 2px;
  background: var(--mk-deep-line-strong);
}
.mk-flow-step:last-child::before { display: none; }
.mk-flow-step h3 { margin: 0 0 0.4rem; font-family: var(--mk-font-head); font-size: 1.1rem; font-weight: 700; letter-spacing: -0.02em; color: var(--mk-deep-ink); }
.mk-flow-step p { margin: 0; color: var(--mk-deep-mute); font-size: 0.95rem; line-height: 1.6; }

@media (min-width: 62rem) {
  .mk-flow { grid-template-columns: repeat(4, 1fr); gap: 2rem; }
  .mk-flow-step { padding-left: 0; padding-top: 2.5rem; }
  .mk-flow-node { top: 0; }
  .mk-flow-step::before {
    left: 1.1rem;
    right: -2rem;
    top: calc(0.55rem - 1px);
    bottom: auto;
    width: auto;
    height: 2px;
  }
}

.mk-held {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  margin: 0 0 clamp(2.5rem, min(5vw, 8vh), 3.5rem);
  padding: 1.25rem 1.5rem;
  border: 1px solid var(--mk-deep-line-strong);
  border-radius: var(--mk-radius-lg);
  background: var(--mk-deep-2);
  color: var(--mk-deep-ink);
  font-size: 1.05rem;
  line-height: 1.6;
}
.mk-held p { margin: 0; max-width: 56rem; }
.mk-held strong { color: var(--mk-deep-ink); font-weight: 650; }
.mk-held-icon {
  display: grid;
  place-items: center;
  flex: none;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  background: color-mix(in srgb, var(--mk-deep-accent) 16%, transparent);
  color: var(--mk-deep-accent);
  font-size: 1.25rem;
}
.mk-held-icon svg { stroke-width: 1.6; }
.mk-flow-step .mk-flow-fig {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.35rem 0.6rem;
  margin: auto 0 0;
  padding-top: 0.9rem;
  color: var(--mk-deep-ink);
  font-size: 0.95rem;
  font-weight: 600;
}
.mk-flow-step .mk-flow-fig .mk-flow-cap { color: var(--mk-deep-mute); font-size: 0.85rem; font-weight: 500; }
.mk-deep .mk-sample { border-color: var(--mk-deep-line-strong); color: var(--mk-deep-mute); }

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

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

.mk-price {
  display: flex;
  flex-direction: column;
  padding: 1.75rem;
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius-lg);
  background: var(--mk-surface);
}
/* Every plan carries the same weight. Nothing here says which one to pick: the
   page says the free plan does the job, so that is the filled button. */
.mk-price-name { margin: 0; font-weight: 650; font-size: 0.975rem; }
.mk-price-amount {
  margin: 0.9rem 0 0;
  font-family: var(--mk-font-head);
  font-weight: 700;
  font-size: 2.25rem;
  letter-spacing: -0.04em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.mk-price-interval { margin: 0.4rem 0 0; color: var(--mk-quiet); font-size: 0.85rem; }
.mk-price-desc { margin: 1rem 0 0; color: var(--mk-mute); font-size: 0.925rem; line-height: 1.55; }
/* Three lines, so the check rows start on one line across the row of cards. */
@media (min-width: 56rem) { .mk-price-desc { min-height: 4.65em; } }
.mk-price-list { list-style: none; margin: 1.25rem 0 1.75rem; padding: 1.25rem 0 0; border-top: 1px solid var(--mk-line); display: grid; gap: 0.7rem; }
.mk-price-list li { display: flex; gap: 0.7rem; align-items: flex-start; font-size: 0.9rem; line-height: 1.45; }
.mk-price-list .mk-no { color: var(--mk-quiet); }

/* The tick: drawn from two borders, so it is a shape and not a glyph. */
.mk-tick {
  position: relative;
  flex: none;
  width: 1.1rem;
  height: 1.1rem;
  margin-top: 0.1rem;
  border-radius: 50%;
}
.mk-yes .mk-tick { background: var(--mk-accent-soft); }
.mk-yes .mk-tick::after {
  content: "";
  position: absolute;
  left: 0.36rem;
  top: 0.2rem;
  width: 0.28rem;
  height: 0.5rem;
  border: solid var(--mk-accent-ink);
  border-width: 0 2px 2px 0;
  transform: rotate(40deg);
}
.mk-no .mk-tick::after {
  content: "";
  position: absolute;
  left: 0.25rem;
  top: 0.5rem;
  width: 0.6rem;
  height: 2px;
  background: var(--mk-line-strong);
}

/* ---- 19.10 How it is guarded ----------------------------------------------------- */

.mk-guard { display: grid; gap: 2rem; }
@media (min-width: 62rem) {
  .mk-guard { grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.4fr); gap: clamp(2rem, min(6vw, 8vh), 5rem); align-items: start; }
  .mk-guard-head { position: sticky; top: 6rem; }
}
.mk-guard-head .mk-section-lede { margin-bottom: 0; }
.mk-guard-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  border-top: 1px solid var(--mk-line);
}
.mk-guard-item { padding: 1.4rem 0; border-bottom: 1px solid var(--mk-line); }
.mk-guard-item h3 { margin: 0 0 0.4rem; font-family: var(--mk-font-head); font-size: 1.05rem; font-weight: 700; letter-spacing: -0.02em; }
.mk-guard-item p { margin: 0; color: var(--mk-mute); font-size: 0.925rem; line-height: 1.6; max-width: 38rem; }

/* ---- 19.11 The closing band ------------------------------------------------------- */

.mk-closing { padding: clamp(3.5rem, min(7vw, 9vh), 5.5rem) 0; }
.mk-closing-grid { display: grid; gap: 2rem; align-items: center; }
.mk-closing .mk-cta-row { margin-top: 0; }
.mk-closing .mk-section-lede { margin-bottom: 0; }
@media (min-width: 62rem) {
  .mk-closing-grid { grid-template-columns: minmax(0, 1fr) auto; }
}

/* ---- 19.12 The footer ----------------------------------------------------------------- */

.mk-footer { background: var(--mk-bg-alt); border-top: 1px solid var(--mk-line); color: var(--mk-mute); }
.mk-footer-inner { max-width: var(--mk-wrap); margin-inline: auto; padding: clamp(2.5rem, min(5vw, 8vh), 3.5rem) 1.5rem 1.5rem; }
.mk-footer-word { margin: 0; font-family: var(--mk-font-head); font-weight: 700; font-size: 1.15rem; letter-spacing: -0.02em; color: var(--mk-ink); }
.mk-footer-tag { margin: 0.35rem 0 0; font-size: 0.925rem; max-width: 28rem; }
.mk-footer-cols { display: grid; gap: 2rem; margin-top: 2.25rem; }
.mk-footer-cols a { display: block; color: var(--mk-mute); text-decoration: none; font-size: 0.925rem; padding: 0.2rem 0; }
.mk-footer-cols a:hover { color: var(--mk-ink); text-decoration: underline; }
.mk-footer-head { margin: 0 0 0.6rem; font-weight: 650; font-size: 0.85rem; color: var(--mk-ink); }
.mk-footer-note p { margin: 0 0 0.6rem; font-size: 0.9rem; line-height: 1.55; }
.mk-footer-note .mk-footer-small { font-size: 0.82rem; color: var(--mk-quiet); }
.mk-footer-rule { height: 1px; background: var(--mk-line); margin: 2rem 0 1.25rem; }
.mk-footer-base { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem 1.5rem; font-size: 0.8rem; color: var(--mk-quiet); }
@media (min-width: 48rem) { .mk-footer-cols { grid-template-columns: 1fr 1fr 2fr; gap: 3rem; } }

/* THE FURNITURE A SUPER ADMIN SUPPLIES (plans/93). Each piece is drawn only when
   its setting is non-empty, so none of these rules matches anything on a platform
   that has not filled them in. The contact line carries the support email and the
   support phone side by side; the foot carries who operates the platform and the
   two documents. */
.mk-footer-contact { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.1rem 0.9rem; }
.mk-footer-contact > span { font-weight: 650; color: var(--mk-mute); }
.mk-footer-note .mk-footer-contact a { font-size: inherit; }
.mk-footer-legal { display: flex; flex-wrap: wrap; gap: 0.1rem 1.25rem; }
.mk-footer-legal a { color: var(--mk-mute); text-decoration: none; padding: 0.5rem 0; }
.mk-footer-legal a:hover { color: var(--mk-ink); text-decoration: underline; }
@media (min-width: 48rem) { .mk-footer-legal { margin-left: auto; } }

/* ---- 19.12b Terms and privacy -----------------------------------------------------------
   /terms and /privacy: one reading column in the public shell, set under the nav
   edge like every other page here. The body is plain paragraphs, never markup. */

.mk-legal { padding: clamp(2rem, min(5vw, 8vh), 4rem) 0 clamp(3rem, min(7vw, 10vh), 5rem); }
.mk-legal-doc { max-width: 42rem; }
.mk-legal-back { margin: 0 0 clamp(1.25rem, min(3vw, 5vh), 2rem); font-size: 0.9rem; }
.mk-legal-back a { color: var(--mk-mute); font-weight: 600; text-decoration: none; }
.mk-legal-back a:hover { color: var(--mk-ink); text-decoration: underline; }
.mk-legal-title {
  margin: 0;
  font-family: var(--mk-font-head);
  font-weight: 700;
  font-size: clamp(1.75rem, min(3vw, 5.2vh), 2.5rem);
  line-height: 1.12;
  letter-spacing: -0.03em;
  text-wrap: balance;
}
.mk-legal-date { margin: 0.6rem 0 0; font-size: 0.875rem; color: var(--mk-quiet); }
.mk-legal-lede { margin: 0.85rem 0 0; color: var(--mk-mute); line-height: 1.6; text-wrap: pretty; }
.mk-legal-body { margin-top: clamp(1.5rem, min(3vw, 5vh), 2.25rem); padding-top: clamp(1.25rem, min(2.5vw, 4vh), 1.75rem); border-top: 1px solid var(--mk-line); }
.mk-legal-p { margin: 0 0 1.1rem; font-size: 1.025rem; line-height: 1.7; color: var(--mk-ink); text-wrap: pretty; overflow-wrap: anywhere; }

/* ---- 19.13 The signed-out forms ------------------------------------------
   Sign in, sign up, forgot and reset share this: the fields and the actions
   below. The split panel that frames them is section 14. */

/* A form is not boxed; a MESSAGE (check your email, link spent) is. */
.mk-auth-card:not(:has(> form)) {
  padding: 1.25rem 1.35rem;
  border: 1px solid var(--mk-line);
  border-radius: var(--mk-radius-lg);
  background: var(--mk-bg-alt);
}
.mk-auth-card p { margin: 0 0 0.5rem; }

.mk-field { margin-bottom: 1.15rem; }
.mk-label { display: block; font-size: 0.875rem; font-weight: 600; margin-bottom: 0.4rem; }
.mk-input {
  width: 100%;
  min-height: 2.9rem;
  padding: 0.65rem 0.9rem;
  border: 1px solid var(--mk-field-line);
  border-radius: var(--mk-radius);
  background: var(--mk-surface);
  color: var(--mk-ink);
  font: inherit;
  transition: border-color var(--app-fast) var(--app-ease), box-shadow var(--app-fast) var(--app-ease);
}
.mk-input:hover { border-color: var(--mk-ink); }
.mk-input:focus {
  outline: 2px solid var(--mk-accent);
  outline-offset: 0;
  border-color: var(--mk-accent);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--mk-accent) 16%, transparent);
}
.mk-input[readonly] { background: var(--mk-bg-alt); color: var(--mk-mute); }
.mk-input-invalid { border-color: var(--mk-bad); }
.mk-hint { margin: 0.65rem 0 0; font-size: 0.82rem; 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-bad); }
.mk-required { color: var(--mk-quiet); }

.mk-auth-actions { margin-top: 1.5rem; display: grid; gap: 0.75rem; }
.mk-auth-actions .mk-btn { min-height: 2.9rem; }
/* The sentence under the sign-up button (plans/93). A sentence, not a checkbox. */
.mk-auth-actions .mk-consent { margin: 0; font-size: 0.82rem; line-height: 1.5; color: var(--mk-quiet); text-align: center; text-wrap: balance; }
.mk-consent a { color: var(--mk-ink); font-weight: 600; }

/* ---- 19.14 Motion ------------------------------------------------------------------------
   ONE AUTHORED MOMENT: the statement window rises into place and its four
   checks draw in turn, top to bottom, the way a payment is followed through.
   Everything else on the page is still. The hidden state lives behind .mk-anim
   (progressive enhancement: layout.html adds the class only when it can also
   remove it). Reduced motion never starts the mechanism. */

.mk-anim .mk-rise {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 600ms var(--app-ease), transform 700ms var(--app-ease);
}
.mk-anim .mk-rise.mk-in { opacity: 1; transform: none; }

.mk-anim .mk-trail-dot { transform: scale(0.6); opacity: 0; transition: transform 380ms var(--app-ease), opacity 380ms var(--app-ease); }
.mk-anim .mk-rise.mk-in .mk-trail-dot { transform: none; opacity: 1; }
.mk-anim .mk-rise.mk-in .mk-trail-row:nth-child(1) .mk-trail-dot { transition-delay: 450ms; }
.mk-anim .mk-rise.mk-in .mk-trail-row:nth-child(2) .mk-trail-dot { transition-delay: 700ms; }
.mk-anim .mk-rise.mk-in .mk-trail-row:nth-child(3) .mk-trail-dot { transition-delay: 950ms; }
.mk-anim .mk-rise.mk-in .mk-trail-row:nth-child(4) .mk-trail-dot { transition-delay: 1200ms; }
.mk-anim .mk-slip { opacity: 0; transform: translateY(10px); transition: opacity 500ms var(--app-ease), transform 600ms var(--app-ease); }
.mk-anim .mk-rise.mk-in .mk-slip { opacity: 1; transform: none; transition-delay: 1450ms; }

/* ---- 19.15 Small screens -------------------------------------------------------------------- */

@media (max-width: 52rem) {
  .mk-nav-links { display: none; }
  .mk-nav-actions { margin-left: auto; }
}
@media (max-width: 34rem) {
  .mk-brand-word { display: none; }
  .mk-link-quiet { padding-inline: 0.5rem; }
  .mk-nav { padding-inline: 1rem; gap: 0.5rem; }
  .mk-wrap { padding-inline: 1.125rem; }
  .mk-cta-row .mk-btn-lg { flex: 1 1 100%; }
}

/* ==========================================================================
   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 {
  /* Icon and label share the first row; the figure and its sentence take the card's
     full width below. A figure beside the icon had 176px of room for a 186px
     "Nu 10,000.00" at 1440 and wrapped onto two lines (plans/87). */
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  column-gap: var(--app-s-3);
  /* The card is the measure: the figure below sizes itself from the card's own width (cqi), so
     "Nu 1,250,000.00" fits in a 180px card as well as in a 270px one. A viewport clamp could not
     know how many columns the grid had chosen (plans/87 review I2). */
  container-type: inline-size;
  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); }
/* The article's inner block dissolves so label, figure and sentence can be placed on the grid. */
.app-kpi > div { display: contents; }
.app-kpi-icon  { grid-column: 1; grid-row: 1; }
.app-kpi-label { grid-column: 2; grid-row: 1; }
.app-kpi-figure,
.app-kpi-meta  { grid-column: 1 / -1; }
.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: var(--app-s-3) 0 0;
  white-space: nowrap;
  font-family: var(--app-font-sans);
  font-weight: var(--app-w-semibold);
  font-size: clamp(1rem, 12cqi, 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.
   --------------------------------------------------------------------------- */
/* The shell (`.co`, `.co-sheet`, `.co-foot`) is section 12.1. What follows is the little of the
   app's own vocabulary that still renders inside it (the bare notice page, the language switch),
   restated in the checkout's tokens so the app's theme cannot reach it. */
.co .app-page-head { display: block; margin: 0 0 1.25rem; padding: 0; border: 0; }
.co .app-eyebrow { color: var(--co-ink-3); font-size: .8125rem; margin: 0 0 .25rem; letter-spacing: .02em; }
.co .app-title { font-size: 1.375rem; line-height: 1.25; margin: 0; color: var(--co-ink); }
.co .app-subtitle { color: var(--co-ink-2); margin: .375rem 0 0; font-size: .9375rem; }
.co .app-btn {
  min-height: 2.875rem; border-radius: 10px; font-size: 1rem; font-weight: 600;
  background: var(--co-sheet); border: 1px solid var(--co-field); color: var(--co-ink);
}
.co-h3 { font-size: var(--app-fs-base); font-weight: var(--app-w-semibold); margin: 0 0 var(--app-s-2); }

/* ---------------------------------------------------------------------------
   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; }
}

.co-pay { margin: 0 0 var(--app-s-2); }
.co-pay .app-btn { width: 100%; min-height: 3rem; font-size: 1.0625rem; }
.co .app-meta { color: var(--app-ink-muted); font-size: var(--app-fs-base); margin: 0 0 .5rem; }
.co a:not(.co-btn) { 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-line-strong);
  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); }
/* ☠️ THIS PANEL RENDERED TRANSPARENT, AND CSS SAID NOTHING. Reported
   2026-09-04: "the top profile menu has transparent background making it hard
   to read text." It did — over whatever the page had behind it.

   The cause is two custom properties that are USED here and DEFINED nowhere:
   --app-surface-1 appeared exactly once in the whole stylesheet, on the line
   below, and --app-edge four times, never once as a declaration. A var() with
   no definition and no fallback makes the whole declaration invalid, so the
   panel had no background and no border at all — and nothing warns, which is
   why a floating menu shipped see-through.

   The real tokens are --app-surface (#ffffff in light, near-black in dark) and
   --app-line-strong (the card ring). Semantic rather than a literal white,
   because this application HAS a dark mode and a hardcoded white panel would
   put near-black text on white inside a dark page. Lint L29 now refuses any
   var(--app-*) that nothing defines. */
.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);
  border: 1px solid var(--app-line-strong);
  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-line-strong);
  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-line-strong);
}

/* 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)); }

/* A COLOUR AS IT WILL READ ON A LIGHT PAGE AND ON A DARK ONE, side by side
   (plans/98, /admin/platform). The pair is two .app-brand-swatch, each with its
   own inline --app-brand and a caption; this only lays them out. Not
   .app-grid-2: that is a two-PANEL layout with a 20rem floor, and a form column
   is narrower than two of them. */
.app-swatch-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--app-s-3);
  margin-block: var(--app-s-3);
}
.app-swatch-pair .app-brand-swatch { block-size: 2.25rem; margin: 0; }
.app-swatch-pair .app-meta { margin: var(--app-s-2) 0 0; }

/* =========================================================================
   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 The scope: one light, self-contained world ------------------

   REDESIGNED 2026-10-03 (impeccable; surface brief
   .impeccable/surfaces/pb-applications-pay-token-pay-index-html.md).

   THE SHEET IS TWO HALVES. `.co-slip` is the order — who, how much, what for —
   and `.co-pane` is the one thing to do next. On a phone the same markup stacks
   as slip-top, pane, slip-detail so the amount leads and the action sits under
   the thumb. One accent only: the MERCHANT's colour, which this page passes in
   as three custom properties (--app-brand, --app-brand-ink, --app-brand-edge;
   lib/brand.js computes the last two so a pale brand stays readable). The
   platform's own look stays out of the way: a payer is deciding whether to
   trust the business they meant to pay, not ours.

   Light only, deliberately. The merchant app has a theme toggle because
   merchants live in it all day; a payer sees this page for forty seconds and
   their phone's dark mode must not decide whether a payment screen looks
   trustworthy. The tokens below therefore also RE-STATE the app tokens the
   nested components read, so a dark-mode phone gets the same page.

   This is the only block in the stylesheet allowed colour literals outside
   section 2/3: they are TOKENS, declared once here, and every rule below
   reads them. Contrast is measured, not hoped: ink-2 on white 9.6:1, ink-3 on
   the slip 5.9:1, ok on its fill 5.6:1, bad on its fill 7.4:1, the input edge
   on white 3.2:1. ---------------------------------------------------- */

.co {
  color-scheme: light;

  --co-ink:      #101413;
  --co-ink-2:    #3c4743;
  --co-ink-3:    #56625d;
  --co-slip:     #f3f6f4;
  --co-sheet:    #ffffff;
  --co-line:     #dfe6e2;
  --co-field:    #86948d;
  --co-ok:       #0b6b43;
  --co-ok-fill:  #e6f4ec;
  --co-bad:      #a61b1b;
  --co-bad-fill: #fdecec;
  --co-hover:    rgb(16 20 19 / 0.05);
  --co-press:    rgb(0 0 0 / 0.1);
  --co-r:        8px;
  --co-w:        26rem;          /* the width of one column's content */
  --co-top:      clamp(2rem, 7vh, 4.5rem);   /* where a half's first line starts on a laptop, from the top edge */
  --co-num:      "Manrope", "Inter", ui-sans-serif, system-ui, sans-serif;

  /* The app tokens nested components (flash, details, QR, icons) read. */
  --app-bg:          var(--co-sheet);
  --app-surface:     var(--co-sheet);
  --app-surface-2:   var(--co-slip);
  --app-surface-3:   var(--co-slip);
  --app-ink:         var(--co-ink);
  --app-ink-muted:   var(--co-ink-2);
  --app-ink-faint:   var(--co-ink-3);
  --app-line:        var(--co-line);
  --app-line-strong: var(--co-line);
  --app-field-line:  var(--co-field);
  --app-action:      var(--co-ink);
  --app-action-hover: var(--co-ink-2);
  --app-action-ink:  #ffffff;
  --app-ring:        var(--co-ink);
  /* The brand as a visible EDGE on white. A branded page overrides it inline
     (lib/brand.js edgeOn); every other page gets the neutral. */
  --app-brand-edge:  var(--app-brand);
  --app-bad-ink:     var(--co-bad);
  --app-bad-fill:    var(--co-bad-fill);
  --app-bad-edge:    var(--co-bad);
  --app-ok-ink:      var(--co-ok);
  --app-ok-fill:     var(--co-ok-fill);
  --app-ok-edge:     var(--co-ok);

  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--co-sheet);
  color: var(--co-ink);
  font-family: var(--app-font-sans);
  font-size: var(--app-fs-md);
  line-height: var(--app-lh-normal);
  /* The slip's colour bleeds past the sheet to the screen edge (12.3); nothing
     it draws there may become a horizontal scrollbar. */
  overflow-x: clip;
}

/* The `hidden` attribute is how this page's scripts show and hide things (the
   remember buttons, the share note, the "checking" line). Any rule below that sets
   `display` would silently beat it, so it is restated here with the last word. */
.co [hidden] { display: none !important; }

/* Dzongkha needs a Tibetan-capable face and room above and below the line for
   its stacked marks. Nothing is downloaded: phones that read Dzongkha ship one,
   and a payer on slow data must not wait for a font to read a price. */
:lang(dz) .co,
.co:lang(dz) {
  font-family: "Noto Sans Tibetan", "Jomolhari", "Kailasa", "Microsoft Himalaya", "Tibetan Machine Uni", var(--app-font-sans);
  line-height: 1.85;
  /* Tibetan script is drawn small for its size: at the Latin scale a title reads
     like a footnote. Lift the type tokens about 12% inside this scope only, so
     the merchant's own screens (which stay English) are untouched. */
  --app-fs-sm:   0.9375rem;
  --app-fs-base: 1rem;
  --app-fs-md:   1.125rem;
  --app-fs-lg:   1.25rem;
  --app-fs-xl:   1.5rem;
  --app-fs-2xl:  1.875rem;
}
/* The classes below set a tight leading of their own, which would beat the 1.85
   above and let stacked Tibetan marks collide on a wrapped title or a two-line
   button. Negative tracking and wide capital-style tracking both break the
   joining of Tibetan syllables, so they are zeroed. Specificity (class + :lang +
   class) wins without !important. */
.co:lang(dz) .co-btn,
.co:lang(dz) .co-title,
.co:lang(dz) .co-subtitle,
.co:lang(dz) .co-merchant-name,
.co:lang(dz) .co-amount-label { line-height: 1.6; }
.co:lang(dz) .co-hint { line-height: 1.7; }
.co:lang(dz) .co-title,
.co:lang(dz) .co-amount,
.co:lang(dz) .co-stamp-word { letter-spacing: 0; }
/* The translations screen (plan 94) edits Dzongkha in an ordinary text field, which would
   otherwise draw it in the merchant screens' Latin stack and lean on whatever fallback the
   browser picks. Same Tibetan-capable stack as the checkout above, same room for stacked marks. */
.app-textarea:lang(dz) {
  font-family: "Noto Sans Tibetan", "Jomolhari", "Kailasa", "Microsoft Himalaya", "Tibetan Machine Uni", var(--app-font-sans);
  font-size: 1.0625rem;
  line-height: 1.85;
}

.co-sheet {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 68rem;
  margin-inline: auto;
}
.co-wrap { flex: 1 1 auto; display: flex; flex-direction: column; }

/* The bare page: a 404, the engine's own error page, a CSRF refusal, a 403. It has no
   merchant, no navigation and no way to know who is reading, so it is one quiet card
   (plans/87). It replaces .co-card, whose rules went with the old checkout. */
.co-notice {
  flex: none;
  width: min(calc(100% - 2rem), var(--co-w));
  margin: clamp(2rem, 14vh, 6rem) auto var(--app-s-8);
  padding: var(--app-s-6);
  border: 1px solid var(--co-line);
  border-radius: var(--co-r);
  background: var(--co-sheet);
}
.co-notice .app-page-head { display: block; margin: 0 0 var(--app-s-4); }
.co-notice .app-eyebrow { color: var(--co-ink-3); }
.co-notice .app-subtitle { color: var(--co-ink-2); }
.co-notice .app-meta { color: var(--co-ink-3); }

/* ---- 12.2 The split: phone first --------------------------------------- */

.co-split {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "id" "pane" "detail";
  align-content: start;
}
/* The slip is one box on a laptop and two on a phone; `contents` lets its two
   halves take their own places in the grid without a second copy of the markup. */
.co-slip { display: contents; }
.co-id     { grid-area: id; }
.co-detail { grid-area: detail; }
.co-pane   { grid-area: pane; }

.co-id,
.co-detail {
  background: var(--co-slip);
  padding-inline: var(--app-s-5);
}
.co-id     { padding-block: var(--app-s-5) var(--app-s-6); border-bottom: 1px solid var(--co-line); }
.co-detail { padding-block: var(--app-s-5) var(--app-s-8); border-top: 1px solid var(--co-line); }
.co-pane   { padding: var(--app-s-6) var(--app-s-5) var(--app-s-8); }

@media (min-width: 56rem) {
  /* Two halves at the width of the screen's own middle: the grey half bleeds to
     the left edge, the white half to the right, whatever the window is. */
  .co-split {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-areas: "slip pane";
    align-content: stretch;
  }
  .co-slip {
    grid-area: slip;
    display: block;
    background: var(--co-slip);
    box-shadow: 0 0 0 100vmax var(--co-slip);
    clip-path: inset(0 0 0 -100vmax);
    padding: var(--co-top) clamp(2rem, 5vw, 4rem) var(--app-s-10) var(--app-s-6);
  }
  .co-id, .co-detail {
    max-width: var(--co-w);
    margin-left: auto;
    padding-inline: 0;
    border: 0;
    background: transparent;
  }
  .co-detail { padding-block: var(--app-s-6) 0; }
  .co-pane {
    padding: var(--co-top) var(--app-s-6) var(--app-s-10) clamp(2rem, 5vw, 4rem);
  }
  .co-pane-inner { max-width: var(--co-w); padding-top: var(--app-s-4); }
}

/* A page that is not a split (the invalid link, the return pages, the receipt)
   is one column, centred, on the same white sheet. */
.co-single {
  width: 100%;
  max-width: var(--co-w);
  margin: 0 auto;
  padding: clamp(2rem, 8vh, 4.5rem) var(--app-s-5) var(--app-s-10);
}

/* ---- 12.3 The slip ------------------------------------------------------ */

.co-merchant { display: flex; align-items: center; gap: var(--app-s-3); min-width: 0; }
.co-mark {
  flex: none;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--co-r);
  object-fit: contain;
  background: var(--co-sheet);
  border: 1px solid var(--co-line);
}
.co-mark-initial {
  display: grid;
  place-items: center;
  background: var(--app-brand);
  border-color: var(--app-brand-edge);
  color: var(--app-brand-ink);
  font-family: var(--co-num);
  font-size: var(--app-fs-lg);
  font-weight: var(--app-w-semibold);
  text-transform: uppercase;
}
.co-merchant-text { min-width: 0; }
.co-merchant-name {
  margin: 0;
  font-size: 1.25rem;
  font-weight: var(--app-w-semibold);
  line-height: var(--app-lh-snug);
  overflow-wrap: anywhere;
}
.co-invoice { margin: 0; font-size: var(--app-fs-base); color: var(--co-ink-3); }

.co-amount-label { margin: var(--app-s-6) 0 var(--app-s-1); font-size: var(--app-fs-base); color: var(--co-ink-3); }
.co-amount {
  margin: 0;
  font-family: var(--co-num);
  font-size: clamp(2.25rem, 9vw, 2.75rem);
  font-weight: var(--app-w-semibold);
  line-height: 1.1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.co-amount-cur {
  margin-right: 0.3em;
  font-size: 0.45em;
  font-weight: var(--app-w-medium);
  letter-spacing: 0;
  color: var(--co-ink-3);
  vertical-align: 0.6em;
}
.co-note { margin: var(--app-s-4) 0 0; font-size: var(--app-fs-base); color: var(--co-ink-2); }

.co-issued { margin: 0; font-size: var(--app-fs-sm); color: var(--co-ink-3); }

.co-lines { list-style: none; margin: var(--app-s-4) 0 0; padding: 0; border-top: 1px solid var(--co-line); }
.co-line {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--app-s-4);
  padding: var(--app-s-3) 0;
  border-bottom: 1px solid var(--co-line);
}
.co-line-desc { min-width: 0; overflow-wrap: anywhere; }
.co-line-meta { display: block; font-size: var(--app-fs-sm); color: var(--co-ink-3); font-variant-numeric: tabular-nums; }
.co-line-amt { flex: none; font-variant-numeric: tabular-nums; }

.co-totals { margin: 0; padding: 0; }
.co-total-row {
  display: flex;
  justify-content: space-between;
  gap: var(--app-s-4);
  padding: var(--app-s-2) 0;
  font-variant-numeric: tabular-nums;
}
.co-total-row dt { color: var(--co-ink-2); }
.co-total-row dd { margin: 0; text-align: right; }
.co-total-due { font-weight: var(--app-w-semibold); border-top: 1px solid var(--co-line); margin-top: var(--app-s-1); padding-top: var(--app-s-3); }
.co-total-due dt { color: var(--co-ink); }

/* The memorable moment: the slip stops asking and starts proving. It arrives
   once, quickly, and only for someone who has not asked the system to hold
   still. */
.co-stamp {
  display: inline-flex;
  align-items: center;
  gap: var(--app-s-2);
  margin: var(--app-s-6) 0 var(--app-s-2);
  padding: var(--app-s-2) var(--app-s-3);
  border: 1.5px solid var(--co-ok);
  border-radius: var(--co-r);
  background: var(--co-ok-fill);
  color: var(--co-ok);
  font-size: var(--app-fs-sm);
  font-weight: var(--app-w-bold);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
/* A refunded payment is not a success to be proud of: the stamp and the tick drop the
   green and go quiet (plans/86 ruling 2). */
.co-stamp-neutral { border-color: var(--co-ink-3); background: var(--co-slip); color: var(--co-ink-2); }
.co-stamp-tick { display: inline-flex; }
.co-stamp-time {
  padding-left: var(--app-s-2);
  border-left: 1px solid currentColor;
  font-weight: var(--app-w-medium);
  letter-spacing: 0;
  text-transform: none;
}
@media (prefers-reduced-motion: no-preference) {
  .co-stamp:not(.co-stamp-still) { animation: co-stamp-in 280ms var(--app-ease) both; }
}
@keyframes co-stamp-in {
  from { opacity: 0; transform: scale(1.14) rotate(-2deg); }
  to   { opacity: 1; transform: none; }
}
.co .app-nav-icon { flex: none; width: 1.125rem; height: 1.125rem; }

/* ---- 12.4 The pane: type ----------------------------------------------- */

.co-title {
  margin: 0 0 var(--app-s-2);
  padding: 0;
  font-size: var(--app-fs-xl);
  font-weight: var(--app-w-semibold);
  line-height: var(--app-lh-snug);
  letter-spacing: var(--app-track-tight);
}
.co-subtitle { margin: 0 0 var(--app-s-1); font-size: var(--app-fs-md); font-weight: var(--app-w-semibold); }
.co-h3 { margin: var(--app-s-5) 0 var(--app-s-2); font-size: var(--app-fs-base); font-weight: var(--app-w-semibold); }
.co-lede { margin: 0 0 var(--app-s-5); color: var(--co-ink-2); }
.co-hint { margin: var(--app-s-3) 0 0; font-size: var(--app-fs-sm); line-height: var(--app-lh-normal); color: var(--co-ink-3); }
.co-hint a, .co-links a, .co-foot a, .co-tap { color: var(--co-ink); text-decoration: underline; text-underline-offset: 0.2em; }
.co-links { margin: var(--app-s-2) 0 0; font-size: var(--app-fs-base); }
/* A link a thumb has to hit is a 44px-tall box, not a line of text. */
.co-links a, .co-tap, .co-lang a {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
}
.co-contact { margin-top: var(--app-s-3); }
.co-contact .co-hint { margin-top: 0; }
/* A live region must exist before it speaks, so it is present and empty; empty,
   it takes no room. */
.co-live:empty { margin: 0; }
.co-hint + .co-lede { margin-top: var(--app-s-3); }
.co-rule { border: 0; border-top: 1px solid var(--co-line); margin: var(--app-s-6) 0; }

.co-problem {
  display: flex;
  align-items: flex-start;
  gap: var(--app-s-3);
  margin: 0 0 var(--app-s-5);
  padding: var(--app-s-3) var(--app-s-4);
  border: 1px solid var(--co-bad);
  border-radius: var(--co-r);
  background: var(--co-bad-fill);
  color: var(--co-bad);
  font-size: var(--app-fs-base);
}
.co-problem .app-nav-icon { margin-top: 0.1875rem; }

/* ---- 12.5 The pane: the way to pay ------------------------------------- */

.co-form { display: flex; flex-direction: column; gap: var(--app-s-4); margin: 0; }
.co-actions { display: flex; flex-direction: column; gap: var(--app-s-3); margin: var(--app-s-5) 0 0; }
.co-actions > .co-form { display: block; }
/* Whatever follows a sentence in the pane gets a full step of air; the first
   thing in the pane gets none, so the title and the slip's first row line up. */
.co-pane-inner > .co-form,
.co-pane-inner > .co-btn,
.co-pane-inner > .co-actions { margin-top: var(--app-s-5); }
.co-pane-inner > :first-child { margin-top: 0; }

.co-methods {
  display: flex;
  flex-direction: column;
  gap: var(--app-s-3);
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
.co-methods > legend { display: block; width: 100%; margin: 0 0 var(--app-s-3); padding: 0; }

.co-method { position: relative; display: block; cursor: pointer; }
.co-method-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.co-method-body {
  display: grid;
  grid-template-columns: 1.125rem minmax(0, 1fr);
  column-gap: var(--app-s-3);
  align-items: start;
  padding: var(--app-s-4);
  border: 1px solid var(--co-line);
  border-radius: var(--co-r);
  background: var(--co-sheet);
  transition: border-color var(--app-fast) ease, box-shadow var(--app-fast) ease;
}
.co-method-body::before {
  content: "";
  grid-row: 1 / span 3;
  width: 1.125rem;
  height: 1.125rem;
  margin-top: 0.1875rem;
  border: 2px solid var(--co-field);
  border-radius: 50%;
  background: var(--co-sheet);
}
.co-method-body > * { grid-column: 2; }
.co-method:hover .co-method-body { border-color: var(--co-field); }
.co-method-input:checked + .co-method-body {
  border-color: var(--app-brand-edge);
  box-shadow: 0 0 0 1px var(--app-brand-edge);
}
.co-method-input:checked + .co-method-body::before {
  border-color: var(--app-brand-edge);
  background: radial-gradient(circle, var(--app-brand-edge) 0 42%, var(--co-sheet) 46%);
}
.co-method-input:focus-visible + .co-method-body { outline: 2px solid var(--co-ink); outline-offset: 2px; }

/* One way to pay is a statement, not a question: same look, no radio. */
.co-method-static { cursor: default; }
.co-method-static .co-method-body { grid-template-columns: minmax(0, 1fr); }
.co-method-static .co-method-body::before { display: none; }
.co-method-static .co-method-body > * { grid-column: 1; }

.co-method-label { font-weight: var(--app-w-semibold); }
.co-method-blurb { font-size: var(--app-fs-base); color: var(--co-ink-2); }
.co-method-body .pay-method-test { justify-self: start; margin-top: var(--app-s-2); }

/* ---- 12.6 Buttons and fields ------------------------------------------- */

.co-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 3rem;
  padding: var(--app-s-3) var(--app-s-5);
  border: 1px solid var(--co-field);
  border-radius: var(--co-r);
  background: var(--co-sheet);
  color: var(--co-ink);
  font: inherit;
  font-weight: var(--app-w-semibold);
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: box-shadow var(--app-fast) ease, transform var(--app-fast) ease;
}
.co-btn:hover { box-shadow: inset 0 0 0 999px var(--co-hover); }
.co-btn:active { transform: translateY(1px); }
.co-btn:focus-visible { outline: 2px solid var(--co-ink); outline-offset: 2px; }
.co-btn[aria-busy="true"] { cursor: progress; opacity: 0.7; }

.co-btn-primary {
  min-height: 3.25rem;
  /* The EDGE form, not the fill: a pale brand on this white pane is 1.1:1 and the
     button would have no boundary at all. brand.js picks the brand itself when it
     holds 3:1 and the ink otherwise. */
  border-color: var(--app-brand-edge);
  background: var(--app-brand);
  color: var(--app-brand-ink);
  font-size: var(--app-fs-lg);
}
.co-btn-primary:hover { box-shadow: inset 0 0 0 999px var(--co-press); }

/* "Forget this" and its kind: there, honest, and not a competitor for the tap. */
.co-btn-quiet {
  width: auto;
  min-height: 2.75rem;
  margin-top: var(--app-s-3);
  padding-inline: 0;
  border-color: transparent;
  background: transparent;
  color: var(--co-ink-2);
  font-weight: var(--app-w-regular);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.co-field { display: flex; flex-direction: column; gap: var(--app-s-2); }
.co-label { font-size: var(--app-fs-base); font-weight: var(--app-w-semibold); }
.co-input {
  width: 100%;
  min-height: 3rem;
  padding: 0 var(--app-s-4);
  border: 1px solid var(--co-field);
  border-radius: var(--co-r);
  background: var(--co-sheet);
  color: var(--co-ink);
  /* 16px or larger: below that a phone zooms the page when the field is tapped. */
  font: inherit;
  font-size: var(--app-fs-md);
}
.co-input:focus { border-color: var(--co-ink); outline: 2px solid var(--co-ink); outline-offset: 0; }
.co-input-code { font-size: var(--app-fs-2xl); letter-spacing: 0.3em; text-align: center; }

/* ---- 12.7 After the payment -------------------------------------------- */

.co-check {
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  margin: 0 0 var(--app-s-4);
  border-radius: 50%;
  background: var(--co-ok-fill);
  color: var(--co-ok);
}
.co-check .app-nav-icon { width: 1.5rem; height: 1.5rem; }
.co-check-neutral { background: var(--co-slip); color: var(--co-ink-2); }

.co-facts { margin: var(--app-s-5) 0; padding: 0; border-top: 1px solid var(--co-line); }
.co-fact {
  display: flex;
  justify-content: space-between;
  gap: var(--app-s-4);
  padding: var(--app-s-3) 0;
  border-bottom: 1px solid var(--co-line);
}
.co-fact dt { color: var(--co-ink-3); }
.co-fact dd { margin: 0; text-align: right; overflow-wrap: anywhere; }

/* ---- 12.8 The other way, the footer, the language ---------------------- */

.co-alt { margin-top: var(--app-s-8); padding-top: var(--app-s-6); border-top: 1px solid var(--co-line); }
.co-qr { margin: var(--app-s-4) 0; }
.co-qr .app-qr { width: min(13.75rem, 100%); padding: var(--app-s-2); border: 1px solid var(--co-line); border-radius: var(--co-r); background: var(--co-sheet); image-rendering: pixelated; }

/* THE LANGUAGE SWITCH HEADS THE PANE (plan 94, moved up 2026-10-04). It used to be the last thing in
   the pane, below the Pay button and every hint, so a Dzongkha reader who landed on English had to
   scroll to find the one link written in their own script. It is now the first thing in the pane
   (or in a single column), a row of 44px hit boxes with a globe in front, and the draft line, when
   there is one, hangs directly under it. */
.co-langbar { margin: 0 0 var(--app-s-4); }
.co-lang { display: flex; flex-wrap: wrap; align-items: center; column-gap: var(--app-s-4); margin: 0; font-size: var(--app-fs-base); }
.co-lang-icon { display: inline-flex; color: var(--co-ink-3); }
/* `.co a:not(.co-btn)` (above) sets every checkout link to ink; the switch tells its two states apart
   by weight and underline, with the language the page is in quiet and the other one a link. */
.co .co-lang a { color: var(--co-ink-2); text-decoration: underline; text-underline-offset: 0.2em; }
.co .co-lang a[aria-current] { color: var(--co-ink); font-weight: var(--app-w-semibold); text-decoration: none; }
/* The Dzongkha name is written in Tibetan script whatever language the page is in, and an English page
   has no Tibetan stack of its own: without this the one link a Dzongkha reader is looking for is drawn in
   whatever fallback the browser picks, small, and cut tight against its line. Same stack and room as the
   checkout's own Dzongkha (.co:lang(dz)). */
.co-lang a:lang(dz) {
  font-family: "Noto Sans Tibetan", "Jomolhari", "Kailasa", "Microsoft Himalaya", "Tibetan Machine Uni", var(--app-font-sans);
  font-size: 1.25rem;
  line-height: 1.6;
}
/* On a laptop the first line of each half starts at --co-top so the pane's title and the slip's merchant
   name sit on one line. The switch goes INTO that space instead of pushing the title down: its top edge is
   the top of the pane, and the margin below hands back exactly what is left of the space above the title
   (--co-top plus the pane's 1rem of padding, less the 44px row). Nothing else on the pane moves, so a
   stage that was built to fit 1440x812 still fits. A draft line adds its own height and no more. */
@media (min-width: 56rem) {
  .co-pane-inner > .co-langbar:first-child {
    margin-top: calc(-1 * (var(--co-top) + var(--app-s-4)));
    margin-bottom: calc(var(--co-top) + var(--app-s-4) - 2.75rem);
  }
  /* With a draft line under it the title is already below where it would have been, so the room that
     keeps the two halves aligned is not owed and a plain step of air is enough: that is what lets the
     tallest stage (the QR, in Dzongkha, on a 812px screen) keep its last button in view. */
  .co-pane-inner > .co-langbar-draft:first-child { margin-bottom: var(--app-s-4); }
}
/* The one quiet line a draft translation carries (plan 94): in the language the page is in and
   again in English, so a payer who cannot read the first still learns the second. Not an alert and
   not a banner: nothing is wrong, it is only honest about how far to trust the wording. */
.co-lang-draft { margin: 0; font-size: var(--app-fs-sm); color: var(--co-ink-3); }
.co-lang-draft span { display: block; }
.co-lang-draft span + span { margin-top: var(--app-s-1); }
/* Tibetan is drawn small for its size (see .co:lang(dz)): the Dzongkha half of the line is set a
   step up so it is as legible as the English half under it. */
.co-lang-draft span:lang(dz) { font-size: var(--app-fs-base); }

.co-foot { border-top: 1px solid var(--co-line); background: var(--co-sheet); }
.co-foot-in { max-width: 68rem; margin-inline: auto; }
.co-foot-cell { padding: var(--app-s-4) var(--app-s-5); }
.co-foot p { margin: 0; font-size: var(--app-fs-sm); color: var(--co-ink-3); }
@media (min-width: 56rem) {
  /* The same two columns as .co-split, and the same padding the slip has, so the
     sentence starts exactly where the slip's content starts. */
  .co-foot-in { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .co-foot-cell { padding: var(--app-s-4) clamp(2rem, 5vw, 4rem) var(--app-s-4) var(--app-s-6); }
  .co-foot p { max-width: var(--co-w); margin-left: auto; }
}

/* The test banner spans the whole page, above both halves, and bleeds to the screen
   edges like the slip does. It reuses .app-flash (a second thing that looks like a
   flash and is not one is how a design system drifts) and only drops the card
   chrome that made it float. */
/* `.co .co-testbanner`, not `.co-testbanner`: .pay-testbanner (the pre-sheet rule,
   section 12.9) comes later in this file and gives the banner a bottom margin,
   which opened a white gap between it and the slip. The extra class wins on
   specificity, so the order of the two files' sections stops mattering. */
.co .co-testbanner {
  margin: 0;
  padding-inline: var(--app-s-5);
  border: 0;
  border-bottom: 1px solid var(--co-bad);
  /* The hairline has to bleed with the fill. A shadow can only paint the fill (it
     spreads in every direction), so the line is a border-image whose outset runs
     100vmax out each side; unlike a pseudo-element it adds no scrollable width. */
  border-image: linear-gradient(var(--co-bad), var(--co-bad)) 0 0 1 0 / 0 0 1px 0 / 0 100vmax 0 100vmax;
  border-radius: 0;
  background: var(--co-bad-fill);
  box-shadow: 0 0 0 100vmax var(--co-bad-fill);
  clip-path: inset(0 -100vmax);
}
.co-testbanner strong { color: var(--co-bad); }

/* Print: the page is a payment screen, not a document; /print is the document. */
@media print { .co-foot, .co-langbar { display: none; } }

/* ---- 12.9 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 {
  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);
}

/* ---- 12.10 The card form ---------------------------------------------- */

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

/* THE TEST BANNER, above every stage of a sandbox invoice.
   It reuses .app-flash rather than introducing a banner component, because a
   second thing that looks like a flash and is not one is how a design system
   starts to drift. What is added here is only what this instance needs: room
   below it, and a width that matches the cards it sits above. */
.pay-testbanner {
  margin-block: 0 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. A class
   called .mk-split was once the landing page's display heading; used as a
   layout container it handed every descendant a 70px uppercase display font
   and two rounds of "explicit typography" were spent treating the symptom
   before getComputedStyle found the cause. Check a class name is free before
   reusing it.
   =========================================================================
   Form on the left, something worth looking at on the right.

   WHAT IS ON THE RIGHT, AND WHY IT IS NOT A PHOTOGRAPH. A photo of strangers
   in an office says nothing about what this product does, costs a large asset
   on the one page people load on the worst connection they own, and dates the
   moment the clothes do. What goes there is the product's OWN material — an
   invoice that has been paid, an amount, a settlement line — drawn in CSS.

   REWRITTEN 2026-10-04 with section 19. The panel is a DEEP field
   (--mk-deep), which is dark teal in both themes: it was built from --mk-ink
   with a hardcoded white label once, and because --mk-ink is the colour that
   swaps, the whole panel turned pale grey in the dark theme with a white
   headline on it (reported 2026-10-03, guarded by lint L33).

   On a phone the panel is not shown. It is decoration for a wide screen, and a
   person signing in on a phone wants the form, not a sample under it.
   ---------------------------------------------------------------------- */

.mk-authpanel {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: start;
  max-width: var(--mk-wrap);
  margin-inline: auto;
  /* The top pairs vw with vh: on a 13-inch laptop the panel on the right must
     end above the fold, and height is what that laptop does not have. */
  padding: clamp(2rem, min(5vw, 6vh), 3.5rem) 1.5rem clamp(3rem, 7vw, 5rem);
}
@media (min-width: 60rem) {
  .mk-authpanel {
    grid-template-columns: minmax(0, 25rem) minmax(0, 1fr);
    gap: clamp(3rem, 7vw, 7rem);
  }
}

.mk-authpanel-form { width: 100%; max-width: 25rem; margin-inline: auto; }
.mk-authpanel-head { margin-bottom: 1.75rem; }
.mk-authpanel-title {
  margin: 0;
  font-family: var(--mk-font-head);
  font-weight: 700;
  font-size: clamp(1.75rem, min(3vw, 5vh), 2.25rem);
  line-height: 1.1;
  letter-spacing: -0.03em;
  text-wrap: balance;
}
.mk-authpanel-sub { margin: 0.65rem 0 0; color: var(--mk-mute); line-height: 1.55; text-wrap: pretty; }

/* How far along sign-up is: three bars, the ones done in teal. */
.mk-steps-tiny { display: flex; align-items: center; gap: 0.3rem; margin-bottom: 1.1rem; }
.mk-steps-tiny span { margin-left: 0.5rem; font-size: 0.8rem; font-weight: 500; color: var(--mk-quiet); }
.mk-steps-tiny i { display: block; width: 1.75rem; height: 0.25rem; border-radius: 99px; background: var(--mk-line-strong); }
.mk-steps-tiny i.mk-on { background: var(--mk-accent); }

/* ---- 14.1 The panel --------------------------------------------------------- */

.mk-authpanel-aside { display: none; }
@media (min-width: 60rem) {
  .mk-authpanel-aside {
    display: flex;
    flex-direction: column;
    gap: 1.75rem;
    padding: clamp(1.75rem, 3vw, 2.5rem);
    border-radius: var(--mk-radius-xl);
    background: var(--mk-deep);
    color: var(--mk-deep-ink);
    box-shadow: var(--mk-shadow-lg);
    /* Level with the form's heading, and it stays in view beside a long form. */
    position: sticky;
    top: 6rem;
  }
}
.mk-authpanel-aside-title {
  margin: 0;
  font-family: var(--mk-font-head);
  font-weight: 700;
  font-size: clamp(1.5rem, min(2.4vw, 4.2vh), 2rem);
  line-height: 1.15;
  letter-spacing: -0.03em;
  text-wrap: balance;
}
.mk-authpanel-aside-sub { margin: 0.7rem 0 0; color: var(--mk-deep-mute); line-height: 1.6; max-width: 30rem; text-wrap: pretty; }

/* ---- 14.2 The sample statement on the panel ------------------------------------ */

.mk-panelcard-stack {
  border: 1px solid var(--mk-deep-line);
  border-radius: var(--mk-radius-lg);
  background: var(--mk-deep-2);
  overflow: hidden;
}
.mk-panelcard-card { padding: 1rem 1.25rem; border-bottom: 1px solid var(--mk-deep-line); }
.mk-panelcard-card:last-child { border-bottom: 0; }
.mk-panelcard-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.mk-panelcard-label { font-size: 0.82rem; color: var(--mk-deep-mute); }
.mk-panelcard-amount {
  margin: 0.3rem 0 0;
  font-family: var(--mk-font-head);
  font-weight: 700;
  font-size: 1.35rem;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}
.mk-panelcard-meta { margin: 0.2rem 0 0; font-size: 0.82rem; color: var(--mk-deep-mute); }
.mk-panelcard-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.15rem 0.6rem;
  border-radius: 99px;
  background: color-mix(in srgb, var(--mk-deep-accent) 16%, transparent);
  color: var(--mk-deep-accent);
  font-size: 0.72rem;
  font-weight: 650;
}
.mk-panelcard-dot { width: 0.4rem; height: 0.4rem; border-radius: 50%; background: var(--mk-deep-accent); }
.mk-panelcard-sample { display: flex; align-items: center; gap: 0.6rem; margin: 0; font-size: 0.8rem; color: var(--mk-deep-mute); }
.mk-authpanel-aside .mk-sample { border-color: var(--mk-deep-line-strong); color: var(--mk-deep-mute); }

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

.mk-auth-foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.5rem 1rem;
  margin: 1.5rem 0 0;
  font-size: 0.9rem;
  color: var(--mk-mute);
}
.mk-auth-foot a { color: var(--mk-ink); font-weight: 600; }

/* ==========================================================================
   THE PAYER'S RECEIPT, 2026-09-03
   ========================================================================== */

/* The two facts somebody came back to confirm: when it was paid, and how.
   Above the disclosure, because most visitors want nothing else. */
.pay-paidfacts { margin-block: var(--app-s-4); }

/* SUMMARY FIRST, DETAIL ON DEMAND. <details> is used rather than a scripted
   panel so the whole thing works with JavaScript off — this page is public and
   is opened on whatever device a customer happens to hold. */
.pay-details {
  margin-block: var(--app-s-4);
  border-top: 1px solid var(--app-line);
  padding-top: var(--app-s-4);
}
.pay-details > summary {
  cursor: pointer;
  font-weight: var(--app-w-medium);
  color: var(--app-ink);
  list-style: none;
}
.pay-details > summary::-webkit-details-marker { display: none; }
/* The affordance is drawn rather than relying on the browser's own marker,
   which differs between engines and disappears entirely when list-style is
   removed. It rotates on open so the control says which way it goes. */
.pay-details > summary::before {
  content: "›";
  display: inline-block;
  margin-right: var(--app-s-2);
  transition: transform 0.15s ease;
}
.pay-details[open] > summary::before { transform: rotate(90deg); }
.pay-details > summary:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
  border-radius: var(--app-r-8);
}
.pay-details-body { padding-top: var(--app-s-3); }

@media (prefers-reduced-motion: reduce) {
  .pay-details > summary::before { transition: none; }
}

/* The checkout's disclosure (2026-10-03). Same <details> as above, dressed for the
   sheet: a 44px row, a drawn chevron (the icon component, not a text glyph), and
   a ring in the ink so it shows on white whatever the merchant's colour is. */
.co .pay-details > summary {
  display: flex;
  align-items: center;
  gap: var(--app-s-2);
  min-height: 2.75rem;
  color: var(--co-ink);
}
.co .pay-details > summary::before { content: none; }
.co .pay-details > summary:focus-visible { outline-color: var(--co-ink); }
.co-chev { display: inline-flex; flex: none; transition: transform 0.15s ease; }
.co-chev .app-nav-icon { width: 1rem; height: 1rem; }
.co .pay-details[open] > summary .co-chev { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
  .co-chev { transition: none; }
}

/* The bank's QR stage (2026-10-03). One column: what to scan, how, and how long
   the code lives. The tile is the only large thing; everything else is the
   checkout's ordinary type so the stage reads as the same sheet. */
.co .co-scan { display: grid; gap: var(--app-s-4); justify-items: start; }
.co .co-scan > div { display: grid; gap: var(--app-s-4); justify-items: start; width: 100%; }
.co .co-scan > div[hidden] { display: none; }
.co .co-scan .co-title, .co .co-scan .co-lede, .co .co-scan .co-hint { margin: 0; }
.co .co-qrtile {
  width: min(17.5rem, 100%);
  aspect-ratio: 1;
  padding: var(--app-s-3);
  display: grid;
  place-items: center;
  border: 1px solid var(--co-line);
  border-radius: var(--co-r);
  background: var(--co-sheet);
}
.co .co-qrtile img { display: block; width: 100%; height: 100%; image-rendering: pixelated; }
.co .co-steps { margin: 0; padding: 0; list-style: none; counter-reset: co-step; display: grid; gap: var(--app-s-2); }
.co .co-steps li {
  counter-increment: co-step;
  display: grid;
  grid-template-columns: 1.5rem 1fr;
  column-gap: var(--app-s-2);
  align-items: baseline;
  color: var(--co-ink-2);
}
.co .co-steps li::before {
  content: counter(co-step);
  font-family: var(--co-num);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--co-ink);
}
.co .co-expiry { margin: 0; color: var(--co-ink-3); font-size: var(--app-fs-sm); }
.co .co-expiry strong { color: var(--co-ink); font-variant-numeric: tabular-nums; }
/* A short laptop screen is the binding constraint, not a narrow one (the payer
   page is checked at 1440x812): trade the tile's size for keeping the buttons in
   view, but never below the 240px a phone camera needs to read it. */
@media (min-width: 56rem) and (max-height: 860px) {
  .co .co-qrtile { width: 15rem; }
  .co .co-scan, .co .co-scan > div { gap: var(--app-s-3); }
}
.co:lang(dz) .co-steps li { line-height: 1.6; }
