/* mixtape.works design tokens — generated from packages/ui/src/tokens.css.
   DO NOT EDIT. Run `pnpm --filter @mixtape/ui kit` after changing the source.

   This is the plain-CSS build, for products with no Tailwind and no build step. It is the
   same palette, the same type, the same radii and the same motion as every other product -
   that is the entire point of it. Add a colour to the source, never here, and never to a
   product's own stylesheet. */

:root {
  --font-sans: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif;
  --font-mono: var(--font-geist-mono), ui-monospace, "SF Mono", Menlo, monospace;
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 18px;
  --radius-xl: 26px;
  --ease-out-soft: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --animate-rise: rise 380ms cubic-bezier(0.22, 1, 0.36, 1) both;
  --animate-fade: fade 200ms cubic-bezier(0.22, 1, 0.36, 1) both;
  --animate-pop: pop 200ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
  --animate-pulse-soft: pulse-soft 2.4s cubic-bezier(0.22, 1, 0.36, 1) infinite;
}
/* mixtape.works design tokens — one system, every product.

   Two layers on purpose:
   1. `@theme inline` maps Tailwind utilities onto CSS variables *by reference*, so
      `bg-canvas` resolves at paint time rather than being frozen at build time.
   2. The palettes below re-point those variables. Swap `data-theme` on <html> and the
      whole app re-themes with no re-render and no flash.

   Add a colour here, never in an app. If a product needs its own accent, override
   `--c-accent*` on a wrapper element; everything else inherits. */



/* ── The page's width, for every mixtape page ──────────────────────────────
   One number: the landing column, a product's dashboard, Studio and the account all stop
   here. 90rem is 1440px at the default size, so a 1440 screen is filled edge to edge (less
   the gutters) and a wider one keeps the column at 1440 rather than stretching lines across
   it (Chris, 2026-09-21: "every page including dashboards on 1440px screens should fill out
   the whole width content-wise (for bigger windows dont)"). It was 1200px on the landing
   layer, 80rem in Studio and 72rem in the account, and none of them agreed. */
:root { --page-max: 90rem; }

/* ── Dark: the studio default and the brand's home ─────────────────────── */
:root,
:root[data-theme='dark'] {
  color-scheme: dark;

  --c-canvas: #0a0b0d;
  --c-surface: #101216;
  --c-surface-2: #171a21;
  --c-line: #23272f;
  --c-line-strong: #323843;

  --c-ink: #eceef1;
  --c-ink-muted: #a3abb6;
  --c-ink-subtle: #6d7583;

  --c-accent: #f2b544;
  --c-accent-strong: #ffc85c;
  --c-accent-ink: #0a0b0d;
  --c-accent-soft: #f2b5441f;

  --c-ok: #3ecf8e;
  --c-rec: #f0604d;
  --c-warn: #e9b949;
  --c-go: #6fbf5e;
  --c-go-strong: #5aa84a;
  --c-go-ink: #0a0b0d;
  --c-idea: #b892f6;
  --c-plan: #4cc4e0;
  --c-build: #5b8def;
  --c-review: #f0916d;
  --c-surface-sunk: #0c0d10;

  --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a3abb6' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}

/* ── Light: warm paper, not clinical white ─────────────────────────────── */
:root[data-theme='light'] {
  color-scheme: light;

  --c-canvas: #fbfaf8;
  --c-surface: #ffffff;
  --c-surface-2: #f4f2ee;
  --c-line: #e6e2db;
  --c-line-strong: #d3cec4;

  --c-ink: #17191c;
  --c-ink-muted: #565c66;
  --c-ink-subtle: #868d99;

  /* The brand orange, on paper. Two corrections, in order.
     First, the light accent used to be a dark brown-gold chosen so *white* text could sit on
     it; dark ink on a brighter amber - the relationship dark mode already has - reads better
     and looks like the brand. Then on 2026-09-16 Chris said the buttons and badges still
     looked brownish here, and the cause was hue rather than lightness: #c8871f is a
     low-chroma gold, and a filled patch of it reads as mustard. #d97706 is the same
     lightness at a purer orange, so it costs nothing - it is fractionally better as text
     (3.05:1 against paper, up from 2.90) while dark ink on it stays at 5.7:1. Small accent
     labels remain the deliberate cost of one token doing both jobs; splitting fill from text
     would mean touching 73 call sites to buy a tenth of a ratio. */
  --c-accent: #d97706;
  --c-accent-strong: #b45309;
  --c-accent-ink: #1c1405;
  --c-accent-soft: #d977061f;

  --c-ok: #17864f;
  --c-rec: #c3402c;
  --c-warn: #a67908;
  --c-go: #3f8f31;
  --c-go-strong: #2f7524;
  --c-go-ink: #ffffff;
  --c-idea: #7c4dcc;
  --c-plan: #0f7d99;
  --c-build: #2f5fd0;
  --c-review: #c2551f;
  --c-surface-sunk: #f1f2f4;

  --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23565c66' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}

/* Follow the OS when the user hasn't chosen. `data-theme` always wins. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    color-scheme: light;

    --c-canvas: #fbfaf8;
    --c-surface: #ffffff;
    --c-surface-2: #f4f2ee;
    --c-line: #e6e2db;
    --c-line-strong: #d3cec4;

    --c-ink: #17191c;
    --c-ink-muted: #565c66;
    --c-ink-subtle: #868d99;

    --c-accent: #d97706;
    --c-accent-strong: #b45309;
    --c-accent-ink: #1c1405;
    --c-accent-soft: #d977061f;

    --c-ok: #17864f;
    --c-rec: #c3402c;
    --c-warn: #a67908;
    --c-go: #3f8f31;
    --c-go-strong: #2f7524;
    --c-go-ink: #ffffff;
    --c-idea: #7c4dcc;
    --c-plan: #0f7d99;
    --c-build: #2f5fd0;
    --c-review: #c2551f;
    --c-surface-sunk: #f1f2f4;

    --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23565c66' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  }
}

/* ── Phones in dark: the page is black ───────────────────────────────────
   Chris, 2026-09-22: "on mobile only, when on dark mode, the bg color of the page (outside of
   our cards/boxes etc) should be totally black". A phone screen is mostly OLED, where black is
   the panel switched off and the cards read as lit objects on it; on a desktop monitor the
   near-black stays. Only the canvas moves - surfaces, lines and ink keep their values, so the
   cards look exactly as they do everywhere else. Both ways a page can be dark: chosen
   (data-theme) and followed from the OS (no stamp). syncThemeColor reads --c-canvas, so the
   status bar follows by itself. */
@media (max-width: 767.98px) {
  :root[data-theme='dark'] { --c-canvas: #000; }
}
@media (max-width: 767.98px) and (prefers-color-scheme: dark) {
  :root:not([data-theme]) { --c-canvas: #000; }
}

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes fade {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@keyframes pop {
  from {
    opacity: 0;
    transform: scale(0.94);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes pulse-soft {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.45;
  }
}

@layer base {
  html {
    -webkit-tap-highlight-color: transparent;
    /* The root element, not just body: any moment where the page is between renders paints
       this, and without it the browser falls back to `color-scheme` - which is dark by
       default here, so light mode flashed dark. */
    background-color: var(--c-canvas);
  }

  /* Interface scale. The middle step is the size the UI is designed at, so it sets no
     attribute at all; the other two nudge it either way. A phone gets a smaller spread
     because its layout already runs close to the screen edges.

     The root's font size, not `zoom` on the body - and the difference is not cosmetic.
     **CSS `zoom` breaks `position: fixed` on iOS**: a bar with `bottom: 0` under a zoomed
     body resolves against a scaled coordinate space and lands short of the bottom edge by
     the difference, so at 0.94 on a 932pt screen the tab bar floated about 55pt up the
     screen. It looked correct on any page long enough to scroll, because scrolling drags a
     mis-positioned fixed element back into place, which is what made it look like a
     content-height problem for weeks. The docked chat card is fixed too, and had the same
     flaw waiting.

     Every size and every spacing step in this system is already in `rem` - `--spacing` is
     .25rem, the type scale is rem throughout - so moving the root font size scales the whole
     interface with no zoom anywhere and no coordinate space to get wrong. Values written in
     px deliberately stay put: a 16px field has to stay 16px or iOS zooms the page on focus,
     and a touch target should not shrink because somebody likes small text. */
  :root[data-scale='s'] {
    font-size: 15px;
  }
  :root[data-scale='l'] {
    font-size: 17.25px;
  }

  @media (min-width: 768px) {
    :root[data-scale='s'] {
      font-size: 14.4px;
    }
    :root[data-scale='l'] {
      font-size: 18.4px;
    }
  }

  /* Colour changes are animated so the theme toggle dissolves rather than jump-cuts.
     Scoped to colour only, so transforms and animations stay crisp. */
  body,
  header,
  main,
  aside,
  nav,
  section,
  article,
  li,
  input,
  textarea,
  select,
  button {
    transition:
      background-color 380ms cubic-bezier(0.22, 1, 0.36, 1),
      border-color 380ms cubic-bezier(0.22, 1, 0.36, 1),
      color 380ms cubic-bezier(0.22, 1, 0.36, 1);
  }

  ::selection {
    background: var(--color-accent);
    color: var(--color-accent-ink);
  }

  /* Anything focusable gets the same visible ring — keyboard and switch users included. */
  :focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
  }

  /* Honour the OS setting. Motion is decoration in an app; it is never load-bearing.
     A marketing page opts out with `data-motion="always"` on <html>, because there the
     motion IS the product - and because older Windows machines have animations disabled
     globally, so honouring the setting there shows a stranger a dead page. Owner's standing
     decision. Scoping it here rather than trying to undo it further down is deliberate:
     `animation-duration: revert` rolls back to the user-agent value, which is 0s, so an
     "un-disable" rule would disable the animations just as thoroughly. */
  @media (prefers-reduced-motion: reduce) {
    :root:not([data-motion='always']) *,
    :root:not([data-motion='always']) *::before,
    :root:not([data-motion='always']) *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
      scroll-behavior: auto !important;
    }
  }
}

/* The shared settings element (elements/settings.ts) paints nothing until its script has
   defined it. Hold its exact footprint meanwhile - 44px square, pulled flush with the edge -
   so the header does not shift sideways the moment the cog appears. */
mixtape-settings:not(:defined) {
  display: inline-block;
  width: 44px;
  height: 44px;
  margin-right: var(--mx-cog-edge, -0.5rem);
}

/* Native <select> anchors its arrow to the padding box, so adding padding-right only
   squeezes the label — it never moves the arrow off the border. Suppress the native
   control and draw our own chevron at a fixed inset instead. Colour comes from a
   per-theme variable because a data: URI can't read currentColor. */


/* Minimum comfortable touch target — 44px is the Apple/WCAG floor. */


/* Physical feedback on tap. Cheap, and it makes the whole app feel alive. */


/* Children rise in sequence; set --i on each child for the stagger. */


/* iOS safe areas. `viewport-fit=cover` deliberately extends the page under the notch and
   the home indicator so backgrounds reach the screen edges - these keep *content* out of
   them. Applied to the sticky header and the fixed tab bar, never to <html>, because
   padding there breaks sticky positioning. */




/* The tab bar's own version: enough to clear a home indicator, not a whole band of it.
   Four items with labels already stand well clear, and the full inset left a strip of empty
   canvas under them that read as the bar floating rather than sitting on the edge. */
/* While the keyboard is up, everything pinned to the bottom stands aside. There is nothing
   to gain from a tab bar and a usage dock squeezed between the text you are writing and the
   keyboard you are writing it with. */
:root[data-kb] [data-overlay-bar] {
  display: none;
}

/* ...and the thing you are typing into comes to the keyboard, rather than the page being
   scrolled until it happens to be visible.

   Four rounds went into scrolling that card into the right place: correcting the browser,
   then racing it, then giving up and trusting it - and it still left the field under the
   keyboard on a phone. Docking removes the question. `bottom: var(--kb)` is the top of the
   keyboard on both platforms: zero on Android, where the layout viewport already shrank,
   and the keyboard's height on iOS, where it did not.

   `--kb-bar` is the editor toolbar Android floats over the page - arrows and a Done tick -
   which no measurement includes and no attribute refuses. It is reserved *inside* the card
   rather than by lifting the card, so the strip it covers is the card's own background
   instead of the conversation showing through underneath. Zero on iOS, which draws no such
   bar for a `contenteditable`. See Keyboard.tsx. */
:root[data-kb] [data-docks-to-keyboard] {
  position: fixed;
  left: 0;
  right: 0;
  bottom: var(--kb, 0px);
  padding-bottom: var(--kb-bar, 0px);
  z-index: 45;
  border-radius: 0;
  border-left: 0;
  border-right: 0;
  max-height: 100%;
}

/* A field you can tap without the page jumping.

   iOS zooms the whole page when you focus a control whose text is under 16px, and every
   measurement taken afterwards is in a scaled viewport - which is how the chat card once
   ended up somewhere else entirely. 16px on a phone is the guard.

   The trigger is the *device*, not the window width, and tying it to a `sm:` breakpoint got
   that wrong: a phone in landscape and a tablet are both wider than any breakpoint and both
   still zoom. So ask what is pointing at the screen. A mouse gets the smaller, tidier text;
   anything you touch gets 16px, at any width. */
.tap-text {
  font-size: 13px;
}
@media (pointer: coarse) {
  .tap-text {
    font-size: 16px;
  }

  /* No compensation for the interface scale here any more. It used to be a `zoom` on the
     body, which rendered this 16px at 15.04 and let iOS zoom the page anyway; the scale is
     the root font size now and this is px, so 16 stays 16 at every setting. */
}

/* A strip that scrolls sideways in step with another one, so only one of them should show
   a scrollbar. The board's headings mirror the columns below them; two bars would be two
   controls for one axis. */
[data-no-scrollbar] {
  scrollbar-width: none;
}
[data-no-scrollbar]::-webkit-scrollbar {
  display: none;
}

/* Chrome that gets out of the way.

   Scrolling down is the one time the two bottom bars are worth less than the rows they are
   covering, so they leave - and come back the moment you scroll up, which is when you are
   looking for them. Only past a threshold, so a short nudge does not flap them. Never while
   the keyboard is up: those bars are already hidden then, and a transform on top of that is
   a second answer to a settled question.

   The header goes too, but only on a phone: on a desktop it carries the nav and there is
   screen to spare. It is `sticky`, so by the time it leaves - a hundred and twenty pixels
   down - the place it holds in the flow is already scrolled past, and nothing opens a gap
   where it was. The board's sticky column headings move up to take its place, or they would
   hang below a bar that is no longer there. */
[data-chrome-bar] {
  transition: transform 220ms var(--ease-out-soft);
}
:root[data-chrome-away] [data-chrome-bar='bottom'] {
  transform: translateY(100%);
}
@media (max-width: 767px) {
  :root[data-chrome-away] header[data-chrome-bar] {
    transform: translateY(-100%);
  }
  /* Up into the space the header left - all the way, and filling the notch itself rather
     than starting under it. Stopping at the inset leaves a band of status bar with the
     board scrolling behind it, which is what the header had been covering. */
  :root[data-chrome-away] [data-board-headings] {
    top: 0;
    padding-top: env(safe-area-inset-top);
  }
}
@media (prefers-reduced-motion: reduce) {
  [data-chrome-bar] {
    transition: none;
  }
}



/* Landscape on a notched phone puts the cutout on one side. */


/* The studio floor. Two-frame sprite animation done the pixel-art way: cross-fade nothing,
   just swap which frame is visible on a step timing function, so it reads as real frames
   rather than a smooth tween. */
@keyframes bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-1.5%); }
}
@keyframes frame-a {
  0%, 49.9% { opacity: 1; }
  50%, 100% { opacity: 0; }
}
@keyframes frame-b {
  0%, 49.9% { opacity: 0; }
  50%, 100% { opacity: 1; }
}
.frame-a { animation: frame-a 0.44s steps(1, end) infinite; }
.frame-b { animation: frame-b 0.44s steps(1, end) infinite; }

/* The two frames are sibling <g> elements at the same coordinates inside one SVG, so they
   already overlap - opacity alone swaps which one you see. CSS positioning does not apply
   to SVG groups, so do not reach for it here. */

/* Somebody who does not want motion still gets the colour and the bubble, which carry the
   same information - the animation is emphasis, never the only signal. */
@media (prefers-reduced-motion: reduce) {
  .frame-a, .frame-b { animation: none; }
  .frame-b { opacity: 0; }
}

/* ── No zoom-lurch when a field is focused ──────────────────────────────────────────────
 *
 * iOS Safari zooms the whole page in when you tap into a control whose text is smaller
 * than 16px, and zooms back out when you leave it. Every text field in this app is 14px,
 * so typing anywhere meant the page jumping in and out.
 *
 * The usual fix - `maximum-scale=1, user-scalable=no` in the viewport meta - takes
 * pinch-zoom away from everybody permanently to solve a typing annoyance. Giving the
 * controls the 16px Safari is asking for costs nothing and leaves zoom alone.
 *
 * Deliberately unlayered: Tailwind's utilities live in a cascade layer, and anything
 * layered loses to unlayered rules regardless of order - which is the only way to beat the
 * `text-sm` that every control here carries.
 */
@media (pointer: coarse) {
  input:not([type='checkbox']):not([type='radio']):not([type='color']):not([type='range']),
  select,
  textarea {
    font-size: 16px;
  }

  /* The pre-divided 17.05px that used to sit here is gone with the `zoom` it compensated
     for. 16px is 16px at every interface scale now. */
}

/* mixtape.works landing layer — the "spiced up" half of the design system.

   `tokens.css` is the system every product shares: the palette, the type, the radii, the
   motion vocabulary. It is deliberately quiet, because most of what we build is an app
   somebody works in all day. A landing page has the opposite job - it has about four
   seconds to look like a company worth buying from - so this file adds the loud half:
   full-bleed bands, glass surfaces, an animated ribbon, bento cards with live vignettes,
   and DOM screencasts instead of video files.

   Two rules keep the two halves one system:
   1. **Every colour here comes from `--c-*`.** No landing page invents a palette. Swap
      `data-theme` and this re-themes with everything else, because there is nothing to
      re-theme separately.
   2. **It is plain CSS.** Our Next apps get it through `@mixtape/ui/landing.css`; a product
      that is a bare Node server with no build step gets the identical file out of
      https://mixtape.works/kit/. One file, one look, both worlds.

   Put a `.lp` class on the page root and everything below is in scope.

   Removing the ribbon: delete the `.lp-wave` block here, `landing/wave.js`, and the two
   markup blocks. Nothing else depends on it. */

/* ── Landing-only variables ──────────────────────────────────────────────
   Hairlines are alpha, never a flat grey: a line at 8% black picks up whatever sits
   behind it, which on a page made of gradients and glass is the difference between a
   border and a stripe. The veil is the one alternate band background. */
.lp {
  --lp-hair: rgba(255, 255, 255, 0.10);
  --lp-hair-strong: rgba(255, 255, 255, 0.16);
  --lp-veil: rgba(23, 26, 33, 0.55);
  --lp-glass: color-mix(in srgb, var(--c-surface) 65%, transparent);
  --lp-glass-up: color-mix(in srgb, var(--c-surface) 78%, transparent);
  --lp-glass-down: color-mix(in srgb, var(--c-surface) 48%, transparent);
  --lp-tile: color-mix(in srgb, var(--c-accent) 7%, var(--c-surface-2));
  --lp-header-h: 3.6875rem;
  --lp-gutter: 1.5rem;
  --lp-max: var(--page-max, 90rem);
  /* Ribbon strength. Dark is the brand's home, so it is tuned there first - and the light
     value is lower rather than higher, which is the opposite of what the reference site
     needed. Our paper is #fbfaf8, much lighter than theirs, so the same saturated band
     reads twice as loud on it and starts competing with the copy instead of sitting behind
     it. Measured on the real page, not guessed. */
  --lp-wave-opacity: 0.34;
  --lp-dots: #d9c6a8;
  --lp-dots-blend: screen;
  --lp-dots-opacity: 0.10;
}

:root[data-theme='light'] .lp {
  --lp-hair: rgba(0, 0, 0, 0.08);
  --lp-hair-strong: rgba(0, 0, 0, 0.13);
  --lp-veil: rgba(255, 255, 255, 0.62);
  --lp-wave-opacity: 0.3;
  --lp-dots: #7a5a24;
  --lp-dots-blend: multiply;
  --lp-dots-opacity: 0.09;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme]) .lp {
    --lp-hair: rgba(0, 0, 0, 0.08);
    --lp-hair-strong: rgba(0, 0, 0, 0.13);
    --lp-veil: rgba(255, 255, 255, 0.62);
    --lp-wave-opacity: 0.3;
    --lp-dots: #7a5a24;
    --lp-dots-blend: multiply;
    --lp-dots-opacity: 0.09;
  }
}

