/* Geist, self-hosted. Generated by packages/ui/scripts/kit.mjs - do not edit.
   Link this BEFORE tokens.css. Inside the monorepo you do not need it: next/font loads the
   same family and sets the same two variables. */

@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;
}
:root {
  --font-geist-sans: 'Geist';
  --font-geist-mono: 'Geist Mono';
}

/* 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), var(--font-script, 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;
  /* A destructive button's fill: the light theme's red in BOTH themes, because the dark theme's
     #f0604d carries a white label at only 3.2:1. White on #c3402c is 5.1:1, on the hover 6.6:1,
     and the button still stands at 3.8:1 against the dark page (2026-10-05, task #1304). Defined
     here once; the light blocks inherit it. */
  --c-danger: #c3402c;
  --c-danger-strong: #a8331f;
  --c-danger-ink: #ffffff;
  --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");
  /* Elevation for a window over the page (a confirm, a dialog): a colour, never #000 - deep in the dark, ink on paper. */
  --shadow-lift: 0 24px 60px color-mix(in srgb, var(--c-canvas) 55%, transparent);
}

/* ── 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");
  /* Elevation for a window over the page (a confirm, a dialog): a colour, never #000 - deep in the dark, ink on paper. */
  --shadow-lift: 0 24px 60px color-mix(in srgb, var(--c-ink) 22%, transparent);
}

/* 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");
    --shadow-lift: 0 24px 60px color-mix(in srgb, var(--c-ink) 22%, transparent);
  }
}

/* ── 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-inline-end: 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. */
}

/* An always-dark surface - the photo viewer - and what sits on it, the same in both themes. Named
   here so the app layer names no colour of its own (the gate's colour check reads a product's
   vendored app.css; RentKeeper went red on the viewer's literal #050505/#fff, 2026-10-05). */
:root {
  --c-scrim: #050505;
  --c-scrim-ink: #ffffff;
}

/* ── The base every page wears ──────────────────────────────────────────────
   The landing and app layers are written for border-box, a body with no margin, and the kit's
   face on the body - and until 2026-10-05 the kit never said so: Dayrule wrote it into its own
   sheet, and RentKeeper did not, so it went live in the browser's Times with every box a little
   wider than its column. Here once, for every product. `:where()` keeps it at zero
   specificity: a product's own body rule, Tailwind's preflight (Studio) or a class still wins. */
:where(*, *::before, *::after) { box-sizing: border-box; }
/* `hidden` always hides. The browser's own [hidden] rule loses to any layout that sets display - a
   table restacked as rows on a phone (`tr { display: block }`) showed every row the kit's filter had
   hidden, while the laptop showed two (2026-10-05). The kit's filter, sort bar and photo picker all
   hide with the attribute, so it has to win. */
[hidden]:not([hidden='until-found']) { display: none !important; }
:where(body) {
  margin: 0;
  background: var(--c-canvas);
  color: var(--c-ink);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
}

/* ── Every script of the kit's 42 languages (mixtape-i18n.mjs LANGUAGES, 2026-10-05) ──────────
   The device's own fonts for the scripts no design downloads, in the regional form the page's
   language asks for: one Han character is drawn differently in Japan, Korea, mainland China and
   Taiwan, and only lang tells the browser which. */
:lang(ja) { --font-script: 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', 'Yu Gothic UI', 'Yu Gothic', Meiryo, 'Noto Sans JP', 'Noto Sans CJK JP'; }
:lang(ko) { --font-script: 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR', 'Noto Sans CJK KR'; }
:lang(zh-Hans) { --font-script: 'PingFang SC', 'Microsoft YaHei', 'Noto Sans SC', 'Noto Sans CJK SC'; }
:lang(zh-Hant) { --font-script: 'PingFang TC', 'Microsoft JhengHei', 'Noto Sans TC', 'Noto Sans CJK TC'; }
:lang(th) { --font-script: Thonburi, 'Leelawadee UI', 'Noto Sans Thai'; }
:lang(hi) { --font-script: 'Kohinoor Devanagari', 'Nirmala UI', 'Noto Sans Devanagari'; }
:lang(ar) { --font-script: 'Geeza Pro', 'Segoe UI', 'Noto Sans Arabic'; }

/* Letter-spacing tears Arabic's joined letters apart and breaks Hindi's and Thai's stacked marks,
   and spreads Chinese, Japanese and Korean thin: the kit's spaced capitals (labels, eyebrows,
   badges) and tight display headings are Latin habits. In these scripts, the font's own spacing. */
:where(:lang(ja), :lang(ko), :lang(zh-Hans), :lang(zh-Hant), :lang(th), :lang(hi), :lang(ar)) * { letter-spacing: normal !important; }
/* Marks above and below the line need room a Latin heading's tight line-height does not give. */
:where(:lang(th), :lang(hi), :lang(ar)) :is(h1, h2, h3, .lp-h1, .lp-h2, .lp-h3, .mx-h1, .mx-h2) { line-height: 1.4 !important; }
/* Korean breaks lines between words, as English does - not inside one. */
:lang(ko) { word-break: keep-all; }
:where(:lang(ja), :lang(zh-Hans), :lang(zh-Hant)) { line-break: strict; }

/* Right to left (Arabic): the kit lays out with start and end, and these are the few things that
   only have a direction - a slide, an arrow. --mx-dir reaches inside the menus' shadow roots,
   which cannot see the page's dir attribute. */
:root { --mx-dir: 1; --mx-end: right; }
[dir='rtl'] { --mx-dir: -1; --mx-end: left; }

/* DAYRULE'S ACCENT — the one file in this product allowed to name a colour.
   Orange is mixtape's own; every product carries its own (studio rule, 2026-09-16, see
   https://mixtape.works/kit/DESIGN.md). Dayrule is teal: it is a calendar product, and teal
   reads as time rather than as alarm, which red-orange does.

   The dark rule repeats tokens.css's own selector list on purpose. A bare :root loses to its
   :root[data-theme="dark"], and the product would silently keep mixtape's orange in the dark
   theme with nothing erroring.

   Measured, not chosen by eye: as text 10.6:1 on the dark canvas and 5.3:1 on paper; the ink
   on the fill 9.1:1 and 5.5:1 - both well past the 4.5:1 a button label needs.

   --c-accent-shine is the middle of the sign-up button's flowing gradient: sky, teal's neighbour.
   Without it the kit mixes the accent with its red, and teal plus red is brown-olive (Chris,
   2026-09-30: "the brown in dayrule's cta btn im not a fan of"). The label on it: 7.9:1 dark,
   5.9:1 light. */
:root,
:root[data-theme="dark"] {
  --c-accent: #2dd4bf;
  --c-accent-strong: #5eead4;
  --c-accent-ink: #04211d;
  --c-accent-soft: #2dd4bf1f;
  --c-accent-shine: #38bdf8;
}
:root[data-theme="light"] {
  --c-accent: #0f766e;
  --c-accent-strong: #115e59;
  --c-accent-ink: #ffffff;
  --c-accent-soft: #0f766e1f;
  --c-accent-shine: #0369a1;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    --c-accent: #0f766e;
    --c-accent-strong: #115e59;
    --c-accent-ink: #ffffff;
    --c-accent-soft: #0f766e1f;
    --c-accent-shine: #0369a1;
  }
}

/* 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));
  /* The chosen half of a segmented switch (Yearly | Monthly). In dark the plain surface is the
     track's own colour, so the chosen one did not read as chosen (Chris, 2026-10-15): lifted. */
  --lp-seg-on: color-mix(in srgb, var(--c-ink) 18%, var(--c-surface));
  --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-seg-on: var(--c-surface);
  --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-seg-on: var(--c-surface);
    --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; }

/* ── Boxed tinted bands (Chris 2026-10-21: "lets keep it (for now)") ─────
   On a tablet and a desktop a tinted band no longer fills the page's width: its content sits in
   one big rounded box, the content's width, wearing the veil the band used to. A phone keeps the
   full-width band. Everything about it is this one block.
   TO REVERT to full-width tinted bands: delete this block, rebuild the kit, re-vendor
   landing.css into every product and publish them (DESIGN.md "Bands"). Nothing else depends on it.
   Two classes, so the tablet/phone `.lp-band { padding-block: 3rem }` further down cannot win. */
@media (min-width: 576px) {
  .lp-band.lp-band--tint { background: none; padding-block: 0.75rem; }
  .lp-band--tint > .lp-container {
    width: min(var(--lp-max), calc(100% - 2 * var(--lp-gutter)));
    padding-block: 3rem;
    border-radius: 24px;
    background: var(--lp-veil);
  }
  .lp-band--tint.lp-band--tight > .lp-container { padding-block: 2rem; }
  /* A closing call to action that IS the band's container (<div class="lp-container lp-final">)
     gets the box's room too: `.lp-final:first-child { padding-block: 0 }` below leaves the band
     to pad it, and a boxed band does not - its heading sat on the box's top edge (2026-10-21). */
  .lp-band--tint > .lp-container.lp-final { padding-block: 3rem; }
}
@media (min-width: 992px) {
  .lp-band.lp-band--tint { padding-block: 1rem; }
  .lp-band--tint > .lp-container { padding-block: 4rem; border-radius: 28px; }
  .lp-band--tint.lp-band--tight > .lp-container { padding-block: 2.5rem; }
  .lp-band--tint > .lp-container.lp-final { padding-block: 4rem; }
}

/* A document page - privacy, terms, refunds, help - in the landing frame: the fixed header's
   clearance, a reading measure, text from the left. Every product used to invent its own
   (Dayrule `section.page`, Storefront `.sn-doc`), and RentKeeper's did it by padding the kit's
   own .lp-container, which also pushed its landing hero 130px down on phones (2026-10-05).
   <section class="lp-doc"><div class="lp-container"> ... </div></section> */
.lp-doc { padding-block: calc(var(--lp-header-h) + 3rem) 4rem; }
.lp-doc > .lp-container { max-width: 46rem; }
/* A document reads as paragraphs and blocks with room between them; the first card or grid after the
   text gets a little more, so it does not sit on the line above it (RentKeeper's privacy page). */
.lp-doc > .lp-container > * + * { margin-top: 1rem; }
.lp-doc > .lp-container > * + :is(.lp-bento, .lp-grid, .lp-faq, .lp-card, .mx-panel) { margin-top: 2rem; }
@media (max-width: 575.98px) { .lp-doc { padding-block: calc(var(--lp-header-h) + 2rem) 3rem; } }

