/* ============================================================
   Type

   Noto Sans Georgian, self-hosted. The old stack asked the
   operating system for a Georgian face and got whatever was
   installed: Sylfaen on most Windows machines, which is thin,
   narrow and was never drawn for screen text. Shipping the font
   means every reader sees the same page, and the variable weight
   axis lets the body text sit a step heavier than a default 400
   without turning bold.

   Two subsets only, 66 KB together: the Georgian block and basic
   Latin. unicode-range keeps the browser from fetching the Latin
   file at all on a page that has no Latin on screen.
   ============================================================ */
@font-face {
  font-family: "Noto Sans Georgian";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/assets/fonts/noto-georgian-ge.woff2") format("woff2");
  unicode-range: U+0589, U+10A0-10FF, U+1C90-1CBA, U+1CBD-1CBF, U+205A, U+2D00-2D2F, U+2E31;
}
@font-face {
  font-family: "Noto Sans Georgian";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/assets/fonts/noto-georgian-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                 U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ============================================================
   RAZVALI

   Built on the visual system Georgian drivers already know from
   myauto.ge: a light grey page, flat white panels held by a thin
   grey border rather than a shadow, 8–16px corners, dense 15px
   type, and a single hot orange doing all the pointing.

   The one deliberate departure is the orange itself. The
   reference paints white 12–14px text on #fd4100, which lands at
   3.5:1 and is unreadable for a lot of people; the fill here is
   #d43900 (4.8:1) and the brighter tone is kept for decoration
   only, where nothing has to be read off it.
   ============================================================ */

:root {
  /* surfaces */
  --page: #f5f5f8;
  --surface: #ffffff;
  --surface-2: #f7f8fa;
  --surface-3: #f2f3f6;
  --hero-bg: #e9ecf2;

  /* ink: three steps, every one of them ≥ 4.5:1 on white */
  --ink: #272a37;
  --muted: #55586a;
  --muted-2: #5f626e;
  --faint: #bebfc3;      /* decorative only — dividers, empty stars */

  --line: #e5e7eb;
  --line-2: #d9dce2;

  /* accent — dark enough to clear 4.5:1 both as white-on-fill (5.4:1) and as
     text on the grey page (4.9:1), which the lighter #d43900 missed. */
  --accent: #c73500;
  --accent-hover: #a82c00;
  --accent-bright: #fd4100;   /* decoration: the brand mark, large glyphs */
  --accent-soft: #fff1ec;
  --signal: #c0392b;
  --signal-soft: #fdeceb;
  --ok: #1c7c4a;

  /* The shipped face first; the rest only matter for the moment before it
     arrives, or if the request fails. */
  --font: "Noto Sans Georgian", "BPG Arial", "SF Georgian", Sylfaen,
          system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Georgian has no ascenders or descenders to speak of, so the same nominal
     size reads smaller than Latin does. 16px with a 460 weight is where this
     face stops looking faint on a white page. */
  --text: 16px;
  --weight-body: 460;

  --radius: 12px;
  --radius-lg: 16px;
  --radius-sm: 8px;
  --pill: 999px;

  --shadow: 0 3px 12px rgba(0, 0, 0, 0.07);
  --shadow-lg: 0 6px 18px rgba(0, 0, 0, 0.09);

  --header-h: 64px;
  --tabbar-h: 58px;
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

/* The browser hides [hidden] with a low-specificity display:none, so any rule
   here that sets display on the same element silently un-hides it. That is
   what kept the city list on screen underneath the district panel. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--text);
  font-weight: var(--weight-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;

  /* Sticky footer. A short page — signing in, a confirmation, an empty
     result — otherwise leaves the footer stranded partway down with grey
     below it. main takes the slack so the footer always meets the bottom
     edge, and on a long page nothing changes.
     dvh second: it wins where supported, and browsers without it keep vh. */
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}
main { flex: 1 0 auto; }
.site-footer { flex-shrink: 0; }

h1, h2, h3, h4 { margin: 0; line-height: 1.25; letter-spacing: -0.01em; font-weight: 750; }
h1 { font-size: clamp(1.5rem, 3vw, 2.1rem); }
h2 { font-size: clamp(1.15rem, 2vw, 1.4rem); }
h3 { font-size: 1.05rem; font-weight: 650; }

p { margin: 0 0 0.9rem; }
a { color: inherit; text-decoration: none; }
img { max-width: 100%; height: auto; display: block; }

::selection { background: var(--accent); color: #fff; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* ---- layout ---- */
.wrap { max-width: 1200px; margin: 0 auto; padding: 0 1rem; }
.wrap-narrow { max-width: 460px; margin: 0 auto; padding: 0 1rem; }
.section { padding: 2rem 0; }
.bg-surface { background: var(--surface); }
.stack > * + * { margin-top: 0.9rem; }
.muted { color: var(--muted); }
.muted-2 { color: var(--muted-2); }
.small { font-size: 0.9rem; }
.tiny { font-size: 0.8rem; }
.center { text-align: center; }
.num { font-variant-numeric: tabular-nums; }
.accent { color: var(--accent); }
.danger { color: var(--signal); }
.grid { display: grid; gap: 0.9rem; }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.row { display: flex; align-items: center; gap: 0.7rem; }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.wrap-gap { flex-wrap: wrap; }
.mt-0 { margin-top: 0; } .mt-1 { margin-top: 0.5rem; } .mt-2 { margin-top: 0.9rem; }
.mt-3 { margin-top: 1.4rem; } .mt-4 { margin-top: 1.9rem; } .mt-5 { margin-top: 2.5rem; }
.mb-0 { margin-bottom: 0; }

/* ---- header ---- */
.site-header {
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 50;
  transition: box-shadow 0.2s ease;
}
/* Set by app.js once the page has moved: the bar earns a shadow only when
   there is something underneath it to cast onto. */
.site-header.is-stuck { box-shadow: var(--shadow); }
.site-header .inner {
  display: flex; align-items: center; justify-content: space-between;
  height: var(--header-h); gap: 1rem;
}
/* ---- the mark ---- */
.brand {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-size: 1.25rem; font-weight: 800; letter-spacing: -0.02em;
  flex-shrink: 0;
  color: var(--ink);
}
.brand-word { line-height: 1; }
.mark { display: block; flex-shrink: 0; overflow: visible; }

/* The camber: each wheel leans off vertical about its own contact patch. */
.mark-wheel { transform-box: fill-box; transform-origin: 50% 100%; }
.mark-wheel-l { transform: rotate(-9deg); }
.mark-wheel-r { transform: rotate(9deg); }

a.brand:hover .mark-wheel-l { transform: rotate(-14deg); }
a.brand:hover .mark-wheel-r { transform: rotate(14deg); }
a.brand .mark-wheel { transition: transform 0.35s cubic-bezier(0.34, 1.4, 0.64, 1); }

/* One orchestrated moment: the pair starts out of true and settles. Nothing
   else on the page animates on load.

   No fill-mode and no delay on purpose. The keyframes end exactly where the
   rules above already put the wheels, so the mark is correct before the
   animation, during it, and after it — and correct too if the animation never
   runs at all. The offset between the two wheels lives inside the keyframes
   rather than in animation-delay, which would otherwise hold the first frame
   (a visibly crooked logo) while it waited. */
.mark-play .mark-wheel-l { animation: align-l 1.5s cubic-bezier(0.33, 1.2, 0.5, 1); }
.mark-play .mark-wheel-r { animation: align-r 1.6s cubic-bezier(0.33, 1.2, 0.5, 1); }

@keyframes align-l {
  0%   { transform: rotate(-24deg); }
  55%  { transform: rotate(-4deg); }
  100% { transform: rotate(-9deg); }
}
@keyframes align-r {
  0%, 12% { transform: rotate(2deg); }
  60%     { transform: rotate(15deg); }
  100%    { transform: rotate(9deg); }
}

.hero-mark { margin-bottom: 1rem; color: var(--ink); }
.nav { display: flex; align-items: center; gap: 1.1rem; }
/* :not(.btn) matters — ".nav a" outranks ".btn-accent" on specificity, so
   without it a button inside the nav loses its white text on the orange. */
.nav a:not(.btn) { font-size: 0.9rem; color: var(--muted); font-weight: 500; }
.nav a:not(.btn):hover { color: var(--accent); }

/* ---- language ----
   A <details> dropdown. Everything about opening and closing is native, so
   the control works with the script blocked and answers the keyboard without
   a roving tabindex to maintain; app.js only closes it on an outside click.

   margin-left:auto rather than a wrapper element: the header is
   space-between, so this pushes the control and the account nav to the right
   as one group and leaves the brand and the wide links on the left. */
.lang {
  position: relative;
  margin-left: auto;
  flex-shrink: 0;
}
.lang > summary {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  height: 2rem;
  padding: 0 0.55rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--bg);
  color: var(--muted);
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  cursor: pointer;
  user-select: none;
  transition: border-color 0.15s ease, color 0.15s ease;
  /* Both are needed: Safari draws its own triangle through the -webkit rule,
     everything else through list-style. */
  list-style: none;
}
.lang > summary::-webkit-details-marker { display: none; }
.lang > summary:hover { color: var(--ink); border-color: var(--line-2); }
.lang[open] > summary { color: var(--ink); border-color: var(--line-2); }

.lang-caret { color: var(--muted-2); transition: transform 0.18s ease; }
.lang[open] .lang-caret { transform: rotate(180deg); }

.flag { display: block; flex-shrink: 0; }

/* Stated rather than left to the <details> element: Chrome skips a closed
   panel with content-visibility, which does not take an absolutely
   positioned child out of the flow — the menu measured as a live 176x127 box
   sitting over the page while shut. Same trap as [hidden] against a
   display rule. */
.lang:not([open]) .lang-menu { display: none; }

.lang-menu {
  position: absolute;
  z-index: 70;
  top: calc(100% + 0.4rem);
  right: 0;
  min-width: 11rem;
  margin: 0;
  padding: 0.3rem;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: calc(var(--radius) - 2px);
  box-shadow: 0 10px 30px rgba(16, 18, 27, 0.14);
  animation: lang-drop 0.16s ease;
}
/* No fill-mode and the end state is the resting state, so a blocked
   animation leaves the menu open and readable rather than at frame zero. */
@keyframes lang-drop {
  from { opacity: 0; transform: translateY(-4px); }
}
.lang-menu li { margin: 0; }
.lang-menu a {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.5rem 0.6rem;
  border-radius: calc(var(--radius) - 6px);
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--ink);
  white-space: nowrap;
}
.lang-menu a:hover { background: var(--bg); }
.lang-menu a[aria-current="true"] { font-weight: 700; }
.lang-tick { margin-left: auto; color: var(--accent); }

