﻿@charset "UTF-8";
/* ==========================================================================
   CarHub — components.css
   Buttons, cards, forms, filter rail, tables, badges, chips, pagination.

   NO COLOUR VALUE MAY APPEAR IN THIS FILE. Every colour is var(--token).
   tools/theme-audit.py enforces it.

   Semantic rules this file obeys, and must go on obeying:
     .btn--action  is --action. Every primary call to action is orange.
     .badge--live / .countdown / .dot--live are --live. Auctions only.
     .badge--verified is --ok. .badge--featured is --warning. REDESIGN-01
     retired --gold: the redesign palette makes verified GREEN and featured
     AMBER, which is two claims in two colours rather than one gold badge
     doing both jobs. docs/CARHUB_REDESIGN.md section 3.
   ========================================================================== */

/* ==========================================================================
   1. BUTTONS
   ========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: var(--control-h);
  padding-inline: var(--sp-5);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
.btn:hover { text-decoration: none; }

/* The primary call to action. Orange, on every theme. This is the answer to
   "the buttons look dull" and it is not a per-page decision. */
/* --on-action is near-black rather than white, and that is measured rather
   than chosen. White on #FF6A00 is 2.87:1 and fails WCAG AA at every size;
   near-black is 5.58:1. The FILL is the client's brand orange, untouched —
   see the note in theme-indigo.css and tools/contrast-check.py. */
.btn--action {
  background: var(--action);
  color: var(--on-action);
  font-weight: var(--fw-bold);
}
.btn--action:hover { background: var(--action-hover); box-shadow: var(--shadow-2); }

.btn--primary {
  background: var(--primary);
  color: var(--on-primary);
}
.btn--primary:hover { background: var(--primary-hover); }

.btn--outline {
  background: var(--surface);
  border-color: var(--border-strong);
  color: var(--primary);
}
.btn--outline:hover {
  border-color: var(--primary);
  background: var(--primary-soft);
}

.btn--ghost { color: var(--primary); }
.btn--ghost:hover { background: var(--primary-soft); }

.btn--danger {
  background: var(--danger);
  color: var(--text-inverse);
}

/* .btn--sm is a DESKTOP compaction and never the only way to reach anything:
   docs/CARHUB_REDESIGN.md section 14 asks for 44px touch targets, so below
   the phone breakpoint every button goes back to --control-h. See section 18. */
.btn--sm { min-height: var(--control-h-sm); padding-inline: var(--sp-3); font-size: var(--fs-xs); }
.btn--lg { min-height: var(--control-h-lg); padding-inline: var(--sp-6); font-size: var(--fs-base); }
.btn--block { width: 100%; }

/* A button on a dark band. It has to be visible against --surface-dark, and
   its focus ring has to be too. */
.btn--onDark {
  border-color: var(--border-dark);
  background: var(--surface-dark-2);
  color: var(--text-inverse);
}
.btn--onDark:hover { background: var(--primary); }
.btn--onDark:focus-visible { outline-color: var(--focus-ring-dark); }

.btn[disabled],
.btn[aria-disabled="true"] {
  opacity: .5;
  cursor: not-allowed;
  pointer-events: none;
}

/* ==========================================================================
   2. CARDS — THE VEHICLE CARD                        REDESIGN-01, Part 6
   ==========================================================================
   ONE CARD, SIX PAGES. The home page's featured grid, the city listing grid,
   the similar-cars strip on a car detail page, a dealer profile's stock, the
   dealer directory strip and my-ads all draw this. It is redesigned once,
   here, and every one of those six was looked at afterwards.

   docs/CARHUB_REDESIGN.md section 9:
     4:3 image, clean crop, rounded TOP corners
     top-left status badge: Featured / Verified / Auction
     title on one or two lines
     specs line: year, fuel, mileage
     location with a small icon
     price is the strongest text after the title
     hover raises the elevation and PRESERVES THE LAYOUT

   TWO THINGS FROM THAT SECTION ARE DELIBERATELY NOT BUILT.

   The heart. There is no favourites table in docs/DB_SCHEMA.md, so a heart
   would be a control that forgets what it was told the moment the page
   reloads. That is a feature and it belongs in its own cycle, with a table
   behind it.

   "New" as a status. This site sells used cars — docs/CARHUB_MASTER.md — so
   the third badge is Auction, which is a state an ad on this site is actually
   in.
   ========================================================================== */
.card {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-1);

  /* The lift is a transform, and that is the whole trick. Changing the border
     width or the margin on hover reflows the row and every card beside it
     jumps; translateY moves one card in its own layer and the grid does not
     know it happened. docs/CARHUB_REDESIGN.md section 5: "small lift/shadow
     increase; no dramatic animation". */
  transition: box-shadow var(--dur-base) var(--ease),
              transform var(--dur-base) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.card:hover {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-2);
  transform: translateY(-3px);
}
/* Under prefers-reduced-motion the durations collapse to 1ms in tokens.css,
   but a 3px jump is still a jump. The shadow alone carries the hover. */
@media (prefers-reduced-motion: reduce) {
  .card:hover { transform: none; }
}

/* Fixed 4:3 so the grid does not shift while photographs load.
   docs/ARCHITECTURE.md section 3. --surface-alt rather than white, so an ad
   with no photograph is an empty slot rather than a hole. */
.card__media {
  position: relative;
  aspect-ratio: var(--card-aspect);
  background: var(--surface-alt);
  overflow: hidden;
}
/* THE PHOTOGRAPH IS A LINK — REDESIGN-03b Part 3. Only the title linked
   before, and most people click the picture.

   It fills the media box and nothing else does, so the badges and the photo
   count — which are position:absolute with z-index 2 over it — sit ON the
   photograph and outside the link. The card does not become one giant hit
   area, and a badge is still a label rather than a destination. */
.card__shot {
  position: absolute;
  inset: 0;
  display: block;
}
.card__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--dur-base) var(--ease);
}
/* A photograph that eases in 2% under a fixed 4:3 frame. The FRAME does not
   move, so nothing round it reflows — this is inside overflow:hidden. */
.card:hover .card__media img { transform: scale(1.02); }
@media (prefers-reduced-motion: reduce) {
  .card:hover .card__media img { transform: none; }
}

/* ---- The status badge, top-left ----------------------------------------
   Three states and three tokens, and the tokens ARE the semantic rule:

     Featured  --warning   a promoted position. Amber.
     Verified  --ok        a trusted seller. Green.
     Auction   --live      there is a clock running. Red, and --live is
                           auctions and nothing else on this whole site.

   The plain badge stays --primary and carries a WORD rather than a status:
   "Dealer" is a fact about who is selling, not a claim about the car. */
.card__badge {
  position: absolute;
  top: var(--sp-3);
  left: var(--sp-3);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 5px var(--sp-3);
  border-radius: var(--radius-pill);
  background: var(--primary);
  color: var(--on-primary);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: .02em;
  line-height: 1.35;
  box-shadow: var(--shadow-1);
}
.card__badge--auction  { background: var(--live);    color: var(--on-live); }
.card__badge--featured { background: var(--warning); color: var(--on-warning); }

/* .card__badge--verified EXISTS NOW AND IT HAS A COLUMN BEHIND IT.
   REDESIGN-01 left this rule unwritten and said why: docs/CARHUB_REDESIGN.md
   section 9 lists Verified as one of the three card states, but nothing on
   the card's projection sourced the claim — AdRow knew the ad had a
   dealer_id, which is not the same thing as dealers.is_verified — and
   changing a query for a visual reason was the one thing that cycle was not
   allowed to do. REDESIGN-01b Part 5 authorised that single column, so the
   badge is drawn from dealers.is_verified and makes the same claim, in the
   same --ok, as .card__tick on a dealer card and .badge--verified on a
   dealer profile.

   THE FILL IS --ok-text RATHER THAN --ok, AND THAT IS A MEASUREMENT. The
   other two badges are a solid fill with a legible word on it, so this one
   has to be as well. White on --ok (#16A34A) is 3.30:1 and fails AA for a
   12px word; white on --ok-text (#128040) is 5.02:1 and passes. No new token
   was invented for it: --ok-text is the theme's own answer to "this green
   has to carry text", and every theme already defines it. */
.card__badge--verified {
  background: var(--ok-text);
  color: var(--on-primary);
}

.card__photos {
  position: absolute;
  right: var(--sp-3);
  bottom: var(--sp-3);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 4px var(--sp-2);
  border-radius: var(--radius-sm);
  background: var(--overlay);
  color: var(--on-primary);
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs);
  line-height: 1.4;
}

.card__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-4);

  /* Equal card heights, and the price pinned to the bottom of them, so that
     a row of four cards has its four prices on one line whatever the titles
     above them did — the price is what the eye scans a row for.

     An auction card spends about 68px on the bid strip that a for-sale card
     does not have, so the for-sale body carries that difference as a minimum
     height instead. Both types then come out the same height in the same grid
     and neither photograph is stretched to make up the difference.

     216, not the 256 it was before REDESIGN-01: the specs line lost
     transmission and the card gained a location line, which is a net wash,
     and the old figure left a visible hole above the price on a card with a
     one-line title. */
  min-height: 216px;
}
.card--auction .card__body { min-height: 148px; }

/* One or two lines, and then an ellipsis. A three-line title on one card in a
   row of four pushes the price of that card out of line with the other three,
   and the price is the thing the eye is scanning down the row for.

   AND ALWAYS TWO LINES' WORTH OF HEIGHT, WHETHER OR NOT THE TITLE FILLS THEM.
   REDESIGN-03b Part 5. The clamp was here from REDESIGN-01 and it was only
   half the rule: it stopped a long title running to three lines and did
   nothing about a short one taking up one, so "Toyota Corolla Altis Grande
   1.8" and "Honda City 1.2 CVT" made two different card heights in the same
   row and put their prices on two different levels. The min-height is the
   clamp's own arithmetic — two lines of this font size at this line height —
   so it cannot drift away from the clamp when either changes. */
.card__title {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--text);
  line-height: var(--lh-snug);
  min-height: calc(2em * var(--lh-snug));
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.card__title a { color: inherit; }
.card__title a:hover { color: var(--primary); text-decoration: none; }

/* Year, fuel, mileage. Tabular, so "78,000 km" and "9,000 km" line up down a
   column of cards. */
.card__specs {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-sm);
  color: var(--text-2);
}

/* The location line, with its pin. The icon is 12px and the row is one line:
   a city that wraps under its own pin reads as two facts.

   .card__loc and NOT .card__where. The dealer card has owned .card__where
   since BUILD-05b and it says something different — a city and an area, with
   no icon. Two components sharing a class name because the words are similar
   is how one of them breaks when the other is tuned. */
.card__loc {
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  font-size: var(--fs-sm);
  color: var(--text-2);
}
.card__loc svg { flex: none; color: var(--text-3); }
.card__loc span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* THE PRICE IS THE STRONGEST TEXT AFTER THE TITLE — section 9 — and it is
   --action-text rather than --action. #FF6A00 on white is 2.87:1, which fails
   AA even for large text; the darkened value is 5.50:1 and is the same hue.
   `margin: auto 0 0` pins it to the bottom of the body, so the price sits on
   one line across a row of four cards whatever the titles above it did. */
.card__price {
  margin: auto 0 0;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-price);
  font-weight: var(--fw-bold);
  color: var(--action-text);
  letter-spacing: -.01em;
}

/* ONE LINE UNDER THE PRICE, ON A CAR ON LEASE AND NOWHERE ELSE. LEASE-01
   Part 4: the card's headline figure is the down payment and this is the
   instalments underneath it — "36 instalments of PKR 1.2 lac".

   .card__price carries `margin: auto 0 0`, which pins the pair to the bottom
   of the body; this one must NOT, or the two would be pushed apart by the
   same auto margin and the note would sit on the foot. Secondary weight and
   --text-2, because the down payment above it is the figure being compared
   and a second bold number beside it would be two headlines. */
.card__note {
  margin: 2px 0 0;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-sm);
  color: var(--text-2);
}

/* The seller line. Subtle and last — section 9 calls it "a subtle final
   line", and it must not compete with the price above it. */
.card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  margin-top: var(--sp-1);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
  color: var(--text-2);
  font-size: var(--fs-xs);
}
.card__seller { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- The auction card --------------------------------------------------- */
.card--auction { border-color: var(--live); }

.card__bid {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--border);
  background: var(--live-soft);
}
.card__bid .k { display: block; font-size: var(--fs-xs); color: var(--text-2); }
.card__bid .v {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-price);
  font-weight: var(--fw-bold);
  color: var(--live-text);
}
.card__bid .bids {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs);
  color: var(--text-2);
}

/* ---- A closed auction, ported from the demo for BUILD-09b ----------------
   /auctions/ shows finished auctions beside running ones, so the two have to
   read apart at a glance across a grid. The --live border and the --live bid
   strip come off and the card drops back to the ordinary surface: --live is
   for auctions with the clock going and nothing else (docs/THEMING.md
   section 4), and a closed auction wearing it is the one thing that would
   make the whole colour meaningless.

   THE PHOTOGRAPH DESATURATES RATHER THAN DISAPPEARING: the car is still the
   thing being identified. And the outcome is spelled out in a badge as well,
   so the grey is never the only signal — the same rule as every other status
   on the site.

   REDESIGN-01 FIXED A REAL BUG IN THESE FIVE LINES. They read var(--bg-alt),
   which no theme in this application has ever defined, so both background
   declarations did nothing and a closed auction kept a white bid strip.

   --bg-alt IS a token in demo/assets/css/carhub.css, and that is how it got
   here: these lines were ported from that file, lines 780-785, without
   translating the two names the two stylesheets spell differently. The demo's
   --bg-alt is this application's --surface-alt, and the demo's --accent is
   --live. Porting from the demo is how most of this file was written and it is
   the right way to build this site; the NAMES are what has to be translated.

   theme-audit.py T4 was added for this and catches it now. T2 could not: it
   checks that every theme defines the contract, not that every var() in a
   component names something in it. */
.card--closed { border-color: var(--border); background: var(--surface-alt); }
.card--closed .card__bid { background: var(--surface-alt); }
.card--closed .card__bid .v { color: var(--text-2); }
.card--closed .card__badge { background: var(--text-2); color: var(--on-live); }
.card--closed .card__media img { filter: grayscale(.85); opacity: .8; }
.card--closed .card__title a { color: var(--text-2); }
.card--closed:hover { transform: none; }

/* ==========================================================================
   3. BADGES, CHIPS AND THE LIVE DOT
   ========================================================================== */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 2px var(--sp-2);
  border-radius: var(--radius-pill);
  background: var(--surface-alt);
  color: var(--text-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  line-height: 1.6;
}

/* Each status badge is a TINT with the darkened TEXT value on it, never the
   raw fill. --ok on --ok-soft measures 3.06:1 and --warning on --warning-soft
   2.06:1; the -text values are 4.50:1 and 4.58:1. The raw value stays for the
   fill, the tick and the rule — tools/contrast-check.py has the whole table. */
.badge--ok      { background: var(--ok-soft);      color: var(--ok-text); }
.badge--danger  { background: var(--danger-soft);  color: var(--danger-text); }
.badge--warning { background: var(--warning-soft); color: var(--warning-text); }

/* --live is auctions, countdowns and the LIVE dot. Nothing else. */
.badge--live {
  background: var(--live-soft);
  color: var(--live-text);
}
.badge--live-solid {
  background: var(--live);
  color: var(--on-live);
}

/* REDESIGN-01 SPLIT THESE TWO. They were one gold badge doing two jobs, and
   the two jobs are different claims: Verified says an administrator has seen
   the seller, Featured says the ad has a promoted position. A tick the same
   colour as a promotion is a tick nobody trusts. docs/CARHUB_REDESIGN.md
   section 3 gives them green and amber; --gold is gone from the contract. */
.badge--verified {
  background: var(--ok-soft);
  border: 1px solid var(--ok);
  color: var(--ok-text);
}
.badge--featured {
  background: var(--warning-soft);
  border: 1px solid var(--warning);
  color: var(--warning-text);
}

/* A pill. An outlined, neutral tag: a dealer's specialisation, a fact in a
   page header. NOT a badge — a badge carries a status and a semantic colour,
   and a pill carries a word. Keeping them apart is what stops "Toyota" being
   drawn in --ok because it happened to sit beside a tick.

   As an <a> it is a link into the directory filtered on that tag. */
.pill {
  display: inline-flex;
  align-items: center;
  padding: 3px var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text-2);
  font-size: var(--fs-xs);
}
a.pill:hover {
  border-color: var(--primary);
  color: var(--primary);
  text-decoration: none;
}

.dot--live {
  width: 8px; height: 8px;
  border-radius: var(--radius-pill);
  background: var(--live);
  flex: 0 0 auto;
}

/* The clock. Three boxes on a card, and --live because a countdown is an
   auction. base.css already gives .countdown the mono family and tabular
   figures, so the digits do not jitter as they tick. */
.countdown { display: flex; gap: var(--sp-1); }
.countdown__box {
  min-width: 34px;
  padding: 3px 4px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  text-align: center;
}
.countdown__box b {
  display: block;
  font-family: var(--font-num);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  color: var(--live-text);
  font-variant-numeric: tabular-nums;
}
.countdown__box span {
  display: block;
  font-size: 9px;
  letter-spacing: .06em;
  color: var(--text-2);
}
.countdown.is-closed {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  color: var(--danger-text);
  align-items: center;
}

/* A removable filter chip.

   REDESIGN-02 Part 3: THE WHOLE CHIP IS THE LINK, not a 12px cross inside it,
   and it is 36px tall rather than 28. The only thing anybody ever wants to do
   to an applied filter is take it off, so the chip is that action; the cross
   is now an icon on a button-sized target rather than the target itself.
   Still an <a href>, so it still works with no script. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 36px;
  padding-inline: var(--sp-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-xs);
}
.chip--filter {
  border-color: color-mix(in srgb, var(--action) 45%, var(--border-strong));
  background: color-mix(in srgb, var(--action) 9%, var(--surface));
  color: var(--action-text);
  font-weight: var(--fw-semibold);
}
.chip--filter:hover {
  border-color: var(--action);
  text-decoration: none;
}
.chip__x {
  display: grid;
  place-items: center;
  width: 20px; height: 20px;
  border: 0;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--action) 18%, var(--surface));
  color: var(--action-text);
  cursor: pointer;
  line-height: 1;
}
.chip--filter:hover .chip__x {
  background: var(--action);
  color: var(--on-action);
}

/* ==========================================================================
   4. FORMS
   ========================================================================== */
.field { margin-bottom: var(--sp-3); }

.field__label {
  display: block;
  margin-bottom: var(--sp-1);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
}

.field__help {
  margin-top: var(--sp-1);
  color: var(--text-2);
  font-size: var(--fs-xs);
}

.input,
.select,
.textarea {
  width: 100%;
  min-height: var(--control-h);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-sm);
  transition: border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
.input:hover, .select:hover, .textarea:hover { border-color: var(--primary); }
.input:focus, .select:focus, .textarea:focus {
  border-color: var(--primary);
  outline: 2px solid var(--focus-ring);
  outline-offset: 1px;
}
.textarea { min-height: 120px; resize: vertical; line-height: var(--lh-base); }

/* A select is a control and it should look like one. Without this it is the
   platform widget with a platform arrow, which on Windows is a grey square
   that does not match anything else on the page. The chevron is drawn with a
   gradient rather than an SVG data URI because a data URI carrying a stroke
   colour is a colour value in a stylesheet, and tools/theme-audit.py rule T1
   decodes percent-encoding before it looks. currentColor keeps it on the
   theme. */
.select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: var(--sp-7);
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position:
    right calc(var(--sp-4) + 5px) center,
    right var(--sp-4) center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.select::-ms-expand { display: none; }

.input::placeholder, .textarea::placeholder { color: var(--text-3); }

.input--error,
.select--error,
.textarea--error,
.input[aria-invalid="true"],
.select[aria-invalid="true"],
.textarea[aria-invalid="true"] {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px var(--danger-soft);
}

/* ---- The error line, and WHY IT IS ALWAYS THERE ------------------------
   REDESIGN-03 Part 1: "never shifting the layout when they appear".

   validate.js writes into the span the tag helper already rendered rather
   than creating one, which was half the answer — but the span was
   display:none while it was empty, so the first failed blur on a forty-field
   form pushed everything under it down by a line. On a phone that is the
   field the seller is looking at jumping out from under his thumb.

   So the line is RESERVED whether or not there is a message in it: one line
   of --fs-xs at --lh-snug, always. It costs about 20px per field and it is
   bought back out of .field's bottom margin, which drops from --sp-4 to
   --sp-3 for exactly this reason. A message that wrapped to two lines would
   shift the page again, so the messages are short enough not to at the
   narrowest column the form uses — verification 3 measures it rather than
   assuming it.

   --danger-text and not --danger: #DC2626 on white is 4.03:1 and this is
   12px text. #B91C1C is 5.94:1. docs/THEMING.md section 3 is the rule and
   this line was breaking it. */
.field__error,
.field-validation-error {
  display: block;
  min-height: calc(var(--fs-xs) * var(--lh-snug));
  margin-top: var(--sp-1);
  color: var(--danger-text);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
}
/* Beats .field-validation-valid { display:none } on specificity, not on
   order: the empty span is what holds the space open. */
.field__error.field-validation-valid { display: block; }

/* The 1,000-character description counter. It turns red in the last 40
   characters — docs/AD_FORM_SPEC.md — and the red is --danger-text rather
   than --danger for the same reason .field__error's is: this is 12px text on
   white, #DC2626 measures 4.03:1 and #B91C1C measures 5.94:1.
   docs/THEMING.md section 3 is the rule. */
.counter {
  color: var(--text-2);
  font-size: var(--fs-xs);
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
}
.counter--warn { color: var(--danger-text); font-weight: var(--fw-bold); }

/* City and "Registered in" are two different facts and are never laid out as
   a pair. A hairline rule separates them — docs/AD_FORM_SPEC.md.

   The rule that separates them is the VERTICAL one in section 14. The
   horizontal one that used to be here as well is gone: since REDESIGN-03 the
   group's note carries a border under it, and two hairlines four pixels apart
   read as a mistake rather than as a separation. */
.fieldrow--split { padding-top: 0; }

/* ---- The tick box, and it is 44px now ---------------------------------
   REDESIGN-02 measured this at 22px and could not raise it: the filter rail
   and the posting form share the class, and the rail had just been rebuilt
   around its own 44px override. REDESIGN-03 Part 1 raises the base, which is
   what "44px minimum on every control at every width" means for the twenty
   feature tick boxes on step 1, the Remember me on the sign-in card and the
   terms box on the register card — none of which had ever been more than
   thirteen pixels of actual target.

   THE LABEL IS THE TARGET AND THE BOX STAYS SMALL, the same decision the rail
   made and for the same reason: a 44px tick box looks like a mistake. The row
   is 44px tall, the whole row is inside the <label>, and the box in it is
   18px — the size a checkbox is supposed to be.

   .rail .check overrides the padding and the gap for the rail's tighter
   sixteen groups and is unaffected by this; verification 2 of this cycle
   re-measures the rail afterwards, because that is exactly the sort of change
   that fixes one screen and quietly loosens another. */
