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