/* In the footer it is a row of its own and there is nothing below it to open
   into, so the menu goes up. */
.site-footer .lang { margin: 1.25rem 0 0; display: inline-block; }
.site-footer .lang-menu { top: auto; bottom: calc(100% + 0.4rem); left: 0; right: auto; }
@keyframes lang-rise {
  from { opacity: 0; transform: translateY(4px); }
}
.site-footer .lang-menu { animation-name: lang-rise; }

@media (prefers-reduced-motion: reduce) {
  .lang-menu { animation: none; }
  .lang-caret { transition: none; }
}

/* Two buttons in the header once a customer is signed in — the invitation to
   list a workshop and the way into their account. On a phone the second one
   folds down to its mark so the first keeps its words, which are the ones
   that mean anything to somebody who has not seen the offer before. */
@media (max-width: 560px) {
  .site-header .btn-tight { padding-left: 0.6rem; padding-right: 0.6rem; }
  .site-header .btn-tight .btn-label { display: none; }
}

/* The word beside the mark is what has to go once the language control is in
   the row: brand, control and the orange button overran a 375px phone by
   29px with it in. The mark on its own is still the brand and still the link
   home. Measured, not guessed. */
@media (max-width: 820px) {
  /* A 38px row is a miss on a touch screen; the rest of the site already
     lifts its links to 44px at this width. */
  .lang-menu a { min-height: 44px; }
}

@media (max-width: 460px) {
  .site-header .brand-word { display: none; }
  .site-header .inner { gap: 0.6rem; }
  .site-header .lang > summary { padding: 0 0.4rem; gap: 0.3rem; }
  .site-header .lang-code { display: none; }
  /* With the control down to a flag and a caret, an 11rem panel hung off the
     left edge of a 360px screen with nothing to spare. */
  .site-header .lang-menu { min-width: 9.5rem; }
}

.site-footer {
  border-top: 1px solid var(--line);
  background: var(--surface);
  padding: 1.75rem 0;
  margin-top: 2rem;
}

/* ---- cards: flat, held by a border, lifted only on hover ---- */
.card {
  background: var(--surface);
  border-radius: var(--radius);
  border: 1px solid var(--line);
}
.card-pad { padding: 1rem 1.1rem; }
a.card { transition: box-shadow 0.2s ease, border-color 0.2s ease, transform 0.2s ease; }
a.card:hover {
  border-color: var(--line-2);
  box-shadow: var(--shadow-lg);
  transform: translateY(-2px);
}

/* ============================================================
   Podium

   The three highest-rated workshops, directly under the hero. It
   is deliberately not the same object as a listing card: a ranking
   should not read as another row of results, so this is a wide,
   flat band with the position doing the shouting.
   ============================================================ */
.podium-band { background: var(--surface); border-bottom: 1px solid var(--line); }

.podium {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.7rem;
}

.podium-card {
  display: flex;
  align-items: flex-start;
  gap: 0.8rem;
  height: 100%;
  padding: 0.9rem 1rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  transition: box-shadow 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}
.podium-card:hover {
  border-color: var(--line-2);
  box-shadow: var(--shadow-lg);
  transform: translateY(-2px);
}

/* The number is the point of the component, so it is the largest thing in it. */
.podium-rank {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  font-size: 1.15rem;
  font-weight: 800;
  line-height: 1;
  background: var(--surface-3);
  color: var(--muted);
}
/* Gold, silver, bronze — the one place on this site where colour carries
   meaning on its own, so the position is also printed inside the circle. */