.check {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--control-h);
  padding-block: var(--sp-1);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  cursor: pointer;
}
.check input {
  flex: none;
  width: 18px;
  height: 18px;
  accent-color: var(--primary);
}
.check__count { margin-left: auto; color: var(--text-3); font-size: var(--fs-xs); }

/* A ticked feature reads as ticked without having to find the box. It is the
   same wash the rail's selected option uses, mixed from --primary and
   --surface so that a theme carries it and no token is added. */
.checkcols .check {
  padding-inline: var(--sp-2);
  margin-inline: calc(var(--sp-2) * -1);
  border-radius: var(--radius-sm);
}
.checkcols .check:hover { background: var(--surface-alt); }
.checkcols .check:has(input:checked) {
  background: color-mix(in srgb, var(--primary) 7%, var(--surface));
  color: var(--primary);
  font-weight: var(--fw-medium);
}

/* ==========================================================================
   5. FILTER RAIL                                     REDESIGN-02, Part 3
   ==========================================================================
   Sixteen groups is a lot of vertical space and BUILD-05's rail read as a wall
   of controls. Four things changed and none of them is decoration:

   CLEAR GROUP HEADINGS. The heading is the whole width of the rail, 44px tall,
   set in the display face at --fw-semibold with an uppercase eyebrow weight to
   it, and it carries the chevron. It is a button, so it is also the thing that
   opens and closes the group.

   TIGHTER OPTION ROWS WITH A 44px TOUCH AREA, WHICH IS NOT A CONTRADICTION.
   REDESIGN-01 measured the rail's checkboxes at 13px and handed the number
   here. The BOX is still small — a 44px checkbox looks like a mistake — but
   THE LABEL IS THE CONTROL, and the label is now the full width of the rail
   and --control-h tall. That is 236 x 44 against 13 x 13, and the visible row
   is tighter than before rather than looser: the padding came out, the count
   is right-aligned on the baseline, and the swatch shrank from 20px to 16px.

   The rail is NOT twice as tall for it, because the long groups fold — see
   _RailGroup.cshtml. Measured at 1366px on a 100-ad Lahore listing: 2,214px
   before, and the figure after is in the REDESIGN-02 report.

   SELECTED STATE IN THE ACTION COLOUR. --action-soft behind the row, --action
   on the accent bar down its inside edge, and the label in --action-text —
   which is the darkened orange, 5.50:1 on white, because raw #FF6A00 is
   2.87:1. docs/THEMING.md section 3a-ii.

   A COUNT AGAINST EVERY OPTION, and an option with nought behind it is dimmed
   and sinks to the bottom of its group rather than disappearing. A make that
   vanishes when it reaches nought is a make the visitor cannot see he has
   filtered away.
   ========================================================================== */
.rail {
  position: sticky;

  /* BELOW the sticky header, not under it. --header-h is the header's own
     height, so the rail parks --sp-4 clear of its bottom edge instead of
     --sp-4 from the top of the window. QA-01 A-03: at 16px the 71px header
     covered the whole "Filters / Clear all / X" row, and a real pointer click
     on "Clear all" landed on the header and did nothing.

     The header's 1px bottom border needs no term of its own: it is drawn
     inside the 16px gap this leaves, so there is nothing to subtract.

     Same expression as the max-height below, which had it right all along. */
  top: calc(var(--header-h) + var(--sp-4));
  display: flex;
  flex-direction: column;

  /* Sixteen groups are taller than a laptop window, so the rail scrolls
     inside itself and stays beside the results instead of sticking once and
     then scrolling away. --header-h is the sticky header above it.

     Its bottom still lands inside the window: the rail starts at
     --header-h + --sp-4 (86px) and runs 100vh - --header-h - --sp-5 tall, so
     it ends 8px above the fold. */
  max-height: calc(100vh - var(--header-h) - var(--sp-5));

  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  overflow: hidden;
}

.rail form {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* The part between the fixed head and the fixed foot. */
.rail__scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* The sheet's grab bar. Not drawn on a desktop at all. */
.rail__grab { display: none; }

.rail__group { border-bottom: 1px solid var(--border); }
.rail__group:last-child { border-bottom: 0; }

.rail__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  width: 100%;
  min-height: var(--control-h);
  padding-inline: var(--sp-4);
  border: 0;
  background: none;
  color: var(--text);
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  text-align: left;
  cursor: pointer;
}
.rail__head:hover { background: var(--surface-alt); }
.rail__head svg { flex: none; color: var(--text-3); }

.rail__body { padding: 0 var(--sp-3) var(--sp-3); }

.rail__note {
  padding: var(--sp-3) var(--sp-4);
  color: var(--text-2);
  font-size: var(--fs-xs);
  line-height: var(--lh-snug);
}

/* Colour swatches in the rail read colours.hex from the database. That hex is
   car paint, not a design token — docs/DB_SCHEMA.md. The value arrives as an
   inline style on the element, never from this file. */
.swatch {
  width: 16px; height: 16px;
  flex: none;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  display: inline-block;
}

/* ==========================================================================
   6. TABLES
   ========================================================================== */
.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}
.table th, .table td {
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border);
  text-align: left;
  vertical-align: middle;
}
.table th {
  background: var(--surface-alt);
  color: var(--text-2);
  font-weight: var(--fw-medium);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: .04em;
}
.table tbody tr:hover { background: var(--surface-alt); }
.table .num { text-align: right; }

.table-scroll { overflow-x: auto; }

/* ==========================================================================
   7. PAGINATION
   Ordinary crawlable links. docs/PAGES.md — /used-cars/lahore/page-2/
   ========================================================================== */
.pager {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
  margin-block: var(--sp-5);
}
.pager a, .pager span {
  display: inline-grid;
  place-items: center;
  min-width: 36px; height: 36px;
  padding-inline: var(--sp-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: var(--fs-sm);
}
.pager a:hover { border-color: var(--primary); text-decoration: none; }
.pager [aria-current="page"] {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--on-primary);
  font-weight: var(--fw-bold);
}

/* ==========================================================================
   8. NOTICES AND EMPTY STATES
   ========================================================================== */
.notice {
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-left: 3px solid var(--primary);
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
  font-size: var(--fs-sm);
}
.notice--ok      { border-left-color: var(--ok);      background: var(--ok-soft); }
.notice--danger  { border-left-color: var(--danger);  background: var(--danger-soft); }
.notice--warning { border-left-color: var(--warning); background: var(--warning-soft); }
.notice--live    { border-left-color: var(--live);    background: var(--live-soft); }

.empty {
  padding: var(--sp-7) var(--sp-4);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--text-2);
  text-align: center;
}

/* ==========================================================================
   9. BREADCRUMB
   ========================================================================== */
.crumbs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-block: var(--sp-3);
  color: var(--text-2);
  font-size: var(--fs-xs);
}
.crumbs a { color: var(--text-2); }
.crumbs a:hover { color: var(--primary); }

/* ==========================================================================
   10. SITE HEADER AND FOOTER                         REDESIGN-01, Part 4
   ==========================================================================
   docs/CARHUB_REDESIGN.md section 6. Sticky, 70px — the middle of the
   document's 68-72 band. Logo left, the four destinations centre, account and
   the orange Post Your Ad right. Mobile: logo, account icon, compact menu.

   WHITE ON AN ORDINARY PAGE, --surface-dark OVER THE HOME PAGE HERO, and the
   dark one is a solid fill rather than a transparent overlay. A transparent
   header is correct for exactly as long as the hero is under it; the moment
   somebody scrolls, white nav links are sitting on white cards. Solid navy
   stays legible at every scroll position and still reads as one block with
   the hero beneath it.

   THE ACTIVE STATE IS --action, not an underline. Section 6 is explicit about
   that, and it is why nav links carry a bottom rule that is transparent until
   the page is the current one — the space is reserved either way, so nothing
   shifts when a link becomes current.

   TWO THINGS FROM THE APPROVED MOCKUP ARE NOT HERE, AND WERE NOT OVERSIGHTS.

   "Car Advice" is BUILD-11 content and is not built. A nav item that 404s
   costs more than the item is worth.

   The Saved / favourites state. There is no favourites table, so there is
   nothing for it to count. It comes back with the feature.
   ========================================================================== */
.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  min-height: var(--header-h);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.site-header .wrap {
  min-height: var(--header-h);
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

/* ---- The dark treatment, over the home page hero ----------------------- */
.site-header--dark {
  background: var(--surface-dark);
  border-bottom-color: transparent;
}
.site-header--dark .wordmark     { color: var(--text-inverse); }
.site-header--dark .wordmark__accent { color: var(--action); }
.site-header--dark .nav a        { color: var(--text-on-dark-2); }
.site-header--dark .nav a:hover  { background: var(--surface-dark-2); color: var(--text-inverse); }
.site-header--dark .nav a[aria-current="page"] { color: var(--action); }
.site-header--dark .acct__btn    { border-color: var(--border-dark); background: transparent; color: var(--text-inverse); }
.site-header--dark .acct[open] > .acct__btn { background: var(--surface-dark-2); border-color: var(--action); }
.site-header--dark .acct--member .acct__btn { color: var(--text-inverse); }
.site-header--dark .btn--ghost   { color: var(--text-inverse); }
.site-header--dark .btn--ghost:hover { background: var(--surface-dark-2); }
.site-header--dark .menu-btn     { border-color: var(--border-dark); background: transparent; color: var(--text-inverse); }
.site-header--dark .nav.is-open  { background: var(--surface-dark); border-bottom-color: var(--border-dark); }

/* The one measured reason this rule exists: --focus-ring on --surface-dark is
   1.24:1. A keyboard user tabbing the nav over the hero would see nothing. */
.site-header--dark :focus-visible { outline-color: var(--focus-ring-dark); }

/* ---- Brand -------------------------------------------------------------
   THE WORDMARK. REDESIGN-01b Part 4 — see Views/Shared/_BrandLockup.cshtml
   for why the letters are live type in the site's own face rather than an SVG
   path, and docs/THEMING.md section 9 for the whole rule.

   What went: .brand__mark, a 34px square of --primary with two initials in
   it, drawn beside the name. Two marks for one brand, and the square was the
   louder of them.

   TWO COLOURS AND BOTH OF THEM ARE TOKENS. The head of the name takes --text,
   the accent takes the action colour, and NOTHING here is a literal — a
   reseller who switches to theme-emerald gets a green accent without touching
   a file, which is the whole point of tools/theme-audit.py T1.

   THE ACCENT IS --action-text ON A LIGHT SURFACE AND --action ON A DARK ONE,
   AND THAT IS A MEASUREMENT RATHER THAN A PREFERENCE. #FF6A00 on white is
   2.87:1; the same orange darkened for text, #B34700, is 5.50:1. It is the
   rule docs/THEMING.md section 3 already sets for every action-coloured word
   on a light surface, and a wordmark is a word. On --surface-dark the raw
   brand orange measures 6.04:1 and is used undarkened, so the dark header and
   the footer carry the client's exact colour. */
.brand { display: flex; align-items: center; gap: var(--sp-2); flex: none; }
.brand:hover { text-decoration: none; }

.wordmark {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  letter-spacing: -.02em;
  line-height: 1;
  color: var(--text);
  white-space: nowrap;
}
.wordmark__accent { color: var(--action-text); }

/* Site:LogoPath, when the client has uploaded one. Empty falls back to the
   wordmark, which is a real fallback and not a broken image. */
.brand__logo { display: block; max-height: 36px; width: auto; }

/* ---- Nav ---------------------------------------------------------------
   Centred: auto margins on both sides push it off the brand and off the
   account area, so it stays in the middle of the row whatever the brand name
   is called and whether or not somebody is signed in. */
.nav {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  margin-inline: auto;
}
/* white-space: nowrap so a label can never break in the middle. A nav item is
   a flex child, and a flex child under pressure answers by wrapping its own
   text -- "Buy Cars" over two lines inside its pill -- rather than by telling
   anybody the row is too narrow. The row folds into the menu button at the
   width where it genuinely stops fitting instead; see the 940px block. */
.nav a {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  min-height: var(--control-h);
  padding: 0 var(--sp-3);
  border-radius: var(--radius-sm);
  border-bottom: 2px solid transparent;
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
}
.nav a:hover { background: var(--surface-alt); color: var(--primary); text-decoration: none; }

/* --action, and the 2px rule is reserved above whether it is drawn or not, so
   becoming the current page moves nothing. */
/* The dealer link — FIX-06 Part 1a. In the folded menu on a phone and
   nowhere in the desktop row, which stays six items; the account menu carries
   it at every width. The 940px block below shows it. */
.nav a.nav__fold { display: none; }

.nav a[aria-current="page"] {
  color: var(--action-text);
  border-bottom-color: var(--action);
  font-weight: var(--fw-bold);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}

/* Sign in / Register free when anonymous; Post your ad when signed in. */
.auth { display: flex; align-items: center; gap: var(--sp-2); flex: none; }

/* The account control. Anonymous, it is a phones-only fallback for the two
   auth buttons. Signed in, it is the named menu and is shown at every width,
   because it is the way back to My ads from anywhere on the site. */
.acct { display: none; position: relative; flex: none; }
.acct__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--control-h);
  height: var(--control-h);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--primary);
  cursor: pointer;
}
/* A <summary> since FIX-06, so the browser opens the menu with no script.
   These three take off the disclosure triangle every engine draws on one. */
.acct > summary { list-style: none; }
.acct > summary::-webkit-details-marker { display: none; }
.acct > summary::marker { content: ""; }

.acct[open] > .acct__btn {
  border-color: var(--primary);
  background: var(--primary-soft);
}
.acct__menu {
  position: absolute;
  right: 0;
  top: calc(100% + var(--sp-2));
  z-index: 45;
  min-width: 190px;
  padding: var(--sp-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  box-shadow: var(--shadow-3);
}
.acct__group { display: flex; flex-direction: column; }
.acct__menu a,
.acct__menu button {
  display: block;
  width: 100%;
  padding: var(--sp-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  text-align: left;
  cursor: pointer;
}
.acct__menu a:hover,
.acct__menu button:hover {
  background: var(--surface-alt);
  color: var(--primary);
  text-decoration: none;
}
.acct__menu form { margin: 0; }
.acct__menu hr { margin: var(--sp-2) 0; }

.acct--member { display: block; }
.acct--member .acct__btn {
  width: auto;
  gap: var(--sp-2);
  padding: 0 var(--sp-2);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
}
.acct__ini {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-pill);
  background: var(--primary);
  color: var(--on-primary);
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: var(--fw-bold);
}
.acct__name { max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acct__who {
  display: block;
  padding: var(--sp-2) var(--sp-3);
  margin-bottom: var(--sp-2);
  border-bottom: 1px solid var(--border);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
}
.acct__who small {
  display: block;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs);
  font-weight: var(--fw-regular);
  color: var(--text-2);
}

.menu-btn {
  display: none;
  align-items: center;
  justify-content: center;
  width: var(--control-h);
  height: var(--control-h);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--primary);
  cursor: pointer;
}

/* ==========================================================================
   FOOTER
   ==========================================================================
   DARK NAVY, and that is the palette rather than a preference:
   docs/CARHUB_REDESIGN.md section 3 lists the footer as a use of the dark
   surface, beside the header-over-hero and the seller CTA band. The page is
   --bg and the footer is the bottom of it, which is what closes the page off.

   NO APP STORE OR PLAY STORE BADGES. The approved mockup draws both. There is
   no app, and a badge that links nowhere costs more trust than it buys.

   The four columns, the contact block, the social row, the popular searches
   and the legal line. Every string in it is a settings row — T3 fails the
   build on a brand name or a phone number typed into the markup.
   ========================================================================== */
/* THE 96px MARGIN THAT USED TO BE HERE IS THE VOID ABOVE THE FOOTER.
   REDESIGN-01b Part 3. --sp-9 is the SECTION rhythm — the gap BETWEEN two
   bands of page content — and the footer is not a section of the page, it is
   the end of it. So the margin was being added to the last band's own bottom
   padding and to the footer's 72px padding-top on top of that: 128px of
   empty --bg between the last thing on the page and the navy, on the home
   page, the listing, the detail page and a dealer profile alike, because all
   four share this shell.

   24px, and the colour change does the rest. The margin is not nought
   because a page that sets MainClass to "wrap" — the four posting steps —
   has no bottom padding of its own, and its last control would otherwise sit
   directly on the footer. */
.site-footer {
  margin-top: var(--sp-5);
  padding-top: var(--sp-8);
  background: var(--surface-dark);
  color: var(--text-on-dark-2);
  font-size: var(--fs-sm);
}
.site-footer :focus-visible { outline-color: var(--focus-ring-dark); }

/* h2, not h4. They are the top of the footer landmark, and an h4 after a page
   full of h2s skips a level on every page on the site. The size is what makes
   them read as column labels; the element is what makes them navigable. */
.site-footer h2 {
  margin-bottom: var(--sp-4);
  color: var(--text-inverse);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: .08em;
}
.site-footer li { margin-bottom: var(--sp-3); }
.site-footer a { color: var(--text-on-dark-2); }
.site-footer a:hover { color: var(--text-inverse); }

.footer-cols {
  display: grid;
  grid-template-columns: 1.4fr repeat(4, minmax(0, 1fr));
  gap: var(--sp-6) var(--sp-5);
  padding-bottom: var(--sp-7);
}

/* The first column: the brand, the tagline and the contact details, all of
   them settings rows. */
/* The footer is --surface-dark, where --action measures 6.04:1 and needs no
   darkening — so the footer carries the brand orange exactly. */
.footer-brand .wordmark { color: var(--text-inverse); }
.footer-brand .wordmark__accent { color: var(--action); }
.footer-brand p { margin: var(--sp-4) 0 0; max-width: 34ch; }
.footer-contact { margin-top: var(--sp-4); display: grid; gap: var(--sp-2); }
.footer-contact a,
.footer-contact span { display: flex; align-items: center; gap: var(--sp-2); }
.footer-contact svg { flex: none; color: var(--action); }

/* The social row. The icons are monochrome and inherit currentColor
   deliberately: a platform mark is a registered trademark and its own brand
   guidelines govern the colour version — the same reasoning that put letter
   monograms rather than manufacturer badges on the make tiles. The 44px box
   is the tap target; the glyph inside it is 20px.

   An empty setting renders no icon, and five empty settings render no list at
   all. docs/THEMING.md section 7. */
.social-block {
  display: flex;
  align-items: center;
  gap: var(--sp-2) var(--sp-4);
  flex-wrap: wrap;
}
.social-block h2 { margin-bottom: 0; }
.footer-social { border-top: 1px solid var(--border-dark); padding: var(--sp-4) 0; }

.social-row { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
.social-row li { margin: 0; }
.social {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--control-h);
  height: var(--control-h);
  border-radius: var(--radius-sm);
  color: var(--text-on-dark-2);
}
.social:hover { color: var(--text-inverse); background: var(--surface-dark-2); }

.footer-popular { border-top: 1px solid var(--border-dark); padding: var(--sp-4) 0; }
.footer-popular ul { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3); }
.footer-popular li { margin: 0; font-size: var(--fs-xs); }
.footer-popular a { color: var(--text-on-dark-2); }

.footer-legal {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-4);
  padding: var(--sp-4) 0;
  border-top: 1px solid var(--border-dark);
  color: var(--text-on-dark-2);
  font-size: var(--fs-xs);
}

/* The skip link, and it is the first thing in the document for a reason. */
.skip-link {
  position: absolute;
  left: var(--sp-3);
  top: -60px;
  z-index: 100;
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius-sm);
  background: var(--primary);
  color: var(--on-primary);
}
.skip-link:focus { top: var(--sp-2); }

/* ==========================================================================
   11. OVERLAY
   ========================================================================== */
.overlay {
  position: fixed;
  inset: 0;
  background: var(--overlay);
}


/* ==========================================================================
   12. AUTH PAGES — register, sign in, forgot password, reset, profile
   Ported from the approved demo (demo/assets/css/carhub.css) into this
   file's token names. One card shape for all of them: register carries a
   reassurance column beside it, the rest do not, and the card stays centred
   either way because the grid is centred rather than the columns being fixed.
   ========================================================================== */
/* ---- The auth band                                REDESIGN-03, Part 4 ----
   IT IS NOT A BAND ANY MORE. --surface-alt across the whole screen with a
   white card on it was the only way to make a card read as a card back in
   BUILD-03, when the page and the card were both #FFFFFF. REDESIGN-01 split
   --bg from --surface for exactly that reason, and the grey band has been a
   leftover ever since — it also left a strip of --bg under it wherever the
   card was shorter than the space, which is a seam nobody designed.

   The page is --bg like every other page and the card stands on it. */
.authpage {
  padding: var(--sp-8) 0 var(--sp-9);
}

.authgrid {
  display: grid;
  grid-template-columns: minmax(0, 460px);
  justify-content: center;
  gap: var(--sp-6);
  align-items: start;
}
.authgrid--wide { grid-template-columns: minmax(0, 460px) minmax(0, 300px); }

.authcard {
  min-width: 0;
  padding: var(--sp-6);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-2);
}
.authcard h1 { font-size: var(--fs-xl); margin-bottom: var(--sp-2); }

.authcard__intro {
  margin-bottom: var(--sp-5);
  color: var(--text-2);
  font-size: var(--fs-sm);
}

.authcard__foot {
  margin-top: var(--sp-4);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
  color: var(--text-2);
  font-size: var(--fs-sm);
  text-align: center;
}
.authcard__foot a { font-weight: var(--fw-medium); }

/* The reassurance column. It is deliberately NOT a card: two cards side by
   side would be two things of equal weight, and the form is the thing on the
   screen. It reads as the page talking. */
.authside { min-width: 0; font-size: var(--fs-sm); padding-top: var(--sp-2); }
.authside h2 { font-size: var(--fs-base); }

/* A ticked list. The register reassurance column, and "what happens next" on
   the confirmation screens. */
.ticklist li {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-2) 0;
}
.ticklist svg { flex: none; margin-top: 4px; color: var(--ok); }

/* The required-field star beside a label. */
.req { color: var(--danger); font-weight: var(--fw-bold); }

/* A value the member may read but not change — the phone number on the
   profile page. It looks like an input and is not one, because a disabled
   input reads as "temporarily unavailable" and this is permanent. */
.readonly-value {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--control-h);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
  color: var(--text);
  font-family: var(--font-num);
}

/* ==========================================================================
   13. PASSWORD FIELD
   ========================================================================== */
.pwfield { position: relative; }
.pwfield .input { padding-right: 42px; }

/* 44 x 44, and it sits ON the input's border rather than inside it. Inside
   the border the tallest it can be is 42px, which is two pixels short of the
   rule for the sake of a line nobody can see under a transparent button.
   REDESIGN-03 Part 5. */
.pwfield__eye {
  position: absolute;
  right: 0;
  top: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--control-h);
  height: var(--control-h);
  padding: 0;
  border: 0;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: none;
  color: var(--text-2);
  cursor: pointer;
}
.pwfield__eye:hover { color: var(--primary); }

/* ==========================================================================
   14. VALIDATION MESSAGES
   The classes the ASP.NET tag helpers render and wwwroot/js/validate.js
   toggles. .field__error above is the styling; these two are the states, and
   they are here rather than in the script because a colour in a script is
   what tools/theme-audit.py rule T1 exists to stop.
   ========================================================================== */
