/* ==========================================================================
   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 }
.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 }

.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) }
}