/* ── Page frame ──────────────────────────────────────────────────────────
   Full-bleed bands are `width: 100vw` children of a centred container, so they push past
   the viewport edge by a scrollbar's width unless something clips. Clip at `html`, not at
   the container: the primary button's glow and the CTA band's shadow are *meant* to spill
   past their own box. */
html:has(.lp) { overflow-x: clip; }

.lp {
  position: relative;
  /* A stacking context, so the fixed background layers below can sit at z-index -1 inside
     the page and still paint behind the content rather than behind the whole document. */
  z-index: 0;
}

.lp-container {
  width: 100%;
  max-width: var(--lp-max);
  margin-inline: auto;
  padding-inline: var(--lp-gutter);
}

.lp-bleed {
  width: 100vw;
  margin-inline: calc(50% - 50vw);
  position: relative;
}

/* ── The three background layers ─────────────────────────────────────────
   All fixed, all inert, all behind the content. Never `background-attachment: fixed` on
   any of them: it forces Chrome onto main-thread scrolling for the whole document, which
   is how a page with no heavy content still scrolls like it does. */
.lp-bg {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
}

.lp-bg__wash {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    135deg,
    color-mix(in srgb, var(--c-idea) 14%, transparent) 0%,
    color-mix(in srgb, var(--c-review) 12%, transparent) 26%,
    color-mix(in srgb, var(--c-rec) 11%, transparent) 52%,
    color-mix(in srgb, var(--c-accent) 14%, transparent) 78%,
    color-mix(in srgb, var(--c-accent-strong) 12%, transparent) 100%
  );
}

/* 2rem grid of 1.5px dots. Blended rather than drawn flat, so it reads as texture in the
   light and as a faint star-field in the dark instead of as a grey mesh in both. */
.lp-bg__dots {
  position: absolute;
  inset: 0;
  opacity: var(--lp-dots-opacity);
  mix-blend-mode: var(--lp-dots-blend);
  background-image: radial-gradient(circle at 1.5px 1.5px, var(--lp-dots) 1.5px, transparent 0);
  background-size: 2rem 2rem;
}

/* ── The ribbon ──────────────────────────────────────────────────────────
   Strength lives on the wrapper's opacity, never on the canvas: the canvas fades itself in
   once (see `--drawn`) and a second opacity on the same element would fight that. */
.lp-wave {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
  opacity: var(--lp-wave-opacity);
  /* The cheap fallback, and the only thing phones ever get. It paints during LCP, so it is
     two radial gradients and nothing else - no mask, no color-mix. */
  background:
    radial-gradient(48% 42% at 78% 12%, rgba(242, 181, 68, 0.30), transparent 70%),
    radial-gradient(40% 38% at 96% 34%, rgba(240, 96, 77, 0.20), transparent 72%);
}

.lp-wave__canvas {
  display: block;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.7s ease;
}

.lp-wave--drawn .lp-wave__canvas { opacity: 1; }

/* Frosted ends instead of a faded ribbon: two composited strips cost one blur each, where
   fading the geometry would mean a multi-tap read per pixel in the shader. */
