/* ==========================================================================
   SCOPE — components and motion
   --------------------------------------------------------------------------
   Loaded after design.css. Two jobs:

     1. Give the components that already exist the physical feedback they had
        none of. .btn, .card, .job and the rest were static: they changed colour
        on hover and did nothing at all on touch, which on a phone means the
        only confirmation a tap registered is the page changing half a second
        later.

     2. Add the patterns the product had no answer for -- a bottom sheet, a
        segmented control, a skeleton, a tab bar, a toast.

   EVERYTHING ANIMATES transform AND opacity, NOTHING ELSE. Those two are the
   only properties a compositor can run without the main thread, so they hold
   60fps on a mid-range Android while the estimate is being priced. Animating
   width, height, top or margin means a layout pass per frame, and on this
   product that competes with a Monte Carlo running in the same tab.

   ALL OF IT IS OFF UNDER prefers-reduced-motion. Not reduced -- off. The block
   at the foot of this file is a blanket, because a partial answer here is the
   thing people with vestibular disorders actually report.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Press
   --------------------------------------------------------------------------
   :active, not :hover, because the finger has no hover. The scale is 0.97 and
   the reason it is not smaller is that anything under about 0.95 reads as the
   control moving AWAY from the finger rather than yielding to it.

   transform-origin: center and a spring curve, so it settles rather than
   stopping. The release is slower than the press (dur-2 out, dur-1 in): a fast
   press and a slow return is what physical buttons do.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .btn, .hq-btn, .card-press, .job, .jobpick-card, .line, .svc,
  .card-opt label, .tol-opt label, .posture-opt label, .seg-opt label,
  .tabbar a, .sheet-handle, .link-block {
    transition: transform var(--dur-2) var(--ease-spring),
                box-shadow var(--dur-2) var(--ease-out),
                background-color var(--dur-1) var(--ease),
                border-color var(--dur-1) var(--ease);
  }
  .btn:active, .hq-btn:active, .card-press:active, .job:active,
  .jobpick-card:active, .line:active, .svc:active,
  .card-opt label:active, .tol-opt label:active, .posture-opt label:active,
  .seg-opt label:active, .tabbar a:active, .link-block:active {
    transform: scale(.97);
    transition-duration: var(--dur-1);
  }
  /* A card lifts on hover on a pointer device and does nothing on a touch one.
     hover: hover is the query that tells them apart -- a plain :hover rule
     fires on tap on iOS and then STICKS until the next tap elsewhere. */
  @media (hover: hover) {
    .card-press:hover, .job:hover, .jobpick-card:hover {
      transform: translateY(-1px);
      box-shadow: var(--sh-3);
    }
  }
}

/* --------------------------------------------------------------------------
   2. The segmented control
   --------------------------------------------------------------------------
   The posture chooser -- Keen / Balanced / Safe -- is three radios. It was
   three bordered boxes. This is the iOS-style segmented control: one track,
   one thumb, and the thumb moves.

   THE THUMB IS A SEPARATE ELEMENT, not a background on the checked label, and
   that is the whole trick. A background cannot animate BETWEEN two elements;
   a single absolutely positioned thumb translated by a custom property can,
   and translation is compositor work.

   No script. --seg-i is set by :has() off which radio is checked, so the thumb
   position is pure CSS and survives the page being served with JS off.
   -------------------------------------------------------------------------- */
