/* ==========================================================================
   Novo express booking — the low-friction path: packages -> date -> add-on ->
   details -> reserve.

   The component owns NO heading and NO surrounding copy. Each page writes its
   own intro above the mount point, so the same calculator can be introduced
   differently on the homepage and the pricing page without forking it.

   Mount:  <div class="novo-express" data-full="/pricing/residential/"></div>
   Needs:  shared.css, booking.css, pricing-engine.js, express.js
   ========================================================================== */

/* ---- colour roles ---------------------------------------------------------
   The component is designed for a paper ground and every page that mounts it
   gives it one — the homepage packages band was dark and was changed to a
   light tint rather than the component growing a second palette. These stay as
   roles because they name what each colour is FOR, which is worth having even
   with one ground. */
:root{
  --x-fg:var(--ink); --x-soft:var(--ink-soft); --x-line:var(--line);
  --x-surface:var(--paper); --x-surface-2:var(--paper-deep);
  --x-accent:var(--gold-deep); --x-sel:rgba(59,47,38,.045);
  --x-fill:var(--ink); --x-fill-fg:var(--paper); --x-thumb-ring:var(--paper);
  --x-card-shadow:none;
  --x-feat-bg:var(--ink); --x-feat-fg:var(--paper); --x-feat-line:var(--ink);
  --x-feat-soft:rgba(251,248,242,.78); --x-feat-rule:rgba(251,248,242,.18);
  --x-feat-accent:var(--gold); --x-feat-btn:var(--gold); --x-feat-btn-fg:var(--ink);
  --x-feat-shadow:none;
}
.novo-express{display:block;}
/* The full calculator's frame. Lives here rather than on one page, because
   every page that mounts the panel can now carry the full calculator too. */
.calc-frame-wrap{background:var(--paper);border:1px solid var(--line);
  border-radius:var(--radius-lg);overflow:hidden;box-shadow:0 20px 50px -20px rgba(21,19,15,.2);}
/* Starting height only — JS fits it to the real content on load. Kept close to
   the actual desktop height so there is no visible jump. */
.calc-frame-wrap iframe{display:block;width:100%;border:none;height:820px;}
/* The hidden ATTRIBUTE only works while nothing gives the element a display of
   its own: [hidden]{display:none} lives in the browser's own stylesheet, and
   any author rule beats it. .novo-express is display:block right above, and
   .trust-row on the pricing page is display:flex — so hiding either one set
   the attribute and changed nothing, which is why the full calculator opened
   UNDERNEATH the panel it was supposed to replace. Both are hidden by script,
   so this has to outrank whatever the page says. */
.novo-express[hidden],
[data-express-intro][hidden]{display:none !important;}

/* ---- the four steps live in one panel and cross-fade between each other.
     Keyframes, not transitions: a declared transition on a class-changed
     property has failed repeatedly in this codebase, and a keyframe's final
     frame IS the resting state. Nothing animates across the swap — the panel
     fades out, the layout changes while it is invisible, and it fades back. */
.x-step{display:none;}
.x-step.on{display:block;}
.x-fading{animation:xFade .30s ease both;}
.x-entering{animation:xRise .42s cubic-bezier(.2,.6,.3,1) both;}
@keyframes xFade{from{opacity:1;}to{opacity:0;}}
@keyframes xRise{from{opacity:0;transform:translateY(10px);}to{opacity:1;transform:none;}}
/* The same cross-fade, usable on anything — the full calculator takes the
   panel's place with it, so it reads as this thing becoming its full
   version rather than a second calculator appearing underneath. */
.x-swap-out{animation:xFade .24s ease both;}
.x-swap-in{animation:xRise .42s cubic-bezier(.2,.6,.3,1) both;}
@media (prefers-reduced-motion:reduce){.x-fading,.x-entering,.x-swap-out,.x-swap-in{animation:none;}}
/* ---- step 0: the packages ---- */
.x-fields{display:grid;grid-template-columns:1fr 1fr;gap:26px;margin-bottom:30px;}
.x-field .lab{font-family:var(--fm);font-size:11px;letter-spacing:.13em;text-transform:uppercase;
    color:var(--x-soft);display:block;margin-bottom:4px;}