.lp-wave__frost {
  position: absolute;
  top: 0;
  bottom: 0;
  -webkit-backdrop-filter: blur(2.125rem);
  backdrop-filter: blur(34px);
  pointer-events: none;
}
.lp-wave__frost--left {
  left: 0;
  width: 42vw;
  -webkit-mask-image: linear-gradient(90deg, #000 0%, rgba(0, 0, 0, 0.82) 42%, transparent 100%);
  mask-image: linear-gradient(90deg, #000 0%, rgba(0, 0, 0, 0.82) 42%, transparent 100%);
}
.lp-wave__frost--right {
  right: 0;
  width: 22vw;
  -webkit-mask-image: linear-gradient(270deg, #000 0%, rgba(0, 0, 0, 0.82) 42%, transparent 100%);
  mask-image: linear-gradient(270deg, #000 0%, rgba(0, 0, 0, 0.82) 42%, transparent 100%);
}

/* ── Bands ───────────────────────────────────────────────────────────────
   Exactly two section backgrounds, strictly alternating down the page: transparent, then
   the veil. Add or remove a section and fix the alternation - it is the page's rhythm, and
   two tinted bands in a row read as a mistake nobody can name. */
.lp-band {
  width: 100vw;
  margin-inline: calc(50% - 50vw);
  padding-block: 4.5rem;
  position: relative;
}
.lp-band--tint { background: var(--lp-veil); }
.lp-band--tight { padding-block: 3rem; }
/* The hero sets its own vertical rhythm, including the clearance for the fixed header, so
   the band around it contributes none. Without this the top of the page carries the band's
   4.5rem *and* the hero's 4rem *and* the header's height, and the headline sits a third of
   the way down the screen. */
/* Two classes, so the tablet/phone rule `.lp-band { padding-block: 3rem }` further down
   cannot win on source order: it did until 2026-09-21, and every phone got 3rem of empty
   band above the hero's own header clearance - a gap under the bar Chris spotted at once. */
.lp-band.lp-band--hero { padding-block: 0; }

/* ── Type ────────────────────────────────────────────────────────────────
   One display treatment shared by the hero and every section title, so the page has a
   single voice. Geist is the system font - we do not add a second family for landing
   pages; the display weight and the negative tracking are what make it read as display. */
.lp-eyebrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  /* min-height, not height. A two-word English eyebrow fits on one line at any width; a
     Hungarian one does not, and a fixed-height pill puts its second line outside its own
     background. The padding is what centres the text now, and every pill on a page still
     matches because they all start from the same minimum. */
  min-height: 1.8125rem;
  padding: 0.3125rem 0.8125rem 0.25rem;
  box-sizing: border-box;
  line-height: 1.25;
  text-align: center;
  text-wrap: balance;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c-accent);
  background: var(--c-accent-soft);
  border: 1px solid color-mix(in srgb, var(--c-accent) 22%, transparent);
  border-radius: 999px;
  align-self: flex-start;
  /* Its own composited layer. Sections above have fractional heights, so pills land on
     sub-pixel offsets and rasterise differently from one another - which reads as pills of
     visibly different heights. In a layer each one rasterises in integer local coordinates
     and Chrome pixel-aligns it when compositing. */
  transform: translateZ(0);
}

.lp-eyebrow__dot {
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 50%;
  background: var(--c-accent);
  animation: lp-dot 2.4s ease-in-out infinite;
}

.lp-h1 {
  margin: 0;
  font-size: clamp(2.5rem, 6.2vw, 4.5rem);
  font-weight: 800;
  line-height: 1.04;
  letter-spacing: -0.035em;
  text-wrap: balance;
  color: var(--c-ink);
}
/* The second line goes on its own row on purpose, rather than being left to wrap. */
.lp-h1 span { display: block; }

.lp-h2 {
  margin: 0.875rem 0 0;
  font-size: clamp(2rem, 2.5vw, 2.5rem);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -0.035em;
  text-wrap: balance;
  color: var(--c-ink);
}

.lp-lead {
  margin: 0.875rem 0 0;
  font-size: 1.125rem;
  line-height: 1.55;
  color: var(--c-ink-muted);
  text-wrap: pretty;
}

.lp-sub {
  margin: 1.5rem 0 0;
  max-width: 720px;
  font-size: 1.125rem;
  line-height: 1.55;
  color: color-mix(in srgb, var(--c-ink) 85%, transparent);
  text-wrap: pretty;
}

.lp-trust {
  margin: 1.5rem 0 0;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--c-ink) 52%, transparent);
}

.lp-bullets {
  margin: 1.5rem 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}
.lp-bullets li {
  position: relative;
  display: grid;
  grid-template-columns: 1.25rem 1fr;
  gap: 0.7rem;
  align-items: start;
  font-size: 1.125rem;
  line-height: 1.5;
  color: var(--c-ink-muted);
  text-wrap: pretty;
}
.lp-bullets li::before {
  content: "";
  margin-top: 0.28em;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 50%;
  background: var(--c-accent-soft);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--c-accent) 25%, transparent);
}
/* The tick itself is a mask rather than an icon font or an <svg> per bullet: one data URI,
   no network, and it takes its colour from the token like everything else. */
.lp-bullets li::after {
  content: "";
  position: absolute;
  left: 0;
  margin-top: 0.28em;
  width: 1.25rem;
  height: 1.25rem;
  background: var(--c-accent);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 12.5 4 4 8-8.5'/%3E%3C/svg%3E") center / 0.8125rem 0.8125rem no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 12.5 4 4 8-8.5'/%3E%3C/svg%3E") center / 0.8125rem 0.8125rem no-repeat;
}

/* ── Buttons ─────────────────────────────────────────────────────────────
   Primary and secondary share exactly one geometry. The secondary subtracts its border
   from its padding, so the two are the same height to the pixel when they sit side by
   side - which they almost always do. */
.lp-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  padding: 1.15rem 1.75rem;
  border-radius: 12px;
  font-size: 16px;
  font-weight: 700;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  border: none;
}

.lp-btn--primary {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  color: var(--c-accent-ink);
  /* A → B → C → B → A, so the two ends of the gradient match and the loop has no seam.
     The middle stop stays inside the warm family. It was `--c-go` (green), which reads well
     enough on the dark palette and turns to olive mud against warm paper - a mixed-hue
     gradient has to be checked in BOTH themes, because the one that fails is never the one
     you designed it in. */
  background: linear-gradient(
    120deg,
    var(--c-accent-strong) 0%,
    var(--c-accent) 25%,
    color-mix(in srgb, var(--c-accent) 62%, var(--c-rec)) 50%,
    var(--c-accent) 75%,
    var(--c-accent-strong) 100%
  );
  background-size: 200% 100%;
  animation: lp-flow 9s linear infinite, lp-breathe 3.6s ease-in-out infinite;
  box-shadow: 0 0 24px color-mix(in srgb, var(--c-accent) 35%, transparent);
  transition: transform 0.2s ease, box-shadow 0.3s ease;
}
.lp-btn--primary::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Above the gradient, below the label. A positioned pseudo-element at z-index 0 paints
     after inline text, so the label has to be a span with a layer of its own - a bare text
     node cannot be raised above this and the sweep would wash over the words. */
  z-index: 0;
  background: linear-gradient(115deg, transparent 38%, rgba(255, 255, 255, 0.18) 50%, transparent 62%);
  transform: translateX(-130%);
  animation: lp-sheen 5.5s ease-in-out infinite;
  pointer-events: none;
}
.lp-btn--primary:hover,
.lp-btn--primary:focus-visible {
  transform: translateY(-2px);
  /* The glow animation is dropped so the stronger static shadow can win - a running
     box-shadow animation outranks a hover rule for as long as it is running. */
  animation: lp-flow 9s linear infinite;
  box-shadow:
    0 0 44px color-mix(in srgb, var(--c-accent) 60%, transparent),
    0 0 0 0.375rem color-mix(in srgb, var(--c-accent) 18%, transparent);
}
.lp-btn--primary:active { transform: translateY(0); }

.lp-btn__label {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
}
.lp-btn__arrow { animation: lp-nudge 4.5s ease-in-out infinite; }
.lp-btn--primary:hover .lp-btn__arrow,
.lp-btn--primary:focus-visible .lp-btn__arrow {
  animation: none;
  transform: translateX(3px);
  transition: transform 0.2s ease;
}

.lp-btn--ghost {
  padding: calc(1.15rem - 1px) calc(1.75rem - 1px);
  color: var(--c-ink);
  background: var(--lp-glass);
  border: 1px solid var(--lp-hair-strong);
  -webkit-backdrop-filter: blur(0.75rem);
  backdrop-filter: blur(12px);
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
}
.lp-btn--ghost:hover,
.lp-btn--ghost:focus-visible {
  background: var(--lp-glass-up);
  border-color: color-mix(in srgb, var(--c-accent) 45%, transparent);
  transform: translateY(-2px);
  box-shadow: 0 10px 30px -18px var(--c-accent);
}

/* A pill, for the "reveal" affordances and the sticky heads. */
.lp-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.6rem 1.05rem;
  border-radius: 999px;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--c-ink);
  background: var(--lp-glass);
  border: 1px solid var(--lp-hair);
  -webkit-backdrop-filter: blur(0.75rem);
  backdrop-filter: blur(12px);
  cursor: pointer;
  transition: border-color 0.2s ease, background 0.2s ease;
}
.lp-pill:hover { border-color: color-mix(in srgb, var(--c-accent) 40%, transparent); }
.lp-pill__chev { transition: transform 0.25s var(--ease-out-soft); }
.lp-pill[aria-expanded='true'] .lp-pill__chev { transform: rotate(180deg); }

/* ── Header ──────────────────────────────────────────────────────────────
   Transparent until the page moves, so the ribbon runs unbroken behind it on first paint.
   A tiny scroll watcher flips `.is-scrolled` on the element; everything else is CSS. */
.lp-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  height: var(--lp-header-h);
  display: flex;
  align-items: center;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background 0.25s ease, border-color 0.25s ease, backdrop-filter 0.25s ease;
}
.lp-header.is-scrolled {
  background: var(--lp-glass);
  border-bottom-color: var(--lp-hair);
  -webkit-backdrop-filter: blur(0.75rem);
  backdrop-filter: blur(12px);
}
.lp-header__inner {
  position: relative;
  width: 100%;
  max-width: var(--lp-max);
  margin-inline: auto;
  padding-inline: var(--lp-gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
/* Absolutely centred on the page rather than flex-centred between the two clusters: the
   left lockup and the right actions are never the same width, so flex centring puts the
   nav visibly off the page's true centre line. */
.lp-header__nav {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 1.75rem;
}
.lp-header__nav a {
  font-size: 0.9062rem;
  font-weight: 600;
  color: var(--c-ink);
  opacity: 0.72;
  text-decoration: none;
  transition: color 0.2s ease, opacity 0.2s ease;
}
.lp-header__nav a:hover { opacity: 1; color: var(--c-accent); }
.lp-header__right { display: flex; align-items: center; gap: 0.75rem; }
/* Studio pulls the cog flush with the header edge; here something follows it. */
.lp-header__right mixtape-settings { --mx-cog-edge: 0; }
/* Two round 44px targets side by side read further apart than two glyphs should: 4px closer. */
.lp-header__right mixtape-settings + mixtape-profile { margin-left: -0.25rem; }

/* Anything a header link scrolls to needs room to land under the fixed bar. */
.lp [id] { scroll-margin-top: calc(var(--lp-header-h) + 1.25rem); }

/* ── Hero ────────────────────────────────────────────────────────────────
   The gaps above and below the button row are equal on purpose; an unequal pair is the
   most common way a centred hero looks subtly wrong. */
.lp-hero {
  padding-block: calc(var(--lp-header-h) + 4rem) 4rem;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.lp-hero .lp-eyebrow { align-self: center; margin-bottom: 1.5rem; }
.lp-hero__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
  margin-top: 1.5rem;
}

/* The demo directly under the hero reads as part of it, so the gap above is small and the
   container gutter is cancelled - it sits flush with the content edge. */
.lp-hero-demo { padding-block: 1.25rem calc(3.5rem + 1.25rem); }

/* ── Split spotlight ─────────────────────────────────────────────────────
   7/12 demo, 5/12 copy, alternating sides down the page. */
.lp-spotlight {
  display: grid;
  grid-template-columns: 7fr 5fr;
  gap: 3rem;
  align-items: center;
}
.lp-spotlight--flip { grid-template-columns: 5fr 7fr; }
.lp-spotlight--flip .lp-spotlight__copy { order: -1; }
.lp-spotlight__demo { max-width: 75%; }
.lp-spotlight:not(.lp-spotlight--flip) .lp-spotlight__demo { margin-right: auto; }
.lp-spotlight--flip .lp-spotlight__demo { margin-left: auto; }

/* ── Sticky-head bento ───────────────────────────────────────────────────*/
.lp-grid {
  display: grid;
  grid-template-columns: 360px 1fr;
  gap: 44px;
  align-items: start;
}
.lp-grid--flip { grid-template-columns: 1fr 360px; }
.lp-grid--flip .lp-grid__head { order: 2; }
.lp-grid__head {
  position: sticky;
  top: calc(var(--lp-header-h) + 1.5rem);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}
.lp-grid__head .lp-pill { margin-top: 1.5rem; }

.lp-bento { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.75rem; }
.lp-card {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  min-width: 0;
  padding: 1.125rem;
  border-radius: 16px;
  background: var(--lp-glass);
  border: 1px solid var(--lp-hair);
  -webkit-backdrop-filter: blur(0.75rem);
  backdrop-filter: blur(12px);
}
.lp-card--wide { grid-column: span 2; }
.lp-card--up { background: var(--lp-glass-up); }
.lp-card--down { background: var(--lp-glass-down); }
.lp-card--row { flex-direction: row; align-items: center; gap: 1rem; padding: 1.125rem 1.25rem; }
.lp-card__title { font-size: 1.0938rem; font-weight: 700; color: var(--c-ink); }
.lp-card__text { margin-top: 2px; font-size: 0.9688rem; line-height: 1.5; color: var(--c-ink-muted); text-wrap: pretty; }

/* The tile a vignette lives in. Fixed height, clipped, tinted with the accent so the row
   of them reads as one material. */
/* Everything inside a tile sizes border-box, whatever the host page's reset says: these
   vignettes ship to products that have their own CSS and some of them do not set it. */
.lp-viz,
.lp-viz * { box-sizing: border-box; }

.lp-viz {
  height: 6.25rem;
  border-radius: 11px;
  background: var(--lp-tile);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  position: relative;
}

/* ── Reveal grid ─────────────────────────────────────────────────────────*/
.lp-reveal { display: none; margin-top: 1.375rem; }
.lp-reveal.is-open { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.75rem; }
.lp-reveal__item {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.25rem 0.625rem;
  align-items: center;
  padding: 0.875rem;
  border-radius: 14px;
  background: var(--lp-glass);
  border: 1px solid var(--lp-hair);
}
.lp-reveal__name { font-size: 0.9062rem; font-weight: 700; color: var(--c-ink); }
.lp-reveal__text { grid-column: 1 / -1; font-size: 0.8125rem; line-height: 1.45; color: var(--c-ink-muted); }

/* ── Centred section head, and the fineprint under an offer ──────────────*/
.lp-head-centre { max-width: 760px; margin: 0 auto 2.25rem; text-align: center; }
.lp-head-centre .lp-eyebrow { align-self: center; }
.lp-head-centre .lp-h2 { margin-top: 0; }
.lp-head-centre .lp-lead { margin-inline: auto; }
/* A section that centres its heading centres what follows it. A list flush to the left margin
   under a centred heading reads as a stray block that lost its own heading - the list is
   centred as a block, its items still left-aligned against each other so the ticks line up. */
.lp-container:has(> .lp-head-centre) > .lp-bullets {
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
}
.lp-container:has(> .lp-head-centre) > .lp-hero__actions,
.lp-container:has(> .lp-head-centre) > .lp-actions {
  justify-content: center;
}
.lp-container:has(> .lp-head-centre) > .lp-fine { text-align: center; }
.lp-fine {
  margin: 0.875rem 0 0;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--c-ink-muted);
}

/* A whole card that is a link. Colour and underline come off; the lift stays. */
a.lp-card {
  text-decoration: none;
  color: inherit;
  transition: border-color 0.2s ease, transform 0.2s ease;
}
a.lp-card:hover {
  border-color: color-mix(in srgb, var(--c-accent) 55%, transparent);
  transform: translateY(-2px);
}
a.lp-card .lp-card__cta { align-self: flex-start; width: auto; }

/* ── A product, in its own colour ──────────────────────────────────────────
   Where the company lists what it makes (mixtape.works, the account's product list), each
   card wears the product's accent instead of mixtape's orange: the mark, a wash in the
   corner, the hover edge and the button all say which product this is before the name does.
   The page sets --p-dark and --p-light on the card (the product's own two accents); this
   re-points --c-accent inside the card, so the kit's own rules paint with it. Same selector
   list as tokens.css, so an explicit theme choice and the OS both win the way they do there. */
.lp-product { --c-accent: var(--p-dark); }
:root[data-theme='light'] .lp-product { --c-accent: var(--p-light); }
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) .lp-product { --c-accent: var(--p-light); }
}
.lp-product {
  background-image: radial-gradient(150% 130% at 0% 0%, color-mix(in srgb, var(--c-accent) 16%, transparent), transparent 72%);
}
.lp-product__head { display: flex; align-items: center; gap: 0.75rem; }
/* The bare mark in the product's accent, never on a tile - see .mx-brand svg in app.css. */
.lp-product__icon { width: 2rem; height: 2rem; flex: none; color: var(--c-accent); }
.lp-product .lp-card__title { font-size: 1.375rem; letter-spacing: -0.01em; }

