/* ==========================================================================
   VARNERDOORS — the in-house SalesRabbit.
   Mobile-first: this runs on a rep's phone, in the sun, one-handed.
   ========================================================================== */

*{ box-sizing:border-box }
[hidden]{ display:none !important }

/* This app is phone-only. The body is PINNED rather than scrolled: iOS then
   cannot rubber-band the page, cannot pull-to-refresh (a reload would lose
   the map position mid-street), and the layout stops resizing every time
   Safari's toolbars slide in and out. */
html,body{ height:100%; overflow:hidden; overscroll-behavior:none }
body{
  position:fixed; inset:0;
  margin:0; background:var(--vw-black); color:#fff;
  font:400 15px/1.45 Inter,system-ui,-apple-system,"Segoe UI",Helvetica,Arial,sans-serif;
  display:flex; flex-direction:column;
  /* Sunlight and gloves: no grey flash on tap, no accidental text selection
     when a thumb rests, no long-press "copy/look up" popover over the map. */
  -webkit-tap-highlight-color:transparent;
  -webkit-touch-callout:none;
  -webkit-user-select:none; user-select:none;
}

/* Words a rep may actually need to copy stay selectable. */
.history,.sheet input,.sheet textarea{ -webkit-user-select:text; user-select:text }

/* ---- The LIVE tab ------------------------------------------------------
   Owners only, revealed by knock.js once /api/auth/me confirms it.

   Deliberately shaped like a recording indicator rather than another icon
   button: a red dot and the word LIVE is a thing every person already reads
   instantly, and this is the one place in the app that opens something
   actually happening right now. The dot breathes rather than blinks — a hard
   blink in a header is a nag, and this sits on screen all day. */
.vw-live{
  position:relative; display:flex; align-items:center; gap:6px; flex:none;
  height:34px; padding:0 11px 0 9px; margin-right:6px;
  border:1px solid rgba(216,12,17,.5); border-radius:99px;
  background:rgba(216,12,17,.1);
  color:#fff; text-decoration:none;
  font:900 10.5px/1 Inter,system-ui,sans-serif; letter-spacing:.14em;
}
.vw-live .rec{
  width:8px; height:8px; border-radius:50%; flex:none;
  background:var(--vw-red);
  animation:recPulse 2s ease-in-out infinite;
}
@keyframes recPulse{
  0%,100%{ opacity:1;   box-shadow:0 0 0 0 rgba(216,12,17,.55) }
  50%    { opacity:.45; box-shadow:0 0 0 6px rgba(216,12,17,0) }
}
/* A real 44px target under a 34px pill, per the Toolbox touch rule. */
.vw-live::before{
  content:""; position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
  width:100%; height:44px; min-width:44px;
}
.vw-live:active{ background:rgba(216,12,17,.22) }

/* A phone header is tight. Keep the dot, drop the word — the pulsing red dot
   is the signal; the letters are only ever confirmation. */
@media (max-width:430px){
  .vw-live{ font-size:0; letter-spacing:0; gap:0; padding:0 10px; min-width:34px;
            justify-content:center }
}

@media (prefers-reduced-motion:reduce){
  .vw-live .rec{ animation:none }
}

/* Owners only — revealed by knock.js once /api/auth/me confirms it. 44px of
   target around a small glyph, per the Toolbox touch rule. */
.vw-dash{
  position:relative; display:flex; align-items:center; justify-content:center;
  width:34px; height:34px; margin-right:2px; flex:none;
  border:1px solid rgba(216,12,17,.5); border-radius:8px;
  color:var(--vw-red); text-decoration:none;
}
.vw-dash svg{ width:17px; height:17px }
.vw-dash::before{
  content:""; position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
  width:44px; height:44px;
}
.vw-home{
  color:rgba(255,255,255,.75); text-decoration:none; flex:none;
  font:700 12px/1 Inter,system-ui,sans-serif; letter-spacing:.04em;
  white-space:nowrap;
  /* A real 44px target, not a 12px word. */
  padding:14px 10px; margin:-14px -10px;
}
@media (max-width:400px){
  /* Keep the arrow, drop the word: the lockup itself already goes home. */
  .vw-home{ font-size:0; min-width:44px; text-align:right }
  .vw-home::before{ content:"\2190"; font-size:19px; font-weight:700 }
}
/* On a narrow phone the header runs out of room once an owner has both the
   LIVE tab and the dashboard in it — measured at 379px of content in a 375px
   bar, which is a wrap or a clipped control.

   The TOOLBOX badge is what gives way. Its whole job is "this is a crew tool,
   not the customer site", and on this screen the black bar, the red rule and
   the word VARNERDOORS have already said that three times over. The badge
   earns its place on a laptop; it does not earn 69px on a phone. */
@media (max-width:430px){
  .vw-toolbox{ display:none }
}

/* One line, always. The lockup keeps its space; the back link gives way. */
.vw-bar__in{ flex-wrap:nowrap }
.vw-mark{ flex:none; white-space:nowrap }
.vw-toolbox{ flex:none }