.seg {
  position: relative;
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: 0;
  padding: 3px;
  background: var(--well);
  border: var(--bw) solid var(--construction);
  border-radius: var(--r-pill);
  isolation: isolate;
}
.seg::before {
  content: "";
  position: absolute; z-index: 0;
  inset: 3px auto 3px 3px;
  width: calc((100% - 6px) / var(--seg-n, 3));
  background: var(--readout);
  border-radius: var(--r-pill);
  box-shadow: var(--sh-2);
  transform: translateX(calc(var(--seg-i, 0) * 100%));
  transition: transform var(--dur-3) var(--ease-spring);
}
.seg:has(.seg-opt:nth-of-type(1) input:checked) { --seg-i: 0; }
.seg:has(.seg-opt:nth-of-type(2) input:checked) { --seg-i: 1; }
.seg:has(.seg-opt:nth-of-type(3) input:checked) { --seg-i: 2; }
.seg-opt { position: relative; z-index: 1; display: contents; }
.seg-opt label {
  display: flex; align-items: center; justify-content: center;
  min-height: 40px; padding: 0 var(--s3);
  position: relative; z-index: 1;
  font-size: var(--fs-meta); font-weight: 600;
  color: var(--muted); cursor: pointer;
  border-radius: var(--r-pill);
  /* The label never paints a background. The thumb behind it is the surface,
     so the two can never disagree about which segment is selected. */
  background: none;
}
.seg-opt input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.seg-opt input:checked + label { color: var(--ink); }
.seg-opt input:focus-visible + label {
  outline: var(--bw-strong) solid var(--navy); outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   3. Skeletons
   --------------------------------------------------------------------------
   A spinner on a blank screen says "wait". A skeleton says "here is the shape
   of what is coming", which on a slow connection is the difference between
   waiting and wondering whether it is broken.

   The shimmer is a background-position animation on a gradient, which is the
   one exception to the transform-and-opacity rule in this file -- and it is
   safe because background-position on a fixed-size box does not trigger layout
   or paint of anything else. It is also the only way to do it without a
   pseudo-element per skeleton.
   -------------------------------------------------------------------------- */
.sk {
  --sk-base: var(--n-100);
  --sk-lift: var(--n-200);
  background: linear-gradient(90deg,
              var(--sk-base) 0%, var(--sk-lift) 50%, var(--sk-base) 100%);
  background-size: 200% 100%;
  border-radius: var(--r-sm);
  /* So a skeleton cannot be read out as content. It has no text, but an empty
     box with a background is still an element a rotor will stop on. */
  pointer-events: none;
}
.sk[aria-hidden="true"] { user-select: none; }
.sk-line { height: 1em; margin: .35em 0; }
.sk-line.is-short { width: 45%; }
.sk-line.is-mid   { width: 70%; }
.sk-title { height: 1.6em; width: 60%; border-radius: var(--r-sm); }
.sk-block { height: 120px; border-radius: var(--r); }
.sk-pill  { height: 28px; width: 92px; border-radius: var(--r-pill); }

@media (prefers-reduced-motion: no-preference) {
  .sk { animation: sk-shimmer 1400ms linear infinite; }
  @keyframes sk-shimmer { to { background-position: -200% 0; } }
}

/* --------------------------------------------------------------------------
   4. Stagger
   --------------------------------------------------------------------------
   List items arriving together read as a page load. Arriving 40ms apart reads
   as a list being dealt. The delay is capped: past about the eighth row the
   stagger stops being rhythm and becomes waiting, so rows beyond it all use
   the same delay and land together.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .stagger > * {
    animation: rise var(--dur-3) var(--ease-out) backwards;
    animation-delay: calc(var(--i, 0) * 40ms);
  }
  .stagger > *:nth-child(n+9) { animation-delay: 320ms; }
  @keyframes rise {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: none; }
  }
}

/* --------------------------------------------------------------------------
   5. The bottom sheet
   --------------------------------------------------------------------------
   Built on <dialog>, so the focus trap, the Escape key, the inert backdrop and
   the top-layer stacking are all the platform's rather than a script's. That
   matters here more than usual: this product's console has no JavaScript at
   all and its shell has five live regions with a documented arming gate, so
   every hand-rolled focus trap is a thing that can fight them.

   Drag to dismiss needs a pointer handler and lives in ui.js. The sheet works
   without it: Escape closes, the backdrop closes, and the handle is a real
   button with an accessible name.
   -------------------------------------------------------------------------- */
.sheet {
  width: 100%; max-width: 34rem;
  margin: auto auto 0;                      /* bottom-anchored */
  padding: 0;
  border: 0;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  background: var(--readout);
  color: var(--ink);
  box-shadow: var(--sh-4);
  /* The home indicator. Without this the last control in a sheet sits under
     the bar on an iPhone and cannot be pressed. */
  padding-bottom: env(safe-area-inset-bottom, 0px);
  max-height: 88dvh;
  overflow: hidden auto;
  overscroll-behavior: contain;             /* the page behind does not scroll */
}
.sheet::backdrop {
  background: rgba(6, 15, 23, .5);
  backdrop-filter: blur(6px);
}
@supports not (backdrop-filter: blur(6px)) {
  .sheet::backdrop { background: rgba(6, 15, 23, .66); }
}
.sheet-handle {
  display: block; width: 100%;
  padding: var(--s3) 0 var(--s2);
  background: none; border: 0; cursor: grab;
}
.sheet-handle::before {
  content: ""; display: block;
  width: 36px; height: 4px; margin: 0 auto;
  background: var(--n-300); border-radius: var(--r-pill);
}
.sheet-body { padding: var(--s2) var(--s5) var(--s6); }
.sheet-head { padding: 0 var(--s5) var(--s3); }
.sheet-head h2 { margin: 0; }

@media (prefers-reduced-motion: no-preference) {
  .sheet[open] { animation: sheet-up var(--dur-4) var(--ease-spring); }
  .sheet[open]::backdrop { animation: fade-in var(--dur-3) var(--ease-out); }
  @keyframes sheet-up { from { transform: translateY(100%); } }
  @keyframes fade-in  { from { opacity: 0; } }
}
/* Set by ui.js while a drag is in progress, so the sheet follows the finger
   with no transition fighting it. */
.sheet.is-dragging { transition: none; }

/* --------------------------------------------------------------------------
   6. The tab bar
   --------------------------------------------------------------------------
   Mobile only. The product's primary actions live in the bottom third, which
   is the only part of a 6.7in phone a thumb reaches without the hand moving.

   Hidden above 48rem, where the rail takes over -- display: none and not a
   clip, so exactly one navigation is in the accessibility tree and the tab
   order at any width.
   -------------------------------------------------------------------------- */
.tabbar {
  position: fixed; z-index: var(--z-nav);
  inset: auto 0 0 0;
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  background: color-mix(in srgb, var(--readout) 92%, transparent);
  backdrop-filter: saturate(180%) blur(20px);
  border-top: var(--bw) solid var(--construction);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
@supports not (backdrop-filter: blur(20px)) {
  .tabbar { background: var(--readout); }
}
.tabbar a {
  display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 3px;
  min-height: var(--tap); padding: var(--s2) var(--s1);
  font-size: var(--fs-micro); font-weight: 600;
  color: var(--muted); text-decoration: none;
  overflow-wrap: anywhere; hyphens: auto; text-align: center;
}
.tabbar a[aria-current="page"] { color: var(--ink); }
.tabbar-mark {
  width: 22px; height: 3px; border-radius: var(--r-pill);
  background: transparent;
}
.tabbar a[aria-current="page"] .tabbar-mark { background: var(--orange-600); }
@media (min-width: 48rem) { .tabbar { display: none; } }

/* --------------------------------------------------------------------------
   7. Cross-document transitions
   --------------------------------------------------------------------------
   This is a server-rendered app: every navigation is a real document load.
   @view-transition opts the whole site into the browser's cross-document
   transition, so a tap on a job row fades to the record instead of blinking
   white -- with no router, no SPA and no script.

   Unsupported engines ignore the at-rule entirely and get today's behaviour,
   which is the correct fallback and needs no feature detection.
   -------------------------------------------------------------------------- */
@view-transition { navigation: auto; }

@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) { animation: vt-out var(--dur-2) var(--ease-in) both; }
  ::view-transition-new(root) { animation: vt-in  var(--dur-3) var(--ease-out) both; }
  @keyframes vt-out { to   { opacity: 0; transform: translateY(-4px); } }
  @keyframes vt-in  { from { opacity: 0; transform: translateY(6px); } }
}
/* The price is the shared element between the builder and the record: naming
   it means the figure travels rather than being replaced. */