/* ── 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;
  /* As wide as its words, never as wide as its container: a grid or a stretched flex column
     would otherwise give it the whole row. Once it wraps (a Hungarian eyebrow on a phone) the
     browser still sizes the box to the full row however short the balanced lines are - the kit
     script measures the lines and narrows it (kit/pills.ts). */
  width: fit-content;
  max-width: 100%;
  justify-self: 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;
  inset-inline-start: 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 is `--c-accent-shine`, a neighbour of the accent's own hue. Unset, it is
     mixtape's amber mixed toward red, which stays warm. Mixing ANY accent with red is not safe:
     Dayrule's teal went brown-olive (Chris, 2026-09-30, "the brown... im not a fan of"), as
     `--c-go` green once did against warm paper. A product sets its shine next to its accent;
     a design's accent gets one from kit/design.ts shineFor(). Check a gradient in BOTH themes,
     because the one that fails is never the one you designed it in.
     app.css `.mx-cta` is this same button for an app screen (Studio's Continue): change both. */
  background: linear-gradient(
    120deg,
    var(--c-accent-strong) 0%,
    var(--c-accent) 25%,
    var(--c-accent-shine, 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-inline-start: -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; justify-self: center; margin-bottom: 1.5rem; }
/* THE PILL IS THE HERO'S (Chris, 2026-09-28: "only the hero cta can have a pill, all other
   section's headings dont need a pill"). A section's title stands on its own; an eyebrow above
   every one of them is a row of identical badges saying nothing the title does not. Kept out
   here rather than trusted to every product's markup, so a page written before the rule - or
   by a Builder that missed it - still shows one pill, at the top. */
.lp-eyebrow:not(.lp-hero .lp-eyebrow) { display: none; }
.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-inline-end: auto; }
.lp-spotlight--flip .lp-spotlight__demo { margin-inline-start: 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; }
/* Three across on a desktop, for a set of exactly three - two to a row would leave the third
   alone on its own line. Below the breakpoint the bento's own rules apply. */
@media (min-width: 992px) {
  .lp-bento.lp-bento--3 { grid-template-columns: repeat(3, 1fr); }
}
/* Four across on a wide screen, for a list that grows (mixtape.works' products, Chris 2026-10-05):
   a last card on its own stays one column wide - never stretched across the row. */
@media (min-width: 1200px) {
  .lp-bento.lp-bento--4 { grid-template-columns: repeat(4, 1fr); }
}
.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; }
/* The margins are set, not left to the element: a product that marks its card titles up as the
   headings they are (<h3>, which search engines read) got the browser's 1em above and below,
   and Storefront Ninja's bento had a gap under every title that Dayrule's <div>s did not
   (Chris, 2026-10-01). Same look whatever the element. */
.lp-card__title { margin: 0; font-size: 1.0938rem; font-weight: 700; color: var(--c-ink); }
.lp-card__text { margin: 2px 0 0; 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 title written straight into its band's container, without the .lp-head-centre block
   (Hotel, 2026-10-06 - Chris: "gap issues below headings": "What it does" and "How it works" sat
   on their cards, flush left over a centred page). It reads as that block does: centred, its
   paragraph centred under it, and the block's gap before what follows. The closing CTA
   (.lp-final) and the sticky-head grid (.lp-grid__head) keep their own. */
/* A document page's title and lead too (.lp-doc): its body stays left, but its heading reads like every
   other heading on the site (Storefront Ninja mirrored this rule for its own document pages, 2026-10-09 -
   one rule for every product's privacy, terms and help pages instead). */
:is(.lp-band, .lp-doc) > .lp-container:not(.lp-final) > .lp-h2 {
  max-width: 760px;
  margin: 0 auto 2.25rem;
  text-align: center;
}
:is(.lp-band, .lp-doc) > .lp-container:not(.lp-final) > .lp-h2:has(+ :is(.lp-lead, .lp-sub)) { margin-bottom: 0; }
:is(.lp-band, .lp-doc) > .lp-container:not(.lp-final) > .lp-h2 + :is(.lp-lead, .lp-sub) {
  max-width: 760px;
  margin: 0.875rem auto 2.25rem;
  text-align: center;
}
/* 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;
}
/* And they stand further off than a hero's do. A hero's buttons follow straight on from the
   sentence that set them up; a closing offer has said its piece - the eyebrow, the heading,
   the lead and usually the small print - and the button is the answer to all of it, so it
   gets air above it rather than reading as one more line of the paragraph (Chris, 2026-09-22). */
.lp-container:has(> .lp-head-centre) > .lp-hero__actions,
.lp-container:has(> .lp-head-centre) > .lp-actions {
  justify-content: center;
  margin-top: 3.25rem;
}
.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; }
/* What the product gets done, in one bold line above how it works, and what its free plan
   holds, in the product's own colour just above the button (mixtape.works, 2026-10-14). */
.lp-product__headline { margin: 0; font-size: 1.0625rem; font-weight: 600; line-height: 1.35; color: var(--c-ink); text-wrap: balance; }
.lp-product__free { margin: 0; font-size: 0.8125rem; font-weight: 600; color: var(--c-accent); }

/* 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; }
/* Free plus three paid plans (2026-10-13: every product is free forever, with paid plans by size). Four in a row
   on a wide screen, two by two in between, one column on a phone like the others. */
.lp-compare--four { grid-template-columns: repeat(4, 1fr); max-width: 1200px; }
@media (max-width: 1180px) { .lp-compare--four { 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;
  inset-inline-end: 0.875rem;
  /* The letter-spacing also follows the LAST letter, so the end side gives that much back and the
     word sits in the middle of its pill. */
  padding-block: 3px;
  padding-inline: 0.5625rem calc(0.5625rem - 0.07em);
  border-radius: 999px;
  font-size: 0.6562rem;
  font-weight: 700;
  line-height: 1.4;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--c-accent-ink);
  background: var(--c-accent);
}
/* Capitals have no descenders, so a line box centres them high. Where the browser can, the box is cut to
   the capitals and the pill pads them evenly. */
@supports (text-box: trim-both cap alphabetic) {
  .lp-card__badge { text-box: trim-both cap alphabetic; padding-block: 0.375rem; }
}
.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(--lp-seg-on, 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-inline-end: 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; }
/* The questions sit in a centred column, so their heading does too - whether a product wraps it
   in .lp-head-centre or writes it straight into the container (Chris, 2026-09-28: "the heading
   above the q&a sections should always be in the middle"). */
.lp-container:has(> .lp-faq) > :is(.lp-h2, .lp-lead, .lp-eyebrow) { text-align: center; margin-inline: auto; }
.lp-container:has(> .lp-faq) > .lp-h2 { margin-bottom: 2.25rem; }
.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; }
/* The divider and the extra padding set a closing call-to-action apart from the section it
   SHARES a band with - so only when something comes before it. One that opens its own band is
   set apart by the band already; with them it showed a hairline floating a band's padding below
   the edge. That was fixed once for one markup (.lp-container > .lp-final, mixtape.works
   2026-09-21) and came straight back on Storefront Ninja, which writes the two classes on one
   element (2026-09-28) - so the rule now asks what comes before, not how it is nested. */
.lp-final:not(:first-child) { border-top: 1px solid var(--lp-hair); }
.lp-final:first-child { padding-block: 0; }
.lp-final .lp-h2 { margin-top: 0; }
/* Its sentence centred under the heading, whichever text style it wears: .lp-sub has a max-width
   and no auto margins, so in a centred closing section it hung at the left (Hotel, 2026-10-06). */
.lp-final :is(.lp-lead, .lp-sub) { max-width: 720px; margin-inline: auto; }

/* ── 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);
  /* No rule on top: the glass and the band above it meet without a line (Chris, 2026-10-21). */
  -webkit-backdrop-filter: blur(0.75rem);
  backdrop-filter: blur(12px);
}

/* LEGACY - the one-row footer every landing page drew before the kit's own (below). Kept only until each product
   and mixtape.works has moved to landingFooter(); then this block and .lp-footer__links can go.
   The studio line on one side, the page links on the other, wrapping on a phone. Every link a 44px target on
   touch - they were 21px tall on a 390px phone (Hotel, 2026-10-07). */
.lp-footer__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1.5rem;
  font-size: 0.8125rem;
  color: var(--c-ink-subtle);
}
.lp-footer__row p { margin: 0; }
.lp-footer__links { display: flex; flex-wrap: wrap; gap: 0 1.25rem; }
.lp-footer a { color: inherit; text-underline-offset: 3px; }
.lp-footer a:hover { color: var(--c-ink); }
@media (pointer: coarse) {
  .lp-footer a { display: inline-flex; align-items: center; min-height: 44px; }
}

/* ── Landing footer ──────────────────────────────────────────────────────
   The kit's own (Chris, 2026-10-21): server/mixtape-footer.mjs landingFooter(), React LandingFooter.
   Four columns on a desktop - the product with its one-liner and "A product of mixtape.works", the header's
   links, help and legal, the newsletter - two by two on a tablet, one on a phone; below them the copyright and
   four placeholder social icons.
   Its ground follows the section above it: a fill (the tinted bands' veil) after a plain section, none right
   after a tinted band, so two tints never touch and the page never ends on two plain grounds. `lp-footer--plain`
   / `lp-footer--fill` (afterTint) override what the page says. */
.lp-footer--kit {
  padding-block: 3.5rem 2rem;
  background: var(--lp-veil);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--c-ink-muted);
}
:is(main:has(> .lp-band--tint:last-of-type), .lp-band--tint) + .lp-footer--kit:not(.lp-footer--fill),
.lp-footer--kit.lp-footer--plain { background: transparent; }
.lp-footer__grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2.25rem; }
@media (min-width: 576px) {
  .lp-footer__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2.5rem 2rem; }
}
@media (min-width: 992px) {
  .lp-footer__grid { grid-template-columns: minmax(0, 1.45fr) minmax(0, 0.85fr) minmax(0, 0.95fr) minmax(0, 1.55fr); gap: 2.5rem; }
  .lp-footer__grid--three { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr) minmax(0, 1fr); }
}
/* Zero specificity, so every class below sets its own margins over it. */
:where(.lp-footer--kit) p { margin: 0; }
/* The product: its mark beside its name and one-liner; the studio line under the two. */
.lp-footer__id { display: flex; align-items: center; font-size: 1rem; line-height: normal; }
/* The mark and name are the header's own lock-up (.mx-brand / .mx-logo, app.css), at the header's 1rem, not a footer copy
   of their own (Chris, 2026-10-26). The one-liner under them, from the column's own edge (Chris, 2026-10-21). */
.lp-footer__line { max-width: 22rem; margin-top: 0.75rem; }
.lp-footer__of { margin-top: 1rem; color: var(--c-ink-subtle); font-size: 0.8125rem; }
.lp-footer__of a { text-decoration: underline; }
.lp-footer__head {
  margin-bottom: 0.875rem;
  color: var(--c-ink-subtle);
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
/* Arabic is joined script: the monospace face draws it letter by letter, and spacing breaks it apart - so its
   headings take the page's own face, unspaced. */
.lp-footer--kit:lang(ar) .lp-footer__head { font-family: inherit; letter-spacing: 0; text-transform: none; }
.lp-footer__list { display: grid; gap: 0.5rem; margin: 0; padding: 0; list-style: none; }
/* On a touch screen the old footer's 44px link rows (above) spread the lists far apart (Chris, 2026-10-21: "a big gap
   between all the links"): here the rows are 2rem, touching, which keeps a fair target and a tight list. */
@media (pointer: coarse) {
  .lp-footer--kit .lp-footer__list { gap: 0; }
  .lp-footer--kit .lp-footer__list a { min-height: 2rem; }
}
.lp-footer--kit a { color: var(--c-ink-muted); text-decoration-color: color-mix(in srgb, currentColor 35%, transparent); }
.lp-footer__list a { text-decoration: none; }
.lp-footer--kit a:hover { color: var(--c-ink); }
/* The newsletter: the address and its button on one row, the mixtape box and the small print under it. */
.lp-footer__lead { margin-bottom: 0.875rem; }
.lp-footer__news { position: relative; margin: 0; }
.lp-footer__label { display: block; margin-bottom: 0.375rem; color: var(--c-ink-muted); font-size: 0.8125rem; }
.lp-footer__field { display: flex; gap: 0.5rem; }
.lp-footer__input {
  flex: 1;
  min-width: 0;
  height: 2.75rem;
  padding: 0 0.875rem;
  border: 1px solid var(--c-line-strong);
  border-radius: 10px;
  background: var(--c-surface);
  color: var(--c-ink);
  font: inherit;
  font-size: 0.9375rem;
}
.lp-footer__input:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 1px; }
/* The button the field's own height, so the two read as one control. */
.lp-btn.lp-footer__go { flex: none; height: 2.75rem; min-height: 0; padding-block: 0; padding-inline: 1.125rem; font-size: 0.875rem; }
.lp-footer__check { display: flex; align-items: flex-start; gap: 0.5rem; margin-top: 0.75rem; font-size: 0.8125rem; line-height: 1.4; cursor: pointer; }
.lp-footer__check input { flex: none; width: 1rem; height: 1rem; margin: 0.1rem 0 0; accent-color: var(--c-accent); }
/* The honeypot: a field no person sees or reaches, which a form-filling bot does. */
.lp-footer__hp { position: absolute; inset-inline-start: -9999px; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.lp-footer__fine { margin-top: 0.625rem; color: var(--c-ink-subtle); font-size: 0.75rem; }
.lp-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 3rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--lp-hair);
  color: var(--c-ink-subtle);
  font-size: 0.8125rem;
}
/* Placeholders until the accounts exist: no links, nothing to press. */
.lp-footer__social { display: flex; gap: 1rem; color: var(--c-ink-subtle); }
.lp-footer__icon { display: inline-flex; width: 1rem; height: 1rem; }
.lp-footer__icon svg { width: 100%; height: 100%; }

