/* Bustan — MIORA's design system. Tokens and atoms, shared by every surface.
 *
 * **An atom used by two surfaces lives here.** `portal.css` is layouts and
 * instruments, never a card. The dining rail broke this rule and shipped every
 * diner page unstyled while a byte-budget test stayed green — bytes were never
 * what it measured.
 *
 * **Logical direction only.** No `left`, no `right`, in any token name,
 * component name, or property. `tools/design_lint.py` fails the build on one.
 * The reason is mechanical: the Ribbon is a horizontal pager with a 14px peek
 * and direction is its whole mechanism, Week-Dots carry a today-underline and a
 * reading order, and the Ticket sets numerals beside a state seal. Retrofitting
 * direction into those costs the design system rather than an afternoon.
 *
 * **The anti-coupon law is structural, not a guideline.** There is no badge
 * atom, no starburst, and no red that is not an error. In this segment visible
 * discounting reads as decline, so her rate is quiet fir text and nothing on
 * any surface shouts about it.
 *
 * **The strikethrough clause was lifted on 2026-08-30, deliberately.** It read
 * "no strikethrough" from the first draft, and the reasoning was sound for the
 * thing it was aimed at: a struck price is the grammar of a sale, and a sale
 * is what this product is not. The frozen rebuild strikes the salon's price
 * anyway, and looking at both pages side by side settles why. Two bare figures
 * on one line state no relation — a reader meeting `120  96` has to work out
 * which is which, and the quiet grey of the first is not an answer. The rule
 * through it says *this is the price, and it is not the one you pay* in a mark
 * every reader already knows. The rest of the law does the work the clause was
 * carrying: no badge announces it, no percentage is set larger than the price,
 * no colour changes, and the mark appears **once**, on the salon's own figure,
 * beside hers. Struck once and quietly is a statement of fact; struck under a
 * starburst is a coupon, and that is still banned.
 */

/* ============================================================ 1 · tokens */

:root {
  /* Native controls — the date picker, the select's own menu, a scrollbar —
     follow the page rather than the platform default. Without it a
     `datetime-local` on a dark salon page renders as a white slab with a black
     calendar glyph, which is what looking at the page found. Set beside the
     tokens rather than in the dark block, because it has to change with them. */
  color-scheme: light;
  /* Ground and paper. Ivory rather than white: white is a screen, ivory is a
     room. The card sits a half-step above the page, never on a shadow that
     reads as a popup. */
  /* **Paper, not white.** #FFFDF6 read as an unpainted default; the garden's
     ivory is warmer and reads as a page. `--raised` is the darkest ground any
     text is ever set on, which is why every ink below is measured against it
     rather than against the card. */
  --ground: #FDFBF4;
  --card: #FFFFFF;
  --raised: #EFE6CE;
  --line: #E9E2D0;
  --rule: #E6DEC8;

  /* Ink. Fir is the one action colour app-wide — a salon's own identity kit
     dresses its pane, but the Request button is fir everywhere, because an
     action that changes colour per salon is an action she has to re-learn. */
  --ink: #222C25;
  --fir: #2F4A38;
  --fir-deep: #24382B;
  --on-fir: #FDFBF4;
  /* Darkened from the mockups' #8C9383 and #A9A28C, deliberately.
     Those values measure 3.17:1 and 2.55:1 against white — the second fails
     even WCAG's 3:1 large-text floor, and both carry body-size text: 13px row
     labels, 12px captions, the 9px tracked section headers, and the empty
     state's reason line. Nothing here is decorative.
     These clear 4.5:1 against the darkest ground they are ever set on
     (--raised). The system loses a little of the mockups' airiness and gains
     a section header a woman can read in a bright salon, which is the trade
     the design law already names: money is serious, legibility first. */
  /* **The garden's greys, darkened until they hold.** The mockups set these
     at #7E8478 and its lighter kin — 3.09:1 on `--raised`, which is below the
     3:1 large-text floor and these carry 12.5px body. Darkened 21% until they
     clear 4.5:1 on the darkest ground they are set on. Same trade the greys
     already record: a little of the mockups' airiness, for a caption a woman
     can read in a bright salon. */
  --sub: #64685F;
  --faint: #6E6A5E;

  /* Accents, and each has exactly one job.
     Rose is generosity — gifts and credits, never a warning.
     Peach is access — open windows and quiet hours.
     Brass is the Rate Seal and nothing else. */
  --rose: #AB4457;
  --rose-soft: #E491A0;
  --rose-wash: #FDEDF0;
  --rose-line: #F3CBD5;
  /* Peach carries one thing — the Pulse pane's 7.5px tracked kind label — and
     the mockup value measures **2.28:1**, which is not legible at any size and
     is a caption at that one. `--peach-wash` and `--peach-line` are grounds and
     keep the mockups' own values; this is the ink. */
  --peach: #995533;
  --peach-wash: #FDF1E8;
  --peach-line: #F4DFCB;
  /* **Measured, not eyeballed — the same trade §1 records for the greys.**
     The mockups' brass and fir measure 3.94:1 and 4.31:1 against `--raised`,
     the darkest ground either is set on, and both carry body-size text: the
     seal, the paid tick, the today mark. Darkened until they clear 4.5:1.
     The dark twin's own values are re-measured in its block. */
  --brass: #7C6336;
  --brass-soft: #C98E43;

  /* States. `--amber` is waiting, not warning; `--alert` is the only red on
     any surface and never touches money. */
  --good: #456E4E;
  --amber: #93866F;
  --alert: #A6432F;

  /* Type. Each face with one job — see tools/fetch_fonts.py.

     **Instrument Sans replaced Karla, and the figures are the reason.** Karla
     sets proportional oldstyle by default, so a page of times and dates read
     `30` as `3O` and `31` as `3I` — on the one surface where every number is
     functional. Every figure that means something is now tabular lining, set
     once in `.num` rather than remembered per component.

     **DM Mono is struck from the member rail.** Money in a monospace read as
     code rather than as currency; her price is Cormorant, and the console
     keeps DM Mono where a column of figures must align.

     **Italic Cormorant is retired as a page-title style.** It was on every
     screen and read bridal; display is roman 600 now, and italic survives
     only as a deliberate accent. */
  /* **The garden's own inks.** Ornament is drawn from these and never from
     literals, so a theme change moves the flowers with the type. `--sage` is
     the growing green the week and the vine share; the two gold leaves are the
     crest and the bloom; `--ring` and `--petal` are the rosette's. */
  --sage: #5F7355;
  --sage-pale: #8CA07E;
  --gold-leaf: #C7AE68;
  --gold-leaf-2: #D9C48A;
  --ring: #D8C49E;
  --petal: #E3D0AE;
  --petal-rose: #F3C3CE;
  /* The pane's paint. Warmer and more saturated than the line work, because
     paint is what it is — and named here so the one painted surface in the
     system still answers to the theme. */
  --pane-ground: #FFFBF0;
  --sage-leaf: #A7BC97;
  --bloom-rose: #EFA9B6;
  --bloom-rose-deep: #C9647A;
  --bloom-peach: #F6C8AE;
  --bloom-peach-deep: #DE9A73;
  --rose-deep: #DA9AA9;
  --display: 'Cormorant Garamond', 'Amiri', Georgia, serif;
  --ui: 'Instrument Sans', 'IBM Plex Sans Arabic', system-ui, -apple-system, sans-serif;
  /* **The member rail's figures are the UI face, set tabular and lining.**
     They were DM Mono, and money in a monospace reads as code rather than as
     currency — her price is the one number on the page that should feel like
     a price. The console keeps `--figure-mono`, where a dense column of
     amounts genuinely needs the glyphs to align. */
  --figure: 'Instrument Sans', system-ui, -apple-system, sans-serif;
  --figure-mono: 'DM Mono', ui-monospace, SFMono-Regular, monospace;
  --hero: 'Fraunces', 'Cormorant Garamond', Georgia, serif;
  --wordmark: 'Aref Ruqaa', 'Amiri', serif;
  --arabic: 'IBM Plex Sans Arabic', 'Amiri', 'Aref Ruqaa', sans-serif;
  /* **A display Arabic, for the one place Arabic is a name rather than text.**
     Plex Arabic is the reading face and is right for a price list; a salon's
     own name set in it is a label. Aref Ruqaa is a Ruqʿah display face and was
     already self-hosted and unused — an undefined `var()` here would have
     silently fallen back to the reading face and nobody could have told. */
  --arabic-display: 'Aref Ruqaa', 'Amiri', var(--arabic);

  /* The scale, named once. 12.5 / 14 / 19 / 25 / 38 — base drops from ~17 to
     14, because the rail was a phone layout stretched to a desktop viewport
     and everything on it was one size too large for the room it was in. */
  --t-caption: 12.5px;
  --t-body: 14px;
  --t-lede: 19px;
  --t-title: 25px;
  --t-display: 38px;

  --radius-card: 18px;
  --radius-sheet: 22px;
  --radius-btn: 12px;
  --radius-chip: 999px;

  --step: 4px;
  --gutter: 20px;

  /* The one place a physical direction is named, and it is named once.
     `linear-gradient()` has no reliable logical keyword — CSS Images 4 defines
     one and browsers do not agree on it — so the direction becomes a token that
     flips with the document, and every component reads the token. A component
     that spelled the side itself would be a component that stops mirroring. */
  --flow-end: to right;

  /* One shadow, used sparingly. A system with five shadows has none. */
  --lift: 0 12px 26px -16px rgb(51 64 47 / 0.22);
}

/* The dark twin. Bustan at dusk rather than an inversion: the garden keeps its
   fir and its rose, and the ivory becomes the deep shade under a tree. */
:root:dir(rtl) {
  --flow-end: to left;
}

:root:not([data-theme='light']) {
  @media (prefers-color-scheme: dark) {
    color-scheme: dark;
    --ground: #16150F;
    --card: #1E1D16;
    --raised: #262419;
    --line: #33301F;
    --rule: #3B3726;
    --ink: #EDE7DA;
    --fir: #7C9E86;
    --fir-deep: #6B8D75;
    --on-fir: #12140F;
    --sub: #9AA58F;
    --faint: #8F8A7A;
    /* The garden at night: the same plants, grown in less light. Every one is
       darkened rather than inverted — a bloom that gets brighter when the room
       does not is a light source, and none of these is one. */
    --sage: #7C9070;
    --sage-pale: #5E7053;
    --gold-leaf: #7C6A3E;
    --gold-leaf-2: #8E7A4A;
    --ring: #4B4230;
    --petal: #5A4E38;
    --petal-rose: #5C3A42;
    --pane-ground: #1B1A13;
    --sage-leaf: #536046;
    --bloom-rose: #6E4048;
    --bloom-rose-deep: #93555F;
    --bloom-peach: #6B4A38;
    --bloom-peach-deep: #96694A;
    --rose-deep: #7A4E58;
    --rose-wash: #2A1A1E;
    --rose-line: #47262D;
    --peach-wash: #2A2018;
    --peach-line: #45341F;
    /* **The accents, re-measured against this ground.** Every one of these was
       inherited from the light palette and never checked here — and three of
       them failed: rose measures 2.98:1 on `--raised` (below even the 3:1
       large-text floor) and carries the gift line; brass 3.44:1 and carries
       today's mark; fir-as-good 1.86:1, which is a tick nobody can see. Lifted
       to clear 4.5:1 on the darkest ground each is set on, the same trade §1
       documents for the mockup greys: a little of the palette's depth for a
       figure a woman can read in bed at eleven at night, which is when this
       product is used.
  
       Each keeps its one job: rose is generosity, peach is access, brass is the
       seal. */
    --rose: #E491A0;
      --peach: #E5AE8C;
    --brass: #C9A96A;
    --brass-soft: #DCB878;
    --good: #7C9E86;
  }
}

:root[data-theme='dark'] {
  color-scheme: dark;
  --ground: #16150F;
  --card: #1E1D16;
  --raised: #262419;
  --line: #33301F;
  --rule: #3B3726;
  --ink: #EDE7DA;
  --fir: #7C9E86;
  --fir-deep: #6B8D75;
  --on-fir: #12140F;
  --sub: #9AA58F;
  --faint: #8F8A7A;
  --sage: #7C9070;
  --sage-pale: #5E7053;
  --gold-leaf: #7C6A3E;
  --gold-leaf-2: #8E7A4A;
  --ring: #4B4230;
  --petal: #5A4E38;
  --petal-rose: #5C3A42;
  --pane-ground: #1B1A13;
  --sage-leaf: #536046;
  --bloom-rose: #6E4048;
  --bloom-rose-deep: #93555F;
  --bloom-peach: #6B4A38;
  --bloom-peach-deep: #96694A;
  --rose-deep: #7A4E58;
  --rose-wash: #2A1A1E;
  --rose-line: #47262D;
  --peach-wash: #2A2018;
  --peach-line: #45341F;

  /* **The accents, re-measured against this ground.** Every one of these was
     inherited from the light palette and never checked here — and three of
     them failed: rose measures 2.98:1 on `--raised` (below even the 3:1
     large-text floor) and carries the gift line; brass 3.44:1 and carries
     today's mark; fir-as-good 1.86:1, which is a tick nobody can see. Lifted
     to clear 4.5:1 on the darkest ground each is set on, the same trade §1
     documents for the mockup greys: a little of the palette's depth for a
     figure a woman can read in bed at eleven at night, which is when this
     product is used.

     Each keeps its one job: rose is generosity, peach is access, brass is the
     seal. */
  --rose: #E491A0;
  --peach: #E5AE8C;
  --brass: #C9A96A;
  --brass-soft: #DCB878;
  --good: #7C9E86;
}