.x-field .val{font-family:var(--fd);font-weight:800;font-size:26px;letter-spacing:-.03em;display:block;}
.x-field input[type=range]{width:100%;margin-top:10px;-webkit-appearance:none;appearance:none;
    height:4px;border-radius:2px;background:var(--x-line);outline:none;}
.x-field input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:22px;height:22px;
    border-radius:50%;background:var(--gold-deep);border:3px solid var(--x-thumb-ring);cursor:pointer;
    box-shadow:0 1px 4px rgba(21,19,15,.3);}
.x-head-row{display:flex;align-items:flex-end;justify-content:space-between;gap:18px;flex-wrap:wrap;}
.x-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-top:6px;}
.x-cards.with-basic{grid-template-columns:repeat(4,1fr);}
.x-card{background:var(--x-surface);border:1px solid var(--x-line);border-radius:8px;
    padding:20px 18px;display:flex;flex-direction:column;box-shadow:var(--x-card-shadow);}
.x-card.feat{background:var(--x-feat-bg);color:var(--x-feat-fg);border-color:var(--x-feat-line);
    box-shadow:var(--x-feat-shadow);}
/* Same shape as the calculator's .tier-badge: a gold pill sitting inline with
     the name. Fading opacity rather than appearing keeps the card's width
     settled, and because it sits AFTER the name nothing moves when it changes. */
.x-badge{display:inline-block;font-family:var(--fm);font-size:11px;letter-spacing:.1em;
    text-transform:uppercase;color:var(--ink);background:var(--gold);padding:3px 10px;
    border-radius:999px;margin-left:10px;vertical-align:middle;position:relative;top:-2px;
    opacity:0;transition:opacity .9s ease;white-space:nowrap;}
.x-badge.on{opacity:1;}
.x-card h3{display:flex;align-items:center;flex-wrap:wrap;gap:0;}
.x-card.feat .tag{color:var(--x-feat-accent);}
.x-card h3{font-family:var(--fd);font-weight:800;font-size:19px;letter-spacing:-.03em;margin:0 0 12px;}
.x-card ul{list-style:none;margin:0 0 16px;padding:0;flex:1;}
.x-card li{font-size:13.5px;line-height:1.45;color:var(--x-soft);padding:5px 0;
    border-top:1px solid var(--x-line);}
.x-card li:first-child{border-top:none;}
.x-card.feat li{color:var(--x-feat-soft);border-top-color:var(--x-feat-rule);}
/* "Everything in Essentials" is not another bullet — it is the anchor the
     rest of the card is measured against, so it carries the weight. */
.x-card li.carry{color:var(--x-fg);font-weight:600;}
.x-card.feat li.carry{color:var(--x-feat-fg);}
/* Desktop compares the cards side by side, so each shows only what it adds
     and they stay a comparable height. Mobile stacks them, nothing is being
     compared, and the full list is what someone actually wants. */
.x-full{display:none;}
.x-price{font-family:var(--fd);font-weight:800;font-size:30px;letter-spacing:-.035em;
    font-variant-numeric:tabular-nums;line-height:1;margin-bottom:14px;}
/* The recommended card is ink, and .pill is ALSO ink-filled — so the button
     was invisible until hover, exactly as Justin saw. shared.css already
     solves this for .tier-card.feat; .x-card.feat needs the same flip. */
/* Gold is the recommendation's colour, so it belongs to exactly one button.
   The other cards get the same shape as an outline: still obviously the
   action, visibly the second choice. */
.x-card .pill{background:transparent;color:var(--ink);border-color:var(--ink);}
.x-card .pill:hover{background:var(--ink);color:var(--paper);border-color:var(--ink);}
.x-card.feat .pill{background:var(--x-feat-btn);color:var(--x-feat-btn-fg);border-color:var(--x-feat-btn);}
.x-card.feat .pill:hover{background:var(--paper);border-color:var(--paper);color:var(--ink);}
/* expand control — hidden on desktop, where there is room for the list */
/* More air, and enough weight to read as a control rather than a caption —
     it was sitting almost against the button below it. */
