/*
 * Responsive + app-shell layer.
 *
 * Kept separate from tokens.css on purpose: tokens.css is ALSO inlined into <head>
 * by ApplicationHelper#inline_design_tokens, so anything added there ships twice on
 * every request. This file is linked once via stylesheet_link_tag "app".
 *
 * BREAKPOINT: 780px. Set by docs/Grahik_Product_Documentation.md L74-76 and by the
 * prototype's `window.innerWidth <= 780`. CSS can't use custom properties in media
 * queries, so 780px is repeated literally below -- change all occurrences together.
 *
 *   >780px  desktop: left sidebar, multi-column grids, centered modals
 *  <=780px  mobile:  bottom tab bar + FAB, single column, bottom-sheet modals
 */

:root {
  --topbar-h: 65px;
  /*
   * Material 3's navigation-bar spec (80dp) rather than the 49pt of a bare iOS tab
   * bar: our items stack a 24px icon over a label, which is the taller M3 shape, and
   * this is the one control an owner hits mid-haircut with one thumb. Excludes the
   * home-indicator inset -- --safe-b is added on top wherever this is used.
   */
  --bottom-nav-h: 80px;
  --fab-size: 56px;
  /* Resolves to 0 unless the viewport meta carries viewport-fit=cover. */
  --safe-b: env(safe-area-inset-bottom, 0px);
}

/* ---------- global hygiene ---------- */

html {
  -webkit-text-size-adjust: 100%;
  /* "See who's open" and the footer's #faq are anchor jumps; a cut to the target
     loses the visitor's place on the page. Reduced motion resets this above. */
  scroll-behavior: smooth;
}

body {
  overscroll-behavior-y: none;
}

img, svg, video {
  max-width: 100%;
}

/* Suppress the grey flash on tap without removing focus affordance. */
a, button, [role="button"], label, summary {
  -webkit-tap-highlight-color: transparent;
}

:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* Turbo's navigation progress bar. It only appears on visits slower than 500ms, so
   most taps never see it -- when it does show, it should read as ours, not as the
   library's stock blue. Turbo injects its own stylesheet at the top of <head>, so
   this later rule wins at equal specificity. It sits on the white top bar, so the
   colour has to carry it: solid brand deepening toward the leading edge, with the
   same glow the public queue header wears. */
.turbo-progress-bar {
  height: 3px;
  background: linear-gradient(90deg, var(--brand), var(--brand-dark));
  box-shadow: 0 0 8px var(--brand-glow);
}

/* Available to screen readers, invisible on screen. Used for section headings that
   are structurally required but visually redundant. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

@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;
  }
}

/* ---------- app shell ---------- */

.app-shell {
  display: flex;
  max-width: 1320px;
  margin: 0 auto;
  align-items: flex-start;
}

.app-content {
  flex: 1;
  min-width: 0;
  padding: 24px 32px 64px;
}

.app-sidebar {
  width: 216px;
  flex: none;
  padding: 24px 14px;
  position: sticky;
  top: var(--topbar-h);
  align-self: flex-start;
}

.app-topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: 12px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* The topbar's switch to the customer side (shared/_nav, AppMode). A quiet pill: it
 * leaves the business, so it should not compete with anything that runs it. */
.mode-switch {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding: 0 14px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--ink);
  font: 700 13px var(--font-body);
  text-decoration: none;
  white-space: nowrap;
}

.mode-switch__icon {
  color: var(--brand);
  flex: none;
}

/* ---------- toasts ----------
 *
 * app/views/shared/_toasts.html.erb, driven by toast_controller.js. A fixed stack at
 * the top of the viewport, newest first, over everything but a <dialog> (which lives
 * in the top layer regardless of z-index). The region takes no pointer events of its
 * own so the topbar underneath stays tappable between toasts.
 */
.toasts {
  position: fixed;
  /* Under the topbar, not over it: the bar holds the business name and the way to
     Settings, and a toast is not worth hiding those for five seconds. Pages without
     the bar (sign-in, the public page) simply get the toast a little lower. */
  top: calc(var(--topbar-h) + 8px + env(safe-area-inset-top, 0px));
  left: 16px;
  right: 16px;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  max-width: 420px;
  min-height: 44px;
  padding: 11px 6px 11px 16px;
  border-radius: var(--r-lg);
  background: var(--ink);
  color: var(--bg);
  box-shadow: var(--shadow-md);
  pointer-events: auto;
  touch-action: pan-x;
  user-select: none;
  -webkit-user-select: none;
  /* Arrives by settling rather than travelling: a short drop from just above, growing
     from 96%, on an expo-out curve that is quick at first and lands softly. The old
     full-height slide dragged the toast through the top bar on its way in. Opacity
     finishes first so the words are readable before the motion ends. Transitions
     rather than keyframes: two toasts a second apart must each retarget from wherever
     they are, not restart. */
  transition:
    opacity 220ms var(--ease-out),
    transform 460ms cubic-bezier(0.16, 1, 0.3, 1);
}

@starting-style {
  .toast { opacity: 0; transform: translateY(-14px) scale(0.96); }
}

/* A new toast is appended below the ones already showing and slides in from the top
   edge, so it has to pass behind them, not over: later siblings paint on top by
   default, and these three rules reverse that for the depth of the stack. */
.toast:nth-last-child(1) { z-index: 1; }
.toast:nth-last-child(2) { z-index: 2; }
.toast:nth-last-child(3) { z-index: 3; }

.toast--alert {
  background: var(--warn-soft);
  color: var(--warn-ink);
}

/* Out is shorter than in and eases in rather than out: leaving is a response (a tap,
   a swipe, the timer they were done with), and it should get out of the way. It fades
   and shrinks in place, then its slot closes so the toasts below glide up instead of
   jumping -- toast_controller pins the height first so there is a number to shrink
   from. A swipe keeps its own direction: the controller sets the transform inline. */
.toast.is-leaving {
  opacity: 0;
  transform: translateY(-6px) scale(0.96);
  overflow: hidden;
  min-height: 0;
  padding-block: 0;
  margin-bottom: -8px; /* the stack's gap, so the collapsed slot takes no room */
  transition:
    opacity 160ms cubic-bezier(0.4, 0, 1, 1),
    transform 200ms cubic-bezier(0.4, 0, 1, 1),
    height 240ms cubic-bezier(0.16, 1, 0.3, 1) 120ms,
    padding 240ms cubic-bezier(0.16, 1, 0.3, 1) 120ms,
    margin 240ms cubic-bezier(0.16, 1, 0.3, 1) 120ms;
}

/* While a finger holds it the toast follows the finger exactly. Any transition here
   would put lag between the two. */
.toast.is-dragging {
  transition: none;
}

.toast__text {
  flex: 1;
  min-width: 0;
  font: 600 13.5px/1.4 var(--font-body);
}

.toast__close {
  flex: none;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border: 0;
  border-radius: var(--r-md);
  background: none;
  color: inherit;
  opacity: 0.6;
  cursor: pointer;
  transition: opacity 160ms var(--ease-out), background-color 160ms var(--ease-out);
}

.toast__close:hover,
.toast__close:focus-visible {
  opacity: 1;
  background: rgb(255 255 255 / 12%);
}

.toast--alert .toast__close:hover,
.toast--alert .toast__close:focus-visible {
  background: rgb(0 0 0 / 6%);
}

/* Reduced motion keeps the fade and drops the travel: a message that appears from
   nothing is easier to miss than one that fades in. The blanket rule at the top of this
   file would cut the fade to 0.01ms, hence the carve-out. */
@media (prefers-reduced-motion: reduce) {
  .toast,
  .toast.is-leaving {
    transition-property: opacity;
    transition-duration: 200ms !important;
  }
  @starting-style {
    .toast { transform: none; }
  }
  .toast.is-leaving { transform: none !important; }
}

@media (min-width: 781px) {
  .toasts {
    left: auto;
    right: 24px;
    align-items: flex-end;
  }
}

/* The Copy button on the QR share block. Its label swaps to "✓ Copied" and back;
   page_share_controller parks it in .is-swapping for one frame around each swap so the
   new words scale up into place rather than cut. */
.page-share__copy {
  display: inline-block;
  transition: opacity 160ms var(--ease-out), transform 160ms var(--ease-out);
}

.page-share__copy.is-swapping {
  opacity: 0;
  transform: scale(0.9);
  transition: none;
}

/* ---------- layout primitives ---------- */

.gk-grid-home,
.gk-grid-money {
  display: grid;
  gap: 16px;
}

.gk-grid-home  { grid-template-columns: 2fr 1fr; }
.gk-grid-money { grid-template-columns: 1.2fr 1fr; }

/* ---------- public customer journey (B1 discover -> B2 services -> B3 details) ---------- */

/* Step panels are toggled by join_flow_controller; !important so a flex/grid
   display on the panel itself can't defeat the hidden attribute. */
[hidden] {
  display: none !important;
}

/*
 * The public page's width. A phone column by default; from 600px it loosens to a
 * tablet column, and from 900px the discover screen becomes two columns under a
 * full-width header. The status and stub screens share the shell but not the grid --
 * one number, one message -- so they stay a centred column at every width.
 *
 * 600 and 900 rather than the app's 780: this page has no sidebar or tab bar to
 * agree with, and 780 falls awkwardly between a portrait and a landscape tablet.
 * The home page directory uses the same pair.
 */
.pq-shell {
  max-width: 480px;
  margin: 0 auto;
  min-height: 100dvh;
  background: var(--bg);
}

/* Wait card, overlapping the header's bottom edge on the phone. Inline until the
   desktop grid needed to cancel the overlap, which inline styles cannot be told to do. */
.pq-wait {
  margin: -18px 16px 0;
}

.pq-services {
  padding: 18px 16px 4px;
}

.pq-services__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 4px;
}

.pq-services__label {
  font: 600 11px var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 1.4px;
  color: var(--muted);
}

.pq-services__summary {
  font: 600 12px var(--font-body);
  color: var(--muted);
  white-space: nowrap;
}

/* One service. The name leads and the duration sits under it, quieter, so the eye
   runs name -> price down the list instead of reading two equal columns. */
.pq-svc {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 44px;
  padding: 11px 0;
  border: 0;
  border-bottom: 1px solid var(--border);
  background: none;
  color: var(--ink);
  font: inherit;
  text-align: left;
}

.pq-svc:last-child {
  border-bottom: 0;
}

button.pq-svc {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

button.pq-svc:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  border-radius: var(--r-md);
}

.pq-svc__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
}

.pq-svc__name {
  font: 600 14px var(--font-body);
  color: var(--ink);
}

.pq-svc__meta {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font: 500 12px var(--font-body);
  color: var(--muted);
}

.pq-svc__clock {
  flex: none;
}

.pq-svc .svc-price {
  font-size: 14px;
}

.pq-svc__add {
  display: grid;
  place-items: center;
  flex: none;
  width: 28px;
  height: 28px;
  border: 1.5px solid var(--border-2);
  border-radius: 50%;
  font: 600 17px/1 var(--font-body);
  color: var(--ink);
  transition: background-color 0.15s ease, border-color 0.15s ease;
}

@media (hover: hover) {
  button.pq-svc:hover .pq-svc__add {
    border-color: var(--ink);
  }
}

button.pq-svc:active .pq-svc__add {
  background: var(--surface-2);
}

/* The shop closed after this page loaded: the broadcast emptied the join bar but
   left these rows as rendered. Without a bar to follow there is nothing to join,
   so the rows go back to being a price list. */
.pq-shell:not(:has(.pq-cta__button)) button.pq-svc {
  pointer-events: none;
}

.pq-shell:not(:has(.pq-cta__button)) .pq-svc__add {
  display: none;
}

.pq-colophon {
  text-align: center;
  padding: 22px 16px 26px;
  font: 500 11.5px var(--font-body);
  color: var(--muted);
}

/* Grahik's wordmark and a way back to the directory, at every width. Thin on a
   phone, where the shop's own header is the thing the page is for. */
.pq-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 48px;
  padding: 2px 16px 0;
}

.pq-top__link {
  font: 600 13px var(--font-body);
  color: var(--muted);
  text-decoration: none;
  padding: 8px 0;
  transition: color 160ms var(--ease-out);
}

.pq-top__link:hover {
  color: var(--ink);
}

@media (min-width: 600px) {
  .pq-shell {
    max-width: 640px;
    padding: 0 16px;
  }

  .pq-top {
    min-height: 56px;
    padding: 4px 4px 0;
  }

  /* On white, the flat ink block reads as a card once it no longer touches the
     viewport edges, so it gets the corners to match. The wait card still overlaps
     its bottom edge by the same 18px. */
  .pq-header {
    padding: 28px 28px 36px;
    border-radius: var(--r-3xl);
  }

  .pq-header-photo {
    min-height: 220px;
  }

  /* The join form does not want the room. A name field 640px wide reads as a
     search box; keep the steps at a hand's width and centre them. */
  .pq-step {
    max-width: 560px;
    margin: 0 auto;
  }
}

@media (min-width: 900px) {
  .pq-shell {
    max-width: 1080px;
    padding: 0 24px;
  }

  /*
   * Header across the top, then the live queue on the left and the way to join it on
   * the right: the CTA card first, the price list under it. The DOM keeps the phone's
   * order (panel, services, colophon, join bar) so the join bar is still the last
   * child the sticky rule relies on below 900px; only the areas move it.
   *
   * The 16px and 32px edge columns line the content up with the header's text: the
   * left-hand blocks carry their own 16px inline padding, the right-hand ones do not.
   */
  .pq-discover {
    display: grid;
    grid-template-columns: 16px minmax(0, 1fr) 32px 380px 32px;
    grid-template-areas:
      "header   header   header   header   header"
      ".        panel    .        cta      ."
      ".        panel    .        side     ."
      ".        panel    .        map      ."
      "colophon colophon colophon colophon colophon";
    grid-template-rows: auto auto auto 1fr auto;
    align-items: start;
  }

  /* The steps sit under the same top margin as the header they replaced, so the
     screen does not jump to the viewport edge when the join begins. */
  .pq-step {
    padding-top: 8px;
  }

  .pq-discover > .pq-header {
    grid-area: header;
    margin-top: 4px;
    padding: 40px 36px 40px;
  }

  .pq-discover .pq-header-photo {
    min-height: 320px;
  }

  .pq-discover .pq-header-name {
    font-size: clamp(28px, 3.4vw, 40px);
    letter-spacing: -1.4px;
  }

  /* Identity down the left, the phone number at the bottom right. The Call link
     used to trail under the blurb, where a wide header left it stranded mid-slab;
     as the header's one solid button it now reads as the thing to do with a shop
     you are looking at from a desk. Everything else stays in column one. */
  .pq-discover .pq-header-body {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
    column-gap: 24px;
  }

  /* start, not stretch: the status pill is an inline-flex box that a grid cell
     would otherwise pull to the column's full width. */
  .pq-discover .pq-header-body > * {
    grid-column: 1;
    justify-self: start;
  }

  .pq-discover .pq-actions {
    grid-column: 2;
    grid-row: 1 / span 6;
    flex-direction: column;
    align-items: stretch;
    justify-self: end;
    margin: 0;
  }

  .pq-discover .pq-call {
    justify-content: center;
    min-height: 44px;
    padding: 12px 22px;
    background: var(--bg);
    border-color: var(--bg);
    color: var(--ink);
    font-size: 14px;
    transition: background-color 160ms var(--ease-out), transform 120ms var(--ease-out);
  }

  .pq-discover .pq-call:hover {
    background: var(--brand-tint);
    border-color: var(--brand-tint);
  }

  .pq-discover .pq-call:active {
    transform: scale(0.97);
  }

  .pq-discover #public-queue-panel {
    grid-area: panel;
  }

  /* No header to overlap on the left column; the card sits in the flow. */
  .pq-discover .pq-wait {
    margin: 24px 16px 0;
  }

  .pq-discover .pq-map {
    grid-area: map;
    margin: 16px 0 0;
  }

  .pq-discover .pq-services {
    grid-area: side;
    margin-top: 16px;
    padding: 18px 20px 8px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-2xl);
  }

  .pq-discover .pq-colophon {
    grid-area: colophon;
  }

  /* The join bar becomes a card in the right column: nothing to stick to, and the
     bottom shadow and safe-area padding were for a bar on a phone's edge. */
  .pq-discover .pq-cta-slot {
    grid-area: cta;
    position: static;
    margin-top: 24px;
  }

  .pq-discover .pq-cta {
    padding: 20px;
    border: 1px solid var(--border);
    border-radius: var(--r-2xl);
    box-shadow: var(--shadow-md);
  }
}

/* The unclaimed-business stub: one message and a claim link, so it takes the tablet
   width and no more. After the media rules on purpose, so it wins at every width. */
.pq-shell--narrow {
  max-width: 560px;
}

/* The status screen: one number, so it stays a hand's width at every size. It has
   its own side padding, and the top bar sits flush inside it rather than adding
   more. */
.pq-shell--status {
  max-width: 460px;
  padding: 0 20px calc(24px + var(--safe-b));
}

.pq-shell--status .pq-top {
  padding-inline: 0;
}

/* Selectable service card. The checked state is pure CSS (:has) -- the controller
   only computes the running total and the enabled state of Continue. */
.svc-pick {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  padding: 14px 16px;
  margin-bottom: 10px;
  border: 1.5px solid var(--border-2);
  border-radius: var(--r-lg);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}