/* ============================================================ 2 · ground */

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

body {
  margin: 0;
  background: var(--ground);
  position: relative;
  isolation: isolate;
  color: var(--ink);
  font-family: var(--ui);
  /* **The rail was a phone layout at a desktop viewport**, and everything on
     it was one size too large for the room it was in. The scale is named in
     the token block; this is its base. */
  font-size: var(--t-body);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* **Paper grain — ivory should read as paper, not as a screen.** The
   consolidated mockup multiplies a still SVG turbulence over the ground at
   half opacity; the same noise, fixed, painted *behind* the content — the
   mockup lets it touch the cards too, but a texture over 12.5px captions is
   a texture the contrast budget never agreed to. Deterministic pixels, so
   the look gate's goldens stay byte-stable. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.5;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/><feColorMatrix type='saturate' values='0'/></filter><rect width='160' height='160' filter='url(%23n)' opacity='.22'/></svg>");
}

/* At dusk the same grain would only mud the shade — multiply over a near-black
   ground is invisible at best and a smear at worst, so the paper texture is a
   daylight fact. */
:root:not([data-theme='light']) {
  @media (prefers-color-scheme: dark) {
    body::before { content: none; }
  }
}

:root[data-theme='dark'] body::before { content: none; }

/* Arabic sets in Amiri and reads a size larger — the script's x-height is
   smaller at the same point size, and matching the Latin number would leave it
   looking timid beside it. */
:root[lang='ar'] body,
[lang='ar'] {
  font-family: var(--arabic);
  font-size: 15px;
  line-height: 1.75;
}

/* An Arabic block takes the width of its own words, never a sibling's.
 *
 * `dir="rtl"` sets where the run *starts*, and on a block that means the box's
 * end edge. The salon page stacks the Latin name, the Arabic name and the area
 * inside one shrink-wrapped column, so the box was sized by `Layali Al Reem`
 * and `ليالي الريم` hugged the far side of it — aligned with neither the name
 * above it nor the area below, floating in the middle of the page on the one
 * screen that is the salon's own.
 *
 * `fit-content` is the whole fix and it is safe on long text: it resolves to
 * the available width once the words no longer fit on a line, so a paragraph
 * still wraps. Not on `input`, which owns its box already.
 */
p[lang='ar'],
div[lang='ar'] {
  width: fit-content;
}

/* Every figure is tabular, and **every figure is a bidi island**.
 *
 * The tabular part is ordinary: a column of money that shifts as digits change
 * is a column nobody can scan.
 *
 * The isolation is not ordinary, and it was found by rendering the page in
 * Arabic and reading it. `−100.00` inside an RTL document renders visually as
 * `100.00−`: the minus is a neutral character, so the bidi algorithm reorders
 * it to the far end of the run. **The sign is the whole meaning of that line** —
 * it is what says the amount comes off her bill — and it was landing on the
 * wrong side of the number, on the bill stack, which is the one screen where
 * the brand is or is not what it claimed.
 *
 * A number in Arabic prose is written left to right regardless. `isolate` stops
 * it perturbing the sentence around it; `direction: ltr` keeps the sign welded
 * to its digits. Flex still places the box by the row's direction, so the
 * mirroring is untouched — only the run's internal order is pinned.
 *
 * The same treatment is what phone numbers, dates and identifiers need, and for
 * the same reason. */
.fig,
.money,
.dash,
.testmode,
input.fig,
.stand .code,
.prices .was,
.dc .dw,
.slot,
.lr > .v,
.lr > dd,
.seal b,
.week span,
.ticket .when .meridiem,
.hero-num,
td.num,
code,
kbd {
  font-family: var(--figure);
  /* Lining as well as tabular: Cormorant and Karla both default to oldstyle,
     which is how `30` came to read as `3O` and `31` as `3I` on a page whose
     every number is a time, a date or an amount. */
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: 'tnum' 1, 'lnum' 1;
  unicode-bidi: isolate;
  direction: ltr;
}

/* ============================================================ 3 · marks */

.mark {
  display: inline-flex;
  flex-direction: column;
  line-height: 1;
}

.mark .ar {
  font-family: var(--wordmark);
  font-size: 22px;
  color: var(--fir);
}

/* The Latin half of the lockup. **8px tracked caps under a 22px wordmark** is
   a caption, and on the desk that slot carries the salon's own trading name —
   so a salon's identity was a caption beneath somebody else's mark. */
.mark .en {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.28em;
  color: var(--sub);
  margin-block-start: 2px;
}

/* --- The handoff --------------------------------------------------------- */
/* F-23. One line above the bill: the same state, one tap away. It is quiet on
   purpose — the web finished the whole job, and the app is an upgrade rather
   than a requirement. A banner that shouted would be the install wall this
   product exists without. */

.handoff {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--raised);
  font-size: 12px;
  color: var(--sub);
  text-decoration: none;
}

.handoff b {
  color: var(--ink);
  font-weight: 500;
}

/* --- The code by the mirror -------------------------------------------- */
/* O-03. The salon's own address as a square, printed once and stuck up where
   she sits. It is inline SVG at scale 1, so the size is set here rather than in
   the generator — a poster and a phone screen want different squares and only
   the stylesheet knows which one it is on.

   `image-rendering: pixelated` is not needed and would be wrong: this is
   vector, and a printer should get the vector. */

.qr {
  margin-block: 12px;
  display: flex;
  justify-content: center;
}

.qr svg {
  inline-size: min(260px, 60vw);
  block-size: auto;
}

/* A printed poster is the whole point, so the print sheet drops everything the
   wall does not need and leaves the square and the address. */
@media print {
  .hd,
  .rail,
  .btn,
  nav {
    display: none;
  }

  .qr svg {
    inline-size: 70vw;
  }
}

/* --- Stand mode ------------------------------------------------------- */
/* O-03. The daily code, on a tablet propped by the mirror, read from a metre
   away. It is the same number the desk screen shows — one page, one query, one
   truth — set large enough to be read rather than leaned into.
 *
 * The rail and the header are hidden because a stand is not being operated: it
 * is being looked at, by somebody who is not staff. */

.stand {
  min-block-size: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  text-align: center;
  padding: var(--gutter);
  background: var(--ground);
}

.stand .code {
  font-size: clamp(56px, 18vw, 132px);
  letter-spacing: 0.12em;
  line-height: 1;
  color: var(--ink);
}

@media print {
  /* Two things get printed: the stand, taped up when a tablet cannot be
     spared, and O-13's statement, which is what an accountant is given.
     Neither wants navigation on the page. */
  .stand { min-block-size: auto; }
  .hd,
  .rail,
  .today,
  .testmode,
  .btn { display: none; }

  .page,
  .work { padding: 0; }

  .card {
    box-shadow: none;
    break-inside: avoid;
  }
}

/* --- The picker ------------------------------------------------------- */
/* F-04a, ported from the mockup's own rules onto the tokens and onto logical
   directions — the mockup writes `left` and `bottom`, and this system is
   built direction-first so the Arabic rendering flips without touching a
   component.
 *
 * **The slots are the counts.** A slot inside an armed window wears its
 * `instant` tag because the salon has already said yes to that hour; the rest
 * are questions it will answer. That distinction is the whole of F-06 and it
 * was invisible while the rail served a `datetime-local`. */

.daychips {
  display: flex;
  gap: 7px;
}

.dc {
  flex: 1;
  border-radius: 16px;
  border: 1px solid var(--line);
  background: var(--card);
  padding-block: 10px;
  text-align: center;
  text-decoration: none;
  display: block;
}

.dc.on {
  border-color: var(--fir);
  background: var(--fir);
}

.dc.on .dw { color: var(--on-fir); opacity: 0.7; }

.dc.on .dn { color: var(--on-fir); }

/* A day the salon does not trade is not offered. It stays visible so the week
   keeps its shape — five chips, one of them plainly shut. */
.dc.shut {
  opacity: 0.4;
  pointer-events: none;
}

.dc .dw {
  font-size: 9.5px;
  font-weight: 600;
  color: var(--faint);
  letter-spacing: 0.22em;
  text-transform: uppercase;
}

.dc .dn {
  /* The numeral is a figure, not a title — the markup wears `.num`, which is
     the one place the figure face is declared. Cormorant here read as
     ceremony over a Tuesday. */
  font-weight: 600;
  font-size: 22px;
  color: var(--ink);
  margin-block-start: 2px;
}

.slotgrid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-block: 10px 18px;
}

.slot {
  block-size: 42px;
  border-radius: 16px;
  border: 1px solid var(--line);
  background: var(--card);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
  position: relative;
  cursor: pointer;
}

.slot:has(input:checked),
.slot.on {
  border-color: var(--fir);
  background: var(--fir);
  color: var(--on-fir);
}

.slot input {
  position: absolute;
  opacity: 0;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  cursor: pointer;
}

.slot:focus-within {
  outline: 2px solid var(--fir);
  outline-offset: 2px;
}

/* The picker's room line, over the question. */
.pick-room {
  display: block;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--tint-ink, var(--brass));
  margin: 0 0 4px;
}

/* The picker's dock. The chosen line, its pair, the echoed time, one verb. */
.pickdock {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px 12px;
  margin-block-start: 14px;
}

