/* ==============================================================================================
   RAVENOR — QUOTE. One implementation, every page.
   ----------------------------------------------------------------------------------------------
   This file used to live inside shell.css, which the HOMEPAGE does not load — so the homepage had
   no drawer at all and shipped a second, inline quote form instead. Splitting the panel out is what
   lets both shells share ONE interaction, one context model and one submit path.

   Loaded by every page, including index.html. It must therefore stand on its own: the six design
   tokens below are re-declared because the homepage's inline CSS never received them, and every
   other custom property this file uses is already defined by both shells under the same name.
   ============================================================================================== */
:root{
  --danger:#cf6a6a;
  --ease-mech:cubic-bezier(.16,.84,.24,1);
  --t-fast:.18s;--t-ctl:.28s;--t-move:.42s;--t-mass:.52s;
}

/* the one inline error the quote form can show */
.rv-field-err{color:var(--danger);font-size:12px;margin-top:6px;display:none}
.rv-field-err.on{display:block}

/* ============================================================================================
   QUOTE — one form, two shapes
   --------------------------------------------------------------------------------------------
   Desktop gets a right-hand drawer: the page the visitor was reading stays visible behind it, so
   the enquiry is attached to the thing they were looking at rather than replacing it. Phones get
   a bottom sheet, because a 92%-height panel anchored to the bottom edge puts the first field
   and the submit button inside thumb reach, and because a right-side drawer on a 375px screen is
   just a full-screen modal wearing the wrong animation.

   It is ONE form and ONE submit path. The two selectors below only change where the panel is and
   how it arrives.
   ============================================================================================ */
.rv-scrim{position:fixed;inset:0;z-index:90;background:rgba(6,8,7,.62);
  backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);
  opacity:0;pointer-events:none;transition:opacity var(--t-mass) var(--ease)}
.rv-scrim.open{opacity:1;pointer-events:auto}

/* The panel is built from the same material as the header and the cards: a graphite body with a
   vertical fall and one inset sheen along the top edge. A flat #14181A rectangle read as an admin
   drawer next to a header that is layered glass — same palette, different class of object. No
   added transparency, no glow: the depth is in the gradient, which is how the rest of the site
   does it. */
.rv-quote{position:fixed;z-index:95;display:flex;flex-direction:column;
  background:
    radial-gradient(120% 55% at 50% 0%,rgba(216,232,219,.055),transparent 62%),
    linear-gradient(180deg,rgba(22,27,24,.99) 0%,rgba(16,20,18,.99) 46%,rgba(12,15,13,.99) 100%);
  transition:transform var(--t-mass) var(--ease-mech),opacity var(--t-move) var(--ease);
  will-change:transform}
/* desktop: right drawer */
.rv-quote{top:0;right:0;height:100%;width:min(430px,94vw);border-left:1px solid var(--border-in);
  overflow-y:auto;overscroll-behavior:contain;
  /* Tab into a lower field and it lands clear of the panel edge rather than flush against it */
  scroll-padding-bottom:24px;scroll-padding-top:72px;
  box-shadow:inset 1px 0 0 rgba(236,246,238,.055),-30px 0 70px -40px rgba(0,0,0,.92);
  transform:translateX(102%);
  /* Heavier than the site default: a panel this size should read as something with mass being
     moved into place, so it accelerates hard and settles without overshoot. No spring — a
     bouncing drawer reads as an app, not as a workshop. */
  transition:transform var(--t-mass) var(--ease-mech),opacity var(--t-move) var(--ease)}
.rv-quote.open{transform:none}
/* THE FIELD — the one contained interactive surface on the site (surface E).
   A slow sage bloom that drifts behind the panel head, at an intensity where you notice it only
   if you look. It is confined to the head, never behind the inputs: a moving background under a
   form is a legibility problem dressed as craft. Two blooms on long, prime-ish durations so they
   never visibly loop, and the whole thing is one pseudo-element — no canvas, no rAF, nothing to
   schedule on a phone. `prefers-reduced-motion` freezes it into a static gradient that still
   looks deliberate. */