/* The shared bar knows nothing about notches; this page does. */
.vw-bar{ padding-top:env(safe-area-inset-top) }
.vw-bar__in{ padding-left:max(16px,env(safe-area-inset-left));
             padding-right:max(16px,env(safe-area-inset-right)) }

/* ------------------------------------------------------------- filters --- */
.filters{
  display:flex; gap:6px; padding:8px 10px; overflow-x:auto;
  touch-action:pan-x;   /* swipe the chips sideways; never zoom them */
  background:var(--vw-carbon); border-bottom:1px solid var(--vw-line);
  -webkit-overflow-scrolling:touch; scrollbar-width:none; flex:none;
  padding-left:max(10px,env(safe-area-inset-left));
  padding-right:max(10px,env(safe-area-inset-right));
}
.filters::-webkit-scrollbar{ display:none }
.fchip{
  flex:none; display:flex; align-items:center; gap:7px;
  font:700 13px/1 Inter,system-ui,sans-serif; color:#fff;
  background:#222; border:1px solid var(--vw-line); border-radius:99px;
  padding:12px 14px; cursor:pointer; white-space:nowrap;
  min-height:44px;                     /* the tap-target floor */
}
.fchip .dot{ width:10px; height:10px; border-radius:50%; flex:none }
.fchip .n{ color:rgba(255,255,255,.55); font-weight:600 }
.fchip.off{ opacity:.38 }

/* ---- Today against the target -----------------------------------------
   Deliberately a thin strip rather than a card: it should be glanceable
   between doors and never cost the map any height. Turns green the moment
   the number is met, because that is the only moment it needs to shout. */
.quota{
  position:relative; flex:none; height:26px;
  background:#141414; border-bottom:1px solid var(--vw-line);
  display:flex; align-items:center; overflow:hidden;
}
.quota-fill{
  position:absolute; left:0; top:0; bottom:0; width:0;
  background:linear-gradient(90deg, rgba(216,12,17,.55), rgba(216,12,17,.30));
  transition:width .45s cubic-bezier(.2,.7,.3,1);
}
.quota.met .quota-fill{
  background:linear-gradient(90deg, rgba(39,174,96,.55), rgba(39,174,96,.30));
}
.quota-text{
  position:relative; z-index:1;
  padding-left:max(12px,env(safe-area-inset-left));
  font:800 11px/1 Inter,system-ui,sans-serif; letter-spacing:.04em;
  color:rgba(255,255,255,.9); white-space:nowrap;
}
.quota-text b{ font-weight:900 }
.quota-text .goal{ color:rgba(255,255,255,.5); font-weight:700 }

/* Where the chrome actually ends. Measured by knock.js rather than guessed,
   because this offset has already drifted once: it was a hardcoded 134px
   computed for an older header, and the moment the filter chips grew to 44px
   targets and the quota strip appeared, the Map/Satellite toggle started
   sitting on top of the rep's own numbers.

   A number that has to be kept in sync by hand with three other elements'
   heights will drift again. This one cannot. */
/* (the offset lives on the .basemap and .searchbtn rules themselves, below) */

/* ----------------------------------------------------------------- map --- */
#map{ flex:1; min-height:0; background:#0d1117 }
.leaflet-container{ font:inherit }
.leaflet-control-attribution{
  background:rgba(10,10,10,.6) !important; color:rgba(255,255,255,.55) !important;
  font-size:9px !important;
}
.leaflet-control-attribution a{ color:rgba(255,255,255,.7) !important }

/* ---- The grade ---------------------------------------------------------
   Raw Esri aerial is flat and slightly hazy — grey roofs, washed lawns. A
   contrast and saturation lift makes edges read harder, which on a phone in
   sunlight is the difference between seeing a driveway and guessing at it.
   This is doing real work, not decoration: the same pixels look sharper when
   local contrast goes up.

   Deliberately NOT a red wash. The brand red is doing a job on this screen —
   it means "this pin, this button, this is the thing" — and tinting the whole
   world red would both fight those signals and make a rep worse at telling
   one roof from the next. The chrome carries the brand; the map carries the
   houses. Applied to the tile pane only, so pins and the blue dot stay true. */
.leaflet-tile-pane{
  filter:contrast(1.08) saturate(1.14) brightness(1.02);
}

/* Past z19 there are no more photographs — z20 is Esri's z19 stretched. The
   browser smooths that into mush, so lean the other way and let the edges
   stay hard. knock.js adds this class only at the zoom where it applies. */
.zoomed-past-photos .leaflet-tile-pane{
  filter:contrast(1.16) saturate(1.16) brightness(1.02);
}
.zoomed-past-photos .leaflet-tile{
  image-rendering:-webkit-optimize-contrast;
}

/* A tile fades in over 200ms by default, which on a slow connection reads as
   "still blurry". Snap them in instead — the sharp tile should appear the
   instant it decodes. */
.leaflet-tile{ transition:none !important }

