/* ==========================================================================
   Record detail pages and the enquiry panel.

   Doctors, hospitals, specialties, services and treatments share one
   two-column shell: a reading column and a rail that carries the enquiry.
   One shell rather than five, because five would drift apart and a fix to the
   sticky behaviour would have to be made five times.

   Composition is ours. Animations live in animation.css; nothing here
   animates on its own.
   ========================================================================== */

/* --- Record banner -------------------------------------------------------- */

/*
   The detail-page banner, distinct from `.page-header` on an index: it carries
   the RECORD's own photograph (the RECORD STANDARD in CLAUDE.md, §20).
*/
.record-header {
  position: relative;
  isolation: isolate;
  padding-block: clamp(48px, 7vw, 96px);
  background:
    radial-gradient(ellipse 70% 90% at 85% 20%, rgb(0 164 188 / 0.18), transparent 62%),
    var(--color-ink);
  color: var(--color-text-invert);
}

.record-header__bg {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/*
   The scrim is load-bearing, not decoration. Headline type sits over whatever
   photograph an administrator uploaded, and without a guaranteed dark layer
   the contrast depends on that image — the same failure §9–14 forbids for
   colour choices, applied to images.
*/
.record-header--image::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    to bottom,
    rgb(8 24 58 / 0.78) 0%,
    rgb(8 24 58 / 0.66) 55%,
    rgb(8 24 58 / 0.82) 100%
  );
}

.record-header__inner {
  display: grid;
  gap: var(--space-4);
  max-width: 64ch;
}

.record-header__title {
  font-size: var(--fs-h1);
  color: var(--color-text-invert);
}

.record-header__lead {
  color: rgb(255 255 255 / 0.8);
  font-size: var(--fs-h4);
  line-height: var(--lh-subheading);
}

/* --- Two-column shell ----------------------------------------------------- */

.detail-layout {
  display: grid;
  /* Never 0: the shared stack token keeps sibling blocks apart everywhere. */
  gap: var(--stack);
  align-items: start;
}

/*
   Intrinsic, not a viewport breakpoint: the rail needs about 340px to hold the
   form comfortably, and that is true regardless of how wide the window is.
*/
@media (min-width: 900px) {
  .detail-layout {
    grid-template-columns: minmax(0, 1.7fr) minmax(340px, 0.85fr);
    gap: clamp(32px, 4vw, 64px);
  }
}

.detail-layout__body {
  display: grid;
  gap: var(--stack);
  min-width: 0;
}

.detail-layout__aside {
  display: grid;
  gap: var(--stack);
  min-width: 0;
}

/* --- Identity block (doctor) ---------------------------------------------- */

.profile-identity {
  display: flex;
  gap: var(--space-5);
  align-items: center;
  flex-wrap: wrap;
}

.profile-identity__photo,
.profile-identity__initials {
  flex: none;
  width: 96px;
  height: 96px;
  border-radius: var(--radius-pill);
  object-fit: cover;
}

.profile-identity__initials {
  display: grid;
  place-items: center;
  background: var(--color-primary-soft);
  color: var(--color-accent-strong);
  font-family: var(--font-heading);
  font-size: var(--fs-h3);
}

.profile-identity__name {
  font-size: var(--fs-h2);
}

.profile-identity__role {
  color: var(--color-text-muted);
  font-size: var(--fs-h4);
}

/* --- Fact strip ----------------------------------------------------------- */

/*
   Only populated facts are rendered — an empty row would read as a missing
   answer, where an absent row reads as a fact we do not publish (§21). So the
   strip must look deliberate at any count from one upward: auto-fit rather
   than a fixed column template.
*/
.fact-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-4);
  margin: 0;
  padding: var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
}