.vt-price { view-transition-name: price; }
.vt-header { view-transition-name: header; }

/* --------------------------------------------------------------------------
   8. Toast
   -------------------------------------------------------------------------- */
.toast {
  position: fixed; z-index: var(--z-toast);
  left: 50%; bottom: calc(env(safe-area-inset-bottom, 0px) + var(--s6));
  transform: translateX(-50%);
  max-width: min(28rem, calc(100vw - var(--s6)));
  padding: var(--s3) var(--s5);
  background: var(--n-900); color: var(--n-0);
  border-radius: var(--r-pill);
  box-shadow: var(--sh-4);
  font-size: var(--fs-meta); font-weight: 600;
}
@media (prefers-reduced-motion: no-preference) {
  .toast { animation: toast-in var(--dur-3) var(--ease-spring); }
  @keyframes toast-in {
    from { opacity: 0; transform: translate(-50%, 12px) scale(.96); }
  }
}

/* --------------------------------------------------------------------------
   9. Dimension lines
   --------------------------------------------------------------------------
   The brand's structural device, per the brief: a hairline with tick
   terminators, the way a measurement is drawn on a survey. Used where a
   dimension is being STATED, never as decoration -- it is the one visual
   idiom in the product that means "this is a measured quantity".
   -------------------------------------------------------------------------- */