.svc-pick__main {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

.svc-pick__name {
  font: 700 14.5px var(--font-body);
}

.svc-pick input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}

/* The tick. Without it the cards were indistinguishable from the read-only price rows
   on the screen before, so nothing said a card was selectable or that more than one
   could be picked. Drawn as a rotated L rather than a glyph so it inherits weight. */
.svc-pick__box {
  position: relative;
  flex: none;
  width: 22px;
  height: 22px;
  border: 1.5px solid var(--border-2);
  border-radius: 50%;
  background: var(--surface);
}

.svc-pick__box::after {
  content: "";
  position: absolute;
  top: 4px;
  left: 7.5px;
  width: 5px;
  height: 10px;
  border: solid var(--brand);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
  opacity: 0;
}

.svc-pick:has(input:checked) {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}

.svc-pick:has(input:checked) .svc-pick__box {
  background: #fff;
  border-color: #fff;
}

.svc-pick:has(input:checked) .svc-pick__box::after {
  opacity: 1;
}

/* The price is the one part of a chosen card that does not inherit the label's colour:
   .svc-price pins --ink, which on the brand fill is dark green on dark green. */
.svc-pick:has(input:checked) .svc-price,
.svc-pick:has(input:checked) .svc-price__was {
  color: #fff;
}

.svc-pick:has(input:checked) .svc-price__off {
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
}

.svc-pick:has(input:focus-visible) {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

/*
 * The public live queue. Rows were inline-styled until the viewer's own row had to be
 * markable from the browser as well as the server -- my_row_controller re-applies
 * .is-mine after every broadcast, and an inline style is not a thing it can toggle.
 */
.q-rows {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}

.q-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--border);
  text-decoration: none;
  color: inherit;
}

.q-row__pos {
  min-width: 20px;
  font: 800 14px var(--font-head);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.q-row__who {
  flex: 1;
  min-width: 0;
}

.q-row__name {
  font: 600 13px var(--font-body);
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.q-row.is-mine {
  background: var(--brand-soft);
}

.q-row.is-mine .q-row__name {
  color: var(--ink);
  font-weight: 700;
}

/*
 * The tap-through arrow belongs to the href, not to .is-mine: the status screen marks
 * the viewer's row too and has nowhere to send them, so it must not advertise a tap.
 * A row that is not [href] is not a link at all -- <a> without one is plain text.
 */
.q-row__go {
  display: none;
  flex: none;
  font: 700 16px var(--font-body);
  color: var(--brand);
}

.q-row[href] .q-row__go {
  display: block;
}

.q-row[href]:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: -2px;
}

/* Total + Continue, pinned to the bottom of the services step. */
.pq-footer {
  position: sticky;
  bottom: 0;
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding: 14px 16px calc(20px + var(--safe-b));
  margin-top: 16px;
}

.pq-footer__hint {
  font: 500 12.5px var(--font-body);
  color: var(--muted);
  margin-bottom: 10px;
}

.pq-footer__total {
  display: flex;
  justify-content: space-between;
  font: 700 15px var(--font-head);
  color: var(--ink);
  margin-bottom: 10px;
}

/* What the customer is about to commit to, read back on the last step. */
.pq-summary {
  background: var(--brand-soft);
  border-radius: var(--r-lg);
  padding: 12px 14px;
  margin-bottom: 18px;
  font: 700 14px var(--font-body);
  color: var(--ink);
}

/*
 * The join call to action, pinned to the bottom of the discover screen. It was inline
 * in the panel until the price list grew long enough to push it off screen, leaving a
 * customer who scrolled down to check prices with no way to act without scrolling back.
 *
 * Sticky, not fixed: as the last child of the screen it comes to rest in its natural
 * place at the end of the scroll instead of covering the footer for good.
 *
 * The sticky element has to be the broadcast wrapper, not the bar inside it. A sticky
 * box cannot travel outside its containing block, and the wrapper is exactly as tall
 * as the bar -- putting sticky on the bar gives it nowhere to go, and it silently
 * never sticks. On the wrapper, the containing block is the whole screen.
 *
 * The wrapper keeps no styling of its own so that the states which render nothing into
 * it (closed, walk-in-only, no services) leave a zero-height box rather than an empty
 * white bar. It still has to be in the document: reopening broadcasts a replace of it.
 */
.pq-cta-slot {
  position: sticky;
  bottom: 0;
  z-index: 3;
}

.pq-cta {
  background: var(--surface);
  border-top: 1px solid var(--border);
  box-shadow: 0 -6px 18px rgba(10, 10, 10, 0.06);
  padding: 12px 16px calc(14px + var(--safe-b));
}

.pq-cta__button {
  width: 100%;
  min-height: 44px;
  padding: 16px;
  background: var(--brand);
  color: #fff;
  border: none;
  border-radius: var(--r-lg);
  font: 800 16px var(--font-head);
  box-shadow: var(--shadow-brand);
  cursor: pointer;
}

.pq-cta__meta {
  text-align: center;
  margin-top: 8px;
  font: 500 12.5px var(--font-body);
  color: var(--muted);
}

/*
 * ---------- public status screen (B4 in queue / B5 your turn) ----------
 *
 * These were inline styles until the screen had to react to things the server
 * cannot see: whether the stream is still connected, and whether the number just
 * moved. Both are classes toggled on .pq-shell, which outlives the broadcast that
 * replaces #public-status -- an inline style on the replaced markup would be gone
 * the moment the queue moved.
 */

.pq-pos {
  font: 800 clamp(56px, 22vw, 88px)/0.95 var(--font-head);
  letter-spacing: -2px;
  color: var(--ink);
  margin-top: 10px;
  font-variant-numeric: tabular-nums;
}

.pq-pos__ord {
  font-size: 0.32em;
  vertical-align: top;
  position: relative;
  top: 0.35em;
}

/* Only when the rank actually changed, not on every re-render: queue_alert_controller
   compares the old position with the new one before adding this. A queue that moved
   behind you is not news; your own number moving is. */
.pq-pos.is-bumped {
  animation: posBump 0.45s cubic-bezier(0.2, 0.8, 0.3, 1);
}

@keyframes posBump {
  from { transform: translateY(9px) scale(0.94); opacity: 0.35; }
  to   { transform: none; opacity: 1; }
}

/* The two dark cards -- your turn, all done -- are the one moment of the visit worth a
   beat. They arrive as fresh nodes when the broadcast replaces #public-status, so
   @starting-style is enough: no JavaScript, and nothing replays on the screens the
   customer sits on for twenty minutes. The badge on "All done" lands a touch after
   the card so the tick reads as a result, not a decoration. Longer than any owner-side
   entrance on purpose: each customer sees it once. */
.pq-arrive {
  transition: opacity 420ms var(--ease-out), transform 420ms var(--ease-out);
}

.pq-arrive__badge {
  transition: opacity 360ms var(--ease-out) 120ms, transform 360ms var(--ease-out) 120ms;
}

@starting-style {
  .pq-arrive { opacity: 0; transform: translateY(12px) scale(0.97); }
  .pq-arrive__badge { opacity: 0; transform: scale(0.6); }
}

/* Reduced motion keeps the fade and drops the travel: an empty card that then fills
   is more jarring than a short cross-fade, so this is gentler rather than nothing. */
@media (prefers-reduced-motion: reduce) {
  .pq-arrive, .pq-arrive__badge {
    transition-property: opacity;
    transition-duration: 200ms !important;
    transition-delay: 0ms;
  }
  @starting-style {
    .pq-arrive, .pq-arrive__badge { transform: none; }
  }
}

/* The minutes carry more weight than the ordinal above them: the rank says where you
   are in a list, the minutes say whether to set off now. */
.pq-wait {
  margin-top: 8px;
}

.pq-wait__num {
  font: 800 27px var(--font-head);
  letter-spacing: -0.8px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.pq-wait__num span {
  font: 600 14px var(--font-body);
  margin-left: 3px;
}

.pq-wait__word {
  font: 700 19px var(--font-head);
  letter-spacing: -0.4px;
  color: var(--ink);
}

.pq-wait__sub {
  font: 500 12px var(--font-body);
  color: var(--muted);
  margin-top: 2px;
}

/* Both labels are server-rendered and CSS picks between them, so the swap survives a
   broadcast without any JavaScript writing text into replaced markup. */
.pq-live {
  display: flex;
  align-items: center;
  gap: 6px;
  font: 600 11px var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 1.2px;
  color: var(--muted);
}

.pq-live__off { display: none; }

.pq-shell.is-stream-down .pq-live { color: var(--warn-ink); }
.pq-shell.is-stream-down .pq-live__on { display: none; }
.pq-shell.is-stream-down .pq-live__off { display: inline; }

.pq-shell.is-stream-down .pq-live__dot {
  background: var(--warn);
  animation: none;
}

/* The only control on the screen, and a destructive one. It read as a disabled ghost:
   muted ink on nothing, with no response to being pressed. */
.pq-leave {
  margin-top: 20px;
  min-height: 44px;
  padding: 12px 20px;
  background: transparent;
  border: 1.5px solid var(--muted-3);
  border-radius: var(--r-lg);
  font: 700 13px var(--font-body);
  color: var(--ink);
  cursor: pointer;
  transition: background-color 160ms var(--ease-out), transform 120ms var(--ease-out);
}

.pq-leave:hover { background: var(--surface-2); }
.pq-leave:active { background: var(--border); transform: scale(0.97); }

/* The confirm sheet: every data-turbo-confirm (confirm_sheet.js) and the leave-queue
   sheet on the status page. A centered card on desktop, a bottom sheet on a phone (the
   gk-modal rules). The safe choice is the loud button; the action is the quiet red one. */
.confirm-sheet { max-width: 400px; }

.confirm-sheet__body { padding: 24px 22px 22px; }

.confirm-sheet__title {
  margin: 0;
  font: 800 21px/1.2 var(--font-head);
  letter-spacing: -0.4px;
  color: var(--ink);
}

.confirm-sheet__place {
  margin: 12px 0 0;
  font: 400 17px/1.35 var(--font-serif);
  font-style: italic;
  color: var(--ink);
}

.confirm-sheet__place:empty { display: none; }

.confirm-sheet__text {
  margin: 8px 0 0;
  font: 500 13.5px/1.5 var(--font-body);
  color: var(--muted);
}

.confirm-sheet__actions {
  display: flex;
  flex-direction: row-reverse;
  gap: 8px;
  margin-top: 22px;
}

.confirm-sheet__actions form { display: contents; }

.confirm-sheet__stay,
.confirm-sheet__go {
  min-height: 44px;
  padding: 11px 18px;
  border-radius: var(--r-lg);
  font: 700 13.5px var(--font-head);
  cursor: pointer;
}

.confirm-sheet__stay {
  flex: 1;
  border: none;
  background: var(--ink);
  color: var(--bg);
}

.confirm-sheet__go {
  border: 1.5px solid var(--border-2);
  background: none;
  color: var(--error);
}

.confirm-sheet__go:hover:not(:disabled) { background: var(--surface-2); }
.confirm-sheet__go:disabled { opacity: 0.4; cursor: not-allowed; }

.confirm-sheet__type-label {
  display: block;
  margin-top: 16px;
  font: 500 13px/1.4 var(--font-body);
  color: var(--muted);
}

.confirm-sheet__type-label strong { color: var(--ink); font-weight: 700; }

.confirm-sheet__type {
  display: block;
  width: 100%;
  margin-top: 8px;
  min-height: 44px;
  padding: 10px 12px;
  border: 1.5px solid var(--border-2);
  border-radius: var(--r-lg);
  background: var(--surface);
  font: 500 16px var(--font-body);
  color: var(--ink);
}

.confirm-sheet__type:focus { outline: none; border-color: var(--ink); }

/* Thumb reach: stacked, full width, Keep on top where the thumb lands first. */
@media (max-width: 780px) {
  .confirm-sheet { max-width: none; }
  .confirm-sheet__body { padding: 18px 20px 20px; }
  .confirm-sheet__actions { flex-direction: column; gap: 10px; }
  .confirm-sheet__stay,
  .confirm-sheet__go { min-height: 50px; width: 100%; font-size: 14.5px; }
  .confirm-sheet__go { border-color: transparent; }
}

/* Done and left-the-queue both used to stop at a sentence. */
.pq-next {
  display: flex;
  flex-direction: column;
  margin-top: 22px;
}

.pq-next__primary {
  min-height: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--brand);
  color: #fff;
  border-radius: var(--r-lg);
  font: 700 14px var(--font-body);
  text-decoration: none;
  transition: background-color 160ms var(--ease-out), transform 120ms var(--ease-out);
}

.pq-next__primary:hover { background: var(--brand-dark); }
.pq-next__primary:active { transform: scale(0.97); }

/* The bill under "You're up!", and the way to pay it. A light card after the dark one:
   the dark card says go, this one is the paperwork, and it should read as calmer. */
.pq-pay {
  margin-top: 14px;
  padding: 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-2xl);
}

.pq-pay__eyebrow {
  margin: 0;
  font: 600 11px var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 1.4px;
  color: var(--muted);
}

.pq-pay__items {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
}

.pq-pay__items li,
.pq-pay__total {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-variant-numeric: tabular-nums;
}

.pq-pay__items li {
  padding: 6px 0;
  font: 500 14px var(--font-body);
  color: var(--ink-3);
}

.pq-pay__total {
  margin-top: 8px;
  padding-top: 12px;
  border-top: 1px dashed var(--muted-3);
  font: 800 22px var(--font-head);
  letter-spacing: -0.5px;
  color: var(--ink);
}

.pq-pay__total span:first-child {
  font: 600 14px var(--font-body);
  letter-spacing: 0;
  align-self: center;
}

.pq-pay__label {
  margin-top: 20px;
  font: 700 13px var(--font-body);
  color: var(--ink);
}

.pq-pay__apps {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-top: 10px;
}

.pq-pay__app {
  min-height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 10px;
  background: var(--brand);
  color: #fff;
  border-radius: var(--r-lg);
  font: 700 14px var(--font-body);
  text-align: center;
  text-decoration: none;
  transition: background-color 160ms var(--ease-out), transform 120ms var(--ease-out);
}

.pq-pay__app:hover { background: var(--brand-dark); }
.pq-pay__app:active { transform: scale(0.97); }

.pq-pay__app--other {
  background: transparent;
  color: var(--ink);
  border: 1.5px solid var(--muted-3);
}

.pq-pay__app--other:hover { background: var(--surface-2); }

.pq-pay__vpa {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 12px;
  padding: 10px 12px;
  background: var(--surface-2);
  border-radius: var(--r-lg);
}

.pq-pay__vpa-text { min-width: 0; display: flex; flex-direction: column; }

.pq-pay__vpa-label {
  font: 600 10px var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 1.2px;
  color: var(--muted);
}

.pq-pay__vpa-id {
  font: 600 14px var(--font-body);
  color: var(--ink);
  overflow-wrap: anywhere;
  user-select: all;
}

.pq-pay__copy {
  flex: none;
  min-height: 36px;
  padding: 0 14px;
  background: transparent;
  border: 1.5px solid var(--muted-3);
  border-radius: var(--r-md);
  font: 700 13px var(--font-body);
  color: var(--ink);
  cursor: pointer;
}

.pq-pay__copy:active { transform: scale(0.97); }

.pq-pay__hint {
  margin: 12px 0 0;
  font: 500 12.5px var(--font-body);
  color: var(--muted);
  text-align: center;
}

.pq-paid {
  display: inline-block;
  margin-top: 14px;
  padding: 6px 12px;
  border-radius: 999px;
  background: var(--brand);
  color: #fff;
  font: 700 12px var(--font-body);
}

/* Back to the shop page from the status screen. */
.pq-crumb {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  text-decoration: none;
}

.pq-crumb__arrow {
  font: 700 16px var(--font-body);
  color: var(--muted);
}

.pq-crumb__name {
  font: 700 15px var(--font-head);
  color: var(--muted);
}

.pq-back {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-left: -10px;
  flex: none;
  background: none;
  border: none;
  font: 700 18px var(--font-body);
  color: var(--ink);
  cursor: pointer;
}

/* Public page header. Without photos this is the flat ink block the page has always
   had; with them, the photo track scrolls behind the text under a scrim heavy enough
   that a bright midday shopfront cannot swallow the shop name. */
.pq-header {
  position: relative;
  overflow: hidden;
  background: var(--ink);
  color: var(--bg);
  padding: 22px 20px 30px;
}

/* Only the photo states get a minimum height: the photo track fills the header via
   inset:0, and a short shop name would otherwise leave too little room to show a
   photo. The plain header has no photos to size for, so it stays content-driven at
   its pre-existing height -- unconditional min-height here would grow the zero-photo
   header past what it rendered before Task 5 and break the "visually identical"
   requirement plus the panel's calibrated overlap. */
.pq-header-photo {
  min-height: 170px;
}

.pq-header-glow {
  position: absolute;
  top: -60px;
  right: -60px;
  width: 200px;
  height: 200px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--brand-glow), transparent 70%);
}

.pq-header-photo .pq-header-body { position: relative; pointer-events: none; }
.pq-header-body { position: relative; }

/* Clamped, not fixed at 28px: a long shop name at this tracking wraps badly on a
   360px phone, and the name is the one thing that has to survive the scrim. */
.pq-header-name {
  font: 800 clamp(22px, 7vw, 28px) var(--font-head);
  letter-spacing: -1.1px;
  margin-top: 12px;
}