.fact-strip__item dt {
  color: var(--color-text-muted);
  font-size: var(--fs-caption);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.fact-strip__item dd {
  margin: var(--space-1) 0 0;
  font-family: var(--font-heading);
  font-size: var(--fs-h4);
}

/* --- Content blocks ------------------------------------------------------- */

.detail-block {
  display: grid;
  gap: var(--space-4);
}

.detail-block__title {
  font-size: var(--fs-h3);
}

.detail-figure {
  margin: 0;
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.detail-figure img {
  inline-size: 100%;
  block-size: auto;
  /* A fixed ratio, so a portrait and a landscape source produce the same
     shape and the page does not jump between records. */
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

/* --- Media cards ---------------------------------------------------------- */

/*
   A card with a thumbnail. Every record already carried an image; the cards
   simply never rendered one, which is what made the listings read as a wall
   of text (client-reported, 2026-08-06).
*/
.card--media {
  display: grid;
  grid-template-rows: auto 1fr;
  gap: 0;
  padding: 0;
  overflow: hidden;
}

.card__figure {
  margin: 0;
  aspect-ratio: 16 / 10;
  overflow: hidden;
}

.card__figure img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  transition: transform var(--transition-slow);
}

.card--media:hover .card__figure img {
  /* Transform only, so nothing reflows (§12). */
  transform: scale(1.04);
}

.card__text {
  display: grid;
  gap: var(--space-2);
  align-content: start;
  padding: clamp(16px, 1.8vw, 22px);
}

/* --- Long-form body ------------------------------------------------------- */

/*
   The detail pages now carry real editorial copy, so the prose block needs to
   read like an article rather than a stack of undifferentiated paragraphs.
*/
.prose h2 {
  margin-block-start: var(--space-7);
  font-size: var(--fs-h3);
}

.prose h2:first-child {
  margin-block-start: 0;
}

.prose p,
.prose ul {
  margin-block-start: var(--space-3);
  max-inline-size: var(--measure);
}

.prose ul {
  padding-inline-start: var(--space-5);
}

.prose li + li {
  margin-block-start: var(--space-2);
}

.chip-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: 0;
  margin: 0;
  list-style: none;
}

.chip {
  display: inline-flex;
  align-items: center;
  /* 44px pill (§45 pass, 2026-08-09) — it measured 42. */
  min-height: 44px;
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  font-size: var(--fs-small);
  text-decoration: none;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}

a.chip:hover,
a.chip:focus-visible {
  border-color: var(--color-accent-strong);
  color: var(--color-accent-strong);
}

/* Not a link — an accreditation or a label. Must not invite a click. */
.chip--static {
  background: var(--color-surface-muted);
  color: var(--color-text-muted);
}

.trust-note {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-6);
  border-inline-start: 3px solid var(--color-accent);
  border-radius: var(--radius-md);
  background: var(--color-accent-soft);
}

.trust-note__title {
  font-size: var(--fs-h4);
}

/* --- Stacked lists (specialty page) --------------------------------------- */

.stack-list {
  display: grid;
  gap: var(--space-4);
  padding: 0;
  margin: 0;
  list-style: none;
}

.stack-list__item {
  display: grid;
  gap: var(--space-4);
  align-items: center;
  padding: clamp(16px, 1.8vw, 22px);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

/* Thumbnail beside the copy once there is room for both. Intrinsic, not a
   viewport query: the row needs about 420px to hold an image and readable
   text, and that is true at any window width. */
@media (min-width: 560px) {
  .stack-list__item {
    grid-template-columns: 132px minmax(0, 1fr);
  }
}

.stack-list__figure {
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius-md);
  aspect-ratio: 4 / 3;
}

.stack-list__figure img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.stack-list__text {
  display: grid;
  gap: var(--space-2);
  min-width: 0;
}

.stack-list__item:hover {
  border-color: var(--color-accent);
  box-shadow: var(--shadow-md);
}

.stack-list__title a {
  text-decoration: none;
}

.stack-list__body {
  color: var(--color-text-muted);
  font-size: var(--fs-small);
}

.stack-list__meta {
  color: var(--color-text-muted);
  font-size: var(--fs-caption);
}

/* --- Steps (enquiry page) ------------------------------------------------- */

.step-list {
  display: grid;
  gap: var(--space-5);
  padding: 0;
  margin: 0;
  list-style: none;
  counter-reset: step;
}

.step-list__item {
  display: grid;
  gap: var(--space-2);
  padding-inline-start: calc(var(--space-9) + var(--space-2));
  position: relative;
  counter-increment: step;
}