/* ── 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.

   The frame draws no box of its own, and a stage paints no ground: the phone, the browser
   window, whatever the demo shows, stand on the page itself (Chris, 2026-10-20, FleetFault:
   "remove that area around the two screens"). The frame still clips, so a stage keeps what it
   draws clear of its edges by more than its shadow reaches: --lp-demo-shadow, the one shadow
   for a demo's devices, fades out within about 40px at the sides and bottom (the page's
   --shadow-lift, a 60px blur, would end in a hard line there). */
.lp-demo {
  --lp-demo-shadow: 0 12px 32px rgb(0 0 0 / 0.24);
  width: 100%;
}
.lp-demo__fit {
  position: relative;
  width: 100%;
  overflow: hidden;
}
.lp-demo__stage {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: top left;
}
/* The fake cursor: an empty `<span class="lp-cursor" hidden></span>` in the stage, which demo.js
   fills with the kit's two shapes - the arrow, and a pointing finger wherever a finger is what
   would really touch the screen: over a demo's phone (demo.js sets .is-touch while the cursor's
   point is on a .lp-phone), and everywhere on a touch screen, since nobody watching on a phone
   has an arrow (Chris, 2026-10-20). Either one's point is where moveTo() puts it, and the click
   ring opens round that point. Inside a stage, so px. */
.lp-cursor {
  position: absolute;
  top: 0;
  left: 0;
  width: 24px;
  height: 24px;
  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 svg { position: absolute; inset: 0; width: 24px; height: 24px; overflow: visible; }
/* The theme's ink with a canvas edge, so it reads on either ground. */
.lp-cursor path { fill: var(--c-ink); stroke: var(--c-canvas); stroke-width: 1.4; stroke-linejoin: round; stroke-linecap: round; }
.lp-cursor .lp-cursor__knuckles { fill: none; }
.lp-cursor__hand { display: none; }
.lp-cursor.is-click::after {
  content: "";
  position: absolute;
  left: -8.5px;
  top: -8.5px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 2px solid var(--demo-accent, var(--c-accent));
  /* Held at its faded last frame: demo.js only restarts the class, so without it the ring
     came back round the cursor after every click (FleetFault, 2026-10-20). */
  animation: lp-ripple 0.55s ease-out forwards;
}
.lp-cursor.is-touch .lp-cursor__arrow { display: none; }
.lp-cursor.is-touch .lp-cursor__hand { display: block; }
/* The fingertip (8, 2 in its box) on the point, not the box's corner. */
.lp-cursor.is-touch { margin: -2px 0 0 -8px; }
.lp-cursor.is-touch.is-click::after { left: -2px; top: -8px; }
@media (pointer: coarse) {
  .lp-cursor .lp-cursor__arrow { display: none; }
  .lp-cursor .lp-cursor__hand { display: block; }
  .lp-cursor { margin: -2px 0 0 -8px; }
  .lp-cursor.is-click::after { left: -2px; top: -8px; }
}

/* ── Demo phone ──────────────────────────────────────────────────────────
   The phone a demo shows: `<div class="lp-phone"><div class="lp-phone__screen">...`. The kit
   draws it - a thin bezel (4px, Chris 2026-10-20: "lower the border of the phone by more than
   half"), the front camera over whatever the screen shows, the demo shadow; the product places
   and sizes it (position, left, top, width, height) and fills the screen. */
.lp-phone {
  position: absolute;
  padding: 4px;
  border-radius: 44px;
  background: color-mix(in srgb, var(--c-ink) 16%, var(--c-surface));
  box-shadow: inset 0 0 0 1px var(--lp-hair-strong, var(--c-line-strong)), var(--lp-demo-shadow);
}
.lp-phone__screen {
  position: relative;
  height: 100%;
  overflow: hidden;
  border-radius: 40px;
  background: var(--c-canvas);
}
.lp-phone__screen::before {
  content: "";
  position: absolute;
  z-index: 30;
  top: 10px;
  left: 50%;
  width: 84px;
  height: 22px;
  margin-left: -42px;
  border-radius: 12px;
  background: var(--c-scrim, #050505);
}
.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-compare--four,
  .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:not(.lp-footer--kit),
  .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:not(.lp-footer--kit),
  .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: start; }
  .lp-demo { margin-inline: calc(var(--lp-gutter) * -1); width: calc(100% + var(--lp-gutter) * 2); }
}

/* ── Right to left ────────────────────────────────────────────────────────────────────────── */
/* Arrows point the way the page reads, and step that way on hover. */
/* `scale`, not `transform`: it composes with the arrows' own nudge, and turns the nudge the same
   way - an arrow that points left steps left. */
[dir='rtl'] :is(.lp-product__arrow, .lp-btn__arrow) { display: inline-block; scale: -1 1; }
/* The scripted demos are a picture of a screen, with a cursor at fixed points: drawn as they are. */
[dir='rtl'] .lp-demo__stage { direction: ltr; }


/* ── "Made with mixtape.works" ────────────────────────────────────────────
   The one line every page a FREE plan shows the public carries (a shop, a board, a guest page, a report form),
   gone on any paid plan (2026-10-13). Quiet on purpose: it is a thank-you, not an ad. */
.mx-made-with {
  display: block;
  margin: 1.5rem auto 0.75rem;
  text-align: center;
  font-size: 0.75rem;
  color: var(--c-ink-subtle);
}
.mx-made-with a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.mx-made-with a:hover { color: var(--c-ink); }

/* ── The pricing band drawn by server/mixtape-pricing.mjs (2026-10-13) ───────────────────────────────────────
   Free plus paid plans by size, yearly first. Every card is a column, so the buttons line up at the foot. */
.mx-plan__seg { display: flex; justify-content: center; margin-bottom: 1.5rem; }
.mx-plan { display: flex; flex-direction: column; min-width: 0; }
.mx-plan__size { margin: 0.25rem 0 0; color: var(--c-ink-muted); font-size: 0.875rem; }
.mx-plan__price { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 6px; margin: 0.625rem 0 0; color: var(--c-ink); line-height: 1.1; font-variant-numeric: tabular-nums; }
.mx-plan__price strong { font-size: 2.125rem; font-weight: 800; letter-spacing: -0.03em; }
.mx-plan__price .lp-was { margin-inline-end: 0; font-size: 1.25rem; font-weight: 600; }
.mx-plan__per { font-size: 0.9375rem; font-weight: 500; color: var(--c-ink-muted); }
/* Close under the price it explains (Chris, 2026-10-21: the gap was too big, then "even closer, like 5px up"). Free has no billing to explain, but
   keeps the line's height (--none), so every card's list starts at the same height. */
.mx-plan__billed { display: flex; flex-wrap: wrap; align-items: center; gap: 0.375rem; margin: calc(0.125rem - 5px) 0 0; color: var(--c-ink-muted); font-size: 0.875rem; }
.mx-plan__billed--none { visibility: hidden; }
.mx-plan .lp-plan__list { display: grid; gap: 0.5rem; margin-bottom: 1.25rem; }
.mx-plan .lp-plan__list li { position: relative; padding-inline-start: 1.375rem; color: var(--c-ink); font-size: 0.9375rem; }
.mx-plan .lp-plan__list li::before { content: '✓'; position: absolute; inset-inline-start: 0; color: var(--c-accent); font-weight: 700; }
.mx-plan .lp-card__cta { margin-top: auto; }
.mx-plan .lp-card__cta:disabled { opacity: 0.55; cursor: not-allowed; animation: none; box-shadow: none; }
.mx-plan__fine { max-width: 46rem; margin-inline: auto; text-align: center; }
/* The "Launch deal" badge has its own row above the title on every card (empty on Free, hidden while Monthly is
   chosen), so the titles and prices line up across the cards whichever is shown. */
/* The row keeps its height on the Free card too (no badge), so the titles line up; the badge is
   centred in it at its own height, never stretched to fill it (a stretched pill put its word at the top). */
.mx-plan__flag { display: flex; align-items: center; min-height: 1.5rem; margin-bottom: 0.5rem; }
.mx-plan__flag .lp-card__badge { position: static; }

/* 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; }
/* mixtape.works' own lock-up (components/Logo.tsx, and the kit landing footer for mixtape.works): the cassette in the
   accent and the wordmark with a quieter ".works". In rem, so it follows the interface size (Chris, 2026-10-26). */
.mx-logo { display: inline-flex; align-items: center; gap: 0.625rem; }
.mx-logo__mark { width: 1.75rem; height: 1.75rem; flex: none; color: var(--c-accent); }
.mx-logo__word { color: var(--c-ink); font-size: 0.9375rem; font-weight: 600; letter-spacing: -0.015em; line-height: 1.2; }
.mx-logo__tld { color: var(--c-ink-muted); }
.mx-logo__mark > svg { display: block; width: 100%; height: 100%; }
/* 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);
  /* Clear of the home indicator, not a whole band of it - Studio's own bar (safe-b-tight): the
     full inset left an empty strip under the labels. 58px of tabs + 24 = Apple's own 83pt bar. */
  padding-bottom: max(calc(env(safe-area-inset-bottom) - 10px), 4px);
}
.mx-tabs ul {
  display: flex;
  align-items: stretch;
  justify-content: space-around;
  max-width: 35rem;
  /* margin 0: a product without a reset kept the browser's 1em above and below the list, and
     the bar stood 33px taller than its tabs (RentKeeper on Chris's iPhone, 2026-10-05). */
  margin: 0 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, as tall as it really is (kit/frame.ts --mx-tabs-h: a wrapped
     label makes it taller); the bar is gone from `md` up. */
  padding-bottom: calc(var(--mx-tabs-h, calc(3.75rem + env(safe-area-inset-bottom))) + 0.75rem);
}
@media (min-width: 768px) {
  .mx-nav { display: flex; }
  .mx-tabs { display: none; }
  .mx-main { padding-bottom: 2rem; }
}

/* The signed-in frame's footer (2026-10-07): the studio line, Privacy, support. Its own class, so
   no product borrows the landing one (Hotel's .lp-footer on /hotel, eight runs asking for this).
   After .mx-main, it clears the phone's tab bar itself and .mx-main stops padding for it. */