.x-expand{display:none;width:100%;align-items:center;justify-content:space-between;gap:10px;
    font-family:var(--fm);font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;
    background:none;border:none;border-top:1px solid var(--x-line);padding:16px 0 0;margin:0 0 18px;
    color:var(--x-fg);cursor:pointer;}
.x-card.feat .x-expand{color:var(--x-feat-fg);border-top-color:var(--x-feat-rule);}
.x-expand .chev{transition:transform .2s ease;font-size:15px;}
.x-expand[aria-expanded="true"] .chev{transform:rotate(90deg);}
/* The offer card animates in and out rather than appearing, so a listing
     crossing $300K does not make the row jump. */
.x-card.offer{display:none;}
.x-card.offer.in{display:flex;animation:xRise .9s cubic-bezier(.25,.8,.3,1) both;}
/* While the month is in flight every day is closed, which would read as a
   fully booked calendar rather than one that has not answered yet. */
#calHost.is-loading .cal{opacity:.5;}
#calHost.is-loading .cal-day{cursor:default;}

/* ---- steps 1-3: work on the left, the running total on the right ---- */
.x-two{display:grid;grid-template-columns:1.6fr 1fr;gap:34px;align-items:start;}
/* The breakdown and the add-on buttons sit side by side, so they have to be
   the same card — the breakdown was --paper-deep against the add-ons' paper
   and read as two different materials. Sticky moved to the column: the reserve
   button is a sibling BELOW this card, and a sticky card scrolls over it. */
.x-sum{background:var(--x-surface);border:1px solid var(--x-line);border-radius:8px;padding:22px;}
.x-two > div:last-child{position:sticky;top:20px;}
.x-sum h4{font-family:var(--fm);font-size:11px;letter-spacing:.13em;text-transform:uppercase;
    color:var(--x-soft);margin:0 0 10px;}
.x-sum-tier{font-family:var(--fd);font-weight:800;font-size:24px;letter-spacing:-.03em;line-height:1;}
.x-sum-total{font-family:var(--fd);font-weight:800;font-size:36px;letter-spacing:-.035em;margin:6px 0 4px;}
.x-sum-dur{font-family:var(--fm);font-size:11px;letter-spacing:.09em;text-transform:uppercase;
    color:var(--x-soft);margin-bottom:14px;}
.x-inc{list-style:none;margin:0 0 16px;padding:0;}
.x-inc li{display:flex;gap:9px;font-size:14px;line-height:1.5;color:var(--x-soft);padding:3px 0;}
.x-inc .tick{color:var(--x-accent);flex:none;}
.x-sum-when{font-family:var(--fm);font-size:11.5px;color:var(--x-accent);margin-bottom:12px;
    min-height:16px;border-top:1px solid var(--x-line);padding-top:12px;}
.x-lines{list-style:none;margin:0;padding:14px 0 0;border-top:1px solid var(--x-line);}
.x-lines li{display:flex;justify-content:space-between;gap:12px;font-size:13.5px;
    color:var(--x-soft);padding:4px 0;}
.x-lines li b{color:var(--x-fg);font-weight:600;}
.x-steps-dots{display:flex;gap:7px;align-items:center;margin-bottom:22px;}
.x-dot{width:7px;height:7px;border-radius:50%;background:var(--x-line);}
.x-dot.on{background:var(--x-accent);}
.x-dot.done{background:var(--x-fill);}
.x-steplab{font-family:var(--fm);font-size:11px;letter-spacing:.1em;text-transform:uppercase;
    color:var(--x-soft);margin-left:8px;}
.x-back{font-family:var(--fm);font-size:11px;letter-spacing:.07em;text-transform:uppercase;
    background:none;border:none;padding:0;color:var(--x-soft);cursor:pointer;
    text-decoration:underline;text-underline-offset:4px;}
.x-back:hover{color:var(--x-fg);}
/* ---- step 2: the upsell ---- */
.x-addons{display:grid;gap:12px;margin-top:6px;}
.x-addon{display:flex;align-items:center;gap:16px;text-align:left;width:100%;
    background:var(--x-surface);border:1px solid var(--x-line);border-radius:10px;padding:16px 18px;
    cursor:pointer;font:inherit;color:inherit;}