/* Street and pincode, one notch quieter than the category line above it: it answers
   "where exactly" for someone who already knows which shop. */
.pq-address {
  font: 500 12.5px var(--font-body);
  color: rgba(250, 250, 247, 0.62);
  margin-top: 4px;
}

/* The owner's own words, below the address and above the Call button. Plain text: the
   field is unformatted, and the cap in Business::ABOUT_LIMIT keeps it to a blurb. Set
   a shade brighter than the address so it reads as copy, not as another detail line. */
.pq-about {
  margin: 12px 0 0;
  max-width: 34em;
  font: 500 13.5px/1.5 var(--font-body);
  color: rgba(250, 250, 247, 0.85);
}

/* The only tappable thing inside .pq-header-body, which the photo header makes
   pointer-events:none so a swipe across the name reaches the carousel behind it. */
.pq-call {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 36px;
  margin-top: 14px;
  padding: 8px 16px;
  border: 1.5px solid rgba(250, 250, 247, 0.35);
  border-radius: var(--r-pill);
  color: var(--bg);
  font: 700 13px var(--font-head);
  text-decoration: none;
  pointer-events: auto;
}

/* Call and Directions side by side; the row carries the gap from the blurb above,
   so each pill drops its own. */
.pq-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}
.pq-actions .pq-call { margin-top: 0; }
.pq-call svg { flex: none; }

/* The map card: a static map that is itself the directions link, with the address
   as its footer. Same surface and radius as the services card so the two read as
   one column of facts about the shop. */
.pq-map {
  display: block;
  margin: 16px 16px 0;
  border: 1px solid var(--border);
  border-radius: var(--r-2xl);
  background: var(--surface);
  overflow: hidden;
  text-decoration: none;
}

.pq-map__image {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 2 / 1;
  object-fit: cover;
  background: var(--brand-soft);
}

.pq-map__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 16px;
}

.pq-map__where {
  min-width: 0;
  font: 500 12.5px/1.45 var(--font-body);
  color: var(--muted);
}

.pq-map__go {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex: none;
  font: 700 13px var(--font-head);
  color: var(--brand);
}

.pq-photo-track {
  position: absolute;
  inset: 0;
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}
.pq-photo-track::-webkit-scrollbar { display: none; }

.pq-photo {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  object-fit: cover;
  scroll-snap-align: start;
}

.pq-photo-scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg,
    rgba(15, 30, 28, 0.25) 0%,
    rgba(15, 30, 28, 0.55) 45%,
    rgba(15, 30, 28, 0.92) 100%);
}

.pq-photo-counter {
  position: absolute;
  top: 18px;
  right: 14px;
  font: 600 11px var(--font-mono);
  letter-spacing: 0.8px;
  color: rgba(250, 250, 247, 0.7);
  pointer-events: none;
}

.pq-photo-dots {
  position: absolute;
  bottom: 26px;
  left: 0;
  right: 0;
  display: flex;
  gap: 5px;
  justify-content: center;
}

.pq-photo-dot {
  width: 5px;
  height: 5px;
  padding: 0;
  border: none;
  border-radius: 9px;
  background: rgba(250, 250, 247, 0.45);
  cursor: pointer;
  transition: width 0.2s ease;
}

.pq-photo-dot.is-active { width: 16px; background: var(--bg); }

/* Worn by the in-chair row alone, so the colour is the state and needs no inline
   override from the caller. Waiting rows carry no pill at all. */
.pq-status-pill {
  flex: none;
  padding: 3px 8px;
  border-radius: var(--r-pill);
  background: var(--warn-soft);
  color: var(--warn-ink);
  font: 700 11px var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 1px;
}

/* ---------- modals / bottom sheets ---------- */

/*
 * BUG FIX: dialog.gk-modal (tokens.css) sets no max-height and no overflow, so on a
 * phone with the keyboard raised the add-member modal ran past the viewport with no
 * way to reach its submit button. Constrain and scroll at every size.
 */
dialog.gk-modal {
  max-height: 85dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Modal buttons and submits are styled inline in the views; the press response is
   the one thing they share, so it lives here. */
.gk-modal button,
.gk-modal input[type="submit"] { transition: transform 120ms var(--ease-out); }
.gk-modal button:active,
.gk-modal input[type="submit"]:active { transform: scale(0.97); }

@media (max-width: 780px) {
  /* Bottom sheet: anchored to the bottom edge, full width, rounded top corners. */
  dialog.gk-modal {
    max-width: none;
    width: 100%;
    margin: 0 auto 0;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    top: auto;
    border-radius: var(--r-3xl) var(--r-3xl) 0 0;
    max-height: 92dvh;
    padding-bottom: var(--safe-b);
    /* A slide, not the desktop fade: the sheet stays opaque and travels on transform
       only. Same discrete overlay/display trick as tokens.css, so it slides back out
       along the edge it came in from instead of vanishing on close(). */
    opacity: 1;
    transform: translateY(100%);
    transition: transform 240ms var(--ease-drawer),
                overlay 240ms allow-discrete, display 240ms allow-discrete;
  }

  dialog.gk-modal[open] {
    transform: none;
    transition-duration: 320ms;
  }

  @starting-style {
    dialog.gk-modal[open] { transform: translateY(100%); }
  }

  /* Grab handle, so it reads as a sheet rather than a stuck panel. */
  dialog.gk-modal::before {
    content: "";
    display: block;
    width: 36px;
    height: 4px;
    border-radius: var(--r-pill);
    background: var(--border-2);
    margin: 10px auto 0;
  }
}

/* ---------- centered card screens (onboarding, login, landing) ---------- */

/*
 * dvh rather than vh: on iOS Safari 100vh includes the browser chrome, so a
 * "full height" centered card gets clipped and jumps as the URL bar hides.
 * On mobile these go full-bleed and behave like app screens.
 */
.center-shell {
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.center-card {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-2xl);
  box-shadow: var(--shadow-lg);
  padding: 32px;
}

/* Opt-in for long cards that should scroll internally on desktop. */
.center-card--scroll {
  max-height: 90dvh;
  overflow-y: auto;
}

/* Landing page wants a little more air above the wordmark. */
.center-card--hero {
  padding-top: 44px;
}

/* ---------- signed-out auth pages (login, code entry, signup) ---------- */

/*
 * A flex column rather than calc(100dvh - <bar height>): .center-shell centres against
 * min-height:100dvh, and subtracting a magic number from that goes wrong the moment the
 * wordmark wraps or the head font's metrics shift. The bar is the fixed row, the shell
 * is the row that grows, and the card stays centred in whatever is left.
 */
.auth-page {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

.auth-topbar {
  flex: none;
  padding: calc(18px + env(safe-area-inset-top, 0px)) 24px 4px;
}

/*
 * Capped to the cards' own max-width and centred, so the wordmark sits on the card's
 * centre line rather than drifting into the corner of a wide monitor -- the card is the
 * only thing on the page and it is centred, so anything else in the column would read as
 * off-axis. No bottom rule: a full-width line would cut across a card that floats on the
 * page. The phone override below goes back to the left edge, where the card fills the
 * width and the heading it sits above is left-aligned.
 */
.auth-topbar__inner {
  width: 100%;
  max-width: 420px;
  margin-inline: auto;
  text-align: center;
}

/* The shell is the growing row now; its own 100dvh would push the bar off screen. */
.center-shell--flush {
  min-height: 0;
  flex: 1;
}

@media (max-width: 780px) {
  .center-shell {
    padding: 0;
    align-items: stretch;
  }

  .center-card,
  .center-card--scroll {
    min-height: 100dvh;
    max-height: none;
    overflow-y: visible;
    border: none;
    border-radius: 0;
    box-shadow: none;
    padding: 24px 20px calc(28px + var(--safe-b));
  }

  /*
   * The card drops its border and shadow here but keeps its 420px cap, so between about
   * 420px and this breakpoint it is a white column on a grey field. The bar takes the
   * card's surface and its exact column rather than going edge to edge -- a full-width
   * white band over a narrower white card reads as two mismatched surfaces, and the
   * wordmark would sit against the viewport edge while the heading below it starts
   * 170px in. On a real phone the column is the whole width and this is simply flush.
   */
  .auth-topbar {
    padding: 0;
  }

  /* The top inset moves onto the inner too: left on the outer it would paint a strip of
     page background above the white column, which is the seam this is here to avoid. */
  .auth-topbar__inner {
    background: var(--surface);
    padding: calc(20px + env(safe-area-inset-top, 0px)) 20px 0;
    text-align: left;
  }

  /* The rule above claims 100dvh for the card, which under a bar overflows by exactly
     the bar's height. Stretching from the flex row gives the same full-screen card. */
  .auth-page .center-card {
    min-height: 0;
  }
}

/* ---------- service editor rows (settings + onboarding step 2) ---------- */

/*
 * Laid out by the editor's own width, not the viewport's: the same partials sit in the
 * Settings card and in onboarding's 520px card, and only the container knows whether
 * five columns fit. Narrow, each service is a small labelled card -- name on one line,
 * the three numbers side by side under it. Wide, the list becomes a table with one
 * column header and the per-field labels step aside.
 */
.svc-editor {
  container-type: inline-size;
}

.svc-list {
  display: grid;
  gap: 10px;
}

.svc-item {
  padding: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
}

.svc-row {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.5fr) minmax(0, 0.35fr) minmax(0, 0.45fr);
  grid-template-areas:
    "name  name  name  del"
    "price offer mins  mins";
  /* The offer column is the widest: it carries the % OFF badge beside the number. */
  gap: 10px 8px;
  align-items: end;
}

.svc-field--name  { grid-area: name; }
.svc-field--price { grid-area: price; }
.svc-field--offer { grid-area: offer; }
.svc-field--mins  { grid-area: mins; }
.svc-del          { grid-area: del; }

.svc-field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}

.svc-field__label {
  font: 600 11.5px var(--font-body);
  color: var(--muted);
}

.svc-field__label em {
  font-style: normal;
  font-weight: 500;
  color: var(--muted-2);
}

/* The name field and the number boxes share one frame so the row reads as one set. */
.svc-input,
.svc-box {
  height: 44px;
  border: 1.5px solid var(--border-2);
  border-radius: var(--r-lg);
  background: var(--surface);
  transition: border-color 120ms var(--ease-out);
}

.svc-input:focus,
.svc-box:focus-within {
  border-color: var(--brand);
}

.svc-input {
  width: 100%;
  min-width: 0;
  padding: 0 12px;
  font: 600 14px var(--font-body);
  color: var(--ink);
  outline: none;
}

.svc-box {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  padding: 0 10px;
  cursor: text;
}

.svc-box__affix {
  flex: none;
  font: 700 13px var(--font-head);
  color: var(--muted);
}

.svc-box__affix--end {
  font: 600 11.5px var(--font-body);
}

.svc-box__input {
  flex: 1;
  width: 100%;
  min-width: 0;
  padding: 0;
  border: none;
  background: transparent;
  font: 700 14px var(--font-body);
  color: var(--ink);
  outline: none;
  -moz-appearance: textfield;
}

.svc-box__input::-webkit-outer-spin-button,
.svc-box__input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.svc-box__input::placeholder {
  font-weight: 500;
  color: var(--muted-2);
}

/* Green so a live offer is visible in a list the eye is scanning for prices. */
.svc-box__input--offer {
  color: var(--success);
}

/*
 * What the offer takes off, inside the offer box beside the number it comes from, so
 * the owner sees what the customer will read without leaving this screen. Empty when
 * no offer runs, and then it takes no room.
 */
.svc-off {
  flex: none;
  font: 700 9.5px var(--font-mono);
  letter-spacing: 0.3px;
  color: var(--success);
  white-space: nowrap;
}

.svc-off:not(:empty) {
  padding: 3px 6px;
  border-radius: var(--r-pill);
  background: var(--success-soft);
}

/* An offer at or above the list price is what the model rejects on save. */
.svc-off--over {
  color: var(--error);
  background: transparent !important;
  padding-right: 0 !important;
}

.svc-del {
  justify-self: end;
  width: 44px;
  height: 44px;
  align-self: start;
  margin: -6px -6px 0 0;
  border-radius: 50%;
  background: transparent;
  border: none;
  color: var(--muted-2);
  font: 600 14px var(--font-body);
  cursor: pointer;
}

.svc-del:hover {
  color: var(--error);
  background: var(--surface-2);
}

.svc-error {
  margin: 8px 0 0;
  font: 600 12px var(--font-body);
  color: var(--error);
}

/* Only the row the owner just added, not every row on a page load or a rejected Save
   streaming the list back: create.turbo_stream.erb passes `entering` and nothing else
   does. Entry only -- a removed row goes at once, because that is what was asked for. */
.svc-item--enter {
  transition: opacity 200ms var(--ease-out), transform 200ms var(--ease-out);
}

@starting-style {
  .svc-item--enter { opacity: 0; transform: translateY(-6px); }
}

/* The column header only exists in the wide table. */
.svc-head {
  display: none;
}

/* ---- the add panel: dashed, to read as an empty slot waiting to be filled ---- */

.svc-add {
  margin-top: 18px;
  padding: 14px;
  border: 1.5px dashed var(--border-2);
  border-radius: var(--r-xl);
  background: var(--surface-2);
}

.svc-add__title {
  margin-bottom: 12px;
  font: 700 13.5px var(--font-head);
  color: var(--ink);
}

.svc-add .svc-row {
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.5fr) minmax(0, 0.8fr);
  grid-template-areas:
    "name  name  name"
    "price offer mins"
    "btn   btn   btn";
}

.svc-add__btn {
  grid-area: btn;
  height: 44px;
  padding: 0 18px;
  background: var(--brand);
  color: #fff;
  border: none;
  border-radius: var(--r-lg);
  font: 700 13.5px var(--font-body);
  cursor: pointer;
}

.svc-add__btn:hover {
  background: var(--brand-dark);
}

@container (min-width: 520px) {
  .svc-list {
    gap: 8px;
  }

  .svc-head,
  .svc-row {
    grid-template-columns: minmax(0, 1fr) 92px 144px 84px 36px;
    grid-template-areas: "name price offer mins del";
    gap: 8px;
  }

  .svc-head {
    display: grid;
    padding: 0 2px 2px;
    font: 600 11.5px var(--font-body);
    color: var(--muted);
  }

  /* No rows, no columns to name. */
  .svc-list:not(:has(.svc-item)) .svc-head {
    display: none;
  }

  .svc-item {
    padding: 0;
    background: none;
    border: none;
    border-radius: 0;
  }

  .svc-row {
    align-items: center;
  }

  /* The header names the columns once; screen readers still get each field's label. */
  .svc-list .svc-field__label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .svc-del {
    align-self: center;
    width: 36px;
    height: 36px;
    margin: 0;
  }

  /* The panel's padding costs the name the width it has in the table, so it keeps a
     line of its own and the numbers and the button share the next. */
  .svc-add .svc-row {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) minmax(0, 0.9fr) auto;
    grid-template-areas:
      "name  name  name  name"
      "price offer mins  btn";
    align-items: end;
  }

  .svc-add__btn {
    padding: 0 22px;
  }
}

/* ---------- a service's money, wherever it is shown to a reader ---------- */

.svc-price {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  font: 700 13.5px var(--font-head);
  color: var(--ink);
  white-space: nowrap;
}

.svc-price--lg {
  font-size: 14px;
}

.svc-price__off {
  align-self: center;
  padding: 2px 7px;
  border-radius: var(--r-pill);
  background: var(--success-soft);
  color: var(--success);
  font: 700 11px var(--font-mono);
  letter-spacing: 0.4px;
}

/* The note's age, under the note. Stale notes -- written on an earlier day -- are
   dated rather than counted, and demoted along with the note they date. */
.note-stamp {
  margin-top: 8px;
  font: 600 11px var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 1.1px;
  color: var(--muted-2);
}

.note-stale {
  color: var(--muted-3);
}

/* --muted, not --muted-2: struck-through text is already hard enough to read. */
.svc-price__was {
  font: 600 0.85em var(--font-head);
  color: var(--muted);
}

/* Bottom nav + FAB are desktop-hidden; the media query below turns them on. */
.bottom-nav, .fab {
  display: none;
}

.bottom-nav a {
  flex: 1;
  min-height: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 8px 4px;
  text-decoration: none;
  color: var(--muted);
  font: 600 11px var(--font-body);
}

.bottom-nav a[aria-current="page"] {
  color: var(--brand);
}

/* ---------- open/closed card (dashboard) ---------- */

/*
 * One card, two independent writes. The toggle row is its own <form> (button_to) and
 * the note is another below it, so the card border lives on this wrapper rather than
 * on either form -- that is what makes them read as one control.
 */
.open-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-sm);
  margin-bottom: 18px;
  overflow: hidden;
}

/* Open is the state worth tinting: closed should look inert, not alarming. */
.open-card.is-open {
  border-color: var(--brand-tint);
}

.open-card__toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  min-height: 44px;
  padding: 14px 16px;
  border: none;
  background: none;
  text-align: left;
  cursor: pointer;
}

.open-card__state {
  display: flex;
  align-items: center;
  gap: 11px;
  min-width: 0;
}

.open-card__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--muted-3);
  flex: none;
}

.open-card__label {
  display: block;
  font: 700 14px var(--font-body);
  color: var(--ink);
}

.open-card__hint {
  display: block;
  font: 500 12px var(--font-body);
  color: var(--muted);
  margin-top: 2px;
}

