/* 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, no strikethrough, 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.
 */

/* ============================================================ 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. */
  --ground: #FFFDF6;
  --card: #FFFFFF;
  --raised: #F6EFDC;
  --line: #EFE8D6;
  --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: #33402F;
  --fir: #33553F;
  --fir-deep: #2A4433;
  --on-fir: #FFFDF6;
  /* 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. */
  --sub: #696F61;
  --faint: #746C56;

  /* 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: #B4485C;
  --rose-soft: #E491A0;
  --rose-wash: #FDEDF0;
  --rose-line: #F3CBD5;
  --peach: #D98E67;
  --peach-wash: #FDF1E8;
  --peach-line: #F4DFCB;
  --brass: #8F723E;
  --brass-soft: #C98E43;

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

  /* Type. Six faces, each with one job — see tools/fetch_fonts.py. */
  --display: 'Cormorant Garamond', 'Amiri', Georgia, serif;
  --ui: 'Karla', 'Amiri', system-ui, -apple-system, sans-serif;
  --figure: 'DM Mono', ui-monospace, SFMono-Regular, monospace;
  --hero: 'Fraunces', 'Cormorant Garamond', Georgia, serif;
  --wordmark: 'Aref Ruqaa', 'Amiri', serif;
  --arabic: 'Amiri', 'Aref Ruqaa', serif;

  --radius-card: 18px;
  --radius-sheet: 22px;
  --radius-btn: 13px;
  --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;
    --rose-wash: #2A1A1E;
    --rose-line: #47262D;
    --peach-wash: #2A2018;
    --peach-line: #45341F;
  }
}

: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;
  --rose-wash: #2A1A1E;
  --rose-line: #47262D;
  --peach-wash: #2A2018;
  --peach-line: #45341F;
}

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

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

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--ui);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* 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: 16px;
  line-height: 1.75;
}

/* 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);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 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);
}

.mark .en {
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.42em;
  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;
}

/* --- 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: 14px;
  border: 1px solid var(--rule);
  background: var(--card);
  padding-block: 8px;
  text-align: center;
  text-decoration: none;
  display: block;
}

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

/* 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: 8px;
  color: var(--faint);
  letter-spacing: 0.1em;
}

.dc .dn {
  font-family: var(--display);
  font-weight: 600;
  font-size: 16px;
  color: var(--ink);
  margin-block-start: 1px;
}

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

.slot {
  block-size: 40px;
  border-radius: 12px;
  border: 1.4px solid var(--rule);
  background: var(--card);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  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 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, under "your 25% on all of
   it". The posted figure is quieter and **never struck through** — the
   anti-coupon law is explicit that there is no strikethrough and no percentage
   that is not hers. It is not a discount being advertised; it is what the
   salon charges, beside what she pays. */

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

.prices .was {
  font-size: 13px;
  color: var(--sub);
}

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

.hd {
  max-inline-size: 44rem;
  margin-inline: auto;
  padding: 14px var(--gutter) 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}

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

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

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

.hd nav a:hover,
.hd nav a[aria-current='page'] {
  color: var(--ink);
  border-block-end-color: var(--rule);
}

/* 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);
  font-style: italic;
  font-weight: 600;
  font-size: 27px;
  line-height: 1.12;
  color: var(--fir);
  margin: 0;
}

.sect {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.26em;
  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);
}

/* 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;
  gap: 5px;
  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%;
  block-size: 44px;
  border: 1px solid transparent;
  border-radius: var(--radius-btn);
  background: var(--fir);
  color: var(--on-fir);
  font-family: var(--ui);
  font-size: 13.5px;
  font-weight: 700;
  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 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: 5px var(--gutter);
  background: var(--brass-soft);
  color: #241B08;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  text-align: center;
}

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

.seal {
  inline-size: 46px;
  block-size: 46px;
  border-radius: 999px;
  background: var(--card);
  border: 1px solid var(--line);
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
  flex: 0 0 auto;
  /* The brass ring sits at `inset: -5px`, so the visual box is 56px while the
     layout box is 46. Without this the ring of one seal crowds the next, and
     every caller would have to know the ring exists to space around it. */
  margin: 5px;
}

.seal::after {
  content: '';
  position: absolute;
  inset: -5px;
  border: 1.6px dotted var(--brass);
  border-radius: 999px;
  opacity: 0.55;
}

.seal b {
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  font-weight: 500;
  color: var(--fir);
}

.seal i {
  font-style: normal;
  font-size: 6.5px;
  font-weight: 700;
  letter-spacing: 0.2em;
  color: var(--sub);
}

/* The free tier wears the same seal without the tier line — she is not being
   sold to, she is being told her rate. */
.seal.free::after {
  border-style: solid;
  opacity: 0.3;
}

/* --- 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: 8.5px;
  font-weight: 700;
  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: 500;
  font-size: 52px;
  line-height: 0.9;
  color: var(--ink);
}

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

.ticket .who {
  font-size: 12px;
  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);
}

.crest .ar {
  font-family: var(--arabic);
  font-size: 9.5px;
  color: var(--sub);
  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;
}

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

.stack .pays .v {
  font-size: 21px;
  font-weight: 500;
}

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

/* 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-family: var(--font-en);
  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;
}

.face-page {
  inline-size: 52px;
  block-size: 52px;
  border-radius: 15px;
  font-size: 18px;
  margin-block-end: 10px;
}

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