.x-addon:hover{border-color:var(--x-accent);}
.x-addon[aria-pressed="true"]{border-color:var(--x-fill);background-color:var(--x-sel);}
.x-addon .tick{width:22px;height:22px;border-radius:50%;border:1.5px solid var(--x-line);
    flex:none;display:grid;place-items:center;font-size:12px;color:transparent;}
.x-addon[aria-pressed="true"] .tick{background:var(--x-fill);border-color:var(--x-fill);color:var(--x-fill-fg);}
.x-addon .body{flex:1;}
.x-addon .body strong{display:block;font-family:var(--fd);font-weight:700;font-size:16px;
    letter-spacing:-.02em;margin-bottom:2px;}
.x-addon .body span{font-size:13px;color:var(--x-soft);line-height:1.45;}
.x-addon .amt{font-family:var(--fd);font-weight:800;font-size:17px;letter-spacing:-.02em;white-space:nowrap;}
.x-addon-wrap{display:grid;}
.x-addon.is-covered{cursor:default;opacity:.6;}
.x-addon.is-covered .tick{background:var(--x-fill);border-color:var(--x-fill);color:var(--x-fill-fg);}
.x-addon.is-covered .amt{font-family:var(--fm);font-size:11px;letter-spacing:.07em;text-transform:uppercase;}
/* Reading what an upgrade contains should not commit you to it, so the peek
     is its own target inside the button and stops the click there. */
.x-peek{display:inline-block;margin-top:8px;font-family:var(--fm);font-size:10.5px;
    letter-spacing:.07em;text-transform:uppercase;color:var(--x-accent);cursor:pointer;
    text-decoration:underline;text-underline-offset:3px;}
.x-peek:hover{color:var(--x-fg);}
/* The peek lives in the breakdown now, so the add-on column never changes
     height and the Next button stays put. */
.x-peek-panel{margin-top:16px;padding-top:14px;border-top:1px solid var(--x-line);
    animation:xRise .28s ease both;}
.x-peek-panel h5{font-family:var(--fm);font-size:11px;letter-spacing:.09em;text-transform:uppercase;
    color:var(--x-accent);margin:0 0 8px;font-weight:400;}
.x-peek-panel ul{list-style:none;margin:0;padding:0;}
.x-peek-panel li{display:flex;gap:9px;font-size:13.5px;line-height:1.5;color:var(--x-soft);padding:3px 0;}
.x-peek-panel .tick{color:var(--x-accent);flex:none;}
.x-actions{display:flex;gap:12px;align-items:center;margin-top:24px;flex-wrap:wrap;}
/* Copied from calculator.html rather than shared — .btn lives in that file's
     own <style>. If this mockup is adopted it belongs in booking.css alongside
     the rest of the booking UI, so both use one copy. */
/* Same treatment as "Book this package" on the recommended card: gold is the
   colour of the action you are being pointed at, and it lightens on hover. */
.btn{display:block;width:100%;font-family:var(--fm);font-size:11.5px;letter-spacing:.09em;
    text-transform:uppercase;padding:15px 20px;border-radius:999px;border:1px solid var(--gold);
    background:var(--gold);color:var(--ink);cursor:pointer;text-decoration:none;text-align:center;margin-top:16px;}
.btn:hover{background:var(--paper);border-color:var(--ink);color:var(--ink);}
.btn[disabled]{opacity:.4;cursor:not-allowed;pointer-events:none;}
.x-gate{font-family:var(--fm);font-size:11px;color:var(--x-soft);margin-top:10px;}
@media(max-width:900px){
    /* On a phone four cards of seven lines each is a wall to scroll past before
       reaching a single button, so the list collapses. The recommended card
       opens itself — see renderCards — because that is the one most people
       want to read, and making them tap for it is friction on the wrong card. */
    .x-expand{display:flex;}
    .x-short{display:none;}
    .x-card ul{display:none;}
    .x-card.open .x-full{display:block;margin-bottom:18px;animation:xRise .3s ease both;}
    .x-cards,.x-cards.with-basic{grid-template-columns:1fr;}
    .x-two{grid-template-columns:1fr;}
    .x-two > div:last-child{position:static;}
    .x-fields{grid-template-columns:1fr;}
  }