/*
 * The switch pill. Shared by the dashboard open/closed card and the settings joining
 * toggle -- the same kind of decision, so the same control. Presentation only: each
 * call site wraps it in its own button_to, because what the tap does and what it is
 * labelled belong to the call site, not to the pill.
 *
 * .is-on is set by the server from the record. There is no client-side state: the knob
 * moves when the round trip comes back, which is also when the change is true.
 */
.switch {
  width: 38px;
  height: 22px;
  border-radius: var(--r-pill);
  background: var(--border-2);
  position: relative;
  flex: none;
}

.switch.is-on {
  background: var(--brand);
}

.switch__knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #fff;
  box-shadow: var(--shadow-sm);
  /* transform, not left: left re-lays-out the row on every frame. */
  transition: transform 180ms var(--ease-out);
}

.switch.is-on .switch__knob {
  transform: translateX(16px);
}

/* The note row. One quiet line when there is no note; the live note quoted back when
   there is, because the owner should always be able to see what their page is saying
   in their name. Tapping it opens the editor in a modal (#status-note-modal). */
.open-card__note {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  padding: 12px 16px;
  border: none;
  border-top: 1px solid var(--border);
  background: none;
  text-align: left;
  cursor: pointer;
  transition: background-color 160ms var(--ease-out);
}

.open-card__note:active {
  background: var(--bg);
}

.open-card__note-action {
  margin-left: auto;
  flex: none;
  font: 700 13px var(--font-body);
  color: var(--brand);
}

/* A live note is quoted back in the same serif the public page renders it in, so the
   owner recognises it as the thing customers are reading. */
.open-card__note-live {
  font: 400 14px var(--font-serif);
  font-style: italic;
  color: var(--ink);
  min-width: 0;
  overflow-wrap: anywhere;
}

.open-card__note-add {
  font: 600 13px var(--font-body);
  color: var(--muted);
}

/* The note editor. A gk-modal: centered on desktop, a bottom sheet on a phone. */
.open-note-modal__body {
  padding: 20px 20px 22px;
}

.open-note-modal__head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
}

.open-note-modal__title {
  margin: 0;
  font: 800 19px var(--font-head);
  color: var(--ink);
}

.open-note-modal__close {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  margin: -8px -8px -8px 0;
  border: none;
  border-radius: 50%;
  background: none;
  color: var(--muted);
  cursor: pointer;
}

.open-card__form-hint {
  display: block;
  font: 500 11.5px var(--font-body);
  color: var(--muted);
  margin-bottom: 7px;
}

.open-card__input {
  width: 100%;
  padding: 12px 14px;
  border: 1.5px solid var(--border-2);
  border-radius: var(--r-lg);
  font: 500 14px var(--font-body);
  color: var(--ink);
  background: var(--bg);
}

.open-card__actions {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
}

.open-card__save,
.open-card__clear {
  min-height: 40px;
  padding: 10px 18px;
  border-radius: var(--r-lg);
  font: 700 13px var(--font-head);
  cursor: pointer;
}

.open-card__save {
  border: none;
  background: var(--brand);
  color: #fff;
}

.open-card__clear {
  border: 1px solid var(--border-2);
  background: none;
  color: var(--muted);
}

/* Remove is destructive, so it sits apart from Cancel and Save. */
.open-card__clear--start {
  margin-right: auto;
  color: var(--error);
}

/* On a phone the editor is a bottom sheet with the keyboard up: Save becomes the one
   big thumb target, Remove stays beside it, and Cancel goes (✕ and the backdrop close
   the sheet). */
@media (max-width: 780px) {
  .open-card__cancel { display: none; }

  .open-card__actions { gap: 10px; }

  .open-card__save,
  .open-card__clear {
    min-height: 48px;
  }

  .open-card__save {
    flex: 1;
    font-size: 14px;
  }

  .open-card__clear--start { margin-right: 0; }
}

/* ---------- queue row actions ----------
 *
 * Used by app/views/queue/_entry.html.erb. These are the controls a barber hits
 * mid-cut, one-handed, so they carry the same 44px target as .bottom-nav a rather
 * than the ~30px the old inline padding gave them.
 *
 * button_to wraps every control in a <form>, so the FORM is the flex item and the
 * button only fills it -- sizing has to run through both.
 */

/* ---------- page header (every owner screen) ----------
 *
 * One header for the dashboard, queue, customers, money, the customer detail and the
 * import steps: title on the left, actions on the right, an optional back control and
 * a one-line subtitle. Settings has its own shell but shares the back control and the
 * title rules below, so the two read as the same header. Wraps rather than shrinks:
 * on a phone the actions drop to a row of their own under the title instead of
 * squeezing it -- "Customers · 12" beside two buttons broke into three lines at 375px.
 */
.page-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px 14px;
  margin-bottom: 18px;
}

.page-head__title,
.settings-screen__title {
  margin: 0;
  font: 800 24px var(--font-head);
  letter-spacing: -0.7px;
  color: var(--ink);
  min-width: 0;
}

/* A full-width line under the title. Muted, because it qualifies the title rather
   than competing with it ("Showing regulars", a phone number, an instruction). */
.page-head__sub {
  flex-basis: 100%;
  margin: -4px 0 0;
  font: 500 13px var(--font-body);
  color: var(--muted);
}

.page-head__actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

/* 36px circle around a 20px chevron: the same control on every screen that has a
   parent, and a real thumb target -- the bare "←" glyph it replaces was 18px tall. */
.page-head__back,
.settings-screen__back {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  margin-left: -8px;
  border-radius: 50%;
  color: var(--ink);
  text-decoration: none;
  flex: none;
}

.page-head__back,
.settings-screen__back { transition: background-color 160ms var(--ease-out), transform 120ms var(--ease-out); }

.page-head__back:hover,
.settings-screen__back:hover { background: var(--surface-2); }

.page-head__back:active,
.settings-screen__back:active { transform: scale(0.92); }

.page-head__back svg,
.settings-screen__back svg {
  width: 20px;
  height: 20px;
}

@media (max-width: 780px) {
  /* Actions stay beside the title when they fit and take a row of their own when they
     don't; when they do wrap, the row spans the width and the buttons share it. */
  .page-head__actions {
    flex: 1 0 auto;
    justify-content: flex-end;
  }
}

/* ---------- buttons ----------
 *
 * The one pair of header/inline buttons used across the owner app. Hero actions in a
 * modal or on the public page keep their own larger styles; these are the 13px/700
 * actions that sit next to a title or at the foot of a card.
 */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 40px;
  padding: 0 16px;
  border-radius: var(--r-md);
  font: 700 13px var(--font-body);
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  border: 1px solid transparent;
  transition: background-color 160ms var(--ease-out), border-color 160ms var(--ease-out),
              transform 120ms var(--ease-out);
}

.btn:active { transform: scale(0.97); }

.btn--primary {
  background: var(--ink);
  color: var(--bg);
}

.btn--primary:hover { background: var(--ink-2); }

.btn--secondary {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--border-2);
}

.btn--secondary:hover { background: var(--surface-2); }

/* The destructive one. Outlined, not filled: it sits in a row beside Edit and
   Deactivate and must be the last thing the eye lands on, not the first. */
.btn--danger {
  background: transparent;
  color: var(--error);
  border-color: var(--error);
}

.btn--danger:hover { background: var(--warn-soft); }

@media (max-width: 780px) {
  .btn { min-height: 44px; }
}

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

.q-stat {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  padding: 4px 10px;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--muted);
  font: 600 11px var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 1px;
  white-space: nowrap;
}

.q-stat b {
  font: 800 15px var(--font-head);
  letter-spacing: -0.3px;
  color: var(--ink);
}

.q-stat--seated {
  background: var(--warn-soft);
  border-color: transparent;
  color: var(--warn-ink);
}

.q-stat--seated b { color: var(--warn-ink); }

@media (max-width: 780px) {
  .q-stats {
    order: 3;
    flex-basis: 100%;
  }
}

/*
 * Two tiers. Whoever is in service is a card built around the Done buttons -- on a
 * phone that is the tap the owner makes most, so it is 52px tall and never shares a
 * line with anything. Everyone waiting is a compact row underneath.
 */
.q-tier {
  display: flex;
  justify-content: space-between;
  margin: 18px 0 8px;
  font: 600 10.5px var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 1.4px;
  color: var(--muted);
}

#queue-list > .q-tier:first-child {
  margin-top: 0;
}

.q-tier__n {
  font-weight: 600;
  color: var(--warn-ink);
}

/* One card per person in service. Stacked on a phone; side by side on a desktop so a
   two-staff shop is not scrolling past a full-width Done button. */
.q-heroes {
  display: grid;
  gap: 10px;
}

@media (min-width: 781px) {
  .q-heroes {
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  }
}

.q-hero {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 4px solid var(--warn);
  border-radius: var(--r-2xl);
  box-shadow: var(--shadow-md);
  padding: 18px 18px 12px;
  /* Entrance only, and only for a card that is genuinely new: the list is morphed
     (QueueController#refresh_queue, BroadcastOwnerQueue), so a card that was already
     seated keeps its node and never replays this. Short and subtle on purpose --
     Start is the owner's most frequent tap. */
  transition: opacity 180ms var(--ease-out), transform 180ms var(--ease-out);
}

@starting-style {
  .q-hero { opacity: 0; transform: scale(0.97); }
}

/* Reduced motion keeps the fade and drops the scale. The blanket rule at the top of
   this file would cut the fade to 0.01ms; this carve-out follows .pq-arrive. */
@media (prefers-reduced-motion: reduce) {
  .q-hero {
    transition-property: opacity;
    transition-duration: 120ms !important;
  }
  @starting-style {
    .q-hero { transform: none; }
  }
}

.q-hero__name {
  font: 800 24px var(--font-head);
  letter-spacing: -0.6px;
  color: var(--ink);
  overflow-wrap: anywhere;
}

.q-hero__svc {
  margin-top: 4px;
  font: 500 13.5px var(--font-body);
  color: var(--muted);
}

.q-hero__phone,
.q-next__phone {
  font: 600 12px var(--font-body);
  color: var(--success);
  text-decoration: none;
}

.q-hero__phone {
  display: inline-block;
  margin-top: 4px;
}

.q-hero__meta {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-top: 12px;
}

.q-hero__total {
  font: 800 20px var(--font-head);
  color: var(--ink);
}

/* Each button_to is a form; the forms are the grid items so Cash and UPI split the
   width and the single-payment Done (or Remove) can span it. */
.q-hero__actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-top: 14px;
}

.q-hero__actions form {
  margin: 0;
  min-width: 0;
}

.q-hero__actions .q-hero__wide {
  grid-column: 1 / -1;
}

.q-hero__btn {
  width: 100%;
  min-height: 52px;
  border: 0;
  border-radius: var(--r-lg);
  background: var(--brand);
  color: #fff;
  font: 700 15px var(--font-body);
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: background-color 160ms var(--ease-out), transform 120ms var(--ease-out);
}

.q-hero__btn:hover { background: var(--brand-dark); }
.q-hero__btn:active { transform: scale(0.97); }

/* The second payment tile. Darker so Cash and UPI are never misread for each other. */
.q-hero__btn--dark { background: var(--brand-dark); }
.q-hero__btn--dark:hover { background: var(--ink); }

/* Destructive but rare here, so it is text, and only turns red under the finger. */
.q-hero__remove {
  width: 100%;
  min-height: 40px;
  border: 0;
  background: none;
  color: var(--muted);
  font: 600 12.5px var(--font-body);
  cursor: pointer;
  transition: color 160ms var(--ease-out);
}

.q-hero__remove:hover,
.q-hero__remove:active { color: var(--error); }

.q-next {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}

.q-next__empty {
  margin: 0;
  padding: 24px;
  font: 400 15px var(--font-serif);
  font-style: italic;
  color: var(--muted);
}

.q-next__row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--border);
}

.q-next__row:last-child { border-bottom: 0; }

.q-next__pos {
  min-width: 22px;
  font: 800 16px var(--font-head);
  color: var(--muted-2);
  font-variant-numeric: tabular-nums;
}

.q-next__row.is-front .q-next__pos { color: var(--ink); }

.q-next__who {
  flex: 1;
  min-width: 120px;
}

.q-next__name {
  font: 700 14.5px var(--font-head);
  color: var(--ink);
}

.q-next__svc {
  margin-top: 1px;
  font: 500 12px var(--font-body);
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.q-next__actions {
  display: flex;
  align-items: center;
  margin-left: auto;
}

.q-next__actions form { margin: 0; }

/* Start is filled only at the front of the line; further back it is there for a
   second staffer, not the default next move. */
.q-next__btn {
  min-height: 40px;
  padding: 0 14px;
  border: 1.5px solid var(--border-2);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--ink);
  font: 700 13px var(--font-body);
  cursor: pointer;
  transition: background-color 160ms var(--ease-out), border-color 160ms var(--ease-out), transform 120ms var(--ease-out);
}

.q-next__btn:hover { background: var(--surface-2); }
.q-next__btn:active { transform: scale(0.97); }

.q-next__btn--primary {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--bg);
}

.q-next__btn--primary:hover { background: var(--ink-2); }

.q-next__remove {
  min-height: 40px;
  padding: 0 8px;
  border: 0;
  background: none;
  color: var(--muted-2);
  font: 600 12.5px var(--font-body);
  cursor: pointer;
  transition: color 160ms var(--ease-out);
}

.q-next__remove:hover,
.q-next__remove:active { color: var(--error); }

@media (prefers-reduced-motion: reduce) {
  .q-hero__btn, .q-hero__remove, .q-next__btn, .q-next__remove { transition: none; }
}

/* ---------- settings: rail + sheet ---------- */

/* One response, two layouts. The rail and the pane are both always in the DOM; the
   modifier on .settings-screen decides which the current width shows. Mobile is the
   base case (hub shows the rail, a section shows the pane) and the >780 layer turns
   both on at once -- the same breakpoint the app shell uses to drop the sidebar.

   A settings screen is ONE surface, not a stack of boxed cards. Each group inside it is
   closed by a full-bleed hairline instead of wrapped in its own border, which spends no
   width on nested edges and no height on the gaps between them. On a phone the surface
   runs edge to edge, so the fields get the whole screen rather than a box inside a box. */

.settings-screen { max-width: 900px; }

.settings-screen__head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
}

/* .settings-screen__back and __title take their rules from .page-head (queue section). */

/* ---- the sheet and its groups ---- */

/* A group is the unit that saves: one group, one form, at most one Save, and that Save
   governs exactly what sits between the group's heading and the hairline below it. The
   boundary is structural rather than decorative -- it is the only thing telling an owner
   which fields a Save covers -- which is why the rhythm is strict: generous padding
   inside a group, a hairline between groups, and the Save flush against that hairline. */

.settings-sheet {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
}

.settings-group { padding: 20px; }

.settings-group + .settings-group { border-top: 1px solid var(--border); }

.settings-group__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}

.settings-group__title {
  font: 700 15.5px var(--font-head);
  letter-spacing: -0.2px;
  color: var(--ink);
  margin: 0;
}

.settings-group__note {
  font: 500 12px var(--font-body);
  color: var(--muted-2);
  flex: none;
}

.settings-group__foot {
  display: flex;
  justify-content: flex-end;
  margin-top: 18px;
}

.settings-group__hint {
  font: 500 12px/1.6 var(--font-body);
  color: var(--muted);
  margin: 16px 0 0;
}

/*
 * The joining toggle. button_to renders a <button>, whose content model is phrasing
 * content -- hence spans throughout, no <div>, exactly as the dashboard card does it.
 * Full-bleed against the group padding so the tap target reaches the sheet edge.
 */
.settings-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  width: 100%;
  min-height: 44px;
  padding: 0;
  border: none;
  background: none;
  text-align: left;
  cursor: pointer;
}

.settings-toggle__text { min-width: 0; }

.settings-toggle__label {
  display: block;
  font: 700 14px var(--font-body);
  color: var(--ink);
}

.settings-toggle__hint {
  display: block;
  font: 500 12px/1.55 var(--font-body);
  color: var(--muted);
  margin-top: 3px;
}

/* ---- fields ---- */

.settings-field__label {
  display: block;
  font: 600 12.5px var(--font-body);
  color: var(--muted);
  margin-bottom: 7px;
}

/* A second label under a filled field needs air; the sibling selector saves every
   caller from carrying its own margin. */
.settings-field ~ .settings-field__label { margin-top: 18px; }

.settings-field {
  width: 100%;
  padding: 12px 14px;
  border: 1.5px solid var(--border-2);
  border-radius: var(--r-lg);
  font: 600 14px var(--font-body);
  color: var(--ink);
  background: var(--surface);
}

.settings-field:focus {
  outline: none;
  border-color: var(--brand);
}

/* A select carries the browser's own chrome; the padding above already fits it, but
   inherited line-height and a native height do not, so the height is pinned. */
select.settings-field { height: 46px; }

.settings-field--multiline {
  resize: vertical;
  line-height: 1.5;
  font-weight: 500;
}

/* Area and city side by side: the pair reads as one place, and stacking two short
   fields wastes a screen of scroll on a phone. Each half carries its own label, so the
   sibling rule for label spacing has to be restated on the row. */
.settings-field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.settings-field ~ .settings-field-row,
.settings-field-row ~ .settings-field__label { margin-top: 18px; }

/* ---- Dashboard nudge (one row pointing at a Settings task) ---- */