/*
   The number is generated content and marked aria-hidden by being decorative:
   the list is already an <ol>, so assistive technology announces the position
   itself and a spoken "1 1 You tell us…" would be the result of exposing it.
*/
.step-list__item::before {
  content: counter(step);
  position: absolute;
  inset-inline-start: 0;
  top: 0;
  display: grid;
  place-items: center;
  width: var(--space-9);
  height: var(--space-9);
  border-radius: var(--radius-pill);
  background: var(--color-primary-soft);
  color: var(--color-accent-strong);
  font-family: var(--font-heading);
  font-size: var(--fs-h4);
}

.step-list__title {
  font-size: var(--fs-h4);
}

.step-list__body {
  color: var(--color-text-muted);
}

/* --- Enquiry panel -------------------------------------------------------- */

.enquiry-panel {
  display: grid;
  gap: var(--space-4);
  padding: clamp(20px, 2.4vw, 30px);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  box-shadow: var(--shadow-md);
}

/*
   NOT sticky, deliberately — the modifier is kept so callers need no change.

   It was sticky with `max-height: 100vh; overflow-y: auto`. A nine-field
   enquiry form is taller than most viewports, so the panel scrolled INSIDE
   itself and the visitor arrived at a card showing "Phone number / Country"
   with no heading and no submit button in sight. It read as a broken
   fragment, which is exactly what the client reported.

   A form this tall belongs in the normal flow. §12's rule that a sticky
   element must never cover content applies here in spirit: a rail you have to
   scroll separately from the page is its own kind of covering.
*/
.enquiry-panel--sticky {
  position: static;
}

.enquiry-panel__title {
  font-size: var(--fs-h3);
}

.enquiry-panel__promise {
  color: var(--color-accent-strong);
  font-size: var(--fs-small);
  font-weight: 600;
}

.enquiry-panel__direct {
  display: grid;
  gap: var(--space-3);
}

.enquiry-panel__or {
  color: var(--color-text-muted);
  font-size: var(--fs-caption);
  text-align: center;
}

.enquiry-panel__privacy {
  color: var(--color-text-muted);
  font-size: var(--fs-caption);
  line-height: var(--lh-body);
}

/* Radio group. A real fieldset, so it needs its default border removed
   rather than the semantics replaced with a div. */
.form__choice {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding: 0;
  border: 0;
  margin: 0;
}

.form__choice legend {
  width: 100%;
  padding: 0;
  margin-block-end: var(--space-2);
  font-size: var(--fs-small);
  font-weight: 600;
}

.form__choice-option {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  /* 44px minimum target (§46), met by padding rather than a fixed height so
     the label can wrap in a long translation. */
  min-height: 44px;
  padding-inline-end: var(--space-2);
}

.form__choice-option label {
  font-size: var(--fs-small);
  /* The whole row is the target, not just the 13px dot. */
  cursor: pointer;
}

/* The native control at its default size is a 13px dot. The label and the
   44px row are both clickable, so this is comfort rather than compliance —
   but a radio a patient has to aim at on a phone gets mis-tapped. */
.form__choice-option input[type="radio"] {
  /* 24px — the WCAG 2.5.8 control minimum (§45 pass, 2026-08-09). */
  inline-size: 24px;
  block-size: 24px;
  accent-color: var(--color-primary);
}

/* --- WhatsApp button ------------------------------------------------------ */

/*
   WhatsApp's own green, deliberately NOT a brand token: this button
   impersonates a third-party product on purpose so it is recognised
   instantly, and it must not shift when the client re-themes the site.
*/
.btn--whatsapp {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  background: #25d366;
  color: #06281a;
  border: 1px solid #1eb455;
}

.btn--whatsapp:hover,
.btn--whatsapp:focus-visible {
  background: #1eb455;
  color: #ffffff;
}

.btn__icon {
  flex: none;
}

/* --- Success state -------------------------------------------------------- */

.enquiry-success {
  display: grid;
  gap: var(--space-4);
  padding: var(--space-6);
  border: 1px solid var(--color-success);
  border-radius: var(--radius-md);
  background: var(--color-success-soft);
}