.field-validation-valid { display: none; }

/* The styling is on .field__error in section 4, together with the reserved
   line that stops a message shifting the page when it appears. This is the
   state class on its own, for the few spans that carry no .field__error. */
.field-validation-error {
  display: block;
  margin-top: var(--sp-1);
  color: var(--danger-text);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
}

/* The summary at the top of a form: "that number and password do not match".
   Errors with no field to sit under. */
.validation-summary-valid { display: none; }
.validation-summary-errors {
  margin-bottom: var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--danger);
  border-left-width: 3px;
  border-radius: var(--radius-sm);
  background: var(--danger-soft);
  color: var(--danger-text);
  font-size: var(--fs-sm);
}
.validation-summary-errors ul { margin: 0; }
.validation-summary-errors li + li { margin-top: var(--sp-1); }

/* The same box, written by wwwroot/js/validate.js when it refuses a submit.
   FIX-03: a form that stops must say why, and it must say it where the member
   is looking rather than beside a control he may not be able to see.

   It borrows .validation-summary-errors above so that "this did not go
   through" looks the same whether the server said it or the browser did. What
   is added here is only the list: a heading line, one link per field, and the
   sentence that goes with a field nobody can see.

   There is no markup for any of this in any view. With JavaScript off the
   element is never created and the form is exactly what it was. */
.formerrors p { margin: 0 0 var(--sp-2); font-weight: var(--fw-medium); }
.formerrors ul { margin: 0; padding-left: var(--sp-5); }
.formerrors li { list-style: disc; }
.formerrors li + li { margin-top: var(--sp-1); }
.formerrors a { color: inherit; text-decoration: underline; }
.formerrors a:hover { text-decoration: none; }
.formerrors:focus { outline: none; }
.formerrors:focus-visible { outline: 2px solid var(--danger); outline-offset: 2px; }

/* The script scrolls this box to the top of the window. The header is sticky
   and --header-h tall, so without this the first line of the refusal ends up
   underneath it -- which on the one screen that mattered would have hidden
   the word "not on the screen". CSS decides the offset, not the script: the
   header's height is a token and the script has no business knowing it. */
.formerrors { scroll-margin-top: calc(var(--header-h) + var(--sp-4)); }

/* The field the member cannot see. Not a second colour — the box is already
   --danger-text — just the quieter weight that marks it as our apology
   rather than his mistake. */
.formerrors__unseen {
  display: block;
  margin-top: var(--sp-1);
  font-size: var(--fs-xs);
  font-style: normal;
  font-weight: var(--fw-regular);
  opacity: 0.85;
}

/* ==========================================================================
   15. THE HOME PAGE                                  REDESIGN-01, Part 5
   ==========================================================================
   docs/CARHUB_REDESIGN.md sections 7 and 8, in this file's token names.

   Hero -> search panel -> quick actions -> featured cars -> budget ->
   popular makes -> why choose -> seller CTA.
   ========================================================================== */
.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-4);
  margin-bottom: var(--sp-5);
}
.section-head h1,
.section-head h2 { margin: 0; }
/* The "View all 19 cars in Lahore →" link on a section head.
   :not(.btn) IS LOAD-BEARING. A section head can also hold a real button —
   /my-ads/ and /my-bids/ both put "Post a new ad" there — and a bare
   `.section-head a` rule beats `.btn--primary` on specificity, which painted
   the label of a navy button in the action colour. */
.section-head a:not(.btn) {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--action-text);
}

.lead { font-size: var(--fs-base); color: var(--text-2); max-width: 78ch; }

.eyebrow {
  color: var(--text-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: .08em;
}

.small { font-size: var(--fs-xs); }
.mono  { font-family: var(--font-num); font-variant-numeric: tabular-nums; }

/* ==========================================================================
   THE HERO                                       docs/CARHUB_REDESIGN.md §8
   ==========================================================================
   420-520px, and NOT a full-screen banner — section 8 says so in as many
   words. --hero-h is 460 with the photograph, --hero-h-short 420 without it.

   REDESIGN-01b PART 1 REBUILT THIS, AND THE REASON IS WORTH WRITING DOWN.
   REDESIGN-01 built a layout designed around a photograph and then shipped it
   without one: the content sat left to leave room for a car that was not
   there, and the right half was a flat navy field. The photograph exists now
   — Minar-e-Pakistan with a car, at three sizes under wwwroot/img/hero — so
   the layout has something to be built around.

   THE EMPTY STATE IS STILL A DESIGN AND HAS TO BE, because a dealer who buys
   this platform starts with no image. It is .hero--empty below: shorter,
   centred, and lit from behind rather than left as a flat rectangle with the
   text pushed into one corner of it. A layout that only makes sense with a
   picture in it is the thing the client rejected.

   The overlay is a token, --hero-overlay, not a literal here. A lighter theme
   needs a heavier wash to hold white text at 4.5:1 over a bright photograph,
   and that is a theme decision. */
.hero {
  position: relative;
  min-height: var(--hero-h);
  display: flex;
  align-items: center;
  background: var(--surface-dark);
  color: var(--text-inverse);
  overflow: hidden;
}
.hero :focus-visible { outline-color: var(--focus-ring-dark); }

/* The photograph, when Site:HeroImage names one. NOT lazy — it is the largest
   thing above the fold and lazy-loading it is lazy-loading the page. The box
   is reserved by the hero's own min-height and by width/height on the
   element, so nothing moves when the bytes land. */
.hero__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 72%;
  z-index: 0;
}

/* THE OVERLAY RUNS ACROSS, NOT OVER. A flat wash at one opacity over the
   whole frame is what makes a photograph read as a background instead of a
   picture — and it was what REDESIGN-01 shipped: .66 alpha held to 42% and
   still .57 at the middle of the frame.

   It is a left-to-right gradient now. Full strength under the headline,
   holding out to 28%, and gone by 64% — which is where the monument and the
   car are.

   THE max() IS NOT DECORATION AND IT WAS PUT THERE BY A MEASUREMENT. On
   percentages alone the dark zone shrinks with the viewport while the text
   does not: at 768px the supporting line ran to 404px and the gradient had
   already cleared at 491px, which measured 1.42:1 against the sunset behind
   it. The floor holds the strong part out to 380px and the fade out to 700px
   whatever the frame is, so the text column is covered at every width and
   the photograph is only affected on the narrowest desktop. Above about
   1360px the percentages win and nothing changes.

   The numbers at all seven widths are in docs/THEMING.md section 3. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(90deg,
    var(--hero-overlay) 0%,
    var(--hero-overlay) max(28%, 380px),
    transparent max(64%, 700px));
}
.hero__wrap { position: relative; z-index: 2; width: 100%; padding-block: var(--sp-6); }

.hero__title {
  margin: 0;
  max-width: 15ch;
  color: var(--text-inverse);
  font-size: var(--fs-3xl);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  letter-spacing: -.02em;
}
.hero__sub {
  margin: var(--sp-4) 0 0;
  max-width: 40ch;
  color: var(--text-on-dark-2);
  font-size: var(--fs-md);
}

/* WHITE, NOT --text-on-dark-2, WHEN THERE IS A PHOTOGRAPH BEHIND IT, and
   that is arithmetic rather than taste. #B9C2DC has a relative luminance of
   .535, so it cannot reach 4.5:1 against anything lighter than .080 — which
   a scrimmed sunset is not. White reaches it at .183, which the scrim does
   hold. On the empty hero the background IS --surface-dark at .011 and the
   softer grey keeps the hierarchy, so it stays there. */
.hero:not(.hero--empty) .hero__sub { color: var(--text-inverse); }

/* Section 8's three trust indicators, and section 13 wants them recurring
   rather than decorative. CITIES-01 Part 7 replaced what they say -- they are
   now "Every ad checked before it goes live", "Live auctions" and "Free to
   post an ad", and the longest of the three is why this row wraps. */
.hero__trust {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-5);
  margin-top: var(--sp-6);
  padding: 0;
}
.hero__trust li {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-inverse);
}
.hero__trust svg { flex: none; color: var(--ok); }

/* ON A NARROW SCREEN THE GRADIENT TURNS, AND IT TURNS ON THE SAME LINE THE
   <picture> SWITCHES ON — 767.98px. Below it the photograph is the taller
   mobile crop with the car across the middle of the frame, and a
   left-to-right wash on a 390px frame either covers the car or never reaches
   the text. So it runs down instead: full strength behind the headline and
   the supporting line, clear over the bottom where the car is.

   The old rule was on the 640px line, which left 640-768px taking the mobile
   photograph and the desktop wash — a crop chosen for a vertical gradient
   with a horizontal one over it. */
@media (max-width: 767.98px) {
  .hero__scrim {
    background: linear-gradient(180deg,
      var(--hero-overlay) 0%, var(--hero-overlay) 58%, transparent 96%);
  }
}

/* ---- The empty hero, Site:HeroImage unset --------------------------------
   A DEALER WHO BUYS THIS PLATFORM STARTS HERE, so it is a finished look and
   not a gap waiting to be filled. Three differences, and each of them answers
   something the client said about the version with the photograph missing.

   SHORTER. 420px rather than 460 — the bottom of the redesign document's
   band. There is no picture to fill the extra 40px, so it is not taken.

   CENTRED. The content was left-aligned to leave room for a car on the right.
   With no car it left a third of the band empty and read as a mistake, so
   with no image the block centres and the band is symmetrical on purpose.

   LIT. Two soft radials off --surface-dark-2, one behind the headline and one
   low and wide, so the navy has depth in it rather than being a flat
   rectangle somebody forgot to finish. Both are tokens; the band follows a
   theme change like everything else on the page. */
.hero--empty {
  min-height: var(--hero-h-short);
}
.hero--empty::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(70% 120% at 50% 6%, var(--surface-dark-2) 0%, transparent 70%),
    radial-gradient(150% 100% at 50% 118%, var(--surface-dark-2) 0%, transparent 62%);
}
.hero--empty .hero__wrap { text-align: center; }
.hero--empty .hero__title,
.hero--empty .hero__sub { max-width: 30ch; margin-inline: auto; }
.hero--empty .hero__trust { justify-content: center; }

/* ==========================================================================
   THE SEARCH PANEL
   ==========================================================================
   It OVERLAPS the hero — section 8 asks for the overlap by name, "to create
   visual depth". The negative margin is on the panel rather than on the hero,
   so the hero keeps its declared height and the section under it just starts
   lower.

   THE TABS ARE Used Cars, Dealers and Auctions. The approved mockup has a
   fourth, New Cars. THIS SITE DOES NOT SELL NEW CARS — docs/CARHUB_MASTER.md
   — and a tab that produces an empty grid every time is worse than three tabs
   that all work. */
.searchpanel {
  position: relative;
  z-index: 3;
  margin-top: calc(var(--sp-4) * -1);
  padding: var(--sp-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--surface);
  box-shadow: var(--shadow-3);
}

.searchtabs {
  display: flex;
  gap: var(--sp-1);
  margin-bottom: var(--sp-5);
  border-bottom: 1px solid var(--border);
}
.searchtabs a {
  display: inline-flex;
  align-items: center;
  min-height: var(--control-h);
  padding: 0 var(--sp-4);
  margin-bottom: -1px;
  border-bottom: 2px solid transparent;
  color: var(--text-2);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
}
.searchtabs a:hover { color: var(--text); text-decoration: none; }
.searchtabs a[aria-current="page"] {
  color: var(--action-text);
  border-bottom-color: var(--action);
}

/* Make, Model, Price, City, More filters, and the button. Five controls and
   an action, which is exactly what section 8 asks for. */
.searchrow {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr)) auto auto;
  align-items: end;
  gap: var(--sp-3);
}
.searchrow .field { margin: 0; }
.searchrow .btn--action { min-height: var(--control-h-lg); }

/* "More filters" is a link into the listing rail rather than a control that
   opens something here: the sixteen filters live on the listing page and
   duplicating three of them into the home page is two places for a filter to
   drift. */
.searchrow__more {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--control-h-lg);
  padding-inline: var(--sp-3);
  color: var(--text-2);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  white-space: nowrap;
}
.searchrow__more:hover { color: var(--primary); text-decoration: none; }

/* The popular-make shortcuts under the row. */
.searchpanel__popular {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-4);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm);
}
.searchpanel__popular > span { color: var(--text-2); }

/* ==========================================================================
   QUICK ACTIONS — Buy a Car, Sell Your Car, Find a Dealer
   ========================================================================== */
.quick {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
  padding: var(--sp-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
  color: var(--text);
  transition: box-shadow var(--dur-base) var(--ease),
              transform var(--dur-base) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.quick:hover {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-2);
  transform: translateY(-3px);
  text-decoration: none;
}
@media (prefers-reduced-motion: reduce) { .quick:hover { transform: none; } }

.quick__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border-radius: var(--radius-md);
  background: var(--warning-soft);
  color: var(--action-text);
}
.quick h3 { margin: 0 0 var(--sp-1); font-size: var(--fs-md); color: var(--text); }
.quick p  { margin: 0; font-size: var(--fs-sm); color: var(--text-2); }
.quick__go {
  display: inline-block;
  margin-top: var(--sp-3);
  color: var(--action-text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
}

/* ==========================================================================
   TILES — popular makes, budget bands, cities
   ==========================================================================
   POPULAR MAKES ARE LETTER MONOGRAMS AND THERE IS NO MANUFACTURER LOGO FILE
   ANYWHERE IN THIS APPLICATION. The approved mockup draws the Toyota, Honda,
   Suzuki, Hyundai, KIA, BMW and Mercedes marks. Every one of them is a
   registered trademark, and shipping them inside a product that is resold to
   other dealers is a risk this project does not need to take for a 40px
   square. The monogram is the same treatment the dealer cards use. If the
   client later licenses the marks it is one column and a swap.
   docs/DESIGN_TOKENS.md, "Manufacturer marks". */
.tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 124px;
  padding: var(--sp-4) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
  color: var(--text);
  text-align: center;
  transition: box-shadow var(--dur-base) var(--ease),
              transform var(--dur-base) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.tile:hover {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-2);
  transform: translateY(-3px);
  text-decoration: none;
}
@media (prefers-reduced-motion: reduce) { .tile:hover { transform: none; } }

.tile__logo {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-md);
  background: var(--primary-soft);
  color: var(--primary);
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: var(--fw-bold);
}
.tile__name  { font-size: var(--fs-sm); font-weight: var(--fw-semibold); }
.tile__count {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs);
  color: var(--text-2);
}

.tile { position: relative; }

/* A budget band. The figure IS the tile, so it is set in the numeric face at
   the card-title size. */
.tile--budget { align-items: flex-start; text-align: left; min-height: 96px; }
.tile--budget .tile__name {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-md);
  font-weight: var(--fw-bold);
}

/* A BAND WITH NOTHING IN IT. REDESIGN-01b Part 5: "Under 15 lac — 0 cars"
   was a link to an empty grid, and the client was right that it is a dead
   tile. It is drawn — the five bands are a price ladder and a missing rung
   reads worse than an empty one — but it is not a control: the view renders
   a <span>, so there is nothing to click, nothing to tab to and nothing to
   announce. This rule is what says so on the screen. */
.tile--empty {
  border-style: dashed;
  box-shadow: none;
  opacity: .55;
}
.tile--empty:hover { transform: none; box-shadow: none; border-color: var(--border); }

/* ==========================================================================
   POPULAR MAKES — the strip                            REDESIGN-01b Part 2
   ==========================================================================
   WHAT THIS REPLACED: seven .tile cards on a four-across grid, each 124px
   tall, each holding a 44px tinted square with one letter in it and the name
   and count stacked underneath. Seven items on a four-column grid leaves a
   row of three and three tiles' worth of nothing beside them, and the client
   named it as the second-weakest thing on the page.

   MONOGRAM, NAME AND COUNT ON ONE LINE, in a strip 48px tall — a little over
   a third of what it was. The monogram treatment itself has not changed and
   is not being reopened: docs/DESIGN_TOKENS.md, "Manufacturer marks". Toyota,
   Honda, Suzuki, Hyundai, KIA, BMW and Mercedes are registered trademarks and
   this platform is resold.

   NO RAGGED LAST ROW AT ANY WIDTH, and the flex rule is the whole reason:
   flex-grow 1 with flex-shrink 0 means the tiles share out any spare space on
   a wide screen so the strip always ends flush, and refuse to compress on a
   narrow one so the strip scrolls sideways instead of wrapping into a short
   second row. One rule, both behaviours, no breakpoint.
   ========================================================================== */
.makerow {
  display: flex;
  gap: var(--sp-3);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  /* Space for the hover lift, so it is not clipped by the scroll container. */
  padding-block: var(--sp-1) var(--sp-2);
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}

.maketile {
  flex: 1 0 auto;
  scroll-snap-align: start;

  /* position:relative is load-bearing. The count carries a .visually-hidden
     span with the word "cars" in it, and .visually-hidden is position:
     absolute — without a positioned ancestor its containing block is the
     document, so the off-screen half of a scrolled strip lands in the
     document's own scroll width and the whole page scrolls sideways on a
     phone. docs/CARHUB_REDESIGN.md section 14: never allow horizontal
     scrolling. */
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 48px;
  padding: 0 var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
  color: var(--text);
  white-space: nowrap;
  transition: box-shadow var(--dur-base) var(--ease),
              transform var(--dur-base) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.maketile:hover {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-2);
  transform: translateY(-2px);
  text-decoration: none;
}
@media (prefers-reduced-motion: reduce) { .maketile:hover { transform: none; } }

/* The letter. 28px rather than the old 44px square — it is a mark beside a
   word now, not the picture on a card. */
.maketile__logo {
  flex: none;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-sm);
  background: var(--primary-soft);
  color: var(--primary);
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
}
.maketile__name { font-size: var(--fs-sm); font-weight: var(--fw-semibold); }

/* The count sits at the far end of the tile, so a row of them lines up on the
   right edge whatever the names are called. */
.maketile__count {
  margin-left: auto;
  padding-left: var(--sp-2);
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--text-2);
}

/* A make M. Jamil has pinned to the front. makes.is_featured, added by the
   migration REDESIGN-01 Part 3 authorised: a make added this morning has no
   cars, so it can never reach a list ordered by live count, and it is exactly
   the make he wants to promote — which is also why REDESIGN-01b Part 5 keeps
   a pinned make with a zero count on the page when every other zero comes
   off. The star says the position was bought rather than earned, so a visitor
   is not being told BYD is popular. */
.maketile--pinned { border-color: var(--warning); }
.maketile__pin { color: var(--warning); line-height: 0; }

/* ==========================================================================
   WHY CHOOSE — four reassurances, docs/CARHUB_REDESIGN.md §7 and §13
   ========================================================================== */
.why { display: flex; align-items: flex-start; gap: var(--sp-4); }
.why__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-md);
  background: var(--ok-soft);
  color: var(--ok-text);
}
.why h3 { margin: 0 0 var(--sp-1); font-size: var(--fs-md); color: var(--text); }
.why p  { margin: 0; font-size: var(--fs-sm); color: var(--text-2); }

/* ==========================================================================
   THE SELLER CTA BAND
   ========================================================================== */
.sellcta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-5);
  padding: var(--sp-7);
  border-radius: var(--radius-xl);
  background: var(--surface-dark);
  color: var(--text-inverse);
}
.sellcta :focus-visible { outline-color: var(--focus-ring-dark); }
.sellcta h2 { margin: 0; color: var(--text-inverse); }
.sellcta p  { margin: var(--sp-2) 0 0; color: var(--text-on-dark-2); }

/* ==========================================================================
   THE REST OF THE HOME PAGE
   ========================================================================== */
/* Chip-style link lists: quick links, cities, popular searches. */
.linklist { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.linklist a {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--control-h-sm);
  padding: 0 var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-sm);
}
.linklist a:hover {
  border-color: var(--primary);
  color: var(--primary);
  text-decoration: none;
}
.linklist .c {
  color: var(--text-2);
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs);
}

/* A plain bordered box: the how-it-works steps, the leasing panel, the stub
   panels on the routes later cycles fill in. */
.box {
  padding: var(--sp-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.box--alt { background: var(--surface-alt); }

/* An auction box. The demo writes this one --accent-soft and calls the class
   .box--accent; here it is --live, because BUILD-04 split --action for primary
   calls to action from --live for auctions and docs/THEMING.md section 4 says
   --live is "auctions, countdowns and the LIVE dot, and nothing else". The
   block it wears is about bidding on both pages that use it, so it is --live
   on both. */
/* THE PINK WASH, AT THE STRENGTH REDESIGN-01b SETTLED ON. That cycle cut the
   home page's full-width Live Auctions band to 38% of --live-soft, because at
   full strength the wash competed with the red-bordered cards it was there to
   frame. This block is the same wash on the auctions listing and it is the
   same 38% now — REDESIGN-02 Part 6, "keep it consistent here". The border
   stays --live at full strength: that is the signal, and it is on one edge
   rather than across the whole page. base.css's .band--live carries the twin
   of this rule and the same arithmetic. */
.box--live {
  background: color-mix(in srgb, var(--live-soft) 38%, var(--bg));
  border-color: var(--live);
}

/* The line under an auction explainer that says no money passes through this
   site. It is separated by a rule rather than by a heading because it is not a
   step — it is the sentence a first-time bidder has to have read. */
.box__money {
  border-top: 1px solid var(--live);
  margin-top: var(--sp-4);
  padding-top: var(--sp-3);
  font-size: var(--fs-sm);
}

/* ---- Numbered steps, ported from the demo for BUILD-09b ------------------
   The same component admin.css has had since BUILD-07, moved here because two
   public pages now draw it — /how-it-works/ and /auctions/. A second copy in
   two stylesheets is two places for the circle to change size. admin.css keeps
   its own smaller variant for the review screen; this is the public one.

   The number is a CSS counter rather than markup, so the <ol> stays an <ol>
   and a screen reader reads "list of 4 items" rather than a paragraph that
   happens to begin with a digit. */
.numsteps { counter-reset: numstep; list-style: none; padding-left: 0; margin: 0; }
.numsteps li { position: relative; padding-left: 42px; min-width: 0; }
.numsteps li + li { margin-top: var(--sp-4); }
.numsteps li::before {
  counter-increment: numstep;
  content: counter(numstep);
  position: absolute;
  left: 0;
  top: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--primary-soft);
  color: var(--primary);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
}
.numsteps h3 { font-size: var(--fs-md); margin: 0 0 var(--sp-1); }
.numsteps p { font-size: var(--fs-sm); color: var(--text-2); margin-bottom: 0; }
.numsteps p + p { margin-top: var(--sp-2); }

.numsteps--across {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-4);
}
.numsteps--across li + li { margin-top: 0; }

/* Inside an auction box the circles take the auction colour, for the reason
   the box does. */
.numsteps--live li::before { background: var(--live); color: var(--on-live); }

/* ---- A block the client has to fill in ----------------------------------
   /about/ has one: the owner's name, photograph and paragraph. A biography
   invented by a web developer is worse than no biography at all, and a buyer
   in Lahore can tell the difference — so the page says so on its face rather
   than shipping invented copy that reads as real.

   THE DASHED BORDER IS THE POINT. It is what makes an unfinished block look
   unfinished from across the room, and it comes out with the placeholder when
   the content arrives. */
