/* ==========================================================================
   AU landing V3 — Round 47 form surfaces
   --------------------------------------------------------------------------
   Six real forms, two presentation modes:

     .v3Popup  — top trio. Centred card on desktop, bottom sheet under 640px.
     .v3Sheet  — lower trio. Bottom sheet at every width, capped 480px and
                 centred on desktop.

   The sheet motion is ported verbatim from the shipped country sites
   (stunningdentistry-ca-main, src/components/layout/MobileStickyBar.tsx:1112-1145):
   300ms, cubic-bezier(0.22, 1, 0.36, 1), translate-y-full -> 0 plus opacity,
   black/45 scrim, drag handle, max-h 85svh, rounded-t 32px. That is the
   "scrolls up from there" behaviour he asked for.

   EVERY rule is scoped `body.v3` so V1, V2 and the deployed index.html are
   untouched. Loaded only by index-v3.html, after css/v3.css.

   Motion law: transform + opacity only, nothing that triggers layout.
   ========================================================================== */

body.v3 {
  --v3f-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --v3f-dur: 300ms;
  --v3f-line: #D0D5DD;
  --v3f-line-soft: #EAECF0;
  --v3f-ink: #101828;
  --v3f-ink-soft: #667085;
  --v3f-surface: #fff;
}

/* ---------- scrim ---------- */
body.v3 .v3fx {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  justify-content: center;
  background: rgba(0, 0, 0, 0.45);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--v3f-dur) var(--v3f-ease);
}
body.v3 .v3fx.open {
  opacity: 1;
  pointer-events: auto;
}
body.v3.v3-locked {
  overflow: hidden;
}
/* the page's own sticky chrome must not sit above an open surface.
   R56: .sideRail came out of this list with the rail itself — V3 no longer
   renders one, and a selector for an element that cannot exist is the dead
   limb R52 and R53 both paid for. V1/V2 keep their rail and their own rules. */
body.v3.v3-locked .bottomBar,
body.v3.v3-locked .subStrip {
  opacity: 0;
  pointer-events: none;
}

/* ---------- shared card shell ----------
   R49: these values are the CA HeroCtaPopup's, read from .caPopup in
   css/style.css:1406-1416 rather than re-invented. style.css is shared with V1,
   V2 and the deployed index.html so it is not edited — but the two popups on
   this page must not drift apart, so the source is named here. */
body.v3 .v3Card {
  position: relative;
  width: 100%;
  max-width: 480px;
  max-height: 85svh;
  display: flex;
  flex-direction: column;
  background: var(--section-surface);
  border: 1px solid #EAECF0;
  border-radius: 24px;
  box-shadow: 0 18px 45px rgba(15, 23, 42, 0.30);
  will-change: transform;
  transition: transform var(--v3f-dur) var(--v3f-ease),
              opacity var(--v3f-dur) var(--v3f-ease);
}

/* ---------- bottom-sheet presentation ---------- */
body.v3 .v3fx.asSheet {
  align-items: flex-end;
}
body.v3 .v3fx.asSheet .v3Card {
  background: var(--section-surface);
  border-radius: 32px 32px 0 0;
  transform: translateY(100%);
  opacity: 0;
}
body.v3 .v3fx.asSheet.open .v3Card {
  transform: translateY(0);
  opacity: 1;
}

/* ---------- centred-popup presentation (ALL widths) ----------
   R48: was a bottom sheet under 640px and a popup only above it. He asked for a
   popup, once per form, three times. It is a popup everywhere now. Only the
   lower trio slides up. */
/* ---- R90 — the inline surface --------------------------------------------
   The footer's "Plan Your Dental Journey to India" form is the same surface
   the popups are, mounted on the page instead of over it. So it has to undo
   every dialog property the base .v3fx sets: the fixed position, the full
   viewport inset, the scrim, the stacking context, and the opacity/pointer
   gate that waits for .open (nothing ever opens this one). */
body.v3 .v3fx.asInline {
  position: static;
  inset: auto;
  z-index: auto;
  background: none;
  opacity: 1;
  pointer-events: auto;
  padding: 0;
  align-items: stretch;
}
body.v3 .v3fx.asInline .v3Card {
  width: 100%;
  max-width: 460px;
  max-height: none;      /* a page section scrolls with the page, not inside itself */
  border-radius: 24px;
  transform: none;
  opacity: 1;
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.16);
}
/* the head reads as a section heading here, not as dialog chrome: no close
   button sits beside it, so the title takes the width it needs */
body.v3 .v3fx.asInline .v3HeadText { text-align: center; }
/* the body is the page's scroll, so its own scroller must not double up */
body.v3 .v3fx.asInline .v3Body { overflow: visible; }
/* the mount centres the card in the footer section */
body.v3 [data-v3-inline] { display: flex; justify-content: center; }

body.v3 .v3fx.asPopup {
  align-items: center;
  padding: 16px;
}
body.v3 .v3fx.asPopup .v3Card {
  max-width: 420px;
  max-height: 88svh;
  border-radius: 24px;
  transform: translateY(28px) scale(0.98);
  opacity: 0;
  box-shadow: 0 18px 45px rgba(15, 23, 42, 0.30);
}
body.v3 .v3fx.asPopup.open .v3Card {
  transform: translateY(0) scale(1);
  opacity: 1;
}

/* the sheet keeps a hair of breathing room from the viewport edge on mobile */
body.v3 .v3fx.asSheet .v3Card {
  width: 100%;
}

@media (prefers-reduced-motion: reduce) {
  body.v3 .v3fx,
  body.v3 .v3Card {
    transition-duration: 1ms;
  }
  body.v3 .v3fx.asSheet .v3Card,
  body.v3 .v3fx.asPopup .v3Card {
    transform: none;
  }
}

/* ---------- drag handle (sheets only) ---------- */
body.v3 .v3Grab {
  flex: 0 0 auto;
  width: 36px;
  height: 4px;
  margin: 10px auto 2px;
  border-radius: 999px;
  background: var(--v3f-line);
}

/* ---------- head ----------
   CA arrangement (index-v3.html:1097-1101 / css/style.css:1417-1426): close
   LEFT, title CENTRED, a same-size spacer right so the title sits true centre. */
body.v3 .v3Head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 16px;
  background: var(--section-surface);
  border-bottom: 1px solid #F5F5F5;
  border-radius: 24px 24px 0 0;
}
body.v3 .v3fx.asSheet .v3Head {
  background: transparent;
  border-bottom: none;
  padding-top: 4px;
}
body.v3 .v3HeadText { flex: 1 1 auto; min-width: 0; text-align: center; }
body.v3 .v3Head h3 {
  text-align: center;
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 600;
  color: var(--v3f-ink);
  margin: 0;
  letter-spacing: -0.01em;
}
body.v3 .v3HeadSpacer { flex: 0 0 auto; width: 32px; height: 32px; }
/* R50: the small mark before the title, as on the reference sheet. It sits
   INSIDE .v3HeadText so it rides with the centred title rather than becoming a
   fourth flex item and pushing it off centre (the R49 bug, twice is enough). */