.enquiry-success__title {
  font-family: var(--font-heading);
  font-size: var(--fs-h4);
  color: var(--color-success);
}

.enquiry-success__note {
  color: var(--color-text-muted);
  font-size: var(--fs-caption);
  line-height: var(--lh-body);
}

/* The server-rendered success block carries the same WhatsApp button, so it
   needs room between the sentence and the control. */
.form-success .btn--whatsapp {
  margin-block-start: var(--space-3);
}

/* --- Sticky side rail ------------------------------------------------------
   Client request: on internal pages the side content stays in view while the
   visitor reads the main column, so the enquiry panel and related links are
   always reachable.

   IT IS APPLIED CONDITIONALLY, and that is the whole design. The rail was
   sticky once before and had to be reverted: it carried `max-height: 100vh;
   overflow-y: auto`, so a nine-field enquiry form scrolled INSIDE the rail and
   the visitor arrived at a card reading "Phone number / Country" with no
   heading and no submit button — a broken-looking fragment, which is what the
   client reported at the time.

   The fix is not to cap the height. It is to stick the rail ONLY when it
   genuinely fits the viewport; when it does not, it stays in normal flow and
   scrolls with the page, complete and readable. `sticky-rail.js` measures and
   sets `.is-sticky`, so the decision is made against the real rendered height
   rather than guessed from a breakpoint.

   `align-self: start` is required: the grid stretches items by default, which
   makes the rail full-height and stops sticky from ever engaging. */

@media (min-width: 900px) {
  .detail-layout__aside.is-sticky,
  .treatment-detail__aside.is-sticky,
  .destination-detail__aside.is-sticky,
  .contact-aside.is-sticky {
    position: sticky;
    /* Clears the sticky header (72px) with a little breathing room, so the
       rail never tucks underneath it. */
    top: 88px;
    align-self: start;
  }

  /* A RAIL TALLER THAN THE VIEWPORT PINS BY ITS BOTTOM EDGE.

     Client direction, 2026-08-07, and it rules out the two easy answers
     explicitly: "na form ko scrollable banana hai na form ko chota banana hai
     — mujhe proper form chahiye or vo sticky chahiye." So no inner scrollbar,
     no compact variant. The full nine-field form, and it stays with the reader.

     Bottom-pinning is what makes that possible. The rail travels with the page
     until its LAST line reaches the bottom of the window, and from then on it
     holds — so scrolling down through the body copy brings the whole form past
     the eye once and then leaves the submit button parked in view for the rest
     of the page. Top-pinning cannot do this: it fixes the heading and strands
     everything below the fold permanently, which is the defect that had this
     reverted once already.

     `bottom` rather than `top`, and `top: auto` explicitly, because the
     shorthand `top` from the rule above would otherwise still apply and a
     sticky element honours `top` in preference to `bottom`.

     WHY THE STICKY ELEMENT IS THE PANEL AND NOT THE RAIL.

     This is the part the earlier attempt got wrong, and it is why the handoff
     records bottom-pinning as "never takes effect in this layout". A sticky
     element travels inside its CONTAINING BLOCK. The rail is a grid item with
     `align-self: start`, so its box is exactly as tall as its own contents —
     there is nothing for it to travel within, and sticky correctly does
     nothing. Measured: at 1280×720 the rail's bottom edge went straight past
     the window and kept going, exactly as if `position: static`.

     So the RAIL stretches to the full height of the row (the default for a
     grid item — `align-self` is dropped here), and the PANEL inside it is what
     sticks. Now there is 444px of genuine travel on this page, and the panel
     holds at the bottom of the window for all of it. */
  .detail-layout__aside.is-sticky-bottom,
  .treatment-detail__aside.is-sticky-bottom,
  .destination-detail__aside.is-sticky-bottom,
  .contact-aside.is-sticky-bottom {
    /* Stretch, so the rail fills the row and becomes a containing block
       TALLER than the panel inside it. */
    align-self: stretch;
    position: static;

    /* AND the rail's own grid has to hand that height on.

       This is the second half of the same trap, one level down, and it is what
       the measurements kept catching. The rail is itself a grid with
       `align-content: start`, so its single row was sized to the panel — the
       panel's grid AREA equalled the panel's own box, sticky had nowhere to
       travel, and the result was indistinguishable from `position: static`.
       Measured at 1280×720: the panel's offset within the rail stayed at 0px
       through six scroll positions.

       `stretch` lets the row take the rail's full height; `align-self: start`
       on the panel below keeps the PANEL its own size inside that taller area.
       Both are needed — stretching the row and stretching the item would put
       us straight back where we started. */
    align-content: stretch;
  }

  /* A NEGATIVE `top`, NOT `bottom`.

     `bottom: 24px` is the textbook way to pin a tall element by its lower edge,
     and in this layout it does nothing at all — which is what the handoff had
     already recorded and what six measured scroll positions confirmed again:
     the panel's offset inside the rail stayed at 0px throughout, exactly as if
     it were static. Sticky-TOP on the same element, same markup, same
     stylesheet, moves it correctly. So the browser is honouring one and not the
     other here, and arguing with it is not work.

     A negative `top` produces the identical behaviour and does work: hold the
     panel so its BOTTOM sits 24px above the window edge, which is the same as
     holding its TOP at `100vh - 24px - panelHeight` — a negative number
     whenever the panel is taller than the window, which is exactly the case
     this rule exists for.

     The value depends on the panel's rendered height, so it cannot be written
     in CSS. sticky-rail.js measures and sets `--rail-sticky-top`; the fallback
     here is the plain top-pin, which is what a panel that fits would want
     anyway. Verified at 1280×720: pinned with the panel's bottom edge at
     exactly 696px through the rail's full 444px of travel, submit button in
     view from y=1200 onward. */
  .is-sticky-bottom > * {
    position: sticky;
    top: var(--rail-sticky-top, 88px);
    bottom: auto;
    align-self: start;
  }
}