/* Pins: a colored dot with a glyph, SalesRabbit-style, readable on satellite. */
.pin{ background:none; border:none }
.pin b{
  display:flex; align-items:center; justify-content:center;
  width:26px; height:26px; border-radius:50% 50% 50% 4px;
  transform:rotate(-45deg);
  border:2px solid #fff; box-shadow:0 1px 5px rgba(0,0,0,.55);
  font:800 11px/1 Inter,system-ui,sans-serif; color:#fff; font-style:normal;
}
.pin b i{ transform:rotate(45deg); font-style:normal }
.pin.draft b{ animation:pulse 1.1s ease-in-out infinite }
@keyframes pulse{ 50%{ box-shadow:0 0 0 9px rgba(216,12,17,.25) } }

/* ---- The rep's own live position ------------------------------------
   A pulsing blue dot, the way every field app draws "you are here". The
   marker itself has no size: each piece is centred on the exact coordinate,
   so the dot never drifts off the spot as the map zooms. */
.me{ background:none; border:none }
.me i{ position:absolute; left:0; top:0; font-style:normal }

/* The heartbeat. Sits under the dot and rings outward, so a rep can find
   themselves on a busy map without hunting. */
.me .pulse{
  width:20px; height:20px; margin:-10px 0 0 -10px; border-radius:50%;
  background:rgba(47,128,237,.5);
  animation:mePulse 2.2s cubic-bezier(.24,.62,.36,1) infinite;
}
@keyframes mePulse{
  0%{   transform:scale(.55); opacity:.85 }
  70%{  transform:scale(3.4);  opacity:0 }
  100%{ transform:scale(3.4);  opacity:0 }
}

/* Which way they are facing. Only shown once the device reports a heading. */
.me .head{ width:0; height:0; transform:rotate(0deg); transition:transform .2s linear }
.me .head::before{
  content:''; position:absolute; left:-9px; bottom:9px;
  border-left:9px solid transparent; border-right:9px solid transparent;
  border-bottom:22px solid rgba(47,128,237,.6);
}
.me .head{ display:none }
.me.facing .head{ display:block }

.me .dot{
  width:20px; height:20px; margin:-10px 0 0 -10px; border-radius:50%;
  background:#2F80ED; border:3px solid #fff;
  box-shadow:0 1px 6px rgba(0,0,0,.55);
}

/* How sure the phone is. A wide ring means poor GPS — worth seeing before
   you trust the dot enough to pin the house next door. */