.rv-quote-field{position:absolute;inset:0 0 auto;height:190px;overflow:hidden;pointer-events:none;
  z-index:0;
  -webkit-mask-image:linear-gradient(180deg,#000 45%,transparent);
  mask-image:linear-gradient(180deg,#000 45%,transparent)}
.rv-quote-field::before,.rv-quote-field::after{content:'';position:absolute;inset:-40%;
  background:radial-gradient(38% 38% at 50% 50%,rgba(171,201,176,.16),transparent 68%);
  animation:rv-drift 23s ease-in-out infinite}
.rv-quote-field::after{
  background:radial-gradient(30% 30% at 50% 50%,rgba(226,238,229,.09),transparent 66%);
  animation-duration:31s;animation-direction:reverse}
@keyframes rv-drift{
  0%,100%{transform:translate3d(-16%,-8%,0) scale(1)}
  50%    {transform:translate3d(14%,10%,0) scale(1.18)}
}
@media(prefers-reduced-motion:reduce){
  .rv-quote-field::before,.rv-quote-field::after{animation:none;transform:none}
}

/* CONTEXT CARD — when the drawer is opened from a project or a service, that is the single most
   useful fact in the panel, so it is a card with a typed label rather than a chip with a colon.
   No internal ids: the visitor sees the same words they just read on the page. */
.rv-quote-ctx{display:none;position:relative;z-index:1;margin:0 0 18px;padding:12px 14px;
  border:1px solid var(--chip-bd);border-radius:var(--r-control,2px);background:rgba(21,32,26,.72);
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px)}
.rv-quote-ctx.on{display:block}
.rv-quote-ctx .k{display:flex;align-items:center;gap:7px;font-family:var(--font-mono);font-size:9.5px;
  letter-spacing:.2em;text-transform:uppercase;color:var(--sage);margin-bottom:5px}
.rv-quote-ctx .k svg{width:var(--ico-xs,12px);height:var(--ico-xs,12px)}
.rv-quote-ctx .v{color:var(--text-hi);font-size:14.5px;line-height:1.4}

/* CAPABILITY STRIP — trust without a single invented number. It states the five things Ravenor
   actually does, in order, which is a claim the workshop can stand behind. Set as a measured
   run of stages, not as badges. */
/* The five stages Ravenor performs. Three things were wrong with how they were drawn.
   (1) SHAPE. Each stage was marked with a 4x4px box at border-radius:50%. At four device pixels
   there are not enough samples to describe a circle, so the renderer produced an antialiased blob
   whose shape depended on the fractional x the preceding text run happened to end on - five marks
   that were meant to be identical and were visibly not. A 3px axis-aligned square rasterises
   exactly at any device-pixel ratio and is already Ravenor's setting-out mark elsewhere.
   (2) CONTRAST. The mark was painted in --chip-bd and the connector in --div, both BORDER tokens
   chosen to sit at the edge of visibility as hairlines. Used as the fill of a solid graphic they
   read as dirt, not as marks. The mark now takes the same foreground weight as the label it
   belongs to.
   (3) WRAP. See quote.js - the connectors are gone, and the row gap is now real so a second line
   sits under the first as a line rather than as a collision. */
.rv-quote-cap{position:relative;z-index:1;display:flex;align-items:center;flex-wrap:wrap;
  gap:8px 16px;margin-top:14px;padding-top:13px;border-top:1px solid var(--div)}
.rv-quote-cap .st{display:flex;align-items:center;gap:7px;font-family:var(--font-mono);font-size:9px;
  letter-spacing:.14em;text-transform:uppercase;color:var(--faint);white-space:nowrap}
.rv-quote-cap .st::before{content:'';width:3px;height:3px;background:var(--faint);flex:none}
@media(max-width:400px){.rv-quote-cap{gap:7px 12px}.rv-quote-cap .st{font-size:8.4px}}

/* Sticky, not fixed, and a child of the scrolling panel - which is what keeps it wheel-transparent
   while the close control stays reachable however far down the visitor has read. */
.rv-quote-head{position:sticky;top:0;z-index:3;display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding:18px 28px 15px;border-bottom:1px solid var(--div);flex:0 0 auto;
  background:linear-gradient(180deg,rgba(18,23,20,.98),rgba(16,20,18,.94))}
.rv-quote-head::after{content:'';position:absolute;left:0;right:0;bottom:-1px;height:1px;
  pointer-events:none;
  background:linear-gradient(90deg,transparent,rgba(171,201,176,.30) 22%,rgba(171,201,176,.36) 50%,
    rgba(171,201,176,.30) 78%,transparent)}
.rv-quote-head h2{font-size:19px;letter-spacing:var(--tr-display);color:var(--text-hi)}
.rv-quote-sub{color:var(--muted);font-size:13.5px;line-height:1.6;margin:0 0 18px}
/* NOTE (2026-08-20): a SECOND `.rv-quote-ctx` block used to sit here — the original one-line
   chip, left behind when the typed context card above replaced it. Same specificity, later in
   the file, so it quietly won: it forced `display:flex` over the card's `display:block`, which
   laid the typed label and the title out side by side in a pill instead of stacked in a card,
   and it overrode the card's padding, radius and background too. The card above was being built
   and never seen. Deleted rather than merged — there is one context card, and it is up there. */
.rv-x{color:var(--faint);border:1px solid var(--border-field);border-radius:var(--r-control,2px);width:34px;height:34px;
  display:flex;align-items:center;justify-content:center;flex:0 0 auto;transition:var(--t-ctl) var(--ease)}
.rv-x:hover{color:var(--text-hi);border-color:var(--ghost-bd)}
.rv-x svg{width:var(--ico-sm,16px);height:var(--ico-sm,16px)}
/* The close control is the way out of a modal, so on touch it gets a full target rather than the
   34px square that reads fine under a mouse pointer. */
@media (pointer:coarse){ .rv-x{width:44px;height:44px} }
/* The panel owns the scroll now. The body is an ordinary block inside it, not a nested scroll
   container - that nesting was the whole defect: with only the middle third scrollable, a wheel or
   a two-finger trackpad gesture over the title or over the footer moved nothing (measured dead at
   1280x720, 1366x768 and 1440x900). overscroll-behavior stays on the panel so the page underneath
   never takes over at the ends of the list. */
/* THE SEND BAR MUST NOT SIT ON TOP OF A FIELD.
   The footer used to be `position:sticky`, and a sticky footer OVERLAYS - it does not reserve. On a
   768px-tall laptop the description's bottom finished 51px BELOW the top of the Send bar, so the
   last thing a visitor types was unreachable. Reserving the bar's height in padding fixed the
   overlap but left an empty band the size of the bar sitting above it on every short form.

   The footer is simply not sticky any more. The panel is the one scroll owner, the footer is the
   last item in it, and `margin-top:auto` keeps it at the bottom when the form is short. Nothing
   overlays, nothing needs reserving, and no field can hide. On a short viewport you scroll to
   reach Send, which is what every form does. */
.rv-quote-body{padding:20px 28px 4px;flex:1 1 auto;display:flex;flex-direction:column}
/* The description absorbs whatever height is left, so a short form has no dead band above the send
   bar - and because the body now RESERVES the bar's height in padding, growing into that slack can
   no longer push the field underneath it. That reserve is what makes this safe; without it, this
   same rule is what put the textarea 51px under the bar. */
.rv-quote-body .fld-grow{flex:1 1 auto;display:flex;flex-direction:column;min-height:0}
.rv-quote-body .fld-grow textarea{flex:1 1 auto;min-height:118px;resize:vertical}
.rv-quote-row{display:grid;grid-template-columns:1fr 1fr;gap:11px}
.rv-quote-body .rv-quote-consent{display:flex;align-items:flex-start;gap:10px;margin:2px 0 18px;
  font-size:13px;line-height:1.5;letter-spacing:normal;text-transform:none;cursor:pointer}
.rv-quote-consent input{flex:none;width:18px;height:18px;margin:1px 0 0;accent-color:var(--green)}
.rv-quote-consent span{flex:1}
/* The phone control is a flag, a dial code and a number in one field. Sharing a 390px sheet with
   the email field left it about 160px wide and the example number was cut off mid-digit, which
   reads as a broken input on the one field people are most careful about. Below 520px the two
   stack and both get the full width. */
@media(max-width:520px){ .rv-quote-row{grid-template-columns:1fr;gap:0} }
.rv-quote-body label{display:block;font-size:11.5px;letter-spacing:.06em;text-transform:uppercase;
  font-family:var(--font-display);color:var(--faint);margin:0 0 7px}
.rv-quote-body .fld{margin-bottom:14px}
/* Sticky to the bottom of the panel: the way out of the form is always one glance away, and it
   never becomes a dead strip that swallows a scroll gesture. */
.rv-quote-foot{position:relative;z-index:3;padding:14px 28px 18px;border-top:1px solid var(--div);
  flex:0 0 auto;margin-top:auto;
  background:linear-gradient(180deg,rgba(16,20,18,.86),rgba(12,15,13,.98) 45%);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}
.rv-quote-note{text-align:center;margin-top:12px;color:var(--faint);font-size:11px;
  font-family:var(--font-mono);letter-spacing:.3px}
/* segmented choice: a native <select> cannot carry three scripts and a hover state that matches
   the rest of the page, and a custom listbox is one more thing to make accessible. Radios styled
   as chips keep the native keyboard and screen-reader behaviour for free. */
/* Eleven bordered capsules in a 470px column is what made this panel read as a calculator: every
   answer looked like a button, so the eye counted controls instead of reading a question. Same
   radios, same keyboard and screen-reader behaviour, same values on the wire - what changes is
   that an unselected option is now quiet type with a mark, and only the CHOSEN one is drawn. */
.rv-seg{display:grid;grid-template-columns:1fr 1fr;gap:2px 18px}
.rv-seg input{position:absolute;opacity:0;width:1px;height:1px}
.rv-seg label{display:flex;align-items:center;gap:11px;padding:9px 2px;cursor:pointer;
  font-family:var(--font-body);font-size:14.5px;font-weight:400;letter-spacing:0;
  text-transform:none;line-height:1.35;color:var(--muted);border-radius:6px;
  transition:color var(--t-fast,.18s) ease}
/* Round, because only one answer can be chosen. A square here would promise multi-select and the
   data model has a single value. 10px, well clear of the sub-pixel size where a small circle
   stops rasterising as one. */
.rv-seg label::before{content:'';flex:none;width:10px;height:10px;border-radius:50%;
  border:1px solid var(--border-ctl);background:transparent;
  transition:border-color .18s ease,box-shadow .18s ease}
.rv-seg label:hover{color:var(--text-hi)}
.rv-seg label:hover::before{border-color:var(--ghost-bd)}
.rv-seg input:checked+label{color:var(--text-hi)}
.rv-seg input:checked+label::before{border-color:var(--sage);
  box-shadow:inset 0 0 0 3px var(--sage),0 0 0 3px rgba(171,201,176,.14)}
.rv-seg input:focus-visible+label{outline:2px solid var(--sage);outline-offset:3px}
@media(max-width:420px){ .rv-seg{grid-template-columns:1fr} }

/* Touch. A finger is not a mouse pointer: on a coarse pointer every choice becomes a 44px target,
   which is also why the gap grows — 44px targets 8px apart are still one mis-tap away from each
   other. Left alone on fine pointers so the desktop rhythm is unchanged. */
@media (pointer:coarse){
  .rv-seg{gap:2px 16px}
  .rv-seg label{min-height:44px;padding:11px 2px}
}

/* Help text under a field — quieter than the label, never mistaken for an error. */
.rv-fld-help{margin:6px 0 0;font-size:11.5px;line-height:1.45;color:var(--faint)}

/* ---------------------------------------------------------------- phone country picker
   vendor/intl-tel-input 29.1.1 ships a light theme; these rules put it on the Ravenor surface.
   Recovered from the treatment that shipped before the picker was removed, so the panel looks the
   way the owner already approved rather than the way a default library looks. */
.iti{width:100%;display:block}
.iti__country-selector{background:#12161a;border:1px solid var(--border-in);border-radius:var(--r-control,2px);
  box-shadow:0 16px 44px rgba(0,0,0,.55);overflow:hidden}
/* The search field: theme it, do NOT re-lay-it-out.
   The overlap in the review captures was self-inflicted — this rule used to set `padding:10px 12px`,
   and that shorthand overwrote the vendor's own
       padding-left: calc(spacer + globe-icon-size + spacer)   =  10 + 17 + 10  =  37px
   which is the space the absolutely-positioned magnifier sits in. The placeholder then started at
   12px, underneath an icon occupying 10–27px.
   So only the block padding is set here: the inline padding stays the vendor's calc(), which also
   keeps its [dir=rtl] variant and the room reserved for the clear button correct without this file
   having to know either number. */
.iti__search-input{background:#0e1311;color:var(--text);border:0;
  border-bottom:1px solid var(--border-in);padding-block:10px}
.iti__search-input::placeholder{color:var(--faint)}
/* The vendor draws the magnifier and the clear control in --iti-icon-color (#555, chosen for a
   white panel). On this surface that is nearly invisible, so both are re-stated in our muted tone —
   colour only, no geometry. */
.iti__search-icon-svg{stroke:var(--muted)}
.iti__search-clear-svg .iti__search-clear-x{stroke:var(--muted)}
.iti__search-clear-svg .iti__search-clear-bg{fill:var(--muted)}
.iti__search-clear:hover,.iti__search-clear:focus-visible{background:rgba(171,201,176,.14)}
.iti__country-list{background:transparent}
.iti__country{color:var(--text)}
.iti__country.iti__highlight,.iti__country:hover{background:rgba(171,201,176,.14)}
.iti__country-name{color:var(--text)}
.iti__dial-code{color:var(--muted)}
.iti__selected-dial-code{color:var(--text)}
/* The selected-country button is a control in its own right and must be reachable and visible. */
.iti__selected-country{border-radius:var(--r-control,2px) 0 0 var(--r-control,2px)}
.iti__selected-country:focus-visible{outline:2px solid var(--sage);outline-offset:-2px}
@media (pointer:coarse){ .iti__selected-country{min-height:44px} }
/* The dropdown must not be cut off by the panel it lives in. .rv-quote-body is the drawer's scroll
   container and has to stay overflow-y:auto, so the list cannot simply escape it — instead it is
   capped to a height that fits inside that container and given its own scroll, which keeps the
   whole list reachable without the panel and the list fighting over the wheel. */
.rv-quote .iti__dropdown-content{z-index:120;max-height:min(46vh,300px)}
.rv-quote .iti__country-list{max-height:min(38vh,240px);overflow-y:auto;overscroll-behavior:contain}

/* success state — replaces the form rather than sitting under it, so there is no half-submitted
   form left on screen inviting a second send */
.rv-quote-done{display:none;flex-direction:column;align-items:center;justify-content:center;
  text-align:center;padding:50px 30px;gap:14px;flex:1 1 auto}
.rv-quote.done .rv-quote-done{display:flex}
.rv-quote.done .rv-quote-body,.rv-quote.done .rv-quote-foot{display:none}
.rv-quote-done .tick{width:64px;height:64px;border-radius:50%;border:1px solid var(--sage);
  display:flex;align-items:center;justify-content:center;color:var(--sage);
  box-shadow:0 0 0 8px rgba(171,201,176,.06);position:relative}
/* the ring draws itself once, like a mark being scribed */
.rv-quote.done .tick::after{content:'';position:absolute;inset:-7px;border-radius:50%;
  border:1px solid rgba(171,201,176,.34);animation:rv-scribe .85s cubic-bezier(.16,.84,.24,1) both}
@keyframes rv-scribe{from{transform:scale(.72);opacity:0}to{transform:scale(1);opacity:1}}
.rv-quote.done .tick svg{stroke-dasharray:32;stroke-dashoffset:32;
  animation:rv-tick .5s cubic-bezier(.16,.84,.24,1) .16s forwards}
@keyframes rv-tick{to{stroke-dashoffset:0}}
@media(prefers-reduced-motion:reduce){
  .rv-quote.done .tick::after{animation:none}
  .rv-quote.done .tick svg{animation:none;stroke-dashoffset:0}
}
.rv-quote-done .ref{margin-top:6px;font-family:var(--font-mono);font-size:10.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--faint)}
.rv-quote-done .tick svg{width:var(--ico-xl,26px);height:var(--ico-xl,26px)}
.rv-quote-done h3{font-size:19px;color:var(--text-hi)}
.rv-quote-done p{color:var(--muted);font-size:13.5px;max-width:300px;line-height:1.6}

@media(max-width:760px){
  /* bottom sheet */
  .rv-quote{top:auto;bottom:0;right:0;left:0;width:100%;height:auto;max-height:92vh;
    border-left:0;border-top:1px solid var(--border-in);border-radius:18px 18px 0 0;
    box-shadow:inset 0 1px 0 rgba(236,246,238,.06),0 -24px 60px -30px rgba(0,0,0,.95);
    transform:translateY(102%)}
  .rv-quote-head{padding:20px 20px 15px}
  /* grab handle: the affordance that says this panel can be pushed away */
  .rv-quote-head::before{content:'';position:absolute;top:8px;left:50%;transform:translateX(-50%);
    width:38px;height:4px;border-radius:2px;background:var(--border-ctl)}
  .rv-quote-body{padding:18px 20px 20px}
  .rv-quote-foot{padding:14px 20px calc(16px + env(safe-area-inset-bottom,0px))}
}

/* The scroll behind an open panel. Lenis drives the homepage, so quote.js also calls lenis.stop()
   there — overflow:hidden alone does not stop a smooth-scroll engine that owns the wheel. */
/* QUOTE-12: `overflow:hidden` alone returns the classic Windows scrollbar's 15px to the layout
   viewport, so opening the drawer shifted the whole page - and would visibly slide a fixed
   right-edge control. Reserving the gutter permanently means locking changes nothing. */
html{scrollbar-gutter:stable}
body.rv-locked{overflow:hidden}

/* ============================================================================== the edge handle
   A pull-tab welded to the right edge of the viewport. Graphite body, one sage seam down the edge
   it is joined on - the same weld-seam motif the cards use - paired chevrons pointing back at the
   edge the drawer arrives from, and the label set vertically so the tab reads as a fixture rather
   than as a floating button. On hover the whole tab travels 3px out of the edge and the chevrons
   travel with it: one gesture, the distance a real latch moves, no bounce.
   Hidden while the drawer is open, so it is never a control pointing at something already there. */
.rv-quote-handle{position:fixed;right:0;top:50%;transform:translateY(-50%);z-index:70;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:9px;
  width:38px;min-height:88px;padding:18px 0;border:1px solid var(--border);border-right:0;
  border-radius:10px 0 0 10px;cursor:pointer;
  background:linear-gradient(180deg,var(--raised),var(--surface));
  box-shadow:-10px 0 30px -18px rgba(0,0,0,.9),inset 1px 0 0 rgba(255,255,255,.04);
  transition:transform var(--t-ctl,.28s) var(--ease,cubic-bezier(.22,1,.36,1)),
             border-color var(--t-ctl,.28s),box-shadow var(--t-ctl,.28s)}
/* the seam: the edge the tab is joined on, lit in sage */
.rv-quote-handle::before{content:'';position:absolute;left:0;top:10px;bottom:10px;width:1px;
  background:linear-gradient(180deg,transparent,var(--sage),transparent);opacity:.5;
  transition:opacity var(--t-ctl,.28s)}
.rv-quote-handle:hover,.rv-quote-handle:focus-visible{transform:translate(-3px,-50%);
  border-color:var(--chip-bd);box-shadow:-14px 0 36px -18px rgba(0,0,0,.95),inset 1px 0 0 rgba(255,255,255,.06)}
.rv-quote-handle:hover::before,.rv-quote-handle:focus-visible::before{opacity:1}
.rv-quote-handle:focus-visible{outline:2px solid var(--sage);outline-offset:3px}
.rv-quote-handle:active{transform:translate(-1px,-50%)}
/* The phone's whole control is this mark; the pointer build keeps the chevrons and the label
   instead, so it is off here and switched on in assistant.css's mobile breakpoint.
   The two strokes are deliberately unequal. A matched pair of chevrons is how a panel says
   "collapse me", and that is the reading this control has to avoid - so the leading stroke is
   full weight and fully lit while the trailing one is thinner and half lit. The eye reads the
   gradient as movement in the direction of the points, which is the direction the drawer
   actually arrives from. */
.rv-qh-mark{display:none;flex:none;line-height:0;color:var(--sage)}
.rv-qh-mark svg{width:var(--ico-lg,20px);height:var(--ico-lg,20px);display:block;overflow:visible}
.rv-qh-lead{opacity:1}
.rv-qh-trail{opacity:.42;stroke-width:1.35}
/* The pin already travels on press; the trailing stroke travels a little further, so the two
   separate for an instant and the mark reads as being pulled open rather than pressed. */
.rv-qh-lead,.rv-qh-trail{transition:transform var(--t-ctl,.28s) var(--ease,cubic-bezier(.22,1,.36,1)),
  opacity var(--t-ctl,.28s) ease}
.rv-quote-handle:active .rv-qh-lead{transform:translateX(-1px)}
.rv-quote-handle:active .rv-qh-trail{transform:translateX(-2px);opacity:.65}
@media(prefers-reduced-motion:reduce){.rv-qh-lead,.rv-qh-trail{transition:none}
  .rv-quote-handle:active .rv-qh-lead,.rv-quote-handle:active .rv-qh-trail{transform:none}}
/* paired chevrons, drawn not typed - a text arrow is the thing the owner did not want */
.rv-qh-chev{display:flex;align-items:center;gap:3px;flex:none}
.rv-qh-chev i{display:block;width:5px;height:5px;border-left:1.5px solid var(--sage);
  border-bottom:1.5px solid var(--sage);transform:rotate(45deg);
  transition:transform var(--t-ctl,.28s) var(--ease,cubic-bezier(.22,1,.36,1))}
.rv-quote-handle:hover .rv-qh-chev i:first-child{transform:rotate(45deg) translate(1.5px,-1.5px)}
.rv-quote-handle:hover .rv-qh-chev i:last-child{transform:rotate(45deg) translate(2.5px,-2.5px)}
/* The label is present for the pointer that is about to use it and for assistive technology, but
   NOT permanently. With it always on, every page carried three simultaneous "Get a quote" controls
   - header pill, in-page CTA and this tab - which is exactly the repetition the owner rejected.
   At rest the tab is a fabricated mark on the edge; approach it and it names itself. */
/* The label used to be fully collapsed at rest (max-height:0), which left the tab showing two
   5px chevrons and nothing else — and a pair of chevrons on a panel edge is the universal mark
   for "collapse this panel", so the control was reading as a debug widget rather than as the way
   to ask for a price. A control the visitor has to hover to identify is not restraint.
   It is legible at rest and simply brightens on approach; the concern about three shouting
   "Get a quote" controls is answered by keeping it quiet, not by making it anonymous. */
.rv-qh-lbl{writing-mode:vertical-rl;transform:rotate(180deg);
  font-family:var(--font-display);font-size:calc(10px * var(--sz-label,1));
  font-weight:var(--w-eyebrow,500);letter-spacing:var(--tr-label,.26em);text-transform:uppercase;
  color:var(--text-rev);white-space:nowrap;line-height:1;
  max-height:180px;opacity:.6;overflow:hidden;
  transition:opacity var(--t-ctl,.28s) ease,color var(--t-ctl,.28s) ease}
.rv-quote-handle:hover .rv-qh-lbl,
.rv-quote-handle:focus-visible .rv-qh-lbl{opacity:1;color:var(--sage)}
@media(prefers-reduced-motion:reduce){ .rv-qh-lbl{transition:none} }
body.rv-locked .rv-quote-handle{opacity:0;pointer-events:none;transform:translate(24px,-50%)}
/* Below the drawer's breakpoint the panel is a bottom sheet with its own grab handle, and the
   in-page triggers are the affordance. One handle per screen. */
/* The tab is no longer desktop-only. It was hidden here and then re-created below 761px as a
   filled bottom-right pill in assistant.css, which meant the phone got a different control
   wearing the same class - and the loudest one on the page. The phone now gets THIS control,
   sized for touch in assistant.css's own breakpoint. The reasoning that put the rule here (the
   drawer is a bottom sheet on a phone and already carries a grab handle) still holds for the
   drawer; it was never an argument for replacing the opener. */
@media(prefers-reduced-motion:reduce){
  .rv-quote-handle,.rv-qh-chev i{transition:none}
  .rv-quote-handle:hover{transform:translateY(-50%)}
}

/* ============================================================================ budget disclosure
   The one answer a visitor is least ready to give, and the one that cost the most screen. Closed,
   it is a single line that still states its own answer; open, it is the same five radios. Native
   <details>, so the keyboard, the accessibility tree and the open/close semantics are the
   platform's rather than something to re-implement and get wrong. */
.rv-disc{border:1px solid var(--border);border-radius:var(--r-surface,12px);background:rgba(255,255,255,.012)}
.rv-disc summary{display:flex;align-items:center;gap:10px;padding:12px 14px;cursor:pointer;
  list-style:none;min-height:44px;border-radius:var(--r-control,2px)}
.rv-disc summary::-webkit-details-marker{display:none}
.rv-disc summary:focus-visible{outline:2px solid var(--sage);outline-offset:2px}
.rv-disc summary .dk{font-family:var(--font-display);font-size:11.5px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--faint)}
.rv-disc summary .dv{margin-left:auto;font-size:13.5px;color:var(--faint)}
.rv-disc summary .dv.set{color:var(--sage)}
.rv-disc summary svg{width:15px;height:15px;flex:none;color:var(--faint);
  transition:transform var(--t-ctl,.28s) var(--ease,cubic-bezier(.22,1,.36,1))}