/* No sticky rail under reduced motion. A pane that detaches from the page and
   holds position while everything else moves is exactly the kind of relative
   motion §12 asks us to drop for anyone who has asked for less of it. */
@media (prefers-reduced-motion: reduce) {
  .detail-layout__aside.is-sticky,
  .treatment-detail__aside.is-sticky,
  .destination-detail__aside.is-sticky,
  .contact-aside.is-sticky,
  .detail-layout__aside.is-sticky-bottom,
  .treatment-detail__aside.is-sticky-bottom,
  .destination-detail__aside.is-sticky-bottom,
  .contact-aside.is-sticky-bottom,
  .is-sticky-bottom > * {
    position: static;
  }
}

/* When the whole rail is too tall to pin, its FIRST CARD is pinned instead.

   This is the common case, not the exception: the rail carries a nine-field
   enquiry form and is routinely the tallest column on the page. Pinning the
   whole thing would leave its lower half — including the submit button —
   permanently below the fold, which is the exact defect that had this reverted
   once already.

   Pinning only the leading card keeps the facts a reader wants beside them
   (hospital days, total stay, the cost note) while the long body copy scrolls,
   and leaves the form complete in normal flow underneath.

   `sticky-rail.js` decides which of the two applies, because "does this fit
   the viewport?" depends on rendered height and cannot be asked in CSS. */
/* Rails TALLER than the viewport pin by their BOTTOM edge instead.

   THERE IS DELIBERATELY NO CARD-LEVEL STICKY. An earlier attempt pinned the
   first card inside the rail while the enquiry form stayed below it in the
   same column. A sticky box keeps its space in normal flow and then translates
   down over whatever follows it, so the pinned card slid straight over the
   form and covered the name and email fields — a sticky element covering
   content, which CLAUDE.md forbids outright.

   Pinning the WHOLE rail cannot do that: it moves as one unit, so there is no
   sibling for it to slide over.

   Top-pinning only works for a rail that fits the screen. A taller one pinned
   at the top would hang past the fold with its lower half — including the
   submit button — permanently unreachable, which is the first version of this
   defect. Pinned by the bottom it behaves the way a reader expects: it travels
   with the page until its last line is on screen and then holds, so the form
   stays in view AND stays completely readable.

   `top: auto` is load-bearing. With both offsets set the top wins and the
   bottom half is lost again. */