.podium-1 .podium-rank { background: #f6e2b4; color: #6b5312; }
.podium-2 .podium-rank { background: #e4e6ea; color: #4c5058; }
.podium-3 .podium-rank { background: #f0dbc8; color: #6d4a2c; }
.podium-1 .podium-card { border-color: #e6cf9a; }

.podium-body { display: flex; flex-direction: column; gap: 0.3rem; min-width: 0; }
.podium-name {
  font-weight: 700;
  font-size: 0.95rem;
  line-height: 1.3;
  display: flex;
  align-items: center;
  gap: 0.3rem;
}
.podium-score { display: flex; align-items: center; gap: 0.35rem; font-size: 0.85rem; }
.podium-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem 0.75rem;
  font-size: 0.78rem;
  color: var(--muted);
}

/* A customer's own rating, shown back to them where the form used to be. */
.my-review { border-color: var(--line-2); background: var(--surface-3); }

/* ---- an open place ----
   A dashed outline rather than a grey block: a solid card would read as a
   workshop whose name failed to load, and a skeleton bar would read as a page
   still loading. A dashed frame reads as a space kept for somebody. */
/* .board-row is declared further down with a `border` shorthand, which resets
   border-style; .board-row.board-open outranks it so the dashes survive. */
.podium-open,
.board-row.board-open {
  border-style: dashed;
  border-color: var(--line-2);
  background: transparent;
  cursor: default;
}
.podium-open:hover,
.board-row.board-open:hover {
  box-shadow: none;
  transform: none;
  border-color: var(--line-2);
}
.podium-open .podium-rank,
.board-row.board-open .board-rank { background: transparent; box-shadow: inset 0 0 0 1px var(--line-2); color: var(--muted-2); }
.podium-open-label,
.board-open-label { font-size: 0.85rem; color: var(--muted-2); }

/* The medal colours belong to a place somebody holds. An empty first place is
   not gold. */
.podium-1 .podium-open,
.podium-2 .podium-open,
.podium-3 .podium-open { border-color: var(--line-2); }
.podium-1 .podium-open .podium-rank,
.podium-2 .podium-open .podium-rank,
.podium-3 .podium-open .podium-rank { background: transparent; color: var(--muted-2); }

/* ---- places four to ten ----
   Rows, not cards: at this depth a ranking is read down a column rather than
   compared side by side, and seven more cards would push the catalogue off
   the first screen. */
.board-rows {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.4rem;
}
.board-row {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  padding: 0.6rem 0.9rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: calc(var(--radius) - 4px);
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
a.board-row:hover { border-color: var(--line-2); box-shadow: var(--shadow); }

.board-rank {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--muted);
  font-size: 0.85rem;
  font-weight: 700;
  line-height: 1;
}
.board-main { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; flex: 1; }
.board-name {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.9rem;
  font-weight: 700;
  line-height: 1.3;
  /* One line: a long shop name must not push the score off the row. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.board-where { font-size: 0.76rem; color: var(--muted); }
.board-score {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-size: 0.9rem;
}

.board-invite { max-width: 40rem; }

@media (max-width: 560px) {
  .board-row { padding: 0.55rem 0.7rem; gap: 0.6rem; }
  .board-rank { width: 25px; height: 25px; font-size: 0.78rem; }
  .board-name { font-size: 0.85rem; }
}

@media (max-width: 860px) {
  .podium { grid-template-columns: minmax(0, 1fr); }
  .podium-card { align-items: center; }
}
@media (max-width: 560px) {
  .podium-rank { width: 34px; height: 34px; font-size: 1rem; }
  .podium-card { padding: 0.75rem 0.85rem; }
}

/* ============================================================
   Workshop card

   The listing is what the whole catalogue is made of, so it gets
   its own component rather than a generic .card. The price sits
   on the photo in a filled block: across a grid of these, the
   eye lands on the numbers first, which is the comparison a
   driver actually came to make.
   ============================================================ */
.mcard {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  transition: box-shadow 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}
.mcard:hover {
  border-color: var(--line-2);
  box-shadow: var(--shadow-lg);
  transform: translateY(-3px);
}

 /* Dark, not light. The price rides on this in white, so if an image is slow,
    broken or missing the text still has something to sit on. */
.mcard-top { position: relative; aspect-ratio: 4 / 3; background: #262935; overflow: hidden; }
.mcard-top img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.45s cubic-bezier(0.2, 0.6, 0.3, 1); }
.mcard:hover .mcard-top img { transform: scale(1.05); }
.mcard-top-blank { display: grid; place-items: center; background: var(--surface-3); }
.mcard-blank { color: var(--line-2); }

/* A wash under the bottom edge so white text holds on any photo. */
.mcard-top::after {
  content: "";
  position: absolute; inset: auto 0 0 0; height: 55%;
  background: linear-gradient(to top, rgba(20, 22, 30, 0.78), rgba(20, 22, 30, 0));
  pointer-events: none;
}
.mcard-top-blank::after { display: none; }

.mcard-flags { position: absolute; top: 0.5rem; left: 0.5rem; display: flex; flex-wrap: wrap; gap: 0.3rem; z-index: 1; }
.mcard-flag {
  display: inline-flex; align-items: center; gap: 0.25rem;
  font-size: 0.72rem; font-weight: 700;
  padding: 0.2rem 0.45rem;
  border-radius: 5px;
  background: var(--surface); color: var(--ink);
}
.mcard-flag-hot { background: var(--accent); color: #fff; }
.mcard-flag-ok { background: #17663f; color: #fff; }
.mcard-rank {
  display: inline-flex; align-items: center;
  font-size: 0.75rem; font-weight: 800;
  padding: 0.2rem 0.45rem; border-radius: 5px;
  background: var(--ink); color: #fff;
}

.mcard-rating {
  position: absolute; top: 0.5rem; right: 0.5rem; z-index: 1;
  display: inline-flex; align-items: center; gap: 0.25rem;
  font-size: 0.75rem; font-weight: 700;
  padding: 0.22rem 0.45rem;
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.94);
  color: var(--ink);
}
.mcard-rating .icon-star { color: #a86400; }
.mcard-rating-n { font-weight: 500; color: var(--muted); }

.mcard-price {
  position: absolute; left: 0.6rem; bottom: 0.6rem; z-index: 1;
  display: inline-flex; align-items: baseline; gap: 0.25rem;
  font-size: 1.15rem; font-weight: 800; letter-spacing: -0.02em;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
.mcard-price small { font-size: 0.7rem; font-weight: 600; opacity: 0.85; margin-left: 0.15rem; }
/* Without a photo there is no dark wash to sit on. */
.mcard-top-blank .mcard-price { color: var(--accent); text-shadow: none; }

.mcard-body { display: flex; flex-direction: column; flex: 1; padding: 0.8rem 0.85rem 0.85rem; }
.mcard-name { font-size: 0.98rem; font-weight: 700; line-height: 1.3; }
.mcard-tagline {
  margin: 0.25rem 0 0;
  font-size: 0.82rem; color: var(--muted); line-height: 1.4;
  /* Two lines, so every card in a row is the same height. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.mcard-meta {
  display: flex; flex-wrap: wrap; gap: 0.3rem 0.8rem;
  margin-top: 0.6rem;
  font-size: 0.76rem; color: var(--muted-2);
}
.mcard-cat {
  align-self: flex-start;
  margin-top: 0.5rem;
  font-size: 0.72rem;
  padding: 0.18rem 0.45rem;
  border-radius: 5px;
  background: var(--surface-3); color: var(--muted);
}
.mcard-cta {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.3rem;
  margin-top: auto;
  padding-top: 0.75rem;
  font-size: 0.85rem; font-weight: 700;
  color: var(--accent);
}
.mcard-cta .icon { transition: transform 0.2s ease; }
.mcard:hover .mcard-cta .icon { transform: translateX(4px); }

@media (max-width: 640px) {
  .mcard-body { padding: 0.65rem 0.7rem 0.7rem; }
  .mcard-name { font-size: 0.9rem; }
  .mcard-price { font-size: 1rem; }
  /* One line of tagline at card widths around 170px. */
  .mcard-tagline { -webkit-line-clamp: 1; font-size: 0.76rem; }
  .mcard-meta { gap: 0.2rem 0.5rem; font-size: 0.7rem; }
}

/* ---- buttons: rounded rectangles, not pills ---- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  font: inherit;
  font-weight: 600;
  font-size: 0.9rem;
  line-height: 1.2;
  border-radius: var(--radius-sm);
  padding: 0.7rem 1.25rem;
  border: 1px solid transparent;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.btn-accent { background: var(--accent); color: #fff; }
.btn-accent:hover { background: var(--accent-hover); color: #fff; }
.btn-ghost { background: var(--surface); border-color: var(--line-2); color: var(--ink); }
.btn-ghost:hover { border-color: var(--ink); }
.btn-dark { background: var(--ink); color: #fff; }
.btn-dark:hover { background: #3d3f4b; color: #fff; }
.btn-danger { background: none; border-color: transparent; color: var(--signal); padding: 0.35rem 0.5rem; font-weight: 500; }
.btn-danger:hover { text-decoration: underline; }
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn:disabled { opacity: 0.55; cursor: default; }
.btn-block { width: 100%; }
.btn-sm { padding: 0.45rem 0.85rem; font-size: 0.83rem; }
.btn-lg { padding: 0.9rem 1.6rem; font-size: 1rem; border-radius: var(--radius); }

/* ---- chips ---- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.28rem;
  font-size: 0.775rem;
  font-weight: 500;
  padding: 0.25rem 0.6rem;
  border-radius: var(--radius-sm);
  background: var(--surface-3);
  color: var(--muted);
}
/* A step darker than --accent: the tinted background eats the contrast the
   same colour has on plain white. */
.chip-accent { background: var(--accent-soft); color: var(--accent-hover); }
.chip-danger { background: var(--signal-soft); color: var(--signal); }
.chip-ok { background: #e8f5ee; color: var(--ok); }
/* Waiting on someone: amber, and dark enough to read at 12px. */
.chip-warn { background: #fff3d6; color: #7a5c00; }

/* Scrollable chip row: how the reference handles categories on a phone.
   The right edge fades out, so it is visible that the row continues. */
.chip-scroll {
  display: flex; gap: 0.5rem;
  overflow-x: auto;
  padding-bottom: 0.35rem;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 2.5rem), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 2.5rem), transparent);
}
.chip-scroll.is-end { -webkit-mask-image: none; mask-image: none; }
.chip-scroll::-webkit-scrollbar { display: none; }
.chip-scroll > * { flex: 0 0 auto; }
.chip-link {
  display: inline-flex; align-items: center;
  font-size: 0.85rem; font-weight: 500;
  padding: 0.5rem 0.9rem;
  border-radius: var(--radius-sm);
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--muted);
}
.chip-link { transition: border-color 0.15s ease, color 0.15s ease, transform 0.15s ease; }
.chip-link:hover { border-color: var(--line-2); color: var(--ink); transform: translateY(-1px); }
.chip-link[aria-current="true"] { background: var(--ink); border-color: var(--ink); color: #fff; }

/* ============================================================
   Hero
   ============================================================ */
/* A shade deeper and cooler than the page, mixed from the same blue-charcoal
   as the ink rather than from a new colour. It separates the hero from both
   the white panels inside it and the page below without lighting up: the
   whole range from --page to here is under five percent. */
.hero {
  background: var(--hero-bg);
  border-bottom: 1px solid var(--line-2);
  padding: 2.25rem 0 2.5rem;
  overflow: hidden;
}
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  gap: 2.5rem;
  align-items: center;
}
.eyebrow {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-size: 0.78rem; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  /* White, not --surface-3: on the tinted hero the two greys were within a
     percent of each other and the chip disappeared. */
  background: var(--surface);
  border-radius: var(--pill);
  padding: 0.3rem 0.75rem 0.3rem 0.45rem;
  margin-bottom: 0.9rem;
}
.hero h1 { font-size: clamp(1.75rem, 3.6vw, 2.6rem); letter-spacing: -0.025em; }
/* The keyword is the headline; the slogan sits under it in the same block so
   the h1 reads as one sentence to a crawler and as two lines to a reader. */
.hero-sub {
  display: block;
  margin-top: 0.3rem;
  font-size: 0.5em;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--muted);
}

/* ---- breadcrumbs ---- */
.crumbs { display: flex; flex-wrap: wrap; gap: 0.4rem; font-size: 0.8rem; color: var(--muted); }
.crumbs a { color: var(--muted); text-decoration: underline; text-underline-offset: 3px; }
.crumbs a:hover { color: var(--accent); }
.crumbs [aria-current] { color: var(--ink); font-weight: 600; }

/* ============================================================
   FAQ

   The heading and the call to action sit beside the questions
   rather than above them: on a wide screen a single stacked
   column of accordions leaves half the page empty, and the
   answers are what the section is for.
   ============================================================ */
.faq-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
  gap: 2.5rem;
  align-items: start;
}
.faq-intro h2 { font-size: clamp(1.3rem, 2.4vw, 1.8rem); }
.faq-intro p { max-width: 22rem; }
.faq-cta { margin-top: 1.4rem; }
/* Sticks alongside the answers while they are read. */
@media (min-width: 901px) {
  .faq-intro { position: sticky; top: calc(var(--header-h) + 1.5rem); }
}

.faq { display: grid; gap: 0.6rem; }

.faq-item {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.faq-item:hover { border-color: var(--line-2); }
/* The open one is the one being read, so it is the one that stands out. */
.faq-item[open] {
  border-color: var(--line-2);
  box-shadow: var(--shadow);
}

.faq-item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 1.1rem;
  cursor: pointer;
  list-style: none;
  min-height: 56px;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: var(--radius); }

.faq-q { font-weight: 600; font-size: 0.95rem; line-height: 1.4; }
.faq-item[open] .faq-q { color: var(--accent); }

/* A plus drawn from two bars, so it needs no glyph and no icon file; it
   turns into a minus by rotating the upright one out of sight. */
.faq-mark {
  position: relative;
  flex-shrink: 0;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--surface-3);
  transition: background 0.2s ease;
}
.faq-mark::before, .faq-mark::after {
  content: "";
  position: absolute; left: 50%; top: 50%;
  width: 11px; height: 2px;
  margin: -1px 0 0 -5.5px;
  background: var(--muted);
  border-radius: 1px;
  transition: transform 0.25s cubic-bezier(0.2, 0.7, 0.3, 1), background 0.2s ease;
}
.faq-mark::after { transform: rotate(90deg); }
.faq-item[open] .faq-mark { background: var(--accent-soft); }
.faq-item[open] .faq-mark::before,
.faq-item[open] .faq-mark::after { background: var(--accent); }
.faq-item[open] .faq-mark::after { transform: rotate(0deg); }

.faq-a { padding: 0 1.1rem 1.1rem; }
.faq-a p {
  margin: 0;
  padding-top: 0.15rem;
  border-top: 1px solid var(--line);
  padding-top: 0.9rem;
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1.65;
}
/* Only the opening is animated: a closing <details> removes its content
   before a transition could run, so pretending otherwise would stutter. */
.faq-item[open] .faq-a { animation: faq-in 0.3s cubic-bezier(0.2, 0.7, 0.3, 1); }
@keyframes faq-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

@media (max-width: 900px) {
  .faq-grid { grid-template-columns: minmax(0, 1fr); gap: 1.5rem; }
  .faq-intro p { max-width: none; }
  .faq-cta .btn { width: 100%; }
}
@media (max-width: 560px) {
  .faq-item summary { padding: 0.85rem 0.9rem; gap: 0.6rem; }
  .faq-q { font-size: 0.9rem; }
  .faq-a { padding: 0 0.9rem 0.9rem; }
}
@media (prefers-reduced-motion: reduce) {
  .faq-item[open] .faq-a { animation: none; }
}
.hero-actions { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 1.4rem; }
@media (max-width: 560px) {
  /* Full width and stacked: the primary action should not be a target to aim at. */
  .hero-actions { flex-direction: column; }
  .hero-actions .btn { width: 100%; }
}
.hero-stats {
  display: flex; flex-wrap: wrap; gap: 1.75rem;
  margin: 1.4rem 0 0;
  padding-top: 1.1rem;
  border-top: 1px solid var(--line);
}
.hero-stats dt { margin-bottom: 0.15rem; }
.hero-stats dd { margin: 0; font-size: 1.4rem; font-weight: 700; }

/* ---- the alignment stand ---- */
.stand {
  --stand-ramp: #22252f;
  --stand-body: #33374a;
  --stand-glass: #464b63;
  --stand-axis: #6d7288;
  background: var(--ink);
  border-radius: var(--radius-lg);
  color: #e8eaef;
  padding: 0.9rem 1rem 1rem;
  box-shadow: var(--shadow-lg);
}
.stand-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.75rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid #3a3e4d;
}
.stand-title {
  display: inline-flex; align-items: center; gap: 0.4rem;
  font-size: 0.8rem; font-weight: 600; letter-spacing: 0.02em;
}
.stand-live {
  display: inline-flex; align-items: center; gap: 0.35rem;
  font-size: 0.7rem; color: #9ea3b4;
}
.stand-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: #46d07f;
  box-shadow: 0 0 0 0 rgba(70, 208, 127, 0.55);
  animation: stand-pulse 2.4s ease-out infinite;
}
@keyframes stand-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(70, 208, 127, 0.5); }
  70%  { box-shadow: 0 0 0 7px rgba(70, 208, 127, 0); }
  100% { box-shadow: 0 0 0 0 rgba(70, 208, 127, 0); }
}