.placeholder {
  display: grid;
  grid-template-columns: 120px minmax(0, 1fr);
  gap: var(--sp-4);
  align-items: start;
  padding: var(--sp-4);
  border: 1px dashed var(--text-2);
  border-radius: var(--radius-md);
  background: var(--surface-alt);
}
.placeholder__photo {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--text-2);
}
.placeholder__body { min-width: 0; }
.placeholder__body h3 { margin: var(--sp-1) 0 var(--sp-2); }

/* The document body strips the bullets off the lists it holds, because they
   are already numbered or ticked. */
.doc__body .numsteps,
.doc__body .ticklist,
.doc__body .linklist { list-style: none; padding-left: 0; }

/* The FAQ accordion is defined ONCE, further down this file, in the block
   BUILD-12a rewrote. It used to be defined here as well — a bordered box with
   an inner divider — and again below as a flat list with a top rule, so the
   rendered result was the two of them cascaded together: a box with a border
   round it AND a rule under its last item. Neither author knew about the
   other. One definition now, and it is the card treatment. */

/* ==========================================================================
   THE DEALER CARD — docs/PAGES.md, "Dealer directory"

   The same .card as everywhere else on the site, with a head instead of a
   photograph: the .tile__logo monogram above, .card__specs for the mono
   figures line, .pill for the tags and .gallery__thumbs for the stock strip.
   Nothing here is a second version of something that already exists.

   THE WHOLE CARD IS THE LINK, through a stretched ::after on the title's
   anchor rather than an <a> wrapped round the card. That keeps exactly one
   link per card and makes its accessible name the showroom name — not the
   name plus the city plus three figures plus every tag.
   ========================================================================== */
.card--dealer { position: relative; }
.card--dealer .card__body {
  /* No image slot to line up with, so no minimum: the grid row stretches
     every card to the tallest one on its own. */
  min-height: 0;
  gap: var(--sp-2);
}
.card--dealer .card__title a::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* align-items:flex-start, not center: a showroom name of two words wraps to
   two lines on a 280px card, and centred against a 44px monogram that put the
   name and the city off the monogram's axis. REDESIGN-03 Part 3. */
.card__head { display: flex; align-items: flex-start; gap: var(--sp-3); }
.card__headtext { min-width: 0; }

/* The tick belongs to the name and must not be able to sit alone on the
   second line of it.

   min-height:0 with it: the vehicle card's title reserves two lines so that a
   row of cars lines its prices up (REDESIGN-03b Part 5), and a dealer card has
   no price and no photograph to line up with — it would only be a gap under
   every one-line showroom name. */
.card--dealer .card__title {
  display: block;
  line-height: var(--lh-snug);
  min-height: 0;
}
.card--dealer .card__head .tile__logo { flex: none; }
.card--dealer .card__specs {
  padding-top: var(--sp-2);
  border-top: 1px solid var(--border);
  color: var(--text-2);
}

/* --ok, and --ok IS the verification tick across the whole site now.
   REDESIGN-01 retired --gold: verified is green and featured is amber, so the
   tick on a dealer card, the .badge--verified on his profile and the Verified
   chip on a vehicle card are all one colour saying one thing. */
.card__tick { color: var(--ok); flex: none; margin-left: 6px; }
.card__where { font-size: var(--fs-xs); color: var(--text-2); }


/* ---- Numbered steps, ported from the demo for BUILD-09b ------------------
   The same component admin.css has had since BUILD-07, moved here because two
   public pages now draw it — /how-it-works/ and /auctions/. A second copy in
   two stylesheets is two places for the circle to change size. admin.css keeps
   its own smaller variant for the review screen; this is the public one.

   The number is a CSS counter rather than markup, so the <ol> stays an <ol>
   and a screen reader reads "list of 4 items" rather than a paragraph that
   happens to begin with a digit. */
.numsteps { counter-reset: numstep; list-style: none; padding-left: 0; margin: 0; }
.numsteps li { position: relative; padding-left: 42px; min-width: 0; }
.numsteps li + li { margin-top: var(--sp-4); }
.numsteps li::before {
  counter-increment: numstep;
  content: counter(numstep);
  position: absolute;
  left: 0;
  top: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--primary-soft);
  color: var(--primary);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-num);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
}
.numsteps h3 { font-size: var(--fs-md); margin: 0 0 var(--sp-1); }
.numsteps p { font-size: var(--fs-sm); color: var(--text-2); margin-bottom: 0; }
.numsteps p + p { margin-top: var(--sp-2); }

.numsteps--across {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-4);
}
.numsteps--across li + li { margin-top: 0; }

/* Inside an auction box the circles take the auction colour, for the reason
   the box does. */
.numsteps--live li::before { background: var(--live); color: var(--on-live); }

/* ---- A block the client has to fill in ----------------------------------
   /about/ has one: the owner's name, photograph and paragraph. A biography
   invented by a web developer is worse than no biography at all, and a buyer
   in Lahore can tell the difference — so the page says so on its face rather
   than shipping invented copy that reads as real.

   THE DASHED BORDER IS THE POINT. It is what makes an unfinished block look
   unfinished from across the room, and it comes out with the placeholder when
   the content arrives. */
.placeholder {
  display: grid;
  grid-template-columns: 120px minmax(0, 1fr);
  gap: var(--sp-4);
  align-items: start;
  padding: var(--sp-4);
  border: 1px dashed var(--text-2);
  border-radius: var(--radius-md);
  background: var(--surface-alt);
}
.placeholder__photo {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--text-2);
}
.placeholder__body { min-width: 0; }
.placeholder__body h3 { margin: var(--sp-1) 0 var(--sp-2); }

/* The document body strips the bullets off the lists it holds, because they
   are already numbered or ticked. */
.doc__body .numsteps,
.doc__body .ticklist,
.doc__body .linklist { list-style: none; padding-left: 0; }

/* ==========================================================================
   THE FAQ ACCORDION — bordered cards                          BUILD-12a Part 7
   ==========================================================================
   WHAT THIS REPLACED: flat rows separated by hairlines. The client's reference
   draws each question as its own bordered card with a chevron, which is right
   — a list of five questions reads as five things to open rather than as one
   block of text with lines through it, and the hit target is the whole card
   instead of the strip of type.

   NEUTRAL BORDER AT REST, ACCENT ON HOVER AND ON THE OPEN ITEM. The reference
   outlines EVERY item in orange, and that is too much orange for a passive
   list: orange means ACTION on this site — the one thing to press — and five
   orange outlines on a page that also carries "Post Your Ad" spends the signal
   the whole palette is built around. --primary is the state colour here, the
   same one the rail's selected option and the card's hover border use.

   :has() IS PROGRESSIVE, NOT LOAD-BEARING. It puts the accent on the card of
   the OPEN item; a browser without it still gets the hover state, the chevron
   rotation and every answer. Nothing about whether the FAQ works depends on
   it.

   OPEN BY DEFAULT WITHOUT SCRIPT: the answers are marked hidden by the SERVER
   only on the ones that start closed, so a visitor with no JavaScript reaches
   every answer by tabbing, and a visitor with no CSS reads all of them.
   ========================================================================== */
.faq { display: grid; gap: var(--sp-3); }

.faq__item {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  padding-inline: var(--sp-4);
  transition: border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-base) var(--ease);
}
.faq__item:hover { border-color: var(--primary); }

/* ---- THE OPEN ITEM, AND ONLY WHERE "OPEN" MEANS SOMETHING ---------------
   The accent says "this is the one you opened". On the home page and the city
   listing that is true: one answer starts open and four start closed.

   ON /faq/ EVERY ITEM IS OPEN BY DESIGN — docs/SEO_AEO.md wants all twelve
   answers readable without a click — so an unqualified rule outlined twelve
   cards at once, which is exactly the "too much accent on a passive list"
   this treatment was chosen to avoid, just in navy instead of orange. Found
   by measuring the computed border colour rather than by looking at it.

   So the accent applies only inside an accordion that HAS a closed item. All
   open, and every card stays neutral; the chevrons still say which is which.
   :has() is doing real work here and is not decoration, but nothing depends
   on it: without it every card is simply neutral, which is the state /faq/
   wants anyway. */
.faq:has(.faq__q[aria-expanded="false"]) .faq__item:has(.faq__q[aria-expanded="true"]) {
  border-color: var(--primary);
  box-shadow: var(--shadow-1);
}

.faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  width: 100%;
  min-height: var(--control-h);
  padding: var(--sp-3) 0;
  border: 0;
  background: none;
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  text-align: left;
  cursor: pointer;
}
.faq__q:hover { color: var(--primary); }
.faq:has(.faq__q[aria-expanded="false"]) .faq__q[aria-expanded="true"] {
  color: var(--primary);
}

/* The chevron points down when the item is open and right when it is shut,
   which is the direction the content moves rather than an arbitrary spin. */
.faq__q .arrow {
  flex: none;
  color: var(--text-2);
  transition: transform var(--dur-base) var(--ease);
}
.faq:has(.faq__q[aria-expanded="false"]) .faq__q[aria-expanded="true"] .arrow {
  color: var(--primary);
}
.faq__q[aria-expanded="false"] .arrow { transform: rotate(-90deg); }

.faq__a {
  padding: 0 0 var(--sp-4);
  color: var(--text-2);
  max-width: 88ch;
}

/* ==========================================================================
   16. LONG-FORM PAGES — terms, privacy, and the rest of the flat pages
   A contents rail beside the text, and the reset's list-style: none put back,
   because these are the only pages on the site with real prose lists in them.
   ========================================================================== */
.doc {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  align-items: start;
  gap: var(--sp-5) var(--sp-6);
  padding: var(--sp-4) 0 var(--sp-7);
}
.doc__toc {
  position: sticky;
  top: calc(var(--header-h) + var(--sp-4));
  min-width: 0;
  font-size: var(--fs-sm);
}
.doc__toc h2 {
  margin-bottom: var(--sp-3);
  color: var(--text-2);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: .07em;
}
.doc__toc li { margin-bottom: var(--sp-2); }
.doc__toc a { color: var(--text); }

.doc__body { min-width: 0; max-width: 78ch; }
.doc__body > section + section { margin-top: var(--sp-6); }
.doc__body h2 { font-size: var(--fs-md); }
.doc__body h3 { font-size: var(--fs-base); margin-top: var(--sp-4); }
.doc__body ul,
.doc__body ol {
  margin: 0 0 var(--sp-3);
  padding-left: var(--sp-5);
  list-style: disc;
}
.doc__body ol { list-style: decimal; }
.doc__body li { margin-bottom: var(--sp-1); }
.doc__body .linklist,
.doc__body .ticklist { list-style: none; padding-left: 0; }
.doc__updated { color: var(--text-2); font-size: var(--fs-xs); }

/* Two-column label and value: the privacy page's contact block. */
.kv {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 3px var(--sp-3);
  font-size: var(--fs-sm);
}
.kv dt { color: var(--text-2); min-width: 0; }
.kv dd { margin: 0; font-family: var(--font-num); font-weight: var(--fw-bold); text-align: right; }

/* One line of state at the top of a page. Bigger than .notice, which is small
   print inside a sidebar. */
.banner {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
  font-size: var(--fs-sm);
}
.banner__icon { flex: none; margin-top: 2px; color: var(--text-2); }
.banner__body { min-width: 0; }
.banner p:last-child { margin-bottom: 0; }

/* The client note at the top of the two legal pages. Dashed and grey, because
   everything on this site that comes out before launch is dashed. NOT --live:
   that colour means an auction is running. It stays until a lawyer has read
   the text — docs/PRE_LAUNCH_CHECKLIST.md rows 13 and 14. */
.banner--draft {
  background: var(--surface);
  border-style: dashed;
  border-color: var(--text-2);
}

/* Two more, added in BUILD-06 and following .notice's variants exactly so the
   two components do not have two different ideas of what amber means.
   --warning is "read this before you press the button"; --danger is the
   moderator's reason on a rejected ad. Neither is --live: that colour means
   an auction is running and nothing else. */
.banner--warning {
  background: var(--warning-soft);
  border-color: var(--warning);
}
.banner--warning .banner__icon { color: var(--warning); }

.banner--danger {
  background: var(--danger-soft);
  border-color: var(--danger);
}
.banner--danger .banner__icon { color: var(--danger); }

/* ==========================================================================
   17. NOT FOUND
   ========================================================================== */
.notfound {
  max-width: 640px;
  margin: 0 auto;
  padding: var(--sp-8) 0;
  text-align: center;
}
.notfound__code {
  margin: 0 0 var(--sp-3);
  font-size: 64px;
  font-weight: var(--fw-bold);
  color: var(--border);
  line-height: 1;
}
.notfound .lead { margin-left: auto; margin-right: auto; }
.notfound__search { display: flex; gap: var(--sp-2); margin: var(--sp-5) 0; }
.notfound__search .input { min-width: 0; }
.notfound .linklist { justify-content: center; }

/* ==========================================================================
   18. RESPONSIVE
   ==========================================================================
   docs/CARHUB_REDESIGN.md section 14. Desktop >=1200, tablet 768-1199,
   phone <768; NEVER a horizontal scrollbar; touch targets at least 44px.

   The breakpoints below are the demo's and they are still right for the
   reasons they were chosen: 860 is where the header row stops laying out flat
   and 640 is the phone. The header lost its search box in REDESIGN-01 — the
   redesign puts search in the hero, and the box it replaced posted `q` to a
   listing page that has never read it — so the 1100 rule that narrowed it is
   gone with it.
   ========================================================================== */
@media (max-width: 1100px) {
  .nav a { padding-inline: var(--sp-2); }
  .searchrow { grid-template-columns: repeat(2, minmax(0, 1fr)) auto; }
}

@media (max-width: 1000px) {
  /* The contents rail is a courtesy on a phone and a wall in front of the
     text. It goes; every heading is still one scroll away. */
  .doc { grid-template-columns: minmax(0, 1fr); }
  .doc__toc { display: none; }
}

/* ---- THE NAV FOLDS AT 940px, AND BUILD-12b MOVED IT UP FROM 860 ----------
   The old rule said "below 860 it pushes the auth buttons off the right edge
   and drags the whole page sideways", which was a measurement of a FIVE-item
   row. Leasing makes it six, and re-measuring gives a different number: with
   the longest display name this database holds in the account chip, the six
   labels stop fitting on one line at 920px and fit at 940.

   IT DID NOT OVERFLOW BELOW THAT -- IT WRAPPED, WHICH IS WORSE. Every item is
   a flex child of a row with room to spare somewhere else, so the browser
   answered the squeeze by breaking "Buy Cars" onto two lines inside its own
   pill. That already happened at 861 with five items; six widened the band it
   happens in. So the labels are nowrap now and the fold is at the width where
   the row genuinely stops fitting, and the two facts are kept together
   deliberately: a nav that cannot break its labels MUST fold before it
   overflows, or the page scrolls sideways. */
@media (max-width: 940px) {
  .nav { display: none; }
  .nav.is-open {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    position: absolute;
    top: var(--header-h);
    left: 0;
    right: 0;
    z-index: 45;
    margin: 0;
    padding: var(--sp-2);
    border-bottom: 1px solid var(--border);
    background: var(--surface);
  }
  .nav.is-open a { border-bottom-color: transparent; }
  .nav a.nav__fold { display: inline-flex; }
  .menu-btn { display: inline-flex; }
  .auth { margin-left: auto; }
}

@media (max-width: 860px) {
  .rail { position: static; }

  .searchrow { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .searchrow .btn,
  .searchrow__more { grid-column: 1 / -1; }
  .searchrow__more { justify-content: center; }

  .footer-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* The reassurance column moves below the card rather than squeezing it. */
  .authgrid--wide { grid-template-columns: minmax(0, 460px); }

  /* ---- EVERY TAPPABLE THING GOES BACK TO 44px ------------------------
     docs/CARHUB_REDESIGN.md section 14. This block was at 640px and
     REDESIGN-03 Part 5 moved it to 860, because 768 is one of the widths
     the mobile pass names and .btn--sm was still 36px there — a tablet is
     a touch screen and 768px is the width the client will hold one at.

     The first four were measured by REDESIGN-01b:

       .btn--sm      36px. A desktop compaction. The header's Post Your Ad,
                     every action on a my-ads row, Open and bid on my-bids.
       .linklist a   36px. The popular-make chips and the city row, which sit
                     shoulder to shoulder, which is where a 36px target beside
                     another 36px target puts a thumb on the wrong one.
       .section-head a  21px. "View all 19 cars in Lahore" — a whole section's
                     worth of destination, one line of text high.
       .brand        34px. The way home from every page on the site.

     And these five by REDESIGN-03, on the member journey:

       .pager a      36px. Page 2 of a listing, and the two beside it.
       .pwfield__eye 40x42. The show-password control on three screens.
       summary       19px. "Why we ask for this" on step 1, "Before you send
                     it" on the CNIC screen, "Report this ad" on a car.
       .switch       21px. The WhatsApp toggle on step 3 of the posting form.
       .formnav__draft 21px. "Save and finish later" — the control that
                     stops a seller losing forty fields of work. */
  .btn--sm { min-height: var(--control-h); padding-inline: var(--sp-4); }
  .linklist a { min-height: var(--control-h); }
  .section-head a:not(.btn) { display: inline-flex; align-items: center; min-height: var(--control-h); }
  .brand { min-height: var(--control-h); }
  .pager a, .pager span { min-height: var(--control-h); min-width: var(--control-h); }
}

@media (max-width: 640px) {
  .auth { display: none; }
  .acct { display: block; margin-left: auto; }

  /* 320px. The named account control is 174px wide — wider than the space
     left beside the brand — so it drops back to the initials chip and the name
     moves inside the menu, where .acct__who already prints it. */
  .acct--member .acct__name,
  .acct--member .acct__btn > svg { display: none; }
  .acct--member .acct__btn { width: var(--control-h); padding: 0; }

  .searchrow { grid-template-columns: minmax(0, 1fr); }
  .searchpanel { padding: var(--sp-4); margin-top: calc(var(--sp-6) * -1); }

  /* The tabs scroll rather than wrap: three tabs on two lines above a form
     reads as two rows of navigation. */
  .searchtabs { overflow-x: auto; scrollbar-width: none; }
  .searchtabs::-webkit-scrollbar { display: none; }

  .hero__wrap { padding-block: var(--sp-5); }
  .hero__title { max-width: none; }

  .footer-cols { grid-template-columns: minmax(0, 1fr); }

  /* A footer column on a phone is a stack of links a thumb works down. */
  .site-footer li { margin-bottom: 0; }
  .site-footer li a {
    display: flex;
    align-items: center;
    min-height: var(--control-h);
  }
  .footer-popular li a {
    display: inline-flex;
    align-items: center;
    min-height: var(--control-h);
  }

  .sellcta { padding: var(--sp-5); }

  /* One column, so there is no row to line up with. Drop the equal-height
     minimum rather than add 68px of empty space to every card on a phone. */
  .card__body,
  .card--auction .card__body { min-height: 0; }

  .notfound__code { font-size: 48px; }

  .authpage { padding: var(--sp-5) 0 var(--sp-6); }
  .authcard { padding: var(--sp-4); }
}

/* 380px and below is the 360px phone with a browser chrome inset. Five budget
   tiles and four quick actions are already one column by here; what is left is
   the header row.

   The brand name USED to be hidden here, leaving the initials square as the
   way home. There is no square any more — the wordmark IS the logo, and a
   header with no logo in it is not a saving. So it steps down a size instead
   and stays legible and tappable. */
@media (max-width: 380px) {
  .wordmark { font-size: var(--fs-md); }
}

/* ==========================================================================
   17. THE CITY LISTING PAGE
   demo/used-cars-lahore.html. BUILD-05.

   The two-column body itself is .layout in base.css — the same shape the
   dealer directory uses in BUILD-05b, because docs/PAGES.md requires the two
   to be one screen with different rows in it.
   ========================================================================== */
.rail__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  flex: none;
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

/* The number of filters applied, beside the word. The same figure the Filters
   button on a phone carries, and both are drawn from Chips. */
.rail__count {
  display: inline-grid;
  place-items: center;
  min-width: 22px;
  height: 22px;
  margin-inline-start: var(--sp-1);
  padding-inline: 6px;
  border-radius: var(--radius-pill);
  background: var(--action);
  color: var(--on-action);
  font-family: var(--font-num);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  vertical-align: middle;
}

.rail__clear {
  display: inline-flex;
  align-items: center;
  min-height: var(--control-h);
  padding-inline: var(--sp-2);
  color: var(--text-2);
  font-size: var(--fs-xs);
  text-decoration: underline;
}
.rail__clear:hover { color: var(--danger); }

/* The Close button belongs to the phone sheet and to nothing else. */
.rail__close {
  display: none;
  place-items: center;
  width: var(--control-h);
  height: var(--control-h);
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--surface-alt);
  color: var(--text);
  cursor: pointer;
}

.rail__arrow { transition: transform var(--dur-fast) var(--ease); }
.rail__head[aria-expanded="false"] .rail__arrow { transform: rotate(-90deg); }

.rail__search { padding-block: var(--sp-2); }

/* A group the visitor has narrowed on gets a marker in the ACTION colour, so
   the rail says at a glance where the filters are without having to be
   scrolled. It was --primary until REDESIGN-02; a selection is a thing the
   visitor did, and this rail says so in one colour throughout. */
.rail__group.is-active > .rail__head {
  box-shadow: inset 3px 0 0 var(--action);
}

/* ---- One option row -------------------------------------------------------
   THE LABEL IS THE CONTROL AND IT IS 44px TALL. The checkbox inside it stays
   small; a 44px tick box looks like a mistake and is not what section 14 of
   the redesign document is asking for. What it asks for is a target, and the
   target is the whole row. */
.rail .check {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--control-h);
  padding-inline: var(--sp-4);
  margin-inline: calc(var(--sp-3) * -1);
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  cursor: pointer;
}
.rail .check:hover { background: var(--surface-alt); }

.rail .check input {
  flex: none;
  width: 18px; height: 18px;
  accent-color: var(--action);
}

.rail .check__label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rail .check__count {
  margin-left: auto;
  flex: none;
  color: var(--text-3);
  font-family: var(--font-num);
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
}

/* SELECTED, IN THE ACTION COLOUR. The wash is mixed from --action and
   --surface rather than being a new token, so a theme change carries it and
   the 46-token contract is unchanged — the same trick REDESIGN-01b used on
   .band--live. The label is --action-text, the darkened orange, because raw
   #FF6A00 on white is 2.87:1. */
.rail .check:has(input:checked) {
  background: color-mix(in srgb, var(--action) 9%, var(--surface));
  box-shadow: inset 2px 0 0 var(--action);
}
.rail .check:has(input:checked) .check__label {
  color: var(--action-text);
  font-weight: var(--fw-semibold);
}
.rail .check:has(input:checked) .check__count { color: var(--action-text); }

/* An option that would return nothing. Dimmed, never hidden — and since
   REDESIGN-02 it also sinks to the bottom of its group, so the six options a
   folded group shows are six worth pressing. _RailGroup.cshtml does the
   ordering; this is what it looks like. */