body.v3 .v3HeadIcon {
  display: inline-flex;
  width: 22px; height: 22px;
  margin-right: 6px;
  vertical-align: -5px;
  align-items: center; justify-content: center;
  border-radius: 50%;
  background: rgba(0, 88, 200, 0.10);
  color: var(--brand-blue);
}
body.v3 .v3HeadIcon svg { width: 14px; height: 14px; }
body.v3 .v3Head .v3Sub {
  display: block;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--v3f-ink-soft);
  margin-top: 2px;
  letter-spacing: 0;
}
body.v3 .v3Close,
body.v3 .v3Call {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: none;
  line-height: 1;
  cursor: pointer;
  transition: background 150ms ease, color 150ms ease, filter 150ms ease;
}
body.v3 .v3Close { background: #F2F4F7; color: #344054; }
body.v3 .v3Close:hover { background: #DC2626; color: #fff; }
body.v3 .v3Close svg,
body.v3 .v3Call svg { width: 17px; height: 17px; }
/* R74: the call glyph gained an aria-hidden wrapper, so the flex centring
   needs a box that is not an inline span of its own line-height. */
body.v3 .v3Call > span { display: inline-flex; }

/* R50: the two glyphs live in the button together and CSS picks. At step 2 of a
   chat surface this button goes BACK; everywhere else it closes. JS keeps the
   aria-label honest in step with it. */
body.v3 .v3Close .icClose,
body.v3 .v3Close .icBack { display: none; align-items: center; }
body.v3 .v3Close .icClose { display: inline-flex; }
body.v3 .v3fx.atStep2 .v3Close .icClose { display: none; }
body.v3 .v3fx.atStep2 .v3Close .icBack { display: inline-flex; }

/* R74 — the button dials now, so WhatsApp's #25D366 goes with it. That green
   was kept off-palette on purpose because it was a platform mark; a call
   button carries no such obligation.
   R75 — blue, at his call.
   R76 — green, at his call. #16A34A is the site's own action-green (used on
   the hero CTA), distinct from WhatsApp's #25D366. */
body.v3 .v3Call {
  background: #16A34A;
  color: #fff;
  text-decoration: none;
}
body.v3 .v3Call:hover { filter: brightness(1.06); }

/* ---------- scrollable body ---------- */
body.v3 .v3Body {
  flex: 1 1 auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 12px 12px calc(12px + env(safe-area-inset-bottom));
  background: var(--section-surface);
  border-radius: 0 0 24px 24px;
}
/* the sheet keeps its own 32px top corners and squares off at the viewport floor */
body.v3 .v3fx.asSheet .v3Head { border-radius: 32px 32px 0 0; }
body.v3 .v3fx.asSheet .v3Body { border-radius: 0; }
/* Brand book rule 1: warm cream is the canvas, never pure white. Sheets match
   the popups rather than going white. */
body.v3 .v3fx.asSheet .v3Body { background: var(--section-surface); }

/* ---------- the white panel on cream (.caInnerCard, css/style.css:1448-1449) ----
   R49: THE missing piece. R47/R48 laid the fields flat on the cream body, so the
   form had no object to sit in. The CA card floats a white card inside the cream
   one — two surface levels — and that is what makes it read as finished. */
body.v3 .v3Panel {
  border-radius: 10px;
  border: 1px solid #EAECF0;
  background: #fff;
  padding: 10px 12px;
}
body.v3 .v3Panel + .v3Panel { margin-top: 8px; }
body.v3 .v3PanelTitle {
  font-size: 12px;
  font-weight: 600;
  color: var(--v3f-ink);
  margin: 0;
}
body.v3 .v3PanelTitle + .v3PanelFields { margin-top: 8px; }
body.v3 .v3PanelFields {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

body.v3 .v3Body form {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* ---------- fields ---------- */
body.v3 .v3Field { display: flex; flex-direction: column; gap: 5px; }
/* [hidden] is display:none in the UA sheet, but the class rule above outranks
   it. Without this the Others describe box renders even while hidden=true. */
body.v3 .v3Field[hidden] { display: none; }
body.v3 .v3Label {
  font-size: 11.5px;
  font-weight: 600;
  color: #344054;
  letter-spacing: 0.01em;
}
body.v3 .v3Body input[type="text"],
body.v3 .v3Body input[type="tel"],
body.v3 .v3Body input[type="email"],
body.v3 .v3Body input[type="number"],
body.v3 .v3Body select,
body.v3 .v3Body textarea {
  width: 100%;
  border-radius: 8px;
  border: 1px solid var(--v3f-line);
  background: #fff;
  padding: 9px 12px;
  font-size: 13px;
  font-family: inherit;
  color: var(--v3f-ink);
  margin: 0;
  -webkit-appearance: none;
  appearance: none;
}
body.v3 .v3Body textarea { min-height: 74px; resize: vertical; line-height: 1.45; }
body.v3 .v3Body select {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%23667085' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  padding-right: 32px;
}
body.v3 .v3Body input:focus,
body.v3 .v3Body select:focus,
body.v3 .v3Body textarea:focus {
  outline: none;
  border-color: var(--brand-blue);
  box-shadow: 0 0 0 1px var(--brand-blue);
}
body.v3 .v3Body .field-invalid { border-color: #DC2626; }
body.v3 .v3Body .fieldError {
  display: none;
  font-size: 11px;
  color: #DC2626;
  margin: 0;
}
body.v3 .v3Body .fieldError.show { display: block; }

/* ---------- segmented control (audio / video, male / female) ---------- */
body.v3 .v3Seg {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 6px;
}
body.v3 .v3Seg label {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 42px;
  padding: 8px 10px;
  border: 1px solid var(--v3f-line);
  border-radius: 10px;
  background: #fff;
  font-size: 12.5px;
  font-weight: 600;
  color: #344054;
  cursor: pointer;
  text-align: center;
  transition: border-color 150ms ease, background 150ms ease, color 150ms ease;
}
body.v3 .v3Seg input { position: absolute; opacity: 0; pointer-events: none; }
body.v3 .v3Seg label:has(input:checked) {
  border-color: var(--v3-gold);
  background: rgba(201, 168, 76, 0.12);
  color: var(--v3-gold-text);
}
body.v3 .v3Seg label:has(input:focus-visible) {
  box-shadow: 0 0 0 2px var(--brand-blue);
}
body.v3 .v3Seg .segIcon { font-size: 14px; line-height: 1; }

/* ---------- phone with the +61 affix ---------- */
body.v3 .v3Phone {
  display: flex;
  align-items: stretch;
  border: 1px solid var(--v3f-line);
  border-radius: 8px;
  background: #fff;
  overflow: hidden;
}
body.v3 .v3Phone:focus-within {
  border-color: var(--brand-blue);
  box-shadow: 0 0 0 1px var(--brand-blue);
}
body.v3 .v3Phone .v3Cc {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 0 10px;
  background: #F9FAFB;
  border-right: 1px solid var(--v3f-line);
  font-size: 13px;
  font-weight: 600;
  color: #344054;
  white-space: nowrap;
}
body.v3 .v3Phone .v3Cc .ccFlag { font-size: 13px; }
body.v3 .v3Phone input {
  border: none !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  flex: 1 1 auto;
  min-width: 0;
}
body.v3 .v3Phone.field-invalid { border-color: #DC2626; }

/* ---------- contact row: [+61 | number]  or  [email], ONE line (R48) ----------
   Was a stacked phone / full-width rule / email block. He asked for the country
   code, the number and the email on the same line. */
body.v3 .v3Contact {
  display: flex;
  align-items: center;
  gap: 7px;
}
/* The phone side needs MORE than half, not less: it spends ~42px on the +61
   affix before the placeholder starts, while "Email address" leaves the email
   side slack. R48 shipped 44/56 and clipped; 50/50 clipped again once R49's
   panel padding took 24px off the row. 56/44 is measured to fit both, and the
   probe now checks rendered placeholder width rather than trusting this. */
body.v3 .v3Contact .v3Phone { flex: 1 1 56%; min-width: 0; }
body.v3 .v3Contact input[type="email"] { flex: 1 1 44%; min-width: 0; }
/* the affix tightens inside the row so the pair survives a 390px viewport */
body.v3 .v3Contact .v3Phone .v3Cc { padding: 0 8px; gap: 4px; }
body.v3 .v3Contact .v3Phone input,
body.v3 .v3Contact input[type="email"] { padding-left: 9px; padding-right: 9px; }
body.v3 .v3Or {
  flex: 0 0 auto;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #98A2B3;
}

/* ---------- photo upload ---------- */
body.v3 .v3Upload {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 11px 12px;
  border: 1px dashed var(--v3f-line);
  border-radius: 10px;
  background: #FCFCFD;
  cursor: pointer;
  transition: border-color 150ms ease, background 150ms ease;
}
body.v3 .v3Upload:hover { border-color: var(--v3-gold); background: rgba(201, 168, 76, 0.06); }
body.v3 .v3Upload input[type="file"] { display: none; }
body.v3 .v3Upload .upIcon {
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  border-radius: 9px;
  display: grid;
  place-items: center;
  background: rgba(201, 168, 76, 0.14);
  font-size: 17px;
  overflow: hidden;
}
body.v3 .v3Upload .upIcon img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
body.v3 .v3Upload .upText { min-width: 0; }
body.v3 .v3Upload .upText strong {
  display: block;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--v3f-ink);
}
body.v3 .v3Upload .upText em {
  display: block;
  font-style: normal;
  font-size: 11px;
  line-height: 1.4;
  color: var(--v3f-ink-soft);
  margin-top: 1px;
}
/* only once a file is chosen does the text become a filename worth truncating */
body.v3 .v3Upload .upText em.isFile {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-top: 1px;
}
/* R48: the "photo delivery is not switched on" note (.v3UpNote) and the
   .v3Upload.pending state are gone — he named that text and did not want it.
   The admission moved into the CRM message instead, see js/v3-forms.js §7. */

/* ---------- consent ---------- */
body.v3 .v3Consent {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 11.5px;
  line-height: 1.45;
  color: #475467;
  cursor: pointer;
}
body.v3 .v3Consent input {
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  margin-top: 1px;
  accent-color: var(--v3-gold-text);
}
body.v3 .v3Consent.field-invalid { color: #DC2626; }

/* ---------- the required dot (R72) ----------
   His ask: make it visible which fields are compulsory. These three hide their
   labels, so the mark cannot ride on label text — it sits inside the control's
   right edge instead. Same #DC2626 as .fieldError above: a required mark and a
   validation error are one conversation, not two reds.

   Decorative only. `required` on the input is what assistive tech announces,
   so the dot is aria-hidden by construction (a ::after on a wrapper is never
   in the a11y tree) and nothing here changes what a screen reader hears. */
body.v3 .v3Field.isRequired { position: relative; }
body.v3 .v3Field.isRequired::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 11px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #DC2626;
  transform: translateY(-50%);
  pointer-events: none;
}
/* the inputs give the dot its own gutter so a long value never runs under it */
body.v3 .v3Field.isRequired > input,
body.v3 .v3Field.isRequired .v3Phone input { padding-right: 26px; }
/* a visible label means the field is taller than one control, so centring on
   the wrapper would drop the dot into the middle of the box */
body.v3 .v3Field.isRequired:has(.v3Label)::after { top: 33px; }

/* R77 — a required SEGMENTED field. The rules above assume the control is a
   single input the dot can sit inside; a seg is a row of tiles, and both
   placements land the dot ON one of them (11px from the right edge is inside
   the last tile, and 33px down is mid-card on the tall `cards` variant). It
   rides the LABEL's line instead — the only part of a seg that behaves like
   one row — and stays on the RIGHT, where every other field's dot is. Moving
   it to the left of the label would put one of the form's dots on a different
   side from all the others, which reads as a different mark rather than the
   same one. */
body.v3 .v3Field.isRequired:has(.v3Seg)::after,
body.v3 .v3Field.isRequired:has(.v3Label):has(.v3Seg)::after {
  top: 7px;
  right: 2px;
  transform: none;
}
body.v3 .v3Field.isRequired:has(.v3Seg) > .v3Label { padding-right: 14px; }

/* the seg's own invalid state — a red rim on the group, matching the
   field-invalid treatment the inputs get */
body.v3 .v3Seg.field-invalid label {
  border-color: #DC2626;
}

/* (R71 widened this one surface to 620px to fit a two-column row. R73 removed
   the row, so the reason went with it and e-consult is back on the shared
   480px shell — a width with no remaining justification is drift.) */

/* ---------- the one-line condition box (R73) ----------
   His call: the condition is one line, with the dropzone under it. It stays a
   <textarea> so a patient with more to say can still type it — the box scrolls
   instead of truncating, which an <input> would not do. min-height has to be
   overridden explicitly: the shared rule above sets 74px and `rows="1"` loses
   to it every time. */
body.v3 .v3Body textarea.isOneLine {
  min-height: 0;
  height: 40px;
  padding-top: 10px;
  padding-bottom: 10px;
  resize: none;
  overflow-y: auto;
}

/* (R71's .v3Row — condition and photo in two columns — is gone at R73. He
   stacked the pair full-width instead, so no surface declares a row and the
   renderer went with it. See js/v3-forms.js, renderField.) */

/* ---------- captcha (R70) ----------
   The widget is a Google iframe at a FIXED 304x78. We do not get to restyle
   it, only to fit it. On desktop the popup's inner width clears 304 easily and
   this block does nothing but space it. The narrow case is handled below. */
body.v3 .v3Captcha {
  min-width: 0;
}
body.v3 .v3Captcha .g-recaptcha {
  /* the iframe is inline-block; this stops the 78px box collapsing before
     Google's script has swapped the div for the real widget */
  min-height: 78px;
}
body.v3 .v3Captcha .fieldError { margin-top: 2px; }

/* ---------- submit ---------- */
body.v3 .v3Submit {
  width: 100%;
  border: none;
  border-radius: 10px;
  padding: 12px 16px;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 700;
  color: #111827;
  cursor: pointer;
  background: var(--grad-gold-cta);
  box-shadow: 0 8px 18px rgba(141, 110, 27, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.35);
  transition: box-shadow 150ms ease, opacity 150ms ease;
}
body.v3 .v3Submit:hover { box-shadow: 0 10px 24px rgba(141, 110, 27, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.40); }
body.v3 .v3Submit[disabled] { opacity: 0.6; cursor: default; }
body.v3 .v3Submit.pending { color: transparent; position: relative; }
body.v3 .v3Submit.pending::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 16px;
  height: 16px;
  border: 2px solid rgba(17, 24, 39, 0.3);
  border-top-color: #111827;
  border-radius: 50%;
  animation: v3spin 700ms linear infinite;
}
@keyframes v3spin { to { transform: rotate(360deg); } }

/* ---------- result panel ---------- */
body.v3 .v3Result {
  display: none;
  padding: 18px 4px 6px;
  text-align: center;
}
body.v3 .v3Result.show { display: block; }
body.v3 .v3Result .rTick {
  width: 46px; height: 46px;
  margin: 0 auto 10px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 22px;
  background: rgba(18, 183, 106, 0.12);
  color: #12B76A;
}
body.v3 .v3Result.bad .rTick { background: rgba(220, 38, 38, 0.1); color: #DC2626; }
body.v3 .v3Result h4 { font-size: 15px; font-weight: 700; color: var(--v3f-ink); margin: 0 0 5px; }
body.v3 .v3Result p { font-size: 12.5px; line-height: 1.5; color: #475467; margin: 0 0 12px; }
body.v3 .v3Result .rWa {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 10px 18px;
  border-radius: 10px;
  font-size: 13px;
  font-weight: 700;
  color: #fff;
  text-decoration: none;
  background: linear-gradient(135deg, #16A34A 0%, #25D366 100%);
}
/* the slot a supplied per-treatment price infographic drops into later */
body.v3 .v3ArtSlot { display: none; margin-top: 12px; }
body.v3 .v3ArtSlot.show { display: block; }
body.v3 .v3ArtSlot img { width: 100%; height: auto; border-radius: 10px; display: block; }

/* ======================================================================
   8b. CHAT SURFACES  (R50)
   ======================================================================
   The country-site "24/7 CRM Support" sheet, ported:
   Project Web/stunningdentistry-au/src/components/layout/MobileStickyBar.tsx
   lines 2059-2161. Its exact values are used where they hold up
   (bubble #EFF6FF on #1E3A5F ink, user bubble #0058C8, 12px/10px/8px radius
   ladder), and dropped where they don't: its emoji are line icons here, and
   the greeting sits on the CREAM body rather than its near-white, because
   brand book rule 1 makes cream the canvas.

   The old calculator chooser lived here. The chips replaced it. */

body.v3 .v3Step { display: none; }
body.v3 .v3Step.on { display: block; }
/* the step swap is a transform+opacity move only, per the motion law */
@media (prefers-reduced-motion: no-preference) {
  body.v3 .v3Step.on { animation: v3StepIn 180ms var(--v3f-ease) both; }
}
@keyframes v3StepIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ---- the bubbles ---- */
body.v3 .v3Say {
  border-radius: 12px;
  background: #EFF6FF;
  padding: 10px 12px;
  margin: 0;
  font-size: 13px;
  line-height: 1.45;
  color: #1E3A5F;
}
body.v3 .v3Say p { margin: 0; font-size: 13px; line-height: 1.45; color: #1E3A5F; }
body.v3 .v3Say:focus { outline: none; }
body.v3 .v3Say:focus-visible { outline: 2px solid var(--brand-blue); outline-offset: 2px; }
body.v3 .v3SayCta {
  margin-top: 7px !important;
  font-size: 12px;
  font-weight: 600;
  color: #4B7BB5 !important;
}
/* the choice, echoed back — right-aligned, the way a sent message reads.
   The <p> is the alignment box; the <span> inside it is the pill, so the pill
   hugs the label instead of stretching the full width. */
body.v3 .v3Said {
  margin: 0 0 8px;
  text-align: right;
}
body.v3 .v3Said:empty { display: none; }
body.v3 .v3Said span {
  display: inline-block;
  max-width: 100%;
  padding: 6px 12px;
  border-radius: 10px;
  background: var(--brand-blue);
  color: #fff;
  font-size: 12px;
  font-weight: 500;
  line-height: 1.35;
  text-align: left;
}
body.v3 .v3Tape { margin-bottom: 10px; }

/* ---- the chips ---- */
body.v3 .v3Chips {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
  margin-top: 10px;
}
body.v3 .v3Chip {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 48px;
  padding: 9px 10px;
  border: 1px solid #D0D5DD;
  border-radius: 10px;
  background: #fff;
  text-align: left;
  cursor: pointer;
  font-family: inherit;
  color: #344054;
  transition: border-color 150ms ease, background 150ms ease, transform 150ms ease;
}
body.v3 .v3Chip:hover {
  border-color: var(--brand-blue);
  background: rgba(0, 88, 200, 0.05);
}
body.v3 .v3Chip:active { transform: scale(0.985); }
body.v3 .v3Chip:focus-visible { outline: 2px solid var(--brand-blue); outline-offset: 2px; }
body.v3 .v3Chip .chIcon {
  flex: 0 0 auto;
  width: 28px; height: 28px;
  display: inline-flex;
  align-items: center; justify-content: center;
  border-radius: 8px;
  background: rgba(0, 88, 200, 0.08);
  color: var(--brand-blue);
}
body.v3 .v3Chip .chIcon svg { width: 16px; height: 16px; }
body.v3 .v3Chip .chText { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
body.v3 .v3Chip strong {
  font-size: 12px; font-weight: 600; line-height: 1.25;
  color: var(--v3f-ink);
}
body.v3 .v3Chip em {
  font-style: normal; font-size: 10.5px; line-height: 1.3;
  color: var(--v3f-ink-soft);
}

/* ---- the one-layer selector (R54) ----
   Price List and Video Consult ask everything on one screen now, so their chips
   are a control inside the form rather than a gate in front of it. Same chip,
   two differences it has to carry that a step-1 grid never did: a SELECTED
   state (nothing else on the screen records the answer once the step is gone)
   and an invalid state (it is required, and it validates with the fields). */
body.v3 .v3ChipPanel .v3Chips { margin-top: 0; }
body.v3 .v3Chip.on {
  border-color: var(--brand-blue);
  background: rgba(0, 88, 200, 0.07);
  box-shadow: inset 0 0 0 1px var(--brand-blue);
}
body.v3 .v3Chip.on strong { color: var(--brand-blue-deep); }
/* the same #DC2626 the field-invalid rule at the top of this file uses — one
   error colour on the surface, not a second one for chips */
body.v3 .v3Chips.field-invalid .v3Chip { border-color: #DC2626; }
body.v3 .v3ChipPanel .fieldError { margin-top: 8px; }

/* ---- back to the choices ---- */
body.v3 .v3Restep {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 10px;
  padding: 4px 2px;
  border: none;
  background: none;
  font-family: inherit;
  font-size: 12px;
  font-weight: 500;
  color: var(--v3f-ink-soft);
  cursor: pointer;
}
body.v3 .v3Restep svg { width: 14px; height: 14px; }
body.v3 .v3Restep:hover { color: var(--brand-blue); }
body.v3 .v3Restep[hidden] { display: none; }

/* ---------- calculator panes ---------- */
body.v3 .v3Calc { display: none; }
body.v3 .v3Calc.on { display: block; }
/* R50: the pane's controls live in the same white .v3Panel the forms use, so
   both faces of a chat sheet sit on the cream at the same level. */
body.v3 .v3Calc .v3Panel { padding: 12px; }
body.v3 .v3Calc .v3Panel .v3Field + .v3Field { margin-top: 10px; }
body.v3 .v3Back {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-bottom: 11px;
  padding: 5px 10px 5px 7px;
  border: 1px solid var(--v3f-line);
  border-radius: 999px;
  background: #fff;
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 600;
  color: #344054;
  cursor: pointer;
}
body.v3 .v3Back:hover { border-color: var(--v3-gold); }

/* Brand book rule 1: Palette C, and never near-black navy as a surface. This is
   the consideration-tier trust-strip gradient (#0058C8 -> #001B5E), the same
   pair the country sites use. */
body.v3 .v3Out {
  margin-top: 13px;
  padding: 14px;
  border-radius: 12px;
  background: var(--grad-trust-strip);
  color: #fff;
  text-align: center;
}
body.v3 .v3Out .oLabel,
body.v3 .v3Out .oValue { display: block; }
body.v3 .v3Out .oLabel {
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: rgba(255, 255, 255, 0.62);
}
body.v3 .v3Out .oValue {
  font-size: 27px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--v3-sand);
  margin-top: 3px;
  font-variant-numeric: tabular-nums;
}
body.v3 .v3Out .oNote {
  font-size: 10.5px;
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.62);
  margin: 7px 0 0;
}
body.v3 .v3Break {
  list-style: none;
  margin: 11px 0 0;
  padding: 11px 0 0;
  border-top: 1px solid rgba(255, 255, 255, 0.16);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
body.v3 .v3Break li {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 11.5px;
  color: rgba(255, 255, 255, 0.82);
}
body.v3 .v3Break li b { font-weight: 700; color: #fff; font-variant-numeric: tabular-nums; }

/* ---------- R77. THE BRAND BOOK SECTIONS ----------------------------------
   The four calculator panes now carry the Brand Book's own sections (see the
   note above calcPanes() in js/v3-forms.js for what each one replaced). The
   drill-downs are pill rows rather than <select>s, which is how the Brand Book
   presents them and reads better on a phone than three stacked dropdowns.

   No new palette: gold comes from --brand-gold / --grad-gold-cta, ink and
   hairlines from the tokens already in this file, and the price/fare card is
   the existing .v3Out. Everything is inside .v3Panel-equivalent white on the
   sheet's cream, same as the forms. */
body.v3 .bbSec {
  padding: 12px;
  border-radius: 12px;
  background: #fff;
  border: 1px solid var(--v3f-line);
}
/* R79 — .bbTag and .bbIntro are GONE, not merely unused: the pane intros and
   the visa tagline they styled were deleted when each pane stopped introducing
   itself three times over. The one surviving sentence per pane is the chip's
   chat `reply`, which is styled by the transcript, not here. */

/* ---- R90 — the pane pictures ------------------------------------------
   His dictation: "all these need to have pictures inside them ... can be
   copied from the brand book". One 16:9 picture at the head of the visa and
   flight panes; on the treatment pane the same frame carries the picked
   treatment's own still and appears with the price. The stay tiers each carry
   a real named hotel from that tier instead (see .bbTierShot below).
   Sized in the same 12px/10px rhythm as .bbSec and .bbTier so the picture
   reads as part of the card, not a banner pasted above it. */
body.v3 .bbShot {
  margin: 0 0 11px;
  border-radius: 10px;
  overflow: hidden;
  background: var(--v3-cream-soft);
  border: 1px solid var(--v3f-line);
}
body.v3 .bbShot img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}
/* the treatment shot sits BELOW its drill-down, next to the price it explains,
   so the pane still opens on its first control (the R79 rule) */
body.v3 .v3Calc[data-pane="treat"] .bbShot { margin: 11px 0 0; }
body.v3 .bbTierShot {
  margin: 0 0 9px;
  border-radius: 8px;
  overflow: hidden;
}
body.v3 .bbTierShot img {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

/* a drill-down step: its label, then its row of choices */
body.v3 .bbStep + .bbStep { margin-top: 11px; }
body.v3 .bbStepLbl {
  display: block;
  margin-bottom: 6px;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--v3f-ink-soft);
}
body.v3 .bbPills {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
body.v3 .bbPill {
  padding: 7px 12px;
  border-radius: 999px;
  border: 1px solid var(--v3f-line);
  background: #fff;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--v3-ink);
  cursor: pointer;
  transition: border-color 160ms ease-out, background-color 160ms ease-out,
              transform 140ms ease;
}
body.v3 .bbPill:hover { border-color: var(--brand-gold); }
body.v3 .bbPill:active { transform: translateY(1px) scale(0.98); }
body.v3 .bbPill:focus-visible {
  outline: 2px solid var(--brand-blue);
  outline-offset: 2px;
}
/* the chosen one wears the gold, so the drill-down's trail stays visible while
   you move down it — three rows, three answered questions */
body.v3 .bbPill[aria-pressed="true"] {
  background: var(--grad-gold-cta);
  border-color: var(--brand-gold-dark);
  color: var(--v3-ink);
}
body.v3 .bbHint {
  margin: 0;
  font-size: 11.5px;
  color: var(--v3f-ink-soft);
}

/* visa: the three numbered steps */
body.v3 .bbSteps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 11px;
}
body.v3 .bbSteps li { display: flex; gap: 10px; align-items: flex-start; }
body.v3 .bbNum {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--grad-gold-cta);
  color: var(--v3-ink);
  font-size: 11.5px;
  font-weight: 800;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
body.v3 .bbStepBody { min-width: 0; }
body.v3 .bbStepBody strong {
  display: block;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--v3-ink);
}
body.v3 .bbStepBody em {
  display: block;
  margin-top: 2px;
  font-style: normal;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--v3f-ink-soft);
}
body.v3 .bbLink {
  display: inline-flex;
  margin-top: 12px;
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid var(--brand-gold);
  font-size: 12px;
  font-weight: 700;
  color: var(--v3-ink);
  text-decoration: none;
  transition: background-color 160ms ease-out, transform 140ms ease;
}
body.v3 .bbLink:hover { background: var(--v3-gold-light); }
body.v3 .bbLink:active { transform: translateY(1px) scale(0.99); }
/* R79 — .bbPayoff is gone with the visa payoff line it styled. */

/* accommodation: the three tiers */
body.v3 .bbTiers {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
body.v3 .bbTier {
  padding: 11px;
  border-radius: 10px;
  border: 1px solid var(--v3f-line);
  background: var(--v3-cream-soft);
}
body.v3 .bbTierHead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
body.v3 .bbTierHead strong { font-size: 12.5px; font-weight: 700; color: var(--v3-ink); }
body.v3 .bbStars { font-size: 10px; letter-spacing: 0.06em; color: var(--brand-gold); }
body.v3 .bbTierAccess,
body.v3 .bbTierBlurb {
  margin: 4px 0 0;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--v3f-ink-soft);
}
body.v3 .bbTierPrice {
  margin: 5px 0 0;
  font-size: 12px;
  font-weight: 700;
  color: var(--v3-ink);
}

/* the quote button closes the treatment pane, so it gets the sheet's own
   spacing rather than sitting flush against the disclaimer above it */
body.v3 .bbSec > .v3Submit { margin-top: 12px; }

@media (hover: none) {
  body.v3 .bbPill:hover { border-color: var(--v3f-line); }
  body.v3 .bbPill[aria-pressed="true"]:hover { border-color: var(--brand-gold-dark); }
  body.v3 .bbLink:hover { background: transparent; }
}

/* R79 — .v3Disc is gone. It styled the three qualifier paragraphs under the
   generated figures ("estimates, not quotes"); he asked for all three cut at a
   checkpoint, so the class has no call site left anywhere in the page. The
   trade that comes with it is recorded in NOTES.md R79, not hidden here. */

/* ======================================================================
   9. STICKY BOTTOM BAR — the Canada look  (R51)
   ======================================================================
   "I want the look to be changed only, okay, as the Canada website one."
   So: behaviour untouched (js/v3-forms.js is not edited this round), only the
   skin. Every value below is READ from the reference rather than invented —
   Project Web/stunningdentistry-ca-main/src/components/layout/MobileStickyBar.tsx,
   the bar container div and ActionPill, with the Tailwind resolved to literals.

   The two bars were different objects. Ours was edge-to-edge, flush, divided by
   a hairline. Canada's is a row of SEPARATED pills, each a free-standing shell
   with a 4px halo, on a white bar carrying a gold hairline and a diagonal wash.

   .bottomBar / .bottomBtns live in css/style.css, which V1, V2 and the deployed
   index.html all load, so it is NOT edited. Everything here is a body.v3
   override, same discipline as R48-R50.

   Three of his calls are encoded below:
     - the mark is a DOT, not an icon (the icons came out of the markup)
     - Video Consult keeps its gold (brand rule 6: gold marks the booking)
     - solid state only; the reference's glass branch is not ported */

/* ---- the bar ----
   Two elements do the reference's one job, so the chrome and the row split:
   .bottomBar is the full-bleed surface (white, gold hairline, gold wash) and
   .bottomBtns is the capped, centred pill row inside it. Putting the white on
   the row instead would turn the bar into a 520px island floating on the page. */
body.v3 .bottomBar {
  position: fixed;
  isolation: isolate;
  background: #fff;
  border-top: 1px solid rgba(234, 236, 240, 0.9);
  /* the gold hairline is the bar's signature: a 1px inset line, not a border */
  box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.08),
              inset 0 1px 0 0 rgba(201, 168, 76, 0.22);
}
/* the reference paints its gold wash with a spare absolutely-positioned div;
   a pseudo-element does the same job without touching the markup */
body.v3 .bottomBar::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(118deg,
    rgba(201, 168, 76, 0.14) 0%,
    rgba(255, 255, 255, 0) 40%,
    rgba(232, 213, 168, 0.10) 58%,
    rgba(201, 168, 76, 0.08) 100%);
}

/* ---- the row ----
   R54: his call, "decrease the height of the section a bit". The 4px saved at
   each end takes the row from 56 to 52; the height does NOT come out of the
   pill, which holds its 44px tap target below md:. Shrinking a control to make
   a bar shorter is the wrong trade. */
body.v3 .bottomBtns {
  position: relative;
  z-index: 1;
  gap: 8px;
  height: auto;
  min-height: 44px;
  padding: 4px 8px calc(4px + env(safe-area-inset-bottom));
  align-items: center;
  justify-content: center;
  background: transparent;
}

/* ---- the pill: the sub-header strip's pill, brought down to the bar (R53) ----
   His call, from two shots: the bar wears .subStrip's pill, so the page's top
   row and its bottom row read as one system instead of two.

   Every value below is copied from css/style.css:311-321 (.subStrip a,
   .subStrip button). That sheet is shared with V1/V2/index.html and is never
   edited, so the source is named here and the two cannot drift silently:
       #fff fill · 1px var(--brand-gold) hairline · 999px · --brand-blue-deep 700

   Out with the CA shell (R51): the 8px radius, the #D5D7DA border, the 4px
   #F5F5F5 halo, the white-to-#FAFAFA gradient, the sheen layer and the 6px
   colour dot. The strip carries none of them, and a gold-hairline pill wearing a
   coloured dot would be a third look rather than the one he pointed at. The dot
   markup came out of index-v3.html with this CSS — a span nothing styles is the
   R52 lesson, and the ::after sheen went with it for the same reason: R52
   deleted the only rule that ever raised it above opacity 0.

   Kept from R51 on purpose: the 44px tap target below md:, the press transform,
   the blue focus ring, and the gold conversion pill (brand rule 6). */
body.v3 .bottomBtns .bbV3 {
  position: relative;
  isolation: isolate;
  z-index: 1;
  overflow: hidden;
  /* R54, "increase the width of the buttons": on the phone the pills take the
     whole track instead of hugging three short labels, which is what left the
     bar looking empty. Grown from `auto`, NOT from 0 — an equal-thirds basis
     puts "Cost Calculator" (118px of nowrap label) into a 119px slot and clips
     it under the overflow:hidden above. R53's hug returns at md:; see the note
     on the 768px block for why stretching there would be wrong. */
  flex: 1 1 auto;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;                    /* tap target, not decoration */
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid var(--brand-gold);
  box-shadow: none;
  /* an inset 1px outline mirroring the border, purely so focus can swap its
     colour without the border moving anything */
  outline: 1px solid var(--brand-gold);
  outline-offset: -1px;
  background: transparent;
  font-family: var(--font-sans);
  font-size: 0.76rem;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: 0;
  text-align: center;
  white-space: nowrap;
  color: var(--brand-blue-deep);
  cursor: pointer;
  transition: border-color 200ms ease-out, outline-color 200ms ease-out,
              color 150ms ease, transform 150ms ease;
}

/* the fill stays a pseudo-element rather than a background, because the gold
   pill needs a gradient in the same slot and the press transform reads better
   with the paint isolated. Flat #fff now, as on the strip. */
body.v3 .bottomBtns .bbV3::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: #fff;
  opacity: 1;
  transition: filter 200ms ease;
}
body.v3 .bottomBtns .bbV3:hover {
  border-color: var(--brand-gold-dark);
  outline-color: var(--brand-gold-dark);
}
body.v3 .bottomBtns .bbV3:active { transform: translateY(1px) scale(0.98); }
body.v3 .bottomBtns .bbV3:focus-visible {
  outline: 2px solid var(--brand-blue);
  outline-offset: -1px;
}

body.v3 .bottomBtns .bbV3 .bbLabel {
  position: relative;
  z-index: 1;
  min-width: 0;
}

/* ---- there is deliberately NO open state on these pills (R52) ----
   R50 built one (the pill fills with its own colour, dot goes white) and R51
   re-skinned it. It reached a screen in neither round: section 3 hides the
   whole bar while a surface is open (body.v3.v3-locked .bottomBar {opacity:0})
   and the sheet card reaches the viewport floor regardless. Measured in both
   rounds — bar opacity 0, card bottom 900 of a 900px viewport — and now
   asserted live in automation/out/r52-probe.mjs so it stays measured.

   Before rebuilding it: the Canada site sits its sheet at bottom:48px, ON TOP
   of a visible bar, which is the whole reason a filled pill can anchor the
   sheet to the button that summoned it. Reproducing that needs .bottomBar
   dropped from the v3-locked hide list AND .asSheet given a bottom offset
   FIRST — a change to how sheets sit, not how they look. Styling alone renders
   to nobody, which is exactly the round this comment replaces. */

/* ---- the gold conversion pill ----
   Video Consult stays gold: it takes the strip's shell, radius and spacing like
   the other two, and keeps the gold fill so the booking action still reads as
   the one that converts (brand rule 6). The strip itself has no gold pill, so
   this is the one deliberate departure from it — his standing call since R50.
   Text stays #111827 rather than the strip's navy: measured against the dark end
   of --grad-gold-cta, near-black holds the better contrast. */
body.v3 .bottomBtns .bbV3.isGold {
  color: #1D1D1F;   /* R68: Charcoal Grey — was #111827 */
  border-color: var(--brand-gold-dark);
  outline-color: var(--brand-gold-dark);
  box-shadow: none;
}
body.v3 .bottomBtns .bbV3.isGold::before { background: var(--grad-gold-cta); }
body.v3 .bottomBtns .bbV3.isGold:hover::before { filter: brightness(1.05); }
/* The reference caps its pill row and centres it — max-w 520 on mobile, 860 at
   md:, 1320 at lg: — rather than letting three pills stretch a 1440px bar. Ours
   was flush and full-bleed so it never needed the cap; separated pills do, or
   each label floats alone in the middle of a 460px slab. Same three stops,
   applied to .bottomBtns since that is our row. */
body.v3 .bottomBtns {
  max-width: 520px;
  margin-inline: auto;
  width: 100%;
}
@media (min-width: 768px) {
  /* R54: 860 → 720. The pills stop sharing the track here and go back to
     hugging, because an equal-share pill on a wide row is the 430px slab with a
     small label adrift in it that the 1024px note below records. Wider buttons
     is a phone instruction; on desktop the same intent is served by more
     padding inside a tighter row, not by stretching. */
  body.v3 .bottomBtns { max-width: 720px; }
  /* R53: the shared sheet bumps the strip at md: (css/style.css:1057) but that
     rule reads `.subStrip a` and this page's strip is three BUTTONS, so the
     strip never actually bumps here — it stays 0.76rem at every width. Copying
     the bump would make the bar diverge from the row it is supposed to match,
     so only the padding opens up and the type does not move. */
  body.v3 .bottomBtns .bbV3 {
    flex: 0 1 auto;
    padding: 9px 26px;
    min-height: 0;
  }
}
@media (min-width: 1024px) {
  /* The reference's lg: cap is 1320px, but it holds SIX pills there and we hold
     three. Copying 1320 verbatim gave three 430px slabs with a small label adrift
     in each, which looks nothing like the reference even though the number
     matched. Matching its pill DENSITY is the faithful read, not its max-width. */
  body.v3 .bottomBtns { max-width: 680px; gap: 20px; }
}
@media (max-width: 360px) {
  /* three nowrap labels have to fit a 320-360px track. The strip solves this the
     same way — tighten the gap and the padding before touching the type. */
  body.v3 .bottomBtns { gap: 6px; padding-left: 6px; padding-right: 6px; }
  body.v3 .bottomBtns .bbV3 { font-size: 0.7rem; padding: 7px 8px; }
}

/* ---------- small-screen tightening ---------- */
@media (max-width: 400px) {
  body.v3 .v3Choose { grid-template-columns: 1fr; }
  body.v3 .v3Choose button { min-height: 0; flex-direction: row; align-items: center; gap: 10px; }
  body.v3 .v3Choose .cText { min-width: 0; }
  body.v3 .v3Seg label { font-size: 11.5px; padding: 8px 6px; }
  body.v3 .v3Contact { gap: 5px; }
  body.v3 .v3Contact .v3Phone .v3Cc { padding: 0 6px; font-size: 12px; }
}

/* ==========================================================================
   R56 — the section-by-section polish pass.
   Everything below refines controls that already exist; nothing here
   introduces a new component or a colour outside the V3 palette.
   ========================================================================== */

/* ---------- 1. the seg control, as cards (E-Consult only) ----------
   Same radios as .v3Seg above; this is the `variant: "cards"` skin. The strip
   version stays for Male/Female, where two words need no more room than that.
   Here the choice is the first thing on the form and it gets a tile. */
body.v3 .v3Seg.v3SegCards {
  gap: 8px;
}
body.v3 .v3Seg.v3SegCards label {
  flex-direction: column;
  justify-content: center;
  gap: 7px;
  min-height: 84px;
  padding: 13px 10px;
  border-radius: 12px;
  font-size: 13px;
}
/* the glyph gets a tinted tile of its own, the same 38px chip the upload icon
   uses — one icon-plate size across the surface, not two */
body.v3 .v3Seg.v3SegCards .segIcon {
  width: 38px;
  height: 38px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  background: rgba(201, 168, 76, 0.14);
  color: var(--v3-gold-text);
  transition: background 150ms ease;
}
body.v3 .v3Seg.v3SegCards .segIcon svg { width: 20px; height: 20px; }
body.v3 .v3Seg.v3SegCards label:has(input:checked) .segIcon {
  background: rgba(201, 168, 76, 0.26);
}

/* ---------- R72: the meeting choice, explained ----------
   His read was right — as two matched tiles with a faint tint between them,
   the control said "a setting is toggled" rather than "you are choosing how
   this call happens". Three changes, all inside the existing palette:

   1. A SUB-LINE under each label. The words "Audio call" and "Video call" name
      the format but not the consequence; "Voice only" / "Face to face" is what
      the patient is actually picking between. The difference becomes readable
      instead of inferable from an icon.
   2. A TICK on the chosen tile. Tint alone carries the whole selected state
      today, and a warm cream tint on a white card is a weak signal — at a
      glance neither tile looks chosen. A tick is unambiguous.
   3. A firmer selected edge. 1px gold on 1px grey is a hairline difference;
      the chosen tile now sits on a 2px gold ring with a soft lift, so it reads
      as raised rather than merely coloured.

   Deliberately NOT done: no new colour, no motion beyond the existing 150ms
   transitions, and the radios underneath are untouched — same names, same
   values, same `Video call` default reaching the CRM. */
body.v3 .v3Seg.v3SegCards label {
  min-height: 96px;
  padding: 14px 12px 13px;
  gap: 6px;
  line-height: 1.25;
}
body.v3 .v3Seg.v3SegCards .segSub {
  display: block;
  font-size: 11px;
  font-weight: 500;
  color: var(--v3f-ink-soft);
  margin-top: 1px;
  transition: color 150ms ease;
}
body.v3 .v3Seg.v3SegCards label:has(input:checked) {
  border-width: 2px;
  border-color: var(--v3-gold);
  box-shadow: 0 3px 10px rgba(141, 110, 27, 0.16);
}
/* 2px of border on the chosen tile would shift it 1px against its neighbour;
   the unchosen one carries a transparent ring of the same width to hold the row */
body.v3 .v3Seg.v3SegCards label { border-width: 2px; border-color: var(--v3f-line); }
body.v3 .v3Seg.v3SegCards label:has(input:checked) .segSub { color: var(--v3-gold-text); }

/* the tick, top-right of the chosen tile only */
body.v3 .v3Seg.v3SegCards label::after {
  content: "";
  position: absolute;
  top: 9px;
  right: 9px;
  width: 17px;
  height: 17px;
  border-radius: 50%;
  background: var(--v3-gold) url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='11' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='M2.5 6.2 5 8.7l4.5-5'/%3E%3C/svg%3E") center no-repeat;
  opacity: 0;
  transform: scale(0.7);
  transition: opacity 150ms ease, transform 150ms ease;
}
body.v3 .v3Seg.v3SegCards label:has(input:checked)::after {
  opacity: 1;
  transform: scale(1);
}

/* ---------- 2. Male / Female get their glyphs ----------
   The strip seg sizes its icon by font-size (the set used to be text); these
   are SVGs, so they need real dimensions or they inherit the 24px the sprite
   was drawn at and blow the row height. */
body.v3 .v3Seg:not(.v3SegCards) .segIcon {
  display: inline-grid;
  place-items: center;
  width: 17px;
  height: 17px;
  color: #667085;
  transition: color 150ms ease;
}
body.v3 .v3Seg:not(.v3SegCards) .segIcon svg { width: 17px; height: 17px; }
/* the glyph joins the label in the checked state instead of staying grey and
   reading as a disabled icon next to live text */
body.v3 .v3Seg label:has(input:checked) .segIcon { color: var(--v3-gold-text); }

/* ---------- 3. the photo upload, as a real dropzone ----------
   It was a dashed ROW, which at a glance reads as a filled-in field rather
   than a place to put something. Stacked and centred, the dashed edge is the
   whole shape of the target. */
body.v3 .v3Upload.isDrop {
  flex-direction: column;
  justify-content: center;
  text-align: center;
  gap: 9px;
  padding: 18px 14px;
  border-width: 1.5px;
  border-radius: 12px;
}
body.v3 .v3Upload.isDrop .upIcon {
  width: 44px;
  height: 44px;
  border-radius: 50%;
}
body.v3 .v3Upload.isDrop .upIcon svg { width: 21px; height: 21px; color: var(--v3-gold-text); }
/* the thumbnail fills the circle once a file is picked */
body.v3 .v3Upload.isDrop .upIcon img { border-radius: 50%; }
body.v3 .v3Upload.isDrop .upText { max-width: 100%; }
body.v3 .v3Upload.isDrop .upText em { margin-top: 3px; }

/* ---------- 4. the time chips, three across ----------
   Scoped to the video-consult surface by id, because the 2-column .v3Chips
   grid is right for the eight-item grids and wrong only here: three chips in a
   2-col grid leaves Evening alone on a second row, which reads as a different
   kind of option rather than the third of three. */
#v3fx-videoconsult .v3Chips {
  grid-template-columns: repeat(3, 1fr);
  gap: 7px;
}
#v3fx-videoconsult .v3Chip {
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 6px;
  padding: 12px 6px;
}
#v3fx-videoconsult .v3Chip .chText strong { font-size: 12.5px; }
/* "12pm to 5pm AEST" will not sit on one line in a third of 390px, and it is
   the information the chip exists to give, so it wraps rather than truncates */
#v3fx-videoconsult .v3Chip .chText em {
  font-size: 10px;
  line-height: 1.3;
  white-space: normal;
}
@media (max-width: 360px) {
  #v3fx-videoconsult .v3Chips { gap: 5px; }
  #v3fx-videoconsult .v3Chip { padding: 11px 4px; }
  #v3fx-videoconsult .v3Chip .chText em { font-size: 9.5px; }
}

/* ---------- 5. the cost calculator, tidied ----------
   Craft only. No figure, no formula and no source line is touched here — those
   survived /verify-claims and changing them is a different kind of edit.

   The accommodation pane stacked four fields, two of which are two-character
   numbers. Nights and Rooms now share a row (the .v3Row wrapper added in
   calcPanes), so the pane is three rows of related things instead of four of
   unrelated ones. */
body.v3 .v3Calc .v3Row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  align-items: start;
}
/* .v3Field is a flex column, so a field inside the grid needs no extra rules;
   it just stops being full-width */