.pickdock .dockline {
  flex: 1 1 auto;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.pickdock .dockline .note { font-size: 11.5px; }

.pickdock .docktime {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
}

.pickdock .docktime:empty { display: none; }

.pickdock .prices { margin: 0; }

.pickdock .prices .v {
  font-family: var(--display);
  font-weight: 600;
  font-size: 20px;
  color: var(--fir);
}

.pickdock .was { font-size: 11.5px; }

.pickdock .btn {
  flex: 1 1 100%;
  margin-block-start: 8px;
}

@media (max-width: 999px) {
  .pickdock {
    position: fixed;
    inset-inline: 0;
    inset-block-end: 0;
    z-index: 80;
    margin: 0;
    border-block-start: 1px solid var(--rule);
    background: color-mix(in srgb, var(--raised) 45%, var(--ground));
    padding: 12px var(--gutter) 14px;
  }

  .page.haspickdock { padding-block-end: 132px; }
}

/* Review: the crest row over the stack, and the way back under the verb. */
.rv-head {
  display: flex;
  align-items: center;
  gap: 11px;
}

.rv-head .rv-sigil {
  inline-size: 34px;
  block-size: 34px;
  border-radius: 10px;
}

.rv-head p { margin: 0; }

.rv-head .rv-item {
  font-weight: 600;
  font-size: 13.5px;
  color: var(--ink);
}

.backline {
  text-align: center;
  margin-block: 13px 0;
}

.linkback {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--fir);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* The salon said yes to this hour in advance, so the request confirms itself.
   The tag sits below the numeral rather than inside it — a slot is a time, and
   a word crowding the time is a time that takes longer to read. */
.slot.inst::after {
  content: 'instant';
  position: absolute;
  inset-block-end: -7px;
  inset-inline-start: 50%;
  transform: translateX(-50%);
  font-family: var(--ui);
  font-size: 6.5px;
  letter-spacing: 0.12em;
  font-weight: 700;
  color: var(--fir);
  background: var(--ground);
  padding-inline: 5px;
  border-radius: 99px;
  border: 1px solid var(--line);
}

.slot.dis {
  opacity: 0.35;
  pointer-events: none;
}

/* --- Two quiet actions, side by side ---------------------------------- */
/* The ticket's second row: never the primary verb, always a pair. */

.qact {
  display: flex;
  gap: 10px;
  margin-block-start: 12px;
}

.qact > * {
  flex: 1;
  margin: 0;
}

/* --- Two prices on one line ------------------------------------------- */
/* F-12a: the salon's price and hers, side by side. The posted figure is
   quieter **and struck** — see the amendment in this file's header, which
   lifted the law's strikethrough clause on the evidence of the two pages set
   beside each other. The rule is what makes the pair legible as a pair: it is
   not a discount being advertised, it is what the salon charges beside what
   she pays, and without the mark those are two numbers in a row. */

.prices {
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
  font-variant-numeric: tabular-nums lining-nums;
}

.prices .was {
  font-size: var(--t-caption);
  /* Lighter than `--sub`: struck, this figure is being set aside, and at the
     body weight it competed with the one she pays. Measured 3.1:1 — below the
     floor for text that has to be read, which this no longer is: the figure
     it qualifies is beside it at 22px, and the price list states every posted
     figure again, unstruck, on the review screen before she commits. */
  color: #8E9187;
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}

/* --- The garden: band, lockup, vine, pane, leaf ----------------------- */
/* **The page has a temperature.** Same information, same scale, same lining
   figures as the build it replaces — and a room rather than a template. Every
   piece here is drawn from the token set rather than from an image, so a theme
   change moves the flowers with the type and nothing is fetched to render a
   mood. The kit is `_garden.html`; this is where it sits on the page. */

/* The wash behind the name. It bleeds to the viewport edge because a tint that
   stops at the text column reads as a card, and a card is a component — this
   is supposed to be the room the page is in. */
.page:has(.band) {
  padding-block-start: 0;
}

.band {
  position: relative;
  overflow: hidden;
  margin-inline: calc(50% - 50vw);
  padding-block: 26px 22px;
  border-block-end: 1px solid var(--line);
  margin-block-end: 26px;
}

.band > .bandin {
  position: relative;
  z-index: 2;
  max-inline-size: 66rem;
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: flex-end;
  gap: 18px;
}

/* Six washes, one per salon, chosen by its name. Each fades to nothing before
   the right edge so the band lightens under the hours rather than boxing them. */
.tint-sand { background: linear-gradient(168deg, #FAF3E2 0%, #F6EAD0 62%, rgba(246, 234, 208, 0) 100%); }
.tint-rose { background: linear-gradient(168deg, #FCF1F2 0%, #F9E7EA 60%, rgba(249, 231, 234, 0) 100%); }
.tint-sage { background: linear-gradient(168deg, #F1F5EC 0%, #E6EEDF 60%, rgba(230, 238, 223, 0) 100%); }
.tint-brass { background: linear-gradient(168deg, #FAF2E0 0%, #F2E6C9 60%, rgba(242, 230, 201, 0) 100%); }
.tint-shell { background: linear-gradient(168deg, #FBF2EC 0%, #F6E7DB 60%, rgba(246, 231, 219, 0) 100%); }
.tint-olive { background: linear-gradient(168deg, #F5F4E6 0%, #ECEBD5 60%, rgba(236, 235, 213, 0) 100%); }

/* The crest tile in its salon's own wash — the mockup's c-* crests. Two full
   stops rather than the band's fade: a 34px tile with a transparent corner
   reads as a misprint. The dark block's single tile rule outranks these. */
.sigil.tint-sand { background: linear-gradient(160deg, #F7EFDA, #EFE1BE); }
.sigil.tint-rose { background: linear-gradient(160deg, #FBEBEE, #F6DCE1); }
.sigil.tint-sage { background: linear-gradient(160deg, #EAF0E6, #DCE6D5); }
.sigil.tint-brass { background: linear-gradient(160deg, #FAF2E0, #F0E3C4); }
.sigil.tint-shell { background: linear-gradient(160deg, #FBEDE3, #F5DCCB); }
.sigil.tint-olive { background: linear-gradient(160deg, #F5F4E6, #EAE9D2); }

/* **The washes at dusk — found by looking at the dark twin.** The six tints
   were light literals with no night values, so a band's name set light ink on
   a light blush and vanished: the exact look-found class, on both the salon
   band and the member's. The same plants grown in less light — each wash
   darkened toward the ground rather than inverted, fading the same way. The
   sage ink lifts like every other accent measured against the dark ground. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .tint-sand { background: linear-gradient(168deg, #2b2718 0%, #262213 62%, rgba(38, 34, 19, 0) 100%); }
  :root:not([data-theme='light']) .tint-rose { background: linear-gradient(168deg, #2e1c21 0%, #29181c 60%, rgba(41, 24, 28, 0) 100%); }
  :root:not([data-theme='light']) .tint-sage { background: linear-gradient(168deg, #20261a 0%, #1b2115 60%, rgba(27, 33, 21, 0) 100%); }
  :root:not([data-theme='light']) .tint-brass { background: linear-gradient(168deg, #2b2515 0%, #262011 60%, rgba(38, 32, 17, 0) 100%); }
  :root:not([data-theme='light']) .tint-shell { background: linear-gradient(168deg, #2b211a 0%, #261c15 60%, rgba(38, 28, 21, 0) 100%); }
  :root:not([data-theme='light']) .tint-olive { background: linear-gradient(168deg, #262614 0%, #212110 60%, rgba(33, 33, 16, 0) 100%); }
  :root:not([data-theme='light']) .tint-sage,
  :root:not([data-theme='light']) .tint-olive { --tint-ink: #9DB48F; }
  :root:not([data-theme='light']) .sigil { background: linear-gradient(160deg, #2e2a1c 0%, #262114 100%); }
}

:root[data-theme='dark'] .tint-sand { background: linear-gradient(168deg, #2b2718 0%, #262213 62%, rgba(38, 34, 19, 0) 100%); }
:root[data-theme='dark'] .tint-rose { background: linear-gradient(168deg, #2e1c21 0%, #29181c 60%, rgba(41, 24, 28, 0) 100%); }
:root[data-theme='dark'] .tint-sage { background: linear-gradient(168deg, #20261a 0%, #1b2115 60%, rgba(27, 33, 21, 0) 100%); }
:root[data-theme='dark'] .tint-brass { background: linear-gradient(168deg, #2b2515 0%, #262011 60%, rgba(38, 32, 17, 0) 100%); }
:root[data-theme='dark'] .tint-shell { background: linear-gradient(168deg, #2b211a 0%, #261c15 60%, rgba(38, 28, 21, 0) 100%); }
:root[data-theme='dark'] .tint-olive { background: linear-gradient(168deg, #262614 0%, #212110 60%, rgba(33, 33, 16, 0) 100%); }
:root[data-theme='dark'] .tint-sage,
:root[data-theme='dark'] .tint-olive { --tint-ink: #9DB48F; }
:root[data-theme='dark'] .sigil { background: linear-gradient(160deg, #2e2a1c 0%, #262114 100%); }

/* Each wash carries its own ink — the eyebrow and the Arabic half of a lockup
   set on a tint take the tint's family, darkened until a 9.5px tracked cap
   still clears its ground. The mockups' own values (#B0714A, #61795A) sit
   near 3.5:1 and these are the same trade §1 records: a little of the
   airiness for a label a woman can read. */
.tint-sand,
.tint-brass { --tint-ink: var(--brass); }
.tint-rose { --tint-ink: var(--rose); }
.tint-shell { --tint-ink: var(--peach); }
.tint-sage,
.tint-olive { --tint-ink: #52654A; }

/* ------------------------------------------------- the door's grove ---- */
/* The guest door's collection, one garden card per admitted salon. The card
   is the salon's whole argument — who, where, when she can come, one verb —
   and its tint, botanical and ink derive from its own name, the same three
   strides the salon page walks, so a room keeps one face from the door in. */
.grove {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.gcard {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: var(--radius-card);
  padding: 16px 16px 14px;
  text-decoration: none;
  color: var(--ink);
}

.gcard .tag,
.gcard .arn { color: var(--tint-ink); }

/* The spill wears the tint's family too — ornament may stay lighter than the
   ink because it carries no fact. */
.tint-rose .spill { --gold-leaf: var(--bloom-rose); --gold-leaf-2: var(--petal-rose); --brass: var(--bloom-rose-deep); }
.tint-shell .spill { --gold-leaf: var(--bloom-peach); --gold-leaf-2: var(--bloom-peach); --brass: var(--bloom-peach-deep); }
.tint-sage .spill,
.tint-olive .spill { --gold-leaf: var(--sage-leaf); --gold-leaf-2: var(--sage-leaf); --brass: var(--sage); }
.tint-rose .sigil svg { --gold-leaf: var(--bloom-rose); --gold-leaf-2: var(--petal-rose); --brass: var(--bloom-rose-deep); }
.tint-shell .sigil svg { --gold-leaf: var(--bloom-peach); --gold-leaf-2: var(--bloom-peach); --brass: var(--bloom-peach-deep); }
.tint-sage .sigil svg,
.tint-olive .sigil svg { --gold-leaf: var(--sage-leaf); --gold-leaf-2: var(--sage-leaf); --brass: var(--sage); }

.gcard .spill {
  position: absolute;
  pointer-events: none;
  inset-block-start: -26px;
  inset-inline-end: -14px;
  inline-size: 160px;
  block-size: 140px;
  z-index: 0;
}

.gcard .gc-head {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: flex-end;
  gap: 12px;
}

.gcard .sigil {
  inline-size: 44px;
  block-size: 44px;
  border-radius: 13px;
}

.gcard .lock .en {
  font-size: 26px;
  margin-block-start: 4px;
}

.gcard .lock .tag { font-size: 9.5px; }

.gcard .lock .arn {
  font-size: 13px;
  margin-block-start: 4px;
}

.gcard .gc-when {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 12px;
  margin-block-start: 12px;
}

.gcard .gc-when .when {
  font-size: 11.5px;
  color: var(--sub);
}

.gcard .gc-when .wk {
  display: inline-flex;
  margin-inline-start: auto;
}

.gcard .btn {
  position: relative;
  z-index: 1;
  margin-block-start: 12px;
}

/* **The fading ledger** — the card's glimpse of the page it opens. Three
   lines at most, dissolving into the verb: the fade is the sentence "there
   is more", said without a word. The pair inside wears `.prices .was`/`.v`,
   so the one struck-price rule in the system is the one that strikes it. */
.gcard .glimpse {
  position: relative;
  z-index: 1;
  display: block;
  margin-block-start: 12px;
  padding-block-start: 6px;
  border-block-start: 1px dotted var(--line);
  -webkit-mask-image: linear-gradient(180deg, #000 30%, rgb(0 0 0 / 0.42) 68%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 30%, rgb(0 0 0 / 0.42) 68%, transparent 100%);
}

.gcard .gl-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding-block: 5.5px;
}

.gcard .gl-name {
  flex: 0 1 auto;
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.gcard .gl-lead {
  flex: 1;
  min-inline-size: 14px;
  border-block-end: 1px dotted var(--line);
  transform: translateY(-4px);
}

.gcard .glimpse .prices {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  flex: 0 0 auto;
}

.gcard .glimpse .was { font-size: 11px; }

.gcard .glimpse .v {
  font-family: var(--display);
  font-weight: 600;
  font-size: 17px;
  line-height: 1;
  color: var(--fir);
  min-inline-size: 34px;
  text-align: end;
}

/* The same collection as a roster, where the page is wide enough to read a
   list — the desktop frame's shape. One of the two is always display: none. */
.roster { display: none; }

.rrow {
  display: flex;
  align-items: center;
  gap: 14px;
  padding-block: 13px;
  border-block-end: 1px solid var(--line);
  text-decoration: none;
  color: var(--ink);
}

.rrow .sigil {
  inline-size: 40px;
  block-size: 40px;
  border-radius: 12px;
}

.rrow .rr-say {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-inline-size: 0;
}

.rrow .rr-name {
  font-family: var(--display);
  font-weight: 600;
  font-size: 19px;
  line-height: 1.1;
}

.rrow .rr-name .arn {
  font-family: var(--arabic-display);
  font-size: 12px;
  color: var(--brass);
  margin-inline-start: 6px;
}

.rrow .wk {
  display: inline-flex;
  gap: 6px;
  margin-inline-start: auto;
}

/* The member's own band. The blush is hers the way a salon's tint is the
   salon's; the day is the eyebrow and the greeting is the lockup. */
.band.hers { margin-block-end: 20px; }

.band.hers .tag { color: var(--rose); }

.band.hers .greet-band {
  font-family: var(--display);
  font-weight: 600;
  font-size: 27px;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--ink);
  margin: 4px 0 0;
}

/* Her circle keeps its garden cards at every width — the roster swap below
   is the guest door's, not hers; the :not() carries that exception. */

/* The member's compact roster row: a 34px crest and the sentence, no week. */
.rrow.sm { padding-block: 12px; gap: 12px; }

.rrow.sm .sigil {
  inline-size: 34px;
  block-size: 34px;
  border-radius: 10px;
}

.rrow.sm .rr-name { font-size: 16px; }

/* The frame's small rows carry the Latin name alone — the Arabic half stays
   on the card, the roster and the salon's own page, where there is room for
   a name to be whole. */
.rrow.sm .rr-name .arn { display: none; }

/* The visits ledger row: dot, serif salon, service · state — the when at the
   end with its money under it. */
.vrow {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding-block: 12px;
  padding-inline: 4px;
  border-block-end: 1px solid var(--line);
  text-decoration: none;
  color: var(--ink);
}

.vrow:last-of-type { border-block-end: none; }

.vrow .sdot {
  inline-size: 16px;
  block-size: 16px;
  border-radius: 999px;
  flex: 0 0 auto;
  margin-block-start: 2px;
}

.vrow .sdot.coming { background: var(--raised); border: 1px solid var(--rule); }

.vrow .sdot.paid { background: var(--good); }

.vrow .sdot.past { background: var(--line); }

.vrow .vr-say {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-inline-size: 0;
}

.vrow .vr-name {
  font-family: var(--display);
  font-weight: 600;
  font-size: 16px;
  line-height: 1.2;
}

.vrow .vr-end {
  margin-inline-start: auto;
  text-align: end;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.vrow .vr-when {
  font-size: 12.5px;
  font-weight: 600;
}

.vrow .vr-money { font-size: 11px; }

.vrow .vr-money s { text-decoration-thickness: 1px; }

.vrow .vr-money b { color: var(--ink); }

/* You: the gifts strip — rose, hers, and only when it is true. */
.card.giftstrip {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--rose-wash);
  border-color: var(--rose-line);
  text-decoration: none;
  color: var(--ink);
}

.giftstrip .lbl.rosey,
.doorside .lbl.rosey {
  display: block;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--rose);
}

.giftstrip .note { display: block; margin-block-start: 2px; }

.giftstrip .gift-fig {
  margin-inline-start: auto;
  display: flex;
  align-items: baseline;
  gap: 5px;
}

.giftstrip .gift-fig b {
  font-family: var(--display);
  font-weight: 600;
  font-size: 26px;
  line-height: 1;
  color: var(--rose);
}

/* You: the quiet rows. A fact and a door on one line. */
.row-card { padding: 13px 15px; }

.rowline {
  display: flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
  color: var(--ink);
  cursor: pointer;
  list-style: none;
}

.rowline::-webkit-details-marker { display: none; }

.rowline > span:first-child {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-inline-size: 0;
  flex: 1 1 auto;
}

.row-say {
  font-weight: 600;
  font-size: 13.5px;
}

.row-sub {
  font-size: 8.5px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--faint);
}

.row-go {
  margin-inline-start: auto;
  flex: 0 0 auto;
  font-size: 14px;
  font-weight: 600;
  color: var(--fir);
}

details.row-card[open] .rowline { margin-block-end: 8px; }

/* The ghosted goodbye, centred. */
.signout-line { text-align: center; margin-block-start: 4px; }

.signout-line .btn.plain {
  inline-size: auto;
  block-size: auto;
  padding: 4px 8px;
  font-size: 11px;
  font-weight: 400;
  color: var(--faint);
}

/* The hold strip — B's fact, drawn once here so B has only to say the line. */
.hold {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 13px;
  border-radius: 12px;
  background: var(--raised);
  margin-block-start: 14px;
}

.hold .hi { flex: 0 0 auto; }

.hold .note { margin: 0; }

/* The bill page's answer is a size up from the review's — the frame sets
   'Your bill · 320' at 34 where 'You'll pay about' sits at 26. */
.billcard .stack .pays .v { font-size: 34px; }

/* The receipt's leading fact. */
.paidhead .state {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fir);
}

.paidhead .paidfig {
  font-family: var(--display);
  font-weight: 600;
  font-size: 44px;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--ink);
  margin: 10px 0 0;
}

/* The tip block on the page: the chips, then the sentence that owns the
   mechanism. The frame's tip chip is a small card, not a pill — four equal
   squares of 36, the chosen one filled fir. */
.tips {
  margin-block-start: 2px;
  display: flex;
  gap: 8px;
}

.tips .chip {
  flex: 1;
  block-size: 36px;
  border-radius: 11px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0;
  padding-inline: 0;
}

.tipwhy { margin-block: 8px 0; font-size: 11.5px; }

/* Prima speaks in sentences, at reading rhythm. */
.primaline {
  margin: 0 0 8px;
  font-size: 13.5px;
  line-height: 1.7;
}

/* The signed-in page at width: the ticket spreads, the aside carries her
   pane and the whisper-card. The band is full-bleed above both columns. */
.herside-aside .pane-gift {
  display: flex;
  align-items: baseline;
  gap: 5px;
  margin-block: 4px 2px;
}

.herside-aside .pane-gift b {
  font-family: var(--display);
  font-weight: 600;
  font-size: 30px;
  line-height: 1;
  color: var(--rose);
}

.herside-aside .m0 { margin: 8px 0 0; }

@media (min-width: 1000px) {
  /* The ticket at width: the verb sits at the end, vertically centred. */
  .herside .ticket {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    column-gap: 20px;
  }

  .herside .ticket > * { grid-column: 1; }

  .herside .ticket .btn {
    grid-column: 2;
    grid-row: 1 / span 3;
    inline-size: auto;
    /* The floor holds a short verb; the padding holds a long one. Without it
       *Tell them you are here* would sit against both edges. */
    min-inline-size: 132px;
    padding-inline: 18px;
    margin-block-start: 0;
  }
}

/* Her circle as a shelf on a phone: the cards ride a snap row with a peek of
   the next, which is the frame's carousel with no script at all. */
@media (max-width: 999px) {
  .grove-stay {
    flex-direction: row;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    margin-inline: calc(-1 * var(--gutter));
    padding-inline: var(--gutter);
  }

  .grove-stay::-webkit-scrollbar { display: none; }

  .grove-stay .gcard {
    flex: 0 0 88%;
    scroll-snap-align: center;
  }

  /* One card needs no shelf. */
  .grove-stay .gcard:only-child { flex-basis: 100%; }
}

/* The rose New at a row's end — a fact, not a badge: text in the tag's own
   cut, no ground, no border, and it retires with the sixty days. */
.rrow .newtag {
  margin-inline-start: auto;
  flex: 0 0 auto;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--rose);
}

.rrow .newtag + .wk { margin-inline-start: 14px; }

/* The shelf's typed panes — frame 3-01. An event is a fact with a verb in
   its salon's own ink; the gift wears rose because it is hers. They ride the
   same snap shelf as her cards, narrower, so the cards stay the headline. */
/* The ground sits in :where() at zero specificity, so a salon's wash class
   wins it without a duplicate gradient — the pane in the garden's colour is
   one class, not six more rules. */
:where(.ribpane) {
  border: 1px solid var(--line);
  background: var(--card);
}

.ribpane {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-card);
  padding: 14px 15px;
  text-decoration: none;
  color: var(--ink);
}

.ribpane .spill {
  position: absolute;
  pointer-events: none;
  inset-block-start: -24px;
  inset-inline-end: -12px;
  inline-size: 140px;
  block-size: 122px;
  z-index: 0;
}

.ribpane > :not(.spill) { position: relative; z-index: 1; }

.ribpane .pe {
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--tint-ink, var(--sub));
}

.ribpane .psay {
  font-size: 13.5px;
  font-weight: 500;
  line-height: 1.45;
  margin-block-start: 6px;
}

.ribpane .btn.sm {
  block-size: 36px;
  font-size: 12.5px;
  margin-block-start: auto;
  padding-block-start: 10px;
}

.ribpane .btn { margin-block-start: auto; }

.ribpane.rp-gift {
  background: var(--rose-wash);
  border-color: var(--rose-line);
}

.ribpane.rp-gift .pe { color: var(--rose); }

.ribpane .giftfig {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin-block-start: 8px;
}

.ribpane .giftfig i {
  font-style: normal;
  font-size: 11px;
  color: var(--sub);
}

.ribpane .giftfig b {
  font-family: var(--display);
  font-weight: 600;
  font-size: 30px;
  line-height: 1;
  color: var(--rose);
}

/* One label, two truths — and each shape keeps to its width. */
.at-desk { display: none; }

@media (min-width: 1000px) {
  .at-hand { display: none; }

  .at-desk { display: inline; }

  /* At width the pulse lists above and the gift paints in the aside; the
     shelf keeps only her cards. */
  .grove-stay .ribpane { display: none; }
}

@media (max-width: 999px) {
  /* On a phone the shelf is the pulse, so the list above yields to it. */
  .pulse-list { display: none; }

  .grove-stay .ribpane {
    flex: 0 0 246px;
    scroll-snap-align: center;
  }
}

/* The join card's own two verbs sit above the wash like everything else. */
.gcard .gc-act {
  position: relative;
  z-index: 1;
  margin-block-start: 12px;
}

.gcard .backline {
  position: relative;
  z-index: 1;
  margin-block: 10px 0;
}

/* The Circle's salon rows — variant B of the board. One room per row in its
   own wash (the tint class carries it), the crest, both names, the sentence,
   the week — badges trailing, where facts live. */
.srows {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.srow {
  display: flex;
  align-items: center;
  gap: 12px;
  border-radius: var(--radius-card);
  padding: 12px 14px;
  text-decoration: none;
  color: var(--ink);
  overflow: hidden;
}

.srow .sigil {
  inline-size: 44px;
  block-size: 44px;
  border-radius: 13px;
}

.srow .sr-say {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-inline-size: 0;
  flex: 1;
}

.srow .sr-name {
  font-family: var(--display);
  font-weight: 600;
  font-size: 19px;
  line-height: 1.1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.srow .sr-name .arn {
  font-family: var(--arabic-display);
  font-size: 12px;
  color: var(--tint-ink, var(--brass));
  margin-inline-start: 5px;
}

.srow .sr-sub {
  font-size: 11.5px;
  color: var(--sub);
}

.srow .sr-end {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 5px;
  flex: 0 0 auto;
}

.srow .wk { display: inline-flex; gap: 3px; }

.srow .newtag {
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--rose);
}

/* The switcher — the chip atom, worn as two links. */
.switch {
  display: flex;
  gap: 8px;
  margin-block: 0 10px;
}

.switch .chip { text-decoration: none; }

/* Her usual, as the shelf's first pane. */
.ribpane.usual .pe { font-size: 8.5px; }

.ribpane.usual .up-when {
  font-family: var(--display);
  font-weight: 600;
  font-size: 21px;
  line-height: 1.15;
  margin-block-start: 6px;
}

.ribpane.usual .up-sub {
  font-size: 11.5px;
  color: var(--sub);
  margin-block-start: 2px;
}

.ribpane.usual .btn { margin-block-start: auto; }

/* The pager's dots: grey until chosen, then the pane's own ink. */
.gdots {
  display: flex;
  gap: 7px;
  justify-content: center;
  align-items: center;
  margin-block-start: 10px;
}

.gd {
  inline-size: 7px;
  block-size: 7px;
  border-radius: 999px;
  background: var(--rule);
  cursor: pointer;
  transition: all 0.25s;
}

.gd.on {
  inline-size: 16px;
  background: var(--dot, var(--fir));
}

@media (min-width: 1000px) {
  /* At width the pulse lists above and the aside paints the gift; the shelf
     and its dots are the phone's shape. */
  .sect-shelf,
  #ribbon,
  .gdots { display: none; }
}

/* The planner card in the ticket's seat: the gcard, one size calmer. */
.nextcard { margin-block-start: 4px; }

.nextcard .lock .en { font-size: 24px; }

.grove-note {
  text-align: center;
  margin-block: 18px 6px;
  font-size: 11.5px;
}

/* **One salon, one card — including at width.** `_grove.html`'s own opening
   line says the door's grove and the Circle's list draw the same object, *so a
   room keeps one face wherever she meets it*, and this rule was the thing
   breaking that promise: it hid the garden card above 1000px and swapped in a
   row carrying no price, no verb, no wash and — for any room inside its first
   sixty days, which in a pilot is all of them — no hours either.
   
   So the desk door made MIORA's whole claim in the seal strip and then showed
   nothing that demonstrated it, on the page a referral spends AED 50 a side to
   land her on. And `/explore` has only a grove, so above 1000px it rendered an
   empty page with a heading over it.
   
   The cards lay out as a grid instead. `auto-fit` with a floor rather than a
   fixed count, because the door has four salons today and the collection grows
   by at most two a month — a hardcoded three would be wrong in April. */
@media (min-width: 1000px) {
  .grove:not(.grove-stay) {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    align-items: start;
  }

  /* The roster is gone from the door. The macro stays for `salon_srow`'s
     neighbour — a member's own circle is a list on purpose, because her eye is
     on the rooms she already has. */
  .roster { display: none; }
}

/* --------------------------------------------- the door's own pieces ---- */
/* `.greet.door-h1` — the base .greet is defined later in the sheet and its
   26 was quietly winning at equal specificity. */
.greet.door-h1 {
  margin-block: 18px 0;
  font-size: 28px;
}

/* The seal strip: the rosette wearing the base rate, beside the claim.
   `.card.sealstrip` because `.card` declares display later in the sheet. */
.card.sealstrip {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-block-start: 18px;
  background: var(--ground);
  border-color: var(--petal);
}

.sealfig {
  position: relative;
  inline-size: 64px;
  block-size: 64px;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.sealfig .rr {
  position: absolute;
  inset: 0;
}

.sealfig b {
  font-size: 15px;
  font-weight: 700;
  line-height: 1;
  color: var(--fir);
}

.sealstrip .rateline {
  margin: 0;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.45;
}

.sealstrip .note { margin-block: 4px 0; }

.sealstrip .note b { color: var(--ink); }

.sealstrip .how { margin-block-start: 6px; }

/* The label that closes with a leaf. */
.sect.ldiv svg { flex: 0 0 auto; }
.sect.ldiv::after { content: none; }

.sect.ldiv .r {
  flex: 1;
  block-size: 1px;
  background: linear-gradient(var(--flow-end), var(--rule), transparent);
}

/* The aside is the desktop's half of the door. */
.doorside { display: none; }

@media (min-width: 1000px) {
  .doorside {
    display: flex;
    flex-direction: column;
    gap: 16px;
  }

  /* The mechanism stands open in the aside there, so the strip's folded copy
     of it would be the same words twice on one screen. */
  .sealstrip .how { display: none; }

  .greet.door-h1 { font-size: var(--t-display); }
}

.doorside .pane {
  display: flex;
  text-decoration: none;
  color: var(--ink);
}

.doorside .pane-name {
  display: block;
  font-family: var(--display);
  font-weight: 600;
  font-size: 24px;
  line-height: 1.1;
  margin-block: 6px 2px;
}

.doorside .lbl.rosey { color: var(--rose); }

.how-card .lbl {
  display: block;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--faint);
  margin-block-end: 8px;
}

.how-card .how-said {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.62;
  color: var(--sub);
}

.how-card .how-said b { color: var(--ink); }

/* The band's eyebrow and the Arabic half of the lockup wear the tint's own
   ink — the mockup colours them by the room (terracotta on the peach wash,
   rose on the blush), darkened here to the --tint-ink values §1's trade
   already covers. Brass remains the fallback for a band with no tint. */
.band .lock .tag,
.band .lock .arn {
  color: var(--tint-ink, var(--brass));
}

/* The bloom spilling past the top edge. Anchored to the band's own right edge,
   which is the viewport's — anchored to the text column instead it landed on
   top of the hours, which is the one thing at that end of the band. */
.band .spill {
  position: absolute;
  pointer-events: none;
  inset-block-start: -72px;
  inset-inline-end: -34px;
  z-index: 1;
}

/* **The band's tile, and it is not the gallery's Crest atom.** That one is a
   card with a name under it and a lift beneath; reusing the class here bought
   its padding, its shadow and its white ground, and the sand tile vanished. */
.sigil.shot {
  overflow: hidden;
  background: var(--raised);
}

.sigil img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.sigil {
  inline-size: 58px;
  block-size: 58px;
  border-radius: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  background: linear-gradient(160deg, #F7EFDA, #EFE1BE);
}

/* Hours at the band's end: a caption, not body copy. */
.whenwk .when {
  font-size: var(--t-caption);
  line-height: 1.5;
  color: var(--sub);
}

/* **The bilingual lockup.** دار الورد under Dar Al Ward is the salon as it is
   actually called. Set as half of the name, not as a note about it: the Arabic
   is a display face at 19px in brass, not body copy at 14 in grey. */
.lock {
  line-height: 1;
  min-inline-size: 0;
}

.lock .tag {
  display: block;
  letter-spacing: 0.16em;
}

.lock .en {
  display: block;
  font-family: var(--display);
  font-style: normal;
  font-weight: 600;
  font-size: 42px;
  letter-spacing: -0.02em;
  color: var(--ink);
  margin: 6px 0 0;
  line-height: 1.02;
}

.lock .arn {
  display: block;
  font-family: var(--arabic-display);
  font-size: 19px;
  line-height: 1.15;
  color: var(--brass);
  margin-block-start: 5px;
  /* Shrink-wrapped, or the RTL run aligns to the far end of a stretched
     lockup — بيت نورة floated to the band's other side the moment the phone
     band let .lock take the row. Same rule §2 states for Arabic paragraphs. */
  inline-size: fit-content;
}

/* Hours and the week, at the band's far end. */
.whenwk {
  margin-inline-start: auto;
  text-align: end;
  flex: 0 0 auto;
}

.whenwk p {
  margin: 0;
}

/* The week is seven glyphs wide, not as wide as the sentence above it: as a
   stretched flex child its `justify-content` moved the leaves without moving
   the row, so the box reported the sentence's width and the today marker had
   nothing to sit under. */
.whenwk .wk {
  display: inline-flex;
  margin-block-start: 9px;
}

/* **The band at phone width.** The garden's mobile frame sets the name at
   30px, drops the eyebrow to 9.5, shrinks the bloom, and puts the hours and
   the week on their own line under the lockup — at 420px the 42px name wrapped
   across two lines and the bloom landed on top of the hours.
 *
 * One departure, deliberate: the garden's phone band carries no crest at all,
 * and this keeps it at 44px. G-10 is a law with a test behind it — a salon has
 * a face on every surface — and the surface where most members meet a room is
 * the one place that law should not be quietly dropped for space. */
@media (max-width: 559px) {
  .band {
    padding-block: 16px;
  }

  /* The phone frame's week is dots alone — at eleven pixels the letters were
     halving the leaf, and the sentence beside the dots already says the week
     out loud (and `aria-label` says it precisely). Desktop keeps the letters. */
  .band .wkd em { display: none; }

  .band > .bandin {
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
  }

  .sigil {
    inline-size: 44px;
    block-size: 44px;
    border-radius: 13px;
  }

  .sigil svg {
    inline-size: 26px;
    block-size: 26px;
  }

  .lock .tag {
    font-size: 9.5px;
  }

  .lock .en {
    font-size: 29px;
  }

  .lock .arn {
    font-size: 14px;
  }

  /* Its own line, reading from the start edge like everything else on a
     phone — right-aligned under a left-aligned name is a column of two. */
  .whenwk {
    inline-size: 100%;
    margin-inline-start: 0;
    text-align: start;
    display: flex;
    align-items: center;
    gap: 14px;
  }

  .whenwk .said {
    min-inline-size: 0;
  }

  .whenwk .wk {
    margin-block-start: 0;
    margin-inline-start: auto;
  }

  .band .spill {
    inline-size: 150px;
    block-size: 130px;
    inset-block-start: -24px;
    inset-inline-end: -12px;
  }
}

/* **The vine lives in the margin or it does not appear at all.** Placed 30px
   outside the text column it ran straight down the price list, over Bridal,
   over Hair spa and over the link at the foot — an ornament crossing the one
   column the page exists to be read in. It needs 96px of clear margin, so it
   is drawn only on windows wide enough to have that, and it sits behind the
   content in any case rather than trusting the geometry. */
.withaside-main {
  position: relative;
}

.vine {
  display: none;
  position: absolute;
  pointer-events: none;
  z-index: 0;
  inset-inline-start: -104px;
  inset-block-start: 250px;
}

@media (min-width: 1272px) {
  .vine {
    display: block;
  }
}

/* The list draws above it, so a narrow window or a long name can never put a
   leaf through a price. */
.ledger,
.saidby,
.lede.said {
  position: relative;
  z-index: 1;
}

/* The leaf that ends a list. Two rules fading from nothing, one leaf between. */
.ldiv {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-block: 22px 12px;
}

/* The two closing statements on one line: what this is, and who said it. */
.saidby {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
}

.saidby > * {
  margin: 0;
  flex: 0 0 auto;
}

/* Inside the closing row the disclosure drops the rule and the space it wears
   when it stands alone — the leaf divider above is already the break, and a
   second hairline eighteen pixels under it read as a mistake. */
.saidby .how {
  min-inline-size: 0;
  margin-block-start: 0;
  border-block-start: 0;
  padding-block-start: 0;
}

.saidby .how[open] {
  flex: 1 1 100%;
}

.saidby .note.faint {
  font-size: var(--t-caption);
}

.ldiv .r {
  flex: 1;
  block-size: 1px;
  background: linear-gradient(90deg, transparent, var(--line) 18%, var(--line) 82%, transparent);
}

/* **The one surface licensed for paint.** Everything else in the kit is drawn
   in the grammar layer; the pane is where a picture may go, and what it holds
   is hers — the credit waiting on her first bill, in the rose this system
   keeps for her own money. */
.pane {
  position: relative;
  border-radius: 18px;
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--card);
  min-block-size: 196px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 16px;
  margin-block-start: 14px;
}

.pane .pa {
  position: absolute;
  inset: 0;
}

/* **Paint does not get to make a sentence hard to read.** The gouache is
   cropped to fill, so where a bloom lands is a function of the box — on a
   narrow column a petal sat straight under the line naming her credit. The
   ground fades back in beneath the text, which keeps the painting whole and
   the words legible whatever the crop does. */
.pane::after {
  content: '';
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  block-size: 96px;
  background: linear-gradient(to bottom, transparent, var(--pane-ground) 62%);
}

.pane > *:not(.pa) {
  position: relative;
  z-index: 1;
}

.pane .lbl {
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--rose);
  margin: 0 0 6px;
}

.pane p {
  margin: 0;
  font-size: 13.5px;
  color: var(--ink);
}

/* --- The salon page: nameplate, hours, and the list as the picker ----- */
/* Measured against the frozen rebuild, this page put 224px of furniture above
   the thing it exists to sell — the first price began 486px down where the
   design puts it at 262, five lines above the fold instead of seven. What
   follows is the composition that closes that gap. */

.nameplate {
  display: flex;
  align-items: center;
  gap: 16px;
}

/* Upright and in ink here, not the italic fir of a greeting: this is a proper
   noun on a document, not MIORA speaking to her. */
.nameplate .greet {
  font-style: normal;
  font-size: 34px;
  color: var(--ink);
  letter-spacing: -0.015em;
}

.nameplate .face {
  margin-block-end: 0;
}

/* The eyebrow: where the room is, and that MIORA chose it. */
.tag {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--brass);
  margin: 0 0 2px;
}

/* Hours as a sentence with the week beside it — the card this replaces was
   215px of white between the name and the prices. */
.hours {
  display: flex;
  align-items: center;
  gap: 26px;
  margin-block: 18px 24px;
}

.hours > div:first-child > p {
  margin: 0;
}

.hours .wk {
  margin-inline-start: auto;
}

.wk {
  display: flex;
  gap: 9px;
  align-items: flex-end;
  flex: 0 0 auto;
}

.wkd {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
}

/* Shut: drawn in outline. Open: filled. The week reads as a row of leaves
   with gaps in it, which is the one fact seven dots have to carry. */
.wkd svg {
  fill: none;
  stroke: var(--line);
  stroke-width: 1.2;
  display: block;
}

.wkd svg.open {
  fill: var(--sage-pale);
  stroke: none;
}

/* Today is the one leaf in the action ink, so the week reads at a glance
   without the letter having to be marked as well. */
.wkd.today svg.open {
  fill: var(--fir);
}

.wkd em {
  font-style: normal;
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--sub);
}

/* Today is marked under its letter, so the mark does not change the dot's box
   — seven dots that grow on Tuesdays is a row that jitters through the week. */
.wkd.today em {
  color: var(--ink);
}

/* The sentence over the list: whose prices these are. */
.lede.said {
  color: var(--ink);
  font-weight: 500;
  font-size: var(--t-body);
  max-inline-size: none;
  margin-block-end: 12px;
}

/* **The list is the picker**, so every line that can be asked for is a button.
   Unboxing it was the other half: inside a bordered card the row rules had a
   second frame around them, and the ledger's whole idiom is a printed list on
   paper rather than a widget on a surface. */
.ledger {
  border-block-start: 1px solid var(--line);
}

.ledger .lr {
  inline-size: 100%;
  box-sizing: border-box;
  padding: 13px 12px 13px 14px;
  border: 0;
  border-block-end: 1px solid var(--line);
  border-radius: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: start;
  position: relative;
}

button.lr {
  cursor: pointer;
}

button.lr:hover {
  background: color-mix(in srgb, var(--raised) 34%, transparent);
}

/* The line she is asking for. The fill is the identity sand at low strength
   and the rule is the one action ink — the same pair the seal uses, so a
   selected row reads as *chosen* rather than as highlighted. */
.ledger .lr[aria-current] {
  background: color-mix(in srgb, var(--raised) 42%, var(--card));
}

.ledger .lr[aria-current]::before {
  content: '';
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: 3px;
  background: var(--fir);
}

/* The request panel echoes the chosen line. */
.ask h3 {
  font-family: var(--display);
  font-weight: 600;
  font-size: 24px;
  line-height: 1.15;
  color: var(--ink);
  margin: 10px 0 0;
}

.ask .prices {
  margin-block: 8px 0;
}

.ask .btn {
  margin-block-start: 14px;
}

/* The chosen line's pair in the panel: hers in the display face, in fir —
   the same answer the review's stack gives, at panel size. */
.ask .prices .v.big {
  font-family: var(--display);
  font-weight: 600;
  font-size: 26px;
  color: var(--fir);
}

.ask .was { font-size: 11.5px; }

/* **The dock.** Below the aside's width the request panel rides the bottom
   edge, the way the phone frame draws it: the chosen line, the pair, and the
   one verb — always in reach while the prices scroll. The label, the rule
   and the reassurance fold away; the sentence about nothing being held is
   already beside the prices she is reading. */
@media (max-width: 999px) {
  .withaside-side .ask {
    position: fixed;
    inset-inline: 0;
    inset-block-end: 0;
    z-index: 80;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 2px 12px;
    margin: 0;
    border-radius: 0;
    border-inline: 0;
    border-block-end: 0;
    border-block-start: 1px solid var(--rule);
    background: color-mix(in srgb, var(--raised) 45%, var(--ground));
    padding: 12px var(--gutter) 14px;
  }

  .withaside-side .ask .lbl,
  .withaside-side .ask .rule,
  .withaside-side .ask .note.faint { display: none; }

  .withaside-side .ask h3 {
    flex: 1 1 100%;
    margin: 0;
    font-family: var(--ui);
    font-weight: 400;
    font-size: 11.5px;
    color: var(--sub);
  }

  .withaside-side .ask .prices {
    margin: 0;
    margin-inline-end: auto;
  }

  .withaside-side .ask .prices .v.big { font-size: 20px; }

  .withaside-side .ask .btn {
    flex: 1 1 100%;
    margin-block-start: 8px;
  }

  /* The page keeps clear of its own dock. */
  .page:has(.ask) { padding-block-end: 132px; }
}

/* The label sits inside the panel, where the mockup puts it: outside, the
   `.sect` rule ran a hairline across the column and read as a divider between
   the prices and the panel rather than as this card's own name. */
.ask .lbl {
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--sub);
  margin: 0;
}

.ask .rule {
  block-size: 1px;
  background: var(--line);
  margin-block: 14px;
}

.ask .change {
  text-align: center;
  font-size: 13px;
  margin-block: 12px 0;
}

.ask .change a {
  font-weight: 600;
  color: var(--fir);
}

.prices .v.big {
  font-size: 28px;
}

/* --- Composition: the page with an aside ------------------------------ */
/* **The web gets its own composition rather than a widened phone.** Below the
   fold of a laptop the rail was a single 44rem column with the request panel
   stacked under a long price list — a phone layout at a desktop viewport.
   Above 1000px the list keeps the left and the request panel sits beside it,
   sticky, so the thing she is deciding and the thing she decides with are on
   screen together.
 *
 * Below that width the grid collapses and the source order rules: prices
 * first, request after. That order is the ruling, not the layout's
 * convenience — the list with her rate on every line is what sells the room. */
.withaside {
  display: grid;
  gap: 0 38px;
  align-items: start;
}

@media (min-width: 1000px) {
  .page:has(.withaside) {
    max-inline-size: 66rem;
  }

  .withaside {
    grid-template-columns: minmax(0, 1fr) 20.125rem;
  }

  /* **An empty aside takes its column with it.** The door reserves 322px for
     one painted pane, and that pane is conditional — a guest sees it only
     while a salon is inside its first sixty days. Outside that window the
     column stood empty and the reading measure shrank anyway, so crossing
     1000px made the collection *narrower* than it was at 999. */
  .withaside:not(:has(.withaside-side > *)) {
    grid-template-columns: minmax(0, 1fr);
  }

  .page:has(.withaside:not(:has(.withaside-side > *))) {
    max-inline-size: 44rem;
  }

  .withaside-side {
    position: sticky;
    inset-block-start: 20px;
  }
}

/* --- The price ledger ------------------------------------------------- */
/* **The signature, and it is borrowed rather than invented.** A salon's own
   printed list runs the service to its price on a dotted leader, and reading
   one is a thing every woman in this market has done a hundred times. The
   rail set the two prices as a flush pair, which reads as a table of data;
   the leader reads as a menu.
 *
 * Her price is Cormorant and the largest thing in the row, because it is the
 * number the page exists to state. The salon's price sits before it, quiet and
 * struck — the founder ruled on 2026-08-30 to match the rebuild, and the law
 * at the top of this file records what that clause was holding shut and why
 * lifting it here does not reopen it.
 *
 * The leader is a border on a flexible spacer rather than a row of periods:
 * dots typed into the markup are read aloud by a screen reader, one per dot. */
.ledger .lr,
dl.stack.ledger .lr {
  align-items: baseline;
  gap: 0;
}

.ledger .lr > .k {
  flex: 0 1 auto;
  padding-inline-end: 8px;
  /* A shade heavier than body, and in ink: the service name is what she is
     scanning for, and the generic key grey put the one word she reads below
     the leader it belongs to. The mockup sets every service name in ink. */
  font-size: var(--t-body);
  font-weight: 500;
  color: var(--ink);
}

/* Its Arabic half stays quiet under it. */
.ledger .lr > .k .why { color: var(--sub); }

/* The spacer. It carries no text, so it is invisible to assistive tech and
   audible to nobody. */
.ledger .lr::after {
  /* A zero-width space, not an empty string. An empty pseudo-element has no
     baseline, so `align-self: baseline` had nothing to sit on and the leader
     fell to the bottom of the row — under the Arabic name rather than beside
     the price it belongs to. The strut gives it a first line to align with,
     and carries no text for a screen reader to announce. */
  content: "\200b";
  order: 1;
  flex: 1 1 auto;
  align-self: baseline;
  margin-inline: 6px;
  border-block-end: 1px dotted var(--line);
  min-inline-size: 18px;
}

/* **The prices are the far column, and the leader runs to them.** `.prices`
   is a `<span>` now rather than a `<dd>`, because every bookable line is a
   button and a button may not hold a description list. Without it in this
   selector the pair kept source order, the leader took `order: 1` behind
   them, and the dotted rule ran off the right-hand edge into nothing while
   the name and the figures huddled at the left. */
.ledger .lr > .v,
.ledger .lr > .prices,
.ledger .lr > dd {
  order: 2;
  flex: 0 0 auto;
  padding-inline-start: 8px;
}

/* Two columns of fixed width, so the figures stack down the list rather than
   ragging with the length of each service's name. */
.ledger .prices {
  gap: 9px;
}

.ledger .prices .was {
  min-inline-size: 52px;
  text-align: end;
}

/* The salon's own word for the service, under its name rather than beside it. */
/* **The row is 62px, and it was 74.** The Arabic under each service name was
   inheriting `.why`'s own block spacing on top of the row's padding, so every
   line paid twelve pixels and the list lost one service off the fold for every
   six it showed. */
.ledger .lr > .k .why {
  display: block;
  margin-block: 1px 0;
  font-size: 11.5px;
  line-height: 1.25;
}

.ledger .lr {
  padding-block: 10px;
}




.ledger .prices .v {
  font-family: var(--display);
  font-weight: 600;
  font-size: 22px;
  line-height: 1.1;
  color: var(--fir);
  /* The far column, fixed, so her figures stack down the list rather than
     ragging with the length of each service's name. */
  min-inline-size: 64px;
  text-align: end;
}

/* The phone frame's ledger: a step down on both figures and a hair less air
   at the row's ends, so five services hold the fold the mockup holds. After
   the base rules, or the cascade hands the size straight back. */
@media (max-width: 559px) {
  .ledger .lr {
    padding-block: 11px;
    padding-inline: 8px;
  }

  .ledger .prices .v {
    font-size: 19px;
  }

  .ledger .prices .was {
    font-size: 11.5px;
    min-inline-size: 40px;
  }
}


/* --- The header ------------------------------------------------------- */
/* Every mockup frame wears one: the wordmark on one side, her Rate Seal on the
   other. The rail shipped without it, so every page was an island — once she
   opened a booking there was no way back but the browser's own button, and no
   page ever told her what her rate was except the two that happened to.
 *
 * The mockups' three **browser** frames carry no tab bar, and that distinction
 * is deliberate: a phone app gets tabs, a web page gets a header. This is the
 * header. */

/* **The header aligns with the page under it, and its rule spans the window.**
   It was pinned to 44rem while the salon page runs to 66, so the wordmark and
   the nav sat inboard of the band below them — two columns on one screen, and
   the eye reads the narrower one as an inset panel. The row follows whichever
   column the page is using; the hairline underneath ignores both and runs to
   the edges, which is what makes the band beneath it read as the next storey
   of the same building rather than as a card. */
.hd {
  position: relative;
  max-inline-size: 44rem;
  margin-inline: auto;
  padding: 0 var(--gutter);
  block-size: 58px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}

body:has(.withaside) .hd {
  max-inline-size: 66rem;
}

.hd::after {
  content: '';
  position: absolute;
  inset-inline: calc(50% - 50vw);
  inset-block-end: 0;
  block-size: 1px;
  background: var(--line);
}

.hd .mark { text-decoration: none; }

/* The member rail's lockup, at the mockup's proportions: the Arabic mark at
   20 over 7.5px caps tracked wide. Scoped to the rail's header because the
   desk reuses `.mark .en` for the salon's own trading name, and 7.5px is a
   size only a logotype may be. */
.hd .mark .ar {
  font-size: 20px;
  font-weight: 400;
}

.hd .mark .en {
  font-size: 7.5px;
  letter-spacing: 0.42em;
  margin-block-start: 3px;
}

.hd nav {
  display: flex;
  align-items: center;
  gap: 16px;
}

.hd nav a {
  font-size: 13.5px;
  font-weight: 500;
  color: var(--sub);
  text-decoration: none;
  padding-block: 4px;
  border-block-end: 1.5px solid transparent;
}

.hd nav a:hover {
  color: var(--ink);
}

/* The page she is on, said in ink over a fir rule — the mockup's underline is
   the action colour because the underline is where she is, not a border. */
.hd nav a[aria-current='page'] {
  color: var(--ink);
  font-weight: 600;
  border-block-end-color: var(--fir);
}

/* The header's seal is a link to what it means, and carries no ring shadow of
   its own — it sits against the page rather than on a card. */
.hd .seal { margin-block: 0; }

.greet {
  font-family: var(--display);
  /* **Upright and in ink, everywhere.** The italic fir greeting predates the
     consolidated mockup, which sets every page title roman 600 in ink — the
     italic read bridal as a title, and fir is the action colour, not the
     title's. Italic survives only as a deliberate accent inside a line. */
  font-style: normal;
  font-weight: 600;
  /* 26 on a page, 28 on the door — the frames' own two sizes. */
  font-size: 26px;
  line-height: 1.12;
  letter-spacing: -0.015em;
  color: var(--ink);
  margin: 0;
}

.sect {
  display: flex;
  align-items: center;
  gap: 9px;
  /* 9.5/600/.22em — the frame's one tracked label, measured. It was 9/700/.26
     and every section header sat a shade too black and too wide. */
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--faint);
  margin-block: 15px 8px;
}

.sect::after {
  content: '';
  flex: 1;
  block-size: 1px;
  background: linear-gradient(var(--flow-end), var(--rule), transparent);
}

/* ====================================================== 4 · layout primitives */
/* Three, and no more. A system with a utility for every gap is a system with
   no layout at all — these exist because every surface needs vertical rhythm,
   a wrapping row, and a two-up, and re-inventing those per screen is how the
   dining rail's twenty-eight templates stopped adding up to a portal. */

.flow > * + * {
  margin-block-start: var(--flow-gap, 12px);
}

.cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cluster-gap, 8px);
}

/* A button is full-width by default, because most of the time it is the one
   thing on the screen to do. **In a cluster it is one of several**, so it
   sizes to its own words — otherwise the first exit takes the whole row and
   the other two wrap beneath it at three different widths, which is what
   O-04's three exits looked like. */
.cluster > form {
  flex: 0 0 auto;
  margin: 0;
}

.cluster .btn {
  inline-size: auto;
  padding-inline: 18px;
}

.pair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  gap: 11px;
}

/* ============================================================ 5 · atoms */

.card {
  /* **An atom looks the same whichever element carries it.** A card is a
     `<div>` on most screens and an `<a>` wherever it is tappable, and an
     anchor is inline by default — so the border fragmented around its own line
     boxes and drew an empty rounded rectangle beside the content instead of
     around it. The same law already had to be written for `.chip`. */
  display: block;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--lift);
  padding: 15px 18px;
}

/* Label at the reading start, value at the reading end. The whole reason this
   is one atom: two surfaces rendering "label … value" differently is how a
   salon's number and MIORA's number start to look like different numbers. */
.lr {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding-block: 7px;
}

.lr + .lr {
  border-block-start: 1px solid var(--line);
}

.lr > dt,
.lr > .k {
  color: var(--sub);
  font-size: 13px;
}

.lr > dd,
.lr > .v {
  margin: 0;
  font-variant-numeric: tabular-nums;
  font-size: 14px;
  color: var(--ink);
}

/* A column of prices beside a column of buttons. `.lr` hands the value its
   natural width and the button takes the rest, so a shorter figure made a
   longer button: the composer's four rows ended at three different edges and
   the prices sat in three different columns. Six characters is "650.00", the
   widest a posted line gets before it needs a comma, and `tabular-nums` above
   makes the reservation exact. */
.menu .lr > .v {
  min-width: 6ch;
  text-align: end;
}

/* An atom looks the same whichever element carries it. A chip rendered as a
   link picked up the browser's underline, which is the anti-coupon law losing
   to a user-agent default — and it would have shipped, because it only shows
   on the surfaces that make chips clickable. */
.chip,
.btn {
  text-decoration: none;
}

.chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  /* **A button element inherits no font.** The chip atom never declared one,
     so every <button class="chip"> — the tip row, first among them — set its
     figures in the browser's Arial. Found by the verification harness reading
     computed styles; invisible to every eye that passed over it. */
  font-family: var(--ui);
  block-size: 26px;
  padding-inline: 11px;
  border-radius: var(--radius-chip);
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--fir);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.chip.on {
  background: var(--fir);
  border-color: var(--fir);
  color: var(--on-fir);
}

.chip.waiting {
  color: var(--amber);
  border-color: var(--rule);
}

.chip.open {
  color: var(--good);
}

.chip.gift {
  background: var(--rose-wash);
  border-color: var(--rose-line);
  color: var(--rose);
}

/* The one big number on a screen. One, deliberately — a screen with three hero
   numbers has told her nothing about which matters. */
.hero-num {
  font-family: var(--hero);
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  font-size: clamp(38px, 9vw, 56px);
  line-height: 0.95;
  color: var(--ink);
  margin: 0;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 100%;
  /* 46 × r12, weight 600 at 14 — the consolidated mockup's one button,
     measured off the frames rather than eyeballed. */
  block-size: 46px;
  border: 1px solid transparent;
  border-radius: var(--radius-btn);
  background: var(--fir);
  color: var(--on-fir);
  font-family: var(--ui);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}

.btn:hover {
  background: var(--fir-deep);
}

.btn:focus-visible {
  outline: 2px solid var(--fir);
  outline-offset: 2px;
}

.btn.quiet {
  background: transparent;
  border-color: var(--fir);
  color: var(--fir);
}

/* **The card step, 1-06.** One line — *Card number* — inside the same card as
   the sentence that says what saving it means. The field is Stripe's iframe and
   it arrives unstyled, so the frame is ours: the same border, radius and focus
   the rest of the product's inputs wear, or the most trust-bearing moment on
   the rail is the one place the brand stops. */
.cardstep {
  margin-block-end: 16px;
}

.cardstep .card {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* The bill's field joined this rule the day it stopped being a Payment Element
   (G-362): the Payment Element drew its own frame, the Card Element does not,
   and an unframed iframe on the signature screen is the brand stopping at the
   moment it matters most. */
#card-element,
#element {
  border: 1px solid var(--line);
  border-radius: var(--radius-btn);
  padding: 13px 14px;
  background: var(--card);
}

#card-element.StripeElement--focus,
#element.StripeElement--focus {
  border-color: var(--fir);
}

/* **The card she already gave, named on the bill.** G-361: this screen mounted
   an element over a card already on file, so a visit asked for the same digits
   twice. The line is a statement, not a field — it sits at body weight above
   the button that says what it will take, and it carries **no mark at all**:
   the anti-coupon law's *no badge atom* holds here too, a Visa lozenge on the
   signature screen would be the first logo the rail has ever worn, and a drawn
   card glyph — tried, and looked at — read as a minus sign in a box. The rail
   has no icons anywhere else and the sentence does not need one. */
.saidcard {
  margin: 0 0 4px;
  color: var(--ink);
}

/* **or pay by card** — the rule between a wallet and the fields it replaces.
   Hidden until a wallet actually mounts, because a divider with nothing above
   it says a choice was withheld. Not a heading: she is not choosing between
   two products, she is being told the shorter way exists. */
.orcard {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 16px 0;
  font-size: 12px;
  letter-spacing: 0.02em;
  color: var(--sub);
}

.orcard::before,
.orcard::after {
  content: "";
  flex: 1;
  block-size: 1px;
  background: var(--line);
}

/* **Who is taking her money**, under the footer's three links. Quieter than
   they are and on its own line: it is a fact she is entitled to and not a
   thing to read twice, and a processor verifying this business needs to find
   it on the front door rather than three pages in. */
.pagefoot .whois {
  flex-basis: 100%;
  margin: 10px 0 0;
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--sub);
  text-align: center;
  /* On a phone this wraps, and left to itself it dropped `· Abu Dhabi` alone
     onto a second line — a two-word orphan under a full one. Balanced, the two
     lines are even and it reads as a sentence that happens to wrap. */
  text-wrap: balance;
}

/* The full words, one tap away and never further. Part A permits a summary to
   lead; the hash is always of the whole, which is why the whole is in the
   document rather than behind a request. */
.mandate summary {
  font-family: var(--ui);
  font-size: 13px;
  color: var(--sub);
  cursor: pointer;
}

.mandate-said {
  margin-block-start: 10px;
  max-block-size: 300px;
  overflow-y: auto;
  font-size: 13px;
  line-height: 1.55;
  color: var(--sub);
  white-space: pre-line;
}

/* The two lines under a page's name that say what it is. One size down from
   the heading and one up from a note — a lede is neither. */
.lede {
  font-family: var(--ui);
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--sub);
  margin: 0 0 4px;
  max-inline-size: 34ch;
}

/* The invitation, where the prices are. Rose is this system's credit colour
   — the chip, the pane and the stack's credit line all wear it — so a gift
   reads as a gift here without a word saying so. */
.note.gift {
  color: var(--rose);
  margin-block-start: 10px;
}

/* "How is this real?" — the mechanism, one tap in.
   A `<details>` rather than a modal: no script to load, no state to manage, and
   with JavaScript off it degrades to an open block rather than to nothing. */
.how {
  margin-block-start: 14px;
  border-block-start: 1px solid var(--rule);
  padding-block-start: 12px;
}

.how > summary {
  font-family: var(--ui);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--fir);
  cursor: pointer;
  /* The default triangle is a browser's idea of an affordance, not ours. */
  list-style: none;
}

.how > summary::-webkit-details-marker {
  display: none;
}

.how > summary::after {
  content: ' ›';
  color: var(--faint);
}

.how[open] > summary::after {
  content: ' ⌄';
}

.how > summary:focus-visible {
  outline: 2px solid var(--fir);
  outline-offset: 3px;
}

.how p {
  margin-block: 8px 0;
}

/* **A control that is off must look off.** There was no `:disabled` rule
   anywhere in this system, and five templates ship disabled controls — so
   every one of them rendered solid fir and fully live.

   Two of those are the release queue's dual approval, the only pair of
   controls in the product that moves money: disabled until a tranche is ready,
   and indistinguishable from ready. Somebody clicks Release, nothing happens,
   and they conclude the console is broken rather than that the tranche is not
   yet approved — which is the same failure as the button that was `type=button`
   with no form, arrived at from the other direction.

   Placed on the atom rather than on the pages, because the next disabled
   control should not have to remember. */
.btn:disabled,
.btn[aria-disabled='true'] {
  background: var(--line);
  border-color: var(--line);
  color: var(--faint);
  cursor: not-allowed;
}

/* Hover must not undo it — a control that lights up under the pointer is a
   control that says it can be pressed. */
.btn:disabled:hover,
.btn[aria-disabled='true']:hover {
  background: var(--line);
}

.btn.quiet:disabled,
.btn.plain:disabled {
  background: transparent;
  border-color: var(--line);
  color: var(--faint);
}

.btn.plain {
  background: transparent;
  border-color: transparent;
  color: var(--sub);
  font-weight: 600;
}

.sheet {
  background: var(--card);
  border: 1px solid var(--line);
  border-start-start-radius: var(--radius-sheet);
  border-start-end-radius: var(--radius-sheet);
  padding: 18px var(--gutter) 24px;
  box-shadow: 0 -18px 40px -24px rgb(51 64 47 / 0.3);
}

/* Waiting, degraded, wrong. `.banner.alert` is the only red on any surface and
   never appears beside money. */
.banner {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 10px 14px;
  border-radius: 12px;
  background: var(--peach-wash);
  border: 1px solid var(--peach-line);
  color: var(--ink);
  font-size: 12.5px;
}

.banner.alert {
  background: color-mix(in srgb, var(--alert) 8%, var(--ground));
  border-color: color-mix(in srgb, var(--alert) 26%, var(--ground));
}

/* **Every panel has one.** A blank space is a screen that failed silently; an
   empty state says why it is empty, in one warm line. A job that never ran
   reads `never run`, never a zero. */
.empty {
  padding: 22px 18px;
  text-align: center;
  color: var(--sub);
  font-size: 13px;
  border: 1px dashed var(--rule);
  border-radius: var(--radius-card);
}

.empty .why {
  display: block;
  margin-block-start: 4px;
  font-size: 11.5px;
  color: var(--faint);
}

/* A figure the surface could not fetch reads as a dash and says so. Content
   caches; numbers do not (UI spec law 8). */
.dash {
  color: var(--faint);
}

/* Sandbox. Loud on purpose — the one place this system shouts. */
.testmode {
  position: sticky;
  inset-block-start: 0;
  z-index: 90;
  padding: 4px var(--gutter);
  /* **It was the loudest thing on the page and it is the least important.**
     Saturated brass at 30px pulled the eye before the salon's name did. The
     rebuild sets it as a hairline register in the identity fill: still
     unmissable if you are looking for it, invisible if you are not. */
  background: var(--raised);
  color: var(--brass);
  border-block-end: 1px solid var(--line);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  text-align: center;
  line-height: 14px;
}

/* ============================================== 6 · the five inventions */

/* --- Rate Seal -------------------------------------------------------- */
/* Her standing rate, worn rather than announced. A medallion, not a badge:
   no starburst, no percentage that isn't hers, nothing that varies. */

/* **A rosette: her rate stamped inside a ring of petals.** Three shapes were
   tried in the open — a 46px medallion that stacked the figure over a 6.5px
   tier line nobody could read, a pill that said YOUR RATE beside it, and this,
   which the garden study settles on. The words went to the `aria-label`, which
   is where a screen reader was getting them anyway and where they are a
   sentence rather than tracked capitals. What is left is the figure, ringed.
 *
 * It is a seal and not a badge because it never varies, never animates, and
 * never states a number that is not hers. */
.seal {
  position: relative;
  inline-size: 56px;
  block-size: 56px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  text-decoration: none;
}

.seal .rr {
  position: absolute;
  inset: 0;
}

.seal b {
  position: relative;
  font-variant-numeric: tabular-nums lining-nums;
  font-size: 14px;
  font-weight: 600;
  color: var(--fir);
}

/* Prima's ring is drawn in brass rather than the base tone — the one visible
   difference a subscriber is paying for, and it is a material, not a badge. */
.seal:not(.free) .rr {
  --ring: var(--brass);
  --petal: var(--gold-leaf);
}

/* On a phone the rosette shrinks rather than losing anything: the ring scales
   with the box, so there is nothing to drop. */
@media (max-width: 559px) {
  .seal {
    inline-size: 44px;
    block-size: 44px;
  }

  .seal b {
    font-size: 12px;
  }
}



/* --- Ticket ----------------------------------------------------------- */
/* A visit as one large time, one state seal, one verb. The dominant object on
   Home whenever a visit exists, and silent when none does. */

.ticket {
  display: block;
  position: relative;
}

.ticket .state {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fir);
}

.ticket .when {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-block-start: 8px;
}

.ticket .when b {
  font-family: var(--display);
  font-weight: 600;
  font-size: 46px;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.ticket .when .meridiem {
  font-size: 12px;
  color: var(--sub);
}

/* The hour at 38 on a phone — the frame steps the 46 down where the card
   is the screen's width. The booking page's own ticket keeps 42: the page
   about the visit gets the bigger hour (frames 2-01/2-03 vs 2-04). */
@media (max-width: 559px) {
  .ticket .when b { font-size: 38px; }

  .tkpage .ticket .when b { font-size: 42px; }
}

/* A waiting state wears the asking gold, not the action fir — the frame
   colours the chip by what the visit is doing (2-01), and amber-as-waiting
   is this palette's own law. The bud is the awaiting leaf. */
.ticket:has(.leaf.bud) .state { color: var(--brass); }

/* The name card asks in the label register — tracked caps, the asking gold. */
.asklbl {
  display: block;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--brass);
  margin-block-end: 8px;
}

.ticket .who {
  font-size: 12.5px;
  color: var(--sub);
  margin-block-start: 5px;
}

.ticket .who b {
  color: var(--ink);
  font-weight: 700;
}

.ticket .btn {
  margin-block-start: 12px;
}

/* Leaf grammar: bud awaiting, leaf in the chair, petal paid. Three states of
   one growing thing rather than three unrelated icons — the visit is the same
   object throughout, and the vocabulary says so. */
.leaf {
  inline-size: 16px;
  block-size: 16px;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 9px;
  background: var(--fir);
  color: var(--on-fir);
  flex: 0 0 auto;
}

.leaf.bud {
  background: var(--raised);
  color: var(--fir);
}

.leaf.petal {
  background: var(--rose-wash);
  color: var(--rose);
}

/* --- Week-Dots -------------------------------------------------------- */
/* Seven days at a glance, with almost no words. Filled = open, hollow =
   closed, underline = today.
 *
 * **Availability points forward and never merchandises scarcity.** These say
 * when she can come; they never say how little remains. A count lives only
 * inside the slot picker, as the slots themselves. */

.week {
  display: flex;
  align-items: center;
  gap: 6px;
}

.week i {
  inline-size: 7px;
  block-size: 7px;
  border-radius: 999px;
  border: 1.4px solid var(--sub);
  opacity: 0.45;
  flex: 0 0 auto;
}

.week i.open {
  background: var(--good);
  border-color: var(--good);
  opacity: 1;
}

/* Today. `box-shadow` rather than `border-block-end` so the mark sits below
   the dot without changing its box — seven dots that grow on Tuesdays is a row
   that jitters through the week. */
.week i.today {
  box-shadow: 0 3px 0 -1px var(--brass);
}

.week span {
  font-size: 8px;
  letter-spacing: 0.06em;
  color: var(--sub);
  margin-inline-start: 4px;
}

/* --- Crest Tiles ------------------------------------------------------ */
/* A salon's identity, invented rather than photographed. Photography of
   interiors and clients is culturally constrained in this segment, so every
   image on screen is created — and a salon still has to be recognisable at a
   glance in a circle of five. */

.crest {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--lift);
  padding: 13px 12px 12px;
  text-align: center;
  position: relative;
  overflow: visible;
}

.crest .glyph {
  inline-size: 34px;
  block-size: 34px;
  margin-inline: auto;
  color: var(--fir);
}

.crest h4 {
  font-family: var(--display);
  font-weight: 600;
  font-size: 17px;
  margin: 6px 0 0;
  color: var(--ink);
}

/* **§2's own law, broken by §2's own atom.** The rule above says Arabic sets
   in Amiri and reads *a size larger* — the script's x-height is smaller at the
   same point size — and the crest then set a salon's Arabic name at 9.5px
   against a 17px Latin: 56%, in a low-x-height Naskh, exactly where the
   comment warns small Arabic "looks timid". A large share of members in this
   segment read the Arabic name first, and at 9.5px it was decoration on the
   atom whose stated purpose is that a salon is recognisable at a glance. */
.crest .ar {
  font-family: var(--arabic);
  font-size: 13px;
  color: var(--ink);
  margin-block-start: 1px;
}

.crest .area {
  font-size: 10px;
  color: var(--faint);
}

/* "Something new here", and nothing more. Not a count of unread things and not
   a red dot — a quiet mark that resolves when she looks. */
.crest .new {
  position: absolute;
  inset-block-start: 10px;
  inset-inline-end: 10px;
  inline-size: 7px;
  block-size: 7px;
  border-radius: 999px;
  background: var(--rose-soft);
}

/* The salon's own wash, for its pane and its page header. A salon dresses its
   own surfaces; the actions on them stay fir. */
.crest.kit,
.kit {
  background: linear-gradient(165deg, var(--kit-wash, var(--card)), var(--card));
  border-color: var(--kit-line, var(--line));
}

/* --- Ribbon ----------------------------------------------------------- */
/* A horizontal pager. Pane one is always her usual; panes two to five carry
   live Pulse events and expire with them.
 *
 * **Never autoplays.** The peek is the only invitation to swipe — 14px of the
 * next pane's ground, widening to ~96px when that pane is a featured
 * invitation carrying its own colour. With no news there is one pane and no
 * dots at all: an empty week collapses silently, and filler is banned.
 *
 * Direction comes from the document. `scroll-snap` and logical padding do the
 * mirroring for free; nothing here names a side. */

.ribbon {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--gutter);
  padding-inline: var(--gutter);
  margin-inline: calc(var(--gutter) * -1);
  scrollbar-width: none;
}

.ribbon::-webkit-scrollbar {
  display: none;
}

.ribbon > .pane {
  flex: 0 0 min(300px, 82%);
  scroll-snap-align: center;
  border-radius: var(--radius-sheet);
  background: var(--card);
  border: 1px solid var(--line);
  padding: 14px 16px;
  min-block-size: 200px;
  display: flex;
  flex-direction: column;
}

/* The 14px sliver. It is a peek and not a card: it carries the next pane's
   ground so she knows there is something, and no content she could try to
   read. */
.ribbon > .pane.peek {
  flex-basis: 14px;
  min-block-size: 0;
  padding: 0;
  background: var(--peach-wash);
  border-color: var(--peach-line);
}

/* A featured invitation widens its peek to carry its own type-signature
   ground: package deep fir, access peach, arrival blush, gift rose. */
.ribbon > .pane.peek.featured {
  flex-basis: 96px;
}

.pane .kind {
  font-size: 7.5px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--peach);
}

.pane h3 {
  font-family: var(--display);
  font-weight: 600;
  font-size: 22px;
  margin: 4px 0 0;
  color: var(--ink);
}

.pane .btn {
  margin-block-start: auto;
}

.pane.gift {
  background: var(--rose-wash);
  border-color: var(--rose-line);
}

.pane.gift .kind {
  color: var(--rose);
}

.pane.access {
  background: var(--peach-wash);
  border-color: var(--peach-line);
}

/* Page dots. Absent when there is one pane, because a single dot is a control
   that tells her nothing. */
.dots {
  display: flex;
  gap: 5px;
  justify-content: center;
  margin-block-start: 9px;
}

.dots i {
  inline-size: 5.5px;
  block-size: 5.5px;
  border-radius: 999px;
  background: var(--rule);
  transition: inline-size 160ms ease;
}

.dots i.on {
  inline-size: 15px;
  background: var(--fir);
}

/* ================================================ 7 · the bill stack */
/* posted -> her rate -> credit -> she pays. One component, four surfaces.
 *
 * A price never renders without her rate beside it, and the posted amount
 * stays visible to the end — the stack is the moment the brand is or is not
 * what it claimed. */

.stack {
  margin: 0;
}

.stack .rate .v {
  color: var(--fir);
}

.stack .credit .v {
  color: var(--rose);
}

.stack .pays {
  border-block-start: 1.5px solid var(--ink);
  margin-block-start: 4px;
  padding-block-start: 10px;
}

/* The stack's own colours, per the frame: the posted line is quiet, the
   rate line is hers — fir — and the pays row asks in ink at 13.5/600. */
.stack .lr > .k { font-size: 12.5px; }

.stack .lr.posted .v { color: var(--sub); }

.stack .lr.rate .v { color: var(--fir); }

.stack .pays .k {
  color: var(--ink);
  font-weight: 600;
  font-size: 13.5px;
}

/* The figure she agrees to answers in her price's own face — the same
   Cormorant the ledger's column ends in, in fir, per the frame. */
.stack .pays .v {
  font-family: var(--display);
  font-weight: 600;
  font-size: 26px;
  line-height: 1;
  color: var(--fir);
}

/* **Paid and received never share a container** (UI spec law 5). This is a
   separate object, always, on every surface — MIORA's part never touches her
   bill, and a shared border would say it did. */
.received {
  margin-block-start: 14px;
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  padding: 13px 16px;
}

.received h5 {
  margin: 0 0 6px;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--sub);
}

.received .foot {
  margin-block-start: 6px;
  font-size: 11px;
  color: var(--faint);
}

/* ============================================================ 8 · a11y */

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

.visually-hidden {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ============================================================ 9 · asking her

   Forms. **The first surface with one arrived in Phase B**, and until then
   `core.css` styled none of these — so a sign-in page rendered a system-default
   white input on an ivory ground, in the browser's font, with the label jammed
   against it. Found by looking at the page.

   These are element selectors rather than classes on purpose. A form is markup
   somebody writes quickly at the end of a template, and an atom that has to be
   remembered is an atom that will not be. */

/* A full page on a phone, and a column on anything wider.
 *
 * **Not `.sheet`** — that is a bottom sheet: rounded top corners, a shadow cast
 * upward, made to sit against the bottom edge of a screen with content behind
 * it. Using it as a page container drew a box that ended two thirds of the way
 * down an empty page, which is what looking at the sign-in page found. */
.page {
  max-inline-size: 44rem;
  margin-inline: auto;
  padding: 20px var(--gutter) 40px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.page > h1 {
  margin: 0;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-block: 12px;
}

.field > label,
label.k {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--sub);
}

input[type='text'],
input[type='tel'],
input[type='number'],
input[type='password'],
input[type='search'],
select,
textarea {
  inline-size: 100%;
  box-sizing: border-box;
  min-block-size: 46px;
  padding: 11px 13px;
  font: inherit;
  font-size: 15px;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-btn);
  /* 16px or larger, or iOS zooms the page on focus and she loses the form. */
}

textarea {
  min-block-size: 96px;
  resize: vertical;
}

/* Figures she types are figures: a phone number, a PIN, a six-digit code.
   `input.fig` is in **the one rule** that sets the figure face, up in §2 — a
   second declaration is exactly the shape of the bug that law exists to catch,
   and it carries the isolation with it. A code typed into an Arabic page must
   not reorder as she types it.

   Only the tracking is here, because that is about a keypad rather than a
   typeface. */
input.fig {
  letter-spacing: 0.08em;
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--fir);
  outline-offset: 1px;
  border-color: var(--fir);
}

input::placeholder,
textarea::placeholder {
  color: var(--faint);
}

/* Links. Unstyled until Phase B, because until then no template had one — and
   the first one rendered as browser-default blue and underlined on an ivory
   ground, three inches under a bill set in Cormorant. Found by looking.

   Fir rather than blue, and the underline stays: an underline is how a link is
   recognisable to somebody who cannot rely on the colour. */
a {
  color: var(--fir);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

a:hover {
  color: var(--fir-deep);
}

/* **A card that is a link is not underlined.** The underline is how an inline
   link is recognisable without relying on colour; drawn under every line of a
   whole card it is just noise, and it was — the collection rendered with a rule
   under the salon's name, its area and its availability sentence alike. */
a.card,
a.chip {
  color: inherit;
  text-decoration: none;
}

/* `a.btn` is deliberately absent. `.chip, .btn` already unsets the underline,
   and a type-and-class selector here would outrank `.btn` — which is exactly
   how folding buttons into the rule above once made every link-shaped primary
   button dark-on-dark. "Keep it" on the cancel sheet was legible only by
   knowing it was there. */

a.card:hover {
  border-color: var(--rule);
}

a:focus-visible {
  outline: 2px solid var(--fir);
  outline-offset: 2px;
  border-radius: 3px;
}

/* A quiet aside. **Not `.dash`** — that is the figure face, for an em dash and
   a number a surface could not fetch. Using it for prose set every sentence on
   the sign-in page in DM Mono, which is what looking at the page found. */
.note {
  color: var(--sub);
  font-size: 12.5px;
  line-height: 1.5;
  margin-block: 8px;
}

.note.faint {
  color: var(--faint);
  font-size: 11.5px;
}

/* **And why it did not.** The reason a form would not save, beside the form
   rather than on a screen of its own — which is where it used to go, taking
   what she had typed with it. Warm rather than red: this is almost always a
   typo on line three, and a page that alarms about a typo is a page that
   alarms about everything. */
.card.problem {
  border-color: var(--peach-line);
  background: var(--peach-wash);
  margin-block-end: 16px;
}

/* **It worked.** One line above the work, for the one page after the thing
   happened. Quiet on purpose: this is a receipt, not an alarm — the only place
   this system raises its voice is the sandbox ribbon, and a green banner for
   every saved form would teach people to stop reading the ones that matter. */
.note.said {
  color: var(--ink);
  background: var(--raised);
  border-radius: 10px;
  padding: 9px 12px;
  margin-block-end: 16px;
}

/* The same line on the member's rail, which cannot put it inside `<main>`.
 * Every page there opens its own, so `base.html` renders the flash just above
 * them and it has to take the column by itself — the column and no more, since
 * `.page`'s own block padding would open a gap above a page that said nothing
 * a moment ago. */
.note.said.adrift {
  max-inline-size: 44rem;
  margin-inline: auto;
  margin-block: 16px 0;
  inline-size: calc(100% - 2 * var(--gutter));
}

/* A struck line stays readable. O-10: a corrected bill shows what was struck on
   both screens, because a line that vanished is a line she cannot ask about. */
.struck .k,
.struck .v {
  text-decoration: line-through;
  color: var(--faint);
}

/* A "why" is a sub-line, wherever it sits.
   Only `.empty .why` was ever styled, so every other use of the class rendered
   inline and ran into the label above it: "Pause It stops taking new bookings",
   "Colour course · 6 sessions 260 a session on its own", "Hamdan sedr_admin ·
   +971501110001 · since 26 Aug". Found by reading four screens in a row and
   seeing the same sentence collapse each time. */
.why {
  display: block;
  margin-block-start: 4px;
  font-size: 11.5px;
  color: var(--faint);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}

/* G-10 · a salon's face.
   A collection of at most two salons a month rendered as a name and an area is
   typographically a directory, and the scarcity is the product. A photograph
   when the salon has given one; its own monogram, in its own ink, when it has
   not — never a grey square apologising for a missing image. */
.face {
  display: inline-grid;
  place-items: center;
  flex: none;
  overflow: hidden;
  color: #fff;
  /* **`--font-en` was never defined.** An undefined `var()` is invalid at
     computed-value time, so every monogram silently inherited Karla from
     `body` and the intended face was unknowable from the code — the system's
     one unresolvable variable, in a file whose own comments hunt exactly this
     drift. A monogram is a display letterform: `--display`. */
  font-family: var(--display);
  font-weight: 500;
  letter-spacing: 0.02em;
  line-height: 1;
  /* The bidi isolate matters: an Arabic monogram beside a Latin name must not
     drag the name's punctuation to the other end of the line. */
  unicode-bidi: isolate;
}

.face img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.face-card {
  inline-size: 30px;
  block-size: 30px;
  border-radius: 9px;
  font-size: 11.5px;
  margin-inline-end: 2px;
  vertical-align: -8px;
}

/* **The page tile in the identity fill, not in slate.** The monogram takes a
   colour derived from the salon's own name, which is right in a circle of five
   where the point is telling rooms apart at a glance — and wrong at 52px
   beside the name it spells, where it was the only object on the page in a
   colour the token set does not contain. Here it wears sand and brass, and the
   salon's own derived ink stays where it does its work: the collection. */
.face-page {
  inline-size: 52px;
  block-size: 52px;
  border-radius: 15px;
  font-size: 19px;
  margin-block-end: 0;
  background: var(--raised);
  color: var(--brass);
  border: 1px solid var(--line);
}

/* The desk offline banner. Defined here too because the venue templates load
   core.css, and a rule that exists in one stylesheet is a banner that renders
   unstyled on half the screens that raise it. */
.offline {
  position: sticky;
  inset-block-start: 0;
  z-index: 40;
  margin: 0;
  padding: 10px 16px;
  background: #5A3C2F;
  color: #fff;
  font-size: 12.5px;
  line-height: 1.45;
  text-align: center;
}

/* The same rule on the member rail, for the same reason: her salon's own name
   and its own words for a service must render in an Arabic face whatever
   component they happen to sit in. */
[lang='ar'],
[lang='ar'] * {
  font-family: var(--arabic) !important;
  letter-spacing: 0 !important;
}

/* **Except a name.** `--arabic-display` exists for "the one place Arabic is a
   name rather than text" — and the blanket above was quietly defeating it
   with its !important, so بيت نورة under a lockup rendered in the reading
   face on every surface and nobody could tell (the fallback chain ends in
   the same glyphs at a different cut). Found by reading computed styles
   against the mockup, which is what the mockup round is for. */
.lock .arn,
.rr-name .arn {
  font-family: var(--arabic-display) !important;
}

/* The door, on every page. Quiet on purpose — it is furniture until the
   moment it is the only thing on the screen that helps, and a loud support
   link on a booking page is a product apologising in advance. */
.pagefoot {
  display: flex;
  flex-wrap: wrap;
  gap: 0 0.6rem;
  justify-content: center;
  align-items: center;
  margin-block: 3rem 1.5rem;
  padding-inline: 1rem;
}

/* One quiet line — the mockup sets the three doors with middots between. */
.pagefoot a + a::before {
  content: '·';
  margin-inline-end: 0.6rem;
  color: var(--faint);
}
.pagefoot a {
  color: var(--sub);
  font-family: var(--ui);
  font-size: 12px;
  letter-spacing: 0.04em;
  text-decoration: none;
}
.pagefoot a:hover,
.pagefoot a:focus-visible { color: var(--fir); text-decoration: underline; }

/* The day letters above Week-Dots. Seven dots said a salon opens on some days
   without saying which — the availability glance decorating rather than
   informing. Tracked to the dots' own rhythm so the two rows read as one. */
.week b.wd {
  display: inline-block;
  width: 7px;
  font-family: var(--ui);
  font-size: 8px;
  font-weight: 600;
  letter-spacing: 0;
  text-align: center;
  color: var(--sub);
}
.week b.wd.today { color: var(--brass); }