.dash-nudge {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
  padding: 12px 16px;
  border: 1px solid var(--brand-tint);
  border-radius: var(--r-xl);
  background: var(--brand-soft);
  color: var(--brand);
  text-decoration: none;
}
.dash-nudge svg { flex: none; }

.dash-nudge__text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.dash-nudge__text strong { font: 700 14px var(--font-body); color: var(--ink); }
.dash-nudge__text span { font: 500 12px var(--font-body); color: var(--muted); }
.dash-nudge__go { font: 700 16px var(--font-head); flex: none; }

/* ---- "Find your shop on Google" (businesses/_places_pin) ---- */

/* The search box is a settings-field with a glass in it: same border, same radius,
   so it reads as one more field in the sheet and not as a widget dropped on it. */
.places-pin__search {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 14px;
  border: 1.5px solid var(--border-2);
  border-radius: var(--r-lg);
  background: var(--surface);
  color: var(--muted);
}
.places-pin__search:focus-within { border-color: var(--brand); color: var(--brand); }
.places-pin__search svg { flex: none; }

.places-pin__input {
  flex: 1;
  min-width: 0;
  padding: 12px 0;
  border: none;
  background: none;
  font: 600 14px var(--font-body);
  color: var(--ink);
  outline: none;
}
.places-pin__input::-webkit-search-cancel-button { -webkit-appearance: none; }

/* Suggestions sit in the flow under the box rather than floating over the fields:
   on a phone the keyboard already covers half the screen, and a popover under it is
   the one place a list cannot be scrolled to. */
.places-pin__list {
  margin-top: 6px;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  overflow: hidden;
}
.places-pin__list[hidden] { display: none; }

.places-pin__suggestion {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  padding: 11px 14px;
  border: none;
  border-top: 1px solid var(--border);
  background: none;
  text-align: left;
  cursor: pointer;
}
.places-pin__suggestion:first-child { border-top: none; }
.places-pin__suggestion:hover,
.places-pin__suggestion:focus-visible { background: var(--brand-soft); outline: none; }
.places-pin__suggestion strong { font: 700 14px var(--font-body); color: var(--ink); }
.places-pin__suggestion span { font: 500 12px var(--font-body); color: var(--muted); }

.places-pin__status {
  font: 500 12px/1.5 var(--font-body);
  color: var(--muted);
  margin: 6px 0 0;
  min-height: 0;
}
.places-pin__status:empty { display: none; }

/* The static map: a 2:1 image at the sheet's width, corners matching the fields.
   The caption under it says which of the two pins this is (Google's or ours). */
.places-pin__preview {
  margin: 14px 0 0;
}
.places-pin__preview[hidden] { display: none; }

.places-pin__map {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 2 / 1;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--brand-soft);
  object-fit: cover;
}

.places-pin__caption {
  font: 500 12px/1.5 var(--font-body);
  color: var(--muted);
  margin-top: 6px;
}

/* The pin block sits between fields; give it the same air a field's label gets on
   both sides (the sibling rules above only see direct .settings-field neighbours). */
.settings-field ~ .places-pin { margin-top: 18px; }
.places-pin ~ .settings-field-row { margin-top: 18px; }

/* Read-only values are set as text, not as a filled box: the label above already says
   what they are, and a second fill inside the sheet only adds an edge to look at. */
.settings-readonly {
  font: 700 13.5px var(--font-head);
  color: var(--muted);
  word-break: break-all;
}

.settings-save {
  padding: 11px 18px;
  background: var(--ink);
  color: var(--bg);
  border: none;
  border-radius: var(--r-lg);
  font: 700 13px var(--font-head);
  cursor: pointer;
}

/* dirty-form greys this out until a field differs from what was rendered. Without JS the
   button is simply always live, which is the behaviour this screen had before. */
.settings-save:disabled {
  background: var(--surface-2);
  color: var(--muted-2);
  cursor: default;
}

/* ---- the photo uploader ---- */

/* With photos above it the uploader is just an action, so it is a plain button. With
   none it is the whole group's content, so the invitation and the control are one
   target rather than a dashed sales pitch stacked on a button that repeats it. */
.settings-upload {
  display: block;
  padding: 13px;
  text-align: center;
  background: var(--surface);
  border: 1.5px solid var(--border-2);
  border-radius: var(--r-lg);
  font: 700 13.5px var(--font-head);
  color: var(--ink);
  cursor: pointer;
}

.settings-upload--empty {
  padding: 26px 20px;
  border-style: dashed;
}

.settings-upload--empty .settings-upload__hint {
  display: block;
  max-width: 34ch;
  margin: 6px auto 0;
  font: 500 12.5px/1.55 var(--font-body);
  color: var(--muted);
}

/* "2 of 5" beside the label once there are photos: the limit is visible before it bites. */
.settings-upload__count {
  margin-left: 8px;
  font: 500 12px var(--font-mono);
  color: var(--muted);
}

.settings-upload:hover { border-color: var(--brand); }

.settings-upload__status {
  text-align: center;
  margin-top: 8px;
  font: 500 12px var(--font-body);
  color: var(--muted);
}

/* ---- plain lists inside a group (team members) ---- */

.settings-list__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 0;
}

.settings-list__row:first-child { padding-top: 0; }

.settings-list__row + .settings-list__row { border-top: 1px solid var(--border); }

.settings-list__name {
  font: 700 13.5px var(--font-body);
  color: var(--ink);
}

.settings-list__meta {
  font: 500 11.5px var(--font-body);
  color: var(--muted);
  margin-top: 2px;
}

.settings-list__owner {
  font: 600 10px var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--brand);
}

/* Closes the list the way a Save closes a form: a hairline, then the action. */
.settings-list__add {
  display: block;
  width: 100%;
  margin-top: 4px;
  padding: 14px 0 2px;
  border: none;
  border-top: 1px solid var(--border);
  background: none;
  text-align: left;
  font: 700 13.5px var(--font-body);
  color: var(--brand);
  cursor: pointer;
}

.settings-list__remove {
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: transparent;
  border: none;
  color: var(--muted-2);
  font: 600 14px var(--font-body);
  cursor: pointer;
}

.settings-list__remove:hover { color: var(--error); }

/* ---- the rail ---- */

/* At phone widths the rail IS the hub screen, so it is a grouped list: hairlines between
   rows, no border per row and no gaps to fall down. The >780 layer turns it back into a
   nav column beside the pane. */

.settings-rail {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
}

.settings-rail__row {
  display: flex;
  align-items: center;
  gap: 13px;
  width: 100%;
  padding: 15px 16px;
  border: none;
  background: none;
  color: var(--ink);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  font: inherit;
}

.settings-rail__row + .settings-rail__row { border-top: 1px solid var(--border); }

.settings-rail__row:active { background: var(--surface-2); }

.settings-rail__icon {
  display: flex;
  flex: none;
  color: var(--muted);
}

.settings-rail__row[aria-current="page"] .settings-rail__icon { color: var(--brand); }

.settings-rail__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}

.settings-rail__label {
  font: 700 13.5px var(--font-body);
  color: var(--ink);
}

.settings-rail__summary {
  font: 500 11.5px var(--font-body);
  color: var(--muted);
}

.settings-rail__chevron {
  flex: none;
  color: var(--muted-3);
  display: flex;
}

/* Log out is not a settings section, so it gets a break rather than another hairline:
   a band of page colour across the list, which reads as a new group without opening a
   second bordered box for one row. */
.settings-rail__divider {
  height: 8px;
  background: var(--bg);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

/* button_to wraps its button in a form; the form must not break the rail's flow. */
.settings-rail form { margin: 0; }

.settings-rail__row--logout .settings-rail__label { color: var(--ink); }

.settings-pane__placeholder {
  display: flex;
  flex-direction: column;
  min-height: 320px;
  padding: 20px;
  border-radius: var(--r-xl);
  background: var(--surface);
}

/* A placeholder bar where content is still on its way. The pulse is opacity only, and the
   reduced-motion rule above stops it after one beat. */
.skeleton {
  display: block;
  border-radius: var(--r-md);
  background: var(--surface-2);
  animation: skeletonPulse 1.4s ease-in-out infinite;
}
.skeleton--title { width: 40%; height: 20px; margin-bottom: 20px; }
.skeleton--label { width: 28%; height: 11px; margin: 14px 0 8px; }
.skeleton--field { width: 100%; height: 44px; }
@keyframes skeletonPulse { 50% { opacity: 0.5; } }

/* ---- mobile: one or the other, never both ---- */

.settings-screen--hub .settings-pane { display: none; }
.settings-screen--section .settings-rail { display: none; }
.settings-screen__back--up { display: none; }
.settings-screen--section .settings-screen__back--out { display: none; }
.settings-screen--section .settings-screen__back--up { display: flex; }

@media (max-width: 780px) {
  /* Edge to edge. .app-content pads 16px on a phone; cancelling it here is what turns
     the sheet from a box floating on the page into the screen itself. */
  .settings-sheet,
  .settings-rail {
    margin-inline: -16px;
    border-inline: none;
    border-radius: 0;
  }

  .settings-group { padding: 18px 16px; }
}

@media (min-width: 781px) {
  .settings-screen__body {
    display: flex;
    align-items: flex-start;
    gap: 32px;
  }

  /* Matches .app-sidebar's width so the two nav columns line up down the page. */
  .settings-rail {
    width: 216px;
    flex: none;
    position: sticky;
    top: calc(var(--topbar-h) + 24px);
    background: none;
    border: none;
  }

  .settings-pane {
    flex: 1;
    min-width: 0;
    max-width: 620px;
  }

  .settings-group { padding: 24px 26px; }

  /* Both panels show at this width, so neither mobile hide applies... */
  .settings-screen--hub .settings-pane,
  .settings-screen--section .settings-rail { display: block; }

  /* ...and back always means "leave settings", because the rail handles the rest. */
  .settings-screen--section .settings-screen__back--out { display: flex; }
  .settings-screen--section .settings-screen__back--up { display: none; }

  /* The rail is a nav here, not a list of destinations: chevrons, hairlines and the
     summary line are list-affordances that only make sense when the row IS the screen. */
  .settings-rail__chevron { display: none; }
  .settings-rail__summary { display: none; }
  .settings-rail__row + .settings-rail__row { border-top: none; }

  .settings-rail__row {
    padding: 10px 12px;
    border-radius: var(--r-lg);
    margin-bottom: 2px;
  }

  .settings-rail__row:hover { background: var(--surface-2); }

  .settings-rail__row[aria-current="page"] { background: var(--brand-soft); }

  /* Only meaningful beside a pane: it says "this row is what you're looking at". At phone
     widths the rail is a list of destinations and there is no pane to belong to. */
  .settings-rail__row--default { background: var(--brand-soft); }

  .settings-rail__row--default .settings-rail__icon { color: var(--brand); }

  .settings-rail__label { font-size: 13px; }

  .settings-rail__divider {
    height: 1px;
    background: var(--border);
    border: none;
    margin: 14px 12px;
  }
}


/* ---------- mobile ---------- */

@media (max-width: 780px) {
  .app-shell {
    display: block;
    max-width: none;
  }

  .app-sidebar {
    display: none;
  }

  .app-content {
    /* Clear the fixed bottom nav (and the FAB sitting above it). */
    padding: 16px 16px calc(var(--bottom-nav-h) + var(--fab-size) + var(--safe-b));
  }

  .app-topbar {
    padding: 12px 16px;
  }

  /* Reclaim width for the business name; the date is low value on a phone. */
  .topbar-date {
    display: none;
  }

  .gk-grid-home,
  .gk-grid-money {
    grid-template-columns: 1fr;
  }

  /*
   * Height is stated rather than left to the content: --bottom-nav-h is what the rest
   * of the file reserves clearance for (.app-content padding, the FAB offset), so the
   * bar has to actually be that tall or the two drift apart. border-box means the
   * safe-area padding has to be added to the height, not absorbed by it.
   */
  .bottom-nav {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 55;
    height: calc(var(--bottom-nav-h) + var(--safe-b));
    background: var(--surface);
    border-top: 1px solid var(--border);
    padding-bottom: var(--safe-b);
  }

  .fab {
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    right: 18px;
    bottom: calc(var(--bottom-nav-h) + 14px + var(--safe-b));
    width: var(--fab-size);
    height: var(--fab-size);
    border-radius: 50%;
    background: var(--ink);
    color: var(--bg);
    box-shadow: var(--shadow-brand);
    font-size: 26px;
    line-height: 1;
    text-decoration: none;
    z-index: 56;
  }

  /*
   * iOS Safari zooms the whole page when focusing an input under 16px. Every input
   * in the app is currently 13.5-15px except sessions/new, and they all set size via
   * the `font:` shorthand inline -- which beats a plain class rule, hence !important.
   * Opt a deliberately-large input out with data-keep-font (e.g. the OTP field).
   */
  input:not([type="checkbox"]):not([type="radio"]):not([data-keep-font]),
  select:not([data-keep-font]),
  textarea:not([data-keep-font]) {
    font-size: 16px !important;
  }
}

/* ---------- money book ----------
 *
 * SCOPE: .mb-* only, used by app/views/money/show.html.erb and nothing else.
 *
 * The screen is one day of a ledger. Its signature is .mb-run: every row carries the
 * day's running total down a ruled right margin, the way a paper money book does, so
 * the shape of the day (a fat morning, a dead afternoon) is readable and not just the
 * total. That column survives on the narrowest phone -- it is the reason for the screen.
 */

.mb-datebar {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-2xl);
  padding: 12px;
  box-shadow: var(--shadow-sm);
  margin-bottom: 16px;
}

.mb-nav {
  width: 44px;
  height: 44px;
  flex: none;
  display: grid;
  place-items: center;
  border: 1px solid var(--border-2);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink);
  font: 500 18px/1 var(--font-body);
  text-decoration: none;
  transition: background-color 160ms var(--ease-out), border-color 160ms var(--ease-out), transform 120ms var(--ease-out);
}

.mb-nav:hover { background: var(--surface-2); border-color: var(--ink); }
.mb-nav:active { transform: scale(0.94); }

/* A missing next-day link still occupies the slot, so the date never shifts sideways. */
.mb-nav--off { opacity: 0.28; pointer-events: none; }

/* Go rides beside the next-day arrow rather than taking a grid column of its own --
   date_jump_controller hides it the moment JS is up, and an empty column would leave
   a permanent gap for everyone else. */
.mb-datebar-end { display: flex; align-items: center; gap: 8px; }
.mb-nav--go { width: auto; padding: 0 14px; font: 600 12.5px var(--font-body); }

.mb-pick {
  position: relative;
  text-align: center;
  min-width: 0;
  padding: 4px 8px;
  border-radius: var(--r-lg);
  margin: 0;
}

.mb-pick:hover { background: var(--surface-2); }

/* The weekday leads: a shopkeeper reasons in "Sundays are dead", not in dates. */
.mb-weekday {
  display: block;
  font: 800 clamp(21px, 5.4vw, 27px)/1 var(--font-head);
  letter-spacing: -0.9px;
  color: var(--ink);
}

.mb-fulldate {
  display: block;
  margin-top: 6px;
  font: 500 11.5px/1 var(--font-mono);
  letter-spacing: 0.9px;
  text-transform: uppercase;
  color: var(--muted);
}

.mb-fulldate em { font-style: normal; color: var(--brand); font-weight: 700; }

/* The real control, laid transparently over the label so the whole block is the target. */
.mb-pick input[type="date"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}

/* align-content, not the grid default of stretch: the right column's cards are shorter
   than the ledger beside them and would otherwise grow to match it, leaving the week
   card half empty. */
.mb-col { display: grid; gap: 16px; align-content: start; }

.mb-label {
  font: 600 10.5px/1 var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 1.4px;
  color: var(--muted);
}

/* ---- the day ---- */

.mb-hero {
  background: var(--ink);
  color: var(--bg);
  border-radius: var(--r-2xl);
  padding: 24px;
}

.mb-hero .mb-label { color: var(--brand-tint); }

.mb-amount {
  font: 800 clamp(40px, 10vw, 52px)/1 var(--font-head);
  letter-spacing: -2.2px;
  margin-top: 12px;
  font-variant-numeric: tabular-nums;
}

.mb-amount span { font-size: 0.62em; letter-spacing: -0.5px; margin-right: 2px; opacity: 0.75; }

.mb-sub { margin-top: 10px; font: 500 13px var(--font-body); color: rgba(250, 250, 247, 0.7); }

.mb-rule {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid rgba(250, 250, 247, 0.15);
}

.mb-mixbar {
  display: flex;
  height: 8px;
  border-radius: 4px;
  overflow: hidden;
  margin: 9px 0;
  background: rgba(250, 250, 247, 0.15);
}

.mb-mixbar i { display: block; }
.mb-mixbar .upi { background: var(--brand-tint); }
.mb-mixbar .cash { background: rgba(250, 250, 247, 0.32); }

.mb-legend {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font: 600 11.5px var(--font-body);
  color: rgba(250, 250, 247, 0.78);
  font-variant-numeric: tabular-nums;
}

.mb-compare {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  font: 500 12.5px/1.55 var(--font-body);
  color: rgba(250, 250, 247, 0.82);
}

.mb-delta { font: 700 12.5px var(--font-mono); font-variant-numeric: tabular-nums; }
.mb-delta--up { color: var(--brand-tint); }
.mb-delta--down { color: #f0c48a; }
.mb-delta--flat { color: rgba(250, 250, 247, 0.6); }

/* ---- the wider picture ---- */

.mb-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: 20px;
  box-shadow: var(--shadow-sm);
}