body.v3 .v3Calc .v3Row .v3Field { min-width: 0; }

/* the breakdown is a ledger and reads DOWN the column, so the rule between
   lines does more than the gap did. White alpha, not ink: .v3Out is the
   trust-strip gradient and anything darker than it disappears. */
body.v3 .v3Break { gap: 0; }
body.v3 .v3Break li { padding-block: 5px; }
body.v3 .v3Break li + li { border-top: 1px solid rgba(255, 255, 255, 0.12); }
/* the figures line up as a column of money, which they did not while each row
   sized its own label */
body.v3 .v3Break li b { white-space: nowrap; }

/* (R79 — the desktop overrides for .v3Disc went with the class itself.) */

/* ==========================================================================
   R59 — sbTourism, the single tourism slide
   --------------------------------------------------------------------------
   The R56-R58 journey stepper (jrStage crossfade, jrSteps, jrTicks) is gone
   with its engine; the strip is one looping clip in the page's OWN
   .splitBanner/.sbMedia component (css/style.css:1747-1776), full-bleed as
   before, wearing the standard .sbMedia bottom scrim — no more full-height
   near-black wash, so the strip sits inside the light page.
   ========================================================================== */
body.v3 .sbSection.sbTourism { padding: 0; }
body.v3 .sbTourism .splitBanner {
  max-width: none;
  margin: 0;
  padding: 0;
  gap: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
}
/* R59 set this to 1:1 to match the consult banner, on his call that "the two
   full-bleed banners on this page are one system and must not sit at different
   ratios". R65 moved sbConsult to 16:9/21:9 on his later call, so the square
   here was what broke the pairing, not what kept it.
   R67: sbTourism follows sbConsult back into the pair. The packages reel is a
   native 16:9 cut of 16:9 sources, so a 1:1 box would crop 44% off every beat —
   the terminal doors, the car at the kerb and the market stalls are all framed
   wide, and a square window cuts exactly the context that makes them read as
   travel. The ratio therefore moves off the container onto the video, the sbCrm
   idiom (css/v3.css:957), and the video returns to normal flow so its own ratio
   drives the band height. Same edit, same reasoning as R65 on sbConsult
   (css/v3.css:365-395).
   R88 REVERSED R67 on his dictation: sbTourism (and sbCrm) are 1:1 squares
   again, riding the R46.3 recipe in css/v3.css. The R67 sbMedia/video rules
   that put the video back in normal flow at 16:9 / 21:9 lived here and — this
   file loading LAST — silently beat the square recipe, so they are retired in
   place rather than out-specificity'd. The crop cost R67 named is accepted
   again, the same call as the R86 choose-film cell. */