/* The property-type switch, which the mockup styled inline. */
.x-type{display:inline-flex;border:1px solid var(--x-line);border-radius:999px;overflow:hidden;}
.x-type button{font-family:var(--fm);font-size:11px;letter-spacing:.07em;text-transform:uppercase;
  padding:8px 16px;border:none;background:transparent;color:var(--x-soft);cursor:pointer;}
.x-type button[aria-pressed="true"]{background:var(--x-fill);color:var(--x-fill-fg);}

/* The area question is the same KIND of control as House/Condo above it and has
   to look like it. It had layout rules in shared.css (flex, gap, sizing) but
   never any appearance ones, and .seg — the class the markup borrows — is
   defined only inside calculator.html, which does not reach this document. So
   these rendered as two default grey browser buttons on every page that embeds
   the express calculator. (Justin, 2026-08-21) */
.x-areaseg{display:flex;border:1px solid var(--x-line);border-radius:999px;overflow:hidden;}
.x-areaseg button{font-family:var(--fm);font-size:11px;letter-spacing:.07em;text-transform:uppercase;
  padding:9px 14px;border:none;background:transparent;color:var(--x-soft);cursor:pointer;
  transition:background .16s ease,color .16s ease;}
.x-areaseg button:hover{color:var(--x-fg);}
.x-areaseg button[aria-pressed="true"]{background:var(--x-fill);color:var(--x-fill-fg);}
.x-areaseg button + button{border-left:1px solid var(--x-line);}
.x-full-cta{text-align:center;margin-top:28px;}

/* The one add-on with a figure behind it. Given its own weight so it reads as
   evidence rather than more description, with the source right under it —
   an unattributed statistic is just a claim. */
.x-stat{display:block;font-family:var(--fd);font-weight:700;font-size:15px;letter-spacing:-.02em;
  color:var(--x-accent);margin:2px 0 6px;line-height:1.3;}
.x-stat em{display:block;font-family:var(--fm);font-size:10.5px;letter-spacing:.04em;
  font-style:normal;color:var(--x-soft);margin-top:3px;font-weight:400;}

/* Side by side on web — stacked they read as a list of two ways out rather
   than a way back and an offer. They stack again when the row runs out. */
.x-head-links{display:flex;flex-direction:row;align-items:center;justify-content:flex-end;
  gap:18px;flex-wrap:wrap;}
/* The upgrade reads as an offer, not a way back — same minimal shape as the
   back link so it never competes with Next, but coloured to be findable. */
.x-back.x-up{color:var(--x-accent);}
.x-back.x-up:hover{color:var(--x-fg);}
@media(max-width:640px){.x-head-links{flex-direction:column;align-items:flex-start;gap:6px;}}

/* ---- busy + empty states -------------------------------------------------
   A static "Working…" on a button reads as a frozen page: nothing moves while
   a request is in flight, so the only feedback is text that could equally mean
   it died. Motion is what distinguishes "in progress" from "broken", and an
   empty month has to SAY it is empty rather than render a grid of dead days
   (Justin, 2026-08-31). */
@keyframes novo-spin{to{transform:rotate(360deg);}}
.novo-spin{display:inline-block;width:1em;height:1em;vertical-align:-.14em;
  border:2px solid currentColor;border-right-color:transparent;border-radius:50%;
  animation:novo-spin .7s linear infinite;flex:none;}
/* Reduced motion still needs to move -- a frozen spinner is worse than none --
   so it slows rather than stops. */
@media(prefers-reduced-motion:reduce){.novo-spin{animation-duration:2.4s;}}
.cal-busy{display:flex;align-items:center;justify-content:center;gap:11px;
  padding:30px 16px;color:var(--ink-soft);font-size:14px;min-height:120px;}
.cal-empty{display:flex;flex-direction:column;align-items:center;gap:6px;
  text-align:center;padding:22px 16px;color:var(--ink-soft);font-size:14px;line-height:1.55;}
.cal-empty b{color:var(--ink);font-weight:600;}
.cal-empty .cal-empty-hint{font-size:13px;opacity:.85;}