.rail .check.is-none { opacity: .5; }

.check--swatch { gap: var(--sp-2); }

/* ---- Show more ------------------------------------------------------------
   A <details>, so it opens with no JavaScript and a checkbox inside it is
   still in the form. */
.rail__more > summary {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--control-h);
  padding-inline: var(--sp-4);
  margin-inline: calc(var(--sp-3) * -1);
  border-radius: var(--radius-sm);
  color: var(--action-text);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  list-style: none;
}
.rail__more > summary::-webkit-details-marker { display: none; }
.rail__more > summary:hover { background: var(--surface-alt); }
.rail__more > summary svg { transition: transform var(--dur-fast) var(--ease); }

.rail__more .rail__more-out { display: none; }
.rail__more[open] .rail__more-in { display: none; }
.rail__more[open] .rail__more-out { display: inline; }
.rail__more[open] > summary svg { transform: rotate(180deg); }

.rail__submit {
  flex: none;
  padding: var(--sp-3) var(--sp-4);
  border-top: 1px solid var(--border);
  background: var(--surface);
}

/* ---- The three sliders ---------------------------------------------------
   The two number inputs are the control and are what gets submitted. The
   track is drawn over them by listing.js; with no script the pair below is
   simply a pair of number fields, which is a working filter. */
.range__label {
  margin-bottom: var(--sp-2);
  color: var(--text-2);
  font-family: var(--font-num);
  font-size: var(--fs-xs);
}

.range__pair {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--sp-2);
}
.range__dash { color: var(--text-3); }

.input--sm { min-height: 32px; padding: var(--sp-1) var(--sp-2); font-size: var(--fs-xs); }

/* The rail's own field size. --control-h is 44px and tokens.css says why:
   "a control that is 40px on a phone is a control that gets missed".
   REDESIGN-01 measured these at 24-32px and handed the number to this cycle. */
.input--rail {
  min-height: var(--control-h);
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-sm);
}

.range__track {
  position: relative;
  height: var(--control-h);
  margin-bottom: var(--sp-2);
}
.range__track::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 21px;
  height: 3px;
  background: var(--border-strong);
  border-radius: var(--radius-pill);
}
.range__fill {
  position: absolute;
  top: 21px;
  height: 3px;
  background: var(--action);
  border-radius: var(--radius-pill);
}
.range__track input[type="range"] {
  position: absolute;
  inset-inline: 0;
  top: 0;
  width: 100%;
  height: var(--control-h);
  margin: 0;
  background: none;
  pointer-events: none;
  appearance: none;
  -webkit-appearance: none;
}

/* 22px handles rather than 16. The track is 44px tall, so the finger has the
   full height of it to land on; the handle only has to be big enough to see
   and to grab. */
.range__track input[type="range"]::-webkit-slider-thumb {
  pointer-events: auto;
  appearance: none;
  -webkit-appearance: none;
  width: 22px; height: 22px;
  border: 3px solid var(--action);
  border-radius: var(--radius-pill);
  background: var(--surface);
  box-shadow: var(--shadow-1);
  cursor: pointer;
}
.range__track input[type="range"]::-moz-range-thumb {
  pointer-events: auto;
  width: 22px; height: 22px;
  border: 3px solid var(--action);
  border-radius: var(--radius-pill);
  background: var(--surface);
  box-shadow: var(--shadow-1);
  cursor: pointer;
}

/* ---- Results ------------------------------------------------------------- */
.results__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
}
.results__count { color: var(--text-2); font-size: var(--fs-sm); }

.results__sort {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.results__sort label { color: var(--text-2); font-size: var(--fs-xs); }
.results__sort .select { width: auto; min-width: 180px; }

.results__grid { margin-bottom: var(--sp-5); }

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-bottom: var(--sp-4);
}
.chip--clear { border-style: dashed; color: var(--text-2); }
.chip--clear:hover { border-color: var(--danger); color: var(--danger-text); text-decoration: none; }

.pager__step { padding-inline: var(--sp-3); }
.pager__gap { border-color: transparent; color: var(--text-3); }

.h1__page {
  color: var(--text-2);
  font-size: var(--fs-md);
  font-weight: var(--fw-regular);
}

/* The two small tables under the grid. */
.factbox {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-5);
  margin-top: var(--sp-5);
}
.factbox caption {
  padding-bottom: var(--sp-2);
  color: var(--text-2);
  font-size: var(--fs-xs);
  text-align: left;
  text-transform: uppercase;
  letter-spacing: .04em;
}
.table--compact th, .table--compact td { padding: var(--sp-1) var(--sp-2); }

.intro p { max-width: 72ch; }

/* ---- A PROSE BLOCK WITH NOTHING BESIDE IT ------------------------------
   REDESIGN-03b Part 4, and it was the client's finding: "What a verified
   dealer is" on the dealers directory ran at about 660px with roughly 700px
   of white space beside it, and it read as a broken layout rather than as a
   column.

   A reading measure is right — 72ch is about 12 words a line and the reason
   .intro p has had it since BUILD-05 — but a measure LEFT-ALIGNED IN A
   1,152px BAND is only right when something occupies the rest of the band. On
   a listing page the rail does; at the foot of the page, under a heading,
   nothing does.

   So the block is centred instead. The heading goes inside it, because a
   heading left-aligned over a centred paragraph is a worse seam than the one
   being fixed, and the paragraphs give up their own max-width to the
   container so that one measure governs the whole block and the small print
   under it does not come out narrower again.

   IT IS OPT-IN, one class in the view, and not a rule on .intro. .intro is
   also used where there IS something beside it, and a rule that centres by
   default would be the same mistake pointing the other way. */
.prose {
  max-width: 72ch;
  margin-inline: auto;
}
.prose .intro p,
.prose > p { max-width: none; }

/* The Filters button and the backdrop behind the sheet. Desktop shows
   neither of them. */
.filters-btn { display: none; }
.rail-backdrop { display: none; }

/* The count of applied filters, on the Filters button. Same figure as the one
   in the sheet's own heading, and both come from Chips. */
.filters-btn__count {
  display: inline-grid;
  place-items: center;
  min-width: 20px;
  height: 20px;
  padding-inline: 5px;
  border-radius: var(--radius-pill);
  background: var(--action);
  color: var(--on-action);
  font-family: var(--font-num);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
}

/* ==========================================================================
   THE FILTER SHEET                                   REDESIGN-02, Part 3
   ==========================================================================
   Below 860px the rail is a BOTTOM SHEET rather than the left drawer BUILD-05
   built. docs/CARHUB_REDESIGN.md section 10 asks for "a bottom sheet /
   full-screen panel" by name, and the reason it is the right shape is the
   thumb: a sheet's controls rise from the bottom of the screen, where the hand
   already is, and a left drawer's do not.

   IT IS THREE PARTS AND THE MIDDLE ONE IS THE ONLY THING THAT SCROLLS.
   The heading with Clear all and the close button stays at the top, the
   sixteen groups scroll between, and "Show results" is fixed at the foot — so
   the apply button is never a scroll away, which is the failure mode of every
   filter panel that is one long column.

   AND IT WORKS WITH NO JAVASCRIPT, which is the rule the whole SEO case rests
   on. With no script there is no Filters button (listing.js is what shows it),
   the sheet's fixed positioning never applies because .is-open is never set,
   and the rail is simply a panel in the page above the results with a submit
   button at the bottom of it. Tick, press, the server re-renders.
   ========================================================================== */
@media (max-width: 860px) {
  /* THE BUTTON IS ONLY DRAWN WHEN THERE IS A SCRIPT TO ANSWER IT.
     BUILD-05 drew it from the stylesheet alone and translated the rail off the
     left of the screen in the same rule — so with JavaScript blocked the phone
     got a Filters button that did nothing and a filter form it could not
     reach at all. The rule that the listing filters without script had been
     true of the markup and false of the stylesheet since BUILD-05.
     listing.js adds .has-sheet, and everything below hangs off it. */
  body.has-sheet .filters-btn { display: inline-flex; margin-bottom: var(--sp-4); }

  /* The rail in the page, for the no-script case: full width, above the
     results, and not fixed to anything. */
  .rail {
    position: static;
    max-height: none;
    margin-bottom: var(--sp-5);
  }

  /* And the sheet, which only exists once listing.js has said so. Without
     the script .rail--sheet is never added and none of this applies. */
  .rail--sheet {
    position: fixed;
    inset-inline: 0;
    inset-block-end: 0;
    top: auto;
    z-index: 60;
    max-height: min(86vh, 720px);
    margin-bottom: 0;
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    box-shadow: var(--shadow-3);
    transform: translateY(100%);
    transition: transform var(--dur-base) var(--ease);
  }
  .rail--sheet.is-open { transform: translateY(0); }

  .rail__grab {
    display: block;
    flex: none;
    width: 44px;
    height: 4px;
    margin: var(--sp-2) auto 0;
    border-radius: var(--radius-pill);
    background: var(--border-strong);
  }
  .rail--sheet .rail__close { display: grid; }

  /* The apply button is the whole point of the foot on a phone. */
  .rail--sheet .rail__submit { box-shadow: 0 -1px 0 var(--border); }

  body.has-sheet .rail-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 50;
    background: var(--overlay);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur-base) var(--ease);
  }
  .rail-backdrop.is-open { opacity: 1; pointer-events: auto; }

  body.is-locked { overflow: hidden; }

  .factbox { grid-template-columns: minmax(0, 1fr); }
}

/* ==========================================================================
   18. ONE CAR
   demo/car-corolla.html and demo/car-fortuner-auction.html. BUILD-05.

   Named grid areas rather than two plain columns, because on a phone the order
   has to be gallery, title, price, key specs, everything else: a buyer has to
   know which car this is before the price means anything, and the price has to
   beat the specification table to the top of the screen.
   ========================================================================== */
.detail {
  display: grid;
  gap: var(--sp-5);
  grid-template-columns: minmax(0, 1fr) 340px;
  grid-template-areas:
    "gallery side"
    "title   side"
    "specs   side"
    "body    side";
  align-items: start;
}
.detail__gallery { grid-area: gallery; }
.detail__title   { grid-area: title; }
.detail__side    { grid-area: side; display: grid; gap: var(--sp-4); align-content: start; }
.specstrip       { grid-area: specs; }
.detail__body    { grid-area: body; }

.detail__line { color: var(--text-2); font-size: var(--fs-md); }
.detail__ref  { color: var(--text-3); font-size: var(--fs-xs); }

/* ---- Gallery ---------------------------------- REDESIGN-02, Part 4 ------
   ONE DOMINANT IMAGE AND A THUMBNAIL STRIP — docs/CARHUB_REDESIGN.md §11.

   THE STRIP SCROLLS SIDEWAYS RATHER THAN WRAPPING, and that is the change
   that makes twenty photographs work. A six-column grid of twenty thumbnails
   is four rows, which pushes the price, the seller and the call button a
   screen and a half down the page — on the page where those three are what
   the buyer came for. A 96px scroller is one row whatever the count is.

   The strip is an overflow-x box, so REDESIGN-01b's finding applies: every
   child of it is position:relative, because an absolutely positioned
   descendant with no positioned ancestor lands in the DOCUMENT's scroll width
   and takes the whole page sideways with it. */
.gallery__main {
  position: relative;
  aspect-ratio: var(--card-aspect);
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-alt);
}
.gallery__img { width: 100%; height: 100%; object-fit: cover; }

/* The cursor is a class rather than an inline style, because detail.js is
   what adds it and a script setting a style is a script one rule away from
   setting a colour. tools/theme-audit.py T1. */
.gallery__img.is-zoomable { cursor: zoom-in; }

.gallery__nav {
  position: absolute;
  top: 50%;
  translate: 0 -50%;
  display: grid;
  place-items: center;
  width: var(--control-h); height: var(--control-h);
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-2);
  cursor: pointer;
  opacity: .92;
}
.gallery__nav:hover { opacity: 1; }
.gallery__nav--prev { inset-inline-start: var(--sp-3); }
.gallery__nav--next { inset-inline-end: var(--sp-3); }

/* The expand control. Rendered hidden and unhidden by detail.js, so a visitor
   with no script never sees a button that cannot do anything. */
.gallery__expand {
  position: absolute;
  inset-block-start: var(--sp-3);
  inset-inline-end: var(--sp-3);
  display: grid;
  place-items: center;
  width: var(--control-h); height: var(--control-h);
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-2);
  cursor: pointer;
  opacity: .92;
}
.gallery__expand:hover { opacity: 1; }

.gallery__count {
  position: absolute;
  inset-block-end: var(--sp-3);
  inset-inline-end: var(--sp-3);
  padding: 4px var(--sp-3);
  border-radius: var(--radius-pill);
  background: var(--surface-dark);
  color: var(--text-inverse);
  font-family: var(--font-num);
  font-size: var(--fs-xs);
}

.gallery__thumbs {
  display: flex;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
  padding-bottom: var(--sp-1);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scrollbar-width: thin;
}
.gallery__thumb {
  position: relative;
  flex: 0 0 96px;
  display: block;
  aspect-ratio: var(--card-aspect);
  overflow: hidden;
  border: 2px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-alt);
  scroll-snap-align: start;
  cursor: pointer;
}
.gallery__thumb[aria-current="true"] { border-color: var(--action); }
.gallery__thumb img { width: 100%; height: 100%; object-fit: cover; }

/* ---- The lightbox ---------------------------------------------------------
   BUILT BY detail.js AND NOT RENDERED BY THE SERVER. An overlay in the markup
   is an overlay a visitor with no script can open by tabbing into it and then
   cannot close. Everything below styles elements that only exist once the
   script has run. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: grid;
  grid-template-rows: auto 1fr auto;
  background: var(--overlay-strong);
}
.lightbox__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  color: var(--text-inverse);
}
.lightbox__count { font-family: var(--font-num); font-size: var(--fs-sm); }

.lightbox__stage {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 0;
  padding-inline: var(--sp-4);
}
.lightbox__img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: var(--radius-md);
}

.lightbox__btn {
  display: grid;
  place-items: center;
  width: var(--control-h); height: var(--control-h);
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-2);
  cursor: pointer;
}
.lightbox__btn:disabled { opacity: .4; cursor: default; }

.lightbox__nav {
  position: absolute;
  top: 50%;
  translate: 0 -50%;
}
.lightbox__nav--prev { inset-inline-start: var(--sp-4); }
.lightbox__nav--next { inset-inline-end: var(--sp-4); }

.lightbox__strip {
  display: flex;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  overflow-x: auto;
  overscroll-behavior-x: contain;
}
.lightbox__thumb {
  position: relative;
  flex: 0 0 72px;
  aspect-ratio: var(--card-aspect);
  overflow: hidden;
  padding: 0;
  border: 2px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
  cursor: pointer;
}
.lightbox__thumb[aria-current="true"] { border-color: var(--action); }
.lightbox__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

.lightbox :focus-visible { outline-color: var(--focus-ring-dark); }

/* Three across, on a dealer card, pushed to the foot of the card so the
   strips line up across a row whatever the tags above them did. A showroom
   with no photographs renders no strip at all rather than three grey
   rectangles. */
/* THE DEALER CARD'S STOCK STRIP.                     REDESIGN-03, Part 3
   It used to be .gallery__thumbs with a .gallery__thumbs--3 modifier setting
   grid-template-columns, which worked while the gallery strip was a grid.
   REDESIGN-02 rebuilt the gallery strip as a horizontal SCROLLER — flex, with
   flex: 0 0 96px on every thumbnail — and grid-template-columns does nothing
   to a flex box, so three 96px thumbnails and their gaps came to more than
   the card was wide and the third car was sliced down its middle on every
   card in the directory.

   Three equal columns of whatever the card is, and no scroller inside a card.
   margin-top:auto pins the strip to the bottom of the body, so the strips
   line up across a row of cards of different heights. */
.stockstrip {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-2);
  margin-top: auto;
  padding-top: var(--sp-2);
}
.stockstrip__shot {
  display: block;
  aspect-ratio: var(--card-aspect);
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-alt);
}
.stockstrip__shot img { width: 100%; height: 100%; object-fit: cover; display: block; }

.gallery--empty {
  display: grid;
  place-items: center;
  aspect-ratio: var(--card-aspect);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm);
}

/* ---- The price and seller card -------------------------------------------- */
.sidecard {
  padding: var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}

/* --live is auctions and nothing else. docs/THEMING.md section 4. */
.sidecard--auction { border-color: var(--live); }

.sidecard__label {
  display: block;
  color: var(--text-2);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: .04em;
}
.sidecard__price {
  margin-block: var(--sp-1) var(--sp-3);
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
}
.sidecard--auction .sidecard__price { color: var(--live-text); }

.sidecard__actions { display: grid; gap: var(--sp-2); margin-top: var(--sp-4); }

.sidecard__foot {
  margin-top: var(--sp-4);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
}

/* The number, once it has been asked for. It is not in the document before
   that request — see Views/Cars/_SellerCard.cshtml. */
.phone {
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
  font-family: var(--font-num);
  font-size: var(--fs-md);
  text-align: center;
}

.closes-at { margin-top: var(--sp-2); color: var(--text-2); font-size: var(--fs-xs); }

.bidhistory {
  padding: var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}

/* ---- Key specs ------------------------------------------------------------ */
.specstrip {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: var(--sp-2);
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
}
.specstrip__item { display: grid; gap: 2px; text-align: center; }

/* Three numbers rather than six: the summary strip on My ads. It keeps its
   three columns on a phone rather than leaving an empty fourth cell. */
.specstrip--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.specstrip__label { color: var(--text-3); font-size: var(--fs-xs); }
.specstrip__value { font-weight: var(--fw-medium); font-size: var(--fs-sm); }

/* ---- Specification table --------------------------------------------------- */
.detail__section + .detail__section { margin-top: var(--sp-6); }

.spectable { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.spectable th, .spectable td {
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border);
  text-align: left;
  vertical-align: top;
}
.spectable th { width: 40%; color: var(--text-2); font-weight: var(--fw-regular); }
.spectable td small { display: block; margin-top: 2px; color: var(--text-2); }

/* Paint condition and Original file. Both are compulsory on the posting form
   and both are what a buyer actually reads the table for, so both are shaded. */
.spectable tr.is-key { background: var(--warning-soft); }
.spectable tr.is-key th, .spectable tr.is-key td { font-weight: var(--fw-medium); }

/* ---- Features ---------------------------------------------------------------
   All twenty print. What the car does NOT have is as useful to know as what it
   does — docs/DB_SCHEMA.md section 3. */
.features {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-1) var(--sp-4);
}
.feature {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: 0;
  font-size: var(--fs-sm);
}
.feature--yes { color: var(--text); }
.feature--yes svg { color: var(--ok); }
.feature--no { color: var(--text-3); }

.description p { max-width: 72ch; }

/* ==========================================================================
   THE DEALER PROFILE — docs/PAGES.md page 7

   Two grids. The header band is the showroom name and the contact card side
   by side; the page below is the car grid and the showroom column. Both
   collapse to one column, and in both the contact details go ABOVE the cars
   on a phone — a visitor who has scrolled to a dealer profile is usually
   looking for the showroom, not browsing.
   ========================================================================== */
.dealerhead {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: var(--sp-5) var(--sp-6);
  align-items: start;
  margin-top: var(--sp-3);
}
.dealerhead__main { min-width: 0; }
.dealerhead__contact { min-width: 0; }

/* The name with its monogram beside it.              REDESIGN-03, Part 3
   The directory card has carried a monogram since BUILD-05b and the page it
   links to had none, so pressing a card took the visitor to something that
   did not look like the thing he pressed. Same tile, one step larger. */
.dealerhead__name { display: flex; align-items: center; gap: var(--sp-4); min-width: 0; }
.dealerhead__name h1 { min-width: 0; }
.tile__logo--lg {
  width: 60px;
  height: 60px;
  flex: none;
  border-radius: var(--radius-lg);
  font-size: var(--fs-xl);
}

/* ---- Section headings that carry a count -------------------------------
   `<h2 class="h-sm">Cars for sale from X <span class="c">15</span></h2>` has
   been in the dealer profile since BUILD-05b and NEITHER CLASS HAD A RULE
   BEHIND IT — so the heading was a full 28px h2 and the count was a 28px
   number hanging off the end of the sentence, which is how "Cars for sale
   from Al-Hamd Motors 15" came to be a heading. REDESIGN-03 gives both a
   rule: the heading drops a step, and the count becomes a pill in the
   primary wash. */
.h-sm {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  font-size: var(--fs-lg);
}
.h-sm .c {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 28px;
  padding: 2px var(--sp-2);
  border-radius: var(--radius-pill);
  background: var(--primary-soft);
  color: var(--primary);
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
}

/* The figures card, inside the contact panel. No border of its own — it is
   already inside one — and the labels drop a step because the panel is
   320px wide and "Auctions run" over two lines is not a figure. */
.figures--inline {
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  gap: var(--sp-3);
}
.figures--inline .figures__label { font-size: var(--fs-xs); }
.figures--inline .figures__value { font-size: var(--fs-lg); }

.dealer {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: var(--sp-5) var(--sp-6);
  align-items: start;
  padding: var(--sp-5) 0 var(--sp-6);
}
.dealer__main { min-width: 0; }
.dealer__side {
  min-width: 0;
  position: sticky;
  top: calc(var(--header-h) + var(--sp-4));
}
.dealer__address {
  font-style: normal;
  font-size: var(--fs-sm);
  color: var(--text-2);
  margin-bottom: var(--sp-3);
}

/* .mapbox IS GONE — REDESIGN-03b Part 5. It was a dashed grey rectangle
   reading "Map goes here" on a page the client shows to dealers, held open at
   the size of a map nobody has chosen a provider for. The rule is deleted
   with the markup rather than left behind: a class with no element is the
   thing somebody re-uses by accident. BUILD-10 writes both again with a real
   embed behind them. */

@media (max-width: 1000px) {
  .dealerhead,
  .dealer {
    grid-template-columns: minmax(0, 1fr);
  }
  .dealer__side { position: static; }

  /* THE CONTACT CARD IS NO LONGER FIRST, AND REDESIGN-03 CHANGED IT BY
     LOOKING AT IT. BUILD-05b put it above the name on a phone on the
     reasonable-sounding argument that the telephone number is the point of
     the page. What that produced was a breadcrumb, then a panel headed
     "CONTACT THE SHOWROOM" with a Show phone number button on it, and only
     then the name of the showroom — a call to action for a business the
     visitor has not yet been told the name of.

     The name, the tick, the area and the sentence come first now. The button
     is one short scroll below them and is still the first control on the
     page. .dealer__side keeps its order: the address, the opening hours and
     the specialisation belong above thirty cars, not under them. */
  .dealer__side { order: -1; }
}