/* DIRECT children only — a bare `.sbMedia img` also matches the WhatsApp icon
   inside an overlay CTA and blows it up into a full-width band
   (css/v3.css:373-375 paid for this once). */
body.v3 .sbTourism .sbMedia > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
body.v3 .sbTourism .sbMedia,
body.v3 .sbTourism .sbMedia video {
  border-radius: 0;
  box-shadow: none;
}

/* the eyebrow/caption pair, values carried from the deleted jr* kit so the
   overlay type does not change size with the engine's removal */
body.v3 .sbTourism .sbEyebrow {
  margin: 0;
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--brand-gold-light);             /* style.css:30 */
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
}
body.v3 .sbTourism .sbCaption {
  margin: 0;
  max-width: 46ch;
  font-size: clamp(0.78rem, 2.4vw, 0.95rem);
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.92);
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
}
body.v3 .sbTourism .sbOverlay { gap: 8px; }

/* phone: the eyebrow carries nothing the title does not — the R56 call, kept */
@media (max-width: 640px) {
  body.v3 .sbTourism .sbEyebrow { display: none; }
}


/* ============ R91 — the treatment result: picture LEFT, text RIGHT ============
   His dictation: "the picture should be in the left and the text should be in
   the right". The R90 stack becomes a two-column row, so the shot and the
   price it explains read as one unit. Both children keep their own styling;
   only the axis is new. The row collapses to a stack under 420px, where a 40%
   column cannot carry a 27px price. */