/* The button fills with the product's colour from the left, and the arrow steps right as the
   fill reaches it (Chris, 2026-09-21). The fill is the product's LIGHT-theme accent in both
   themes: it is the deeper of the two, so the white label reads on it either way (Voks
   5.7:1, Dayrule 5.5:1), where the bright dark-theme accents would put white on pastel.
   The whole card is the link, so hovering or focusing the card is what starts it. */
.lp-product .lp-card__cta {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  color: var(--c-accent);
  border-color: color-mix(in srgb, var(--c-accent) 40%, transparent);
  transition: color 0.3s ease 0.08s, border-color 0.45s ease;
}
.lp-product .lp-card__cta::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--p-light);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.45s cubic-bezier(0.65, 0, 0.35, 1);
}
.lp-product__arrow { display: inline-block; transition: transform 0.2s ease; }
.lp-product:hover .lp-card__cta,
.lp-product:focus-visible .lp-card__cta { color: #fff; border-color: var(--p-light); }
.lp-product:hover .lp-card__cta::before,
.lp-product:focus-visible .lp-card__cta::before { transform: scaleX(1); }
/* The fill crosses the arrow about 0.3s in; the arrow waits for it, then steps. */
.lp-product:hover .lp-product__arrow,
.lp-product:focus-visible .lp-product__arrow { transform: translateX(0.25rem); transition: transform 0.22s ease 0.3s; }
@media (prefers-reduced-motion: reduce) {
  .lp-product .lp-card__cta,
  .lp-product .lp-card__cta::before,
  .lp-product__arrow { transition: none; }
}

/* ── Three-card comparison ───────────────────────────────────────────────*/
.lp-compare {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.875rem;
  max-width: 980px;
  margin-inline: auto;
}
.lp-compare--two { grid-template-columns: repeat(2, 1fr); max-width: 820px; }
.lp-compare .lp-card { position: relative; height: 100%; }
/* The emphasised one. `margin-top: auto` on the CTA is what aligns the buttons across all
   three when the copy above them is different lengths. */
.lp-compare .lp-card--pick {
  background: var(--lp-glass-up);
  border-color: color-mix(in srgb, var(--c-accent) 55%, transparent);
  box-shadow: 0 10px 30px -18px var(--c-accent);
}
.lp-card__badge {
  position: absolute;
  top: 0.875rem;
  right: 0.875rem;
  padding: 3px 0.5625rem;
  border-radius: 999px;
  font-size: 0.6562rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--c-accent-ink);
  background: var(--c-accent);
}
.lp-card__cta { margin-top: auto; width: 100%; }

/* ── Plan cards + segmented toggle ───────────────────────────────────────*/
.lp-seg {
  display: inline-flex;
  padding: 3px;
  border-radius: 999px;
  background: var(--lp-glass-down);
  border: 1px solid var(--lp-hair);
}
.lp-seg button {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1.1rem;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--c-ink-muted);
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
}
.lp-seg button[aria-pressed='true'] {
  background: var(--c-surface);
  color: var(--c-ink);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}
.lp-seg__badge {
  padding: 2px 0.4375rem;
  border-radius: 999px;
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--c-go-ink);
  background: var(--c-go);
}
.lp-was { margin-right: 0.4rem; color: var(--c-ink-subtle); text-decoration: line-through; }
.lp-plan__list { margin: 1rem 0 0; padding: 1rem 0 0; list-style: none; border-top: 1px solid var(--lp-hair); }
.lp-plan__fine { margin-top: 0.75rem; font-family: var(--font-mono, ui-monospace, monospace); font-size: 0.75rem; color: var(--c-ink-subtle); }
/* One card shows one variant at a time; the wrapper's data attribute picks it. */
[data-mode='a'] .lp-var--b, [data-mode='b'] .lp-var--a { display: none; }

/* ── FAQ ─────────────────────────────────────────────────────────────────
   Native <details>. No JavaScript, keyboard and screen-reader correct for free. */
.lp-faq { max-width: 760px; margin-inline: auto; display: flex; flex-direction: column; gap: 0.625rem; }
.lp-faq details {
  padding: 0 1.125rem;
  border-radius: 14px;
  background: var(--lp-glass);
  border: 1px solid var(--lp-hair);
  -webkit-backdrop-filter: blur(0.75rem);
  backdrop-filter: blur(12px);
}
.lp-faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.125rem 0;
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--c-ink);
  cursor: pointer;
  list-style: none;
}
.lp-faq summary::-webkit-details-marker { display: none; }
.lp-faq summary::after {
  content: "+";
  flex: 0 0 auto;
  font-size: 1.375rem;
  font-weight: 400;
  line-height: 1;
  color: var(--c-ink-subtle);
  transition: transform 0.25s var(--ease-out-soft);
}
.lp-faq details[open] summary::after { transform: rotate(45deg); }
.lp-faq p { margin: 0 0 1.125rem; font-size: 1.0312rem; line-height: 1.6; color: var(--c-ink-muted); text-wrap: pretty; }

/* ── Final CTA ───────────────────────────────────────────────────────────*/
.lp-final { padding-block: 3.5rem; text-align: center; border-top: 1px solid var(--lp-hair); }
/* A closing call-to-action that opens its own band is already set apart by the band: the
   divider and the extra padding were for one sharing a band with the section above, and on
   its own they left a hairline floating a band's padding below the edge (mixtape.works,
   2026-09-21). */
.lp-band > .lp-container > .lp-final:first-child { border-top: 0; padding-block: 0; }
.lp-final .lp-h2 { margin-top: 0; }

/* ── Footer ──────────────────────────────────────────────────────────────
   `position: relative; z-index: 2`, and `.lp` is z-index 0 with its own stacking context.
   Without the footer's own layer the fixed ribbon inside the page paints straight over it. */
.lp-footer {
  position: relative;
  z-index: 2;
  padding-block: 3rem;
  background: var(--lp-glass);
  border-top: 1px solid var(--lp-hair);
  -webkit-backdrop-filter: blur(0.75rem);
  backdrop-filter: blur(12px);
}

/* ── Demo frame ──────────────────────────────────────────────────────────
   The scale-to-fit scaffolding every DOM screencast uses. Three elements, and which one
   carries the transform is not a matter of taste: iOS Safari fails to render a transformed
   element clipped by its own box, so the transform goes on the CHILD and a plain parent
   does the clipping.

   Never put `will-change: transform` on the stage - it rasterises at natural size and every
   glyph blurs the moment the scale goes above 1. */
.lp-demo { width: 100%; }
.lp-demo__fit {
  position: relative;
  width: 100%;
  overflow: hidden;
  border-radius: 10px;
  border: 1px solid var(--lp-hair);
  background: var(--c-surface);
}
.lp-demo__stage {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: top left;
}
/* The fake cursor. One SVG, one ripple, nothing else. */
.lp-cursor {
  position: absolute;
  top: 0;
  left: 0;
  width: 1.5rem;
  height: 1.5rem;
  z-index: 90;
  pointer-events: none;
  filter: drop-shadow(0 3px 5px rgba(0, 0, 0, 0.42));
  transition: transform 0.45s cubic-bezier(0.5, 0.05, 0.2, 1);
}
.lp-cursor.is-click { transform-origin: 0.25rem 0.25rem; }
.lp-cursor.is-click::after {
  content: "";
  position: absolute;
  inset: -0.5rem;
  border-radius: 50%;
  border: 2px solid var(--demo-accent, var(--c-accent));
  animation: lp-ripple 0.55s ease-out;
}
.lp-pressed { filter: brightness(0.92); }

/* ── Vignette recipes ────────────────────────────────────────────────────
   Abstract, never screenshots: a few spans, pure CSS, looping forever. Siblings are
   staggered with NEGATIVE delays so the whole row is already mid-motion on first paint -
   a vignette that starts from zero when you scroll to it looks like a page still loading. */
@keyframes lp-dot {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--c-accent) 40%, transparent); }
  50% { box-shadow: 0 0 0 0.375rem color-mix(in srgb, var(--c-accent) 0%, transparent); }
}
@keyframes lp-flow { 0% { background-position: 200% 50%; } 100% { background-position: 0% 50%; } }
@keyframes lp-breathe {
  0%, 100% { box-shadow: 0 0 1.5rem color-mix(in srgb, var(--c-accent) 35%, transparent); }
  50% { box-shadow: 0 0 2.625rem 0.25rem color-mix(in srgb, var(--c-accent) 55%, transparent); }
}
@keyframes lp-sheen {
  0%, 62% { transform: translateX(-130%); }
  78%, 100% { transform: translateX(130%); }
}
@keyframes lp-nudge {
  0%, 78%, 100% { transform: translateX(0); }
  84% { transform: translateX(0.25rem); }
  90% { transform: translateX(0); }
  95% { transform: translateX(2px); }
}
@keyframes lp-ripple {
  from { transform: scale(0.3); opacity: 1; }
  to { transform: scale(1.6); opacity: 0; }
}
@keyframes lp-grow { from { transform: scaleY(0.25); } to { transform: scaleY(1); } }
@keyframes lp-shimmer { 0% { transform: translateX(-130%); } 60%, 100% { transform: translateX(260%); } }
@keyframes lp-slide-in {
  0%, 12% { opacity: 0; transform: translateY(0.75rem); }
  22%, 78% { opacity: 1; transform: translateY(0); }
  90%, 100% { opacity: 0; transform: translateY(-0.25rem); }
}
@keyframes lp-walk {
  0%, 13% { background: var(--c-accent-soft); color: var(--c-accent); }
  19%, 100% { background: transparent; color: var(--c-ink-subtle); }
}
@keyframes lp-cycle {
  0%, 26% { opacity: 1; transform: translateY(0); }
  33%, 93% { opacity: 0; transform: translateY(-0.5rem); }
  100% { opacity: 1; transform: translateY(0); }
}
@keyframes lp-blip {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.7); opacity: 0.45; }
}
@keyframes lp-marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@keyframes lp-hop {
  0%, 80%, 100% { transform: translate(0, 0); }
  85% { transform: translate(0.375rem, -0.3125rem); }
}
@keyframes lp-type { from { transform: scaleX(0.86); } to { transform: scaleX(1); } }
@keyframes lp-confirm {
  0%, 38% { opacity: 0; transform: scale(0.6); }
  55%, 96% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(0.6); }
}