.mx-footer {
  border-top: 1px solid var(--c-line);
  padding: 1.25rem max(var(--lp-gutter, 1.5rem), env(safe-area-inset-left))
    calc(var(--mx-tabs-h, calc(3.75rem + env(safe-area-inset-bottom))) + 0.75rem) max(var(--lp-gutter, 1.5rem), env(safe-area-inset-right));
  font-size: 0.8125rem;
  color: var(--c-ink-subtle);
}
.mx-main:has(+ .mx-footer) { padding-bottom: 1.5rem; }
/* The quiet footer (server/mixtape-footer.mjs; Chris, 2026-10-14: "much more minimal"): three small links on
   one centred line - privacy, help, mixtape.works - no rule above it, no underlines, nothing that reads as a
   paragraph. The product's name is in the bar already. */
.mx-footer--quiet { border-top: 0; padding-top: 0.5rem; font-size: 0.75rem; }
.mx-footer--quiet p { display: flex; flex-wrap: wrap; justify-content: center; gap: 0 1.25rem; margin: 0; }
.mx-footer--quiet a { text-decoration: none; }
.mx-footer__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.25rem 1.5rem;
  max-width: var(--lp-max, var(--page-max, 90rem));
  margin: 0 auto;
}
.mx-footer__row p { margin: 0; }
.mx-footer__links { display: flex; flex-wrap: wrap; gap: 0 1.25rem; }
.mx-footer a { color: inherit; text-underline-offset: 3px; }
.mx-footer a:hover { color: var(--c-ink); }
@media (pointer: coarse) {
  .mx-footer a { display: inline-flex; align-items: center; min-height: 44px; }
}
@media (min-width: 768px) {
  .mx-footer { padding-bottom: 1.5rem; }
}
@media print {
  .mx-footer { display: none; }
}

/* ── 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, .mx-footer { --lp-gutter: 0.75rem; }
}
/* On a phone the brand and the profile button read as pushed out against the edges, so each comes
   5px in (Chris, 2026-10-14). Only those two: a relative offset moves the drawing, not the layout,
   so the cog and everything else on the bar stay exactly where they were. Studio's React header
   (AppShell.tsx) does the same. */
@media (max-width: 767.98px) {
  .mx-bar .mx-brand { position: relative; inset-inline-start: 5px; }
  .mx-bar mixtape-profile { inset-inline-end: 5px; }
}

.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); }
/* An icon in a button or a crumb is text-sized. :where() keeps it at element weight, so a product's own size
   class on the svg still wins; without any size an inline <svg> drew as wide as its button (FleetFault, 2026-10-03). */
:where(.mx-btn, .mx-crumb) svg { width: 1rem; height: 1rem; flex-shrink: 0; }

/* ── 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(min(27rem, 100%), 1fr)); }
/* min(): on a phone the column is narrower than 27rem, and a bare minmax(27rem) made the page scroll sideways (FleetFault, 2026-10-03). */
.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; margin: 0; } /* often a <p>: without this it keeps the browser's paragraph margins */
.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); }
/* A destructive action - delete, ban, remove for good. White on the kit's danger fill, which is the
   light theme's red in both themes (tokens.css --c-danger: 5.1:1; the dark theme's own red carries
   white at 3.2:1). One per screen, and never the only way out of a confirm. */
.mx-btn--danger { background: var(--c-danger); color: var(--c-danger-ink); }
.mx-btn--danger:hover { background: var(--c-danger-strong); }
.mx-btn--danger:focus-visible { outline-color: var(--c-danger); }
.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; }
.mx-panel__body > .mx-form:first-child { margin-top: 0; } /* nothing above it to keep apart from */
/* 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);
}
/* Not checkboxes or radios: a field's box rules made a checkbox 100% wide and 2.5rem tall
   (RentKeeper's "rent out anyway", 2026-10-05). Those are .mx-check below. */
.mx-field input:not([type='checkbox'], [type='radio'], [type='file'], [type='hidden']),
.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:not([type='checkbox'], [type='radio']):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);
}
/* A form that saves itself (kit/forms.ts AUTOSAVE): its Save button gives way to a quiet status, and
   comes back beside "Not saved" when a save fails. A live search's button gives way for good. */
form[data-mx-on]:not(.mx-autosave--failed) [data-mx-saver] { display: none; }
.mx-autosave {
  margin: 0;
  min-height: 1.25rem;
  font-size: 0.7812rem;
  line-height: 1.6;
  color: var(--c-ink-subtle);
}
.mx-autosave[data-state='saved'] { color: color-mix(in srgb, var(--c-ok) 70%, var(--c-ink-subtle)); }
.mx-autosave[data-state='notSaved'] { color: var(--c-rec); }
/* A list being fetched again as its search is typed: dimmed only when the answer is slow. */
[data-mx-loading] { opacity: 0.55; transition: opacity 0.15s ease 0.15s; }
.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);
  /* One line while it fits; in a narrow column it wraps rather than running into its neighbour
     (RentKeeper's "SÉRÜLTEN VISSZAHOZVA" in a half-width fact on a phone, 2026-10-05). */
  max-width: 100%;
  overflow-wrap: anywhere;
}
/* A table sizes its columns to their content: there a badge keeps to one line. */
.mx-table .mx-badge { 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); }

/* A checkbox or a radio with its words, on one row, 44px tall: "needs repair", "ban this renter".
   In a .mx-field it is still a row, never the field's uppercase label. */
.mx-check, .mx-field > label.mx-check {
  display: flex; align-items: center; gap: 0.625rem; min-height: 44px;
  font-family: inherit; font-size: 0.9375rem; text-transform: none; letter-spacing: normal;
  color: var(--c-ink); cursor: pointer;
}
.mx-check input { width: 1.125rem; height: 1.125rem; margin: 0; flex: 0 0 auto; accent-color: var(--c-accent); }
.mx-check--danger input { accent-color: var(--c-rec); }
/* The same row when a product forgot the class: a bare <label> around a checkbox or radio in a
   .mx-field read as the field's uppercase label ("WITH VAT INCLUDED", Storefront's VAT page,
   2026-10-06). The class is still right; this keeps a forgotten one from shipping. */
.mx-field > label:has(> input[type='checkbox'], > input[type='radio']) {
  display: flex; align-items: center; gap: 0.625rem; min-height: 44px;
  font-family: inherit; font-size: 0.9375rem; text-transform: none; letter-spacing: normal;
  color: var(--c-ink); cursor: pointer;
}
.mx-field > label > input:is([type='checkbox'], [type='radio']) {
  width: 1.125rem; height: 1.125rem; margin: 0; flex: 0 0 auto; accent-color: var(--c-accent);
}
/* A fieldset used as a field has no browser frame: its legend is the field's label. */
fieldset.mx-field { margin: 0; padding: 0; border: 0; }
fieldset.mx-field > legend {
  padding: 0; margin-bottom: 0.375rem;
  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);
}
/* One of a few, as cards: a fieldset of .mx-check labels, the chosen one outlined. */
.mx-choice { display: grid; gap: 0.5rem; min-width: 0; margin: 0; padding: 0; border: 0; }
.mx-choice > legend {
  padding: 0; margin-bottom: 0.375rem;
  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-choice .mx-check {
  padding: 0.5rem 0.875rem; border: 1px solid var(--c-line); border-radius: var(--radius-md);
  background: var(--c-surface-2);
}
.mx-choice .mx-check:has(input:checked) { border-color: var(--c-accent); box-shadow: inset 0 0 0 1px var(--c-accent); }
.mx-choice .mx-check:has(input:focus-visible) { outline: 2px solid var(--c-accent); outline-offset: 2px; }

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

/* ── Photos, a photo's big view, overlays, sortable tables, filters ─────────
   The app layer's working parts (Chris, 2026-10-05, on RentKeeper). Each is plain HTML that
   mixtape-ui.js enhances - DESIGN.md "Photos, overlays, sorting and filters" has the markup.
   Products use these, never their own. */

/* <mixtape-photos>: a drop area whose plus is the biggest thing in it until photos are chosen,
   then a grid of thumbnails with the plus as the last tile. The real input stays in the form,
   out of sight but focusable, so a `required` one can still say so. */
.mx-photos { display: block; position: relative; }
.mx-photos__native {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; opacity: 0;
}
.mx-photos__area {
  border: 2px dashed var(--c-line-strong);
  border-radius: var(--radius-md);
  padding: 0.75rem;
  background: color-mix(in srgb, var(--c-surface-2) 55%, transparent);
  transition: border-color 0.15s ease, background-color 0.15s ease;
}
.mx-photos__area.is-over { border-color: var(--c-accent); background: var(--c-accent-soft); }
.mx-photos__grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(5.75rem, 1fr)); gap: 0.5rem;
}
.mx-photos__tile { position: relative; aspect-ratio: 1; border-radius: var(--radius-sm); overflow: hidden; background: var(--c-surface-2); }
.mx-photos__tile img { display: block; width: 100%; height: 100%; object-fit: cover; }
.mx-photos__remove {
  position: absolute; top: 0.3rem; inset-inline-end: 0.3rem;
  display: grid; place-items: center; width: 1.875rem; height: 1.875rem;
  border: 0; border-radius: 9999px; background: color-mix(in srgb, var(--c-scrim) 62%, transparent); color: var(--c-scrim-ink); cursor: pointer;
}
.mx-photos__remove svg { width: 0.875rem; height: 0.875rem; }
.mx-photos__remove:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 1px; }
.mx-photos__tile--add { background: transparent; overflow: visible; }
.mx-photos__add {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.3rem;
  width: 100%; height: 100%; padding: 0.5rem;
  border: 1px solid var(--c-line-strong); border-radius: var(--radius-sm);
  background: var(--c-surface); color: var(--c-ink); font: inherit; text-align: center; cursor: pointer;
  transition: border-color 0.15s ease, transform 0.12s ease;
}
.mx-photos__actions { display: flex; flex-wrap: wrap; align-items: center; align-content: center; gap: 0.5rem; list-style: none; grid-column: span 2; }
.mx-btn--danger-text { color: var(--c-rec); }
.mx-photos__add:hover { border-color: var(--c-accent); }
.mx-photos__add:active { transform: scale(0.98); }
.mx-photos__add:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
.mx-photos__add svg {
  width: 2.25rem; height: 2.25rem; padding: 0.45rem; border-radius: 9999px;
  background: var(--c-accent); color: var(--c-accent-ink);
}
.mx-photos__add-label { font-size: 0.8125rem; font-weight: 600; line-height: 1.25; }
.mx-photos__hint { font-size: 0.6875rem; line-height: 1.3; color: var(--c-ink-subtle); }
.mx-photos:not(.has-files) .mx-photos__grid { grid-template-columns: 1fr; }
.mx-photos:not(.has-files) .mx-photos__tile--add { aspect-ratio: auto; min-height: 9.5rem; }
.mx-photos:not(.has-files) .mx-photos__add { border-style: none; background: transparent; gap: 0.5rem; }
.mx-photos:not(.has-files) .mx-photos__add svg { width: 3.5rem; height: 3.5rem; padding: 0.75rem; }
.mx-photos:not(.has-files) .mx-photos__add-label { font-size: 0.9375rem; }
.mx-photos:not(.has-files) .mx-photos__hint { font-size: 0.75rem; }
.mx-photos__bar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem; margin-top: 0.625rem; }
.mx-photos:not(.has-files) .mx-photos__bar:not(:has(.mx-photos__camera)) { display: none; }
.mx-photos__count { font-size: 0.8125rem; color: var(--c-ink-muted); }
.mx-photos__msg { margin: 0.5rem 0 0; font-size: 0.8125rem; color: var(--c-rec); }