body.v3 .bbResultRow {
  display: flex;
  align-items: stretch;
  gap: 10px;
  margin-top: 11px;
}
body.v3 .bbResultRow .bbShot {
  flex: 0 0 40%;
  margin: 0;
}
body.v3 .bbResultRow .bbShot img { height: 100%; aspect-ratio: auto; min-height: 118px; }
body.v3 .bbResultRow .v3Out { flex: 1 1 0; min-width: 0; margin: 0; display: flex; flex-direction: column; justify-content: center; }
/* when no treatment is picked the figure is [hidden]; the price card then owns
   the full width rather than sitting in a 60% column with dead space beside it */
body.v3 .bbResultRow .bbShot[hidden] { display: none; }
body.v3 .v3Out .oValue .oOnly {
  font-style: normal;
  font-size: 0.5em;
  font-weight: 700;
  letter-spacing: 0.02em;
  /* explicit, not inherited: the sand at 0.85 opacity read as near-black on
     the navy card in the R91 render check */
  color: rgba(255, 255, 255, 0.78);
}
/* the row holds on phones too — "picture left, text right" is the instruction,
   and a 393px phone still gives the shot ~140px beside a legible price card.
   Only below 330px, where the price would wrap mid-figure, does it stack. */
@media (max-width: 430px) {
  body.v3 .bbResultRow { gap: 8px; }
  body.v3 .bbResultRow .bbShot { flex: 0 0 38%; }
  body.v3 .bbResultRow .v3Out { padding: 12px 10px; }
  body.v3 .bbResultRow .v3Out .oValue { font-size: 23px; }
  body.v3 .bbResultRow .v3Out .oNote { font-size: 9.5px; }
}
@media (max-width: 330px) {
  body.v3 .bbResultRow { flex-direction: column; }
  body.v3 .bbResultRow .bbShot { flex: 0 0 auto; }
  body.v3 .bbResultRow .bbShot img { aspect-ratio: 16 / 9; height: auto; min-height: 0; }
}