.mb-accrow { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }

.mb-fig {
  font: 800 26px/1 var(--font-head);
  letter-spacing: -1px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.mb-note { font: 500 11.5px var(--font-mono); color: var(--muted-2); letter-spacing: 0.4px; }

.mb-week { display: flex; align-items: flex-end; gap: 6px; margin: 18px 0 2px; }

/* Each bar is a link to that day; the tall flex column keeps the tap target thumb-sized
   even when the bar itself is 3px tall. */
.mb-day {
  flex: 1;
  min-width: 0;
  height: 76px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  gap: 7px;
  text-decoration: none;
}

.mb-day i {
  display: block;
  width: 100%;
  min-height: 3px;
  border-radius: 3px 3px 2px 2px;
  background: rgba(17, 94, 89, 0.24);
  transition: background 0.18s;
}

.mb-day:hover i { background: rgba(17, 94, 89, 0.45); }
.mb-day b { font: 600 10px/1 var(--font-mono); color: var(--muted-2); letter-spacing: 0.5px; }
.mb-day--sel i { background: var(--ink); }
.mb-day--sel b { color: var(--ink); font-weight: 700; }

/* Days that have not happened yet read as ruled-off, not as zero takings. The floor
   height beats the inline height so the hatch is legible as "not yet" rather than
   looking like a 3px rendering artefact. */
.mb-day--future i {
  min-height: 12px;
  background: repeating-linear-gradient(45deg, var(--surface-3), var(--surface-3) 3px, transparent 3px, transparent 6px);
}

.mb-monthrow {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.mb-monthfig {
  font: 700 18px/1 var(--font-head);
  letter-spacing: -0.6px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* ---- the ledger ---- */

.mb-ledger {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

.mb-ledger-head,
.mb-entry {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr) 84px 78px;
  align-items: center;
  gap: 12px;
  padding: 13px 20px;
}

.mb-ledger-head {
  align-items: end;
  padding: 15px 20px 11px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
}

.mb-ledger-head .mb-label:nth-child(3) { text-align: right; }

.mb-ledger-head .mb-label:nth-child(4) {
  text-align: right;
  color: var(--muted-2);
  border-left: 1px solid var(--border-2);
  padding-left: 12px;
}

.mb-entry { border-bottom: 1px solid var(--border); }
.mb-entry:last-child { border-bottom: none; }
.mb-entry:hover { background: #fcfcfa; }

.mb-t {
  font: 500 11.5px var(--font-mono);
  color: var(--muted);
  letter-spacing: -0.2px;
  font-variant-numeric: tabular-nums;
}

.mb-who { min-width: 0; }

/* The name holds one line and truncates. Wrapping it breaks the row rhythm that makes
   a column of entries scannable, and a name split across two lines reads worse than a
   name with an ellipsis. Services below it may wrap freely. */
.mb-who b {
  display: block;
  font: 600 13.5px/1.3 var(--font-body);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mb-who span { display: block; margin-top: 3px; font: 400 12px/1.4 var(--font-body); color: var(--muted); }

.mb-pay {
  display: inline-block;
  margin-left: 2px;
  padding: 1px 7px;
  border-radius: var(--r-pill);
  font: 600 9.5px var(--font-mono);
  letter-spacing: 0.7px;
  text-transform: uppercase;
  font-style: normal;
  vertical-align: 1px;
}

.mb-pay--upi { background: var(--brand-soft); color: var(--brand); }
.mb-pay--cash { background: var(--surface-3); color: var(--ink-3); }

.mb-amt {
  text-align: right;
  font: 700 14.5px var(--font-head);
  letter-spacing: -0.3px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* The ledger margin. One hairline and a carried sum -- the last row's figure IS the
   day total, so the page closes its own loop. */
.mb-run {
  align-self: stretch;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding-left: 12px;
  border-left: 1px solid var(--border);
  font: 500 12.5px var(--font-mono);
  letter-spacing: -0.3px;
  color: var(--muted-2);
  font-variant-numeric: tabular-nums;
}

.mb-entry:last-child .mb-run { color: var(--ink); font-weight: 700; }

.mb-empty { padding: 44px 24px; text-align: center; }
.mb-empty p { margin: 0; font: 400 20px/1.4 var(--font-serif); font-style: italic; color: var(--muted); }
.mb-empty small { display: block; margin-top: 12px; font: 500 12.5px var(--font-body); color: var(--muted-2); }

/* ---- what sold ---- */

.mb-svc + .mb-svc { margin-top: 13px; }

.mb-svc-top {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
  font: 600 12.5px var(--font-body);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.mb-svc-bar { height: 6px; background: var(--surface-2); border-radius: 3px; overflow: hidden; }
.mb-svc-bar i { display: block; height: 100%; background: var(--brand); border-radius: 3px; }

.mb-quiet { margin: 0; font: 400 15px/1.5 var(--font-serif); font-style: italic; color: var(--muted); }

@media (max-width: 780px) {
  .mb-datebar { padding: 10px; gap: 8px; }

  .mb-hero { padding: 20px; }

  /* The four columns hold on a phone -- collapsing them into stacked rows would break
     the alignment that makes a ledger scannable. Only the gutters give way. */
  .mb-ledger-head,
  .mb-entry {
    grid-template-columns: 44px minmax(0, 1fr) 66px 58px;
    gap: 8px;
    padding-left: 14px;
    padding-right: 14px;
  }

  .mb-ledger-head .mb-label:nth-child(4),
  .mb-run { padding-left: 8px; }

  .mb-who b { font-size: 13px; }
  .mb-who span { font-size: 11.5px; }
  .mb-amt { font-size: 13.5px; }
  .mb-run { font-size: 11.5px; }
  .mb-t { font-size: 10.5px; }
  .mb-legend { font-size: 11px; }
  .mb-empty { padding: 34px 18px; }
}

/* Small phones (<=360px hardware). Every fixed column gives up a few pixels so the
   name column keeps enough room for a full name -- "Mahesh Patil" truncating to
   "Mahesh P..." is the failure this tier exists to prevent. Must stay below the 780
   block: same specificity, so source order decides. */
@media (max-width: 400px) {
  .mb-ledger-head,
  .mb-entry {
    grid-template-columns: 38px minmax(0, 1fr) 54px 54px;
    gap: 6px;
    padding-left: 12px;
    padding-right: 12px;
  }

  .mb-ledger-head .mb-label:nth-child(4),
  .mb-run { padding-left: 6px; }

  /* "RUNNING" at the tracked-out label size is wider than its own column here, and
     .mb-ledger clips it without a scrollbar to say so. */
  .mb-ledger-head .mb-label { font-size: 8.5px; letter-spacing: 0.4px; }

  .mb-pay { margin-left: 0; }
}

/* ---------- marketing: home page and for-business page ----------
 *
 * SCOPE: .mkt-* only, used by app/views/pages/business.html.erb (the pitch to owners)
 * and the legal pages, and nothing else. The home page (app/views/home/) has its own
 * sheet, home.css, and its own `dir-*` classes; it shares only the wordmark, the
 * footer and the tokens with this section.
 *
 * BREAKPOINTS: unlike the app shell above (which is max-width:780 corrective), this
 * section is mobile-first with additive min-width layers at 600 / 900 / 1120 / 1440.
 * 780 is deliberately avoided so the two systems can never both apply on one band.
 * Do not add a max-width query below this line.
 */

.mkt-page {
  background: var(--bg);
  /* The directory's neutrals (home.css, .dir-page), re-bound over the product
     tokens for the whole marketing system: nothing here is cut from the teal
     any more, so the brand colour is the only colour on the page and the
     glyphs get a quiet ground to sit on. Not --brand and its tints: those stay
     the brand's. */
  --ink: #222222;
  --ink-2: #222222;
  --ink-3: #444444;
  --muted: #6a6a6a;
  --muted-2: #9a9a9a;
  --surface-2: #f7f7f7;
  --surface-3: #efefef;
  --border: #ebebeb;
  --border-2: #dddddd;
  --border-strong: #222222;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 8px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 6px 20px rgba(0, 0, 0, 0.1), 0 2px 6px rgba(0, 0, 0, 0.04);
  --shadow-lg: 0 24px 56px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.05);
  /* The sticky header's height: the directory's 80px bar. */
  --mkt-header-h: 80px;

  /* Fluid scale, 375px -> 1440px viewport. No font-size media queries anywhere. */
  --mkt-h1: clamp(1.75rem, 1.05rem + 3vw, 3.25rem);
  --mkt-h2: clamp(1.375rem, 1.1rem + 1.2vw, 2rem);
  --mkt-h3: clamp(1.0625rem, 1rem + 0.3vw, 1.25rem);
  --mkt-lead: clamp(1.0625rem, 0.98rem + 0.6vw, 1.375rem);
  --mkt-body: 0.9375rem;
  --mkt-small: 0.8125rem;

  --mkt-lh-tight: 1.15;
  --mkt-lh-head: 1.25;
  --mkt-lh-body: 1.65;

  /* One knob for section rhythm instead of five hand-tuned paddings. */
  --mkt-section-y: clamp(3rem, 1.8rem + 5vw, 6.5rem);
  --mkt-measure: 62ch;
}

.mkt-container {
  width: 100%;
  max-width: 1080px;
  margin-inline: auto;
  padding-inline: 18px;
}

/* The header is sticky (~70px), so an in-page jump to #faq would land
   with the heading underneath it. Clear it. */
.mkt-page [id] {
  scroll-margin-top: 96px;
}

.mkt-container--narrow {
  max-width: 720px;
}

.mkt-hero,
.mkt-instead,
.mkt-steps,
.mkt-benefits,
.mkt-audience,
.mkt-faq,
.mkt-closing {
  padding-block: var(--mkt-section-y);
}

/* ---------- marketing: shared components ---------- */

/* The directory's .dir-btn: a pill, brand-filled, in the body face. */
.mkt-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  padding: 12px 22px;
  border: 1.5px solid transparent;
  border-radius: var(--r-pill);
  font-family: var(--font-body);
  font-size: 0.875rem;
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: transform 160ms var(--ease-out), box-shadow 200ms var(--ease-out),
              background-color 160ms var(--ease-out), border-color 160ms var(--ease-out);
}

/* Press feedback for every variant. The lift on hover below is pointer-only. */
.mkt-btn:active {
  transform: scale(0.97);
  box-shadow: var(--shadow-sm);
}

/* Brand, like the directory's search button and its "Book your spot": the
   one coloured thing on a white page is the thing to press. */
.mkt-btn--primary {
  background: var(--brand);
  color: #fff;
}

.mkt-btn--primary:hover {
  background: var(--brand-dark);
}

@media (hover: hover) and (pointer: fine) {
  .mkt-btn--primary:hover {
    transform: translateY(-1px);
    box-shadow: var(--shadow-md);
  }
}

.mkt-btn--secondary {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--border-2);
}

.mkt-btn--secondary:hover {
  background: var(--surface-2);
  border-color: var(--ink);
}

/* For the dark closing band, where --ink on --brand-dark would vanish. */
.mkt-btn--invert {
  background: #fff;
  color: var(--brand-dark);
  box-shadow: var(--shadow-sm);
}

.mkt-btn--invert:hover {
  background: var(--brand-tint);
}

@media (hover: hover) and (pointer: fine) {
  .mkt-btn--invert:hover { transform: translateY(-1px); }
}

.mkt-btn--lg {
  min-height: 52px;
  padding: 16px 28px;
  font-size: 0.9375rem;
}

.mkt-btn--full {
  display: flex;
  width: 100%;
}

.mkt-icon {
  display: block;
  flex: none;
  color: var(--brand);
}

/* Inside a button the icon must track the label colour, not the brand. */
.mkt-btn .mkt-icon {
  color: currentColor;
}

/* The flat-fill marks (MarketingGlyphHelper, and the category marks the hero
   card borrows). The same lift off the page as .dir-glyph on the home page. */
.mkt-glyph {
  display: block;
  flex: none;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.14));
}

/* A mark on the directory card's no-photo ground: the soft grey gradient a
   shop without a picture gets, cut down to a tile. Step and benefit cards lead
   with one. */
.mkt-tile {
  display: grid;
  place-items: center;
  width: 60px;
  height: 60px;
  flex: none;
  border-radius: var(--r-2xl);
  background:
    radial-gradient(120% 90% at 80% 0%, #fff 0, transparent 55%),
    linear-gradient(160deg, #fafafa, var(--surface-3));
  border: 1px solid var(--border);
}

.mkt-section-title {
  font-family: var(--font-head);
  font-size: var(--mkt-h2);
  font-weight: 800;
  line-height: var(--mkt-lh-head);
  letter-spacing: -0.6px;
  color: var(--ink);
  margin: 0 0 10px;
  text-align: center;
}

.mkt-section-subhead {
  font-family: var(--font-body);
  font-size: var(--mkt-body);
  font-weight: 500;
  line-height: var(--mkt-lh-body);
  color: var(--muted);
  text-align: center;
  max-width: 52ch;
  margin: 0 auto 32px;
}

.mkt-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: 22px 20px;
}

/* ---------- marketing: header ---------- */

.mkt-header {
  position: sticky;
  top: 0;
  z-index: 20;
  border-bottom: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.88);
  backdrop-filter: saturate(180%) blur(10px);
  /* Named so the View Transition below holds it still while the page under it
     crossfades: the bar you just tapped in should not fade out and back. One
     header per page, so the name is never duplicated. */
  view-transition-name: mkt-header;
}

/* At the top of the page the header sits on the hero with no line under it;
   the rule fades in over the first 24px of scroll, so the moment content slides
   underneath is the moment the bar reads as a bar. Scroll-linked, so it has no
   duration to speak of and nothing to reduce; browsers without scroll timelines
   keep the always-on border from the rule above. */
@supports (animation-timeline: scroll()) {
  .mkt-header {
    border-bottom-color: transparent;
    animation: mktHeaderRule linear both;
    animation-timeline: scroll(root);
    animation-range: 0 24px;
  }
}

@keyframes mktHeaderRule {
  to { border-bottom-color: var(--border); }
}

/* ---------- page-to-page ----------
 *
 * Turbo wraps every page swap in document.startViewTransition (see the meta in
 * the layout). The browser default is a 250ms crossfade on `ease`; this is the
 * same crossfade, shorter and on the house curve. Old fades out first and
 * faster so two pages never read as overlapping. */
::view-transition-old(root) {
  animation: 120ms var(--ease-out) both mktPageOut;
}

::view-transition-new(root) {
  animation: 200ms var(--ease-out) both mktPageIn;
}

@keyframes mktPageOut { to { opacity: 0; } }
@keyframes mktPageIn { from { opacity: 0; } }

/* The owner app's chrome is named the same way .mkt-header is, so a tab switch
   crossfades the screen under it while the bar you just tapped stays put. One of
   each per page, and the phone-only pieces are display:none on desktop, so no
   name is ever on screen twice. */
.app-topbar   { view-transition-name: app-topbar; }
.app-sidebar  { view-transition-name: app-sidebar; }
.bottom-nav   { view-transition-name: bottom-nav; }
.fab          { view-transition-name: fab; }

/* While the crossfade runs, the incoming screen is already fading in; playing
   .screen-in on top of it would fade it in twice. Browsers without
   :active-view-transition keep the entrance as their transition. */
:root:active-view-transition .screen-in {
  animation: none;
}

@supports not (backdrop-filter: blur(10px)) {
  .mkt-header {
    background: var(--surface);
  }
}

.mkt-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: var(--mkt-header-h);
}

.mkt-header__actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* The directory's .dir-header__link: text in a pill that only shows on hover. */
.mkt-header__login {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  padding: 0 12px;
  white-space: nowrap;
  border-radius: var(--r-pill);
  border: 1.5px solid transparent;
  font-family: var(--font-body);
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  transition: border-color 160ms var(--ease-out), background-color 160ms var(--ease-out), transform 120ms var(--ease-out);
}

.mkt-header__login:active { transform: scale(0.97); }

.mkt-header__login:hover {
  background: var(--surface-2);
}

/* The global focus rule forces --r-sm; pill controls need their own radius back. */
.mkt-header__login:focus-visible {
  border-radius: var(--r-pill);
}

.mkt-header__cta {
  display: none;
}

/* Language switcher. A <details>, so open/close is the browser's job. */
.mkt-lang {
  position: relative;
}

.mkt-lang__current {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 44px;
  padding: 11px 12px;
  border-radius: var(--r-pill);
  font-family: var(--font-body);
  font-size: 0.875rem;
  font-weight: 700;
  color: var(--ink-3);
  cursor: pointer;
  list-style: none;
  transition: color 160ms var(--ease-out), background-color 160ms var(--ease-out);
}

.mkt-lang__caret {
  transition: transform 200ms var(--ease-out);
}

/* Safari draws its own disclosure triangle from this pseudo-element. */
.mkt-lang__current::-webkit-details-marker {
  display: none;
}

.mkt-lang__current:hover {
  color: var(--ink);
  background: var(--surface-2);
}

.mkt-lang__current:focus-visible {
  border-radius: var(--r-pill);
}

.mkt-lang[open] .mkt-lang__caret {
  transform: rotate(180deg);
}

.mkt-lang__menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 30;
  min-width: 168px;
  margin: 0;
  padding: 6px;
  list-style: none;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: 0 12px 28px rgb(0 0 0 / 12%);
  /* Grows out of the globe it hangs from, top-right, not from its own centre. */
  transform-origin: top right;
  transition: opacity 150ms var(--ease-out), transform 150ms var(--ease-out);

  @starting-style {
    opacity: 0;
    transform: scale(0.95);
  }
}