.stand-body { display: flex; gap: 1rem; align-items: center; padding: 0.9rem 0 0.5rem; }
.stand-car { width: 40%; max-width: 150px; height: auto; flex-shrink: 0; }
.stand-car .wh rect { fill: #cfd3de; }
.stand-car .wh { transform-box: fill-box; transform-origin: 50% 50%; }

/* Resting state is true: if the animation never runs the readout and the
   wheels still agree with each other. */
.stand-car .wh-fl, .stand-car .wh-fr,
.stand-car .wh-rl, .stand-car .wh-rr { transform: rotate(0deg); }

.stand-read { flex: 1; margin: 0; display: grid; gap: 0.4rem; min-width: 0; }
.stand-row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 0.5rem;
  font-size: 0.78rem;
  padding-bottom: 0.4rem;
  border-bottom: 1px solid #33374a;
}
.stand-row:last-child { border-bottom: 0; padding-bottom: 0; }
.stand-row dt { color: #9ea3b4; white-space: nowrap; }
.stand-row dd { margin: 0; font-weight: 600; white-space: nowrap; }
.stand-was { color: #ff7a52; display: none; }
.stand-now { color: #46d07f; display: inline-flex; align-items: center; gap: 0.25rem; }

.stand-foot {
  margin: 0; padding-top: 0.7rem;
  border-top: 1px solid #3a3e4d;
  font-size: 0.7rem; color: #9ea3b4;
}

/* ---- catalogue filter ----
   Flex, not a fixed column count: the category field appears and disappears
   with the number of services, and a grid-template of four tracks left the
   remaining fields sitting in the wrong slots once one of them went away. */
.filter-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: flex-end;
}
.filter-grid > div { flex: 1 1 11rem; min-width: 0; }
/* The free-text field earns more room than the pickers. */
.filter-grid > div:first-child { flex-grow: 1.6; }

@media (max-width: 560px) {
  .filter-grid > div { flex: 1 1 100%; }
}

/* A link inside running text: underlined so colour is not the only cue, and
   padded out to a touch-sized target on a phone without moving the words. */
.link { text-decoration: underline; text-underline-offset: 3px; }
.link:hover { color: var(--accent); }
@media (max-width: 820px) { .link { display: inline-block; padding: 0.55rem 0.2rem; } }

/* ---- detail page: content beside a sticky panel ---- */
.split {
  display: grid;
  grid-template-columns: 1.5fr 1fr;
  gap: 2.5rem;
  align-items: start;
}
.sticky-aside { position: sticky; top: calc(var(--header-h) + 12px); }
@media (max-width: 860px) {
  .split { grid-template-columns: minmax(0, 1fr); gap: 1.25rem; }
  /* Sticky inside a single column would pin the booking form over the page. */
  .sticky-aside { position: static; }
}

/* ============================================================
   Dashboard
   ============================================================ */
.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.6rem;
}
.stat {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0.7rem 0.85rem;
}
/* The only number that means "do something now". */
.stat-hot { border-color: var(--accent); background: var(--accent-soft); }
.stat-label { font-size: 0.72rem; color: var(--muted); }
.stat-value { display: block; margin-top: 0.2rem; font-size: 1.35rem; font-weight: 700; }

.todo {
  background: #fff8e8;
  border: 1px solid #f0dfae;
  border-radius: var(--radius-sm);
  padding: 0.8rem 1rem;
}
.todo-list { margin: 0.4rem 0 0; padding-left: 1.1rem; font-size: 0.85rem; color: #6b551d; }
.todo-list li + li { margin-top: 0.25rem; }

/* ---- one booking ---- */
.bk-list { display: grid; gap: 0.6rem; }
.bk {
  display: grid;
  grid-template-columns: 5.5rem minmax(0, 1fr);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
/* The time is the first thing scanned down a day's list, so it gets its own
   column rather than sitting inside a sentence. */
.bk-when {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0.1rem;
  padding: 0.75rem 0.5rem;
  background: var(--surface-3);
  border-right: 1px solid var(--line);
  text-align: center;
}
.bk-time { font-size: 1.15rem; font-weight: 700; line-height: 1.1; }
.bk-day { font-size: 0.7rem; color: var(--muted); }
.bk-main { padding: 0.75rem 0.9rem; min-width: 0; }
.bk-facts {
  display: flex; flex-wrap: wrap; gap: 0.25rem 0.9rem;
  margin-top: 0.4rem;
  font-size: 0.8rem; color: var(--muted);
}
.bk-note {
  margin: 0.5rem 0 0;
  padding-left: 0.6rem;
  border-left: 2px solid var(--line-2);
  font-size: 0.83rem; color: var(--muted);
}

@media (max-width: 560px) {
  .bk { grid-template-columns: 4.25rem minmax(0, 1fr); }
  .bk-time { font-size: 1rem; }
  .bk-main { padding: 0.65rem 0.7rem; }
  .stat-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stat-value { font-size: 1.15rem; }
}

/* ---- account settings ---- */
.kv { margin: 0; display: grid; gap: 0.55rem; }
.kv > div { display: flex; flex-wrap: wrap; gap: 0.35rem 0.75rem; align-items: baseline; }
.kv dt { flex: 0 0 7.5rem; font-size: 0.8rem; color: var(--muted); }
.kv dd { margin: 0; display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
/* Marks the section that rejected the last submission. */
.card-flagged { border-color: var(--signal); }

/* ---- forms ---- */
.label {
  display: block;
  font-size: 0.8rem; font-weight: 500;
  color: var(--muted);
  margin-bottom: 0.3rem;
}
.field {
  width: 100%;
  font: inherit;
  font-size: 0.925rem;
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-sm);
  padding: 0.62rem 0.8rem;
  color: var(--ink);
  /* box-shadow only: animating border-color leaves the border stuck on its
     old colour when the error class is added or removed. */
  transition: box-shadow 0.15s ease;
}
.field:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 15%, transparent);
}
.field::placeholder { color: var(--muted-2); }
textarea.field { resize: vertical; min-height: 5.5rem; }
select.field { appearance: none; padding-right: 2rem;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%),
                    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 1rem) 55%, calc(100% - 0.7rem) 55%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

