/* index.css — page-specific layout for the marketing home page (index.html): the
   hero, services, results gallery, why, coverage map, quote form, booking wizard,
   price calculator and the intl-tel-input dark theme. The chrome (nav, footer,
   mobile menu, to-top, body grain/glow), the gold eyebrow base and reveal come
   from the shared layer; this file keeps only page layout plus the few chrome
   overrides the home page needs (translucent hero nav, underlined nav links).
   Cascade: tokens -> base -> site -> animations -> index.css. */

  /* The home page keeps the ORIGINAL 100ms reveal step; the site default is half that.
     Its groups are short rows - six services, three reasons, the hero strip - where the
     wider step reads as a cascade and the last item is still under a second behind the
     first. The long columns elsewhere (a policy, the FAQ) are what the smaller default
     is for. */
  :root{--stagger:100ms}

  .container{max-width:1180px;margin:0 auto}

  /* ---------- NAV ---------- */
  /* The bar's skin - background, blur, bottom rule - belongs to chrome.css and is
     NOT re-tinted here: one tone on every page, marketing and console alike. Only
     the link treatment is this page's: dimmer resting colour, underline on hover. */
  .nav-links a{color:var(--ink-dim);position:relative}
  .nav-links a.hide-sm::after{
    content:'';position:absolute;left:0;bottom:-2px;
    width:100%;height:1.5px;
    background:var(--gold);
    transform:scaleX(0);transform-origin:center;
    transition:transform .28s cubic-bezier(.4,0,.2,1);
  }
  @media(hover:hover) and (pointer:fine){
    .nav-links a.hide-sm:hover{color:var(--ink-soft)}
    .nav-links a.hide-sm:hover::after{transform:scaleX(1)}}
  @media(prefers-reduced-motion:reduce){.nav-links a.hide-sm::after{transition:none}}

  /* ---------- HERO ---------- */
  /* Content is anchored under the nav instead of centred in the viewport: centring
     made the gap under the fixed nav grow with screen height. The bottom padding
     keeps the block clear of the absolutely positioned .hero-meta strip: on a short
     viewport the header grows with its content, so the gap above the strip is exactly
     padding-bottom minus the strip's own height plus its 46px offset - 264 = 43 + 46 +
     the 175px the six-item strip needs for its two rows. */
  header{
    min-height:100vh;display:flex;flex-direction:column;justify-content:flex-start;
    align-items:flex-start;text-align:left;
    padding:116px 0 264px;position:relative;
  }
  header .container{width:100%}
  .eyebrow,.lede{margin-left:0;margin-right:auto}
  .eyebrow{
    display:inline-flex;align-items:center;gap:12px;
    color:var(--gold);font-size:.74rem;letter-spacing:.34em;text-transform:uppercase;
    margin-bottom:28px;opacity:0;animation:rise .9s .1s forwards;
  }
  .eyebrow::before{content:"";width:38px;height:1px;background:var(--gold)}
  /* site.css gives .eyebrow a trailing ::after rule too; the home hero has only a
     leading rule, so neutralise the shared trailing line here. */
  .eyebrow::after{content:none}
  /* No ch-based width cap: every line break is authored as a .l span, and the
     longest line no longer fits the old cap - it would wrap into a fourth line. */
  h1{
    font-family:var(--serif);font-weight:300;
    font-size:clamp(2.9rem,8vw,6.2rem);line-height:.98;letter-spacing:-.015em;
    margin-bottom:30px;
  }
  h1 .em{font-style:italic;color:var(--gold-soft)}
  h1 .l{display:block;opacity:0;animation:rise 1s forwards}
  h1 .l:nth-child(1){animation-delay:.18s}
  h1 .l:nth-child(2){animation-delay:.32s}
  .lede{
    font-size:clamp(1.02rem,2vw,1.22rem);color:var(--ink-soft);max-width:50ch;
    margin-bottom:42px;opacity:0;animation:rise 1s .6s forwards;font-weight:300;
  }
  /* The hero's three staggered entrances all START AT opacity 0, so the animation is
     not decoration here - it is the only thing that makes the largest text on the site
     appear. Two consequences the shared reveal rule does not cover, because these are
     `animation`s rather than the `.reveal` transition:
       - a visitor who asked for less motion still got the whole sequence;
       - and if the animation does not run at all, the headline never arrives.
     Under reduced motion they render finished immediately. Same end state, no
     movement, and nothing depending on an animation to become visible. */
  @media(prefers-reduced-motion:reduce){
    .eyebrow,h1 .l,.lede{animation:none;opacity:1}
  }
  .btn{
    font-family:var(--sans);font-size:.86rem;letter-spacing:.08em;text-transform:uppercase;
    padding:16px 32px;border-radius:2px;text-decoration:none;cursor:pointer;
    transition:all .3s;border:1px solid transparent;display:inline-block;
  }
  .btn-gold{background:var(--gold);color:var(--bg);font-weight:500}
  @media(hover:hover) and (pointer:fine){.btn-gold:hover{background:var(--gold-soft);transform:translateY(-2px)}}
  .btn-gold:active{background:var(--gold-soft)}
  .btn-ghost{background:transparent;border-color:var(--line);color:var(--ink)}
  @media(hover:hover) and (pointer:fine){.btn-ghost:hover{border-color:var(--gold);color:var(--gold)}}
  .btn-ghost:active{border-color:var(--gold);color:var(--gold)}

  /* ---------- INSTANT QUOTE (hero) ---------- */
  /* A SHOWCASE of the wizard's calculator, so it owns nothing the wizard owns: no price
     rule, no option list, no second state that could disagree. index.js fills the three
     selects from the wizard's own option grids and prices them with the same function
     over the same prices.json; Book this hands the selection to those options' own click.
     The controls wear .field, the page's existing form skin - only the compact size and
     the drawn chevron are local, and appearance:none is what stops iOS repainting a
     select as its own light capsule on the page a visitor lands on first. */
  .iq{
    /* No top margin of its own: the panel stands exactly where the CTA row stood and the
       lede's own margin-bottom is the whole gap, as it was for that row. (A margin here
       would collapse against that one anyway - it bought nothing.) The tight bottom is
       measured: it is what puts the fine print above the fold on a 1280x720 laptop. */
    max-width:640px;padding:22px 24px 10px;
    background:var(--panel);border:1px solid rgba(201,165,88,.28);border-radius:3px;
    opacity:0;animation:rise 1s .75s forwards;
  }
  .iq-head{font-size:.66rem;letter-spacing:.24em;text-transform:uppercase;color:var(--gold);
    margin-bottom:16px}
  .iq-fields{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
  .iq-field{position:relative;margin:0}
  .iq-field label{font-size:.62rem;letter-spacing:.14em;margin-bottom:7px}
  .iq .field select{appearance:none;-webkit-appearance:none;padding:11px 32px 11px 12px;font-size:.9rem}
  /* Drawn, not an image: a data-URI arrow would have to carry a literal colour, and the
     chevron here is the same shape the calendar and the TOC already use. */
  .iq-field::after{content:'';position:absolute;right:14px;bottom:17px;width:6px;height:6px;
    border-right:1.5px solid var(--ink-soft);border-bottom:1.5px solid var(--ink-soft);
    transform:rotate(45deg);pointer-events:none}
  /* Price on the left, the two routes on the right in the hero row's own order: the
     ghost one, then the gold one at the end of the line. */
  .iq-out{display:flex;align-items:flex-end;gap:12px;margin-top:18px;flex-wrap:wrap}
  .iq-total{margin-right:auto;padding-right:8px}
  .iq-label{font-size:.62rem;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-dim);
    margin-bottom:4px}
  .iq-amount{display:flex;align-items:baseline;gap:10px}
  .iq-price{font-family:var(--serif);font-size:2.1rem;font-weight:400;color:var(--ink);line-height:1}
  /* The price re-renders on every change; the shared fade-in keyframe is what marks it,
     re-armed by removing and re-adding the class. No new duration, no new easing. */
  .iq-price.tick{animation:fade-in var(--dur) var(--ease)}
  .iq-hrs{font-size:.82rem;color:var(--ink-dim)}
  /* Placeholder while prices.json is still in flight: a bar, not a spinner and not a
     guessed number. It does not pulse, so there is nothing for reduced motion to stop. */
  .iq-skeleton{display:inline-block;width:104px;height:.72em;border-radius:2px;
    background:var(--line);opacity:.5}
  /* The pair inherits .btn whole - the hero row's own padding, type and sliding arrow -
     because it IS that row now. Only the line-height is pinned: one is an <a> and one a
     <button>, so the anchor inherits site.css's 1.6 while the button takes the UA's
     `normal`, and that is what left them 4px out of alignment. */
  .iq-msg,.iq-book{line-height:1.2;flex:0 0 auto}
  .iq-book[disabled]{opacity:.45;cursor:default}
  @media(hover:hover) and (pointer:fine){.iq-book[disabled]:hover{background:var(--gold);transform:none}}
  .iq-note{margin-top:6px;font-size:.72rem;color:var(--ink-dim);line-height:1.4}
  @media(prefers-reduced-motion:reduce){
    .iq{animation:none;opacity:1}
    .iq-price.tick{animation:none}
  }
  /* On a phone the panel follows the CTA buttons and the three controls stack 2 + 1:
     three across leaves each select too narrow for "Move-in / out". The price then sits
     over the two routes, Book this last, where a thumb ends up. */
  @media(max-width:620px){
    .iq{padding:18px 16px 14px}
    .iq-fields{grid-template-columns:1fr 1fr}
    .iq-field:last-child{grid-column:1 / -1}
    .iq-out{display:grid;grid-template-columns:1fr;gap:14px}
    .iq-total{margin-right:0;padding-right:0}
    .iq-msg,.iq-book{width:100%;text-align:center}
  }

  /* ---------- NEXT AVAILABLE (hero) ---------- */
  /* One line under the panel, shown only when the server actually has a slot to name.
     It is a control, so it is a <button> - but a quiet one: no border, no fill, the
     hero's own type. The date is the gold; the lead-in stays dim so the line reads as
     information first and an action second. */
  .next-available{
    display:inline-flex;align-items:center;gap:9px;margin-top:16px;padding:6px 0;
    background:transparent;border:0;cursor:pointer;text-align:left;
    font-family:var(--sans);font-size:.84rem;color:var(--ink-dim);
    opacity:0;animation:fade-in var(--dur-slow) var(--ease) forwards;
  }
  .next-available[hidden]{display:none}
  .next-available-dot{width:7px;height:7px;border-radius:50%;background:var(--green);
    flex:0 0 auto}
  .next-available-when{color:var(--gold-soft)}
  @media(hover:hover) and (pointer:fine){
    .next-available:hover .next-available-when{color:var(--gold)}
  }
  .next-available:focus-visible{outline:2px solid var(--gold);outline-offset:3px;border-radius:2px}
  @media(prefers-reduced-motion:reduce){
    .next-available{animation:none;opacity:1}
  }

  /* The strip sits on the page grid: its box is the .container CONTENT column
     (1180 minus the 28px gutters), so both the rule and the labels line up with the
     eyebrow, H1, lede and CTA row instead of running to the screen edges. */
  /* The strip itself no longer fades as one block: its entrance moved onto the items so
     they arrive in sequence, and so the number inside each can lead its caption. The
     rule stays, only the border - the container must be opaque or it would gate the
     children it is trying to stagger. */
  .hero-meta{
    position:absolute;bottom:46px;left:28px;right:28px;
    max-width:calc(1180px - 56px);margin:0 auto;
    display:grid;grid-template-columns:repeat(3,1fr);gap:26px 48px;
    border-top:1px solid var(--line-soft);padding-top:24px;
  }
  /* --i comes from animations.js, so inserting an item cannot desync the cascade. The
     .95s start is the strip's original beat, kept. */
  .hero-meta div{font-size:.8rem;color:var(--ink-dim);letter-spacing:.05em;
    opacity:0;animation:rise .9s forwards;
    animation-delay:calc(.95s + var(--i,0) * var(--stagger))}
  /* Two levels inside one block: the number and the caption start together, but the
     number travels a shorter distance in less time, so it settles first and the caption
     catches up. A real lead cannot be done with delay alone - the parent's own fade
     would gate anything scheduled earlier than itself. */
  .hero-meta b{display:block;font-family:var(--serif);color:var(--ink);font-size:1.5rem;font-weight:400;margin-bottom:3px;
    animation:meta-lead .6s var(--ease-settle) forwards;
    animation-delay:calc(.95s + var(--i,0) * var(--stagger))}
  @keyframes meta-lead{from{transform:translateY(10px)}to{transform:none}}
  @media(prefers-reduced-motion:reduce){
    .hero-meta div,.hero-meta b{animation:none;opacity:1;transform:none}
  }

  @keyframes rise{from{opacity:0;transform:translateY(22px)}to{opacity:1;transform:translateY(0)}}

  /* ---------- SECTION SHELL ---------- */
  section{padding:72px 0;position:relative}
  /* Heading to content: 62px left the heading floating away from what it
     introduces, on every section that has one. */
  .sec-head{margin-bottom:30px}
  .sec-tag{color:var(--gold);font-size:.74rem;letter-spacing:.3em;text-transform:uppercase;
    display:flex;align-items:center;gap:12px;margin-bottom:18px}
  .sec-tag::before{content:"";flex:0 0 38px;height:1px;background:var(--gold)}
  .sec-title{font-family:var(--serif);font-weight:300;font-size:clamp(2rem,4.5vw,3.2rem);line-height:1.05;letter-spacing:-.01em}
  .sec-title .em{font-style:italic;color:var(--gold-soft)}
  /* Authored breaks, same idea as the hero H1: the spans only become lines on a
     phone, where these two titles otherwise wrap at an awkward word. */
  .sec-title .l{display:inline}

  /* reveal-on-scroll is shared (animations.css + site.js) */

  /* ---------- SERVICES ---------- */
  .services{background:linear-gradient(180deg,transparent,var(--bg-2))}
  .svc-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--line-soft);border:1px solid var(--line-soft)}
  .svc{background:var(--bg);padding:46px 38px;transition:background .4s;position:relative}
  @media(hover:hover) and (pointer:fine){.svc:hover{background:var(--panel)}}
  /* One line icon per card, same corner on every card: stroked, no fill, no disc and no
     plate behind it - the card's own surface is the background. It sits ABOVE the
     heading's line, in the card's top-right corner, so it reads as a mark on the card
     rather than a bullet beside the title - which also means the title needs no reserved
     width and can run its full length. It names nothing the heading does not, so it is
     aria-hidden. */
  .svc-icon{position:absolute;top:14px;right:26px;width:28px;height:28px;
    stroke:var(--gold-soft);fill:none;stroke-width:1.4;stroke-linecap:round;
    stroke-linejoin:round;opacity:.75;pointer-events:none}
  .svc h3{font-family:var(--serif);font-weight:400;font-size:1.55rem;color:var(--gold-soft);
    margin-bottom:14px}
  .svc p{color:var(--ink-soft);font-size:.95rem}
  @media(max-width:820px){
    .svc-grid{grid-template-columns:1fr}
    .svc{padding:34px 30px}
    .svc-icon{top:10px;right:22px;width:24px;height:24px}
  }

  /* ---------- RESULTS GALLERY ---------- */
  .gallery-grid{
    display:grid;grid-template-columns:repeat(12,1fr);gap:14px;grid-auto-rows:200px;
  }
  .shot{
    position:relative;overflow:hidden;border-radius:3px;background:var(--panel);
    border:1px solid var(--line-soft);
  }
  /* placeholder look until real photos dropped in */
  .shot .ph{
    position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
    color:var(--ink-dim);font-size:.78rem;letter-spacing:.12em;text-transform:uppercase;
    background:
      repeating-linear-gradient(45deg,rgba(201,165,88,.04) 0 14px,transparent 14px 28px),
      var(--panel);
  }
  .shot img{width:100%;height:100%;object-fit:cover;display:block;transition:transform .7s}
  @media(hover:hover) and (pointer:fine){.shot:hover img{transform:scale(1.05)}}
  .shot .tag{
    position:absolute;left:14px;bottom:14px;z-index:2;
    font-size:.7rem;letter-spacing:.12em;text-transform:uppercase;color:var(--ink);
    background:rgba(15,14,12,.6);backdrop-filter:blur(4px);padding:5px 12px;border-radius:2px;
    border:1px solid var(--line);
  }
  .shot::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,transparent 55%,rgba(15,14,12,.55));z-index:1;pointer-events:none}
  /* layout spans */
  .g1{grid-column:span 5;grid-row:span 2}
  .g2{grid-column:span 4;grid-row:span 1}
  .g3{grid-column:span 3;grid-row:span 1}
  .g4{grid-column:span 4;grid-row:span 1}
  .g5{grid-column:span 3;grid-row:span 1}
  .g6{grid-column:span 4;grid-row:span 1}
  .g7{grid-column:span 8;grid-row:span 1}
  @media(max-width:820px){
    .gallery-grid{grid-template-columns:repeat(2,1fr);grid-auto-rows:160px}
    .g1,.g2,.g3,.g4,.g5,.g6,.g7{grid-column:span 1;grid-row:span 1}
    .g1{grid-column:span 2;grid-row:span 2}
  }

  /* ---------- WHY ---------- */
  .why{background:var(--bg-2)}
  .why-grid{display:grid;grid-template-columns:1fr 1fr;gap:70px;align-items:center}
  @media(max-width:820px){.why-grid{grid-template-columns:1fr;gap:40px}}
  .why-list{display:flex;flex-direction:column;gap:0}
  .why-item{padding:26px 0;border-top:1px solid var(--line-soft);display:flex;gap:22px}
  .why-item:last-child{border-bottom:1px solid var(--line-soft)}
  .why-item .n{font-family:var(--serif);font-style:italic;color:var(--gold);font-size:1.1rem;min-width:34px}
  .why-item h3{font-family:var(--serif);font-weight:400;font-size:1.25rem;margin-bottom:6px}
  .why-item p{color:var(--ink-soft);font-size:.92rem}

  /* ---------- AREA ---------- */
  .area-chips{display:flex;flex-wrap:wrap;gap:12px;margin-top:10px}
  /* Coverage check: one field, one answer line. The answer is a live region so a
     screen reader hears the verdict without moving focus. */
  /* Field and verdict sit on one line; the answer wraps under the field only when
     the row no longer fits. The section head's own gap is tightened here - the field
     belongs to the heading, so the default 62px read as a hole. */
  .area-check{display:flex;flex-wrap:wrap;align-items:center;gap:16px;margin:0 0 6px}
  .area-field{position:relative;flex:0 1 340px}
  .area-check input{width:100%;padding-right:46px}
  /* Clear button: drawn from the input's own box rather than added as an icon file,
     and it only exists once there is something to clear. */
  .area-clear{
    position:absolute;top:50%;right:7px;width:34px;height:34px;
    display:flex;align-items:center;justify-content:center;
    background:none;border:none;cursor:pointer;padding:0;border-radius:50%;
    opacity:0;transform:translateY(-50%) scale(.8);pointer-events:none;
    transition:opacity .3s var(--ease-standard),transform .34s var(--ease-settle),
               background-color .3s var(--ease-standard);
    -webkit-tap-highlight-color:transparent;
  }
  .area-clear.show{opacity:1;transform:translateY(-50%) scale(1);pointer-events:auto}
  .area-clear::before,.area-clear::after{
    content:'';position:absolute;width:13px;height:1.5px;border-radius:1px;
    background:var(--ink-dim);transition:background-color .3s var(--ease-standard);
  }
  .area-clear::before{transform:rotate(45deg)}
  .area-clear::after{transform:rotate(-45deg)}
  @media(hover:hover) and (pointer:fine){
    .area-clear:hover{background:rgba(201,165,88,.1)}
    .area-clear:hover::before,.area-clear:hover::after{background:var(--gold)}}
  .area-clear:active{background:rgba(201,165,88,.1)}
  .area-clear:active::before,.area-clear:active::after{background:var(--gold)}
  @media(prefers-reduced-motion:reduce){.area-clear{transition:none}}
  .area-check input{
    width:100%;background:var(--bg);border:1px solid var(--line-soft);color:var(--ink);
    padding:14px 16px;border-radius:2px;font-family:var(--sans);font-size:.95rem;
    transition:border-color .25s;
  }
  .area-check input:focus{outline:none;border-color:var(--gold)}
  .area-check input:-webkit-autofill,
  .area-check input:-webkit-autofill:hover,
  .area-check input:-webkit-autofill:focus{
    -webkit-text-fill-color:var(--ink);caret-color:var(--ink);
    -webkit-box-shadow:0 0 0 100px var(--bg) inset;box-shadow:0 0 0 100px var(--bg) inset;
    transition:background-color 9999s ease-out;
  }
  .area-check input::placeholder{color:var(--ink-dim)}
  /* One line, two jobs: it explains the map until someone asks a question, then it
     answers. The resting text ships in the markup so it is there without JS. */
  .area-answer{flex:1 1 240px;margin:0;font-size:.9rem;line-height:1.55;color:var(--ink-soft)}
  .area-answer.is-hint{font-size:.82rem;color:var(--ink-dim);letter-spacing:.03em}
  .area-answer[hidden]{display:none}
  .area-answer.is-covered{color:var(--gold-soft)}
  .area-answer a{color:var(--gold);text-decoration:none;border-bottom:1px solid rgba(201,165,88,.35)}
  @media(hover:hover) and (pointer:fine){.area-answer a:hover{border-bottom-color:var(--gold)}}
  /* The city list is ONE container in the owner's order - that order is the desktop
     layout and must not be reshuffled. On a phone the four showcased cities are pulled
     to the front with flex order (DOM order untouched, so the served markup keeps the
     owner's sequence for indexing) and the rest are collapsed behind max-height. The
     collapsed height is measured from the fourth chip by the page script, because it
     depends on how the chips wrap at that width. */
  @media(max-width:760px){
    /* Not the menu's timings: this animates ~600px of height, and ease-settle
       (easeOutQuint) covers three quarters of that in the first third of the run, which
       reads as a jump rather than an unfold. The standard curve spreads the travel, and
       the distance needs longer than a sliding panel does. */
    /* Addressed by id on purpose: `html.reveal-armed .reveal` owns this element's
       transition and outranks a class selector, so a plain .area-chips rule was
       dropped and the list snapped open with no animation at all. The reveal's own
       opacity/transform legs are restated here so the section still fades in. */
    #area-chips{
      max-height:var(--chips-collapsed,116px);overflow:hidden;
      transition:max-height 620ms var(--ease-standard),
                 opacity var(--dur-slow) var(--ease),transform var(--dur-slow) var(--ease);
    }
    #area-chips.open{
      max-height:var(--chips-full,2000px);
      transition:max-height 760ms var(--ease-standard),
                 opacity var(--dur-slow) var(--ease),transform var(--dur-slow) var(--ease);
    }
    .area-chips .chip.feature{order:-1}
  }
  @media(prefers-reduced-motion:reduce){#area-chips,#area-chips.open{transition:none}}
  /* Phone-only control (the desktop shows every city), so it is sized like one: full
     width, centred label, gold tinted rather than a thin outline that reads as a link.
     The chevron flips with aria-expanded - the state is in the markup, not in JS. */
  .area-toggle{
    display:flex;align-items:center;justify-content:center;gap:10px;
    width:100%;margin-top:20px;padding:15px 22px;border-radius:40px;cursor:pointer;
    background:rgba(201,165,88,.09);border:1px solid rgba(201,165,88,.32);
    color:var(--gold-soft);font-family:var(--sans);font-size:.82rem;
    letter-spacing:.12em;text-transform:uppercase;
    transition:background-color .32s var(--ease-standard),border-color .32s var(--ease-standard),
               color .32s var(--ease-standard);
  }
  .area-toggle::after{
    content:'';width:7px;height:7px;flex:0 0 auto;
    border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
    transform:translateY(-2px) rotate(45deg);
    transition:transform .42s var(--ease-settle);
  }
  .area-toggle[aria-expanded="true"]::after{transform:translateY(1px) rotate(-135deg)}
  @media(hover:hover) and (pointer:fine){
    .area-toggle:hover{background:rgba(201,165,88,.17);border-color:var(--gold);color:var(--gold)}
  }
  .area-toggle:active{
    background:rgba(201,165,88,.17);border-color:var(--gold);color:var(--gold);
  }
  @media(prefers-reduced-motion:reduce){.area-toggle,.area-toggle::after{transition:none}}
  /* Desktop lists every city, so the control has nothing to do there. Declared after
     the button's own rules - a later display:flex would otherwise revive it. */
  @media(min-width:761px){.area-toggle{display:none}}
  /* The gold sweeps in from the left behind the label rather than the border simply
     recolouring; the chip lifts a little with it. The fill is a pseudo-element under
     the text (isolation keeps its negative z-index inside the chip), so the pill's
     radius clips it for free. */
  /* A real <button> now - it flies the map to its city - so the UA button skin is
     reset back to the chip it has always looked like. */
  .chip{
    position:relative;isolation:isolate;overflow:hidden;
    border:1px solid var(--line);color:var(--ink-soft);background:transparent;
    font-family:var(--sans);line-height:1.2;cursor:pointer;
    padding:11px 22px;border-radius:40px;font-size:.85rem;letter-spacing:.04em;
    transition:color .48s var(--ease-standard),border-color .48s var(--ease-standard),
               transform .48s var(--ease-settle);
  }
  .chip:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
  /* The chips cascade when their rail reveals. They are not reveal targets themselves -
     one observer for the rail is enough - so the container's .in is what releases them,
     and --i (from animations.js) spaces them out.
     The transform reset deliberately spares a matched or flown-to chip: those carry a
     lift of their own, and a blanket transform:none here would flatten the answer. */
  html.reveal-armed .area-chips .chip{opacity:0;transform:translateY(8px);
    transition:opacity var(--dur-slow) var(--ease-standard),
               transform var(--dur-slow) var(--ease-settle);
    transition-delay:calc(var(--i,0) * var(--stagger))}
  html.reveal-armed .area-chips.in .chip{opacity:1}
  html.reveal-armed .area-chips.in .chip:not(.is-match):not(.is-active){transform:none}
  @media(prefers-reduced-motion:reduce){
    html.reveal-armed .area-chips .chip{transition:none!important;opacity:1!important;transform:none!important}
  }
  .chip::before{
    content:"";position:absolute;inset:0;z-index:-1;background:var(--gold);
    transform:scaleX(0);transform-origin:left center;
    transition:transform .57s var(--ease-settle);
  }
  /* Two states, one look: is-match is the coverage answer, is-active is the city the
     map is currently flown to. They share the fill because they mean the same thing to
     the eye - "this one" - and an address check does both at once. */
  .chip.is-match,.chip.is-active{border-color:var(--gold);color:var(--bg);transform:translateY(-2px)}
  .chip.is-match::before,.chip.is-active::before{transform:scaleX(1)}
  @media(hover:hover) and (pointer:fine){
    .chip:hover{border-color:var(--gold);color:var(--bg);transform:translateY(-2px)}
    .chip:hover::before{transform:scaleX(1)}}
  /* The matched chip also gets one nudge, so it reads as an answer rather than as a
     chip the pointer happens to rest on. */
  .chip.is-match{animation:chip-match .75s var(--ease-settle)}
  @keyframes chip-match{
    0%{transform:translateY(-2px) scale(1)}
    45%{transform:translateY(-2px) scale(1.06)}
    100%{transform:translateY(-2px) scale(1)}
  }
  @media(prefers-reduced-motion:reduce){
    .chip,.chip::before{transition:none}
    .chip.is-match{animation:none}
  }
  /* coverage map */
  .area-map-wrap{margin:34px 0 30px;border:1px solid var(--line-soft);border-radius:6px;overflow:hidden;position:relative;isolation:isolate}
  #area-map{height:420px;width:100%;background:var(--bg-2)}
  @media(max-width:620px){#area-map{height:320px}}
  /* The dark Leaflet skin lives in the SHARED owner (css/shared/map.css),
     loaded by this page and the admin editor alike. */

  /* ---------- QUOTE FORM ---------- */
  .quote{background:linear-gradient(180deg,var(--bg-2),var(--bg))}
  .quote-box{
    display:grid;grid-template-columns:.85fr 1.15fr;gap:0;
    border:1px solid var(--line);border-radius:4px;overflow:hidden;
  }
  @media(max-width:820px){.quote-box{grid-template-columns:1fr}}
  .quote-aside{background:var(--panel);padding:54px 46px;display:flex;flex-direction:column;justify-content:center}
  .quote-aside h3{font-family:var(--serif);font-weight:300;font-size:2rem;line-height:1.1;margin-bottom:18px}
  .quote-aside h3 .em{font-style:italic;color:var(--gold-soft)}
  .quote-aside p{color:var(--ink-soft);font-size:.95rem;margin-bottom:26px}
  .quote-contact{font-size:.9rem;color:var(--ink-dim);line-height:2}
  .quote-contact a{color:var(--gold);text-decoration:none}
  .quote-altcontact{margin-top:20px;font-size:.82rem;color:var(--ink-dim);letter-spacing:.02em}
  .quote-altcontact a{color:var(--ink-soft);text-decoration:none}
  @media(hover:hover) and (pointer:fine){.quote-altcontact a:hover{color:var(--gold)}}
  .quote-form{padding:48px 46px;display:flex;flex-direction:column;gap:20px}
  .field label{display:block;font-size:.74rem;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-dim);margin-bottom:9px}
  .field input,.field select,.field textarea{
    width:100%;background:var(--bg);border:1px solid var(--line-soft);
    color:var(--ink);padding:14px 16px;border-radius:2px;font-family:var(--sans);
    font-size:.95rem;transition:border-color .25s;
  }
  .field input:focus,.field select:focus,.field textarea:focus{outline:none;border-color:var(--gold)}
  .char-count{font-size:.72rem;color:var(--ink-dim);margin-top:6px;text-align:right;letter-spacing:.02em}
  .char-count.low{color:var(--gold-soft)}
  .field-err{font-size:.78rem;color:#e0846b;margin-top:5px;min-height:1.1em;line-height:1.3}
  .field-hint{font-size:.75rem;color:var(--ink-dim);margin-top:5px;line-height:1.3}
  .field-row{display:grid;grid-template-columns:1fr 1fr;gap:16px}
  @media(max-width:520px){.field-row{grid-template-columns:1fr}}
  .quote-form .btn-gold{margin-top:8px;text-align:center;border:none}
  .form-status{margin-top:14px;font-size:.86rem;min-height:1.2em;letter-spacing:.02em}
  .form-status.ok{color:var(--gold-soft)}
  .form-status.err{color:#e0846b}
  .wa-confirm{display:flex;flex-direction:column;gap:6px;margin-top:14px;align-items:flex-start}
  .wa-btn{display:inline-flex;align-items:center;gap:9px;background:#25d366;color:#fff;
    text-decoration:none;font-size:.9rem;font-weight:500;padding:11px 18px;border-radius:4px;
    transition:background .2s}
  @media(hover:hover) and (pointer:fine){.wa-btn:hover{background:#1faa52}}
  .wa-btn:active{background:#1faa52}
  .wa-btn svg{width:18px;height:18px;fill:#fff;flex:0 0 18px}
  .wa-sub{font-size:.76rem;color:var(--ink-dim)}

  /* footer and .socials are shared (site.css) */

  /* ---------- SOCIAL (quote aside only; .socials is shared) ---------- */
  .quote-socials{display:flex;gap:14px;margin-top:24px}
  .quote-socials a{
    width:40px;height:40px;display:flex;align-items:center;justify-content:center;
    border:1px solid var(--line);border-radius:50%;color:var(--ink-soft);transition:all .3s;
  }
  @media(hover:hover) and (pointer:fine){.quote-socials a:hover{border-color:var(--gold);color:var(--gold)}}
  .quote-socials a:active{border-color:var(--gold);color:var(--gold)}
  .quote-socials svg{width:18px;height:18px;fill:currentColor}

  /* back-to-top is shared (site.css); the home page sits it a little higher. */
  .to-top{bottom:24px}

  /* notification preference checkboxes */
  .notify-row{display:flex;flex-wrap:wrap;gap:12px}
  .notify-opt{
    display:inline-flex;align-items:center;cursor:pointer;
    border:1px solid var(--line-soft);background:var(--bg);
    padding:12px 18px;border-radius:3px;transition:all .25s;
    font-size:.9rem;color:var(--ink-soft);user-select:none;line-height:1;
  }
  @media(hover:hover) and (pointer:fine){.notify-opt:hover{border-color:var(--line)}}
  .notify-opt input{accent-color:var(--gold);width:17px;height:17px;cursor:pointer;flex:0 0 auto;margin:0 10px 0 0}
  .notify-opt span{white-space:nowrap}
  .notify-opt input:checked + span{color:var(--gold-soft)}
  .notify-hint{font-size:.78rem;color:var(--ink-dim);margin-top:8px;min-height:1em}
  .notify-legal{font-size:.72rem;line-height:1.5;color:var(--ink-dim);margin-top:6px;max-width:60ch}
  .notify-opt.disabled{opacity:.4;pointer-events:none}

  /* ---------- PASSIVE LEGAL NOTICE ----------
     One informational sentence with visible links, immediately above the submit
     button. It is not a control: no checkbox, no error slot, and nothing here
     enables or disables submitting. It deliberately does NOT use
     white-space:nowrap anywhere - on a narrow phone the sentence wraps to three or
     four lines rather than being clipped or forcing a horizontal scroll. */
  .legal-notice{margin:14px 0 10px;text-align:left;font-size:.78rem;
    line-height:1.55;color:var(--ink-dim);max-width:60ch}
  /* Links must READ as links inside a sentence - underlined and coloured, not
     styled down into body text. */
  .legal-notice a{color:var(--gold-soft);text-decoration:underline}
  @media(hover:hover) and (pointer:fine){.legal-notice a:hover{color:var(--gold)}}
  .legal-recurring{border:1px solid var(--line-soft);border-radius:3px;
    padding:12px 14px;background:var(--bg);margin-top:12px}
  .legal-recurring .legal-disclosure{font-size:.76rem;line-height:1.6;
    color:var(--ink-dim);margin:0}
  .legal-recurring .legal-disclosure b{color:var(--ink-soft)}

  /* ---------- BOOKING WIZARD ---------- */
  .wiz{border:1px solid var(--line);border-radius:4px;overflow:clip;background:var(--bg-2)}
  .wiz-steps{display:flex;align-items:center;gap:16px;padding:22px 40px;border-bottom:1px solid var(--line-soft);background:var(--panel)}
  @media(max-width:520px){.wiz-steps{padding:18px 22px;gap:10px}}
  .wiz-step{display:flex;align-items:center;gap:10px;font-size:.84rem;letter-spacing:.04em;color:var(--ink-dim);white-space:nowrap}
  .wiz-step .num{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;border:1px solid var(--line);font-size:.78rem;color:var(--ink-dim)}
  .wiz-step.active{color:var(--ink)}
  .wiz-step.active .num{background:var(--gold);border-color:var(--gold);color:var(--bg)}
  .wiz-step.done .num{border-color:var(--gold);color:var(--gold)}
  .wiz-line{flex:1;height:1px;background:var(--line-soft);min-width:20px}
  @media(max-width:520px){.wiz-step span:not(.num){display:none}}

  .wiz-grid{display:grid;grid-template-columns:1.45fr .85fr}
  @media(max-width:880px){.wiz-grid{grid-template-columns:1fr}}
  .wiz-main{padding:38px 40px;display:flex;flex-direction:column;gap:28px;align-self:start}
  @media(max-width:520px){.wiz-main{padding:28px 22px}}
  .wiz-divider{height:1px;background:var(--line-soft)}
  .wiz-summary-cell{border-left:1px solid var(--line-soft)}
  @media(max-width:880px){.wiz-summary-cell{border-left:none;border-top:1px solid var(--line-soft)}}
  .wiz-summary{
    position:sticky;top:90px;background:var(--panel);padding:38px 34px;
    display:flex;flex-direction:column;justify-content:flex-start;
  }
  @media(max-width:880px){.wiz-summary{position:static}}
  .wiz-summary .btn{text-align:center;width:100%;cursor:pointer;margin-top:6px}
  .wiz-summary .btn-gold{border:none}
  .wiz-summary .btn-ghost{background:transparent;border:1px solid var(--line);color:var(--ink-soft)}
  @media(hover:hover) and (pointer:fine){.wiz-summary .btn-ghost:hover{border-color:var(--gold);color:var(--gold)}}
  .pay-later-info{font-size:.82rem;color:var(--ink-dim);margin:14px 0 4px}
  .glabel-hint{text-transform:none;letter-spacing:0;color:var(--ink-dim);font-size:.72rem;margin-left:8px}

  /* calendar + slots */
  /* Fail-closed scheduling state: shown instead of the calendar when the
     server scheduling contract is unavailable or invalid. */
  .sched-state{font-size:.84rem;color:var(--ink-dim);padding:18px 0;text-align:center}
  .sched-state[hidden]{display:none}
  .sched-retry{margin-left:10px;font:inherit;color:var(--ink);background:none;
    border:1px solid var(--line);border-radius:8px;padding:5px 12px;cursor:pointer}
  .sched-retry:disabled{opacity:.55;cursor:default}
  .sched{display:grid;grid-template-columns:1.25fr .9fr;gap:22px}
  @media(max-width:620px){.sched{grid-template-columns:1fr;gap:18px}}
  .cal-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}
  .cal-title{font-size:.95rem;color:var(--ink);letter-spacing:.02em}
  .cal-nav{background:transparent;border:1px solid var(--line);color:var(--ink-soft);width:32px;height:32px;border-radius:3px;cursor:pointer;font-size:1rem;line-height:1;transition:all .2s}
  @media(hover:hover) and (pointer:fine){.cal-nav:hover{border-color:var(--gold);color:var(--gold)}}
  .cal-nav:active{border-color:var(--gold);color:var(--gold)}
  .cal-dow{display:grid;grid-template-columns:repeat(7,1fr);gap:3px;margin-bottom:6px}
  .cal-dow span{text-align:center;font-size:.66rem;color:var(--ink-soft);letter-spacing:.05em}
  .cal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:3px}
  /* The ARIA date grid needs rows; the CSS grid needs the cells to be its own direct
     children. display:contents gives both - the row exists in the accessibility tree
     and contributes no box of its own. */
  .cal-row{display:contents}
  /* The keyboard ring must not be mistakable for the SELECTED day, which is a filled
     gold cell - this is an outline outside a cell that is merely focused. */
  .cal-cell:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
  /* The calendar's bottom row: shortcuts left, reset right. It keeps its height whether
     or not either control is showing, so the calendar does not resize as a date is
     chosen and cleared. margin-left:auto rather than space-between, so the reset stays
     right even on a phone where the shortcuts button is not rendered at all. */
  /* Row 2 of the scheduling grid, in the calendar's own column, so on desktop it sits
     directly under the calendar as before. On one column it is simply the last row -
     under the times - which is where a reset belongs when everything is stacked. */
  .cal-foot{grid-column:1;display:flex;align-items:center;gap:12px;min-height:44px}
  /* 44px of tap area, with the icon at its own smaller size inside - the round backdrop
     appears under it on hover, as in the reference. */
  .cal-kbd{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;
    padding:0;background:transparent;border:none;border-radius:50%;cursor:pointer;
    color:var(--ink-dim);
    transition:color 150ms var(--ease),background-color 150ms var(--ease)}
  .cal-kbd svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.4;
    stroke-linecap:round;stroke-linejoin:round}
  @media(hover:hover) and (pointer:fine){
    .cal-kbd:hover{color:var(--gold);background-color:rgba(201,165,88,.12)}
  }
  .cal-kbd:active{color:var(--gold);background-color:rgba(201,165,88,.12)}
  .cal-kbd:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
  /* No physical keyboard, no shortcuts worth documenting. */
  @media(max-width:760px){
    /* No physical keyboard, no shortcuts worth documenting. */
    .cal-kbd{display:none}
    /* A finger needs the full target even though the label is small. */
    .cal-clear{min-height:44px}
  }
  /* The console's outline button - the same face as Fit all in GeoService: transparent
     with a gold edge, filling gold on hover. Restated here rather than reused, because
     this page does not load components.css where that button lives. It carries no
     .btn-label / .btn-arrow spans, so the CTA hover arrow never applies: this is a
     reset, not a call to action. */
  .cal-clear{margin-left:auto;cursor:pointer;display:inline-flex;align-items:center;
    justify-content:center;height:var(--ctrl-h);padding:0 var(--sp-5);
    border:1px solid var(--gold);border-radius:var(--radius-sm);background:transparent;
    color:var(--gold);font-family:var(--sans);font-size:.8rem;letter-spacing:.06em;
    line-height:1;white-space:nowrap;
    transition:background var(--dur) var(--ease),color var(--dur) var(--ease)}
  @media(hover:hover) and (pointer:fine){.cal-clear:hover{background:var(--gold);color:var(--bg)}}
  .cal-clear:active{background:var(--gold);color:var(--bg)}
  .cal-clear:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
  .slot:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
  .cal-cell{aspect-ratio:1;display:flex;align-items:center;justify-content:center;font-size:.86rem;border-radius:3px;color:var(--ink);cursor:pointer;border:1px solid transparent;transition:all .15s}
  @media(hover:hover) and (pointer:fine){.cal-cell:hover:not(.dis):not(.sel):not(.empty){border-color:var(--line);background:rgba(201,165,88,.06)}}
  .cal-cell:active:not(.dis):not(.sel):not(.empty){border-color:var(--line);background:rgba(201,165,88,.06)}
  .cal-cell.dis{color:var(--ink-dim);opacity:.35;cursor:default}
  .cal-cell.sel{background:var(--gold);color:var(--bg);border-color:var(--gold);font-weight:500}
  /* A leading blank only offsets the first day into its column - it is not a day and
     must never light up. The pointer cannot reach it at all. */
  .cal-cell.empty{cursor:default;pointer-events:none;border-color:transparent;background:none}
  /* A holiday is marked with a THIN inset ring, not a border: the border belongs to the
     hover and selected states, and a holiday must not argue with either. Inside a
     selected (gold-filled) cell the ring flips to the page ink so it stays readable. */
  .cal-cell.hol{box-shadow:inset 0 0 0 1px rgba(201,165,88,.55)}
  .cal-cell.hol.sel{box-shadow:inset 0 0 0 1px rgba(15,14,12,.45)}
  /* Name on hover, above the day. Pure CSS from the cell's own data attribute, and
     pointer-events:none so it can never swallow a click or a tap. */
  .cal-cell[data-holiday]{position:relative}
  .cal-cell[data-holiday]::after{
    content:attr(data-holiday);position:absolute;bottom:calc(100% + 6px);left:50%;
    transform:translateX(-50%) translateY(2px);z-index:5;pointer-events:none;
    padding:5px 9px;border-radius:4px;border:1px solid var(--line);
    background:var(--panel);color:var(--ink);white-space:nowrap;
    font-size:.7rem;letter-spacing:.02em;line-height:1;
    opacity:0;transition:opacity 150ms var(--ease),transform 150ms var(--ease)}
  @media(hover:hover) and (pointer:fine){
    .cal-cell[data-holiday]:hover::after{opacity:1;transform:translateX(-50%) translateY(0)}
  }
  /* No hover on touch, so the tooltip never appears there; the name rides the slot
     panel's heading instead. */
  @media(prefers-reduced-motion:reduce){.cal-cell[data-holiday]::after{transition:none}}


  .slots-label{font-size:.66rem;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-dim);margin-bottom:12px}
  .slots-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;max-height:230px;overflow-y:auto}
  @media(max-width:620px){.slots-grid{grid-template-columns:repeat(4,1fr);max-height:none}}
  .slot{border:1px solid var(--line-soft);background:var(--bg);border-radius:3px;padding:10px 6px;text-align:center;font-size:.82rem;color:var(--ink-soft);cursor:pointer;transition:all .15s;white-space:nowrap}
  @media(hover:hover) and (pointer:fine){.slot:hover{border-color:var(--line);color:var(--ink)}}
  .slot:active{border-color:var(--line);color:var(--ink)}
  .slot.sel{background:rgba(201,165,88,.12);border-color:var(--gold);color:var(--gold-soft)}
  .slots-empty{font-size:.84rem;color:var(--ink-dim);padding:24px 0;text-align:center;grid-column:1/-1}

  /* ---------- keyboard shortcuts dialog ---------- */
  /* Reuses the page's modal shell; only the list inside is its own. Dark panel and gold
     key capsules - the reference is white, our surface is not. */
  .kbd-modal{max-width:460px}
  /* Title left, close right - the same icon control the notification panel uses, at the
     same 44px tap size. */
  .kbd-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}
  .kbd-head h3{margin:0}
  .kbd-close{width:44px;height:44px;min-width:44px;flex:0 0 auto;margin:-8px -8px 0 0;padding:0;
    display:inline-flex;align-items:center;justify-content:center;background:none;
    border:1px solid var(--line);border-radius:10px;color:var(--ink-soft);cursor:pointer;
    transition:color var(--dur) var(--ease),border-color var(--dur) var(--ease)}
  .kbd-close svg{width:20px;height:20px;display:block;fill:none;stroke:currentColor;
    stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
  @media(hover:hover) and (pointer:fine){.kbd-close:hover{color:var(--gold);border-color:var(--gold)}}
  .kbd-close:active{color:var(--gold);border-color:var(--gold)}
  .kbd-close:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
  .kbd-list{display:grid;grid-template-columns:auto 1fr;gap:14px 18px;align-items:center;
    margin:22px 0 4px}
  .kbd-list dt{display:flex;gap:4px;align-items:center}
  .kbd-list dd{margin:0;font-size:.86rem;color:var(--ink-soft);line-height:1.4}
  .kbd-sep{color:var(--ink-dim);font-size:.78rem}
  .kbd{display:inline-flex;align-items:center;justify-content:center;min-width:30px;height:26px;
    padding:0 8px;border:1px solid var(--gold);border-radius:4px;background:rgba(201,165,88,.08);
    color:var(--gold-soft);font-family:var(--sans);font-size:.72rem;letter-spacing:.04em;
    white-space:nowrap}
  /* Drawn, not typed: the self-hosted Outfit subset carries no return or left/right
     arrow, so a glyph would fall back to a system face beside the lettered keys. */
  .kbd-arrow{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:1.6;
    stroke-linecap:round;stroke-linejoin:round}

  /* ---------- CONFIRM MODAL ---------- */
  .modal-overlay{
    position:fixed;inset:0;z-index:100;display:none;
    align-items:center;justify-content:center;padding:24px;
    background:rgba(8,7,6,.72);backdrop-filter:blur(4px);
  }
  .modal-overlay.open{display:flex}
  .modal{
    background:var(--bg-2);border:1px solid var(--line);border-radius:6px;
    max-width:460px;width:100%;padding:36px 34px;
    box-shadow:0 30px 80px rgba(0,0,0,.55);
    animation:rise .3s forwards;
  }
  .modal h3{font-family:var(--serif);font-weight:300;font-size:1.7rem;margin-bottom:22px}
  .modal-body{display:flex;flex-direction:column;gap:9px;margin-bottom:28px}
  .modal-row{display:flex;justify-content:space-between;gap:18px;font-size:.92rem;border-bottom:1px solid var(--line-soft);padding-bottom:9px}
  .modal-row:last-child{border-bottom:none}
  .modal-row .k{color:var(--ink-dim);letter-spacing:.02em}
  .modal-row .v{color:var(--ink);text-align:right}
  .modal-row.total .k,.modal-row.total .v{color:var(--gold-soft);font-size:1.05rem}
  /* The post-submit answer. The heading is the same sentence every accepted
     submission gets; the note under it is the only part that differs, because a
     held time and an unheld one are genuinely different news. The countdown is
     tabular so the digits do not shift while it runs. */
  .form-status .hold-head{font-weight:600}
  .form-status .hold-note{margin-top:6px;font-size:.9em;opacity:.92}
  .form-status .hold-left{margin-top:6px;font-variant-numeric:tabular-nums;
    letter-spacing:.02em}

  /* The human check sits between the review and the confirm button, so it is
     solved for the submit it belongs to. Hidden until the page is told the
     deployment has a site key. */
  .turnstile-slot{display:flex;justify-content:center;margin:0 0 18px}
  .turnstile-slot[hidden]{display:none}
  .turnstile-note{margin:0 0 14px;text-align:center;font-size:.82rem;color:#e0846b}
  .turnstile-note[hidden]{display:none}
  .modal-actions{display:flex;gap:12px;justify-content:flex-end}
  .modal-actions .btn{cursor:pointer}
  .modal-actions .btn-ghost{background:transparent;border:1px solid var(--line);color:var(--ink-soft)}
  @media(hover:hover) and (pointer:fine){.modal-actions .btn-ghost:hover{border-color:var(--gold);color:var(--gold)}}
  @media(max-width:520px){.modal-actions{flex-direction:column-reverse}.modal-actions .btn{width:100%;text-align:center}}

  /* ---------- CALCULATOR ---------- */
  .calc{background:linear-gradient(180deg,var(--bg),var(--bg-2))}
  .calc-box{display:grid;grid-template-columns:1.3fr .9fr;gap:0;border:1px solid var(--line);border-radius:4px;overflow:hidden}
  @media(max-width:860px){.calc-box{grid-template-columns:1fr}}
  .calc-form{padding:42px 40px;display:flex;flex-direction:column;gap:30px}
  @media(max-width:520px){.calc-form{padding:30px 22px}}
  .calc-group .glabel{display:block;font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;color:var(--gold);margin-bottom:16px}
  .opt-grid{display:flex;flex-wrap:wrap;gap:10px}
  .opt{
    border:1px solid var(--line-soft);background:var(--bg);color:var(--ink-soft);
    padding:11px 18px;border-radius:3px;font-size:.88rem;cursor:pointer;
    transition:all .25s;user-select:none;font-family:var(--sans);
  }
  @media(hover:hover) and (pointer:fine){.opt:hover{border-color:var(--line);color:var(--ink)}}
  .opt:active{border-color:var(--line);color:var(--ink)}
  .opt.active{border-color:var(--gold);background:rgba(201,165,88,.08);color:var(--gold-soft)}
  .freq-note{font-size:.8rem;color:var(--ink-dim);margin-top:10px;line-height:1.5}
  .freq-note b{color:var(--gold-soft);font-weight:500}
  .addon-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
  @media(max-width:520px){.addon-grid{grid-template-columns:1fr}}
  .addon{
    display:flex;align-items:center;gap:10px;
    border:1px solid var(--line-soft);background:var(--bg);border-radius:3px;
    padding:13px 15px;cursor:pointer;transition:all .25s;
  }
  @media(hover:hover) and (pointer:fine){.addon:hover{border-color:var(--line)}}
  .addon:active{border-color:var(--line)}
  .addon.active{border-color:var(--gold);background:rgba(201,165,88,.06)}
  .addon .ai{width:20px;height:20px;flex:0 0 20px;fill:var(--ink-dim);transition:fill .25s}
  .addon.active .ai{fill:var(--gold)}
  .addon .nm{font-size:.88rem;color:var(--ink)}
  .addon .pr{font-size:.82rem;color:var(--ink-dim);margin-left:auto}
  .addon.active .pr{color:var(--gold)}
  .pets-row{display:flex;align-items:center;gap:14px}
  .pets-row .q{font-size:.88rem;color:var(--ink-soft)}

  .calc-summary{
    background:var(--panel);padding:42px 36px;display:flex;flex-direction:column;
    justify-content:center;border-left:1px solid var(--line-soft);
  }
  @media(max-width:860px){.calc-summary{border-left:none;border-top:1px solid var(--line-soft)}}
  .est-label{font-size:.74rem;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-dim);margin-bottom:10px}
  .est-price{font-family:var(--serif);font-weight:300;font-size:3rem;color:var(--gold-soft);line-height:1;margin-bottom:6px}
  .est-sub{font-size:.82rem;color:var(--ink-dim);margin-bottom:24px;line-height:1.5}
  .calc-lines{border-top:1px solid var(--line-soft);padding-top:18px;margin-bottom:24px;display:flex;flex-direction:column;gap:8px;min-height:20px}
  .calc-lines .ln{display:flex;justify-content:space-between;font-size:.84rem;color:var(--ink-soft)}
  .calc-lines .ln span:last-child{color:var(--ink)}
  .calc-summary .btn-gold{text-align:center;border:none;width:100%;cursor:pointer}
  .calc-note{font-size:.74rem;color:var(--ink-dim);margin-top:16px;line-height:1.5;text-align:center}

  /* ---------- INTL-TEL-INPUT DARK THEME ---------- */
  /* The four sprite paths are NOT here. A `url()` inside a custom property has no
     interoperable base: Chromium resolves it against the stylesheet that USES the
     property, the spec says the one that DECLARES it. That was invisible while this
     file and the vendored stylesheet sat at the same depth below static/ and one
     relative path happened to be correct from both bases - and it stopped being
     invisible the moment the vendored artifact moved into a version directory. They
     are absolute now, emitted with the pool version by index.html, where SSI can
     write it. */
  :root{
    --iti-border-color:rgba(201,165,88,.18);
    --iti-dropdown-bg:#1a1815;
    --iti-hover-color:rgba(201,165,88,.08);
    --iti-text-gray:#7d7464;
    --iti-dialcode-color:#b3aa98;
    --iti-arrow-color:#7d7464;
  }
  .iti{display:block}
  .iti__country-name{color:var(--ink-soft)}
  .iti__search-input{background:var(--bg);color:var(--ink);border-color:var(--line)!important}

  /* ---------- SIZE+BATHS ROW (desktop only) ---------- */
  .size-baths-row{display:grid;grid-template-columns:1fr 1fr;gap:24px}
  #opt-size,#opt-baths{display:grid;grid-template-columns:repeat(3,1fr)}
  #opt-service{display:grid;grid-template-columns:repeat(3,1fr)}
  #opt-freq{display:grid;grid-template-columns:repeat(4,1fr)}
  #opt-size .opt,#opt-baths .opt{display:flex;align-items:center;justify-content:center;gap:5px}
  .oi{width:13px;height:13px;fill:currentColor;flex-shrink:0;opacity:.6}
  .opt.active .oi{opacity:1}
  .mo-m{display:none}

  /* ---------- PT TOOLTIP ---------- */
  .pt-label{cursor:help;position:relative}
  .pt-label::after{
    content:'All times are Pacific Time (PT)';
    position:absolute;top:calc(100% + 4px);left:0;
    background:var(--panel);border:1px solid var(--line);
    color:var(--ink-soft);font-size:.75rem;white-space:nowrap;
    text-transform:none;letter-spacing:0;
    padding:5px 11px;border-radius:3px;z-index:10;
    pointer-events:none;opacity:0;transition:opacity .15s;
  }
  @media(hover:hover) and (pointer:fine){.pt-label:hover::after{opacity:1}}
  .pt-label:active::after{opacity:1}

  /* ---------- MOBILE FIXES ---------- */
  /* The four meta items need ~918px to share one row. Narrower than that the strip
     wraps, and a two-row ABSOLUTE strip lands on the CTA row whenever the viewport is
     short - so it goes back into the flow here, and the hero drops the bottom padding
     that existed only to clear it. */
  @media(max-width:920px){
    header{padding-bottom:56px}
    /* Same rule-on-the-content-column treatment as the pinned desktop strip: the
       gutter is a margin, not padding, so the border-top stops with the text. */
    .hero-meta{position:static;left:auto;right:auto;bottom:auto;margin:40px 28px 0;
      align-self:stretch;grid-template-columns:repeat(2,1fr);gap:24px 40px;width:auto;padding:24px 0 0}
  }
  @media(max-width:760px){
    header{min-height:auto;padding:124px 0 56px}
    section{padding:44px 0}
    .sec-head{margin-bottom:22px}
  }
  @media(max-width:620px){
    .area-chips{display:grid;grid-template-columns:1fr 1fr;gap:10px}
    .chip{text-align:center;justify-content:center}
    .size-baths-row{display:contents}
    #opt-size,#opt-baths,#opt-service{display:grid;grid-template-columns:repeat(3,1fr)}
    #opt-freq{display:grid;grid-template-columns:repeat(2,1fr)}
    .mo-m{display:inline}.mo-d{display:none}
    .opt{text-align:center;padding:10px 10px}
  }
  /* Phone hero only (the desktop kegel still fits down to 601px): the desktop
     font-size overflows the container here, so the browser adds its own break on top
     of the two authored .l lines. Size the headline off the viewport, and drop the
     eyebrow's 38px rule + wide tracking, which together cost about 50px and pushed it
     onto a second line. */
  @media(max-width:600px){
    .hero-meta{grid-template-columns:1fr}
    .sec-title .l{display:block}
    .sec-tag{gap:10px}
    .sec-tag::before{flex-basis:32px}
    header h1{font-size:clamp(1.95rem,8.7vw,2.5rem);line-height:1.1;letter-spacing:-.02em;text-wrap:balance}
    .eyebrow{font-size:.68rem;letter-spacing:.22em;gap:10px}
    .eyebrow::before{display:block;width:32px}
  }

/* ---------- GeoService: address autocomplete + zone note + empty map ---------- */
.addr-combo{position:relative}
.addr-listbox{position:absolute;left:0;right:0;top:100%;z-index:30;margin:4px 0 0;
  padding:4px;list-style:none;background:var(--bg,#141414);
  border:1px solid rgba(255,255,255,.14);border-radius:10px;
  box-shadow:0 10px 30px rgba(0,0,0,.45);max-height:240px;overflow:auto}
.addr-option{padding:8px 10px;border-radius:8px;cursor:pointer;font-size:14px;line-height:1.35}
.addr-option span{opacity:.65}
.addr-option.on{background:rgba(201,165,88,.18)}
.addr-attribution{padding:6px 10px 2px;font-size:12px;font-weight:400;
  font-style:normal;letter-spacing:normal;white-space:nowrap;color:#fff;
  text-align:right;cursor:default}
@media(hover:hover) and (pointer:fine){
  .addr-option:hover{background:rgba(201,165,88,.12)}
}
.addr-state-field{max-width:110px}
.addr-zone-note{margin:6px 0 0;padding:8px 10px;border-radius:8px;font-size:13px;
  background:rgba(201,165,88,.12);border:1px solid rgba(201,165,88,.35)}
#area-map.area-map-empty{display:flex;align-items:center;justify-content:center;
  color:rgba(255,255,255,.65);font-size:14px}

/* City marker/label styling lives in the shared map owner too. */