.mkt-lang__menu a {
  display: block;
  padding: 10px 12px;
  border-radius: var(--r-sm);
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  transition: background-color 120ms var(--ease-out);
}

.mkt-lang__menu a:hover {
  background: var(--surface-2);
}

.mkt-lang__menu a[aria-current="page"] {
  color: var(--brand);
  background: var(--brand-soft);
}

/* The mobile header already carries the logo and Log in, and hides the CTA.
   "Hinglish" plus both of those does not fit at 360px, so only the globe shows
   until the breakpoint that brings the CTA back. */
.mkt-lang__name {
  display: none;
}

/* Vertical padding (not inline-flex) to clear the 44px touch minimum -- it measured
   30px as a bare text link. Flex would make __dot a flex item and centre it, which
   lifts it off the wordmark's baseline. */
.mkt-logo {
  display: inline-block;
  flex: none;
  padding-block: 7px;
  white-space: nowrap;
  font-family: var(--font-head);
  font-size: 1.375rem;
  font-weight: 800;
  letter-spacing: -1px;
  color: var(--ink);
  text-decoration: none;
}

.mkt-logo--sm {
  font-size: 1.0625rem;
}

.mkt-logo__accent {
  color: var(--brand);
}

.mkt-logo__dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--brand);
  margin-left: 3px;
}

/* ---------- marketing: hero ---------- */

.mkt-hero {
  background: var(--bg);
  padding-block: clamp(2rem, 1rem + 4vw, 4.5rem) var(--mkt-section-y);
}

/* Copy, then the listing card under it; from 900px the two sit side by side
   with the copy ranged left, the way the directory's strapline is. */
.mkt-hero__inner {
  display: grid;
  gap: clamp(2rem, 1rem + 4vw, 4rem);
  align-items: center;
}

.mkt-hero__copy {
  text-align: center;
  max-width: 640px;
  margin-inline: auto;
}

.mkt-hero__eyebrow {
  font-family: var(--font-body);
  font-size: 0.78125rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--brand);
  margin: 0 0 14px;
}

.mkt-hero__title {
  font-family: var(--font-head);
  font-size: var(--mkt-h1);
  font-weight: 800;
  line-height: var(--mkt-lh-tight);
  letter-spacing: -1.5px;
  color: var(--ink);
  margin: 0;
  text-wrap: balance;
}

/* The second sentence, in the brand: "Stop waiting. Book your spot." colours
   its second half the same way on the home page. */
.mkt-hero__line {
  color: var(--brand);
}

/* Body face, upright. The serif italic these lines used to carry was hard to
   read at a glance, and this is the first sentence anyone reads. */
.mkt-hero__subhead {
  font-family: var(--font-body);
  font-size: var(--mkt-lead);
  font-weight: 400;
  line-height: 1.5;
  color: var(--muted);
  max-width: 56ch;
  margin: 16px auto 0;
}

.mkt-hero__cta {
  margin-top: 28px;
}

.mkt-hero__micro {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px 14px;
  margin: 18px 0 0;
  padding: 0;
  font-family: var(--font-body);
  font-size: var(--mkt-small);
  font-weight: 500;
  color: var(--muted);
}

.mkt-hero__micro li {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.mkt-hero__micro .mkt-icon {
  color: var(--success);
}

/* ---- the listing card ---- */

/* The directory's card (home.css, .dir-card), redrawn here because that sheet
   is the home page's alone: a square photo with the badge on it, then name,
   meta and the wait line. Hovers like a real card so it reads as the product,
   not a picture of it. */
.mkt-hero__figure {
  margin: 0;
  justify-self: center;
  width: min(100%, 300px);
}

.mkt-listing {
  border-radius: var(--r-3xl);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  padding: 10px 10px 16px;
  transition: transform 300ms var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .mkt-listing:hover { transform: translateY(-4px); }
}

.mkt-listing__photo {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: var(--r-2xl);
  background:
    radial-gradient(120% 90% at 80% 0%, #fff 0, transparent 55%),
    linear-gradient(160deg, #fafafa, var(--surface-3));
}

.mkt-listing__glyph {
  width: 34%;
  height: 34%;
}

.mkt-listing__badge {
  position: absolute;
  top: 12px;
  left: 12px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 10px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.94);
  box-shadow: var(--shadow-sm);
  font: 700 0.75rem/1 var(--font-body);
  color: var(--ink);
}

.mkt-listing__badge::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--success);
}

.mkt-listing__body {
  padding: 12px 6px 0;
  text-align: left;
}

.mkt-listing__name {
  font: 700 0.9375rem/1.3 var(--font-body);
  color: var(--ink);
}

.mkt-listing__meta {
  margin-top: 2px;
  font: 400 0.8125rem/1.4 var(--font-body);
  color: var(--muted);
}

.mkt-listing__line {
  margin-top: 3px;
  font: 500 0.8125rem/1.4 var(--font-body);
  color: var(--muted);
}

.mkt-listing__waiting {
  font-weight: 700;
  color: var(--ink);
}

.mkt-listing__action {
  font-weight: 700;
  color: var(--brand);
}

.mkt-hero__caption {
  margin: 14px 0 0;
  text-align: center;
  font: 500 0.8125rem var(--font-body);
  color: var(--muted-2);
}

/* ---------- marketing: find-or-claim band ----------
 *
 * Already-listed businesses claiming themselves is an acquisition channel, so
 * the search sits high on the page: straight after the three steps, whose
 * second step ends "already listed? claim it". */

.mkt-claim {
  background: var(--surface);
  border-block: 1px solid var(--border);
  padding-block: clamp(2.5rem, 1.5rem + 4vw, 5rem);
}

.mkt-claim__title {
  font-family: var(--font-head);
  font-size: clamp(1.1875rem, 1.05rem + 0.7vw, 1.5rem);
  font-weight: 800;
  line-height: var(--mkt-lh-head);
  letter-spacing: -0.4px;
  color: var(--ink);
  text-align: center;
  margin: 0 0 8px;
}

.mkt-claim__subhead {
  font-family: var(--font-body);
  font-size: var(--mkt-body);
  font-weight: 500;
  line-height: var(--mkt-lh-body);
  color: var(--muted);
  text-align: center;
  max-width: 52ch;
  margin: 0 auto 22px;
}

.mkt-claim__hint {
  font-family: var(--font-body);
  font-size: var(--mkt-small);
  font-weight: 500;
  color: var(--muted);
  text-align: center;
  margin: 14px 0 0;
}

/* ---------- marketing: section heads ----------
 *
 * Shared head furniture for #how, #benefits and #instead: a mono marker, the
 * title, and an optional serif narration line under it. */

.mkt-chapter__head {
  max-width: 720px;
  margin-inline: auto;
  text-align: center;
}

/* The directory band's marker (.dir-band__marker): body face, muted. */
.mkt-chapter__marker {
  font-family: var(--font-body);
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--muted);
  margin: 0 0 12px;
}

.mkt-chapter__title {
  font-family: var(--font-head);
  font-size: var(--mkt-h2);
  font-weight: 800;
  line-height: var(--mkt-lh-head);
  letter-spacing: -0.6px;
  color: var(--ink);
  margin: 0 0 12px;
  text-wrap: balance;
}

.mkt-chapter__narration {
  font-family: var(--font-body);
  font-size: var(--mkt-lead);
  font-weight: 400;
  line-height: 1.5;
  color: var(--muted);
  margin: 0;
}

/* ---------- marketing: motion primitives ----------
 *
 * No animation library. reveal_controller only adds classes:
 *   .is-armed     -> rewind to the start frame (set here, never in a bare rule)
 *   .is-revealed  -> transition forward
 * All easing and timing lives below. Because the start frame is applied ONLY
 * under .is-armed -- which JS adds -- a no-JS or reduced-motion visitor never
 * sees a hidden element. Never give these selectors an unguarded opacity: 0.
 */

/* Hero: the copy rises into place one line after another -- eyebrow, title,
   then the search or the subhead, then the action. The block used to move as
   one slab; a 60ms cascade reads as the page composing itself, and the whole
   thing is still settled inside 900ms. Seen once per visit, so it may take
   its time. */
.mkt-page [data-hero-copy].is-armed > * {
  opacity: 0;
  transform: translateY(12px);
}

.mkt-page [data-hero-copy].is-armed.is-revealed > * {
  opacity: 1;
  transform: none;
  transition: opacity 600ms var(--ease-out), transform 600ms var(--ease-out);
}

.mkt-page [data-hero-copy].is-armed.is-revealed > :nth-child(2) { transition-delay: 60ms; }
.mkt-page [data-hero-copy].is-armed.is-revealed > :nth-child(3) { transition-delay: 120ms; }
.mkt-page [data-hero-copy].is-armed.is-revealed > :nth-child(4) { transition-delay: 180ms; }
.mkt-page [data-hero-copy].is-armed.is-revealed > :nth-child(5) { transition-delay: 240ms; }
.mkt-page [data-hero-copy].is-armed.is-revealed > :nth-child(6) { transition-delay: 300ms; }

/* Scroll reveals. Without [data-reveal-stagger] the whole block moves as one;
   with it, the parent stays put and its children come in one after another. */
.mkt-page [data-reveal].is-armed:not([data-reveal-stagger]) {
  opacity: 0;
  transform: translateY(18px);
}

.mkt-page [data-reveal].is-armed.is-revealed:not([data-reveal-stagger]) {
  opacity: 1;
  transform: none;
  transition: opacity 500ms var(--ease-out), transform 500ms var(--ease-out);
}

.mkt-page [data-reveal-stagger].is-armed > * {
  opacity: 0;
  transform: translateY(18px);
}

.mkt-page [data-reveal-stagger].is-armed.is-revealed > * {
  opacity: 1;
  transform: none;
  transition: opacity 500ms var(--ease-out), transform 500ms var(--ease-out);
}

/* 80ms apart: enough to read as a cascade, not enough that the last card is still
   arriving when the visitor has read the first. Six covers every staggered group on
   the page (the widest is three). */
.mkt-page [data-reveal-stagger].is-armed.is-revealed > :nth-child(2) { transition-delay: 80ms; }
.mkt-page [data-reveal-stagger].is-armed.is-revealed > :nth-child(3) { transition-delay: 160ms; }
.mkt-page [data-reveal-stagger].is-armed.is-revealed > :nth-child(4) { transition-delay: 240ms; }
.mkt-page [data-reveal-stagger].is-armed.is-revealed > :nth-child(5) { transition-delay: 320ms; }
.mkt-page [data-reveal-stagger].is-armed.is-revealed > :nth-child(6) { transition-delay: 400ms; }

/* Sparklines draw themselves left to right. The markup carries pathLength="1",
   which normalises the dash units so no JS getTotalLength() call is needed. */
.mkt-page [data-draw].is-armed {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.mkt-page [data-draw].is-armed.is-drawn {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 1100ms var(--ease-in-out) 200ms;
}

/* ---------- marketing: instead of the notebook ----------
 *
 * The competitive alternative, side by side with Grahik. The "before" card is
 * dark with dotted figures (customers you can't see); the "after" card is the
 * same three questions with numbers in them. */

.mkt-instead {
  background: var(--surface-2);
}

.mkt-instead__pair {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
  max-width: 760px;
  margin: clamp(2rem, 1rem + 3vw, 3rem) auto 0;
}

.mkt-instead-card {
  border-radius: var(--r-3xl);
  padding: 24px 22px;
}

.mkt-instead-card--before {
  background: var(--brand-dark);
}

.mkt-instead-card--after {
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-md);
}

.mkt-instead-card__title {
  font-family: var(--font-head);
  font-size: 1rem;
  font-weight: 800;
  margin: 0 0 16px;
}

/* The card titles are a before/after pair, so they wear the same pill: muted
   on the dark "before" card, brand on the "with Grahik" card with a tick. */
.mkt-instead-card__tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: var(--r-pill);
  font-family: var(--font-body);
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.8px;
}

.mkt-instead-card--before .mkt-instead-card__tag {
  background: rgba(255, 255, 255, 0.12);
  color: rgba(255, 255, 255, 0.85);
}

.mkt-instead-card--after .mkt-instead-card__tag {
  background: var(--brand-soft);
  color: var(--brand);
}

.mkt-instead-card--after .mkt-instead-card__tag .mkt-icon {
  color: currentColor;
}

.mkt-instead-card__figs {
  display: block;
  width: 96px;
  height: auto;
  margin-bottom: 16px;
}

.mkt-instead-card--before .mkt-instead-card__figs {
  color: rgba(255, 255, 255, 0.7);
}

.mkt-instead-card--after .mkt-instead-card__figs {
  color: var(--brand);
}

.mkt-instead-card__rows {
  display: grid;
  gap: 10px;
  margin: 0;
}

.mkt-instead-card__rows div {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
}

.mkt-instead-card__rows dt {
  font-family: var(--font-body);
  font-size: 0.875rem;
  font-weight: 500;
}

.mkt-instead-card__rows dd {
  margin: 0;
  font-family: var(--font-head);
  font-size: 0.9375rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.mkt-instead-card--before .mkt-instead-card__rows dt {
  color: rgba(255, 255, 255, 0.7);
}

.mkt-instead-card--before .mkt-instead-card__rows dd {
  color: rgba(255, 255, 255, 0.5);
}

.mkt-instead-card--after .mkt-instead-card__rows dt {
  color: var(--muted);
}

.mkt-instead-card--after .mkt-instead-card__rows dd {
  color: var(--ink);
}

.mkt-instead-card__spark {
  width: 100%;
  height: 40px;
  margin-top: 16px;
}

/* ---------- marketing: how it works ---------- */

.mkt-steps__grid,
.mkt-features__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
}

/* ---------- marketing: join in three steps ----------
 *
 * An <ol> of three cards. Each leads with its mark on a tile -- the phone, the
 * shop front, the menu -- and the number sits on the tile's corner as a brand
 * disc, so "1, 2, 3" still reads from across the room. On desktop a dashed
 * rule runs between the cards so the row reads as a sequence, not three equal
 * features. */

.mkt-steps {
  background: var(--surface-2);
}

.mkt-steps__grid {
  list-style: none;
  margin: clamp(2rem, 1rem + 3vw, 3rem) 0 0;
  padding: 0;
}

.mkt-step {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-3xl);
  padding: 24px 22px;
}

.mkt-step__head {
  position: relative;
  width: fit-content;
  margin-bottom: 16px;
}

.mkt-step__num {
  position: absolute;
  top: -8px;
  right: -10px;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--brand);
  color: #fff;
  border: 2px solid var(--surface);
  font-family: var(--font-head);
  font-size: 0.875rem;
  font-weight: 800;
  line-height: 1;
}

.mkt-steps__cta {
  text-align: center;
  margin-top: clamp(1.75rem, 1rem + 2.5vw, 2.75rem);
}

.mkt-step__title {
  font-family: var(--font-head);
  font-size: var(--mkt-h3);
  font-weight: 800;
  line-height: 1.3;
  color: var(--ink);
  margin: 0 0 8px;
}

.mkt-step__body {
  font-family: var(--font-body);
  font-size: 0.875rem;
  font-weight: 400;
  line-height: var(--mkt-lh-body);
  color: var(--muted);
  margin: 0;
}

/* ---------- marketing: benefits ----------
 *
 * Six capability cards, each led by its mark on a tile. White, between the
 * white claim band and the grey #instead. */
.mkt-benefits {
  background: var(--bg);
}

.mkt-benefits .mkt-features__grid {
  margin-top: clamp(2rem, 1rem + 3vw, 3rem);
}

.mkt-feature {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-3xl);
  padding: 24px 22px;
  transition: box-shadow 200ms var(--ease-out), transform 200ms var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .mkt-feature:hover {
    box-shadow: var(--shadow-md);
    transform: translateY(-2px);
  }
}

.mkt-feature .mkt-tile {
  margin-bottom: 16px;
}

.mkt-feature__title {
  font-family: var(--font-head);
  font-size: var(--mkt-h3);
  font-weight: 800;
  line-height: 1.3;
  color: var(--ink);
  margin: 0 0 8px;
}

.mkt-feature__body {
  font-family: var(--font-body);
  font-size: 0.875rem;
  font-weight: 400;
  line-height: var(--mkt-lh-body);
  color: var(--muted);
  margin: 0;
}

/* ---------- marketing: audience ---------- */

.mkt-audience__chips {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin: 0;
  padding: 0;
}

/* Who it is NOT for, under the chips. Same face as the narration lines: an
   aside, not a feature. */
.mkt-audience__not-for {
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 400;
  color: var(--muted);
  text-align: center;
  margin: 28px 0 0;
}

/* The home page's category chips (.dir-chip): a mark, a label, a soft shadow. */
.mkt-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 44px;
  padding: 0 16px 0 12px;
  border-radius: var(--r-pill);
  background: var(--surface);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), inset 0 -1px 0 rgba(0, 0, 0, 0.06);
  font-family: var(--font-body);
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
}

/* ---------- marketing: directory search ----------
 *
 * The home page's search pill (.dir-pill), one field wide: a glass, the input,
 * and the brand button on the right end. */