/* ---- password field with a show/hide control ---- */
.pw { position: relative; }
.field-with-toggle { padding-right: 3rem; }

/* Hidden until app.js says it works. Without the script the field is still a
   perfectly good password box; a button that does nothing would be worse. */
.pw-toggle { display: none; }
.pw-ready .pw-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}
.pw-ready .pw-toggle:hover { color: var(--ink); background: var(--surface-3); }
.pw-ready .pw-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.pw-ready .pw-toggle[aria-pressed="true"] { color: var(--accent); }
.pw-icon { display: inline-flex; }

/* Colour alone is not an accessible signal, so the message under the field
   carries the same information in words. */
.field.is-error { border-color: var(--signal); background: #fdf7f6; }
.field.is-error:focus { border-color: var(--signal); box-shadow: 0 0 0 3px color-mix(in srgb, var(--signal) 16%, transparent); }
.field-msg { margin: 0.3rem 0 0; font-size: 0.8rem; color: var(--signal); }
.form-note { font-size: 0.85rem; color: var(--muted); }

/* ---- flash / banners ---- */
.flash {
  border-radius: var(--radius-sm);
  padding: 0.75rem 1rem;
  margin-bottom: 1.1rem;
  font-size: 0.9rem;
  background: var(--surface);
  border: 1px solid var(--line);
}
.flash-ok { background: #e8f5ee; border-color: transparent; color: var(--ok); }
.flash-error { background: var(--signal-soft); border-color: transparent; color: var(--signal); }
.banner { background: var(--accent-soft); border-bottom: 1px solid var(--line); font-size: 0.875rem; }
.banner .inner { padding: 0.55rem 0; display: flex; flex-wrap: wrap; gap: 0.5rem 0.75rem; align-items: center; }

/* ---- price list ---- */
.price-row { display: flex; align-items: baseline; gap: 0.6rem; width: 100%; }
.price-name { flex: 0 1 auto; }
.price-lead {
  flex: 1 1 auto; min-width: 1.5rem; height: 1px; align-self: flex-end;
  margin-bottom: 0.34em;
  border-bottom: 1px dotted var(--line-2);
}
.price-value { flex: 0 0 auto; font-variant-numeric: tabular-nums; font-weight: 700; color: var(--accent); }

/* ---- stars ---- */
/* Decorative: the numeric value sits beside them and the span carries an
   aria-label, so the empty ones may stay light. */
/* ---- icons ---- */
.icon { display: inline-block; vertical-align: -0.16em; flex-shrink: 0; }
/* Keeps the icon and its label on one line with an even gap, wherever the
   pair appears in running text. */
.icon-row { display: inline-flex; align-items: center; gap: 0.34rem; }
.icon-star { fill: currentColor; stroke: none; }

/* ---- stars ---- */
.stars { display: inline-flex; gap: 1px; align-items: center; }
.stars .star { fill: var(--faint); }
/* Darker than the usual bright gold, which sits near 2:1 on white. */
.stars .star.on { fill: #a86400; }

/* ---- photos ---- */
.photo-grid { display: grid; gap: 0.7rem; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.photo { position: relative; aspect-ratio: 4 / 3; overflow: hidden; border-radius: var(--radius-sm); background: var(--surface-3); }
.photo img { width: 100%; height: 100%; object-fit: cover; }
.photo-flag {
  position: absolute; left: 0.5rem; top: 0.5rem;
  background: var(--accent); color: #fff;
  font-size: 0.7rem; font-weight: 600;
  padding: 0.15rem 0.5rem; border-radius: var(--radius-sm);
}
.dropzone {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0.25rem; text-align: center;
  aspect-ratio: 4 / 3;
  border: 1px dashed var(--line-2);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--muted);
  cursor: pointer;
}
.dropzone:hover { border-color: var(--accent); color: var(--accent); }

/* ---- tables ---- */
table.data { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
table.data th, table.data td { text-align: right; padding: 0.6rem 0.75rem; border-bottom: 1px solid var(--line); }
table.data th { color: var(--muted); font-weight: 500; font-size: 0.8rem; }
table.data tr:last-child td { border-bottom: 0; }
.table-scroll { overflow-x: auto; }

/* ---- misc ---- */
.divide > * + * { border-top: 1px solid var(--line); }
.empty { background: var(--surface); border: 1px dashed var(--line-2); border-radius: var(--radius); padding: 2.25rem 1.25rem; text-align: center; color: var(--muted); }
.map-frame { width: 100%; height: 15rem; border: 0; display: block; border-radius: var(--radius-sm); }
.locked { background: var(--surface-3); border-radius: var(--radius); padding: 1.75rem 1.25rem; text-align: center; }
.skip-link { position: absolute; left: -9999px; background: var(--accent); color: #fff; padding: 0.5rem 1rem; }
.skip-link:focus { left: 0; top: 0; z-index: 100; }

/* ---- charts ----
   Bars are elements, not a drawing, so the figures print and read out. The
   row is a flex grid: it does not need to know how many columns it has, which
   is what lets one partial serve thirty days, thirteen weeks and twelve
   months. */
.chart { margin-top: 0.9rem; }
.chart-plot {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 8.5rem;
}
.chart-compact .chart-plot { height: 5rem; }

.chart-col {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  height: 100%;
  gap: 3px;
}

.chart-bar {
  display: block;
  width: 100%;
  border-radius: 3px 3px 0 0;
  background: var(--line-2);
  transition: background-color 0.15s ease;
}
/* The tallest column is the one the eye should land on. */
.chart-col.is-top .chart-bar { background: var(--accent); }
.chart-col:hover .chart-bar { background: var(--ink); }

/* The number above the bar is the point of the chart; it is shown only when
   there is room, which is why the compact variant drops it. */
.chart-value {
  font-size: 0.62rem;
  font-weight: 700;
  color: var(--muted);
  line-height: 1;
  min-height: 0.7rem;
  /* Long values must not widen a column and squash its neighbours. */
  max-width: 100%;
  overflow: hidden;
}
.chart-label {
  font-size: 0.6rem;
  color: var(--muted-2);
  line-height: 1;
  white-space: nowrap;
}
.chart-empty { padding: 1.75rem 0; text-align: center; }

/* ---- one ratio, one bar ---- */
.split-bar {
  display: flex;
  height: 0.65rem;
  border-radius: 999px;
  overflow: hidden;
  background: var(--surface-3);
}
.split-2 { background: var(--accent); }
.split-4 { background: var(--ink); }

@media (max-width: 640px) {
  .chart-plot { height: 7rem; gap: 2px; }
  .chart-compact .chart-plot { height: 4.5rem; }
  /* Thirty day labels do not fit on a phone; every second one is enough to
     read the shape, and the tooltip still names each bar. */
  .chart:not(.chart-compact) .chart-col:nth-child(even) .chart-label { visibility: hidden; }
  .chart-value { display: none; }
}

/* ---- the location picker ----
   The canvas is a window onto a tile grid: the tiles are absolutely placed
   inside it and moved as a group, so panning is one style write per frame
   rather than a reflow. */
.mapick {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 0.75rem;
}
.mapick-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
}
.mapick-canvas {
  position: relative;
  height: 17rem;
  overflow: hidden;
  border-radius: calc(var(--radius) - 4px);
  background: #e8e6df;
  cursor: crosshair;
  touch-action: none;          /* the map takes the drag, not the page */
  user-select: none;
}
.mapick-canvas.is-dragging { cursor: grabbing; }
.mapick-tiles { position: absolute; inset: 0; }
.mapick-tiles img {
  position: absolute;
  width: 256px;
  height: 256px;
  pointer-events: none;        /* clicks belong to the canvas */
}
.mapick-marker {
  position: absolute;
  color: var(--accent);
  /* The point of the pin is its tip, not its middle. */
  transform: translate(-50%, -100%);
  pointer-events: none;
  filter: drop-shadow(0 2px 3px rgba(16, 18, 27, 0.45));
}
.mapick-zoom {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.mapick-zoom button {
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface);
  color: var(--ink);
  font-size: 1rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}
.mapick-zoom button:hover { border-color: var(--line-2); }
.mapick-credit {
  position: absolute;
  right: 0;
  bottom: 0;
  margin: 0;
  padding: 1px 5px;
  font-size: 0.62rem;
  background: rgba(255, 255, 255, 0.78);
  color: #4a4f57;
  border-top-left-radius: 4px;
}
.mapick-credit a { color: inherit; text-decoration: underline; }
.mapick-note { margin: 0.55rem 0 0; }
.mapick-fields {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.5rem;
  margin-top: 0.6rem;
}
/* Typing a coordinate is the fallback, not the way in: keep it quiet. */
.mapick-fields .field { font-size: 0.8rem; padding: 0.45rem 0.6rem; }

/* ---- address suggestions ----
   Our own list under our own input, rather than Google's drop-in element,
   which would replace the field the server reads. Styled as the site styles
   everything else, so it does not arrive looking borrowed. */
.suggest {
  position: absolute;
  z-index: 40;
  left: 0;
  right: 0;
  top: calc(100% + 4px);
  padding: 0.25rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: calc(var(--radius) - 4px);
  box-shadow: 0 10px 26px rgba(16, 18, 27, 0.14);
}
.suggest-item {
  display: block;
  width: 100%;
  padding: 0.5rem 0.55rem;
  border: 0;
  border-radius: calc(var(--radius) - 8px);
  background: none;
  text-align: left;
  font: inherit;
  font-size: 0.85rem;
  color: var(--ink);
  cursor: pointer;
  /* One line each: a long formatted address must not turn the list into a
     wall of text. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.suggest-item:hover,
.suggest-item.is-active { background: var(--bg); }

/* Google draws into its own canvas; it brings its own controls and credit. */
.mapick-google { background: var(--surface-3); }

/* ---- the shop's mark ---- */
.logo-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}
.logo-shot {
  width: 5.5rem;
  height: 5.5rem;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--bg);
  object-fit: cover;
  flex-shrink: 0;
}
.logo-blank {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted-2);
}
/* On a card the mark sits over the photo, bottom-right — opposite the price,
   which already owns the bottom-left corner — so it reads as a badge on the
   picture rather than as a second subject in it. */
.mcard-logo {
  position: absolute;
  right: 0.55rem;
  bottom: 0.55rem;
  z-index: 1;
  width: 2.2rem;
  height: 2.2rem;
  border-radius: 8px;
  border: 2px solid rgba(255, 255, 255, 0.9);
  object-fit: cover;
  background: #fff;
  box-shadow: 0 2px 6px rgba(16, 18, 27, 0.3);
}
.master-logo {
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 10px;
  border: 1px solid var(--line);
  object-fit: cover;
  background: var(--surface);
  flex-shrink: 0;
}

/* ---- two wheels or four ---- */
.wheels {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.5rem;
}
.wheels input { position: absolute; opacity: 0; pointer-events: none; }
.wheels label {
  display: block;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: calc(var(--radius) - 4px);
  background: var(--surface);
  cursor: pointer;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.wheels label:hover { border-color: var(--line-2); }
.wheels input:checked + label {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}
/* Keyboard focus has to be visible on the label, since the radio is hidden. */
.wheels input:focus-visible + label { outline: 2px solid var(--accent); outline-offset: 2px; }
.wheels-n { display: block; font-size: 0.78rem; color: var(--muted); }
.wheels-p { display: block; font-weight: 700; font-variant-numeric: tabular-nums; }

/* The second price on a card and on a profile, quieter than the first. */
.price-alt { font-size: 0.78rem; color: var(--muted); font-weight: 500; }

.field-hint { margin: 0.3rem 0 0; font-size: 0.76rem; color: var(--muted-2); }

@media (max-width: 560px) {
  .mapick-canvas { height: 14rem; }
  .mapick-fields { grid-template-columns: 1fr; }
}

/* ============================================================
   Location picker
   ============================================================ */
.modal { position: fixed; inset: 0; z-index: 150; display: flex; align-items: center; justify-content: center; padding: 1rem; }
.modal[hidden] { display: none; }
body.modal-open { overflow: hidden; }

/* The page dims a moment before the dialog arrives, so the two do not land on
   the same frame.

   Neither of these carries a fill-mode, and neither has an animation-delay.
   A delay plus `backwards` holds the opening frame — a transparent card —
   until the timeline starts, and a dialog that is invisible while its
   backdrop still swallows every click is a page that looks frozen. The offset
   between the backdrop and the card lives inside the keyframes instead, so
   the resting style is the finished style and an animation that never runs
   simply shows the dialog. */
.modal-backdrop {
  position: absolute; inset: 0;
  background: rgba(24, 26, 34, 0.55);
  backdrop-filter: blur(2px);
  animation: fade-in 0.35s ease;
}
.modal-card {
  position: relative;
  background: var(--surface);
  border-radius: var(--radius-lg);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.28);
  width: 100%;
  max-width: 34rem;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
  padding: 1.4rem 1.4rem 1rem;
  animation: modal-in 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes modal-in {
  0%, 16% { opacity: 0; transform: translateY(22px) scale(0.95); }
  100%    { opacity: 1; transform: none; }
}

.modal-title { font-size: 1.25rem; padding-right: 2rem; }
.modal-body { overflow-y: auto; margin: 0 -0.35rem; padding: 0 0.35rem; flex: 1; }
.modal-x {
  position: absolute; top: 0.6rem; right: 0.6rem;
  width: 40px; height: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--radius-sm);
  background: var(--surface-3); color: var(--muted);
  cursor: pointer;
}
.modal-x .icon { transform: rotate(45deg); }   /* the plus becomes a cross */
.modal-x:hover { background: var(--line); color: var(--ink); }
.modal-skip {
  align-self: center;
  margin-top: 0.9rem;
  background: none; border: 0;
  font: inherit; font-size: 0.85rem;
  color: var(--muted); text-decoration: underline; text-underline-offset: 3px;
  cursor: pointer;
  padding: 0.5rem 0.75rem;
}
.modal-skip:hover { color: var(--ink); }

.city-grid { display: flex; flex-wrap: wrap; gap: 0.45rem; }
.city-btn {
  font: inherit; font-size: 0.875rem;
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.55rem 0.85rem;
  min-height: 40px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.city-btn:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.city-btn-lead {
  width: 100%;
  justify-content: flex-start;
  background: var(--ink); border-color: var(--ink); color: #fff;
  font-weight: 600; font-size: 0.95rem;
  min-height: 48px;
}
.city-btn-lead:hover { background: #3d3f4b; border-color: #3d3f4b; color: #fff; }
.city-btn-note { margin-left: auto; font-weight: 400; font-size: 0.78rem; color: #b9bcc7; }
/* The nine official districts read as headings for the areas under them. */
.city-btn-group { background: var(--surface-3); border-color: transparent; font-weight: 600; }

.city-back {
  font: inherit; font-size: 0.85rem;
  display: inline-flex; align-items: center; gap: 0.35rem;
  background: none; border: 0; padding: 0.4rem 0; margin-top: 0.5rem;
  color: var(--muted); cursor: pointer;
}
.city-back:hover { color: var(--accent); }
.icon-flip { transform: rotate(180deg); }

@media (max-width: 560px) {
  .modal { padding: 0; align-items: flex-end; }
  /* On a phone it comes up from the bottom edge, where the thumb is. */
  .modal-card {
    max-width: none;
    max-height: 88vh;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    padding-bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
    animation: sheet-in 0.5s cubic-bezier(0.22, 1, 0.36, 1);
  }
  @keyframes sheet-in {
    0%, 16% { opacity: 0; transform: translateY(100%); }
    100%    { opacity: 1; transform: none; }
  }
}

/* ---- progress through signing up ---- */
.steps {
  display: flex; flex-wrap: wrap; gap: 0.4rem;
  list-style: none; margin: 0 0 1rem; padding: 0;
  font-size: 0.78rem;
}
.steps li {
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.28rem 0.7rem;
  border-radius: var(--pill);
}
.steps-done { background: #e8f5ee; color: var(--ok); }
.steps-now { background: var(--ink); color: #fff; font-weight: 600; }

.step-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 52px; height: 52px;
  border-radius: 50%;
  background: #e8f5ee; color: var(--ok);
}
.ink { color: var(--ink); }

/* ---- navigation progress ---- */
/* Every page here is a full server round trip, so a click is followed by a
   pause with nothing on screen. This bar fills during that pause. It is added
   by app.js and removed by the new page simply loading. */
.navbar-progress {
  position: fixed; top: 0; left: 0;
  height: 3px; width: 0;
  background: var(--accent-bright);
  z-index: 200;
  box-shadow: 0 0 8px rgba(253, 65, 0, 0.6);
  transition: width 0.25s ease;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) { .navbar-progress { display: none; } }

/* ============================================================
   Phone

   The desktop nav is replaced by a fixed tab bar rather than
   simply hidden, which is what the reference does and what the
   old stylesheet got wrong: below 820px it dropped the links and
   left nothing in their place.
   ============================================================ */
.tabbar { display: none; }

@media (max-width: 900px) {
  .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* The stand goes under the copy, wide, where it still reads. */
  .hero-grid { grid-template-columns: minmax(0, 1fr); gap: 1.5rem; }
  .hero-visual { max-width: 30rem; }
}

@media (max-width: 640px) {
  .hero { padding: 1.5rem 0 1.75rem; }
  .hero-stats { gap: 1.25rem; margin-top: 1.1rem; }
  .hero-stats dd { font-size: 1.2rem; }
  .stand-body { gap: 0.75rem; }
  .stand-car { width: 34%; }
  .stand-row { font-size: 0.72rem; }
}

@media (max-width: 360px) {
  /* Only under this do the four readings and the car stop both fitting. */
  .stand-car { display: none; }
}

@media (max-width: 820px) {
  .nav-wide { display: none; }

  /* Room for the fixed bar. It goes on the footer rather than on the body so
     the footer's own background fills the strip: padding on the body would
     leave a band of page colour between the end of the footer and the bar. */
  .site-footer {
    padding-bottom: calc(1.75rem + var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
    margin-top: 0;
  }

  .tabbar {
    display: flex;
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 60;
    height: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background: var(--surface);
    border-top: 1px solid var(--line);
  }
  .tabbar a {
    flex: 1 1 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 3px;
    font-size: 0.72rem;
    font-weight: 500;
    color: var(--muted);
    min-height: 44px;
    text-align: center;
    padding: 0 2px;
  }
  .tabbar a[aria-current="page"] { color: var(--accent); }
  .tabbar svg { width: 21px; height: 21px; display: block; }

  /* A 22px-tall link is a miss on a touch screen. Grow the hit area without
     moving the text: inline-flex keeps the baseline, min-height adds the rest.
     :not(.nav-wide) matters — ".nav a" outranks ".nav-wide" on specificity, so
     without it this rule puts the desktop-only links back on screen and the
     header overflows the phone. */
  .nav a:not(.nav-wide), .site-footer a, .brand, .crumbs a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
  /* The trail keeps its own line height so the row does not become a stack. */
  .crumbs { align-items: center; }

  /* Calling the customer is a one-tap job from the panel. */
  .bk-facts a { min-height: 40px; padding: 0.2rem 0; }

  /* Same for the small buttons, which sit at 32px by default. */
  .btn { min-height: 44px; }
  .btn-sm { padding: 0.5rem 1rem; }
  .site-footer .nav { gap: 0.25rem 1.1rem; flex-wrap: wrap; }
}

/* A page with no scrollbar still must not let the fixed bar sit on the last
   line of the footer, which the footer padding above already handles. */

@media (max-width: 640px) {
  .section { padding: 1.4rem 0; }
  .wrap { padding: 0 0.85rem; }
  .card-pad { padding: 0.85rem 0.9rem; }
  h1 { font-size: 1.45rem; }

  /* Two columns of listings, the way every phone marketplace shows them —
     one per row wastes the screen and hides the comparison. */
  .grid-3, .grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.7rem; }
  .photo-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }


  /* Forms stay single column: a 160px-wide text input is not usable. */
  form .grid-2, form .grid-3 { grid-template-columns: minmax(0, 1fr); }

  /* Full-width primary actions, thumb-height. */
  .stack > .btn, .btn-block { width: 100%; }
  .btn { padding: 0.75rem 1.1rem; }

  .price-row { font-size: 0.875rem; }
  .map-frame { height: 12rem; }
}

/* Only the very narrowest phones drop to a single column. */
@media (max-width: 340px) {
  .grid-3, .grid-2, .photo-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================
   Page load

   The hero arrives in order — eyebrow, headline, copy, search,
   figures — instead of all at once. Driven by --step on each
   element, so the order lives in the markup next to the content
   it applies to.

   No fill-mode: the resting style is the finished style, so a
   blocked or unsupported animation leaves the hero fully visible
   rather than stuck at frame zero.
   ============================================================ */
.hero-step { animation: step-in 0.55s cubic-bezier(0.2, 0.7, 0.3, 1) calc(var(--step, 1) * 70ms) backwards; }

@keyframes step-in {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

/* The stand runs its measurement once the hero has landed. */
.stand-car .wh-fl { animation: toe-out-l 2.6s cubic-bezier(0.3, 0.9, 0.3, 1) 0.5s backwards; }
.stand-car .wh-fr { animation: toe-out-r 2.6s cubic-bezier(0.3, 0.9, 0.3, 1) 0.5s backwards; }
.stand-car .wh-rl { animation: toe-out-l 2.6s cubic-bezier(0.3, 0.9, 0.3, 1) 0.62s backwards; }
.stand-car .wh-rr { animation: toe-out-r 2.6s cubic-bezier(0.3, 0.9, 0.3, 1) 0.62s backwards; }

@keyframes toe-out-l {
  0%, 20% { transform: rotate(-7deg); }
  70%     { transform: rotate(1.5deg); }
  100%    { transform: rotate(0deg); }
}
@keyframes toe-out-r {
  0%, 20% { transform: rotate(6.5deg); }
  70%     { transform: rotate(-1.5deg); }
  100%    { transform: rotate(0deg); }
}

/* The readout swaps from the out-of-true figure to the corrected one at the
   moment the wheels reach zero. Both values are in the markup; only which one
   is displayed changes, so the corrected figure is what sits there at rest. */
.stand-was { animation: read-was 2.6s steps(1, end) 0.5s backwards; }
.stand-now { animation: read-now 2.6s steps(1, end) 0.5s backwards; }
@keyframes read-was { 0%, 62% { display: inline; } 63%, 100% { display: none; } }
@keyframes read-now { 0%, 62% { display: none; } 63%, 100% { display: inline-flex; } }

/* ============================================================
   Reveal on scroll

   app.js adds .reveal-ready once it has an IntersectionObserver
   watching. Without the class nothing is hidden, so the content
   is visible with JavaScript off or still loading.
   ============================================================ */
.reveal-ready .reveal,
.reveal-ready .reveal-group > * {
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.5s ease, transform 0.5s cubic-bezier(0.2, 0.6, 0.3, 1);
}
.reveal-ready .reveal.is-in,
.reveal-ready .reveal-group > .is-in {
  opacity: 1;
  transform: none;
}
/* Cards in a row arrive one after another rather than as a block. */
.reveal-group > :nth-child(2) { transition-delay: 0.06s; }
.reveal-group > :nth-child(3) { transition-delay: 0.12s; }
.reveal-group > :nth-child(4) { transition-delay: 0.18s; }
.reveal-group > :nth-child(5) { transition-delay: 0.24s; }
.reveal-group > :nth-child(6) { transition-delay: 0.3s; }

/* ============================================================
   Motion is an enhancement. Turned off, everything still lands
   in its final state — nothing depends on an animation running.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
  a.card:hover { transform: none; }
  a.card:hover .card-photo img { transform: none; }
  /* The wheels stay at their resting camber; the mark is the logo, not the
     animation, so nothing is lost. Likewise the hero simply appears, and the
     stand shows the corrected reading it would have ended on. */
  .mark-play .mark-wheel-l, .mark-play .mark-wheel-r,
  .hero-step, .stand-car .wh, .stand-was, .stand-now, .stand-dot,
  .modal-backdrop, .modal-card, .modal-card > * { animation: none; }
  .stand-was { display: none; }
  .stand-now { display: inline-flex; }
  .reveal-ready .reveal,
  .reveal-ready .reveal-group > * { opacity: 1; transform: none; }
}