@media (max-width: 1000px) {
  .detail {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "gallery"
      "title"
      "side"
      "specs"
      "body";
  }
  .specstrip { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .features { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 640px) {
  .gallery__thumbs { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .specstrip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .specstrip--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .features { grid-template-columns: minmax(0, 1fr); }
  .results__bar { align-items: flex-start; flex-direction: column; }
}

/* ==========================================================================
   14. POSTING — the four-step form, my ads
   Ported from demo/post-ad-1..4.html, post-ad-done.html and my-ads.html.
   BUILD-06.

   The demo stylesheet has its own token block (--s-4, --fs-12, --radius), so
   every value below is the application token instead. No colour value appears
   here; theme-audit.py rule T1 fails the build if one does.
   ========================================================================== */

/* ---------- Shell ------------------------------------------------------- */
.postad {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  grid-template-areas:
    "head head"
    "main side";
  gap: var(--sp-5) var(--sp-6);
  align-items: start;
  padding: var(--sp-4) 0 var(--sp-7);
}
.postad--single { grid-template-columns: minmax(0, 1fr); grid-template-areas: "head" "main"; }
.postad__head { grid-area: head; min-width: 0; }
/* --fs-2xl and not --fs-lg. Every screen that uses this shell — the four
   posting steps, the profile, the CNIC form — had a 22px h1 with 28px h2s
   under it, so the page heading was the smallest heading on the page. It
   drops to 26px on a phone through the token, like every other h1.
   REDESIGN-03. */
.postad__head h1 { font-size: var(--fs-2xl); margin-bottom: var(--sp-2); }
.postad__main { grid-area: main; min-width: 0; }
.postad__side {
  grid-area: side;
  position: sticky;
  top: calc(var(--header-h) + var(--sp-4));
  min-width: 0;
}

.helpcard {
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--sp-4);
  font-size: var(--fs-sm);
}
.helpcard h2 { font-size: var(--fs-base); margin-bottom: var(--sp-3); }
.helpcard h3 { font-size: var(--fs-sm); margin: var(--sp-4) 0 var(--sp-1); }
.helpcard h3:first-of-type { margin-top: 0; }
.helpcard p { color: var(--text-2); margin-bottom: 0; }

/* On a phone the help column sits ABOVE the form (see the ladder at the end
   of this section), and three paragraphs of it is three paragraphs the seller
   scrolls past every time he posts a car. The ones marked data-collapse-sm
   start closed below 1000px and open on desktop, so the argument is still in
   front of the client and still one tap from the seller. */
details.helpcard > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  min-height: var(--control-h);
  list-style: none;
  cursor: pointer;
}
details.helpcard > summary::-webkit-details-marker { display: none; }
details.helpcard > summary h2 { margin-bottom: 0; }
details.helpcard[open] > summary { margin-bottom: var(--sp-3); }
.helpcard__chev {
  flex: none;
  display: none;
  color: var(--text-2);
  transition: transform var(--dur-fast) var(--ease);
}
details.helpcard[open] .helpcard__chev { transform: rotate(180deg); }

/* ---------- Progress bar, four steps ------------------------------------
   REDESIGN-03 Part 1: "a step indicator showing where he is and what
   remains, not just a number."

   What was here was four hairlines and four 18px discs, and at 12px the
   labels beside them read as caption text rather than as a position in a
   journey. Three things changed:

   THE RAIL IS 6px AND IT FILLS. A completed step is a full bar in --ok, the
   current step is a full bar in --primary, and a step ahead is the empty
   track. Four bars of the same weight said "four things"; four bars of which
   two are filled says "you are half way", which is the question a seller
   standing in a showroom is actually asking.

   THE DISC IS 26px AND CARRIES A TICK. Big enough to read at arm's length,
   and a completed step shows a tick rather than its number because its number
   is no longer the useful fact about it.

   THE LABEL STAYS. Below 640px the three labels that are not the current one
   drop out — there is not room for four across 358px — and .steps__left
   prints what is left instead, so the row still answers "and then what". */
.steps {
  display: flex;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
  list-style: none;
  padding: 0;
}
.steps li { flex: 1; min-width: 0; font-size: var(--fs-xs); color: var(--text-2); }
.steps .bar {
  display: block;
  height: 6px;
  border-radius: 3px;
  background: var(--border);
  margin-bottom: var(--sp-2);
}
.steps .is-done .bar, .steps .is-current .bar { background: var(--primary); }
.steps .is-current { color: var(--primary); font-weight: var(--fw-semibold); }
.steps__t {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-sm);
}
.steps a, .steps .steps__label {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--control-h);
  color: inherit;
  min-width: 0;
}
.steps a:hover { color: var(--primary); text-decoration: none; }
.steps__n {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border-strong);
  background: var(--surface);
  font-family: var(--font-num);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  color: var(--text-2);
}
.steps .is-done { color: var(--ok-text); }
.steps .is-done .bar { background: var(--ok); }
.steps .is-done .steps__n {
  background: var(--ok);
  border-color: var(--ok);
  color: var(--on-primary);
}
.steps .is-current .steps__n {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--on-primary);
  box-shadow: 0 0 0 4px var(--primary-soft);
}

/* "Step 2 of 4 — photographs next, then price, then a preview." The sentence
   that makes the row an indicator of what remains rather than of where he
   is. */
.steps__left {
  margin: 0 0 var(--sp-5);
  color: var(--text-2);
  font-size: var(--fs-sm);
}
.steps__left b { color: var(--text); font-weight: var(--fw-semibold); }

/* ---------- Field groups ------------------------------------------------
   FORTY CONTROLS IN ONE UNDIFFERENTIATED COLUMN IS WHY PEOPLE ABANDON FORMS,
   and until REDESIGN-03 that column was drawn with the browser's own
   <fieldset> border and the <legend> notched into it — the 1997 default, on
   the one screen that decides whether an ad gets posted at all.

   A group is a card now: --surface on the page's --bg, the same border and
   radius the vehicle card uses, and --sp-5 of room inside it. The legend is
   the card's heading and the note under it is the sentence that says why the
   group is being asked. Five cards down the page is five decisions; one
   ruled box of forty controls is one long chore.

   THE LEGEND IS FLOATED. display:block on a <legend> puts it in flow in every
   modern engine, but Chrome still lays it out against the fieldset's BORDER
   box rather than inside the padding, so the heading came out straddling the
   top edge of the card. Floated at width 100% it is unambiguously inside, and
   the element after it clears the float — which is what `legend + *` is for
   below, and the only reason that selector exists. */
.postad__main fieldset {
  margin: 0 0 var(--sp-5);
  padding: var(--sp-5);
  border: 0;
  border-radius: var(--radius-lg);
  background: var(--surface);
  min-width: 0;

  /* THE BORDER IS A SHADOW AND THAT IS NOT A FLOURISH. A <fieldset> draws its
     border with a gap cut out of it where the legend sits, and the gap stays
     even when the legend is display:block — the card came out with a notch in
     its top edge beside every heading. An inset 1px shadow is the same
     hairline with no notch in it, and the elevation rides along behind. */
  box-shadow: inset 0 0 0 1px var(--border), var(--shadow-1);
}
.postad__main fieldset[hidden] { display: none; }
.postad__main legend {
  float: left;
  width: 100%;
  margin: 0 0 var(--sp-1);
  padding: 0;
  color: var(--primary);
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
}
.postad__main legend + * { clear: both; }
.fieldset__note {
  font-size: var(--fs-sm);
  color: var(--text-2);
  margin: 0 0 var(--sp-4);
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--border);
  max-width: 78ch;
}
.fieldrow { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 var(--sp-4); }
.fieldrow--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.fieldrow:last-child > .field:last-child { margin-bottom: 0; }

/* City and Registered in. Side by side in one row they read as one question
   asked twice, and the seller leaves the second on whatever the first says,
   which is where mis-registered cars come from and what the client pays for
   in moderation time. A hairline between the columns says "two questions".
   docs/AD_FORM_SPEC.md. */
.fieldrow--split { column-gap: var(--sp-5); }
.fieldrow--split > * + * { border-left: 1px solid var(--border); padding-left: var(--sp-5); }

/* The unit after the number rather than before it: "78000 km", where the
   price field wants "PKR 52,50,000". Same part, mirrored. */
.bidinput { display: flex; margin-top: var(--sp-2); }
.bidinput__prefix {
  display: flex;
  align-items: center;
  padding: 0 var(--sp-3);
  border: 1px solid var(--border);
  border-right: 0;
  border-radius: var(--radius-sm) 0 0 var(--radius-sm);
  background: var(--surface-alt);
  font-family: var(--font-num);
  font-size: var(--fs-sm);
  color: var(--text-2);
  flex: none;
}
.bidinput .input {
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  min-width: 0;
  font-family: var(--font-num);
}
.bidinput--suffix .input {
  border-radius: var(--radius-sm) 0 0 var(--radius-sm);
  border-right: 0;
}
.bidinput--suffix .bidinput__prefix {
  border-left: 0;
  border-right: 1px solid var(--border);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

.counterline { display: flex; justify-content: space-between; gap: var(--sp-3); }
.checkcols { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 var(--sp-5); }
.input[readonly] { background: var(--surface-alt); color: var(--text-2); cursor: default; }
.select:disabled { background-color: var(--surface-alt); color: var(--text-2); cursor: not-allowed; }

/* ---- 44px ON THE POSTING FORM, AT EVERY WIDTH -------------------------
   REDESIGN-03 Part 1 says it in those words, and it is the one screen where
   --control-h-sm does not apply. tokens.css calls 36px "the desktop-only
   compact size", which is a fair trade on a my-ads row a seller visits
   occasionally and is not one on the form that decides whether an ad gets
   posted at all — a dealer fills this in standing in a showroom holding a
   phone, and the same dealer fills it in at a desk with a trackpad. Both get
   the same target.

   .previewbar is step 4's sticky bar and .postad__main is the other three. */
.postad__main .btn--sm,
.previewbar .btn--sm {
  min-height: var(--control-h);
  padding-inline: var(--sp-4);
  font-size: var(--fs-sm);
}
/* Back / Continue, with Save as draft kept quiet on the left. */
.formnav {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  margin-top: var(--sp-5);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
}
/* "Save and finish later" is not a footnote, it is the control that stops a
   seller losing forty fields of work, and it was 21px tall. REDESIGN-03. */
.formnav__draft {
  display: inline-flex;
  align-items: center;
  min-height: var(--control-h);
  margin-right: auto;
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
}
.formnav__btns { display: flex; gap: var(--sp-2); }

/* ---------- Step 2: photos ----------------------------------------------- */
.dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  text-align: center;
  padding: var(--sp-7) var(--sp-4);
  border: 2px dashed var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
  color: var(--text-2);
  font-size: var(--fs-sm);
}
.dropzone.is-over { border-color: var(--primary); background: var(--primary-soft); color: var(--primary); }

/* The arrow in the middle of the drop zone. The class has been on that <svg>
   since BUILD-06 with no rule behind it — tools/audit.py has listed it as a
   class with no CSS ever since. It was inheriting the zone's --text-2, which
   made the one affordance on the box the palest thing on it. */
.dropzone__icon { color: var(--primary); opacity: .7; }
.dropzone.is-over .dropzone__icon { opacity: 1; }
.dropzone h2 { font-size: var(--fs-base); margin: 0; }
.dropzone p { margin: 0; }

/* ---- The photographs already on the ad ---------------------------------
   ONE GRID, NOT TWO. Until REDESIGN-03 this was a grid of thumbnails and,
   underneath it, a second grid of Delete buttons that lined up with the first
   by counting — so a tile and the button that deletes it were related only by
   position, and on a phone, where the two grids reflowed at different points,
   not always by that.

   Delete lives on the tile now. It cannot be inside the reorder form because
   a delete and a reorder are two different writes and forms do not nest, so
   the button carries form="photodel-{id}" and the forms it names sit at the
   end of the section. That is the HTML `form` attribute doing exactly what it
   is for, and it needs no script: with JavaScript off the button still posts
   the delete.

   The button is a labelled 44px control under the picture rather than a
   22px X in its corner. A seller deleting the wrong photograph of twelve on a
   phone has to upload it again, and the corner of a 110px tile is not where
   to put that. */
.photogrid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--sp-3);
}

.phototile {
  display: grid;
  gap: var(--sp-2);
  min-width: 0;
}

/* Filled slots also carry .card__media, so the 4:3 box and the object-fit on
   the photograph both come from the card rules rather than being written
   again here. */
.photoslot {
  position: relative;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  cursor: grab;
}
.photoslot.is-dragging { opacity: .4; }
.photoslot.is-over { border-color: var(--primary); box-shadow: inset 0 0 0 2px var(--primary); }
.photoslot .card__badge { top: 4px; left: 4px; font-size: 10px; padding: 3px 6px; }

/* The cover is the one buyers see in the search results, so it is marked as
   the cover rather than merely being first. */
.phototile--cover .photoslot { border-color: var(--action); box-shadow: 0 0 0 2px var(--action); }

.thumbtool {
  position: absolute;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--overlay);
  color: var(--on-primary);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
}
/* THE HANDLE IS A BAR ACROSS THE FOOT OF THE TILE, not a button in its
   corner. It was 28px square, and 28px is not a target on a phone — but a
   44px disc on a 105px tile covers most of the photograph, which is the
   thing the seller is trying to look at. A 44px strip along the bottom edge
   is a full-size target, it reads as a grab bar, and it covers the part of
   a car photograph that carries the least information.

   touch-action so a drag from the handle does not scroll the page
   underneath it. */
.thumbtool--drag {
  left: 0;
  right: 0;
  bottom: 0;
  width: auto;
  height: var(--control-h);
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  cursor: grab;
  touch-action: none;
}

/* ---- Upload progress, one line per file --------------------------------
   REDESIGN-03 Part 1 asks for progress per file, and the reason is the
   connection this form is used on: twelve photographs at four to eight
   megabytes each, over a Pakistani mobile connection, is minutes rather than
   seconds. A form that appears to have done nothing for two minutes is a form
   the seller presses again.

   It is ONE request carrying every file, exactly as the plain form post is —
   the batch is what the server counts, accepts and rejects as a batch — and
   the per-file figure is read off the cumulative byte count, because a
   multipart body sends its parts in order. postad.js has the arithmetic. */
.uploadqueue {
  display: grid;
  gap: var(--sp-2);
  margin-top: var(--sp-4);
  list-style: none;
  padding: 0;
}
.uploadfile {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--sp-1) var(--sp-3);
  align-items: center;
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font-size: var(--fs-sm);
}
.uploadfile__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.uploadfile__pct {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs);
  color: var(--text-2);
}
.uploadfile .meter { grid-column: 1 / -1; }
.uploadfile.is-done .uploadfile__pct { color: var(--ok-text); font-weight: var(--fw-semibold); }

/* ---- Before the upload: what the browser did to the photographs --------
   FIX-02 Part A.2, and the client's instruction is the reason it exists at
   all: the shrinking is visible, not silent. A seller whose photographs are
   changed without being told will assume the site did something to his car's
   pictures.

   It is the same box as .uploadqueue underneath it and deliberately so —
   what was prepared and what is going up are one list to the eye, read top
   to bottom, and the seller should not have to work out that they are two
   different things happening to the same eight files.

   .is-bad is a class and never a colour in the script. tools/theme-audit.py
   rule T1 covers .js, and postad.js adds and removes classes only. */
.prepbox { margin-top: var(--sp-4); }
.prepbox__head {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
}
.prepbox__head.is-done { color: var(--text-2); font-weight: var(--fw-regular); }
.prepbox__head.is-bad { color: var(--danger-text); }

.preplist { display: grid; gap: var(--sp-1); list-style: none; padding: 0; margin: 0; }
.prepfile {
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font-size: var(--fs-sm);
  color: var(--text-2);
}
/* A refused file is a sentence the seller has to act on, so it is not one of
   a list of quiet grey lines: it keeps the full text colour and the border
   says which one it is. */
.prepfile.is-bad {
  border-color: var(--danger);
  background: var(--danger-soft);
  color: var(--text);
}
.prepfile b { color: var(--text); }

/* The drop zone while the photographs are being prepared. Not hidden and not
   moved — a zone that disappears takes the page out from under the thumb of
   somebody who was about to press it. */
.dropzone.is-busy { opacity: .55; }
.dropzone.is-busy, .dropzone.is-busy * { cursor: progress; }

.shotlist { list-style: none; padding: 0; margin: 0; }
.shotlist li {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-sm);
}
.shotlist li:last-child { border-bottom: 0; }
.shotlist__n {
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: var(--radius-sm);
  background: var(--primary-soft);
  color: var(--primary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-num);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
}
.shotlist svg { flex: none; color: var(--text-2); }
.shotlist .is-must { font-weight: var(--fw-bold); }
.shotlist .is-must svg { color: var(--primary); }

/* ---------- Step 3: ad type and price ------------------------------------ */
.choices { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); }
.choices--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.choice {
  position: relative;
  display: block;
  padding: var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  cursor: pointer;
  min-width: 0;
}
.choice:hover { border-color: var(--text-2); }
.choice input { position: absolute; opacity: 0; width: 0; height: 0; }
.choice input:focus-visible ~ .choice__t {
  outline: 2px solid var(--focus-ring);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}
.choice__t {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: var(--fs-md);
  color: var(--primary);
}
.choice__d { font-size: var(--fs-sm); color: var(--text-2); margin: var(--sp-2) 0 0; }
.choice__dot {
  flex: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
}
.choice:has(input:checked) { border-color: var(--primary); background: var(--primary-soft); }
.choice:has(input:checked) .choice__dot { border-color: var(--primary); border-width: 5px; }

/* --live is auctions and nothing else. BUILD-04 settled it and BUILD-05b
   applied it to the directory pill; the auction choice on step 3 is the same
   rule. */
.choice--auction .choice__t { color: var(--live-text); }
.choice--auction:has(input:checked) { border-color: var(--live); background: var(--live-soft); }
.choice--auction:has(input:checked) .choice__dot { border-color: var(--live); }
.choice--sm { padding: var(--sp-3); }
.choice--sm .choice__t { font-size: var(--fs-base); }
.choice--sm .choice__d { margin-top: 2px; font-size: var(--fs-xs); }

.pricepreview { margin-top: var(--sp-2); font-size: var(--fs-sm); color: var(--text-2); }
.pricepreview b {
  font-family: var(--font-num);
  font-size: var(--fs-md);
  font-weight: var(--fw-bold);
  color: var(--primary);
}
.pricepreview--auction b { color: var(--live-text); }

/* ---- The bank documents already attached to a lease ad ------------------
   LEASE-01 Part 7. A list of NAMES and nothing else: there is no link to open
   one, because a showroom cannot open its own document back and neither can
   anybody but a Moderator or the Owner, through an audited action. What a
   showroom needs is to see what it sent and to be able to take one off, and
   that is what the row is.

   It stacks on a phone. The 380px check in this cycle's Part 8 is the reason
   the name is allowed to wrap and the meta line is its own row rather than a
   column that would have pushed the Remove control off the edge. */
.doclist {
  margin: var(--sp-2) 0 var(--sp-4);
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--sp-2);
}
.doclist__item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-alt);
  font-size: var(--fs-sm);
}
.doclist__name {
  flex: 1 1 12ch;
  min-width: 0;
  overflow-wrap: anywhere;
  font-weight: var(--fw-bold);
}
.doclist__meta { color: var(--text-2); font-size: var(--fs-xs); }
.doclist__remove {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-left: auto;
  cursor: pointer;
  color: var(--danger-text);
}

/* Toggle switch: "Show a WhatsApp button on my ad". */
.switch {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--control-h);
  cursor: pointer;
  font-size: var(--fs-sm);
}
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch__track {
  position: relative;
  flex: none;
  width: 38px;
  height: 21px;
  border-radius: var(--radius-pill);
  background: var(--border-strong);
  transition: background-color var(--dur-fast) var(--ease);
}
.switch__track::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 17px;
  height: 17px;
  border-radius: 50%;
  background: var(--surface);
  transition: transform var(--dur-fast) var(--ease);
}
/* ~ and not +, because the checkbox tag helper emits a hidden "false" field
   immediately after the checkbox — that is how an unticked box posts at all —
   so the track is the checkbox's second sibling, not its first. */
.switch input:checked ~ .switch__track { background: var(--primary); }
.switch input:checked ~ .switch__track::after { transform: translateX(17px); }
.switch input:focus-visible ~ .switch__track {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* ---------- Step 4: preview ---------------------------------------------- */
.previewbar {
  position: sticky;
  top: var(--header-h);
  z-index: 30;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-4);
  margin: var(--sp-4) 0;
  border-radius: var(--radius-sm);
  background: var(--primary);
  color: var(--on-primary);
  font-size: var(--fs-sm);
}
.previewbar strong { color: inherit; }
.previewbar .btn--outline {
  margin-left: auto;
  background: transparent;
  border-color: var(--on-primary);
  color: var(--on-primary);
}
.previewbar .btn--outline:hover { background: var(--primary-hover); border-color: var(--on-primary); }

.edithead { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); }
.edithead h2 { margin-bottom: var(--sp-3); }
.edithead a { font-size: var(--fs-xs); font-weight: var(--fw-bold); flex: none; }

.confirmbox {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
  padding: var(--sp-4);
  margin-top: var(--sp-6);
}

/* ---------- Submitted confirmation --------------------------------------- */
.done { max-width: 620px; margin: 0 auto; padding: var(--sp-8) 0; text-align: center; }
.done__tick {
  width: 64px;
  height: 64px;
  margin: 0 auto var(--sp-5);
  border-radius: 50%;
  background: var(--ok-soft);
  color: var(--ok);
  display: flex;
  align-items: center;
  justify-content: center;
}
.done__ref {
  display: inline-flex;
  align-items: baseline;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
  font-size: var(--fs-sm);
  color: var(--text-2);
}
.done__ref b { font-family: var(--font-num); font-size: var(--fs-md); color: var(--primary); }

/* The page is centred; the reading blocks inside it are not. A paragraph of
   explanation centred over three lines is harder to read than one that is
   not. */
.done .box, .done .timeline { text-align: left; }
.done .row { display: flex; gap: var(--sp-3); justify-content: center; margin-top: var(--sp-5); }

.timeline { text-align: left; margin: var(--sp-6) 0; list-style: none; padding: 0; }
.timeline li {
  position: relative;
  padding: 0 0 var(--sp-5) var(--sp-5);
  border-left: 2px solid var(--border);
}
.timeline li:last-child { padding-bottom: 0; border-left-color: transparent; }
.timeline__dot {
  position: absolute;
  left: -9px;
  top: 1px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid var(--border);
  background: var(--surface);
  display: flex;
  align-items: center;
  justify-content: center;
}
.timeline .is-done { border-left-color: var(--ok); }
.timeline .is-done .timeline__dot {
  background: var(--ok);
  border-color: var(--ok);
  color: var(--on-primary);
}
.timeline .is-current .timeline__dot {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-soft);
}
.timeline__t { display: block; font-weight: var(--fw-bold); }
.timeline .is-current .timeline__t { color: var(--primary); }
.timeline .is-todo .timeline__t { color: var(--text-2); }
.timeline__when { font-size: var(--fs-xs); color: var(--text-2); }

/* ---------- Tabs ---------------------------------------------------------
   The same control the admin approval queue uses in BUILD-07: one row of
   counted filters over one list. Same job, same part.

   Links rather than buttons, because a tab here is a URL: ?tab=rejected is a
   page a seller can bookmark, come back to, and reach with no script. */
/* A tab is 32px no longer: REDESIGN-03 Part 5 puts 44px under every control
   on every screen in the member journey, and Rejected is the tab a seller
   reaches for on a phone, in a hurry, because his ad came back. The count
   beside the label is a pill rather than grey text, so "Rejected 1" reads as
   one waiting rather than as a footnote. */