.dim {
  display: flex; align-items: center; gap: var(--s2);
  font-family: var(--font-num); font-variant-numeric: tabular-nums;
  font-size: var(--fs-tiny); color: var(--muted);
}
.dim::before, .dim::after {
  content: ""; flex: 1 1 auto; height: 1px;
  background: var(--rule-soft);
  /* The ticks. A 1px rule with a 7px cap at each end, drawn with a gradient so
     it is one element and not three. */
  background-image: linear-gradient(var(--rule-soft), var(--rule-soft));
  background-size: 100% 1px; background-repeat: no-repeat;
  background-position: center;
  position: relative;
}
.dim-tick { position: relative; }
.dim-tick::before {
  content: ""; position: absolute; top: 50%; left: 0;
  width: 1px; height: 7px; margin-top: -3.5px;
  background: var(--rule-soft);
}

/* --------------------------------------------------------------------------
   10. Reduced motion
   --------------------------------------------------------------------------
   A blanket, and deliberately so. A partial answer -- shortening durations,
   keeping "small" movements -- is what people with vestibular disorders
   actually report as the problem, because the movements that trigger it are
   not reliably the large ones.

   The two exceptions are deliberate: a skeleton stops shimmering but stays
   visible, and a view transition degrades to a cut rather than a hold.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
  .sk { animation: none !important; background: var(--sk-base); }
  ::view-transition-group(*), ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* --------------------------------------------------------------------------
   11. The /ui sheet's own layout
   --------------------------------------------------------------------------
   Only this page uses these. Kept in the shipping stylesheet rather than a
   demo one so the page is rendered by exactly what the product is rendered by
   -- a separate demo stylesheet is how a component sheet starts lying.
   -------------------------------------------------------------------------- */
.ui-doc { padding-bottom: var(--s10); }
.ui-head { margin-bottom: var(--s6); }
.ui-themes { display: flex; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s4); }
.ui-rows { display: grid; gap: var(--s3); }
.ui-inline { display: flex; gap: var(--s3); flex-wrap: wrap; align-items: center; }

.ui-swatches {
  list-style: none; margin: var(--s4) 0 0; padding: 0;
  display: grid; gap: var(--s3);
  grid-template-columns: repeat(auto-fill, minmax(min(7rem, 100%), 1fr));
  font-size: var(--fs-micro); color: var(--muted);
}
.ui-sw {
  display: block; height: 44px; margin-bottom: var(--s1);
  border-radius: var(--r-sm);
  border: var(--bw) solid var(--construction);
}

.ui-elevs {
  display: grid; gap: var(--s4); margin-top: var(--s4);
  grid-template-columns: repeat(auto-fit, minmax(min(6rem, 100%), 1fr));
}
.ui-elev {
  display: grid; place-items: center;
  min-height: 84px; border-radius: var(--r);
  background: var(--readout); color: var(--muted);
  font-family: var(--font-num); font-size: var(--fs-meta);
}