/* A row with an a[data-mx-row] opens it from anywhere on the row (kit/tables.ts). */
.mx-table tr:has(a[data-mx-row]) { cursor: pointer; }

/* .mx-gallery: photos already kept, square, each with its date (and a label) under it. A link
   with data-mx-lightbox opens the big view. */
/* One row, however many: it scrolls sideways, and the edge with more beyond it fades
   (data-mx-more, set by the kit script) - the cut-off photo says "swipe for more". */
.mx-gallery {
  display: grid; grid-auto-flow: column; grid-auto-columns: 6.5rem; gap: 0.625rem;
  overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x proximity;
  scrollbar-width: none; padding-bottom: 0.125rem;
}
.mx-gallery::-webkit-scrollbar { display: none; }
.mx-gallery[data-mx-more='end'] { -webkit-mask-image: linear-gradient(to right, black calc(100% - 4.5rem), transparent); mask-image: linear-gradient(to right, black calc(100% - 4.5rem), transparent); }
.mx-gallery[data-mx-more='start'] { -webkit-mask-image: linear-gradient(to left, black calc(100% - 3rem), transparent); mask-image: linear-gradient(to left, black calc(100% - 3rem), transparent); }
.mx-gallery[data-mx-more='both'] { -webkit-mask-image: linear-gradient(to right, transparent, black 3rem, black calc(100% - 4.5rem), transparent); mask-image: linear-gradient(to right, transparent, black 3rem, black calc(100% - 4.5rem), transparent); }
.mx-gallery figure { margin: 0; min-width: 0; scroll-snap-align: start; }
.mx-gallery a { display: block; aspect-ratio: 1; border-radius: var(--radius-sm); overflow: hidden; background: var(--c-surface-2); }
.mx-gallery a:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
.mx-gallery img { display: block; width: 100%; height: 100%; object-fit: cover; transition: transform 0.25s var(--ease-out-soft, ease); }
@media (hover: hover) { .mx-gallery a:hover img { transform: scale(1.04); } }
.mx-gallery figcaption { margin-top: 0.375rem; font-size: 0.75rem; line-height: 1.4; color: var(--c-ink-subtle); overflow-wrap: break-word; }
.mx-gallery time { display: block; color: var(--c-ink-muted); font-variant-numeric: tabular-nums; }

/* The big view: dark in both themes - a photo reads best on black. */
.mx-lightbox {
  width: 100vw; height: 100dvh; max-width: none; max-height: none; margin: 0; padding: 0;
  border: 0; background: var(--c-scrim); color: var(--c-scrim-ink); overscroll-behavior: contain;
}
.mx-lightbox::backdrop { background: color-mix(in srgb, var(--c-scrim) 60%, transparent); }
.mx-lightbox[open] { display: block; animation: mx-fade-in 0.18s ease; }
.mx-lightbox__figure {
  box-sizing: border-box; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0.75rem; width: 100%; height: 100%; margin: 0; padding: 3.75rem 1rem 3rem; touch-action: pan-y;
}
.mx-lightbox__img { max-width: 100%; max-height: calc(100dvh - 9rem); object-fit: contain; border-radius: var(--radius-sm); user-select: none; -webkit-user-drag: none; }
.mx-lightbox__caption { max-width: 40rem; font-size: 0.875rem; line-height: 1.45; color: color-mix(in srgb, var(--c-scrim-ink) 82%, transparent); text-align: center; }
.mx-lightbox__btn {
  position: absolute; display: grid; place-items: center; width: 2.75rem; height: 2.75rem;
  border: 0; border-radius: 9999px; background: color-mix(in srgb, var(--c-scrim-ink) 14%, transparent); color: var(--c-scrim-ink); cursor: pointer;
}
.mx-lightbox__btn svg { width: 1.25rem; height: 1.25rem; }
.mx-lightbox__btn:hover { background: color-mix(in srgb, var(--c-scrim-ink) 24%, transparent); }
.mx-lightbox__btn:focus-visible { outline: 2px solid var(--c-scrim-ink); outline-offset: 2px; }
.mx-lightbox__close { top: max(0.75rem, env(safe-area-inset-top)); inset-inline-end: 0.75rem; }
.mx-lightbox__prev { top: 50%; inset-inline-start: 0.75rem; translate: 0 -50%; }
.mx-lightbox__next { top: 50%; inset-inline-end: 0.75rem; translate: 0 -50%; }
/* Previous is on the right in Arabic, and points right. */
[dir='rtl'] :is(.mx-lightbox__prev, .mx-lightbox__next) svg { transform: scaleX(-1); }
.mx-lightbox.is-single .mx-lightbox__prev,
.mx-lightbox.is-single .mx-lightbox__next { display: none; }
.mx-lightbox__count {
  position: absolute; top: max(1.4rem, calc(env(safe-area-inset-top) + 0.65rem)); left: 50%; translate: -50% 0; margin: 0;
  font-size: 0.8125rem; color: color-mix(in srgb, var(--c-scrim-ink) 75%, transparent); font-variant-numeric: tabular-nums;
}
@media (max-width: 575.98px) {
  .mx-lightbox__prev, .mx-lightbox__next { top: auto; bottom: max(0.75rem, env(safe-area-inset-bottom)); translate: none; }
}
/* The question before something that cannot be undone (kit/confirm.ts, data-mx-confirm): the kit's
   own small window, never window.confirm. Cancel first, the action last; a danger action stays red. */
.mx-confirm {
  width: min(28rem, calc(100% - 2rem));
  border: 1px solid var(--c-line-strong);
  border-radius: var(--radius-lg);
  background: var(--c-surface);
  color: var(--c-ink);
  padding: 1.5rem;
  box-shadow: var(--shadow-lift);
}
.mx-confirm::backdrop { background: color-mix(in srgb, var(--c-scrim) 50%, transparent); }
.mx-confirm__text { margin: 0 0 1.25rem; font-size: 0.9375rem; line-height: 1.55; }
.mx-confirm__actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 0.5rem; }
html.mx-confirm-open { overflow: hidden; }
html.mx-lightbox-open, html.mx-overlay-open { overflow: hidden; }

/* An overlay: a centred panel on a laptop, a sheet that fills the screen on a phone. */
.mx-overlay {
  box-sizing: border-box; width: min(40rem, calc(100vw - 2rem)); max-width: none; max-height: min(90dvh, 56rem);
  margin: auto; padding: 0; border: 1px solid var(--c-line); border-radius: var(--radius-lg);
  background: var(--c-surface); color: var(--c-ink); box-shadow: 0 24px 48px -12px color-mix(in srgb, var(--c-scrim) 38%, transparent); overflow: hidden;
}
.mx-overlay[open] { display: flex; flex-direction: column; animation: mx-overlay-in 0.22s var(--ease-out-soft, ease); }
.mx-overlay::backdrop { background: color-mix(in srgb, var(--c-scrim) 50%, transparent); -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px); }
.mx-overlay__head {
  display: flex; flex: none; align-items: center; justify-content: space-between; gap: 0.75rem;
  padding: 0.75rem 0.75rem 0.75rem 1.25rem; border-bottom: 1px solid var(--c-line);
}
.mx-overlay__title { margin: 0; font-size: 1.0625rem; font-weight: 600; line-height: 1.3; }
.mx-overlay__close {
  display: grid; flex: none; place-items: center; width: 2.75rem; height: 2.75rem;
  border: 0; border-radius: 9999px; background: transparent; color: var(--c-ink-muted); cursor: pointer;
}
.mx-overlay__close:hover { background: var(--c-surface-2); color: var(--c-ink); }
.mx-overlay__close:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
.mx-overlay__close svg { width: 1.125rem; height: 1.125rem; }
.mx-overlay__body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 1.25rem; overscroll-behavior: contain; }
.mx-overlay__body > * + * { margin-top: 1rem; }
.mx-overlay__body .mx-page,
.mx-overlay__body .mx-page__inner { max-width: none; padding: 0; margin: 0; }
.mx-overlay__loading { margin: 0; font-size: 0.875rem; color: var(--c-ink-muted); }
@keyframes mx-overlay-in { from { opacity: 0; transform: translateY(0.75rem) scale(0.98); } }
@keyframes mx-sheet-in { from { transform: translateY(100%); } }
@keyframes mx-fade-in { from { opacity: 0; } }
@media (max-width: 575.98px) {
  .mx-overlay { width: 100vw; height: 100dvh; max-height: none; margin: 0; border: 0; border-radius: 0; }
  .mx-overlay[open] { animation-name: mx-sheet-in; }
  .mx-overlay__head { padding-top: max(0.75rem, env(safe-area-inset-top)); }
  .mx-overlay__body { padding: 1rem 1rem max(1.25rem, env(safe-area-inset-bottom)); }
}
@media (prefers-reduced-motion: reduce) {
  .mx-overlay[open], .mx-lightbox[open] { animation: none; }
}

/* The bottom sheet (kit/sheet.ts, data-mx-sheet): on a phone, docked to the bottom and always there,
   a handle on top that opens it to the full screen; on a wider screen, the block it always was. The
   handle and the scrim are the script's, so without it nothing here applies. */