.tabs { display: flex; gap: var(--sp-2); flex-wrap: wrap; min-width: 0; }
.tabs a {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--control-h);
  padding: 0 var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text);
  white-space: nowrap;
  text-decoration: none;
}
.tabs a:hover { border-color: var(--primary); text-decoration: none; }
.tabs a[aria-current="page"] {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--on-primary);
  font-weight: var(--fw-bold);
}
.tabs .c {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  padding: 1px var(--sp-1);
  border-radius: var(--radius-pill);
  background: var(--surface-alt);
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs);
  color: var(--text-2);
}
.tabs a[aria-current="page"] .c { background: var(--primary-hover); color: var(--on-primary); }

/* ---------- The run meter ------------------------------------------------ */
.meter { display: block; height: 4px; border-radius: 2px; background: var(--border); overflow: hidden; }
.meter__fill {
  display: block;
  height: 100%;
  width: 0;
  background: var(--primary);
  transition: width var(--dur-base) var(--ease);
}
.meter__fill.is-good { background: var(--ok); }
.meter__fill.is-low { background: var(--danger); }

/* ---------- My ads -------------------------------------------------------- */

/* The three figures at the top of the screen. They were the car detail page's
   .specstrip, which is a strip of specifications under a photograph and reads
   as a caption — grey label, grey figure, on the page colour. Here they are
   the first thing the seller looks at, so they are a card with the figures in
   the display face at 28px, and "Waiting for approval" carries the action
   colour when it is not nought, because that is the number he is waiting on.
   REDESIGN-03 Part 2. */
.figures {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-4);
  margin-bottom: var(--sp-5);
  padding: var(--sp-4) var(--sp-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}
.figures__item { display: grid; gap: 2px; min-width: 0; }
.figures__label { color: var(--text-2); font-size: var(--fs-sm); }
.figures__value {
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  color: var(--primary);
  line-height: var(--lh-tight);
}
.figures__item--waiting .figures__value { color: var(--action-text); }

.adrow {
  display: grid;
  grid-template-columns: 132px minmax(0, 1fr) 200px;
  grid-template-areas: "media main act";
  gap: var(--sp-4);
  padding: var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}
.adrow + .adrow { margin-top: var(--sp-3); }

/* A REJECTED ROW IS DRAWN AS ONE. It is the row the whole screen exists for:
   with Email:Enabled false this and the notifications page are the only two
   places a seller is told, and a --danger hairline round the row is what
   makes him find it in a list of fifteen before he reads any of them. */
.adrow--rejected {
  border-color: var(--danger);
  box-shadow: inset 3px 0 0 var(--danger), var(--shadow-1);
}
.adrow__media { grid-area: media; align-self: start; border-radius: var(--radius-sm); }
.adrow__main { grid-area: main; min-width: 0; }
.adrow__act { grid-area: act; display: grid; gap: var(--sp-2); align-content: start; }
.adrow__act form { display: contents; }
.adrow__title {
  font-family: var(--font-body);
  font-size: var(--fs-base);
  font-weight: var(--fw-bold);
  color: var(--text);
  margin: 0 0 2px;
}
.adrow__title a { color: inherit; }
/* --action-text, the same as .card__price. docs/CARHUB_REDESIGN.md section 4
   makes a price the action colour wherever it appears, and a seller looking
   at his own ad should see the figure a buyer sees, in the colour a buyer
   sees it in. */
.adrow__price {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-price);
  font-weight: var(--fw-bold);
  color: var(--action-text);
}
.adrow__stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-4);
  font-family: var(--font-num);
  font-size: var(--fs-xs);
  color: var(--text-2);
  margin: var(--sp-2) 0;
}
.adrow__stats b { color: var(--text); }
.adrow__state { font-size: var(--fs-xs); color: var(--text-2); margin: 0; }
.adrow__state .meter { margin-top: var(--sp-1); max-width: 260px; }

.emptystate {
  text-align: center;
  padding: var(--sp-8) var(--sp-4);
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
}
.emptystate svg { color: var(--text-2); margin-bottom: var(--sp-3); }
.emptystate h2 { font-size: var(--fs-md); }
.emptystate p { color: var(--text-2); max-width: 46ch; margin: 0 auto var(--sp-4); }

/* ---------- The profile's notification list        REDESIGN-03, Part 2 ----
   Five lines in the help column, not a second copy of /notifications/. An
   unread one takes the leading bar and the semibold, so the difference is
   visible without reading a word of it. */
.notifmini { display: grid; gap: var(--sp-3); list-style: none; padding: 0; margin: 0; }
.notifmini__row {
  padding-left: var(--sp-3);
  border-left: 3px solid var(--border);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
}
.notifmini__row.is-unread { border-left-color: var(--primary); font-weight: var(--fw-medium); }
.notifmini__when { display: block; color: var(--text-2); font-size: var(--fs-xs); }

/* A form nav inside a field-group card is the last thing in the card, so it
   keeps its rule above it and loses the space below it. */
.postad__main fieldset > .formnav:last-child { margin-bottom: 0; }

/* ---------- Notifications                          REDESIGN-03, Part 2 ----
   Everything above treats this as a list. It is not a list, it is the only
   channel the site has: Email:Enabled is false, SMS waits on the client
   buying credits, and until one of those is switched on this page is where a
   seller finds out that his car never went up. A dotted timeline with the
   date in grey is what that looked like before this cycle.

   One card per message. The tone comes off the notification kind — sent back
   and taken down are --danger, live and approved are --ok, expiring and
   outbid are --warning — and an unread one carries a 3px bar down its leading
   edge so the eye finds it before it reads a word. */
.notiflist { display: grid; gap: var(--sp-3); list-style: none; padding: 0; margin: 0; }

.notif {
  padding: var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}
.notif__head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}
.notif__when { margin-left: auto; color: var(--text-2); font-size: var(--fs-xs); }
.notif__body { margin: 0; }
.notif__go {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  min-height: var(--control-h);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-sm);
}

/* Unread. The bar is on the leading edge rather than a background wash,
   because a wash over a --danger card would be two colours arguing about the
   same row. */
.notif--unread { box-shadow: inset 3px 0 0 var(--primary), var(--shadow-2); }
.notif__new {
  padding: 2px var(--sp-2);
  border-radius: var(--radius-pill);
  background: var(--primary);
  color: var(--on-primary);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: .04em;
}

.notif--danger  { border-color: var(--danger);  background: var(--danger-soft); }
.notif--ok      { border-color: var(--ok); }
.notif--warning { border-color: var(--warning); }

.notif--danger.notif--unread  { box-shadow: inset 3px 0 0 var(--danger),  var(--shadow-2); }
.notif--ok.notif--unread      { box-shadow: inset 3px 0 0 var(--ok),      var(--shadow-2); }
.notif--warning.notif--unread { box-shadow: inset 3px 0 0 var(--warning), var(--shadow-2); }

/* ---------- The member journey own responsive ladder ---------------------
   Its own ladder, for the same reason the admin panel has one: these screens
   carry a 300px help column and forty form controls, so they run out of room
   at different widths from a listing page. The one rule worth stating: on a
   phone the help column moves ABOVE the form, not below it. A seller standing
   in a showroom will not scroll past forty fields to find out why we asked
   for the paint condition. */

@media (max-width: 1100px) {
  .postad { grid-template-columns: minmax(0, 1fr) 260px; gap: var(--sp-5); }
}

@media (max-width: 1000px) {
  .postad,
  .postad--single {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "head" "side" "main";
  }
  .postad__side { position: static; }
  .helpcard__chev { display: block; }
}

@media (max-width: 860px) {
  .fieldrow, .fieldrow--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .numsteps--across { grid-template-columns: minmax(0, 1fr); gap: var(--sp-4); }
  .checkcols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .photogrid { grid-template-columns: repeat(3, minmax(0, 1fr)); }

  .adrow {
    grid-template-columns: 108px minmax(0, 1fr);
    grid-template-areas: "media main" "act act";
  }
  .adrow__act { grid-template-columns: repeat(3, minmax(0, 1fr)); }

  .dealerhead { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 640px) {
  /* ---- THE 16px RULE, AND IT IS NOT A TYPOGRAPHY DECISION -------------
     iOS Safari ZOOMS THE WHOLE PAGE when a text control smaller than 16px
     takes focus, and it does not zoom back out. The controls are --fs-sm,
     which is 14px, so tapping the mileage box on an iPhone left the seller
     scrolled sideways inside a form he was half way through — on the one
     screen this cycle exists to fix, and on the handset the largest single
     group of sellers uses. Sixteen pixels on a phone, and the desktop keeps
     the compact size. */
  .input, .select, .textarea { font-size: var(--fs-base); }

  /* Four step labels do not fit across 358px of usable width. The current
     step keeps its name; the others keep their number and their colour,
     which is all a completed step has to say — and .steps__left says what
     remains, in words, which is the half of the indicator that matters on a
     phone. */
  .steps__t { display: none; }
  .steps .is-current .steps__t { display: block; }
  .steps__n { width: 24px; height: 24px; }

  /* The four columns are equal, so the one label still on screen got a
     quarter of 358px and came out "Car d…". The current step takes the room
     the other three are not using. */
  .steps .is-current { flex: 2.4; }

  /* A group card keeps its card and loses the roomy inside: --sp-5 of
     padding on both sides of a 328px screen is 48px of the 328 spent on
     nothing. */
  .postad__main fieldset { padding: var(--sp-4); border-radius: var(--radius-md); }
  .postad { padding-top: var(--sp-3); }

  .figures { padding: var(--sp-4); gap: var(--sp-3); }
  .figures__label { font-size: var(--fs-xs); }
  .figures__value { font-size: var(--fs-lg); }

  .fieldrow, .fieldrow--2 { grid-template-columns: minmax(0, 1fr); }
  .placeholder { grid-template-columns: minmax(0, 1fr); }
  .placeholder__photo { max-width: 120px; }

  /* One column, so there is no row to line up with. The rule turns from a
     wall between the two fields into a line above the second, which says the
     same thing stacked. */
  .fieldrow--split > * + * {
    border-left: 0;
    padding-left: 0;
    border-top: 1px solid var(--border);
    padding-top: var(--sp-4);
  }
  .checkcols { grid-template-columns: minmax(0, 1fr); }
  .choices, .choices--3 { grid-template-columns: minmax(0, 1fr); }

  /* TWO ACROSS, NOT THREE. Three tiles across 358px is a 105 x 79 photograph,
     and the 44px grab bar along the foot of it covers more than half the car.
     Two across is 155 x 116 and the bar is a bar. Twenty photographs is ten
     rows of scrolling either way. */
  .photogrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dropzone { padding: var(--sp-5) var(--sp-3); }

  /* Back and Continue side by side and full width, with Save as draft under
     them. Thumb reach beats source order here. */
  .formnav { flex-direction: column-reverse; align-items: stretch; }
  .formnav__btns > .btn { flex: 1; }
  .formnav__draft { margin: var(--sp-2) 0 0; text-align: center; }

  .previewbar { top: 0; }
  .previewbar .btn--outline { margin-left: 0; width: 100%; }

  .done { padding: var(--sp-6) 0; }
  .done__tick { width: 52px; height: 52px; }

  .adrow { grid-template-columns: 88px minmax(0, 1fr); gap: var(--sp-3); padding: var(--sp-3); }
  .adrow__act { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 380px) {
  /* 320px. One photograph across: two leaves 140px, and the tile carries a
     44px grab bar and a 44px delete button under a 105px picture. */
  .photogrid { grid-template-columns: minmax(0, 1fr); }
  .adrow__act { grid-template-columns: minmax(0, 1fr); }
  .formnav__btns { flex-wrap: wrap; }
  .formnav__btns > .btn { flex: 1 1 100%; }
}

/* ==========================================================================
   19. BIDDING, CNIC AND THE PIECES BOTH HALVES SHARE            BUILD-08
   demo/verify-cnic.html and demo/admin/verifications.html.

   These live in components.css rather than in admin.css because the member's
   verification form and the verifier's record screen draw the SAME upload
   boxes and the SAME privacy panel — one of them is a form and one of them is
   a photograph, and a second copy in admin.css is how they start to look like
   two different products.

   NO COLOUR VALUE MAY APPEAR HERE. Every colour is var(--token).
   ========================================================================== */

/* Help text under a field. The demo's .help, and the same size and colour as
   .field__help so a form does not have two shades of grey in it. */
.help {
  margin: var(--sp-1) 0 0;
  color: var(--text-2);
  font-size: var(--fs-xs);
}

/* The one badge variant BUILD-08 needed and the set did not have: a neutral
   "in progress" that is not ok, not danger and not the auction's --live. */
.badge--primary { background: var(--primary-soft); color: var(--primary); }

/* --------------------------------------------------------------------------
   The two CNIC photographs.

   Two boxes side by side, stacking below 620px. The card outline inside an
   empty box is a line drawing and never a photograph: NO REAL OR REALISTIC
   IMAGE OF AN IDENTITY DOCUMENT GOES ANYWHERE NEAR THIS PROJECT, and there is
   a matching comment in verify-cnic's view and in the admin record view.
   -------------------------------------------------------------------------- */
.uploads {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-4);
}

.uploadbox {
  display: grid;
  justify-items: center;
  gap: var(--sp-2);
  padding: var(--sp-4);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--surface-alt);
  text-align: center;
}

.uploadbox__label {
  font-weight: var(--fw-bold);
  font-size: var(--fs-sm);
}

.uploadbox__card { color: var(--border-strong); }

.uploadbox__hint {
  margin: 0;
  color: var(--text-2);
  font-size: var(--fs-xs);
}

/* THE FILE INPUT LOOKED LIKE 1998, and it is the control this screen exists
   for. ::file-selector-button is the only part of a file input a stylesheet
   may touch, and it is enough: the button becomes the outline button used
   everywhere else, 44px tall, and the "no file chosen" text beside it takes
   the page's own type instead of the platform's. */
.uploadbox input[type="file"] {
  width: 100%;
  font-size: var(--fs-sm);
  color: var(--text-2);
}
.uploadbox input[type="file"]::file-selector-button {
  min-height: var(--control-h);
  margin-right: var(--sp-3);
  padding-inline: var(--sp-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--primary);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  cursor: pointer;
}
.uploadbox input[type="file"]::file-selector-button:hover {
  border-color: var(--primary);
  background: var(--primary-soft);
}

/* The photograph itself, on the verifier's screen only. Capped rather than
   full-bleed: he is reading a card, not admiring it, and a 1600px image in a
   sidebar layout pushes the decision buttons off the screen. */
.uploadbox__img {
  display: block;
  width: 100%;
  max-width: 420px;
  height: auto;
  border-radius: var(--radius-sm);
  background: var(--surface);
}

/* --------------------------------------------------------------------------
   The privacy panel — what happens to the documents.

   On the member's form it is the promise; on the verifier's screen it is the
   checklist. Same component, because they are the same four sentences read
   from two sides.
   -------------------------------------------------------------------------- */
.privacy {
  padding: var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-alt);
}

.privacy__head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
  color: var(--text-2);
}

.privacy__head h2 {
  margin: 0;
  font-size: var(--fs-base);
  color: var(--text);
}

/* The ticks in the privacy panel are not "yes you have this" ticks — they are
   statements about what is done with a document. Neutral rather than --ok. */
.ticklist--lock svg { color: var(--text-2); }

/* The four states a verification can be in. A .box with the badge at the top,
   used once, on demo/verify-cnic.html. */
.statecard { display: grid; align-content: start; gap: var(--sp-2); }
.statecard .badge { justify-self: start; }
.statecard p { margin: 0; color: var(--text-2); font-size: var(--fs-sm); }

/* --------------------------------------------------------------------------
   .grid IS DEFINED IN base.css AND THE COPY THAT USED TO BE HERE WAS A BUG.

   BUILD-08 added `.grid { grid-template-columns: minmax(0, 1fr) }` to this
   file, believing it was introducing a plain equal-column grid for the four
   state cards. base.css had owned `.grid` since BUILD-04 and gives it three
   columns; components.css loads afterwards, so the one-column rule won on
   every page of the site.

   .grid--3 and .grid--4 were redefined here as well, which is why nobody
   noticed: those two won the column count back. THE TWO THAT WERE NOT
   REDEFINED WERE LEFT AT ONE COLUMN, and both are on real pages —

     .grid--5  the home page's five budget bands, drawn as five full-width
               strips down the page instead of a row of tiles
     .grid--2  the two-column bands on the home page and the auth screens

   — and so was bare `.grid`, which is what `.grid results__grid` is: THE
   LISTING RESULTS, THE AUCTIONS LISTING AND THE DEALER DIRECTORY WERE ALL
   RENDERING ONE CARD PER ROW ON A 1440px DESKTOP. docs/PAGES.md asks for
   three.

   The fix is deletion, not another override. base.css owns the layout
   primitives and this file owns components; a component stylesheet redefining
   `.grid` is the same category of mistake as a component stylesheet defining
   a colour, and the reason it survived four cycles is that it fails silently
   and only on the modifiers nobody re-declared.
   -------------------------------------------------------------------------- */

/* Label above, value below, one pair per row. The demo's .deflist: a
   definition list read top to bottom, unlike .kv which is a two-column
   label/figure pair aligned to the right. */
.deflist { margin: 0; }
.deflist dt {
  color: var(--text-2);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: .04em;
}
.deflist dd { margin: 0 0 var(--sp-3); overflow-wrap: anywhere; }
.deflist dd:last-child { margin-bottom: 0; }

/* ---------- Report this ad — BUILD-09 -----------------------------------
   A <details> at the foot of a car detail page. Closed by default and open
   with no JavaScript, because a report is not a control to put a button for
   in the middle of somebody's advertisement — and because a visitor who has
   spotted something wrong and finds nowhere to say it telephones the office
   instead. No new token and no colour value: --border, --surface-alt and the
   existing spacing scale. */
details.reportbox {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--sp-4);
  background: var(--surface-alt);
}
details.reportbox > summary {
  display: flex;
  align-items: center;
  min-height: var(--control-h);
  cursor: pointer;
  font-weight: var(--fw-medium);
  color: var(--text-2);
}
details.reportbox[open] > summary { margin-bottom: var(--sp-3); }
details.reportbox textarea.input { resize: vertical; }

/* base.css carries the demo's breakpoints for every .grid modifier — 1000,
   860 and 640 — so the second set that used to be here at 900 and 620 is gone
   with the duplicate .grid above. .uploads is this file's own and stays. */
@media (max-width: 620px) {
  .uploads { grid-template-columns: minmax(0, 1fr); }
}

/* ==========================================================================
   20. LEASING, CITY HEROES AND THE MAKE TILE'S LOGO           BUILD-12a
   ==========================================================================
   Three surfaces this cycle added and one it changed. NO NEW TOKEN: every
   value below is one of the 47 the theme contract already defines, which is
   what tools/theme-audit.py T4 checks and what has been true since R2.
   ========================================================================== */

/* ---- The city listing hero ----------------------------------------------
   THE BREADCRUMB AND THE <h1> SIT ON THE PHOTOGRAPH.        BUILD-12b Part 1

   BUILD-12a BUILT THIS AS A DECORATIVE STRIP AND THAT WAS THE DEFECT. The
   band sat ABOVE the breadcrumb and the heading, carried no text, and had
   only a wash down its bottom edge -- so it spent 300px of the fold saying
   nothing and competed with the white page that started immediately under
   it.

   IT IS THE HOME PAGE'S HERO AT A SHORTER HEIGHT. Same machinery, same
   tokens, same gradient shape: full bleed, the photograph absolutely
   positioned under a gradient that runs ACROSS rather than a wash OVER,
   white text, --focus-ring-dark for anything focusable inside it. The home
   page treatment is approved; a second treatment invented for this page
   would be a second thing to keep in step.

   THE HEIGHT IS A MEASUREMENT, NOT A PREFERENCE. --hero-h is 460px because
   the home page's job is to look like somewhere worth searching. A city
   listing's job is to show cars, and at 1366x768 the <h1>, the Filters
   button, the sort bar and the first row of cards all have to be above the
   fold. --hero-h-city holds the band at a height that fits the breadcrumb
   and a two-line heading with air around them and nothing more. */
.cityhero {
  position: relative;
  min-height: var(--hero-h-city);
  display: flex;
  align-items: center;
  background: var(--surface-dark);
  color: var(--text-inverse);
  overflow: hidden;
}

/* An orange focus ring on a dark band, exactly as the home page hero does it
   -- --focus-ring measures 1.24:1 against --surface-dark and is invisible. */
.cityhero :focus-visible { outline-color: var(--focus-ring-dark); }

.cityhero__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;

  /* The upper third of these photographs is sky. Pulling the frame down puts
     the street in the band rather than the clouds. */
  object-position: 50% 62%;
}

/* THE OVERLAY RUNS ACROSS, NOT OVER, and it is the home page's gradient with
   one number changed.

   REDESIGN-01b's shape is: full strength out to max(28%, 380px), gone by
   max(64%, 700px). The pixel floors are the part that is not decoration --
   on percentages alone the dark zone shrinks with the viewport while the
   text does not, so at 768px the gradient had already cleared before the
   text ended.

   THE FLOOR IS 520px HERE RATHER THAN 380, AND IT WAS PUT THERE BY A
   MEASUREMENT ON A HEADING THIS PAGE CAN ACTUALLY DRAW. The home page's
   headline is a fixed slogan on a 15ch measure. This one is a live count and
   a city name, so its width is DATA: "8 Used Cars in Faisalabad" is 400px of
   32px bold and "1,240 Used Cars in Sheikhupura" is 642px. And these
   photographs are brighter than the home page's sunset: over the band that
   is actually on screen at 1366px, the left third of hero-gujranwala
   averages 106 of 255, hero-sargodha 103 and hero-faisalabad 91, against
   hero-punjab's 44.

   On the home page's 380/700 stops the demo's one-digit headings all pass,
   and the four-digit heading does not: 2.59:1 on Faisalabad at 768px, 2.92:1
   on Sargodha at 1440 and 2.51:1 on Sialkot at 768, against the 3.0:1 that
   32px bold needs. Passing on today's data and failing on next month's is
   the worst way for this to be wrong, because nothing looks broken until the
   inventory grows.

   AND THE AVERAGE IS NOT WHAT BREAKS IT. Sialkot's left third averages 77 --
   darker than three of the others -- and it measured worst of all ten, on a
   patch of sunlit wall that the tail of a long heading lands on. What white
   text has to survive is the LIGHTEST pixel under it, so every figure quoted
   here and in docs/THEMING.md is a worst pixel and not a mean.

   520/860 holds the strong zone over the whole text column at every width
   and on every heading the query can produce -- the same four-digit heading
   measures 4.21:1 at its worst -- and the gradient still clears by 63% of
   the frame at 1366px, so the landmark and the cars are untouched.

   The numbers on all four widths and all ten cities are in
   docs/THEMING.md section 3a-iii. */
.cityhero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(90deg,
    var(--hero-overlay) 0%,
    var(--hero-overlay) max(34%, 520px),
    transparent max(70%, 860px));
}

.cityhero__wrap {
  position: relative;
  z-index: 2;
  width: 100%;
  padding-block: var(--sp-5);
}