.ui-states { display: grid; gap: var(--s5); margin-top: var(--s4); }
.ui-state-k {
  margin: 0 0 var(--s2);
  font-size: var(--fs-micro); font-weight: var(--w-bold);
  letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--muted);
}
.ui-empty {
  display: grid; gap: var(--s2); justify-items: center; text-align: center;
  padding: var(--s7) var(--s5);
  background: var(--well); border-radius: var(--r);
  border: var(--bw) dashed var(--construction);
}
.ui-empty .btn { margin-top: var(--s3); }
.ui-doc code {
  font-family: var(--font-num); font-size: .92em;
  background: var(--well); padding: 1px 5px; border-radius: 5px;
}
@media (min-width: 48rem) {
  .ui-states { grid-template-columns: repeat(3, 1fr); align-items: start; }
}

/* --------------------------------------------------------------------------
   12. The tab bar, and the action bar above it
   --------------------------------------------------------------------------
   THE COLLISION THIS SOLVES. .actionbar is already fixed to the bottom of the
   viewport on every form page -- Continue, Save settings, Create account. The
   tab bar is fixed to the bottom too, at a higher z-index, so without this the
   tab bar sits ON TOP of the primary action of the page and the roofer cannot
   press Continue at all.

   The stacking order is the platform's convention and it is the right way
   round: the tab bar is chrome and owns the true bottom edge, the page's own
   action sits directly above it. Reversing them puts a permanent navigation
   between the thumb and the button the page exists for.

   --tabbar-h is published here rather than measured, because the console and
   the product both need it and nothing on this page runs a script that could
   measure it. It is the token height plus its own padding plus the home
   indicator, which is exactly what the bar is built from.
   -------------------------------------------------------------------------- */
:root {
  --tabbar-h: calc(var(--tap) + var(--s2) * 2 + env(safe-area-inset-bottom, 0px));
}

@media (max-width: 47.999rem) {
  /* The action bar rides above the tab bar, and loses its own safe-area
     padding: the tab bar below it is now the thing touching the home
     indicator, so reserving for it twice would leave a gap. */
  body:has(.tabbar) .actionbar {
    bottom: var(--tabbar-h);
    padding-bottom: 0;
  }
  /* Content clears both. Without this the last row of a list sits under the
     bar and cannot be read or pressed. */
  body:has(.tabbar) main {
    padding-bottom: calc(var(--tabbar-h) + var(--s6));
  }
  body:has(.tabbar):has(.actionbar) main {
    padding-bottom: calc(var(--tabbar-h) + var(--bar-h, 80px) + var(--s6));
  }
  /* The footer too: it is the last thing on the page and was landing under
     the bar on every screen short enough not to scroll. */
  body:has(.tabbar) .sitefoot { margin-bottom: var(--s4); }
}

/* THE PRIMARY SLOT. The action the product exists for, carrying the accent so
   it reads as primary without a different shape -- a floating button over a
   tab bar is two navigations arguing about which is the main one.

   The mark is the channel, not the label colour: --orange-600 is 3.48:1, which
   clears 1.4.11 for a graphical object and does NOT clear 4.5:1 for text. The
   word stays --ink. */
/* THE MARK MEANS "YOU ARE HERE" AND NOTHING ELSE.

   The first cut painted the primary slot's mark orange unconditionally, so on
   the home screen BOTH Home and New estimate wore the bar and two items looked
   current at once. The whole value of a current indicator is that exactly one
   thing has it.

   So the primary slot is distinguished by WEIGHT and INK instead, which is a
   different channel from position and cannot be confused with it. The word is
   --ink rather than --muted and set bold; everything else in the bar is the
   quieter pair. */
.tabbar a.is-primary { color: var(--ink); font-weight: var(--w-bold); }

/* Forced colours: every tint above is discarded, so the current item needs a
   shape. A border-top is the one thing that survives. */
@media (forced-colors: active) {
  .tabbar a[aria-current="page"] { border-top: 3px solid Highlight; }
  .tabbar-mark { display: none; }
}

/* --------------------------------------------------------------------------
   13. Empty states
   --------------------------------------------------------------------------
   First-class, and the same shape everywhere. The product had three different
   answers to "there is nothing here": a designed card on /history, a bare grey
   sentence on /, and on some screens nothing at all.

   A mark, a heading, a sentence and -- only where there is somewhere useful to
   go -- one action. Never demonstration data: a fake estimate sitting in an
   empty list teaches him to distrust the real ones the moment they arrive.
   -------------------------------------------------------------------------- */