.mx-sheet__handle, .mx-sheet__scrim { display: none; }
@media (max-width: 720px) {
  .mx-sheet[data-mx-sheet-on] {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
    box-sizing: border-box; display: flex; flex-direction: column;
    margin: 0; max-height: min(var(--mx-sheet-peek, 45dvh), calc(100dvh - var(--kb, 0px) - 3rem));
    padding: 0 1rem calc(max(env(safe-area-inset-bottom) - 10px, 4px) + 0.75rem);
    /* Colours: the sheet's own, below. */
    border: 1px solid; border-bottom: 0; border-radius: 1.25rem 1.25rem 0 0;
    overscroll-behavior: contain;
    transition: max-height 0.28s var(--ease-out-soft, ease), height 0.28s var(--ease-out-soft, ease);
    animation: mx-sheet-in 0.28s var(--ease-out-soft, ease);
  }
  .mx-sheet[data-mx-sheet-on][data-state='full'] {
    max-height: none;
    height: calc(100dvh - var(--kb, 0px) - max(env(safe-area-inset-top), 0.75rem) - 1.5rem);
  }
  .mx-sheet[data-mx-sheet-on][data-dragging] { transition: none; user-select: none; }
  /* Everything in it keeps its height except the part that scrolls - the heading and the form stay in view. */
  .mx-sheet[data-mx-sheet-on] > * { flex: none; }
  .mx-sheet[data-mx-sheet-on] [data-mx-sheet-scroll] { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
  /* Open, the scrolling part takes all the room there is - a product's own cap on it (a chat list's
     max-height for the page) would leave a band of nothing under the form (Hotel QR, 2026-10-09). */
  .mx-sheet[data-mx-sheet-on][data-state='full'] [data-mx-sheet-scroll] { max-height: none; }
  .mx-sheet[data-mx-sheet-on] > :has([data-mx-sheet-scroll]) { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
  .mx-sheet__handle {
    display: flex; flex: none; align-items: center; justify-content: center; align-self: stretch;
    min-height: 2.25rem; margin: 0 -1rem; padding: 0; border: 0; background: transparent;
    cursor: grab; touch-action: none; -webkit-tap-highlight-color: transparent;
  }
  /* From the sheet's own ink: a line colour vanished into the dark-page sheet, which is about as light as one. */
  .mx-sheet__handle::before { content: ''; width: 2.5rem; height: 0.3125rem; border-radius: 9999px; background: color-mix(in srgb, var(--c-ink) 32%, transparent); }
  .mx-sheet__handle:focus-visible { outline: none; }
  .mx-sheet__handle:focus-visible::before { background: var(--c-accent); }
  .mx-sheet__scrim {
    /* margin: 0 - it sits among the page's own blocks, whose spacing rule (.mx-page > * + *) would push it down. */
    position: fixed; inset: 0; z-index: 44; margin: 0; background: color-mix(in srgb, var(--c-scrim) 45%, transparent);
    animation: mx-fade-in 0.2s ease;
  }
  :root[data-mx-sheet-full] .mx-sheet__scrim { display: block; }
  :root[data-mx-sheet-full] { overflow: hidden; }
  /* Room at the bottom of the page for the settled sheet, so nothing hides behind it. */
  :root[data-mx-sheet-shown] body { padding-bottom: calc(var(--mx-sheet-h, 0px) + 1rem); }
  /* Typing: above the keyboard on iOS, the editor bar's room inside the sheet on Android (src/lib/keyboard.ts). */
  :root[data-kb] .mx-sheet[data-mx-sheet-on] { bottom: var(--kb, 0px); padding-bottom: calc(var(--kb-bar, 0px) + 0.5rem); }
}
@media (max-width: 720px) and (prefers-reduced-motion: reduce) {
  .mx-sheet[data-mx-sheet-on] { transition: none; animation: none; }
  .mx-sheet__scrim { animation: none; }
}

/* The sheet's own colours, small or open, worked out on <html> so the sheet can re-point the tokens its
   contents read without a loop. A LIGHT page: the whole sheet is dark like a phone's own "now playing",
   pill and open sheet alike ("on light mode the extended mode colors should follow the closed mode"), its
   insides - list, fields, chips - a step lighter than it. A DARK page: the page's card colour 15% brighter
   ("like we have on dark for the card bgs like the wifi section's bg color, only 15% brighter"), its
   insides the page's own darker surfaces, set into it. Chris, 2026-10-09, on Hotel QR's room chat. */
:root {
  /* Toward the ink, not toward #fff: the dark ink is a few units off white, so it looks the same, and a
     product's gate reads this file as its own sheet, where a bare colour is a finding (kit task #3111). */
  --mx-sheet-bg: color-mix(in srgb, var(--c-surface) 85%, var(--c-ink)); --mx-sheet-line: color-mix(in srgb, var(--c-surface) 76%, var(--c-ink));
  --mx-sheet-ink: var(--c-ink); --mx-sheet-muted: var(--c-ink-muted); --mx-sheet-subtle: var(--c-ink-subtle);
  --mx-sheet-surface: var(--c-surface); --mx-sheet-surface-2: var(--c-surface-2);
  --mx-sheet-in-line: var(--c-line); --mx-sheet-in-line-strong: var(--c-line-strong);
  /* A big, soft shadow around it - dark in both themes: a light glow on a dark page looked wrong ("make
     that black too"). The page's own deepest colour, never #000. */
  --mx-sheet-glow: 0 18px 56px -6px color-mix(in srgb, var(--c-canvas) 88%, transparent), 0 4px 16px color-mix(in srgb, var(--c-canvas) 60%, transparent);
}
:root[data-theme='light'] {
  --mx-sheet-bg: var(--c-ink); --mx-sheet-line: transparent;
  --mx-sheet-ink: var(--c-canvas); --mx-sheet-muted: color-mix(in srgb, var(--c-canvas) 68%, var(--c-ink)); --mx-sheet-subtle: color-mix(in srgb, var(--c-canvas) 50%, var(--c-ink));
  --mx-sheet-surface: color-mix(in srgb, var(--c-canvas) 8%, var(--c-ink)); --mx-sheet-surface-2: color-mix(in srgb, var(--c-canvas) 14%, var(--c-ink));
  --mx-sheet-in-line: color-mix(in srgb, var(--c-canvas) 16%, var(--c-ink)); --mx-sheet-in-line-strong: color-mix(in srgb, var(--c-canvas) 26%, var(--c-ink));
  --mx-sheet-glow: 0 18px 56px -6px color-mix(in srgb, var(--c-ink) 42%, transparent), 0 4px 16px color-mix(in srgb, var(--c-ink) 14%, transparent);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    --mx-sheet-bg: var(--c-ink); --mx-sheet-line: transparent;
    --mx-sheet-ink: var(--c-canvas); --mx-sheet-muted: color-mix(in srgb, var(--c-canvas) 68%, var(--c-ink)); --mx-sheet-subtle: color-mix(in srgb, var(--c-canvas) 50%, var(--c-ink));
    --mx-sheet-surface: color-mix(in srgb, var(--c-canvas) 8%, var(--c-ink)); --mx-sheet-surface-2: color-mix(in srgb, var(--c-canvas) 14%, var(--c-ink));
    --mx-sheet-in-line: color-mix(in srgb, var(--c-canvas) 16%, var(--c-ink)); --mx-sheet-in-line-strong: color-mix(in srgb, var(--c-canvas) 26%, var(--c-ink));
    --mx-sheet-glow: 0 18px 56px -6px color-mix(in srgb, var(--c-ink) 42%, transparent), 0 4px 16px color-mix(in srgb, var(--c-ink) 14%, transparent);
  }
}
.mx-sheet__open { display: none; }
@media (max-width: 720px) {
  .mx-sheet[data-mx-sheet-on] {
    background: var(--mx-sheet-bg); color: var(--mx-sheet-ink); border-color: var(--mx-sheet-line);
    box-shadow: var(--mx-sheet-glow); color-scheme: dark;
    --c-ink: var(--mx-sheet-ink); --c-ink-muted: var(--mx-sheet-muted); --c-ink-subtle: var(--mx-sheet-subtle);
    --c-surface: var(--mx-sheet-surface); --c-surface-2: var(--mx-sheet-surface-2);
    --c-line: var(--mx-sheet-in-line); --c-line-strong: var(--mx-sheet-in-line-strong);
  }
  /* Made small (data-mx-sheet-bar): a pill over the bottom of the page - its bar alone and the button
     that brings it back. */
  .mx-sheet[data-mx-sheet-on][data-state='min'] {
    left: 0.75rem; right: 0.75rem; bottom: calc(env(safe-area-inset-bottom) + 0.75rem);
    max-height: none; padding: 0.5rem;
    border: 1px solid var(--mx-sheet-line); border-radius: 9999px;
    cursor: pointer; touch-action: none; -webkit-tap-highlight-color: transparent;
  }
  .mx-sheet[data-mx-sheet-on][data-state='min'] > :not([data-mx-sheet-bar]) { display: none; }
  .mx-sheet[data-mx-sheet-on][data-state='min'] > [data-mx-sheet-bar] {
    display: flex; align-items: center; gap: 0.625rem; min-width: 0; margin: 0;
  }
  /* One line each, cut short rather than wrapped: the pill keeps its height whatever the hotel is called. */
  .mx-sheet[data-mx-sheet-on][data-state='min'] > [data-mx-sheet-bar] :is(span, p, strong, b, small):not(.mx-sheet__open) {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .mx-sheet[data-mx-sheet-on][data-state='min'] .mx-sheet__open {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    margin-inline-start: auto; min-height: 2.75rem; padding: 0 1.25rem;
    border: 0; border-radius: 9999px; background: var(--mx-sheet-ink); color: var(--mx-sheet-bg);
    /* Its own size: the bar is usually a heading, and a button at heading size shouts. */
    font: inherit; font-size: 0.9375rem; font-weight: 700; line-height: 1; letter-spacing: 0; cursor: pointer;
  }
  .mx-sheet[data-mx-sheet-on][data-state='min'] .mx-sheet__open:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
}

/* Opening hours as a state (kit/hours.ts, data-mx-hours): a dot before "Open until 20:00", green while open. */
[data-mx-hours-on]::before {
  content: ''; display: inline-block; width: 0.5em; height: 0.5em; margin-inline-end: 0.45em;
  border-radius: 50%; vertical-align: 0.08em; background: var(--c-ink-subtle);
}
[data-mx-hours-on][data-mx-open]::before { background: var(--c-ok); }

/* Sortable columns: the header is a button, its arrow shows which way. */
.mx-sort {
  display: inline-flex; align-items: center; gap: 0.4rem; padding: 0; margin: 0; border: 0; background: none;
  font: inherit; letter-spacing: inherit; text-transform: inherit; color: inherit; cursor: pointer;
}
.mx-sort:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 3px; border-radius: 2px; }
.mx-sort__arrow {
  flex: none; width: 0.4rem; height: 0.4rem; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  opacity: 0; transform: translateY(-0.1rem) rotate(45deg); transition: transform 0.15s ease, opacity 0.15s ease;
}
@media (hover: hover) { .mx-sort:hover { color: var(--c-ink); } .mx-sort:hover .mx-sort__arrow { opacity: 0.45; } }
.mx-table th[aria-sort='ascending'],
.mx-table th[aria-sort='descending'] { color: var(--c-ink); }
.mx-table th[aria-sort='ascending'] .mx-sort__arrow { opacity: 1; transform: translateY(0.12rem) rotate(-135deg); }
.mx-table th[aria-sort='descending'] .mx-sort__arrow { opacity: 1; transform: translateY(-0.1rem) rotate(45deg); }
/* The phone's "Sort by": a kit label and a kit select, like any field (Chris, 2026-10-05: "Rendezés
   looks strange on mobile" - it sat flush to the panel's edge with a bare dash for a choice). In a
   panel it lines up with the rows' text, which sit .875rem in. */
/* Label above, the list across the whole row - as a form field is. Beside each other, the label took
   a third of a phone and "Rückgabe bis · aufsteigend" was cut to "aufstei" (RentKeeper in German,
   2026-10-05); a translated label and option are often a third longer than the English. */
.mx-sortbar { display: flex; flex-direction: column; align-items: stretch; gap: 0.375rem; margin: 0 0 0.75rem; }
.mx-sortbar[hidden] { display: none; }
.mx-sortbar .mx-label { margin: 0; white-space: nowrap; }
/* Inset like the rows wherever the bar sits in a container with no padding of its own: straight in
   a panel, or in a flush panel body (RentKeeper's equipment list, 2026-10-05). */
.mx-panel > .mx-sortbar,
.mx-panel__body--flush > .mx-sortbar { padding: 0.875rem 0.875rem 0; margin-bottom: 0.25rem; }
.mx-sortbar select {
  flex: 1 1 auto; min-width: 0; height: 2.5rem; padding: 0 0.75rem;
  border: 1px solid var(--c-line-strong); border-radius: var(--radius-sm);
  background: var(--c-canvas); color: var(--c-ink); font: inherit; font-size: 0.875rem;
}
.mx-sortbar select:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 1px; }