/* The parts the vignettes are built from. */
.lp-viz-bar { border-radius: 3px; background: color-mix(in srgb, var(--c-ink) 22%, transparent); }
/* `width` is not optional. The bars are `flex: 1` with no basis, so inside a tile that
   centres its child on the main axis the row collapses to zero and the tile renders empty -
   which looks exactly like a vignette nobody wrote, not like a bug. */
.lp-viz-bars { display: flex; align-items: flex-end; gap: 0.5rem; height: 3.875rem; width: 100%; max-width: 260px; }
.lp-viz-bars > i {
  flex: 1;
  border-radius: 5px 5px 2px 2px;
  transform-origin: bottom;
  background: color-mix(in srgb, var(--c-accent) 45%, var(--c-surface-2));
  animation: lp-grow 2.4s ease infinite alternate;
}
.lp-viz-bars > i:nth-child(1) { height: 44%; animation-delay: -0.2s; }
.lp-viz-bars > i:nth-child(2) { height: 62%; animation-delay: -0.7s; }
.lp-viz-bars > i:nth-child(3) { height: 50%; animation-delay: -1.1s; }
.lp-viz-bars > i:nth-child(4) { height: 78%; animation-delay: -1.5s; }
.lp-viz-bars > i:nth-child(5) { height: 96%; background: var(--c-accent); animation-delay: -1.9s; }

.lp-viz-chips { display: flex; flex-wrap: wrap; gap: 0.375rem; justify-content: center; padding: 0 0.75rem; }
.lp-viz-chips > span {
  padding: 0.25rem 0.5625rem;
  border-radius: 999px;
  font-size: 0.7188rem;
  font-weight: 600;
  color: var(--c-ink-subtle);
  animation: lp-walk 6s linear infinite;
}
.lp-viz-chips > span:nth-child(2) { animation-delay: -1s; }
.lp-viz-chips > span:nth-child(3) { animation-delay: -2s; }
.lp-viz-chips > span:nth-child(4) { animation-delay: -3s; }
.lp-viz-chips > span:nth-child(5) { animation-delay: -4s; }
.lp-viz-chips > span:nth-child(6) { animation-delay: -5s; }

.lp-viz-cycle { position: relative; width: 100%; height: 100%; }
.lp-viz-cycle > span {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.625rem;
  font-weight: 700;
  color: var(--c-ink);
  animation: lp-cycle 6s ease infinite;
}
.lp-viz-cycle > span:nth-child(2) { animation-delay: -4s; }
.lp-viz-cycle > span:nth-child(3) { animation-delay: -2s; }

.lp-viz-rows { display: flex; flex-direction: column; gap: 0.3125rem; width: 100%; padding-inline: 0.875rem; }
.lp-viz-rows > span {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.25rem 0.625rem;
  border-radius: 7px;
  font-size: 0.7188rem;
  font-weight: 600;
  white-space: nowrap;
  color: var(--c-ink-muted);
  background: var(--c-surface);
  border: 1px solid var(--lp-hair);
}
.lp-viz-rows > span.is-new { animation: lp-slide-in 4.5s ease infinite; }

.lp-viz-shimmer { position: relative; overflow: hidden; }
.lp-viz-shimmer::after {
  content: "";
  position: absolute;
  inset: 0;
  width: 40%;
  background: linear-gradient(100deg, transparent, color-mix(in srgb, var(--c-accent) 30%, transparent), transparent);
  animation: lp-shimmer 3.2s ease-in-out infinite;
}

.lp-viz-marquee { width: max-content; display: flex; gap: 0.625rem; animation: lp-marquee 32s linear infinite; }
.lp-viz-marquee--slow { animation-duration: 40s; animation-direction: reverse; }
.lp-viz:hover .lp-viz-marquee { animation-play-state: paused; }

.lp-blip { width: 8px; height: 8px; border-radius: 50%; background: var(--c-accent); flex: 0 0 auto; animation: lp-blip 2.4s ease infinite; }

/* ── Responsive ──────────────────────────────────────────────────────────
   One real breakpoint. Below it: every split stacks, sticky heads go static, and the three
   expensive effects - the ribbon, backdrop blur and header translucency - all switch off.
   Blur is not a taste call on a phone: it breaks fixed positioning during touch scroll and
   costs GPU the device does not have. */
@media (max-width: 991.98px) {
  .lp-wave { display: none; }
  .lp-band { padding-block: 3rem; }
  .lp-spotlight,
  .lp-spotlight--flip,
  .lp-grid,
  .lp-grid--flip { grid-template-columns: 1fr; gap: 2rem; }
  .lp-spotlight__demo,
  .lp-spotlight--flip .lp-spotlight__demo { max-width: 100%; margin-inline: 0; }
  .lp-spotlight--flip .lp-spotlight__copy { order: 0; }
  .lp-grid__head { position: static; }
  .lp-header__nav { display: none; }
  .lp-compare,
  .lp-compare--two,
  .lp-reveal.is-open { grid-template-columns: 1fr; max-width: 460px; margin-inline: auto; }

  /* Kill every glass surface at once. */
  .lp-header.is-scrolled,
  .lp-card,
  .lp-reveal__item,
  .lp-faq details,
  .lp-btn--ghost,
  .lp-pill,
  .lp-footer,
  .lp-seg { -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--c-surface); }
  .lp-card--down { background: var(--c-surface); }
  .lp-card--up { background: var(--c-surface-2); }

  /* When the bento grid stacks, a button inside the sticky head would land ABOVE the cards
     it is meant to follow. `display: contents` promotes the head's children to grid items,
     so the button can be ordered below the bento on its own. The head's gutters move onto
     the children, because a `display: contents` box has none. */
  .lp-grid__head { display: contents; }
  .lp-grid { row-gap: 0; }
  .lp-grid__head > * { margin-bottom: 0.875rem; }
  .lp-grid__head > .lp-pill { order: 1; margin-top: 1.375rem; margin-bottom: 0; align-self: start; }
  .lp-bento { margin-bottom: 0; }
}

/* A touch device of any width. Dropping the blur is not enough on its own: a 65%
   translucent surface with nothing blurring behind it is a window, and the page scrolls
   visibly through the header. Whatever turns the blur off has to make the surface opaque
   in the same breath. */
@media (hover: none) and (pointer: coarse) {
  .lp-card,
  .lp-reveal__item,
  .lp-faq details,
  .lp-btn--ghost,
  .lp-pill,
  .lp-seg,
  .lp-footer,
  .lp-header.is-scrolled {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    background: var(--c-surface);
  }
  .lp-card--down { background: var(--c-surface); }
  .lp-card--up { background: var(--c-surface-2); }
}

@media (max-width: 575.98px) {
  /* A phone needs the header's clearance and a breath, not a desktop's 4rem of air. */
  .lp-hero { padding-block: calc(var(--lp-header-h) + 2.25rem) 3rem; }
  .lp { --lp-gutter: 1.75rem; }
  .lp-bento { grid-template-columns: 1fr; }
  .lp-card--wide { grid-column: auto; }
  .lp-grid__head,
  .lp-spotlight__copy { text-align: center; }
  .lp-grid__head .lp-eyebrow,
  .lp-spotlight__copy .lp-eyebrow { align-self: center; }
  /* The list centres as a block; the lines inside it stay left-aligned, because centred
     multi-line body copy is much harder to read than people expect. */
  .lp-bullets { display: inline-flex; text-align: left; }
  .lp-demo { margin-inline: calc(var(--lp-gutter) * -1); width: calc(100% + var(--lp-gutter) * 2); }
}

/* app.css — the dashboard layer: the frame and the furniture every signed-in mixtape page
   wears, as plain CSS classes.
 *
 * Why this file exists. The React products (Studio, mixtape.works) had these as components
 * in src/components — AppShell, Card, Panel, Button, Field, Badge — written in Tailwind
 * utilities. A product that is not React could not use a single one of them, so Dayrule and
 * qr-voks each grew their own, which is the one thing we said we would never do. These
 * classes are the definition; the React components render them, so there is one design and
 * two ways to reach it.
 *
 * Load after tokens.css. Every colour, radius and font here is a token: restyle a product by
 * setting --c-accent and friends, never by forking this file. landing.css is the other half,
 * for the pages a stranger sees; this one is for the pages a signed-in person works in.
 *
 * The frame, as the AppShell built it and this keeps it:
 *   - a sticky top bar, 3.5rem, blurred, with the brand, the nav and the gear
 *   - on a phone the nav moves to a fixed bottom tab bar, where a thumb is
 *   - one content column, 1120px, with its own padding
 * The furniture: .mx-card, .mx-panel, .mx-btn, .mx-field, .mx-table, .mx-badge, .mx-stat.
 */

/* Labels - the mono caps on a panel header, a field, a column, a stat.
 *
 * They were --c-ink-subtle at 10.5-0.6875rem, which is a whisper: Chris asked for a point bigger and
 * ten per cent more visible. One mix, not two colours: a tenth of the way from subtle towards
 * the page's own ink brightens it on a dark theme and darkens it on a light one, by
 * construction, and every product that loads this file moves with it. */
:root {
  --mx-label: color-mix(in srgb, var(--c-ink-subtle) 90%, var(--c-ink));
  --mx-label-size: 0.75rem;
  --mx-label-size-sm: 0.7188rem;
}

/* ── The frame ───────────────────────────────────────────────────────────*/

.mx-app {
  display: flex;
  min-height: 100vh;
  min-height: 100dvh;
  flex-direction: column;
  background: var(--c-canvas);
}

.mx-bar {
  position: sticky;
  top: 0;
  z-index: 40;
  border-bottom: 1px solid var(--c-line);
  background: color-mix(in srgb, var(--c-canvas) 85%, transparent);
  -webkit-backdrop-filter: blur(0.75rem);
  backdrop-filter: blur(0.75rem);
  padding-top: env(safe-area-inset-top);
}
.mx-bar__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  height: 3.5rem;
  width: 100%;
  max-width: var(--lp-max, var(--page-max, 90rem));
  margin-inline: auto;
  padding-inline: max(var(--lp-gutter, 1.5rem), env(safe-area-inset-left)) max(var(--lp-gutter, 1.5rem), env(safe-area-inset-right));
}
.mx-bar__left {
  display: flex;
  min-width: 0;
  align-items: center;
  gap: 1rem;
}
.mx-bar__right {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 0.5rem;
}
/* The cog pulls itself flush with the bar's edge - unless the profile follows it, which then
   takes the edge (--mx-me-edge) and leaves the pair half a rem apart, as on mixtape.works.
   Global, not per bar: Studio's React header pairs them the same way. */
mixtape-settings:has(+ mixtape-profile) { --mx-cog-edge: 0; }
/* The brand lock-up: mark, wordmark, and the product badge next to it. */
.mx-brand {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  gap: 0.625rem;
  color: var(--c-ink);
  font-weight: 600;
  text-decoration: none;
}
/* The mark is the bare drawing in the product's accent - the way mixtape's own cassette mark
   sits in its orange. The rounded tile behind it belongs to the favicon and the home-screen
   icon only, where the OS needs a shape to put it on (Chris, 2026-09-21). A product's mark is
   an SVG drawn in currentColor. */
.mx-brand svg { width: 1.375rem; height: 1.375rem; flex: none; color: var(--c-accent); }
/* Who is signed in, next to the gear. A name, not an address: an address is a credential and
   reads as clutter. Hidden where the bar is tight - the gear menu still says. */
.mx-who {
  display: none;
  max-width: 18ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.8125rem;
  color: var(--c-ink-muted);
}
@media (min-width: 600px) { .mx-who { display: inline-block; } }

.mx-badge-app {
  border: 1px solid var(--c-line-strong);
  border-radius: 999px;
  padding: 2px 0.5rem;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--mx-label);
}

/* ── Navigation: one vocabulary, two bars ────────────────────────────────
   The same destinations with the same icons, whether they are clicked in the top bar or
   thumbed in the bottom one. Desktop shows the top nav; a phone shows the tab bar. */