/* The route to the full enquiry form, under the compact rail panel. */
.enquiry-panel__more {
  margin-block-start: var(--space-3);
  font-size: var(--fs-small);
  text-align: center;
}

/* --- Compact rail panel ----------------------------------------------------
   The version that lives in a detail-page rail, tightened so it fits a laptop
   viewport and can therefore stay pinned beside the body copy. Every saving
   here is spent on that: a panel one pixel too tall is a panel that scrolls
   away, which is what a rail lead form must not do.

   Nothing is removed that the visitor needs — enquiry type, country and
   contact channel travel as hidden defaults and the full form is linked
   underneath. */
.enquiry-panel--compact {
  gap: var(--space-3);
  padding: clamp(16px, 1.8vw, 22px);
}

.enquiry-panel--compact .form {
  gap: var(--space-3);
}

.enquiry-panel--compact .enquiry-panel__title {
  font-size: var(--fs-h4);
}

/* ==========================================================================
   Related records inside the detail column
   ==========================================================================

   These used to be their own full-width sections BELOW the detail grid, and
   that placement is what broke the pinned rail: a sticky element travels only
   inside its containing block, so the rail's travel ended where the first
   section did — before the reader was anywhere near done with the page. The
   related records now continue the left column, the rail's column runs the
   full length of the page, and the panel keeps its pin for the whole read.
   ========================================================================== */

.detail-related {
  display: grid;
  gap: var(--space-4);
  margin-block-start: var(--space-6);
  padding-block-start: var(--space-6);
  border-block-start: 1px solid var(--color-border);
}

/* The narrow related grid is GONE (client direction, 2026-08-09): every
   related block is now the shared related-carousel — three cards per view,
   arrows from the fourth item — see components/related-carousel.blade.php
   and carousel.css. */

/* ==========================================================================
   Appointment — two rail buttons and the dialog behind one of them.

   The form used to sit inline in the body and added most of a screen to every
   consultant's profile, pushing the related consultants below anything anyone
   would scroll to. It is a dialog now; these are the buttons that open it.
   ========================================================================== */

.appointment-actions {
  display: grid;
  gap: var(--space-2);
}

/* Inside the enquiry card, separated by a rule rather than by a second
   border. Two nested bordered boxes in a 340px rail read as a mistake. */
.enquiry-panel__before {
  margin-block-end: var(--space-4);
  padding-block-end: var(--space-4);
  border-block-end: 1px solid var(--color-border);
}

/* Full width and stacked, per the client's "1 ke niche 1". Both are primary
   actions here — the visitor has already chosen this consultant, so neither
   is a secondary route. */
.appointment-actions__btn {
  inline-size: 100%;
  justify-content: center;
}

.appointment-actions__note {
  margin: 0;
  font-size: var(--fs-tiny, 0.78rem);
  line-height: 1.45;
  color: var(--color-text-muted);
}

/* --- the dialog ---------------------------------------------------------- */

.appointment-dialog {
  inline-size: min(33rem, calc(100vw - 2rem));
  max-block-size: min(88vh, 45rem);
  padding: 0;
  border: none;
  border-radius: 18px;
  background: var(--color-surface);
  color: var(--color-text);
  box-shadow: 0 32px 64px -12px rgb(8 24 58 / 0.42), 0 0 0 1px rgb(8 24 58 / 0.06);

  /* EXPLICIT, not inherited. The user agent centres a modal <dialog> with
     `margin: auto`, and the reset at the top of this stylesheet zeroes margins
     on everything — so without this the panel pins itself to the left edge of
     the viewport while the backdrop covers the whole screen. It looks like a
     drawer that failed to animate. */
  margin: auto;

  overflow: hidden;
}