/* Filter chips: one row of choices above a list, each with its count. */
.mx-filter { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 0.875rem; }
.mx-filter button {
  display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.375rem 0.75rem;
  border: 1px solid var(--c-line-strong); border-radius: 9999px; background: transparent;
  color: var(--c-ink-muted); font: inherit; font-size: 0.8125rem; font-weight: 500; line-height: 1.3; cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
@media (hover: hover) { .mx-filter button:hover { color: var(--c-ink); border-color: var(--c-ink-subtle); } }
.mx-filter button:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
.mx-filter button[aria-pressed='true'] { background: var(--c-ink); border-color: var(--c-ink); color: var(--c-canvas); }
.mx-filter__count { font-size: 0.75rem; font-variant-numeric: tabular-nums; opacity: 0.7; }
.mx-filter__empty { margin: 1rem 0 0; font-size: 0.875rem; color: var(--c-ink-muted); }
.mx-filter__empty[hidden] { display: none; }

/* ── 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; }
  .mx-filter button,
  .mx-sortbar select { min-height: 44px; }
  .mx-sortbar select { font-size: 16px; }
  .mx-photos__remove { width: 2.25rem; height: 2.25rem; }
  /* Links that are something to press, not a word in a sentence (FleetFault #996, 2026-10-03):
     the "Sign in" hint under a form was 38 x 17 px, "Open in OpenStreetMap" 179 x 21. Small-text
     links and a link standing on its own (.mx-link) become a 44px box; links inside running
     text stay text - a 44px box mid-sentence jolts the line. */
  .mx-sub a,
  .mx-hint a,
  .mx-empty a,
  a.mx-link { display: inline-flex; align-items: center; min-height: 44px; min-width: 44px; }
  /* A table's row link is the row's own target: one line, 44px tall (plates wrapped to 18px). */
  .mx-table td a { display: inline-flex; align-items: center; min-height: 44px; }
  .mx-table a[data-mx-row] { white-space: nowrap; }
  /* A checkbox in a cell is pressed through its label: 44 x 44, the box in the middle. */
  .mx-table td > label:has(> input[type='checkbox']) {
    display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; cursor: pointer;
  }
  .mx-table td input[type='checkbox'] { width: 1.25rem; height: 1.25rem; accent-color: var(--c-accent); }
}

/* ── The one button a screen is for ─────────────────────────────────────────
   `.mx-cta`: the landing pages' call to action (landing.css `.lp-btn--primary`), brought to the
   app layer for the single most important button on an app screen - Studio's Continue (Chris,
   2026-10-01: "glow and animate like we do the cta btns on the landing pages"). The same
   gradient flowing A → B → shine → B → A, the same breathing glow, the same light sweeping
   across, the same lift on hover; change one and change the other. One per screen at most:
   a second one glowing beside it and neither is the thing to press.
   Everything stops for anybody who asked for less motion (tokens.css). */
.mx-cta {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: linear-gradient(
    120deg,
    var(--c-accent-strong) 0%,
    var(--c-accent) 25%,
    var(--c-accent-shine, 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: mx-cta-flow 9s linear infinite, mx-cta-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;
}
/* The sweep, under the content: every child gets a layer above it, so the light passes behind
   the words rather than washing over them. */
.mx-cta::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: linear-gradient(115deg, transparent 38%, rgba(255, 255, 255, 0.18) 50%, transparent 62%);
  transform: translateX(-130%);
  animation: mx-cta-sheen 5.5s ease-in-out infinite;
  pointer-events: none;
}
.mx-cta > * { position: relative; z-index: 1; }
.mx-cta:hover:not(:disabled),
.mx-cta:focus-visible {
  transform: translateY(-2px);
  /* Without the breathing, or the running animation would outrank this stronger glow. */
  animation: mx-cta-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);
}
.mx-cta:active:not(:disabled) { transform: translateY(0); }
@keyframes mx-cta-flow { 0% { background-position: 200% 50%; } 100% { background-position: 0% 50%; } }
@keyframes mx-cta-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 mx-cta-sheen {
  0%, 62% { transform: translateX(-130%); }
  78%, 100% { transform: translateX(130%); }
}

/* ── Right to left ────────────────────────────────────────────────────────────────────────── */
/* A photo strip scrolls the other way: its fading edge is the other edge. */
[dir='rtl'] .mx-gallery[data-mx-more='end'] { -webkit-mask-image: linear-gradient(to left, black calc(100% - 4.5rem), transparent); mask-image: linear-gradient(to left, black calc(100% - 4.5rem), transparent); }
[dir='rtl'] .mx-gallery[data-mx-more='start'] { -webkit-mask-image: linear-gradient(to right, black calc(100% - 3rem), transparent); mask-image: linear-gradient(to right, black calc(100% - 3rem), transparent); }
[dir='rtl'] .mx-gallery[data-mx-more='both'] { -webkit-mask-image: linear-gradient(to left, transparent, black 3rem, black calc(100% - 4.5rem), transparent); mask-image: linear-gradient(to left, transparent, black 3rem, black calc(100% - 4.5rem), transparent); }
/* Back links and anything a product marks data-mx-flip point the way the page reads. */
[dir='rtl'] :is(.mx-crumb svg, [data-mx-flip]) { transform: scaleX(-1); }

/* Every kit dropdown draws its own chevron at a fixed inset: the native arrow sits on the border
   and padding cannot move it (tokens.css select-field; Chris, 2026-10-05). --mx-end is the side
   a line ends on - left in Arabic. */
.mx-field select, select.mx-input, .mx-sortbar select {
  -webkit-appearance: none;
  appearance: none;
  padding-inline-end: 2.25rem;
  background-image: var(--select-chevron);
  background-repeat: no-repeat;
  background-position: var(--mx-end, right) 0.75rem center;
  background-size: 12px 12px;
}


/* ── One field for a date and its time: data-mx-datetime (kit/datetime.ts, 2026-10-13) ───────────────────────
   The input stays the form's field, out of sight; the button reads like a field and opens the panel in flow
   under it - the month and that day's times side by side, taking turns when the field is narrow. */
.mx-dt { position: relative; display: grid; gap: 0.375rem; min-width: 0; container-type: inline-size; }
.mx-field .mx-dt input.mx-dt__native {
  position: absolute; inset-inline-start: 0; top: 0;
  width: 1px; height: 1px; padding: 0; margin: 0; border: 0;
  opacity: 0; overflow: hidden; clip-path: inset(50%); pointer-events: none;
}
.mx-dt__field {
  display: flex; align-items: center; gap: 0.5rem;
  max-width: 22rem; text-align: start; cursor: pointer;
  padding-inline-end: 2.25rem;
  background-image: var(--select-chevron); background-repeat: no-repeat;
  background-position: var(--mx-end, right) 0.75rem center; background-size: 12px 12px;
}
.mx-dt__field.is-empty { color: var(--c-ink-subtle); }
.mx-dt__field[aria-invalid='true'] { border-color: var(--c-rec); }
.mx-dt__field[aria-expanded='true'] { border-color: var(--c-accent); }
.mx-dt__shown { min-width: 0; overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }
.mx-dt__panel {
  display: grid; grid-template-columns: minmax(0, 18rem) minmax(6.5rem, 9rem); gap: 1rem;
  width: fit-content; max-width: 100%;
  padding: 0.75rem; border: 1px solid var(--c-line-strong); border-radius: var(--radius-md);
  background: var(--c-surface);
}
.mx-dt__panel[hidden] { display: none; }
.mx-dt__days, .mx-dt__times { min-width: 0; }
.mx-dt__head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; min-height: 2.25rem; margin: 0 0 0.375rem; }
.mx-dt__month, .mx-dt__label { font-size: 0.875rem; font-weight: 600; color: var(--c-ink); }
.mx-dt__label { font-family: var(--font-mono, ui-monospace, monospace); font-size: var(--mx-label-size); font-weight: 400; text-transform: uppercase; letter-spacing: 0.08em; color: var(--mx-label); }
.mx-dt__nav, .mx-dt__back, .mx-dt__day, .mx-dt__time {
  margin: 0; border: 0; background: none; font: inherit; color: var(--c-ink); cursor: pointer;
  border-radius: var(--radius-sm); -webkit-tap-highlight-color: transparent;
}
.mx-dt__nav { width: 2.25rem; height: 2.25rem; font-size: 1.25rem; line-height: 1; }
.mx-dt__back { display: none; padding: 0 0.5rem; height: 2.25rem; font-size: 0.875rem; font-weight: 600; text-align: start; }
.mx-dt__grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 2px; }
.mx-dt__wd { padding: 0.25rem 0; text-align: center; font-size: 0.6875rem; color: var(--c-ink-subtle); }
.mx-dt__day { aspect-ratio: 1; min-height: 2.25rem; font-size: 0.875rem; font-variant-numeric: tabular-nums; }
.mx-dt__day.is-out { color: var(--c-ink-subtle); }
.mx-dt__day.is-today { box-shadow: inset 0 0 0 1px var(--c-accent); }
.mx-dt__list { display: grid; gap: 2px; align-content: start; max-height: 17rem; overflow-y: auto; overscroll-behavior: contain; }
.mx-dt__time { height: 2.25rem; padding: 0 0.5rem; font-size: 0.875rem; font-variant-numeric: tabular-nums; }
.mx-dt__day.is-on, .mx-dt__time.is-on { background: var(--c-accent); color: var(--c-accent-ink); font-weight: 600; }
.mx-dt__nav:disabled, .mx-dt__day:disabled, .mx-dt__time:disabled { opacity: 0.3; cursor: default; }
@media (hover: hover) {
  .mx-dt__nav:not(:disabled):hover, .mx-dt__back:hover, .mx-dt__day:not(:disabled, .is-on):hover, .mx-dt__time:not(:disabled, .is-on):hover { background: var(--c-surface-sunk); }
}
.mx-dt__nav:focus-visible, .mx-dt__back:focus-visible, .mx-dt__day:focus-visible, .mx-dt__time:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 1px; }
.mx-dt__msg[hidden] { display: none; }
/* A narrow field: one at a time - the month, then that day's times with the date above them to go back. */
@container (max-width: 29rem) {
  .mx-dt__panel { grid-template-columns: minmax(0, 1fr); width: auto; }
  .mx-dt__panel[data-showing='days'] .mx-dt__times, .mx-dt__panel[data-showing='times'] .mx-dt__days { display: none; }
  .mx-dt__back { display: inline-flex; align-items: center; gap: 0.375rem; }
  .mx-dt__list { grid-template-columns: repeat(3, minmax(0, 1fr)); max-height: 18rem; }
}

/* "Made with mixtape.works" - the same line as landing.css, for the public pages built on the app layer. */
.mx-made-with {
  display: block;
  margin: 1.5rem auto 0.75rem;
  text-align: center;
  font-size: 0.75rem;
  color: var(--c-ink-subtle);
}
.mx-made-with a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.mx-made-with a:hover { color: var(--c-ink); }

/* Dayrule's own rules, appended after the kit (fonts, tokens, accent, landing, app) into the one
   hashed stylesheet. Colours, fonts and radii are only ever var(--c-*), var(--font-*) and
   var(--radius-*) from tokens.css - source.test.mjs fails on a hex code in here.
   Logical properties (inline/block, start/end) throughout, so a right-to-left language
   mirrors without a second sheet.

   What is NOT here any more: the page column, forms, fields, buttons, alerts, tables, badges,
   stats and the whole signed-in frame. Those come from the kit's app layer (kit/app.css, the
   .mx-* classes) and are shared with Studio. What is left is what only a leave tracker has:
   the month grid, the who-is-off lists, the marks on a day, and the pieces of a person form.
   If something here starts to look general, it belongs in packages/ui instead. */

/* The kit's landing layer is written for border-box everywhere (Studio gets it from Tailwind's
   preflight). Without it .lp-container is 100% wide PLUS its gutters, and every page scrolls
   sideways on a phone - measured at 375px in Chromium on #44, on /login as much as on /app. */
*,
*::before,
*::after { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--c-canvas);
  color: var(--c-ink);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
}

/* ── The landing header: the pages a stranger sees. The app wears .mx-bar. ───────────────── */
/* The brand is the kit's .mx-brand in both headers: the bare mark in the accent. */
button.lp-pill { font: inherit; font-size: 0.875rem; font-weight: 600; cursor: pointer; }
button.lp-btn--primary { font-family: inherit; border: 0; cursor: pointer; }

/* /login, /signup and the page a sign-in link opens are landing-side: they clear the fixed
   landing header themselves, since they have no .mx-page column. */