.mkt-search {
  display: flex;
  align-items: center;
  gap: 6px;
  max-width: 560px;
  margin-inline: auto;
  padding: 6px 6px 6px 18px;
  border: 1px solid var(--border-2);
  border-radius: var(--r-pill);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  transition: box-shadow 200ms var(--ease-out), border-color 160ms var(--ease-out);
}

.mkt-search:hover {
  box-shadow: var(--shadow-md);
}

.mkt-search:focus-within {
  border-color: var(--ink);
  box-shadow: var(--shadow-md);
}

.mkt-search__glass {
  color: var(--muted-2);
}

.mkt-search__field {
  flex: 1 1 auto;
  /* Without this the input cannot shrink below its intrinsic ~180px min-content. */
  min-width: 0;
  padding: 12px 8px;
  border: 0;
  background: transparent;
  font-family: var(--font-body);
  /* 16px: anything smaller makes iOS zoom the page in on focus. */
  font-size: 1rem;
  font-weight: 600;
  color: var(--ink);
}

.mkt-search__field::placeholder {
  color: var(--muted);
  font-weight: 500;
}

.mkt-search__field:focus {
  outline: none;
}

.mkt-search__btn {
  flex: 0 0 auto;
  min-height: 44px;
  padding: 0 20px;
  background: var(--brand);
  color: #fff;
  border: 0;
  border-radius: var(--r-pill);
  font-family: var(--font-body);
  font-size: 0.875rem;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 160ms var(--ease-out), transform 120ms var(--ease-out);
}

.mkt-search__btn:active { transform: scale(0.97); }

.mkt-search__btn:hover {
  background: var(--brand-dark);
}

.mkt-search__btn:focus-visible {
  border-radius: var(--r-pill);
}

/* ---------- marketing: FAQ ---------- */

/* --bg, not --surface-2: the early-access band above is already --surface-2,
   and the section order alternates cream / white the whole way. */
.mkt-faq {
  background: var(--bg);
}

.mkt-faq__list {
  max-width: 720px;
  margin-inline: auto;
}

.mkt-faq__item {
  border-bottom: 1px solid var(--border-2);
}

.mkt-faq__q {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 18px 4px;
  cursor: pointer;
  list-style: none;
  font-family: var(--font-head);
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.4;
  color: var(--ink);
}

.mkt-faq__q::-webkit-details-marker {
  display: none;
}

.mkt-faq__q .mkt-icon {
  margin-left: auto;
  transition: transform 200ms var(--ease-out);
}

.mkt-faq__item[open] .mkt-faq__q .mkt-icon {
  transform: rotate(180deg);
}

.mkt-faq__item[open] .mkt-faq__a {
  animation: faqIn 200ms var(--ease-out);
}

@keyframes faqIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

.mkt-faq__a {
  margin: 0;
  padding: 0 4px 20px;
  max-width: var(--mkt-measure);
  font-family: var(--font-body);
  font-size: var(--mkt-body);
  font-weight: 400;
  line-height: var(--mkt-lh-body);
  color: var(--muted);
}

/* ---------- marketing: closing CTA ---------- */

.mkt-closing {
  background: var(--brand-dark);
  text-align: center;
}

/* Same mono marker as the section heads, in the marigold that reads on the
   dark band (--brand is invisible on --brand-dark). */
.mkt-closing__marker {
  font-family: var(--font-body);
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: #e8a04c;
  margin: 0 0 12px;
}

.mkt-closing__title {
  font-family: var(--font-head);
  font-size: var(--mkt-h2);
  font-weight: 800;
  line-height: var(--mkt-lh-head);
  letter-spacing: -0.6px;
  color: #fff;
  margin: 0 0 12px;
  text-wrap: balance;
}

.mkt-closing__body {
  font-family: var(--font-body);
  font-size: var(--mkt-lead);
  font-weight: 500;
  line-height: 1.45;
  color: var(--brand-tint);
  max-width: 40ch;
  margin: 0 auto 28px;
}

/* The global ring is --brand, which is nearly invisible on --brand-dark. */
.mkt-closing :focus-visible {
  outline-color: #fff;
}

/* ---------- marketing: footer ---------- */

.mkt-footer {
  padding-block: 28px;
  border-top: 1px solid var(--border);
}

.mkt-footer__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  text-align: center;
}

.mkt-footer__links {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px 20px;
  margin: 0;
  padding: 0;
}

/* inline-flex + min-height: bare 16px-tall text links miss the touch minimum. */
.mkt-footer__links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--font-body);
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ink-2);
  text-decoration: none;
  transition: color 160ms var(--ease-out);
}

.mkt-footer__links a:hover {
  color: var(--brand);
  text-decoration: underline;
}

/* --muted, not --muted-2: #9b9b9b on --bg is 2.66:1 and fails WCAG AA. */
.mkt-footer__copy {
  font-family: var(--font-body);
  font-size: 0.78125rem;
  font-weight: 500;
  color: var(--muted);
  margin: 0;
}

/* ---------- marketing: legal pages ----------
 *
 * The Privacy Notice and Terms at /privacy and /terms. Long-form reading: one
 * narrow measure, left-aligned (a centred legal document is unreadable), headings
 * that a reader can scan for the one section they came for. Reuses the marketing
 * header and footer; everything between is prose. */

.mkt-legal {
  padding-block: clamp(2rem, 1.2rem + 3vw, 4rem) var(--mkt-section-y);
}

.mkt-legal__eyebrow {
  margin: 0 0 8px;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--brand);
}

.mkt-legal__title {
  margin: 0 0 8px;
  font-family: var(--font-head);
  font-size: var(--mkt-h2);
  font-weight: 800;
  line-height: var(--mkt-lh-head);
  letter-spacing: -0.6px;
  color: var(--ink);
}

.mkt-legal__updated {
  margin: 0 0 28px;
  font-family: var(--font-body);
  font-size: var(--mkt-small);
  font-weight: 500;
  color: var(--muted);
}

.mkt-legal__lead {
  margin: 0 0 8px;
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: var(--mkt-lh-body);
  color: var(--ink-2);
}

.mkt-legal h2 {
  margin: 40px 0 10px;
  font-family: var(--font-head);
  font-size: var(--mkt-h3);
  font-weight: 800;
  line-height: var(--mkt-lh-head);
  letter-spacing: -0.3px;
  color: var(--ink);
}

.mkt-legal h3 {
  margin: 24px 0 8px;
  font-family: var(--font-body);
  font-size: var(--mkt-body);
  font-weight: 700;
  color: var(--ink);
}

.mkt-legal p,
.mkt-legal li {
  font-family: var(--font-body);
  font-size: var(--mkt-body);
  line-height: var(--mkt-lh-body);
  color: var(--ink-2);
}

.mkt-legal p {
  margin: 0 0 14px;
}

.mkt-legal ul {
  margin: 0 0 14px;
  padding-left: 22px;
}

.mkt-legal li + li {
  margin-top: 6px;
}

.mkt-legal a {
  color: var(--brand);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.mkt-legal strong {
  color: var(--ink);
}

/* A what / why / who-sees-it card per situation. The row label is a column on
   wide screens and a caption above the cell on a phone, where a two-column table
   of prose runs out of width. */
.mkt-legal__table {
  width: 100%;
  margin: 0 0 18px;
  border: 1px solid var(--border-2);
  border-radius: var(--r-lg);
  border-collapse: separate;
  border-spacing: 0;
  overflow: hidden;
  background: var(--surface);
}

.mkt-legal__table th,
.mkt-legal__table td {
  padding: 12px 14px;
  vertical-align: top;
  font-family: var(--font-body);
  font-size: var(--mkt-small);
  line-height: 1.55;
  text-align: left;
}

.mkt-legal__table th {
  width: 7.5rem;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--muted);
  background: var(--surface-2);
}

.mkt-legal__table td {
  color: var(--ink-2);
}

.mkt-legal__table tr + tr th,
.mkt-legal__table tr + tr td {
  border-top: 1px solid var(--border);
}

@media (max-width: 560px) {
  .mkt-legal__table tr,
  .mkt-legal__table th,
  .mkt-legal__table td {
    display: block;
    width: auto;
  }

  .mkt-legal__table th {
    padding-bottom: 4px;
  }

  .mkt-legal__table td {
    padding-top: 6px;
  }

  .mkt-legal__table tr + tr td {
    border-top: 0;
  }
}

.mkt-legal__aside {
  margin-top: 28px;
  padding-top: 18px;
  border-top: 1px solid var(--border);
  font-size: var(--mkt-small);
}

/* The one-line notice under a phone field: the point-of-collection notice the
   DPDP Act asks for. Muted, but with a real link -- it is the only place a
   customer joining a queue is told what happens to the number they just typed. */
.collect-notice {
  margin: 0 0 18px;
  font: 500 12px/1.5 var(--font-body);
  color: var(--muted);
}

.collect-notice a {
  color: var(--muted);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* The header's quieter link: text only, no pill, so "For businesses" and
   "Download app" do not read as two equal buttons. */
.mkt-header__login--quiet {
  border-color: transparent;
}

/* ---------- home: "Download app" ----------
 *
 * A button in link's clothing: it wears .mkt-header__login so it sits level with
 * the links beside it, and the icon is the one thing that says "this does
 * something here" rather than "this goes somewhere". */
.mkt-install {
  display: contents;
}

.mkt-install__btn {
  gap: 7px;
  padding-inline: 14px 18px;
  background: var(--surface);
  cursor: pointer;
}

/* The how-to sheet. gk-modal supplies the frame and the phone bottom-sheet; this
   is only the type inside it, in the marketing page's voice. */
.mkt-install__sheet {
  padding: 24px;
}

.mkt-install__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}

.mkt-install__title {
  margin: 0;
  font-family: var(--font-head);
  font-size: 1.1875rem;
  font-weight: 800;
  color: var(--ink);
}

.mkt-install__close {
  flex: none;
  min-width: 44px;
  min-height: 44px;
  margin: -12px -12px -12px 0;
  border: 0;
  background: none;
  font: 700 18px var(--font-body);
  color: var(--muted);
  cursor: pointer;
}

.mkt-install__body {
  margin: 0 0 18px;
  font-family: var(--font-body);
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--ink-3);
}

.mkt-install__steps {
  margin: 0;
  padding-left: 22px;
  font-family: var(--font-body);
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--ink);
}

.mkt-install__steps li + li {
  margin-top: 8px;
}

.mkt-install__steps strong {
  font-weight: 700;
}

.mkt-install__hint {
  color: var(--muted);
}

/* ---------- marketing: responsive layers ---------- */

/* No band is forced to a screen's height: like the directory, the page is as
   long as what is on it, and the section rhythm comes from --mkt-section-y. */

@media (min-width: 600px) {
  .mkt-instead__pair {
    grid-template-columns: 1fr 1fr;
  }

  .mkt-features__grid--six {
    grid-template-columns: 1fr 1fr;
  }

  .mkt-footer__inner {
    flex-direction: row;
    justify-content: space-between;
    text-align: left;
  }
}

@media (min-width: 900px) {
  .mkt-container {
    padding-inline: 24px;
  }

  .mkt-steps__grid,
  .mkt-features__grid {
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;
  }

  .mkt-steps__grid {
    gap: 28px;
  }

  /* The dashed rule between step cards, sitting in the 28px gap at the height
     of the number disc. The last card has nothing to lead to. */
  .mkt-step:not(:last-child)::after {
    content: "";
    position: absolute;
    top: 54px;
    right: -28px;
    width: 28px;
    border-top: 2px dashed var(--brand);
    opacity: 0.45;
  }

  .mkt-header__cta {
    display: inline-flex;
    min-height: 40px;
    padding: 0 18px;
  }

  /* Copy left, the listing card right, the copy ranged left like the
     directory's strapline. The card column is fixed so the copy keeps its
     measure at every width above this. */
  .mkt-hero__inner {
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: clamp(3rem, 2rem + 4vw, 6rem);
  }

  .mkt-hero__copy {
    text-align: left;
    margin-inline: 0;
    max-width: 600px;
  }

  .mkt-hero__subhead {
    margin-inline: 0;
  }

  .mkt-hero__micro {
    justify-content: flex-start;
  }

  .mkt-hero__figure {
    justify-self: end;
    width: 100%;
  }

  .mkt-lang__name {
    display: inline;
  }
}

@media (min-width: 1440px) {
  .mkt-container {
    max-width: 1180px;
  }
}

/* ---------- malik: the founder's control panel ----------
 *
 * A tool, not a product screen: one bar, one page column, tables and the settings
 * form fields. Desktop-first because that is where it is used, but nothing here
 * breaks on a phone -- the table scrolls sideways and the toolbar wraps.
 */

.malik {
  min-height: 100dvh;
  background: var(--bg);
}

.malik-bar {
  position: sticky;
  top: 0;
  z-index: 30;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.malik-bar__inner {
  max-width: 1080px;
  margin: 0 auto;
  padding: 12px 24px;
  display: flex;
  align-items: center;
  gap: 24px;
}

.malik-brand {
  font: 800 19px var(--font-head);
  letter-spacing: -0.6px;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}

.malik-brand__tag {
  display: inline-block;
  margin-left: 6px;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: var(--brand-soft);
  color: var(--brand);
  font: 700 10.5px var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 1.2px;
  vertical-align: middle;
}

.malik-nav {
  display: flex;
  gap: 4px;
  flex: 1;
}

.malik-nav__link {
  padding: 8px 12px;
  border-radius: var(--r-md);
  font: 700 13px var(--font-body);
  color: var(--muted);
  text-decoration: none;
}

.malik-nav__link:hover { color: var(--ink); background: var(--surface-2); }

.malik-nav__link--on { color: var(--ink); background: var(--surface-2); }

.malik-signout-form { margin: 0; }

.malik-signout {
  background: none;
  border: none;
  padding: 8px 4px;
  font: 600 13px var(--font-body);
  color: var(--muted);
  cursor: pointer;
}

.malik-signout:hover { color: var(--ink); }

.malik-page {
  max-width: 1080px;
  margin: 0 auto;
  padding: 24px 24px 64px;
}

.malik-title {
  margin: 0 0 16px;
  font: 800 24px var(--font-head);
  letter-spacing: -0.8px;
  color: var(--ink);
}

.malik-toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

.malik-search {
  display: flex;
  gap: 8px;
  flex: 1 1 320px;
}

.malik-search .settings-field { flex: 1; }

.malik-filters { display: flex; gap: 4px; }

.malik-filter {
  padding: 8px 12px;
  border-radius: var(--r-pill);
  font: 600 12.5px var(--font-body);
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
}

.malik-filter b { font-weight: 800; color: var(--ink); margin-left: 2px; }

.malik-filter--on { background: var(--surface-2); color: var(--ink); }

.malik-sheet {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  overflow-x: auto;
}

.malik-table {
  width: 100%;
  border-collapse: collapse;
  font: 500 13.5px var(--font-body);
  color: var(--ink);
}

.malik-table th {
  padding: 12px 16px;
  text-align: left;
  font: 600 10.5px var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 1.2px;
  color: var(--muted);
  border-bottom: 1px solid var(--border);
}

.malik-table td {
  padding: 12px 16px;
  vertical-align: middle;
  border-bottom: 1px solid var(--border);
}

.malik-table tr:last-child td { border-bottom: none; }

.malik-row--off td:not(.malik-actions) { opacity: 0.55; }

.malik-name { font: 700 14px var(--font-body); color: var(--ink); }

.malik-muted { font: 500 12.5px var(--font-body); color: var(--muted); margin-top: 2px; }

.malik-link { color: var(--brand); text-decoration: none; }

.malik-link:hover { text-decoration: underline; }

.malik-code {
  font: 800 16px var(--font-mono);
  letter-spacing: 2px;
}

.malik-pill {
  display: inline-block;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  color: var(--ink-3);
  font: 700 11px var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 1px;
  white-space: nowrap;
}

.malik-pill--open { background: var(--brand-soft); color: var(--brand); }

.malik-pill--off { background: var(--warn-soft); color: var(--warn-ink); }

.malik-actions {
  text-align: right;
  white-space: nowrap;
}

.malik-actions form { display: inline-block; margin: 0; }

.malik-actions .btn { margin-left: 6px; min-height: 34px; padding: 0 12px; }

.malik-empty {
  margin: 0;
  padding: 44px 24px;
  text-align: center;
  font: 400 15px/1.5 var(--font-serif);
  font-style: italic;
  color: var(--muted);
}

.malik-form { max-width: 720px; }

.malik-form .settings-field-row { margin-top: 18px; }

.malik-form .settings-field-row ~ .settings-field__label,
.malik-form .settings-field ~ .settings-field__label { margin-top: 18px; }

@media (max-width: 780px) {
  .malik-bar__inner { padding: 10px 16px; gap: 12px; flex-wrap: wrap; }
  .malik-nav { order: 3; flex-basis: 100%; }
  .malik-page { padding: 16px 16px 64px; }
}

/* The pin line under "Find your shop on Google" on the panel's business form. */
.malik-pin {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 16px;
  margin: 6px 0 14px;
  font: 500 12.5px var(--font-body); color: var(--ink);
}
.malik-pin__state { display: inline-flex; align-items: center; gap: 6px; }
.malik-pin__state svg { flex: none; color: var(--brand); }
.malik-pin__remove { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; color: var(--muted); }
.malik-pin__remove input { margin: 0; accent-color: var(--brand); }