.me-acc{ stroke:#2F80ED; stroke-opacity:.55; fill:#2F80ED; fill-opacity:.12 }

@media (prefers-reduced-motion:reduce){
  .me .pulse{ animation:none; opacity:.45; transform:scale(1.6) }
}

/* ---------------------------------------------------- floating controls --- */
.basemap{
  position:fixed; top:calc(var(--chrome-bottom, 108px) + 10px); left:10px; z-index:1000;
  display:flex; border-radius:8px; overflow:hidden;
  border:1px solid rgba(0,0,0,.35); box-shadow:0 2px 8px rgba(0,0,0,.4);
}
.basemap button{
  font:700 13px/1 Inter,system-ui,sans-serif; padding:13px 16px; border:none;
  background:rgba(23,23,23,.92); color:rgba(255,255,255,.75); cursor:pointer;
  min-height:44px;
}
.basemap button.on{ background:var(--vw-red); color:#fff }

.locate{
  position:fixed; right:14px; bottom:calc(env(safe-area-inset-bottom) + 22px); z-index:1000;
  width:56px; height:56px; border-radius:50%; border:1px solid rgba(0,0,0,.35);
  background:rgba(23,23,23,.94); color:#fff; cursor:pointer;
  display:flex; align-items:center; justify-content:center;
  box-shadow:0 2px 10px rgba(0,0,0,.5);
}
.locate svg{ width:27px; height:27px }
.locate.on{ color:#2F80ED }                 /* located, map free to pan */
.locate.follow{ background:#2F80ED; color:#fff; border-color:#2F80ED }  /* following */
.locate.searching svg{ animation:meSpin 1.4s linear infinite }
@keyframes meSpin{ to{ transform:rotate(360deg) } }

/* ---- Find a door ------------------------------------------------------
   The case this exists for: a rep is in the truck texting a customer and
   cannot remember which house it was. They have the phone number, so the
   number has to find the pin. */
.searchbtn{
  position:fixed; top:calc(var(--chrome-bottom, 108px) + 10px);
  right:14px; z-index:1000;
  width:46px; height:46px; border-radius:12px;
  border:1px solid rgba(0,0,0,.35); background:rgba(23,23,23,.94);
  color:#fff; cursor:pointer;
  display:flex; align-items:center; justify-content:center;
  box-shadow:0 2px 10px rgba(0,0,0,.5);
}
.searchbtn svg{ width:22px; height:22px }
.searchbtn:active{ background:var(--vw-red) }

.searchpanel{
  position:fixed; left:0; right:0; z-index:1150;
  top:0; padding-top:env(safe-area-inset-top);
  background:var(--vw-carbon); border-bottom:2px solid var(--vw-red);
  box-shadow:0 8px 30px rgba(0,0,0,.6);
  display:flex; flex-direction:column;
  max-height:88dvh;
}
.searchbar{
  display:flex; align-items:center; gap:9px;
  padding:10px 12px;
  padding-left:max(12px,env(safe-area-inset-left));
  padding-right:max(12px,env(safe-area-inset-right));
}
.searchbar .si{ width:19px; height:19px; color:rgba(255,255,255,.45); flex:none }
.searchbar input{
  flex:1; min-width:0; background:#101010; color:#fff;
  border:1px solid var(--vw-line); border-radius:9px;
  padding:12px 12px; font:600 16px/1 Inter,system-ui,sans-serif;  /* 16px: no iOS zoom */
}
.searchbar input::placeholder{ color:rgba(255,255,255,.35); font-weight:500 }
.searchbar input:focus{ outline:none; border-color:var(--vw-red) }
.searchbar input::-webkit-search-cancel-button{ -webkit-appearance:none }
.searchbar button{
  flex:none; background:none; border:none; color:var(--vw-red);
  font:800 14px/1 Inter,system-ui,sans-serif; cursor:pointer;
  padding:14px 6px; margin:-14px 0;
}

.results{ overflow-y:auto; -webkit-overflow-scrolling:touch; padding-bottom:8px }
.hit{
  display:flex; align-items:center; gap:11px; width:100%;
  padding:12px; min-height:56px;
  background:none; border:none; border-top:1px solid rgba(255,255,255,.06);
  color:#fff; text-align:left; cursor:pointer;
}
.hit:active{ background:#1d1d1d }
.hit .dot{
  width:13px; height:13px; border-radius:50%; flex:none;
  border:2px solid rgba(255,255,255,.85);
}
.hit .lines{ flex:1; min-width:0 }
.hit .l1{
  display:block; font:700 14px/1.25 Inter,system-ui,sans-serif;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.hit .l2{
  display:block; margin-top:3px;
  font:600 11.5px/1.3 Inter,system-ui,sans-serif; color:rgba(255,255,255,.48);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.hit mark{ background:rgba(224,168,0,.3); color:#ffd86b; border-radius:2px; padding:0 1px }
.hit .go{ color:rgba(255,255,255,.3); font-size:17px; flex:none }
/* Where a result came from. The map's own pins need no heading — they are the
   expected answer — so only the two remote groups are labelled, and quietly:
   this is a divider, not a section title. */
.group{
  margin:14px 0 4px; padding:0 14px;
  font:800 9.5px/1.4 Inter,system-ui,sans-serif; letter-spacing:.16em;
  text-transform:uppercase; color:rgba(255,255,255,.38);
}
/* A CRM customer and a bare address are not pins, so they must not wear pin
   colours — a coloured dot on this list means a door with a disposition. */
.hit .crmdot,.hit .addrdot{
  display:flex; align-items:center; justify-content:center;
  width:22px; height:22px; border-radius:50%; flex:none;
  font:800 11px/1 Inter,system-ui,sans-serif;
}
.hit .crmdot{ background:rgba(255,255,255,.14); color:rgba(255,255,255,.8) }
.hit .addrdot{ background:rgba(216,12,17,.22); color:#ff8f92; font-size:14px }
.hit:disabled{ opacity:.6 }

.searchmsg{ padding:16px 14px 20px; color:rgba(255,255,255,.4);
            font:600 13px/1.5 Inter,system-ui,sans-serif }

/* --------------------------------------------------------------- sheet --- */
.scrim{ position:fixed; inset:0; z-index:1090; background:rgba(0,0,0,.35);
  touch-action:none }
.sheet{
  position:fixed; left:0; right:0; bottom:0; z-index:1100;
  /* Scrollable, never zoomable — a pinch here is someone aiming at the map. */
  touch-action:pan-y;
  background:var(--vw-carbon); border-top:2px solid var(--vw-red);
  border-radius:16px 16px 0 0; box-shadow:0 -8px 30px rgba(0,0,0,.55);
  max-height:min(80dvh, 640px); overflow:hidden;
  display:flex; flex-direction:column;
  padding-bottom:env(safe-area-inset-bottom);
  /* Slides up rather than appearing, so it reads as "this covers the map"
     instead of "the map changed". */
  animation:sheetUp .18s ease-out;
}
@keyframes sheetUp{ from{ transform:translateY(14px); opacity:.6 } }

/* A real grab area, not a 4px line — this is the swipe-to-close handle. */
.sheet-grab{
  width:44px; height:5px; border-radius:3px; background:rgba(255,255,255,.3);
  margin:10px auto 2px;
}
.sheet-handle{ padding:7px 0 10px; cursor:grab; touch-action:none }
.sheet-body{
  padding:10px 14px 14px; display:flex; flex-direction:column; gap:10px;
  overflow-y:auto; -webkit-overflow-scrolling:touch;
  flex:1 1 auto; min-height:0;   /* this is the scrollport the buttons stick to */
}

.sheet input,.sheet textarea,.sheet button,.sheet a{ touch-action:pan-y }
.sheet input,.sheet textarea{
  width:100%; background:#101010; color:#fff;
  border:1px solid var(--vw-line); border-radius:8px;
  padding:11px 12px; font:inherit; font-size:16px; /* 16px stops iOS zoom */
}
.sheet input::placeholder,.sheet textarea::placeholder{ color:rgba(255,255,255,.35) }
.sheet input:focus,.sheet textarea:focus{ outline:none; border-color:var(--vw-red) }
.sheet-addr input{ font-weight:700 }
/* The geocoder found the street but not the house. Amber, not red: the
   address is probably close, it just has not been confirmed to a parcel. */
.sheet-addr input.rough{ border-color:#E0A800; box-shadow:inset 3px 0 0 #E0A800 }
/* While it is being looked up the placeholder does the talking. */
.sheet-addr input::placeholder{ font-weight:600 }
.grid2{ display:grid; grid-template-columns:1.4fr 1fr; gap:8px }
textarea{ resize:vertical; min-height:52px }

/* ---- Step 1: the quote ------------------------------------------------
   The first thing a rep sees when a pin lands. Built to be read at arm's
   length on a porch: the price is the biggest thing on the screen, and the
   window count is a pair of targets you can hit without looking. */
.step{ display:flex; flex-direction:column; gap:12px }

.q-head{ display:flex; align-items:baseline }
.q-addr{
  font:800 15px/1.3 Inter,system-ui,sans-serif; color:#fff;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

.q-price{ text-align:center; padding:6px 0 2px }
.q-big{
  font:900 52px/1 Inter,system-ui,sans-serif; letter-spacing:-.03em; color:#fff;
  font-variant-numeric:tabular-nums;
}
.q-sub{ margin-top:6px; font:600 12.5px/1 Inter,system-ui,sans-serif; color:rgba(255,255,255,.55) }

/* Counting glass one window at a time is the single most-used control here,
   so the +/- are big and the number between them is unmissable. */
.q-count{ display:flex; align-items:center; gap:14px; justify-content:center }
.q-count button{
  width:64px; height:56px; flex:none;
  background:#101010; border:1.5px solid var(--vw-line); border-radius:12px;
  color:#fff; font:800 30px/1 Inter,system-ui,sans-serif; cursor:pointer;
}
.q-count button:active{ background:#1d1d1d; border-color:var(--vw-red) }
.q-n{ min-width:104px; text-align:center }
.q-n b{ display:block; font:900 30px/1 Inter,system-ui,sans-serif; font-variant-numeric:tabular-nums }
.q-n span{ font:700 10px/1 Inter,system-ui,sans-serif; letter-spacing:.18em;
           text-transform:uppercase; color:rgba(255,255,255,.5) }

#qRange{ width:100%; accent-color:var(--vw-red); height:44px }

.q-row{ display:flex; align-items:center; gap:10px }
.q-lab{
  flex:none; width:74px;
  font:700 10px/1 Inter,system-ui,sans-serif; letter-spacing:.16em;
  text-transform:uppercase; color:rgba(255,255,255,.5);
}
.q-opts{ display:flex; gap:6px; flex:1 }
.q-opts button{
  flex:1; min-height:44px; padding:0 10px;
  background:#101010; border:1.5px solid var(--vw-line); border-radius:9px;
  color:rgba(255,255,255,.75); cursor:pointer;
  font:700 13px/1 Inter,system-ui,sans-serif;
}
.q-opts button.on{ border-color:#fff; background:#1d1d1d; color:#fff }

/* The price they actually agreed to, carried onto the details step. */
.quoted-line{
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:10px 12px; border-radius:9px;
  background:rgba(224,168,0,.13); color:#ffd86b;
  font:700 13px/1 Inter,system-ui,sans-serif;
}
.quoted-line b{ font-size:17px; font-weight:900 }

/* Status rail: two rows of four, color-coded, big enough for a thumb. */
.rail{ display:grid; grid-template-columns:repeat(4,1fr); gap:6px }
.rail button{
  display:flex; flex-direction:column; align-items:center; gap:5px;
  background:#101010; border:1.5px solid var(--vw-line); border-radius:9px;
  padding:12px 2px 11px; cursor:pointer; color:rgba(255,255,255,.8);
  font:700 11px/1 Inter,system-ui,sans-serif; letter-spacing:.02em;
  min-height:62px;   /* two rows of four, each comfortably thumb-sized */
}
.rail button .dot{
  width:16px; height:16px; border-radius:50%; border:2px solid #fff;
  display:flex; align-items:center; justify-content:center;
  font:800 8px/1 Inter,system-ui,sans-serif; color:#fff;
}
.rail button.on{ border-color:#fff; background:#1d1d1d; color:#fff }

/* ---- The closing price ------------------------------------------------
   The quote is what was said on the step; this is what the job sold for, and
   it is the number the CRM opportunity carries. Big target, big type: it is
   entered one-handed at a door, and it is the figure the whole pipeline
   totals up to. */
.close-row{ display:flex; align-items:center; gap:9px; flex-wrap:wrap }
.close-lab{
  flex:none; width:74px;
  font:700 10px/1 Inter,system-ui,sans-serif; letter-spacing:.16em;
  text-transform:uppercase; color:rgba(255,255,255,.5);
}
.close-input{
  flex:1; min-width:120px; display:flex; align-items:center; gap:2px;
  background:#101010; border:1.5px solid var(--vw-line); border-radius:9px;
  padding:0 12px; min-height:52px;
}
.close-input:focus-within{ border-color:#27AE60 }
.close-input .cur{
  font:800 19px/1 Inter,system-ui,sans-serif; color:rgba(255,255,255,.4); flex:none;
}
.close-input input{
  flex:1; min-width:0; background:none; border:none; color:#fff;
  font:900 22px/1 Inter,system-ui,sans-serif; font-variant-numeric:tabular-nums;
  padding:12px 0;
}
.close-input input:focus{ outline:none }
.close-input input::placeholder{ color:rgba(255,255,255,.25); font-weight:700 }
/* Sold at the quote is the common case — one tap, not typing it out again. */
.close-same{
  flex:none; background:none; border:1px solid var(--vw-line); border-radius:8px;
  color:rgba(255,255,255,.7); font:700 12px/1 Inter,system-ui,sans-serif;
  padding:0 12px; min-height:44px; cursor:pointer;
}
.close-same:active{ border-color:#27AE60; color:#fff }
.close-row.differs .close-input{ border-color:#27AE60 }

/* ---- Email, and refusing one ------------------------------------------
   The box and its escape hatch share a row so the choice reads as one
   question, not as a field plus an unrelated button. */
.email-row{ display:flex; gap:8px; align-items:stretch }
.email-row input{ flex:1; min-width:0 }
.noemail{
  flex:none; background:#101010; border:1.5px solid var(--vw-line); border-radius:9px;
  color:rgba(255,255,255,.6); font:700 12px/1 Inter,system-ui,sans-serif;
  padding:0 13px; min-height:52px; cursor:pointer; white-space:nowrap;
}
.noemail.on{ border-color:#fff; background:#1d1d1d; color:#fff }
.email-row input:disabled{ opacity:.4 }
/* Whatever is missing gets marked, all at once — a rep should never have to
   tap book four times to discover four blanks in front of a customer. */
.sheet input.need,.email-row.need input{
  border-color:var(--vw-red); box-shadow:inset 3px 0 0 var(--vw-red);
}

/* Instructions to the REP, not labels on a field. Deliberately quiet — it is a
   reminder, read once and then known — but it stays on the card rather than
   living in a training doc, because the moment it matters is this one. */
.hint{
  margin:-2px 0 0; padding:9px 11px; border-radius:8px; background:#101010;
  border-left:2px solid var(--vw-red);
  font:500 12px/1.45 Inter,system-ui,sans-serif; color:rgba(255,255,255,.6);
}
.hint b{ color:#fff; font-weight:800 }

/* Permission to text, as a tick rather than a checkbox — a native checkbox is
   a 13px target on a phone and this is answered one-handed at a door. */
.consent{
  display:flex; align-items:center; gap:10px; width:100%; text-align:left;
  background:none; border:none; padding:6px 2px; min-height:44px; cursor:pointer;
  color:rgba(255,255,255,.65); font:600 13px/1.3 Inter,system-ui,sans-serif;
}
.consent .tick{
  flex:none; width:22px; height:22px; border-radius:6px;
  border:1.5px solid var(--vw-line); background:#101010; position:relative;
}
.consent.on{ color:#fff }
.consent.on .tick{ border-color:#27AE60; background:#27AE60 }
.consent.on .tick::after{
  content:''; position:absolute; left:7px; top:3px; width:5px; height:10px;
  border:solid #fff; border-width:0 2.5px 2.5px 0; transform:rotate(45deg);
}

/* ---- Booking the job --------------------------------------------------
   The row that opens the calendar, and the calendar itself. Both are built
   for a thumb on a doorstep: nothing under 44px, the day strip scrolls
   sideways so a fortnight fits without a scroll bar, and only the chosen
   day's times are drawn — 14 days of buttons at once is 40 targets nobody
   can scan while a customer is watching. */
.book-row{
  display:flex; align-items:center; gap:11px; width:100%;
  background:#101010; border:1.5px solid var(--vw-line); border-radius:9px;
  padding:12px 13px; min-height:56px; cursor:pointer; text-align:left; color:#fff;
}
.book-row:active{ background:#1d1d1d; border-color:var(--vw-red) }
.book-row .bi{ width:21px; height:21px; flex:none; color:var(--vw-red) }
.book-row .bt{ flex:1; min-width:0 }
.book-row .bt b{ display:block; font:800 14.5px/1.2 Inter,system-ui,sans-serif }
.book-row .bt span{
  display:block; margin-top:3px; font:600 12px/1.3 Inter,system-ui,sans-serif;
  color:rgba(255,255,255,.5);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.book-row .bgo{ flex:none; color:rgba(255,255,255,.3); font-size:21px; line-height:1 }
/* Already on the calendar: the row states the date rather than offering one. */
.book-row.booked{ border-color:#27AE60; background:rgba(39,174,96,.09) }
.book-row.booked .bi{ color:#27AE60 }
.book-row.booked .bt span{ color:#7ee2a8 }

.b-head{ display:flex; align-items:baseline; gap:10px }
.b-who{
  flex:1; min-width:0; font:800 15px/1.25 Inter,system-ui,sans-serif;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.b-len{
  flex:none; font:700 10px/1 Inter,system-ui,sans-serif; letter-spacing:.14em;
  text-transform:uppercase; color:rgba(255,255,255,.45);
}
.b-current{
  display:flex; align-items:center; gap:8px; padding:10px 12px; border-radius:9px;
  background:rgba(39,174,96,.13); color:#7ee2a8;
  font:700 13px/1.35 Inter,system-ui,sans-serif;
}

/* The day strip. Scrolls sideways; the scroll bar is hidden because on a
   phone it is a thumb gesture, not a control. */
.b-days{
  display:flex; gap:7px; overflow-x:auto; -webkit-overflow-scrolling:touch;
  scrollbar-width:none; padding-bottom:2px; margin:0 -14px; padding-left:14px;
  padding-right:14px;
}
.b-days::-webkit-scrollbar{ display:none }
.b-day{
  flex:none; min-width:60px; min-height:62px; padding:9px 8px; cursor:pointer;
  background:#101010; border:1.5px solid var(--vw-line); border-radius:10px;
  color:rgba(255,255,255,.75); text-align:center;
}
.b-day i{
  display:block; font-style:normal;
  font:700 9.5px/1 Inter,system-ui,sans-serif; letter-spacing:.14em;
  text-transform:uppercase; color:rgba(255,255,255,.45);
}
.b-day b{
  display:block; margin-top:5px;
  font:900 19px/1 Inter,system-ui,sans-serif; font-variant-numeric:tabular-nums;
}
.b-day em{
  display:block; margin-top:4px; font-style:normal;
  font:700 9px/1 Inter,system-ui,sans-serif; color:rgba(255,255,255,.35);
}
.b-day.on{ border-color:#fff; background:#1d1d1d; color:#fff }
.b-day.on i,.b-day.on em{ color:rgba(255,255,255,.7) }

.b-times{ display:grid; grid-template-columns:repeat(3,1fr); gap:7px }
.b-time{
  min-height:52px; cursor:pointer; padding:8px 4px;
  background:#101010; border:1.5px solid var(--vw-line); border-radius:9px;
  color:#fff; font:800 14px/1.2 Inter,system-ui,sans-serif;
}
.b-time span{
  display:block; margin-top:3px;
  font:600 10px/1 Inter,system-ui,sans-serif; color:rgba(255,255,255,.4);
}
.b-time.on{ border-color:var(--vw-red); background:rgba(216,12,17,.16) }
/* Sending it. The primary action once a job is booked — the estimate reaching
   the customer is the point of having written it. */
.b-send{
  display:flex; align-items:center; justify-content:center; gap:9px; width:100%;
  background:var(--vw-red); color:#fff; border:none; border-radius:9px;
  font:800 15px/1.2 Inter,system-ui,sans-serif; padding:16px; min-height:54px;
  cursor:pointer;
}
.b-send svg{ width:19px; height:19px; flex:none }
.b-send:disabled{ opacity:.55 }
.b-send.done{ background:#27AE60 }

/* The file. On a phone this is how an estimate reaches a customer who has no
   email: download, share sheet, Messages. Sized as a primary action because
   for those customers it IS the delivery. */
.b-pdf{
  display:flex; align-items:center; justify-content:center; gap:9px;
  background:#fff; color:#0A0A0A; border-radius:9px; text-decoration:none;
  font:800 14.5px/1.2 Inter,system-ui,sans-serif; padding:15px; min-height:52px;
}
.b-pdf svg{ width:19px; height:19px; flex:none }
.b-pdf:active{ opacity:.75 }

/* Opening it, for saving to Files or printing. Quiet: sharing is the action
   that gets an estimate to a customer, this is the one that files it. */
.b-open{
  width:100%; background:none; border:1px solid var(--vw-line); border-radius:9px;
  color:rgba(255,255,255,.7); font:700 13px/1 Inter,system-ui,sans-serif;
  padding:14px; min-height:48px; cursor:pointer;
}
.b-open:active{ border-color:#fff; color:#fff }

.b-msg{
  padding:10px 12px; border-radius:9px; background:#101010;
  color:rgba(255,255,255,.6); font:600 12.5px/1.45 Inter,system-ui,sans-serif;
}
.b-msg.warn{ background:rgba(224,168,0,.14); color:#ffd86b }
.b-msg.bad{ background:rgba(216,12,17,.15); color:#ff9a9c }

.sheet-actions{
  display:flex; flex-direction:column; gap:8px;
  position:sticky; bottom:0; z-index:2;
  background:var(--vw-carbon); padding:8px 0 2px;
  box-shadow:0 -10px 14px -8px var(--vw-carbon);
}
.btn-primary{
  background:var(--vw-red); color:#fff; border:none; border-radius:9px;
  font:800 16px/1 Inter,system-ui,sans-serif; padding:17px; cursor:pointer;
  min-height:52px;
}
.btn-primary:disabled{ opacity:.5 }
.btn-quote{
  display:block; text-align:center; text-decoration:none;
  background:#fff; color:#0A0A0A; border-radius:9px;
  font:800 15px/1 Inter,system-ui,sans-serif; padding:16px; min-height:50px;
}
.btn-ghost{
  background:none; border:none; color:rgba(255,255,255,.65);
  font:700 14px/1 Inter,system-ui,sans-serif; padding:14px 18px; cursor:pointer;
  min-height:44px;
}
.btn-del{
  background:none; border:none; color:rgba(255,255,255,.45);
  font:600 13px/1 Inter,system-ui,sans-serif; padding:14px 4px; cursor:pointer;
  min-height:44px;
  text-decoration:underline; text-underline-offset:3px;
}
.btn-del.confirm{ color:var(--vw-red); font-weight:800 }
.sheet-foot{ display:flex; align-items:center }
.foot-spacer{ flex:1 }
.sheet-err{ color:#ff6b6e; font:600 13px/1.4 Inter,system-ui,sans-serif; min-height:0 }

/* ---- Did this door reach the CRM? -------------------------------------
   A rep sets a pin to Quoted expecting a lead in the pipeline. When that did
   not happen they deserve to be told here, not to find out days later by its
   absence. */
.crm-state{
  display:flex; align-items:flex-start; gap:8px;
  padding:10px 12px; border-radius:9px;
  font:600 13px/1.4 Inter,system-ui,sans-serif;
}
.crm-state .k{ flex:none; font-weight:800 }
.crm-state.ok{ background:rgba(39,174,96,.13); color:#7ee2a8 }
.crm-state.warn{ background:rgba(224,168,0,.14); color:#ffd86b }
.crm-state.quiet{ background:#101010; color:rgba(255,255,255,.5); font-weight:500 }

/* --------------------------------------------------------------- history --- */
.history{ border-top:1px solid var(--vw-line); padding-top:8px }
.history h4{
  margin:0 0 6px; font:800 10px/1 Inter,system-ui,sans-serif;
  letter-spacing:.16em; text-transform:uppercase; color:rgba(255,255,255,.5);
}
.hrow{ display:flex; gap:8px; padding:5px 0; font-size:12.5px; color:rgba(255,255,255,.75) }
.hrow .dot{ width:10px; height:10px; border-radius:50%; flex:none; margin-top:3px }
.hrow .meta{ color:rgba(255,255,255,.45) }
.hrow .note{ display:block; color:rgba(255,255,255,.85) }

/* How old the aerial photographs are, sitting just above Leaflet's own
   attribution. Deliberately quiet — it is reference, not a control — but
   present, because "the map is wrong" and "the map is old" are different
   problems and a rep should be able to tell them apart without asking. */
.imgdate{
  position:fixed; right:8px; z-index:800;
  bottom:calc(env(safe-area-inset-bottom) + 20px);
  background:rgba(10,10,10,.62); color:rgba(255,255,255,.62);
  font:600 9.5px/1 Inter,system-ui,sans-serif; letter-spacing:.03em;
  padding:4px 7px; border-radius:4px; pointer-events:none;
}
@media (max-width:420px){
  /* clear of the locate button's corner */
  .imgdate{ right:70px }
}

/* ---------------------------------------------------------------- toast --- */
/* Undo inside the toast. Big enough to hit while walking, and clearly the
   thing to press — removing a door by accident must be one tap to fix. */
.toast-undo{
  margin-left:14px; padding:6px 12px; min-height:32px;
  border:none; border-radius:99px; cursor:pointer;
  background:var(--vw-red); color:#fff;
  font:800 13px/1 Inter,system-ui,sans-serif; letter-spacing:.03em;
}
.toast{ display:flex; align-items:center }
.toast{
  position:fixed; left:50%; transform:translateX(-50%);
  bottom:calc(env(safe-area-inset-bottom) + 88px); z-index:1200;
  background:#fff; color:#0A0A0A; border-radius:14px;
  font:700 13px/1.35 Inter,system-ui,sans-serif; padding:12px 18px;
  box-shadow:0 4px 16px rgba(0,0,0,.5);
  max-width:min(88vw,420px); text-align:center;   /* wrap, never run off-screen */
}

/* Phone held sideways — usually to read a long street. Keep the map visible:
   the card becomes a narrow right-hand panel instead of a full-width strip. */
@media (orientation:landscape) and (max-height:520px){
  .sheet{
    left:auto; right:0; top:0; bottom:0; width:min(58vw,420px);
    max-height:100dvh; border-radius:0; border-top:none;
    border-left:2px solid var(--vw-red);
    padding-right:env(safe-area-inset-right);
  }
  .rail{ grid-template-columns:repeat(4,1fr) }
  .locate{ bottom:calc(env(safe-area-inset-bottom) + 14px) }
}

/* A big phone still shows the card as a bottom sheet — never a desktop panel. */
@media (min-width:760px) and (orientation:portrait){
  .sheet{ left:50%; transform:translateX(-50%); width:min(100%,560px) }
}