.page { padding-block: calc(var(--lp-header-h) + 2.5rem) 4rem; }
.page .lp-h2 { margin-top: 0; }
.narrow { max-width: 30rem; margin-inline: auto; }
.aside { margin-top: 1.5rem; font-size: 0.875rem; color: var(--c-ink-muted); }
.aside a, .link { color: var(--c-accent); }

/* ── Form pieces the kit has no opinion about ───────────────────────────────────────────── */
.mx-field textarea {
  width: 100%;
  min-height: 12rem;
  padding: 0.6rem 0.8rem;
  font-family: var(--font-mono);
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--c-ink);
  background: var(--c-canvas);
  border: 1px solid var(--c-line-strong);
  border-radius: var(--radius-sm);
  resize: vertical;
}
.mx-field textarea:focus { border-color: var(--c-accent); outline: none; }
/* A note and a reason are prose, not a pasted list: sans, bigger, and short by default. */
.mx-field textarea[name='note'],
.mx-field textarea[name='reason'] { min-height: 5rem; font-family: var(--font-sans); font-size: 16px; }
.mx-field input.mono { font-family: var(--font-mono); font-size: 0.875rem; }
.minted input.mono { border-color: var(--c-accent); }

fieldset.mx-field { margin: 0; padding: 0; border: 0; }
fieldset.mx-field legend {
  padding: 0;
  margin-bottom: 0.4rem;
  font-family: var(--font-mono);
  font-size: var(--mx-label-size);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--mx-label);
}
.choices { display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; }
.check {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
  font-size: 0.9375rem;
  color: var(--c-ink);
  cursor: pointer;
}
.check input { width: 1.25rem; height: 1.25rem; margin: 0; accent-color: var(--c-accent); flex: none; }
.year-pick { display: flex; flex-wrap: wrap; align-items: end; gap: 0.75rem; }
.year-pick .mx-field { min-width: 8rem; }

/* /app/settings/holidays: the company's public holidays, a year to a heading, a day to a row. The delete
   button keeps to the row's end, and on a phone the row wraps under the name instead of
   squeezing it. */
.holiday-year { margin-top: 1.25rem; }
.holiday-year__title {
  margin: 0 0 0.25rem;
  font-family: var(--font-mono);
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--c-ink);
}
.holiday-list { list-style: none; margin: 0; padding: 0; }
.holiday-list li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.75rem;
  min-width: 0;
  padding-block: 0.35rem;
  border-block-end: 1px solid var(--c-line);
}
.holiday-list li:last-child { border-block-end: 0; }
.holiday-list time { min-inline-size: 7.5rem; font-size: 0.875rem; font-variant-numeric: tabular-nums; color: var(--c-ink-muted); }
.holiday-list .mx-btn { min-height: 44px; margin-inline-start: auto; }

.facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: 1rem; margin: 0; }
.facts dt {
  font-family: var(--font-mono);
  font-size: var(--mx-label-size-sm);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--mx-label);
}
.facts dd { margin: 0.25rem 0 0; font-weight: 600; color: var(--c-ink); overflow-wrap: anywhere; }

.example {
  margin: 0.5rem 0 0;
  padding: 0.75rem 0.9rem;
  overflow-x: auto;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--c-ink);
  background: var(--c-surface-sunk);
  border-radius: var(--radius-sm);
}
.bullets { margin: 1rem 0 0; padding-inline-start: 1.2rem; display: grid; gap: 0.35rem; }
.problems { margin: 0.75rem 0; padding-inline-start: 1.2rem; display: grid; gap: 0.4rem; color: var(--c-ink); line-height: 1.45; }
.problems strong { color: var(--c-rec); }

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

/* ── Tables ─────────────────────────────────────────────────────────────────────────────── */
.mx-table tr.left td { color: var(--c-ink-muted); }
/* break-word, not anywhere: `anywhere` lets the browser narrow a column and split a word that
   would have fitted, which is how "Magyarország" and every two-word name came to be hyphen-less
   across two lines. Only a genuinely unbreakable run - a long address - breaks now. */
.mx-table td { overflow-wrap: break-word; }
.mx-table td:nth-child(2) { overflow-wrap: anywhere; }
.lines td.num { white-space: nowrap; }

/* Requests: one block per request on a phone, a table from 48rem. Six cells in a row are
   unreadable at 375px, and leave is asked for on a phone far more often than at a desk. */
.requests td form { margin: 0; }
.requests .mx-btn { min-height: 44px; white-space: nowrap; }
.requests thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
.requests,
.requests tbody,
.requests tr,
.requests td { display: block; }
.requests tr { padding-block: 0.75rem; border-block-end: 1px solid var(--c-line); }
.requests td { padding: 0.1rem 0; border: 0; overflow-wrap: break-word; }
.requests td:first-child { font-weight: 600; }
.requests td form { margin-top: 0.5rem; }

@media (min-width: 48rem) {
  .requests { display: table; }
  .requests thead { position: static; width: auto; height: auto; overflow: visible; clip-path: none; display: table-header-group; }
  .requests tbody { display: table-row-group; }
  .requests tr { display: table-row; padding: 0; border: 0; }
  .requests td { display: table-cell; padding: 0.75rem 0.875rem; border-block-end: 1px solid var(--c-line); }
  .requests td:first-child { font-weight: inherit; }
  .requests td form { margin-top: 0; }
}

/* ── Who is off: the lists on the overview ──────────────────────────────────────────────── */
.who { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
.who li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.15rem 0.75rem; min-width: 0; }
.who__name { font-weight: 600; color: var(--c-ink); overflow-wrap: anywhere; }
.who__what { font-size: 0.875rem; color: var(--c-ink-muted); overflow-wrap: anywhere; }
.who__none { margin: 0; font-size: 0.875rem; color: var(--c-ink-subtle); }
.who a.who__name { color: var(--c-accent); }
.who time { font-variant-numeric: tabular-nums; color: var(--c-ink-muted); }
.days { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.9rem; }
.days li { display: grid; gap: 0.35rem; min-width: 0; }
.days__name { margin: 0; font-size: 0.875rem; font-weight: 600; color: var(--c-ink-muted); }
.days li[aria-current='date'] .days__name { color: var(--c-ink); }
.tag {
  display: inline-block;
  margin-inline-start: 0.4rem;
  padding: 0 0.45rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--c-accent-ink);
  background: var(--c-accent);
  border-radius: var(--radius-sm);
}

/* ── The month grid ─────────────────────────────────────────────────────────────────────── */
/* It scrolls sideways inside its own box; the name column stays put while it does. Sticky is
   meant to stick to this scroller (DESIGN.md §7), which is the only way it moves. */
.cal-scroll {
  overflow-x: auto;
  min-width: 0;
  border: 1px solid var(--c-line);
  border-radius: var(--radius-md);
  background: var(--c-surface);
}
.cal-scroll:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
.cal { border-collapse: separate; border-spacing: 0; font-size: 0.8125rem; }
.cal th,
.cal td { padding: 0; border-block-end: 1px solid var(--c-line); border-inline-end: 1px solid var(--c-line); }
.cal tbody tr:last-child > * { border-block-end: 0; }
.cal thead th { padding: 0.35rem 0.2rem; min-inline-size: 2.25rem; font-weight: 600; text-align: center; color: var(--c-ink-muted); }
.cal__dow,
.cal__num { display: block; white-space: nowrap; }
.cal__num { font-family: var(--font-mono); color: var(--c-ink); }
.cal thead th[aria-current='date'] { box-shadow: inset 0 -3px 0 var(--c-accent); }
.cal thead th[aria-current='date'] .cal__num { color: var(--c-accent); }
.cal .cal__name {
  position: sticky;
  inset-inline-start: 0;
  z-index: 1;
  min-inline-size: 8rem;
  max-inline-size: 11rem;
  padding: 0.45rem 0.75rem;
  text-align: start;
  font-weight: 600;
  color: var(--c-ink);
  background: var(--c-surface);
  overflow-wrap: anywhere;
  transition: background-color 0.12s ease;
}
.cal .cal__name .sub { display: block; margin-top: 0.15rem; font-size: 0.75rem; font-weight: 400; line-height: 1.35; color: var(--c-ink-muted); }
.cal td { position: relative; min-inline-size: 2.25rem; block-size: 2.75rem; }

/* A row answers the pointer, as the kit's .mx-table rows do: the same faint wash of ink, only
   where there is a real pointer. A day's own background says what the day is (the hatching of a
   day off, a holiday's fill), so the wash is not a background: it is a layer of its own over it,
   and ::before so the leave mark, which comes after it, is painted over the wash. The name cell
   is sticky and must stay opaque while the days slide under it, so it mixes the wash into its
   surface instead. */
.cal tbody td::before {
  content: '';
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--c-ink) 4%, transparent);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease;
}
@media (hover: hover) {
  .cal tbody tr:hover td::before { opacity: 1; }
  .cal tbody tr:hover .cal__name { background: color-mix(in srgb, var(--c-ink) 4%, var(--c-surface)); }
}

/* What a day is: shaded when nobody works, filled when somebody is off. The swatches in the key
   are the same rules on a smaller box. */
.cal [data-kind='day_off'],
.swatch[data-kind='day_off'] {
  background: repeating-linear-gradient(135deg, var(--c-line-strong) 0 1px, transparent 1px 6px);
}
.cal [data-kind='holiday'],
.swatch[data-kind='holiday'] {
  background: var(--c-line);
  box-shadow: inset 0 3px 0 var(--c-ink-subtle);
}
.cal td[data-kind='out'],
.swatch[data-kind='out'] { background: var(--c-surface-sunk); }
/* A bar of one height whatever the row's height, so a tall name does not make a tall absence. */
.mark {
  position: absolute;
  inset-inline: 3px;
  inset-block-start: 50%;
  block-size: 2rem;
  margin-block-start: -1rem;
  border-radius: var(--radius-sm);
}
.mark--approved,
.swatch[data-leave='approved'] { background: var(--c-accent); }
.mark--pending,
.swatch[data-leave='pending'] { background: var(--c-accent-soft); border: 2px dashed var(--c-accent); }
.mark--half { block-size: 1rem; margin-block-start: 0; }

.notes { display: grid; gap: 0.35rem; }
.legend { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: 0.6rem 1.25rem; }
.legend li { display: flex; align-items: center; gap: 0.6rem; min-width: 0; font-size: 0.875rem; color: var(--c-ink); }
.swatch {
  position: relative;
  flex: none;
  inline-size: 1.75rem;
  block-size: 1.5rem;
  border: 1px solid var(--c-line);
  border-radius: var(--radius-sm);
}
.swatch[data-half] { background: transparent; }
.swatch[data-half]::after {
  content: '';
  position: absolute;
  inset: 50% 0 0;
  background: var(--c-accent);
  border-radius: var(--radius-sm);
}

/* Where the company stands on the free plan (plans.mjs), under the people page's head: quiet, louder near the line. */
.plan-line { margin: 0.5rem 0 0; font-size: 0.8125rem; color: var(--c-ink-subtle); }
.plan-line a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.plan-line--near { color: var(--c-warn); }
.plan-line--full { color: var(--c-warn); font-weight: 600; }

/* A signed-out page built from app parts (the sign-in link page): it clears the fixed public header,
   and a short page keeps the footer at the bottom of the screen instead of halfway up it. */
body.lp { min-height: 100dvh; display: flex; flex-direction: column; }
body.lp > main { flex: 1 0 auto; }
body.lp > main > .mx-page { padding-block: calc(var(--lp-header-h) + 2.5rem) 4rem; }