/* WHITE, AND THE LINKS TOO. --text-2 is the breadcrumb's colour on an
   ordinary page and it is a dark grey; on a photograph it is unreadable at
   any overlay this band could carry without turning the picture into a
   rectangle. --text-on-dark-2 is no better here for the reason REDESIGN-01b
   recorded about the home page's supporting line: #B9C2DC has a relative
   luminance of .535 and cannot reach 4.5:1 against anything lighter than
   .080, which a scrimmed daylight photograph is not. So the whole trail is
   --text-inverse, and the current page is told apart by weight rather than
   by colour. */
.crumbs--on-hero { color: var(--text-inverse); }
.crumbs--on-hero a { color: var(--text-inverse); }
.crumbs--on-hero a:hover { color: var(--text-inverse); }
.crumbs--on-hero [aria-current="page"] { font-weight: var(--fw-semibold); }

/* The <h1> is --fs-2xl, the page heading size, and NOT the home page's
   --fs-3xl. It is a sentence with a live number in it rather than a slogan,
   it has to fit two lines at 390px, and every step up in size is height the
   first row of cars loses. */
.cityhero__title {
  margin: var(--sp-2) 0 0;
  color: var(--text-inverse);
  text-wrap: balance;
}

/* "— page 2 of 4", ON ITS OWN LINE AND IN WHITE. Two changes and both were
   measured rather than preferred.

   --text-2 is its colour on the four other pages that draw one, all of which
   are white; on this band it is a dark grey on a dark photograph. It is NOT
   --text-on-dark-2 either, for the arithmetic 3a-i records: #B9C2DC cannot
   reach 4.5:1 against anything lighter than .080, and a scrimmed daylight
   photograph is not. So it is white, and told apart by size and weight --
   which is what .h1__page was already doing.

   AND IT IS A BLOCK, because inline it is the one piece of text on this band
   that the gradient cannot cover. It sits AFTER the heading, so on the longest
   heading this site can draw it ran to 790px at 1440 -- past the point the
   gradient has cleared -- and measured 2.07:1 on Sialkot against the 4.5:1
   that 18px regular needs. On its own line it starts at the left edge with the
   breadcrumb and the heading, inside the strong zone, and the same heading
   with the same suffix measures 5.59:1 at its worst across all seven
   photographs and nine widths. Nothing else moves: it is still inside the
   <h1>, still the same sentence to a screen reader, and the band absorbs the
   extra line inside its 300px. */
.cityhero__title .h1__page {
  display: block;
  color: var(--text-inverse);
}

/* ON A NARROW SCREEN THE GRADIENT TURNS, AND IT TURNS ON THE SAME LINE THE
   <picture> SWITCHES ON -- 767.98px. Below it the photograph is the taller
   mobile crop, and a left-to-right wash on a 390px frame either covers the
   whole picture or never reaches the text. So it runs down instead: full
   strength behind the breadcrumb and the heading, clearing over the bottom
   of the frame where the cars are. */
@media (max-width: 767.98px) {
  /* THE CLEAR STRIP IS A FIXED HEIGHT AT THE BOTTOM, NOT A PERCENTAGE, and
     that is the difference between a rule that holds and a rule that holds on
     the demo's data. The band is a min-height and its content is a live count
     and a city name that can take two lines with a "page 2 of 104" under them;
     on percentages the strong zone grows with the band while the text grows
     faster, so the last line ends up in the fade. The measurement that found
     it: the page suffix on the longest heading measured 2.61:1 at 390px on
     Sargodha against the 4.5:1 that 18px regular needs.

     So the text sits at the TOP of the band with --sp-8 reserved under it, and
     the gradient clears exactly that reserve. However many lines the heading
     takes, the strip of photograph at the bottom is the same 72px and no line
     of text is ever in it.

     IT IS SCOPED TO THE BAND THAT HAS A PHOTOGRAPH. The reserve exists to
     leave a strip of picture showing; with no picture there is nothing to
     leave, and 72px of empty navy under the heading reads as a mistake. The
     dark fallback stays centred in its own shorter band. */
  .cityhero:not(.cityhero--empty) { align-items: flex-start; }
  .cityhero:not(.cityhero--empty) .cityhero__wrap {
    padding-block: var(--sp-5) var(--sp-8);
  }
  .cityhero__scrim {
    background: linear-gradient(180deg,
      var(--hero-overlay) 0%,
      var(--hero-overlay) calc(100% - var(--sp-8)),
      transparent 100%);
  }
}

/* ---- NO PHOTOGRAPH: THE DELIBERATE DARK TREATMENT, NEVER A BROKEN IMAGE ---
   cities.hero_image is null, or the three files are not on disk, and this is
   what renders. Lahore ships in this state on purpose: its photograph IS the
   home page hero, one click away, and repeating it here would make the two
   pages look like one page that failed to navigate.

   IT NOW CARRIES WHITE TEXT TOO, so it has to look like a decision rather
   than a photograph that failed to load. It gets the same answer .hero--empty
   gives on the home page: two soft radials off --surface-dark-2, one behind
   the heading and one low and wide, so the navy has depth in it instead of
   being a flat rectangle somebody forgot to finish. Both are tokens, so the
   band follows a theme change like everything else.

   SHORTER, because there is no picture for the extra height to be -- the same
   trade .hero--empty makes at 420px against 460. */
.cityhero--empty {
  min-height: var(--hero-h-city-empty);
  background: var(--surface-dark);
}
.cityhero--empty::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(60% 150% at 22% 0%, var(--surface-dark-2) 0%, transparent 72%),
    radial-gradient(150% 100% at 50% 130%, var(--surface-dark-2) 0%, transparent 60%);
}

/* The listing page's own top padding is redundant under a full-bleed band. */
.section--under-hero { padding-top: var(--sp-5); }

/* ---- A hero on a content page -------------------------------------------
   /leasing/ takes the home page's hero at a shorter height. It carries a
   headline and a line of copy and no search panel under it, so it does not
   need the full 460. */
.hero--page { min-height: var(--hero-h-city-page); }

/* ==========================================================================
   THE LEASE PANEL — a car on lease                               LEASE-01
   ==========================================================================
   WHAT USED TO BE HERE: the instalment calculator, its term slider, the bank
   list and the lead form of the withdrawn /leasing/ page — .calc, .calc__*,
   .termslider, .banklist, .banklist__note, .leadform, .sidecard__lease and
   their 900px collapse. Every one of them went with the markup that used them
   and none had a second caller; the stylesheet is 210 lines shorter for it.

   WHAT IS HERE INSTEAD: the panel that replaces the price panel on a lease
   car page. It reuses .sidecard wholesale — same box, same padding, same
   label, same figure — so only what genuinely differs is written out: a
   modifier for the border, the row of secondary figures, and the total.

   NOT --live AND NOT --action. --live is auctions and nothing else
   (docs/THEMING.md section 4) and --action is "press this". A lease is neither
   a clock nor a button, so the panel takes the ordinary --primary edge the
   rest of the site uses for "this is a thing, and here is what it costs".
   ========================================================================== */
.sidecard--lease { border-color: var(--primary); }

/* The down payment is the largest figure on the page, exactly as an asking
   price is on an ordinary ad. It inherits .sidecard__price's size; this only
   colours it, so the two panels stay the same shape. */
.sidecard--lease .sidecard__price { color: var(--primary); }

/* The instalments line, immediately under the down payment: "36 instalments
   of PKR 1.2 lac". Tabular, because it is money, and secondary weight so the
   down payment above it stays the headline. */
.leasepanel__instalments {
  margin: calc(var(--sp-3) * -1) 0 var(--sp-3);
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-md);
  color: var(--text);
}

/* The total, boxed. It is the one figure on the page that is arithmetic
   rather than a quoted number, so it is set apart and the sentence in it says
   exactly what was added to what. */
.leasepanel__total {
  margin: var(--sp-3) 0;
  padding: var(--sp-3);
  border-radius: var(--radius-md);
  background: var(--surface-alt);
  font-size: var(--fs-sm);
}
.leasepanel__total b {
  display: block;
  margin-top: 2px;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-lg);
}

/* The warning every lease ad carries. --warning rather than --danger: the car
   being in the bank's name until it is paid off is how the product works, not
   a fault with this particular ad. */
.leasepanel__warning {
  margin: var(--sp-3) 0 0;
  padding: var(--sp-3);
  border-left: 3px solid var(--warning);
  background: var(--warning-soft);
  font-size: var(--fs-sm);
}
.leasepanel__warning p { margin: 0; }
.leasepanel__warning p + p { margin-top: var(--sp-2); }

/* ---- The make tile with an uploaded mark on it --------------------------
   THE TILE STAYS WHITE. Not solid orange: orange means ACTION on this site and
   eight orange tiles dilute the one signal the whole palette is built around.
   The monogram keeps --primary-soft behind its letter; a tile carrying an
   IMAGE drops the tint, because a manufacturer's mark on a coloured square is
   a mark somebody has put a background behind. */
.maketile__logo--image {
  background: none;
  padding: 0;
}
.maketile__logo--image img {
  width: 28px;
  height: 28px;
  object-fit: contain;
}

/* ---- Back to the public site, in the admin sidebar ----------------------
   Quieter than a queue link, because it is not work. */
.admin__link--out {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--text-2);
}
.admin__link--out svg { flex: none; }

/* ==========================================================================
   CAR ADVICE — /advice/                                            BUILD-11
   ==========================================================================
   Three page types and one new idea between them: a column of prose that a
   person reads end to end, on a site every other page of which is a grid of
   figures. So the rules below are about MEASURE and RHYTHM, and they add no
   token — the contract is still the 47.

   NOTHING HERE IS A COLOUR VALUE. Every one of these is var(--token), which is
   the whole reason tools/theme-audit.py T1 exists, and the article body is the
   surface where a stray colour is likeliest to hide: it is generated markup,
   so a hard-coded blockquote rule or a grey code background would never be
   looked at again.
   ========================================================================== */

/* ---- The section chips above the grid ----------------------------------
   Five sections and an All, as ordinary links to path pages. NOT a filter
   rail: a rail with five options in it is furniture, and a query-string
   filter would be noindex where these are five landing pages a crawler walks.

   The shape is the removable filter chip, without the cross — same height,
   same radius, same border, so the two read as one family. The current one
   takes --primary rather than --action, because it is where you are and not
   something to press; --action on this site means "press this". */
.catchips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-block: var(--sp-4) var(--sp-5);
}
.catchips a {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);

  /* 44px, like every other target on this site since REDESIGN-02. */
  min-height: var(--control-h);
  padding: 0 var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
}
.catchips a:hover {
  border-color: var(--primary);
  color: var(--primary);
  text-decoration: none;
}
.catchips a[aria-current="page"] {
  border-color: var(--primary);
  background: var(--primary-soft);
  color: var(--primary);
}
.catchips__n {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs);
  color: var(--text-3);
}
.catchips a[aria-current="page"] .catchips__n { color: var(--primary); }

/* A section with nothing published in it is dimmed AND STILL A LINK, which is
   the opposite of what the home page budget tiles do at a count of nought.
   The reason is different: an empty budget band is a dead end for a buyer,
   and an empty section is a promise to the writer about where the next
   article goes. Five links is also no crawl-budget argument at all. */
.catchips--empty { opacity: .55; }

/* ---- The article card ---------------------------------------------------
   Built on .card, so an article card and a vehicle card sit in the same grid
   at the same height with the same media box, the same 4:3 slot and the same
   lift. What is new is the text half: an excerpt where a car has a price. */
.artgrid { margin-bottom: var(--sp-6); }

/* The vehicle card reserves 216px under the photograph for a price pinned to
   the bottom. An article has no price and a three-line excerpt instead, so it
   reserves its own height — and the two are close enough that a mixed row
   never happens anyway, because the two card types are never in one grid. */
.artcard .card__body { min-height: 200px; }

.artcard__cat {
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--action-text);
}

/* The title clamp is the .card__title rule and is not restated. What changes
   is the family: a headline is prose, so it takes the display face rather
   than the body face a car model name takes. */
.artcard__title { font-family: var(--font-display); }

/* Three lines and then an ellipsis, and always three lines worth of height —
   the same rule and the same arithmetic REDESIGN-03b applied to the card
   title, and for the same reason: a two-line excerpt and a three-line one in
   one row put their dates on two different levels. */
.artcard__excerpt {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--text-2);
  line-height: var(--lh-base);
  min-height: calc(3em * var(--lh-base));
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.artcard__meta {
  margin: auto 0 0;
  font-size: var(--fs-xs);
  color: var(--text-3);
}

/* The play marker on a video article cover. --primary and not --live:
   --live is auctions, countdowns and the live dot on this whole site and
   nothing else, and a video is not a clock. docs/THEMING.md section 4. */
.artcard__play { left: var(--sp-3); }

/* No cover photograph, and it draws a DESIGNED thing rather than a hole.

   The 4:3 slot is still held, so the row does not go ragged and nothing moves
   on the day a photograph is added. What fills it is the monogram treatment
   this site already uses on the home page's make tiles and on a dealer card —
   .tile__logo, unchanged, on a --surface-alt ground.

   IT MATTERS MORE HERE THAN ANYWHERE ELSE. Every advertisement has a
   photograph; no article ships with one, because a cover would be an image file
   in a repository that deliberately commits none. So this is what /advice/
   looks like on a first boot and until the client adds his own, and it has to
   look finished — the argument REDESIGN-01b made about the empty hero. */
.artcard__mono {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: var(--surface-alt);
}

/* Larger than the 44px the tiles use, because it is filling a 4:3 box rather
   than sitting beside a name. The treatment is otherwise .tile__logo's and is
   not restated: same radius, same --primary-soft, same display face. */
.artcard__mono .tile__logo {
  width: 88px;
  height: 88px;
  font-size: var(--fs-2xl);
}

/* ---- The band at the foot of the index --------------------------------- */
.advice-out { margin-top: var(--sp-7); }

/* ==========================================================================
   ONE ARTICLE
   ==========================================================================
   A READING MEASURE, CENTRED, because nothing sits beside it. That is the
   REDESIGN-03b rule about a prose block, stated for a whole page rather than
   for a band: where something occupies the rest of the width the measure stays
   on the left, and where nothing does the block is centred with its heading
   inside it. On this page nothing does — there is no rail, no seller card and
   no table of contents — so the whole article is the centred column, and the
   cars underneath it go back to the full width of the wrap, because a grid of
   four cards is not prose. */
.article {
  max-width: 72ch;
  margin-inline: auto;
}

.article__head { margin-bottom: var(--sp-5); }

.article__cat {
  display: inline-block;
  margin-bottom: var(--sp-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--action-text);
}

/* --fs-2xl is the ordinary page h1 and this is not one. An article headline is
   the only thing on its screen and it carries the reading measure under it, so
   it takes the step above — the same size the hero headline takes, at the top
   of a page with nothing else competing for the eye. */
.article__head h1 {
  font-size: var(--fs-3xl);
  line-height: var(--lh-tight);
  margin-bottom: var(--sp-3);
}

.article__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: 0;
  color: var(--text-3);
  font-size: var(--fs-sm);
}

/* The cover, and on this page it IS the largest contentful paint. The box is
   reserved by width and height on the element so nothing moves as it lands. */
.article__cover {
  margin: 0 0 var(--sp-5);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--surface-alt);
}
.article__cover img {
  display: block;
  width: 100%;
  height: auto;
}

/* ---- THE KEY ANSWER ------------------------------------------------------
   The column that is not obvious, and the reason it earns its place:
   docs/SEO_AEO.md section 8 is about being the source an answer engine
   quotes, and an engine extracts SENTENCES — self-contained, specific, first.
   So it is a field of its own rendered above the body, not a paragraph
   somebody remembers to put at the top.

   IT LOOKS LIKE AN ANSWER AND NOT LIKE A WARNING. --primary-soft with a
   --primary edge, which is the site treatment for "this is the important
   fact"; --warning-soft would read as a caveat and --live as an auction. */
.keyanswer {
  margin-bottom: var(--sp-6);
  padding: var(--sp-4) var(--sp-5);
  border: 1px solid var(--border);
  border-left: 4px solid var(--primary);
  border-radius: var(--radius-md);
  background: var(--primary-soft);
}
.keyanswer__label {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--primary);
}

/* Larger than the body, because it is the sentence the page exists to have
   read and quoted. */
.keyanswer__text {
  margin: 0;
  font-size: var(--fs-md);
  line-height: var(--lh-base);
  color: var(--text);
}

/* ---- THE RENDERED BODY --------------------------------------------------
   Everything in here is generated by Markdig from what an administrator typed,
   so this is the one block on the site whose markup nobody writes by hand.
   That cuts both ways: the styling has to cover every element the pipeline can
   emit, because there is no view to notice an unstyled one — and it must reach
   NO further than that, because a rule matching a bare p from here would
   escape into the rest of the site the first time somebody nested it.

   The elements the pipeline can produce, all of them below: headings 2 to 4,
   paragraphs, links, emphasis, lists, blockquotes, tables, images, horizontal
   rules, code and footnotes. Raw HTML cannot be produced at all — that is what
   DisableHtml() means and it is the whole XSS story. */
.prosebody { line-height: var(--lh-base); }

.prosebody > *:first-child { margin-top: 0; }
.prosebody > *:last-child { margin-bottom: 0; }

/* A heading inside an article is a step below the headline, and the space
   above it is what makes the article scannable — which is most of what a
   buying guide is for. */
.prosebody h2 {
  margin: var(--sp-7) 0 var(--sp-3);
  font-size: var(--fs-xl);
}
.prosebody h3 {
  margin: var(--sp-6) 0 var(--sp-2);
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
}
.prosebody h4 {
  margin: var(--sp-5) 0 var(--sp-2);
  font-size: var(--fs-md);
}

.prosebody p { margin: 0 0 var(--sp-4); }

/* Links inside the body are UNDERLINED, and that is deliberate rather than
   inherited. Everywhere else on this site a link sits in a nav, on a card or
   on a button, where its position says what it is; in a paragraph of prose
   colour alone is the only signal, and colour alone is not one for a reader
   who cannot see the difference. The links out of an article into a listing
   page are the point of this whole section, so they are the last links on the
   site that should be hard to spot. */
.prosebody a {
  color: var(--primary);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.prosebody a:hover { color: var(--primary-hover); }

.prosebody ul,
.prosebody ol {
  margin: 0 0 var(--sp-4);
  padding-left: var(--sp-6);
}
.prosebody li { margin-bottom: var(--sp-2); }
.prosebody li > ul,
.prosebody li > ol { margin-top: var(--sp-2); }

.prosebody blockquote {
  margin: var(--sp-5) 0;
  padding: var(--sp-3) var(--sp-5);
  border-left: 3px solid var(--border-strong);
  color: var(--text-2);
}
.prosebody blockquote p:last-child { margin-bottom: 0; }

.prosebody hr {
  margin: var(--sp-6) 0;
  border: 0;
  border-top: 1px solid var(--border);
}

.prosebody img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: var(--sp-5) auto;
  border-radius: var(--radius-md);
}

/* A table is not prose. It has no comfortable measure, so it keeps the
   article width and SCROLLS INSIDE ITSELF rather than pushing the page
   sideways — the rule REDESIGN-03b applied to the listing page fact tables,
   and the one thing that stops a wide table breaking a phone. */
.prosebody table {
  display: block;
  width: 100%;
  overflow-x: auto;
  margin: 0 0 var(--sp-5);
  border-collapse: collapse;
  font-size: var(--fs-sm);
}
.prosebody th,
.prosebody td {
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border);
  text-align: left;
  vertical-align: top;
}
.prosebody thead th {
  border-bottom: 2px solid var(--border-strong);
  color: var(--text-2);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: .04em;
}

.prosebody code {
  padding: 1px 5px;
  border-radius: var(--radius-sm);
  background: var(--surface-alt);
  font-family: var(--font-num);
  font-size: .9em;
}
.prosebody pre {
  margin: 0 0 var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-alt);
  overflow-x: auto;
}
.prosebody pre code { padding: 0; background: none; }

/* Footnotes. Markdig emits them as an ol inside a section.footnotes after a
   rule, and without this they are body text pretending to be a list. */
.prosebody .footnotes {
  margin-top: var(--sp-6);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm);
  color: var(--text-2);
}

/* ---- THE VIDEO FACADE ---------------------------------------------------
   WHAT IS IN THE MARKUP IS A LINK WITH A PHOTOGRAPH IN IT. No iframe, and
   nothing from another origin fetched, until somebody presses play — a video
   frame costs roughly a megabyte against a 500 KB page budget on a Pakistani
   mobile connection, and most readers of an article never press it.
   docs/SEO_AEO.md section 7.

   With no JavaScript the link opens the video where it is hosted. That is the
   whole degradation, and it is a smaller thing rather than a broken one. */
.videobox {
  position: relative;
  margin: 0 0 var(--sp-5);
}
.videobox__go {
  position: relative;
  display: block;

  /* 16:9, held before the poster arrives, so the key answer under it does not
     jump down the page as the image lands. */
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--surface-dark);
}
.videobox__poster,
.videobox__blank {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.videobox__blank { background: var(--surface-dark-2); }

/* The play button. --live, and it is the one place on this site outside an
   auction that the token is used — because it is the same claim: something is
   running and you are being asked to press it now. It is also the colour every
   reader already reads as "play" on a video, and inventing a fourth meaning
   for --primary here would be the site being clever at the reader expense. */
.videobox__play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: grid;
  place-items: center;
  width: 72px;
  height: 72px;
  padding-left: 4px;
  border-radius: var(--radius-pill);
  background: var(--live);
  color: var(--on-live);
  box-shadow: var(--shadow-2);
  transition: transform var(--dur-base) var(--ease);
}
.videobox__go:hover .videobox__play { transform: translate(-50%, -50%) scale(1.06); }
@media (prefers-reduced-motion: reduce) {
  .videobox__go:hover .videobox__play { transform: translate(-50%, -50%); }
}

.videobox__frame {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
  border-radius: var(--radius-lg);
}

.videobox__note {
  margin-top: var(--sp-2);
  color: var(--text-3);
  font-size: var(--fs-xs);
}

/* ---- The cars at the foot of an article --------------------------------
   Full width, outside the reading column: a grid of four cards is not prose
   and has no comfortable measure. The same call REDESIGN-03b made about the
   sold-cars section on a dealer profile. */
.artcars__head,
.artmore__head { margin-top: var(--sp-7); }

.artcars__more {
  margin-top: var(--sp-4);
  color: var(--text-2);
}

/* The admin preview banner. Full width above everything, so nobody publishes a
   draft believing they were looking at the live page. */
.previewbar {
  margin-top: var(--sp-4);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  align-items: baseline;
}

@media (max-width: 1000px) {
  .artgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 640px) {
  .artgrid { grid-template-columns: minmax(0, 1fr); }

  /* The headline comes back to the ordinary page size on a phone: --fs-3xl is
     48px and a four-word headline at 48px takes four lines on a 390px screen,
     which is a screenful of heading before a word of the article. */
  .article__head h1 { font-size: var(--fs-2xl); }

  .keyanswer { padding: var(--sp-4); }

  .videobox__play { width: 56px; height: 56px; }
}