.mx-nav { display: none; align-items: center; gap: 2px; }
.mx-nav a {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.375rem;
  border-radius: var(--radius-sm);
  padding: 0.375rem 0.5rem;
  font-size: 0.875rem;
  color: var(--c-ink-muted);
  text-decoration: none;
  transition: color 0.15s ease;
}
.mx-nav a:hover { color: var(--c-ink); }
.mx-nav a svg { width: 1rem; height: 1rem; flex-shrink: 0; opacity: 0.7; }
.mx-nav a[aria-current='page'] { color: var(--c-ink); }
.mx-nav a[aria-current='page'] svg { color: var(--c-accent); opacity: 1; }
/* The 2px rule under the current page sits on the bar's own border. */
.mx-nav a[aria-current='page']::after {
  content: '';
  position: absolute;
  inset-inline: 0.5rem;
  bottom: -13px;
  height: 2px;
  border-radius: 999px;
  background: var(--c-accent);
}

.mx-tabs {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 40;
  border-top: 1px solid var(--c-line);
  background: color-mix(in srgb, var(--c-canvas) 90%, transparent);
  -webkit-backdrop-filter: blur(0.75rem);
  backdrop-filter: blur(0.75rem);
  padding-bottom: env(safe-area-inset-bottom);
}
.mx-tabs ul {
  display: flex;
  align-items: stretch;
  justify-content: space-around;
  max-width: 35rem;
  margin-inline: auto;
  padding: 0 0.25rem;
  list-style: none;
}
.mx-tabs li { flex: 1; }
.mx-tabs a {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  border-radius: var(--radius-sm);
  padding: 0.5rem 0.25rem;
  font-size: 0.625rem;
  font-weight: 500;
  color: var(--c-ink-subtle);
  text-decoration: none;
}
.mx-tabs a svg { width: 1.375rem; height: 1.375rem; transition: transform 0.15s ease; }
.mx-tabs a[aria-current='page'] { color: var(--c-accent); }
.mx-tabs a[aria-current='page'] svg { transform: scale(1.1); }

.mx-main {
  min-width: 0;
  flex: 1;
  /* Clear of the tab bar on a phone; the bar is gone from `md` up. */
  padding-bottom: calc(4.5rem + env(safe-area-inset-bottom));
}
@media (min-width: 768px) {
  .mx-nav { display: flex; }
  .mx-tabs { display: none; }
  .mx-main { padding-bottom: 2rem; }
}

/* ── The content column ──────────────────────────────────────────────────
   The same width as a landing page's, and the same gutter. Every page we ship - the one a
   stranger lands on and the one they work in after signing in - holds its content in a column
   of exactly one width, or the product reads as two products. */

.mx-page {
  width: 100%;
  max-width: var(--lp-max, var(--page-max, 90rem));
  margin-inline: auto;
  padding: 1.5rem max(var(--lp-gutter, 1.5rem), env(safe-area-inset-left)) 0
    max(var(--lp-gutter, 1.5rem), env(safe-area-inset-right));
}
@media (min-width: 768px) { .mx-page { padding-top: 2rem; } }
/* On a phone the page's edge sits a little closer than one panel is to the next (Chris,
   2026-09-22: first "the same as the padding between the boxes", then "like 2px even
   smaller") - the screen's narrowest dimension was being spent on margin. The bar follows, so
   the logo stays over the panels' edge. The landing layer keeps its own, roomier gutter: that
   is text. */
@media (max-width: 767.98px) {
  .mx-bar, .mx-page { --lp-gutter: 0.75rem; }
}

.mx-page__inner { min-width: 0; }
/* THE PAGE IS A STACK, and it keeps the same rhythm as the stacks inside it.
   Everything a page holds - a panel, a row of columns, a notice - sits .875rem from the
   block above it, the same gap .mx-cols and .mx-stack leave between their own children.
   Without this a full-width panel followed by a row of columns touched it (Dayrule's
   overview, found by Chris 2026-09-21), while the gaps inside the row were .875rem: the one
   place on the page with no air was the join between two kit classes. Voks had solved it
   for itself with this exact rule - which is how you know it belonged here.
   Margins, not gap: the page column is a block, so the head's own 1.25rem below it
   collapses with this rather than adding to it. */
.mx-page > * + *,
.mx-page__inner > * + * { margin-top: 0.875rem; }
/* Two narrower columns than the page's own. A form is easier to read short, but 30rem of
   fields floating in 1200px of page reads as unfinished: a sign-in box is narrow, a page of
   settings is medium, and only a table or a calendar takes the whole width. */
.mx-narrow { max-width: 34rem; margin-inline: auto; }
.mx-medium { max-width: 48rem; margin-inline: auto; }

.mx-page__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1.25rem;
}
.mx-eyebrow {
  margin: 0 0 0.375rem;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: var(--mx-label-size);
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--mx-label);
}
.mx-h1 {
  margin: 0;
  font-size: clamp(1.375rem, 1.4rem + 1vw, 1.875rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--c-ink);
}
.mx-lead {
  margin: 0.5rem 0 0;
  max-width: 62ch;
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--c-ink-muted);
}
.mx-crumb { margin: 0 0 0.75rem; font-size: 0.8125rem; }
.mx-crumb a { color: var(--c-ink-muted); text-decoration: none; }
.mx-crumb a:hover { color: var(--c-ink); }

/* ── Surfaces ────────────────────────────────────────────────────────────*/

.mx-card {
  border: 1px solid var(--c-line);
  border-radius: var(--radius-md);
  background: var(--c-surface);
  padding: 1.25rem;
}
.mx-card__title { margin: 0 0 0.375rem; font-size: 0.9375rem; font-weight: 600; color: var(--c-ink); }
.mx-card__text { margin: 0; font-size: 0.8438rem; line-height: 1.6; color: var(--c-ink-muted); }
.mx-card__text + .mx-card__text { margin-top: 0.625rem; }
.mx-panel {
  overflow: hidden;
  border: 1px solid var(--c-line);
  border-radius: var(--radius-md);
  background: var(--c-surface);
}
.mx-panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  border-bottom: 1px solid var(--c-line);
  padding: 0.5rem 1rem;
  min-height: 2.5rem;
}
.mx-panel__head > :first-child { min-width: 0; }
.mx-panel__head .mx-btn,
.mx-panel__head .mx-badge,
.mx-panel__head .mx-sub { flex: 0 0 auto; white-space: nowrap; }
.mx-panel__head .mx-btn { margin: -0.25rem -0.5rem -0.25rem 0; }
/* The label belongs inside the box it describes - a heading floating above a border reads
   as a different kind of thing when it is not one. */
.mx-panel__title {
  margin: 0;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: var(--mx-label-size);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--mx-label);
}
.mx-panel__body { padding: 1rem; }
.mx-panel__body--flush { padding: 0; }
/* A panel that has been given a height (Panel's `scroll`): the heading stays, the body
   scrolls inside it. With no height set it is an ordinary panel. */
.mx-panel--scroll { display: flex; flex-direction: column; }
.mx-panel--scroll > .mx-panel__head { flex-shrink: 0; }
.mx-panel--scroll > .mx-panel__body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }

.mx-grid {
  display: grid;
  gap: 0.875rem;
  align-items: start;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}
/* Panels side by side, and the one that holds a form or a table taking the whole row. */
/* Two panels to a row, not three: a panel holds a form, and 340px of form is the narrow strip
   this grid exists to get away from. One column below ~880px. */
.mx-cols { display: grid; gap: 0.875rem; align-items: start; grid-template-columns: repeat(auto-fit, minmax(27rem, 1fr)); }
.mx-cols--wide { grid-column: 1 / -1; }
.mx-stack { display: grid; gap: 0.875rem; }

/* A number with its label under it: the four that must agree, side by side. */
.mx-stat { display: grid; gap: 2px; }
.mx-stat__label {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: var(--mx-label-size-sm);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--mx-label);
}
.mx-stat__value { font-size: 1.375rem; font-weight: 600; color: var(--c-ink); }
.mx-stats { display: flex; flex-wrap: wrap; gap: 1.5rem; }

/* ── Controls ────────────────────────────────────────────────────────────*/

.mx-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  padding: 0.625rem 1rem;
  font: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.mx-btn--primary { background: var(--c-accent); color: var(--c-accent-ink); }
.mx-btn--primary:hover { background: var(--c-accent-strong); }
.mx-btn--ghost { border-color: var(--c-line-strong); background: transparent; color: var(--c-ink); }
.mx-btn--ghost:hover { background: var(--c-surface-2); }
.mx-btn--quiet { background: transparent; color: var(--c-ink-muted); padding-inline: 0.625rem; }
.mx-btn--quiet:hover { color: var(--c-ink); background: var(--c-surface-2); }
.mx-btn:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
.mx-btn[disabled], .mx-btn[aria-disabled='true'] { opacity: 0.5; pointer-events: none; }
.mx-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }

/* Controls are 2.5rem tall so a row of them lines up without hand-tuning, and labels are
   mono caps - the studio's voice for metadata. */
/* A form is a stack of fields; a field is label, control, then whatever it has to say. */
.mx-form { display: grid; gap: 1rem; margin-top: 1.125rem; min-width: 0; }
/* Fields side by side once there is room for them.
 *
 * A form reads badly in long lines, but the cure is not a narrow page - a column of controls
 * stranded in the middle of a desktop reads as a bug, and it is the first thing anyone says
 * about it. Keep the page the width of every other page and keep the LINE short: two or three
 * fields to a row here, one to a row on a phone. A field that needs the room (a list of
 * checkboxes, a paste box) takes it with .mx-field--wide. */
.mx-fields {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  align-items: start;
  min-width: 0;
}
.mx-field--wide { grid-column: 1 / -1; }
/* A number, a date or a time is a few characters: an input as wide as the row says otherwise. */
.mx-field input[type='number'],
.mx-field input[type='date'],
.mx-field input[type='time'] { max-width: 15rem; }
.mx-form .mx-actions { margin-top: 2px; }
.mx-field { display: grid; gap: 0.375rem; min-width: 0; }
.mx-field > label, .mx-label {
  display: block;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: var(--mx-label-size);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--mx-label);
}
.mx-field input,
.mx-field select,
.mx-field textarea,
.mx-input {
  width: 100%;
  height: 2.5rem;
  border: 1px solid var(--c-line-strong);
  border-radius: var(--radius-sm);
  background: var(--c-canvas);
  padding: 0 0.75rem;
  font: inherit;
  font-size: 0.875rem;
  color: var(--c-ink);
  transition: border-color 0.15s ease;
}
.mx-field textarea { height: auto; min-height: 6rem; padding: 0.625rem 0.75rem; line-height: 1.5; }
.mx-field input:focus, .mx-field select:focus, .mx-field textarea:focus, .mx-input:focus {
  border-color: var(--c-accent);
  outline: none;
}
.mx-field input[aria-invalid='true'], .mx-field select[aria-invalid='true'] { border-color: var(--c-rec); }
.mx-hint { margin: 0; font-size: 0.75rem; color: var(--c-ink-subtle); }
.mx-err { margin: 0; font-size: 0.7812rem; color: var(--c-rec); }
.mx-alert {
  margin: 0;
  border: 1px solid color-mix(in srgb, var(--c-rec) 40%, transparent);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--c-rec) 10%, transparent);
  padding: 0.625rem 0.75rem;
  font-size: 0.8438rem;
  color: var(--c-ink);
}
.mx-note {
  margin: 0;
  border: 1px solid color-mix(in srgb, var(--c-ok) 40%, transparent);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--c-ok) 10%, transparent);
  padding: 0.625rem 0.75rem;
  font-size: 0.8438rem;
  color: var(--c-ink);
}
.mx-row { display: flex; flex-wrap: wrap; gap: 0.875rem; }
.mx-row > * { flex: 1 1 200px; }

/* ── Tables ──────────────────────────────────────────────────────────────
   A table is the one thing allowed to be wider than the page, inside its own scroller. */
.mx-scroll { overflow-x: auto; }
.mx-table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
.mx-table th {
  text-align: start;
  padding: 0.625rem 0.875rem;
  border-bottom: 1px solid var(--c-line);
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: var(--mx-label-size-sm);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--mx-label);
  white-space: nowrap;
}
.mx-table td { padding: 0.75rem 0.875rem; border-bottom: 1px solid var(--c-line); color: var(--c-ink); vertical-align: top; }
.mx-table tr:last-child td { border-bottom: 0; }
/* A row answers the pointer (Chris, 2026-09-21: "if a table anywhere has rows, i want the
   row's bg to slightly change on hover"): a faint wash of ink, so it reads on either theme and
   on any surface the table sits on. Only where there is a real pointer. */
.mx-table td { transition: background-color 0.12s ease; }

/* A chart's zoom, in its card's corner (<ChartZoom> in @mixtape/ui/chart): ‹ 3M ›. Quiet until
   pointed at, like the rest of a card's header. */