.empty-state {
  display: grid; justify-items: center; text-align: center;
  gap: var(--s2);
  padding: var(--s8) var(--s5);
  background: var(--readout);
  border: var(--bw) solid var(--construction);
  border-radius: var(--r-lg);
  max-width: 34rem; margin-inline: auto;
}
.empty-state-mark {
  display: grid; place-items: center;
  width: 64px; height: 64px; margin-bottom: var(--s2);
  border-radius: var(--r-lg);
  background: var(--well);
  color: var(--rule-soft);
}
.empty-state-mark svg { width: 30px; height: 30px; }
.empty-state-h {
  margin: 0; font-size: var(--fs-lead); font-weight: var(--w-bold);
  color: var(--ink);
}
.empty-state .meta { margin: 0; max-width: 30ch; }
.empty-state .btn { margin-top: var(--s4); }

/* --------------------------------------------------------------------------
   14. The posture control
   --------------------------------------------------------------------------
   Keen / Balanced / Safe. I BUILT A SLIDING THUMB HERE AND TOOK IT OUT AGAIN,
   which is worth writing down so nobody rebuilds it.

   The control already had a good selected state: an orange fill with dark ink,
   a marker triangle above it, and the whole group drawn as a dimension line
   with tick terminators at each end. A white thumb sliding behind an OPAQUE
   orange label gains nothing that can be seen, and what it actually produced
   was a white sliver showing under the selected box.

   So the animation goes on what is already there. The fill and the ink
   transition, and the marker travels, which is the part the eye follows
   anyway. Fewer elements, no artifact, and the brand device survives intact.

   The markup is untouched for the reason the fieldset comment gives:
   test_the_posture_control_is_still_native_radios_in_a_fieldset matches
   `class="tol"` including the closing quote.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .tol-opt label {
    transition: background-color var(--dur-2) var(--ease-out),
                color var(--dur-2) var(--ease-out),
                border-color var(--dur-2) var(--ease-out);
  }
  /* The marker above the group. It is positioned per-option in scope.css, so
     transitioning `left` here is the one place in this file that animates a
     layout property -- on a 12px triangle with no siblings depending on its
     position, which costs one cheap layout on a control that moves three
     times in a session. transform would need the offsets rewritten. */
  .tol-mark, .tol-marker, .tol-opts + .tol-mark {
    transition: left var(--dur-3) var(--ease-spring),
                transform var(--dur-3) var(--ease-spring);
  }
}

/* Each option carries its own price, so the three figures he is choosing
   between want the figure face and want to line up under one another. */
.tol-amt {
  font-family: var(--font-num); font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

/* --------------------------------------------------------------------------
   15. The brand surface, everywhere it belongs
   --------------------------------------------------------------------------
   .home-hero and .step-hero read --field-face and are graduated. .locator --
   the measuring screen, the one screen in the product that goes entirely dark
   -- was painted flat --bezel, so the two full-bleed navy moments in a single
   session were the same shape and a different finish.

   It is the longest dark moment there is (LOCATOR_MIN_MS is 3600ms plus the
   Ordnance Survey round trip), so it is the one most worth grading. Same
   graduation, same direction.
   -------------------------------------------------------------------------- */
/* .locator.locator, and the doubled class is load-bearing. scope.css declares
   `.locator { background: var(--bezel) }` at the same (0,1,0) specificity and
   is loaded from index.html's {% block head %}, which comes AFTER this file in
   the document -- so an equal-specificity rule here loses on order. Repeating
   the class is the smallest way to win it without an !important or a parent
   selector that pretends to be about structure. */
.locator.locator { background: var(--field-face); }

/* The empty state is a card and should sit on the page like one. It had the
   border but not the lift, so it read as an outlined region rather than an
   object -- which on the first screen of a new account is the difference
   between "nothing here yet" and "something failed to load". */
.empty-state { box-shadow: var(--sh-1); }