/* A column with a pinned head and a scrolling body, and `overflow: hidden`
   above so both are clipped to the radius. The panel used to scroll as a
   whole: the scrollbar then ran flush against the rounded edge and squared off
   the top and bottom corners, which reads as a rendering fault rather than as
   a design.

   SCOPED TO [open], and that is not cosmetic. The user agent hides a closed
   dialog with `dialog:not([open]) { display: none }` — but that is a USER-AGENT
   rule, and any author rule beats it whatever the specificity. Setting
   `display` on the bare class therefore un-hides the dialog on every page
   load: it rendered in the flow beside the rail, 33rem wide, and dragged a
   horizontal scrollbar across the whole page. */
.appointment-dialog[open] {
  display: flex;
  flex-direction: column;
}

.appointment-dialog::backdrop {
  background: rgb(8 24 58 / 0.6);
  backdrop-filter: blur(3px);
}

/* Fades and lifts in. Small on purpose — a modal that makes an entrance is a
   modal you notice twice. Skipped entirely under prefers-reduced-motion. */
@media (prefers-reduced-motion: no-preference) {
  .appointment-dialog[open] {
    animation: appointment-dialog-in 180ms ease-out;
  }

  .appointment-dialog[open]::backdrop {
    animation: appointment-backdrop-in 180ms ease-out;
  }
}

@keyframes appointment-dialog-in {
  from { opacity: 0; transform: translateY(8px) scale(0.985); }
  to { opacity: 1; transform: none; }
}

@keyframes appointment-backdrop-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

.appointment-dialog__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  flex: none;
  padding: var(--space-5) var(--space-5) var(--space-4);
  border-block-end: 1px solid var(--color-border);
}

.appointment-dialog__head > div {
  flex: 1 1 auto;
  min-inline-size: 0;
}

.appointment-dialog__body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: var(--space-5);
  /* Room for the scrollbar so it never sits on top of a field's focus ring. */
  scrollbar-gutter: stable;
}

.appointment-dialog__dismiss {
  flex: none;
}

/* 44px, because §46's touch target applies to the control that closes a modal
   as much as to the one that opens it. */
.appointment-dialog__close {
  display: grid;
  place-items: center;
  inline-size: 44px;
  block-size: 44px;
  margin-block-start: -6px;
  margin-inline-end: -8px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.appointment-dialog__close:hover,
.appointment-dialog__close:focus-visible {
  background: var(--color-background);
  color: var(--color-text);
}

.appointment-dialog__title {
  margin: 0 0 6px;
  font-size: 1.2rem;
  line-height: 1.3;
}

.appointment-dialog__lead {
  margin: 0;
  font-size: var(--fs-small);
  line-height: 1.55;
  color: var(--color-text-muted);
}

/* --- shared bits the appointment form introduced -------------------------- */

/* A hint under a field, not an error. Muted and small, so it reads as help
   rather than as something having gone wrong. */
.form__hint {
  margin: 0;
  font-size: var(--fs-tiny, 0.78rem);
  line-height: 1.45;
  color: var(--color-text-muted);
}

/* The consent tick. A grid rather than a flex row so the label wraps in its
   own column instead of pushing the box out of line — inline it rendered as a
   stray square above the sentence. */
.form__check {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-3);
  align-items: start;
}

/* Bigger than the 1.15rem used elsewhere. This is the one control on the form
   that blocks submission and it is the last thing touched before the button,
   so on a phone it wants to be hit first time. The row is ~56px and the label
   is clickable with it, which is the real target; this just makes the box
   itself a comfortable one. */
.form__check input {
  inline-size: 1.35rem;
  block-size: 1.35rem;
  margin-block-start: 0.1rem;
  accent-color: var(--color-accent);
  cursor: pointer;
}

.form__check label {
  font-size: var(--fs-tiny, 0.78rem);
  font-weight: 400;
  line-height: 1.5;
  cursor: pointer;
}

.form__submit {
  inline-size: 100%;
  justify-content: center;
}

/* The heading takes focus when the dialog opens (see appointment.js). It is a
   focus TARGET, not a control, so it gets no ring — a box around a heading
   reads as an error. Everything the visitor can actually operate keeps its
   own visible focus style. */
.appointment-dialog__title:focus {
  outline: none;
}