.mx-chart-zoom button,
.mx-chart-zoom select {
  font: inherit;
  font-size: 0.75rem;
  color: var(--c-ink-muted);
  background: transparent;
  border: 0;
  cursor: pointer;
  transition: color 0.12s ease, background-color 0.12s ease;
}
.mx-chart-zoom select {
  appearance: none;
  -webkit-appearance: none;
  padding: 0.1875rem 0.5rem;
  border-radius: 0.375rem;
  font-family: var(--font-mono, ui-monospace, monospace);
  letter-spacing: 0.06em;
  text-align: center;
}
.mx-chart-zoom button:disabled { opacity: 0.35; cursor: default; }
@media (hover: hover) {
  .mx-chart-zoom button:hover:not(:disabled),
  .mx-chart-zoom select:hover { color: var(--c-ink); background: var(--c-surface-2); }
}
@media (hover: hover) {
  .mx-table tbody tr:hover td { background: color-mix(in srgb, var(--c-ink) 4%, transparent); }
}
.mx-table td .sub { display: block; margin-top: 2px; font-size: 0.75rem; color: var(--c-ink-subtle); }
.mx-table .num { text-align: end; font-variant-numeric: tabular-nums; }
.mx-table a { color: var(--c-ink); text-decoration-color: var(--c-line-strong); text-underline-offset: 3px; }
.mx-table a:hover { color: var(--c-accent); }

.mx-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  border: 1px solid var(--c-line);
  border-radius: 999px;
  padding: 3px 0.625rem;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: var(--mx-label-size-sm);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--c-ink-muted);
  white-space: nowrap;
}
.mx-badge--accent { border-color: color-mix(in srgb, var(--c-accent) 30%, transparent); color: var(--c-accent); }
.mx-badge--ok { border-color: color-mix(in srgb, var(--c-ok) 30%, transparent); color: var(--c-ok); }
.mx-badge--warn { border-color: color-mix(in srgb, var(--c-warn) 35%, transparent); color: var(--c-warn); }
.mx-badge--rec { border-color: color-mix(in srgb, var(--c-rec) 35%, transparent); color: var(--c-rec); }

/* What an empty list says for itself, rather than a blank box. */
.mx-empty {
  margin: 0;
  padding: 2px 0 0.25rem;
  font-size: 0.8438rem;
  line-height: 1.55;
  color: var(--c-ink-subtle);
}
/* A whole page with nothing on it is the one place a centred, roomier empty state belongs. */
.mx-panel__body--flush .mx-empty,
.mx-page > .mx-empty { padding: 1.75rem 1rem; text-align: center; }
.mx-sub { margin: 0; font-size: 0.7812rem; line-height: 1.55; color: var(--c-ink-subtle); }
.mx-stats + .mx-sub { margin-top: 0.875rem; }
.mx-sub + .mx-sub { margin-top: 0.375rem; }
/* A panel body is a stack: whatever is in it, it breathes the same. */
.mx-panel__body > * + * { margin-top: 0.75rem; }

/* ── Touch ───────────────────────────────────────────────────────────────
   THIS LAYER WAS MEASURED OFF A LAPTOP, and it shows: a button comes out about 39px tall,
   a field is 40px, and both carry 14px text. On a mouse that is right. On a phone it is
   two separate bugs, and Voks found them - a product entered by scanning a sticker on an
   office wall, where every control is touched and nothing is clicked.

   44px is the touch minimum (Apple's number; Google says 48). 16px is not a taste: iOS
   Safari zooms the whole page in when a field with smaller text takes focus, and the way
   back out is a pinch - which is why the control font is one of the few px values the kit
   is allowed to keep.

   Gated on a coarse pointer so a desktop keeps the tighter scale it was designed at.
   `hover: none` as well, because a laptop with a touchscreen reports a coarse pointer for
   its finger and still wants the mouse-sized chrome. */
@media (hover: none) and (pointer: coarse) {
  .mx-btn { min-height: 44px; }
  .mx-field input,
  .mx-field select,
  .mx-input { min-height: 44px; }
  .mx-field input,
  .mx-field select,
  .mx-field textarea,
  .mx-input { font-size: 16px; }
  .mx-nav a,
  .mx-tabs a,
  .mx-brand { min-height: 44px; }
}


@font-face{font-family:"Geist";src:url("/s/a369fcf5628ea2aa.woff2") format("woff2-variations");
  font-weight:100 900;font-style:normal;font-display:swap}
@font-face{font-family:"Geist Mono";src:url("/s/fba8f577f38a2bbc.woff2") format("woff2-variations");
  font-weight:100 900;font-style:normal;font-display:swap}

/* EVERY LENGTH BELOW IS IN rem, AND THAT IS NOT A STYLE PREFERENCE.
   Interface size in the settings menu is a root font-size (tokens.css, [data-scale]), so a
   stylesheet written in px ignores the setting completely. Dayrule shipped exactly that bug
   - its sales pages resized and its signed-in pages sat still - and Chris found it. What
   stays px on purpose: hairlines and rings (1-3px), the radii, media-query widths, and the
   44px touch minimum. Print is in mm and pt, because px on paper is whatever the driver
   feels like. 1rem = 16px. */

:root{
  --font-geist-sans:"Geist";
  --font-geist-mono:"Geist Mono";
  /* this product's names for the platform tokens */
  --ink:var(--c-ink);
  --ink-2:var(--c-ink-muted);
  --muted:var(--c-ink-subtle);
  --line:var(--c-line);
  --line-2:var(--c-line-strong);
  --bg:var(--c-canvas);
  --card:var(--c-surface);
  --sunk:var(--c-surface-sunk);
  --link:var(--c-accent);
  --tap:44px;
  /* How long a line of prose may get. See the note on the measure further down. */
  --vk-measure:74ch;
  --r:14px;
  --r-sm:10px;
  --sh-1:0 1px 2px rgba(0,0,0,.05),0 1px 3px rgba(0,0,0,.04);
  /* Neutral by default; a zone page overrides all four in <head>. Neutral has to mean
     *invisible* here, because this is what paints the rule under the top bar on every page
     that has no zone - and colour means zone, nothing else. */
  --accent:var(--c-line-strong);
  --accent-ink:var(--c-ink);
  --accent-soft:var(--c-surface-2);
  --accent-line:var(--c-line-strong);
}

/* THIS PRODUCT'S ACCENT. Orange belongs to mixtape itself; every product carries its own
   (studio rule, 2026-09-16). Voks is violet: it sits with the two zone colours - green idea
   wall, blue leadership board - where the amber fought them, and it is nobody else's. Four
   tokens on :root and the whole kit follows: buttons, pills, ticks, focus rings, the
   settings menu. Measured, not picked by eye: as text 7.2:1 on the dark canvas and 5.5:1 on
   paper; the ink on the fill 6.7:1 and 5.7:1 - both past the 4.5:1 a button label needs. */
/* The dark rule repeats tokens.css's own selector list on purpose: a bare :root loses to its
   :root[data-theme="dark"], which left this page on mixtape's orange in dark mode. */