.rv-disc[open] summary svg{transform:rotate(180deg)}
.rv-disc[open] summary{border-bottom:1px solid var(--div);border-radius:10px 10px 0 0}
.rv-disc-body{padding:12px 14px 14px}
@media(prefers-reduced-motion:reduce){ .rv-disc summary svg{transition:none} }

/* International budget composer. Currency is a stable code and the two native range controls
   remain fully keyboard-operable. Values are stored verbatim; the UI never implies a live FX
   conversion. */
.rv-budget-block{padding:14px;border:1px solid var(--border);border-radius:12px;background:rgba(255,255,255,.012)}
.rv-budget-top{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:12px}.rv-budget-top>label{margin:0}.rv-currency{width:104px;min-height:40px;padding:8px 30px 8px 12px;font-family:var(--font-mono);letter-spacing:.08em}
.rv-budget-block .rv-disc{border-radius:var(--r-surface,12px)}.rv-range{margin-top:14px;padding-top:13px;border-top:1px solid var(--div)}.rv-range-head{display:flex;justify-content:space-between;gap:12px;align-items:baseline;margin-bottom:10px}.rv-range-head>span{font-family:var(--font-display);font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--faint)}.rv-range output{font-family:var(--font-mono);font-size:11px;color:var(--sage);font-variant-numeric:tabular-nums;text-align:right}
.rv-range-track{display:grid;gap:5px}.rv-range-track input[type=range]{width:100%;height:24px;margin:0;accent-color:var(--sage);cursor:pointer;background:transparent}.rv-range-track input[type=range]:focus-visible{outline:2px solid var(--sage);outline-offset:2px;border-radius:5px}.rv-budget-block>.rv-fld-help{margin-top:10px}