:root,:root[data-theme="dark"]{--c-accent:#a78bfa;--c-accent-strong:#c4b5fd;--c-accent-ink:#17102e;
  --c-accent-soft:#a78bfa1f}
:root[data-theme="light"]{--c-accent:#7c3aed;--c-accent-strong:#6d28d9;--c-accent-ink:#fff;
  --c-accent-soft:#7c3aed1f}
@media (prefers-color-scheme:light){
  :root:not([data-theme]){--c-accent:#7c3aed;--c-accent-strong:#6d28d9;--c-accent-ink:#fff;
    --c-accent-soft:#7c3aed1f}
}

/* THE TWO ZONE COLOURS IN THE DARK.
   ZONE_META carries the light values and injects them inline in <head> - they are literal
   hexes there because the poster, the theme-color meta and layout.test.mjs all need a real
   colour, not a var(). Dark needs its own four, and they cannot go in that inline block
   because it has no media query. So the zone is named on <html> and matched here, at a
   higher specificity than the inline :root rule, which is what lets it win. */
:root[data-theme="dark"][data-zone="idea-box"]{--accent:#3ecf8e;--accent-ink:#7fe3b6;
  --accent-soft:#10231b;--accent-line:#1f4a37}
:root[data-theme="dark"][data-zone="managers-board"]{--accent:#6ea8f0;--accent-ink:#9cc4f6;
  --accent-soft:#141d2b;--accent-line:#2a3f5c}
@media (prefers-color-scheme:dark){
  :root:not([data-theme])[data-zone="idea-box"]{--accent:#3ecf8e;--accent-ink:#7fe3b6;
    --accent-soft:#10231b;--accent-line:#1f4a37}
  :root:not([data-theme])[data-zone="managers-board"]{--accent:#6ea8f0;--accent-ink:#9cc4f6;
    --accent-soft:#141d2b;--accent-line:#2a3f5c}
}

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
body{margin:0;background:var(--bg);color:var(--ink);
  font:1rem/1.6 var(--font-sans,"Geist",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif);
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
a{color:var(--link)}
img,svg{max-width:100%}

/* ------------------------------------------------------- the zone's colour
   THE FRAME IS THE KIT'S (app.css): .mx-bar, .mx-tabs, .mx-page, .mx-panel, .mx-btn,
   .mx-field, .mx-badge. Everything below is the part that is genuinely this product's.

   Colour means zone and nothing else, and a wall says which wall it is through its own
   badge, the score on an idea and the button that spends a point. Nothing else - and in
   particular not the bar: a 3px slab under the header made the two wall pages look like a
   different product from every other page, which is the opposite of wearing one kit. */
/* ONE WIDTH FOR THE BOXES, A MEASURE FOR THE WORDS.
   Every block on a page runs the full width of the bar above it - that is the rule. What
   does not run the full width is a line of prose: past about 75 characters the eye loses
   the start of the next line, which is why the kit's own .mx-lead is capped at 62ch. So the
   box reaches both edges and the sentence inside it stops where it should. */
.vk-warn p,.vk-locked p,.mx-alert p,.mx-note p,.vk-post__body p,.vk-why,.mx-card__text,
.vk-row__body p,.mx-panel__body > p{max-width:var(--vk-measure)}
/* A company's name is never 1100px of input. The panel keeps the page's width - every box
   does - and the control inside it stays the size of the thing you type into it. */
.vk-auth .mx-field input{max-width:22rem}
.vk-steps{margin:0;padding-left:1.25rem;display:grid;gap:.375rem;max-width:var(--vk-measure)}
.vk-badges{display:flex;flex-wrap:wrap;gap:.375rem;margin:0}
.vk-badge--zone{border-color:var(--accent-line);background:var(--accent-soft);color:var(--accent-ink)}
.vk-btn--zone{background:var(--accent);border-color:var(--accent);color:var(--c-canvas)}
.vk-btn--zone:hover{background:var(--accent-ink);border-color:var(--accent-ink)}

/* ------------------------------------------------------------------- walls */
.vk-walls{margin-bottom:1.5rem}
.vk-wall{border-left:6px solid var(--accent);text-decoration:none;color:inherit;display:block}
.vk-wall:hover{border-color:var(--line-2);border-left-color:var(--accent)}
.vk-wall .mx-card__title{display:flex;align-items:center;gap:.5rem;font-size:1.0625rem;
  color:var(--accent-ink)}
.vk-wall__go{margin-left:auto;color:var(--muted);font-weight:400}
.vk-wall .vk-badges{margin-top:.625rem}

/* ------------------------------------------------ the three standing blocks
   Warn is a warning, locked is not. A read-only page is information, and dressing it in
   amber next to a real warning is how an app that is working reads as an app that is
   broken. The kit has the red alert and the green note; these two are ours. */
.vk-warn{border:1px solid color-mix(in srgb,var(--c-warn) 34%,transparent);
  border-left:4px solid var(--c-warn);border-radius:var(--r-sm);
  background:color-mix(in srgb,var(--c-warn) 12%,var(--card));
  color:var(--ink-2);padding:.75rem .875rem;font-size:.875rem;line-height:1.55}
.vk-warn strong{display:block;margin-bottom:.25rem;color:var(--c-warn)}
.vk-warn p,.vk-locked p,.mx-note p,.mx-alert p{margin:.5rem 0 0}
.vk-warn p:last-child,.vk-locked p:last-child,.mx-note p:last-child,.mx-alert p:last-child{margin-bottom:0}
.vk-locked{border:1px solid var(--line);border-left:4px solid var(--line-2);
  border-radius:var(--r-sm);background:var(--card);box-shadow:var(--sh-1);
  padding:.75rem .875rem;font-size:.875rem;line-height:1.55;color:var(--ink-2)}
.vk-locked strong{display:block;margin-bottom:.1875rem;color:var(--ink)}
.vk-sectionhead{display:flex;align-items:baseline;gap:.625rem;margin:1.75rem 0 .75rem}
.vk-sectionhead h2{margin:0;font-size:1.1875rem;line-height:1.3;letter-spacing:-.01em;font-weight:700}

/* --------------------------------------------------------------- the points
   The one number this product produces, and the one an employee checks first. */
.vk-budget__row{display:flex;align-items:baseline;gap:.5rem;flex-wrap:wrap}
.vk-budget__big{font-size:1.375rem;font-weight:750;letter-spacing:-.02em;color:var(--accent-ink)}
.vk-budget__bar{display:block;height:.5rem;border-radius:999px;background:var(--c-surface-2);
  overflow:hidden}
.vk-budget__bar i{display:block;height:100%;background:var(--accent);border-radius:999px}

/* ------------------------------------------------------------------- ideas */
.vk-idea__head{display:flex;align-items:center;gap:.75rem}
.vk-score{flex:0 0 auto;display:flex;align-items:baseline;gap:.375rem;
  background:var(--accent-soft);border:1px solid var(--accent-line);
  border-radius:var(--r-sm);padding:.375rem .6875rem}
.vk-score b{font-size:1.3125rem;line-height:1.1;font-weight:750;letter-spacing:-.02em;
  color:var(--accent-ink);font-variant-numeric:tabular-nums}
.vk-score span{font-size:.78125rem;color:var(--accent-ink);opacity:.8}
.vk-byline{font-size:.8125rem;color:var(--muted);min-width:0}
.vk-byline b{display:block;font-weight:650;color:var(--ink-2);font-size:.875rem}
/* THE PAIR IS THE PRODUCT: a problem and the fix for it, and this app will not take one
   without the other. On a phone they stack, in the order they were written. From 60rem - the
   width where a single column of prose starts running past its measure - they sit side by
   side, which is also how they are read: you check the fix against the problem. */
.vk-pair{margin:0}
@media (min-width:60rem){
  .vk-pair{display:grid;grid-template-columns:1fr 1fr;column-gap:2rem;align-items:start}
  .vk-pair dt{margin-top:0}
  .vk-pair dt:nth-of-type(2){grid-column:2;grid-row:1}
  .vk-pair dd:nth-of-type(2){grid-column:2;grid-row:2}
}
.vk-pair dt{font-size:.71875rem;font-weight:750;letter-spacing:.09em;text-transform:uppercase;
  color:var(--accent-ink);margin:.875rem 0 .1875rem}
.vk-pair dt:first-child{margin-top:0}
.vk-pair dd{margin:0}
.vk-pair dd p{margin:0 0 .5rem;line-height:1.6}
.vk-pair dd p:last-child{margin-bottom:0}
.vk-votebar{display:flex;gap:.625rem;align-items:center;flex-wrap:wrap;
  padding-top:.8125rem;border-top:1px solid var(--line)}
.vk-votebar form{margin:0}
/* The logged-out / no-budget state: a sentence that says why and where from, never a
   greyed-out button that reads as a broken app. */
.vk-why{font-size:.84375rem;line-height:1.5;color:var(--ink-2);margin:0}
.vk-why b{color:var(--ink)}

/* ------------------------------------------------------- the managers' wall */
.vk-post__meta{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;font-size:.8125rem;
  color:var(--muted);margin:0}
.vk-post__who{font-weight:650;color:var(--ink-2)}
.vk-post__body p{margin:0 0 .625rem;line-height:1.6}
.vk-post__body p:last-child{margin-bottom:0}

/* -------------------------------------------------------------- moderation
   A hidden post is a line, not a hole: the tombstone keeps its place on the wall and says
   who took it down and why. The moderator's tools sit in one quiet row under the post,
   the hide form behind a native disclosure. */
.vk-modbar{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem}
.vk-modbar form{margin:0}
.vk-mod{min-width:0}
.vk-mod > summary{list-style:none;cursor:pointer;display:inline-flex}
.vk-mod > summary::-webkit-details-marker{display:none}
.vk-mod[open] > summary{color:var(--c-ink)}
.vk-mod__form{display:flex;flex-wrap:wrap;align-items:flex-end;gap:.5rem;margin-top:.5rem}
.vk-mod__form .mx-field{flex:1 1 16rem}
.vk-mod__said{margin-top:.625rem;padding:.75rem .875rem;border:1px dashed var(--line-2);
  border-radius:var(--r-sm);color:var(--ink-2)}
.vk-hidden{background:var(--sunk);border-style:dashed;box-shadow:none}
.vk-tomb{margin:0;font-size:.875rem;color:var(--muted)}
.vk-tomb b{color:var(--ink-2);font-weight:650}
.vk-tomb__why{margin:.375rem 0 0;font-size:.9375rem;color:var(--ink-2);max-width:var(--vk-measure)}

/* ------------------------------------------------------------------- admin
   A row per person, not a table: this console is opened on a phone as often as on a
   laptop, and a table of five columns on a 375px screen is a horizontal scroll nobody
   finds. */
.vk-rows{display:grid;gap:.625rem}
.vk-row{background:var(--card);border:1px solid var(--line);border-radius:var(--r);
  padding:.875rem .9375rem}
.vk-row__name{margin:0;font-weight:650;font-size:1rem}
.vk-row .mx-sub{margin-top:.125rem;word-break:break-word}
.vk-row__body p{margin:.5rem 0 0;font-size:.875rem;line-height:1.55;color:var(--ink-2)}
.vk-row__acts{display:flex;flex-wrap:wrap;gap:.625rem;align-items:flex-end;margin-top:.75rem}
.vk-row__acts form{margin:0;display:flex;gap:.5rem;align-items:flex-end;flex-wrap:wrap}
.vk-row__acts .mx-field{margin:0}
.vk-row__acts select{min-width:9.375rem}
.vk-link{margin-top:.625rem;background:var(--sunk);border:1px solid var(--line);
  border-radius:var(--r-sm);padding:.625rem .6875rem;
  font-family:var(--font-mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);
  font-size:.78125rem;word-break:break-all;color:var(--ink-2)}
.vk-linklist{width:100%;min-height:10rem;padding:.625rem .75rem;border:1px solid var(--c-line);
  border-radius:var(--radius-sm,10px);background:var(--c-surface-sunk);color:var(--c-ink);
  font-family:var(--font-mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);
  font-size:.78125rem;line-height:1.5}

/* ---------------------------------------------------------------- the foot
   Who runs this and how to reach them, on every page of the app. */
.vk-foot{color:var(--muted);font-size:.8125rem;line-height:1.55;border-top:1px solid var(--line);
  padding-top:1rem;margin-top:2.25rem}
.vk-foot p{margin:0}
.vk-foot b{color:var(--ink-2)}

/* ----------------------------------------------------------------- landing */
/*
 * THE SALES PAGE IS THE ONE PAGE HERE THAT IS NOT THE APP, and it is built on the
 * platform's landing layer (static/landing.css): bands, glass cards, the bento, the
 * animated call to action and the looping vignettes all come from there, and its header is
 * the kit's own .lp-header rather than anything written here.
 *
 * WHAT IT DELIBERATELY DOES NOT TAKE is the half of that layer that needs JavaScript beyond
 * the settings menu: the WebGL ribbon and the scripted product demo. This app ships no
 * client-side JavaScript of its own, says so on this very page in the feature list, and
 * sells its anonymity on being checkable rather than promised.
 */
.lp{--lp-max:980px}
.lp-main{display:block}
/* Hungarian headlines are long. The kit tops out at 4.5rem, which puts this one on three
   lines with an em dash opening the last of them; 3.25rem keeps it to two. */
.lp .lp-h1{font-size:clamp(2rem,4.6vw,3.25rem)}

/* The two zone colours as a local accent override, which is how the design system says a
   product carries its own colour: set --c-accent* on a wrapper and inherit everything
   else. Dark first, then the light override, in the order tokens.css uses. */
.z-idea{--c-accent:#3ecf8e;--c-accent-strong:#6fe0ac;--c-accent-ink:#08120d;
  --c-accent-soft:#3ecf8e1f}
.z-manager{--c-accent:#6ea8f0;--c-accent-strong:#96c1f6;--c-accent-ink:#08101c;
  --c-accent-soft:#6ea8f01f}
:root[data-theme="light"] .z-idea{--c-accent:#157a55;--c-accent-strong:#0e5b3e;--c-accent-ink:#fff;
  --c-accent-soft:#157a551f}
:root[data-theme="light"] .z-manager{--c-accent:#2f5d9e;--c-accent-strong:#23497d;--c-accent-ink:#fff;
  --c-accent-soft:#2f5d9e1f}
@media (prefers-color-scheme:light){
  :root:not([data-theme]) .z-idea{--c-accent:#157a55;--c-accent-strong:#0e5b3e;--c-accent-ink:#fff;
    --c-accent-soft:#157a551f}
  :root:not([data-theme]) .z-manager{--c-accent:#2f5d9e;--c-accent-strong:#23497d;--c-accent-ink:#fff;
    --c-accent-soft:#2f5d9e1f}
}

/* Inside a vignette: the score column of the wall, which is the one number this product
   actually produces. Tabular figures so the rows do not jiggle as it animates. */
.viz-score{font-variant-numeric:tabular-nums;font-weight:750;color:var(--c-accent)}
.viz-row-label{overflow:hidden;text-overflow:ellipsis}

.ticks{list-style:none;margin:0;padding:0;display:grid;gap:.625rem}
.ticks li{position:relative;padding-left:1.625rem;font-size:.96875rem;line-height:1.55;color:var(--ink-2)}
.ticks li::before{content:"\2713";position:absolute;left:0;top:0;color:var(--c-accent);font-weight:750}
.ticks li b{color:var(--ink)}
.legal{background:var(--sunk);border:1px solid var(--line-2);border-radius:var(--r);
  padding:1.125rem;margin:1.375rem 0 0;font-size:.90625rem;line-height:1.6;color:var(--ink-2)}
.legal strong{display:block;margin-bottom:.375rem;color:var(--ink);font-size:.9375rem}
.legal p{margin:0 0 .625rem}
.legal p:last-child{margin-bottom:0}

code,.mono{font-family:var(--font-mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);
  font-size:.8125rem;word-break:break-all}

/* ----------------------------------------------------------------- poster */
/* Black on white only: this sheet has to come out of the cheapest office laser
   printer in the building. No accent colour, no grey backgrounds, no icons. */
.sheet{background:#fff;border:1px solid var(--line);border-radius:var(--r);
  box-shadow:var(--sh-1);padding:1.5rem 1rem 1.625rem;text-align:center;color:#000}
.sheet h1{font-size:1.875rem;line-height:1.1;margin:0 0 .25rem;color:#000}
.sheet .ws{font-size:1.0625rem;font-weight:650;line-height:1.3;margin:0 0 1.25rem;color:#000}
.sheet .qr{display:block;width:100%;max-width:18.75rem;height:auto;margin:0 auto 1.125rem}
.sheet .say{font-size:1rem;line-height:1.45;font-weight:600;margin:0 0 .75rem;color:#000}
.sheet .url{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.8125rem;
  word-break:break-all;margin:0;color:#000}
.sheet .zonerow{font-size:.8125rem;line-height:1.5;margin:1rem 0 0;color:#000}
.sheet .zonerow b{font-weight:650}

@media (min-width:768px){
  .vk-sectionhead h2{font-size:1.375rem}
}

/* ---------------------------------------------------------------- printing */
/* THE POSTER IS THE PRODUCT'S FRONT DOOR, so this block is not decoration.
   A4 portrait with a 14 mm margin leaves 182 x 269 mm of paper. The QR is set to
   150 mm across, which is the number that decides whether it works on a wall: the
   rule of thumb for a phone camera is that it reads a code from about ten times its
   width, so 150 mm is ~1.5 m, and a modern phone does better than that. Everything is
   sized in mm or pt here for the same reason - px on paper is whatever the driver
   feels like. Note what CSS cannot do: the browser's own header and footer (the URL
   and the date) are a setting in the print dialog, not something a page can remove. */
@page{size:A4 portrait;margin:14mm}
@media print{
  .mx-bar,.mx-tabs,.vk-foot,.vk-votebar,.mx-page__head,.noprint{display:none!important}
  body{background:#fff;font-size:12pt}
  .mx-panel,.mx-card{box-shadow:none;break-inside:avoid}
  .mx-main{padding:0}
  .mx-page{max-width:none;padding:0;margin:0}
  .sheet{border:0;box-shadow:none;border-radius:0;margin:0;padding:0;
    break-inside:avoid;page-break-inside:avoid}
  .sheet h1{font-size:32pt;margin-bottom:2mm}
  .sheet .ws{font-size:16pt;margin-bottom:12mm}
  .sheet .qr{width:150mm;height:150mm;max-width:none;margin:0 auto 12mm}
  .sheet .say{font-size:15pt;margin-bottom:6mm}
  .sheet .url{font-size:11pt}
  .sheet .zonerow{font-size:10pt;margin-top:8mm}
}