/* A drawer that scrolls needs its own scrollbar to be quiet rather than absent - absent is how a
   visitor fails to notice there is more below. Thin, on the panel only. */
.rv-quote{scrollbar-width:thin;scrollbar-color:var(--border-ctl) transparent}
.rv-quote::-webkit-scrollbar{width:8px}
.rv-quote::-webkit-scrollbar-track{background:transparent}
.rv-quote::-webkit-scrollbar-thumb{background:var(--border-ctl);border-radius:4px;
  border:2px solid transparent;background-clip:content-box}
.rv-quote::-webkit-scrollbar-thumb:hover{background:var(--chip-bd);background-clip:content-box}

/* ------------------------------------------------------------------ budget range
   One control, not two. The two native inputs remain - they are what makes the range keyboard
   operable and correctly announced - but they are made transparent and stacked over a single
   drawn track that paints the SELECTED SPAN between the handles. Only the thumbs take pointer
   events, so both handles stay grabbable even where they overlap. */
.rv-range-track{position:relative;display:block;height:38px;padding:0;margin-top:2px}
.rv-range-track::before{content:'';position:absolute;left:0;right:0;top:50%;height:3px;
  transform:translateY(-50%);border-radius:3px;background:var(--border-ctl)}
.rv-range-track::after{content:'';position:absolute;top:50%;height:3px;transform:translateY(-50%);
  left:var(--lo,0%);right:calc(100% - var(--hi,100%));border-radius:3px;
  background:linear-gradient(90deg,var(--sage),var(--sage-br,#C2DCC6));
  box-shadow:0 0 10px rgba(171,201,176,.45)}
.rv-range-track input[type=range]{position:absolute;left:0;top:0;width:100%;height:38px;margin:0;
  background:none;-webkit-appearance:none;appearance:none;pointer-events:none}
.rv-range-track input[type=range]::-webkit-slider-runnable-track{background:transparent;height:38px}
.rv-range-track input[type=range]::-moz-range-track{background:transparent;height:38px}
.rv-range-track input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;pointer-events:auto;
  width:20px;height:20px;margin-top:9px;border-radius:50%;cursor:grab;
  background:var(--surface-1);border:2px solid var(--sage);
  box-shadow:0 2px 10px rgba(0,0,0,.55),0 0 0 4px rgba(171,201,176,.12);
  transition:box-shadow .2s var(--ease),transform .2s var(--ease)}
.rv-range-track input[type=range]::-moz-range-thumb{pointer-events:auto;
  width:20px;height:20px;border-radius:50%;cursor:grab;
  background:var(--surface-1);border:2px solid var(--sage);
  box-shadow:0 2px 10px rgba(0,0,0,.55),0 0 0 4px rgba(171,201,176,.12)}
.rv-range-track input[type=range]:hover::-webkit-slider-thumb{transform:scale(1.08)}
.rv-range-track input[type=range]:active::-webkit-slider-thumb{cursor:grabbing}
.rv-range-track input[type=range]:focus-visible::-webkit-slider-thumb{
  box-shadow:0 0 0 4px rgba(171,201,176,.35),0 2px 10px rgba(0,0,0,.55)}
.rv-range-track input[type=range]:focus-visible{outline:none}
.rv-range-track input[type=range]:focus-visible::-moz-range-thumb{
  box-shadow:0 0 0 4px rgba(171,201,176,.35)}
@media(prefers-reduced-motion:reduce){
  .rv-range-track input[type=range]::-webkit-slider-thumb{transition:none}
}

/* ==============================================================================================
   QUOTE DRAWER — three-part shell
   The panel itself was the scroll container, so the title, the close control and the SEND BUTTON
   all scrolled away: measured at 1440x900 the submit sat 200px below the fold and was reachable
   only by scrolling to the very end of a 1288px column. A lead-capture surface must never hide
   its own primary action.

   Head and actions are now fixed; only the fields scroll. That also removes the overlay/reserve
   dance (--rvq-foot), because the bar occupies real layout instead of floating over it.
   ============================================================================================== */
.rv-quote{overflow:hidden;display:flex;flex-direction:column}
.rv-quote-head{flex:0 0 auto;position:relative;z-index:2;
  border-bottom:1px solid var(--border-2);background:var(--surface-1)}
.rv-quote-body{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  scrollbar-width:thin;scrollbar-color:var(--border-ctl) transparent;
  padding-bottom:18px!important}
.rv-quote-body::-webkit-scrollbar{width:8px}
.rv-quote-body::-webkit-scrollbar-thumb{background:var(--border-ctl);border-radius:4px;
  border:2px solid transparent;background-clip:content-box}
.rv-quote-foot{display:block;flex:0 0 auto;position:relative;z-index:2;
  border-top:1px solid var(--border-2);background:var(--surface-1);
  box-shadow:0 -14px 26px -22px rgba(0,0,0,.9)}
/* the bar is in flow now, so the body must not reserve space for it as well */
.rv-quote-body{--rvq-foot:0px}

/* ------------------------------------------------------------ off-brand native controls
   A native <select> renders with the operating system's own arrow and focus ring, which is the
   "blue/native-looking" control that broke the drawer's surface. Same box, same type control -
   Ravenor's own chrome. */
/* The arrow is a 1px sage chevron, drawn the same way every other mark in the product is drawn.
   It was a pair of 45deg gradients making a SOLID grey triangle - which removed the operating
   system's arrow but replaced it with a filled wedge that belongs to no part of this design. */
.rv-quote select.rv-input{-webkit-appearance:none;appearance:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%23ABC9B0' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-position:right 13px center;background-size:11px 7px;background-repeat:no-repeat;
  padding-right:38px;cursor:pointer}
/* The option list is painted by the OS and cannot be styled beyond this; give it the panel's
   colours so it is at least not a white sheet dropping out of a dark drawer. */
.rv-quote select.rv-input option{background:var(--modal);color:var(--text)}
.rv-quote select.rv-input:focus-visible{outline:2px solid var(--sage);outline-offset:2px}
.rv-quote select.rv-input:hover{border-color:var(--chip-bd)}

/* checkbox and radio in Ravenor's own language rather than the platform's */
.rv-quote input[type=checkbox],.rv-quote input[type=radio]{-webkit-appearance:none;appearance:none;
  width:18px;height:18px;flex:none;margin:0;border:1.5px solid var(--border-ctl);
  background:var(--surface-1);cursor:pointer;position:relative;
  transition:border-color var(--t-ctl),background var(--t-ctl),box-shadow var(--t-ctl)}
.rv-quote input[type=checkbox]{border-radius:var(--r-control,2px)}
.rv-quote input[type=radio]{border-radius:50%}
.rv-quote input[type=checkbox]:checked,.rv-quote input[type=radio]:checked{
  border-color:var(--sage);background:var(--sage)}
.rv-quote input[type=checkbox]:checked::after{content:'';position:absolute;left:5px;top:1.5px;
  width:5px;height:10px;border:solid var(--on-sage);border-width:0 2px 2px 0;transform:rotate(42deg)}
.rv-quote input[type=radio]:checked::after{content:'';position:absolute;inset:4px;border-radius:50%;
  background:var(--on-sage)}
.rv-quote input[type=checkbox]:focus-visible,.rv-quote input[type=radio]:focus-visible{
  outline:2px solid var(--sage);outline-offset:2px}
.rv-quote input[type=checkbox]:hover,.rv-quote input[type=radio]:hover{border-color:var(--sage)}
@media(prefers-reduced-motion:reduce){
  .rv-quote input[type=checkbox],.rv-quote input[type=radio]{transition:none}
}

/* The choice groups do NOT use that control. `.rv-seg` is the visually-hidden-input + styled-label
   pattern: the label paints the mark via ::before and the input stays off-screen carrying the
   semantics and the focus ring. The generic rule above sets `position:relative` and a real 18px
   box, and being the more specific selector it was winning inside .rv-seg too - which put the
   radios back IN FLOW. In a two-column grid each pair then ate a whole row, so "What do you need"
   rendered one option per line in the right-hand column with the left column empty, and every
   option carried two circles: the real input and the label's mark.
   Restated here at a specificity that holds, rather than by loosening the rule above. */
.rv-quote .rv-seg input[type=radio]{position:absolute;opacity:0;width:1px;height:1px;
  border:0;background:none;margin:0;pointer-events:none}

/* ---------------------------------------------------------------- drawer density
   With the extra width the form stops being one long single-file column: paired fields sit side
   by side, which removes roughly a third of the scroll without shrinking anything. Container
   queries so it responds to the DRAWER's width, not the window's - the same panel is a bottom
   sheet on a phone and a wide drawer on a desktop. */
.rv-quote-body{container-type:inline-size;container-name:rvq}
@container rvq (min-width:520px){
  .rv-quote-body .fld-pair{display:grid;grid-template-columns:1fr 1fr;gap:0 18px;align-items:start}
  .rv-quote-body .rv-budget-top{gap:24px}
}
/* sections read as grouped panels rather than a run of labels */
.q-section-title{display:flex;align-items:baseline;gap:12px;margin:30px 0 16px;
  padding-top:22px;border-top:1px solid var(--border-2)}
.q-section-title:first-of-type{margin-top:4px;padding-top:0;border-top:0}
.q-section-title small{margin-left:auto;font-size:10.5px;letter-spacing:.06em;
  color:var(--faint);text-transform:none}
.rv-budget-block{padding:18px}
.rv-quote-body .fld{margin-bottom:16px}
.rv-quote .rv-input{transition:border-color var(--t-ctl),box-shadow var(--t-ctl),background var(--t-ctl)}
.rv-quote .rv-input:hover{border-color:var(--chip-bd)}
.rv-quote .rv-input:focus-visible{outline:none;border-color:var(--sage);
  box-shadow:0 0 0 3px rgba(171,201,176,.16)}
/* the live range value gets tabular figures so a rolling number does not jitter its own width */
#rvQRangeOut{font-variant-numeric:tabular-nums lining-nums}

/* ---------------------------------------------------------------- quote steps
   The form is one <form> with every field still in it; these rules only control what is visible.
   Chrome is deliberately small - a row of marks and two buttons - because a big wizard frame is
   what makes a short enquiry feel long. */
.q-step[hidden]{display:none}
.q-step{animation:q-step-in .28s var(--ease) both}
@keyframes q-step-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
.q-nav{display:flex;align-items:center;gap:16px;margin-bottom:14px}
.q-dots{display:flex;gap:7px;flex:1;min-width:0}
.q-dots i{width:26px;height:3px;border-radius:2px;background:var(--border-ctl);flex:none;
  cursor:pointer;transition:background .3s var(--ease)}
.q-dots i.done{background:rgba(171,201,176,.55)}
.q-dots i.on{background:var(--sage)}
.q-nav-btns{display:flex;gap:10px;flex:none}
.q-nav .rv-btn{min-height:42px;padding:10px 18px;font-size:13px}
.q-back{background:transparent;border:1px solid var(--border-ctl);color:var(--text)}
.q-next{min-width:128px;justify-content:center}
.q-review-list{display:grid;grid-template-columns:auto 1fr;gap:10px 18px;margin:0;
  font-size:13.5px;line-height:1.6}
.q-review-list dt{color:var(--faint);font-family:var(--font-display);font-size:11px;
  letter-spacing:.06em;text-transform:uppercase;align-self:center}
.q-review-list dd{margin:0;color:var(--text-hi);overflow-wrap:anywhere}
.q-review-empty{grid-column:1/-1;color:var(--muted);text-transform:none;letter-spacing:0;
  font-family:var(--font-body);font-size:13.5px}
@media(max-width:600px){
  .q-nav{gap:12px;flex-wrap:wrap}
  .q-dots{order:-1;width:100%;flex:none}
  .q-nav-btns{width:100%}
  .q-nav-btns .rv-btn{flex:1}
}
@media(prefers-reduced-motion:reduce){.q-step{animation:none}.q-dots i{transition:none}}

/* ============================================================ THE OPTIONAL HALF OF SECTION 02
   The budget controls and the project note now sit inside a <details>. Native, so the keyboard,
   the screen reader and find-in-page all work without a line of script, and the open/closed state
   is the element's own rather than a class someone has to remember to keep in sync.

   The summary is written to read like a sentence the visitor might say, not like a UI affordance:
   a quiet line with a chevron, no button chrome, nothing competing with Send request below it.
   It sits on a hairline because it is a boundary between "what we need" and "what helps". */
.rv-qmore{border-top:1px solid var(--div);margin-top:6px}
.rv-qmore-sum{display:flex;align-items:center;gap:12px;min-height:44px;padding:10px 0;
  cursor:pointer;list-style:none;color:var(--muted);font-size:13px;line-height:1.5;
  transition:color var(--t-ctl) var(--ease)}
.rv-qmore-sum::-webkit-details-marker{display:none}          /* Safari draws its own triangle */
.rv-qmore-sum:hover{color:var(--text-hi)}
.rv-qmore-sum:focus-visible{outline:2px solid var(--sage);outline-offset:3px;border-radius:6px}
.rv-qmore-txt{flex:1 1 auto;min-width:0}
.rv-qmore-mark{flex:none;display:inline-flex;color:var(--faint);
  transition:transform var(--t-ctl) var(--ease),color var(--t-ctl) var(--ease)}
.rv-qmore-mark svg{width:var(--ico-sm,16px);height:var(--ico-sm,16px);display:block}
.rv-qmore[open] .rv-qmore-mark{transform:rotate(180deg);color:var(--sage)}
.rv-qmore-sum:hover .rv-qmore-mark{color:var(--sage)}
.rv-qmore[open]>.fld:first-of-type{margin-top:4px}
@media(prefers-reduced-motion:reduce){.rv-qmore-mark{transition:none}}

/* The budget block was a bordered, tinted card - the single heaviest object in the panel, around
   the single least-required question. Inside a disclosure that frame is doing nothing except
   drawing a box around a box, so it goes; the questions are separated by space and one hairline,
   which is the register the rest of the panel already uses. */
.rv-budget-block>.rv-fld-help{margin-top:12px}

/* The note field: a real writing surface rather than a thin input stretched taller, and without
   the corner grip, which is the one piece of raw browser chrome left in the panel. */
.rv-quote .fld textarea.rv-input{min-height:132px;resize:none;line-height:1.65;padding-top:12px}

/* Verification shares the drawer scroll/focus owner; never a second modal. */
.rv-verification[hidden]{display:none}
.rv-verification{padding:16px 0;border-block:1px solid var(--line,rgba(171,201,176,.2));margin:12px 0}
.rv-verification h3,.rv-verify-channel label{display:block;margin:0 0 10px}
.rv-verify-channel{margin-block:12px}
.rv-verify-channel .rv-input{margin-bottom:10px}
.rv-verify-channel button{margin:0 8px 8px 0;min-height:44px}
.rv-verification p{font-size:13px;line-height:1.5;margin:8px 0}
#rvQVerifyEmail{margin-top:8px;min-height:36px}

/* ----------------------------------------------------------------------------------------------
   THE VERIFICATION STEP, as a step rather than a form fragment.

   Somebody is here for thirty seconds with their mail app in the other hand. Everything on screen
   has to answer one of four questions — where did it go, how long is it good for, where do I type
   it, when can I ask for another — and nothing else should be competing for attention. So: one
   line per answer, in that order, and no card, no icon, no progress bar.

   The two clocks are styled the SAME because they are the same kind of fact, and they are on
   separate lines because they count different things. `is-out` is the only state that changes
   colour, because expiry is the only one that changes what you can do. */
.qv-why{color:var(--muted,#8b938c);font-size:12px;margin:2px 0 14px}
.qv-card{margin-block:16px}
.qv-card+.qv-card{padding-top:16px;border-top:1px solid var(--line,rgba(171,201,176,.2))}
.qv-h{margin:0 0 6px;font-size:15px;font-weight:600;letter-spacing:.01em}
/* `Step 1 of 2`, under the heading of the card it belongs to. Quiet: it is orientation,
   not instruction, and the instruction is the field below it. */
.qv-step{margin:0 0 10px;font-size:11.5px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--muted,#8b938c)}
.qv-dest{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;margin:0 0 2px!important}
.qv-dest span{overflow-wrap:anywhere}
/* A text button, not a second primary action: changing the address is a way out, not the way on. */
.qv-edit{background:none;border:0;padding:0;margin:0!important;min-height:0!important;
  font:inherit;font-size:12px;color:var(--sage,#ABC9B0);text-decoration:underline;
  text-underline-offset:3px;cursor:pointer}
.qv-edit:hover{text-decoration-thickness:2px}
.qv-edit:focus-visible{outline:2px solid var(--sage,#ABC9B0);outline-offset:3px;border-radius:3px}
.qv-valid{margin:0 0 12px!important;font-variant-numeric:tabular-nums;
  color:var(--muted,#8b938c);font-size:12px;min-height:1.2em}
.qv-valid.is-out{color:#e2b57a}
.qv-label{font-size:12px;color:var(--muted,#8b938c)}
.qv-row{display:flex;gap:10px;align-items:stretch;flex-wrap:wrap}
/* Wide enough for six digits and no wider. A full-width box for a six-character value reads as
   "write a sentence here". */
/* 13ch, not 10. Six digits at `.28em` of tracking need more than ten characters of box, and at
   10ch the SIXTH digit was clipped off the right edge — so somebody typing the code they were
   sent could see only five of it and had no way to check their own typing. Tracking exists so
   the digits can be read back in pairs; the box has to be wide enough to hold what it spaces. */
.qv-input{flex:0 0 13ch;text-align:center;letter-spacing:.28em;font-variant-numeric:tabular-nums;
  font-size:18px;margin-bottom:0!important;padding-inline:10px;
  /* The tracking adds a trailing gap after the last digit, which pushes the run off-centre. */
  text-indent:.28em}
.qv-check{margin:0!important}
.qv-check[aria-busy=true]{opacity:.7;cursor:progress}
/* The resend control is the cooldown display. It is text until it becomes pressable, so the
   countdown never looks like a button that is refusing to work. */
.qv-resend{display:inline-block;background:none;border:0;padding:6px 0;margin:10px 0 0!important;
  min-height:32px!important;font:inherit;font-size:12px;color:var(--sage,#ABC9B0);
  text-decoration:underline;text-underline-offset:3px;cursor:pointer;
  font-variant-numeric:tabular-nums}
.qv-resend:disabled{color:var(--muted,#8b938c);text-decoration:none;cursor:default}
.qv-resend:focus-visible{outline:2px solid var(--sage,#ABC9B0);outline-offset:3px;border-radius:3px}
.qv-msg{margin:10px 0 0!important;font-size:12.5px;min-height:1.2em}
.qv-msg.is-bad{color:#e2947a}
.qv-status.is-bad{color:#e2947a}
.qv-card.is-done .qv-msg{color:var(--sage,#ABC9B0)}
/* A FINISHED CARD HAS NOTHING LEFT TO PRESS. The success path disables the field and the button
   without resetting the button's label, so a completed card was headed by the word "Checking…"
   above a dead input. Removing the row leaves the heading, where the code went, and the line
   saying it was accepted — which is all that is still true about it. */
.qv-card.is-done .qv-row,
.qv-card.is-done .qv-label,
.qv-card.is-done .qv-resend,
.qv-card.is-done .qv-valid{display:none}
.qv-cancel{margin-top:6px}
@media (max-width:420px){
  /* Thumb reach: the confirm button goes under the field rather than beside it, at full width,
     because at 360px the two side by side leave a 90px button nobody can hit reliably. */
  .qv-input{flex:1 1 100%}
  .qv-check{width:100%}
}
@media (prefers-reduced-motion:reduce){.qv-check{transition:none}}

/* ==============================================================================================
   THE ATTACHMENT CONTROL — one look, two surfaces
   ----------------------------------------------------------------------------------------------
   Lives here rather than in a file of its own because both callers already load this stylesheet:
   the quote drawer on every page, and the careers form. A third file would mean a third place a
   future change has to reach, and the whole point of `upload.js` being one component is that its
   appearance is one thing too.

   THE NATIVE INPUT IS HIDDEN THE CAREFUL WAY. `display:none` on a file input removes it from the
   accessibility tree AND from the tab order, so the <label> that triggers it becomes unreachable
   by keyboard. Clipping it to a 1px box keeps it focusable, keeps the label's `for=` association
   alive, and keeps the browser's own file dialog as the thing that opens - which is the only
   correct answer, because no script may open a file picker the visitor did not ask for.
   ============================================================================================== */
.rv-up{display:grid;gap:10px}
.rv-up-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;flex-wrap:wrap}
.rv-up-title{font-size:12px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  color:var(--text-hi)}
.rv-up-hint{font-size:11.5px;line-height:1.45;color:var(--faint)}
.rv-up-input{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
/* The label IS the button, so it takes the button's own focus ring rather than inventing one.
   `:focus-visible` on the input, styled through the sibling label, is what makes the keyboard
   path visible - a label cannot receive focus itself. */
.rv-up-pick{cursor:pointer;justify-self:start}
.rv-up-input:focus-visible+.rv-up-list,
.rv-up-input:focus-visible~.rv-up-list{outline:none}
.rv-up-pick:has(+.rv-up-input:focus-visible),
.rv-up-input:focus-visible{outline:none}
.rv-up:focus-within .rv-up-pick{border-color:var(--sage);
  box-shadow:0 0 0 3px rgba(171,201,176,.16)}
/* At the limit the control stops responding and SAYS SO, rather than disappearing. A control
   that vanishes moves the page under the visitor's hand. */
.rv-up-pick.is-disabled{opacity:.5;pointer-events:none}

.rv-up-list{list-style:none;margin:0;padding:0;display:grid;gap:6px}
.rv-up-list:empty{display:none}
.rv-up-item{display:flex;align-items:center;gap:10px;min-height:40px;
  padding:8px 8px 8px 12px;background:var(--inset);
  border:1px solid var(--border-field);border-radius:var(--r-control,2px)}
.rv-up-name{flex:1 1 auto;font-size:13px;color:var(--text);
  /* A 200-character filename must not push the remove control off the row. */
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.rv-up-size{flex:0 0 auto;font-size:11.5px;color:var(--faint);font-variant-numeric:tabular-nums}
.rv-up-x{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  width:32px;height:32px;border-radius:var(--r-control,2px);color:var(--faint);
  background:transparent;border:1px solid transparent;
  transition:color var(--t-fast),background var(--t-fast),border-color var(--t-fast)}
.rv-up-x svg{width:var(--ico-sm,16px);height:var(--ico-sm,16px)}
.rv-up-x:hover{color:var(--danger);background:rgba(207,106,106,.08);
  border-color:rgba(207,106,106,.3)}
.rv-up-x:focus-visible{outline:2px solid var(--sage);outline-offset:2px}
/* 44px on a coarse pointer, like every other control the shell already raises. A 32px remove
   button is exactly the mis-tap that deletes the photograph somebody just waited to upload. */
@media (pointer:coarse){.rv-up-x{width:44px;height:44px}}
.rv-up-item.is-busy .rv-up-name{color:var(--faint)}
.rv-up-item.is-busy{border-style:dashed}
.rv-up-err:empty{display:none}
/* The shared error rule is display:none until `.on` is added, which is right for a line the
   script toggles. This one is emptied rather than toggled, so it states its own visibility. */
.rv-up-err:not(:empty){display:block}

/* ---------------------------------------------------------------------------- refused files
   A refused file keeps its own row, with the reason ON the row. The old behaviour was one shared
   error line that the next successful upload cleared — so attaching five photographs with the
   second refused left the visitor with four files, no message, and nothing to act on.

   Muted amber, not red. The file did not arrive; nothing is broken and nothing is lost, and the
   retry beside it is the point of the row. */
.rv-up-item.is-bad{border-color:rgba(226,181,122,.4);background:rgba(226,181,122,.05)}
/* THE REASON WAS MEANT TO TAKE ITS OWN LINE. `.rv-up-why` is `flex:1 1 100%;order:3`, which is
   the shape of a flex item that wraps to the next row - and the row had no `flex-wrap`, so it
   never did. Everything stayed on one line and the only item that could shrink was the FILENAME,
   which collapsed to `drawi…` beside a full-width reason. On a refusal the name is the first
   thing you need: which of the six photographs did not arrive. Wrapped here rather than on every
   row, because a good row has nothing to wrap and its ellipsis is correct. */
.rv-up-item.is-bad{flex-wrap:wrap}
.rv-up-item.is-bad .rv-up-name{color:#e2b57a;flex:1 1 auto;min-width:9em}
.rv-up-why{flex:1 1 100%;order:3;font-size:11.5px;line-height:1.55;color:var(--muted)}
.rv-up-retry{flex:0 0 auto;background:none;border:0;padding:4px 2px;font:inherit;font-size:12px;
  color:var(--sage);text-decoration:underline;text-underline-offset:3px;cursor:pointer;
  min-height:32px}
.rv-up-retry:hover{text-decoration-thickness:2px}
.rv-up-retry:focus-visible{outline:2px solid var(--sage);outline-offset:2px;border-radius:3px}
@media (pointer:coarse){.rv-up-retry{min-height:44px;padding-inline:8px}}

/* ---------------------------------------------------------------------------- drop target
   Additive to the button, never instead of it: dropping is a mouse convenience and the label is
   the only route a keyboard has. The hint sits beside the button and disappears where a drop
   cannot happen, rather than telling a phone to drag a file onto it. */
.rv-up-drop-hint{font-size:11.5px;color:var(--muted);align-self:center}
@media (pointer:coarse){.rv-up-drop-hint{display:none}}
.rv-up.is-dropping{outline:2px dashed var(--sage);outline-offset:6px;border-radius:8px}
.rv-up.is-dropping .rv-up-drop-hint{color:var(--sage)}


/* What actually arrived. Quiet when everything did, and marked when it did not - a partial
   result is the one case the visitor has to act on, so it is the one that changes colour. */
.q-files-note{margin:10px 0 0;font-size:13px;line-height:1.55;color:var(--faint)}
.q-files-note:empty{display:none}
.q-files-note.is-partial{color:var(--danger)}
