/* ==========================================================================
   Booking — full-screen takeover variant of .dialog.

   Takes the mobile sheet the shared dialog already becomes below 40rem
   (dialog.css) and applies it at every breakpoint: pinned, edge-to-edge,
   full viewport. The frame is a three-row grid — header, scrollable body,
   action bar — so content never sits under the persistent actions.

   Behaviour: assets/js/booking-wizard.js. Markup:
   inc/components/booking-dialog.php.
   ========================================================================== */

.dialog.booking {
  position: fixed;
  inset: 0;
  margin: 0;
  width: 100%;
  max-width: 100%;
  height: 100vh;
  height: 100dvh;
  max-height: none;
  padding: 0;
  border-radius: 0;
  overflow: hidden;
}

.dialog.booking[open] {
  display: grid;
  grid-template-rows: auto 1fr auto;
  align-items: stretch;
  gap: 0;
  animation: dialog-in-mobile 0.3s ease both;
}

.dialog.booking[open].is-closing {
  animation: dialog-out-mobile 0.25s ease both;
}

/* The page behind must not scroll while the takeover is open. */
body.has-booking-open {
  overflow: hidden;
}

/* === Header ==============================================================
   mw-lg column matches body + actions. .heading is logo | BOKA MÖTE | close
   (vertically centred). Progress sits in-flow under that row. */

.dialog.booking > header {
  position: relative;
  z-index: 1;
  background: var(--wp--preset--color--off-white, #fff);
  transition: box-shadow 0.25s ease;
}

/* Lifts off the page once the body has moved under it. Absent at rest. */
.dialog.booking.is-scrolled > header {
  box-shadow: 0 var(--s-4) var(--s-20)
    color-mix(in srgb, var(--wp--preset--color--warm-brown) 14%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .dialog.booking > header {
    transition: none;
  }
}

.dialog.booking > header > .container {
  display: grid;
  gap: var(--s-12);
  padding-block: var(--nav-padding-y) var(--s-12);
}

.dialog.booking .heading {
  display: grid;
  grid-template-columns:
    var(--nav-logo-wide-width) minmax(0, 1fr)
    var(--nav-logo-wide-width);
  align-items: center;
  column-gap: var(--s-16);
  min-height: var(--nav-logo-size);
}

/* Same wordmark as site-header .logo — brown via the dialog’s currentColor. */
.dialog.booking .heading > .mark {
  grid-column: 1;
  justify-self: start;
  display: inline-flex;
  align-items: center;
  width: var(--nav-logo-wide-width);
  height: var(--nav-logo-size);
  color: inherit;
}

.dialog.booking .heading > .mark > .badge,
.dialog.booking .heading > .mark > .wordmark {
  align-items: center;
  width: 100%;
  height: 100%;
}

.dialog.booking .heading > .mark > .badge {
  display: none;
}

.dialog.booking .heading > .mark > .wordmark {
  display: inline-flex;
}

.dialog.booking .heading > .mark svg {
  display: block;
  width: 100%;
  height: 100%;
}

.dialog.booking .heading > .mark > .wordmark path {
  fill: currentColor;
}

/* Same type recipe as a.hub.clean — centred with logo / close on one row. */
.dialog.booking .heading > .title {
  grid-column: 2;
  justify-self: center;
  margin: 0;
  min-width: 0;
  font-family: var(--link-font-family);
  font-size: var(--link-font-size-hub);
  font-weight: var(--link-font-weight-hub);
  font-style: var(--link-font-style);
  letter-spacing: var(--link-letter-spacing-hub);
  line-height: var(--link-line-height-hub);
  text-transform: var(--link-text-transform-hub);
  color: inherit;
}

.dialog.booking .heading > .title[hidden] {
  display: none;
}

@media (min-width: 64rem) {
  .dialog.booking .heading > .title {
    font-size: var(--font-size-base);
  }
}

@media (min-width: 100rem) {
  .dialog.booking .heading > .title {
    font-size: var(--link-font-size-hub);
  }
}

/* The shared dialog pins .close to the corner; here it is a grid item. */
.dialog.booking .heading > .close {
  grid-column: 3;
  justify-self: end;
  position: static;
  top: auto;
  right: auto;
}

/* === Steps ===============================================================
   Under the title row inside mw-lg. Four decorative segments; step index
   is announced via aria-label on the list (no visible X/4). */

.dialog.booking .steps {
  display: flex;
  justify-content: center;
  gap: var(--s-8);
  margin: 0;
  padding: 0;
  list-style: none;
}

.dialog.booking .steps[hidden] {
  display: none;
}

.dialog.booking .steps li {
  width: 100%;
  max-width: 5.5rem;
  height: var(--s-4);
  border-radius: 9999rem;
  background: color-mix(in srgb, var(--wp--preset--color--warm-brown) 16%, transparent);
  transition: background-color 0.25s ease;
}

.dialog.booking .steps li.is-done {
  background: var(--wp--preset--color--warm-brown);
}

/* === Body ================================================================
   Scrollport only. Each step is section.section > .container (mw-* on the
   container), same primitives as normal pages. */

.dialog.booking > .body {
  min-height: 0; /* let the 1fr grid row shrink so only .body scrolls */
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  /* Slots appearing / Fortsätt unlocking must not re-anchor the
     scrollport — that is what read as a body jump on mobile. */
  overflow-anchor: none;
}

/* Dialog sections: tighter block padding; fill the scrollport so choice
   steps can centre their answers under a stable top heading.
   .section is already column-flex — children must flex-grow; min-height:100%
   alone does not stretch .container / .question past their content. */
.dialog.booking .section.step {
  --gap: var(--s-32);
  --pb: var(--s-40);
  align-items: stretch;
  width: 100%;
  min-height: 100%;
  box-sizing: border-box;
}

@media (min-width: 40rem) {
  .dialog.booking .section.step {
    --gap: var(--s-48);
    --pb: var(--s-48);
  }
}

@media (min-width: 64rem) {
  .dialog.booking .section.step {
    --gap: var(--s-60);
  }
}

/* Step content stack inside the shared mw-lg container. */
.dialog.booking .section.step > .container {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  gap: var(--s-40);
  width: 100%;
  min-height: 0;
}

.dialog.booking .section.step[data-booking-step] > .container {
  justify-content: flex-start;
}

.dialog.booking .section.step.centered > .container {
  align-items: center;
  text-align: center;
}

.dialog.booking .section.step[hidden] {
  display: none;
}

/* One question per block. */
.dialog.booking .question {
  display: flex;
  flex-direction: column;
  gap: var(--s-16);
  width: 100%;
}

.dialog.booking .question > * {
  margin: 0;
}

.dialog.booking .question > h4 {
  text-align: center;
  width: 100%;
}

.dialog.booking .question > p:not(.placeholder) {
  max-width: var(--max-ch);
  margin-inline: auto;
  text-align: center;
}

.dialog.booking .question h4:focus {
  outline: none;
}

/* Shared panel surface; component selectors own layout and colour. */
.dialog.booking .surface {
  padding: var(--s-24);
  border: 1px solid var(--border-divider);
  border-radius: var(--border-radius-media);
}

/* === Step 1: topic choices ===========================================
   One centred, single-column choice stack at every width. The shared action
   rules keep buttons full-width on mobile and centred from md upward. */

.dialog.booking .section.step[data-booking-step="1"] > .container {
  align-items: center;
  text-align: center;
}

.dialog.booking .section.step[data-booking-step="1"] > .container > .question {
  flex: 0 1 auto;
}

.dialog.booking .section.step[data-booking-step="1"] .question > .options {
  width: 100%;
  max-width: var(--mw-sm);
  margin: 0 auto;
}

.dialog.booking .section.step[data-booking-step="1"] .option {
  text-align: left;
}

/* === Options =============================================================
   A question's answers. Native radios grouped by name do the work — the
   card is a <label>, the input is visually hidden but still focusable, so
   arrow keys move between answers and :checked drives the selected look
   without a line of JS. The container carries role="radiogroup" only to
   borrow the question heading as its accessible name.

   Mobile defaults to one column; consumers opt into denser columns only
   where their content and available width support it. */

.dialog.booking .options {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-12);
  width: 100%;
}

/* Positioning anchors the radio below, which is stretched over the whole
   card. A .sr-only radio is a 1px box the browser scrolls to on focus,
   and it lands wherever its containing block puts it — that is what made
   picking an option jump the frame. Covering the card means focus can
   only ever scroll to the card itself, which is already in view. */
.dialog.booking .option {
  position: relative;
  display: grid;
  align-content: start;
  gap: var(--s-4);
  padding: var(--s-20) var(--s-24);
  border: 1px solid var(--border-divider);
  border-radius: var(--border-radius-media);
  cursor: pointer;
  transition:
    border-color 0.2s ease,
    box-shadow 0.2s ease,
    background-color 0.2s ease;
}

/* Overrides .sr-only on the radios: same invisibility, but the box is the
   card. Still focusable and still in the accessibility tree. */
.dialog.booking .option > input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  clip-path: none;
  opacity: 0;
  cursor: inherit;
}

.dialog.booking .option .meta {
  font-size: var(--font-size-sm);
  color: color-mix(in srgb, var(--wp--preset--color--warm-brown) 100%, transparent);
  font-weight: 600;
}

/* Step 1 answer cards: left stack (glyph + copy); choice absolutely
   centred to the card — grid 1/-1 only spans explicit rows, so place
   cards with an implicit glyph row would leave the radio top-aligned. */
.dialog.booking .question > .options .option {
  row-gap: var(--s-8);
  padding-inline-end: calc(var(--s-24) + 1.5rem + var(--s-16));
}

.dialog.booking .question > .options .option > .glyph {
  display: grid;
  place-items: start;
  width: 1.5rem;
  height: 1.5rem;
  color: var(--wp--preset--color--warm-brown);
  pointer-events: none;
}

.dialog.booking .question > .options .option > .glyph > svg {
  display: block;
  width: 1.5rem;
  height: 1.5rem;
}

.dialog.booking .question > .options .option > .copy {
  display: grid;
  gap: var(--s-4);
  min-width: 0;
  pointer-events: none;
}

.dialog.booking .question > .options .option .description {
  font-size: var(--font-size-sm);
  line-height: 1.4;
}

.dialog.booking .question > .options .option > .choice {
  position: absolute;
  inset-inline-end: var(--s-24);
  top: 50%;
  translate: 0 -50%;
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  color: var(--wp--preset--color--warm-brown);
  pointer-events: none;
}

.dialog.booking .question > .options .option > .choice > .off,
.dialog.booking .question > .options .option > .choice > .on {
  grid-area: 1 / 1;
  display: grid;
  place-items: center;
}

.dialog.booking .question > .options .option > .choice svg {
  display: block;
  width: 1.5rem;
  height: 1.5rem;
}

.dialog.booking .question > .options .option > .choice .on {
  opacity: 0;
}

.dialog.booking .question > .options .option > .choice .on svg {
  fill: currentColor;
}

.dialog.booking .question > .options .option:has(:checked) > .choice .off {
  opacity: 0;
}

.dialog.booking .question > .options .option:has(:checked) > .choice .on {
  opacity: 1;
}

.dialog.booking .option:hover {
  border-color: color-mix(in srgb, var(--wp--preset--color--warm-brown) 40%, transparent);
}

/* Selected reads as a heavier edge. Drawn as an inset ring rather than a
   2px border so the card never resizes on selection. */
.dialog.booking .option:has(:checked) {
  border-color: var(--wp--preset--color--warm-brown);
  box-shadow: inset 0 0 0 1px var(--wp--preset--color--warm-brown);
  background: color-mix(in srgb, var(--wp--preset--color--warm-brown) 6%, transparent);
}

/* Step-1 answer cards only — timeslot labels stay regular weight so a
   pick cannot reflow the grid and nudge the scroll frame. */
.dialog.booking .question > .options .option:has(:checked) .label {
  font-weight: 500;
}

.dialog.booking .option:has(:focus-visible) {
  outline: 2px solid var(--wp--preset--color--warm-brown);
  outline-offset: 2px;
}

/* Shown but not offered — either the topic rules it out, or the option is
   not live yet. Kept visible so the full set of choices stays legible. */
.dialog.booking .option:has(:disabled) {
  opacity: 0.45;
  cursor: not-allowed;
}

.dialog.booking .option:has(:disabled):hover {
  border-color: var(--border-divider);
}

/* === Step 2: schedule =====================================================
   Mobile-first stack capped at calendar scale. At lg, the mw-md container
   becomes calendar | slots without allowing the times to stretch. */

.dialog.booking .schedule {
  display: grid;
  justify-items: center;
  gap: var(--s-32);
  width: 100%;
  max-width: 26rem;
  margin-inline: auto;
  /* Sits at twice the question's own gap, so the calendar reads as the
     answer area rather than another line of the intro. */
  margin-top: var(--s-16);
}

.dialog.booking .calendar,
.dialog.booking .slots {
  width: 100%;
}

/* Side by side at lg: calendar stays phone-scale and slots take the
   constrained remainder. The stretched slots column centres every state. */
@media (min-width: 64rem) {
  .dialog.booking .schedule {
    grid-template-columns: 26rem minmax(0, 1fr);
    max-width: none;
    justify-items: stretch;
    align-items: stretch;
    gap: var(--s-40);
  }

  .dialog.booking .schedule .calendar {
    width: 26rem;
  }
}

.dialog.booking .calendar {
  display: grid;
  gap: var(--s-16);
}

.dialog.booking .calendar .month {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-16);
}

.dialog.booking .calendar .month p {
  margin: 0;
  text-transform: capitalize;
}

/* No caret-left in assets/svg yet — mirror the right-facing one. */
.dialog.booking .calendar .prev > svg {
  transform: scaleX(-1);
}

.dialog.booking .calendar .weekdays,
.dialog.booking .calendar .days {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: var(--s-4);
}

.dialog.booking .calendar .weekdays span {
  text-align: center;
  font-size: var(--font-size-xs);
  color: color-mix(in srgb, var(--wp--preset--color--warm-brown) 60%, transparent);
}

.dialog.booking .calendar .days button {
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  padding: 0;
  /* Same edge language as timeslot .option cards; 1px always reserved so
     available vs unavailable do not resize the cell. */
  border: 1px solid var(--border-divider);
  border-radius: var(--border-radius-media);
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition:
    background-color 0.2s ease,
    border-color 0.2s ease;
}

.dialog.booking .calendar .days button:hover:not(:disabled) {
  border-color: color-mix(in srgb, var(--wp--preset--color--warm-brown) 40%, transparent);
}

/* Booked out or outside the horizon — shown so the month keeps its shape,
   but plainly not on offer. No border. */
.dialog.booking .calendar .days button:disabled {
  border-color: transparent;
  opacity: 0.3;
  cursor: default;
}

.dialog.booking .calendar .days button[aria-pressed="true"] {
  border-color: var(--wp--preset--color--warm-brown);
  background: var(--wp--preset--color--warm-brown);
  color: var(--wp--preset--color--off-white);
}

.dialog.booking .slots {
  display: grid;
  align-content: center;
  gap: var(--s-16);
}

.dialog.booking .slots > p {
  margin: 0;
}

.dialog.booking .slots .message {
  display: grid;
  grid-template-columns: 1.25rem minmax(0, 1fr);
  align-items: start;
  gap: var(--s-12);
  width: 100%;
  background: color-mix(in srgb, var(--wp--preset--color--warm-brown) 5%, transparent);
  text-align: left;
}

.dialog.booking .slots .message > .icon {
  display: grid;
  place-items: center;
  width: 1.25rem;
  height: 1.25rem;
  margin-top: 0.125em;
  color: var(--wp--preset--color--warm-brown);
}

.dialog.booking .slots .message > .icon > svg {
  display: block;
  width: 100%;
  height: 100%;
}

.dialog.booking .slots .message > .spinner {
  width: 1.25rem;
  height: 1.25rem;
  margin-top: 0.125em;
  border: 2px solid color-mix(
    in srgb,
    var(--wp--preset--color--warm-brown) 25%,
    transparent
  );
  border-top-color: var(--wp--preset--color--warm-brown);
  border-radius: 50%;
  animation: booking-slots-spinner 0.75s linear infinite;
}

.dialog.booking .slots.is-loading .message {
  background: color-mix(
    in srgb,
    var(--wp--preset--color--warm-brown) 5%,
    var(--wp--preset--color--off-white, #fff)
  );
  animation: booking-availability-in 0.25s cubic-bezier(0.05, 0.7, 0.1, 1) both;
}

.dialog.booking .slots.is-loading.is-leaving .message {
  animation: booking-availability-out 0.18s cubic-bezier(0.3, 0, 0.8, 0.15) both;
}

@keyframes booking-slots-spinner {
  to {
    transform: rotate(1turn);
  }
}

@keyframes booking-availability-in {
  from {
    opacity: 0;
    transform: translateY(var(--s-12));
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes booking-availability-out {
  from {
    opacity: 1;
    transform: translateY(0);
  }
  to {
    opacity: 0;
    transform: translateY(var(--s-12));
  }
}

@media (prefers-reduced-motion: reduce) {
  .dialog.booking .slots .message > .spinner {
    animation-duration: 1.5s;
  }

  .dialog.booking .slots.is-loading .message {
    animation: none;
  }
}

.dialog.booking .slots .message > p {
  margin: 0;
}

.dialog.booking .slots > .date {
  width: 100%;
  margin: 0;
  font-weight: 500;
  text-align: center;
}

/* On stacked layouts the slots pane sits below the calendar and can fall
   outside a short viewport. Loading belongs to the calendar it populates,
   so lift that one state over the schedule without moving either component. */
@media (max-width: 63.999rem) {
  .dialog.booking .schedule {
    position: relative;
  }

  .dialog.booking .slots.is-loading {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: grid;
    place-items: center;
    padding: var(--s-16);
    pointer-events: auto;
  }

  .dialog.booking .slots.is-loading .message {
    width: min(100%, 20rem);
    box-shadow: 0 var(--s-4) var(--s-20)
      color-mix(in srgb, var(--wp--preset--color--warm-brown) 14%, transparent);
  }
}

/* Times are the same option card, sized for a clock label — three across
   on a phone, four once there is room. */
.dialog.booking .slots .options {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-8);
}

@media (min-width: 40rem) {
  .dialog.booking .slots .options {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* Beside a fixed 26rem calendar the slots column is narrower than the
   stacked phone block — keep three-across so times stay tappable. */
@media (min-width: 64rem) {
  .dialog.booking .slots .options {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.dialog.booking .slots .option {
  justify-items: center;
  padding: var(--s-12) var(--s-16);
}

/* After times (or empty-day copy): navigate to the contact detour. */
.dialog.booking .slots .contact-hint {
  margin: 0;
  font-size: var(--font-size-sm);
  color: color-mix(in srgb, var(--wp--preset--color--warm-brown) 80%, transparent);
}

.dialog.booking .slots .contact-hint > .nav {
  display: inline;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 0.15em;
  cursor: pointer;
}

.dialog.booking .slots .contact-hint > .nav:focus-visible {
  outline: 2px solid var(--wp--preset--color--warm-brown);
  outline-offset: 2px;
}

.dialog.booking .choice-error {
  margin: 0;
  color: var(--wp--preset--color--error, #b42318);
  font-size: var(--font-size-sm);
}

.dialog.booking .choice-error[hidden] {
  display: none;
}

/* Contact-request detour reuses the step-4 form language without address. */
.dialog.booking .section.step[data-booking-view="kontakt"] > .container {
  --mw: var(--mw-md);
  align-items: center;
  gap: var(--s-16);
  text-align: left;
}

.dialog.booking .section.step[data-booking-view="kontakt"] .question,
.dialog.booking .section.step[data-booking-view="kontakt"] form {
  width: 100%;
  max-width: var(--mw-md);
}

.dialog.booking .section.step[data-booking-view="kontakt"] form {
  gap: var(--s-16);
}

/* === Step 3: floating-label form + overview ==============================
   Field chrome is booking-scoped (not global form.css). Labels rest as
   mid-field placeholders, then float up and scale down on focus or when
   a value is present (:placeholder-shown + placeholder=" "). Mobile and
   tablet stack form | overview inside mw-md; lg expands the outer container
   and places the capped form beside the sticky overview. */

.dialog.booking .section.step[data-booking-step="3"] > .container {
  --mw: var(--mw-md);
  gap: var(--s-16);
  text-align: left;
}

.dialog.booking .compose {
  display: grid;
  grid-template-areas:
    "form"
    "overview";
  gap: var(--s-16);
  width: 100%;
}

.dialog.booking .section.step[data-booking-step="3"] form {
  grid-area: form;
  gap: var(--s-16);
  width: 100%;
  max-width: var(--mw-md);
}

.dialog.booking [data-booking-contact] > .privacy {
  padding-block: 0;
}

.dialog.booking [data-booking-contact] .fields {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0; /* overrides form.css form > div gap */
  width: 100%;
  border: 1px solid var(--border-divider);
  border-radius: var(--border-radius-media);
  background: var(--wp--preset--color--white);
  /* visible so focus rings (outline-offset) are not clipped */
  overflow: visible;
}

.dialog.booking [data-booking-contact] .field {
  position: relative;
  display: block;
  margin: 0;
  border-block-end: 1px solid var(--border-divider);
  /* Radius only while focused — a permanent radius on every cell opens
     gaps in the shared dividers (looked like the textarea lost its top rule). */
  border-radius: 0;
}

/* Comments is the last direct field in .fields. */
.dialog.booking [data-booking-contact] .fields > .field:last-child {
  border-block-end: none;
}

.dialog.booking [data-booking-contact] .field > :is(input, textarea) {
  display: block;
  inline-size: 100%;
  margin: 0;
  padding: 1.55rem var(--s-16) 0.55rem;
  border: none;
  border-radius: 0;
  background: transparent;
  font-family: inherit;
  font-size: inherit;
  font-weight: 500;
  letter-spacing: inherit;
  color: inherit;
  box-shadow: none;
}

.dialog.booking [data-booking-contact] .field > textarea {
  min-height: 6.5rem;
  resize: vertical;
  padding-block-end: var(--s-16);
}

.dialog.booking [data-booking-contact] .field > :is(input, textarea):hover,
.dialog.booking [data-booking-contact] .field > :is(input, textarea):focus-visible {
  border: none;
  outline: none;
  box-shadow: none;
}

/* booking.css loads after form.css and its borderless field reset otherwise
   exposes Chrome's blue autofill paint. Re-cover it with the field surface. */
.dialog.booking
  [data-booking-contact]
  .field
  > input:is(
    :-webkit-autofill,
    :-webkit-autofill:hover,
    :-webkit-autofill:focus,
    :-webkit-autofill:active
  ) {
  -webkit-box-shadow: 0 0 0 1000px var(--wp--preset--color--white) inset;
  -webkit-text-fill-color: var(--wp--preset--color--haga-black);
  caret-color: var(--wp--preset--color--haga-black);
  transition: background-color 99999s ease-in-out 0s;
}

.dialog.booking
  [data-booking-contact]
  .field
  > :is(input, textarea)[aria-invalid="true"] {
  color: var(--form-error-color, var(--color-danger, #b42318));
}

.dialog.booking
  [data-booking-contact]
  .field
  > input[aria-invalid="true"]:-webkit-autofill {
  -webkit-text-fill-color: var(--form-error-color, var(--color-danger, #b42318));
  caret-color: var(--form-error-color, var(--color-danger, #b42318));
}

/* Same language as .btn:focus-visible — outside the cell, not an inset
   ring that doubles up on the shared 1px dividers. Invalid fields flip
   --focus-ring-color the same way form.css does for checkboxes. */
.dialog.booking [data-booking-contact] .field:has([aria-invalid="true"]) {
  --focus-ring-color: var(--form-error-color, var(--color-danger, #b42318));
}

.dialog.booking [data-booking-contact] .field:focus-within {
  z-index: 2;
  border-radius: var(--border-radius);
  outline: 2px solid var(--focus-ring-color, var(--wp--preset--color--warm-brown));
  outline-offset: 2px;
}

/* Floating label only — not the in-field error span. */
.dialog.booking [data-booking-contact] .field > span:not([data-error]) {
  position: absolute;
  inset-inline-start: var(--s-16);
  inset-block-start: 50%;
  max-width: calc(100% - 2 * var(--s-16));
  margin: 0;
  color: color-mix(in srgb, var(--wp--preset--color--warm-brown) 55%, transparent);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.2;
  pointer-events: none;
  transform: translateY(-50%);
  transform-origin: left center;
  transition:
    transform 0.18s ease,
    font-size 0.18s ease,
    color 0.18s ease,
    inset-block-start 0.18s ease,
    opacity 0.18s ease;
}

.dialog.booking [data-booking-contact] .field:has(textarea) > span:not([data-error]) {
  inset-block-start: 1.35rem;
  transform: none;
}

.dialog.booking [data-booking-contact] .field:focus-within > span:not([data-error]),
.dialog.booking [data-booking-contact] .field:has(:is(input, textarea):not(:placeholder-shown)) > span:not([data-error]) {
  inset-block-start: 0.55rem;
  font-size: var(--font-size-xs);
  transform: none;
  color: color-mix(in srgb, var(--wp--preset--color--warm-brown) 65%, transparent);
}

/* At rest, invalid copy replaces the label without changing field height.
   Focus restores the permanent label and moves the error opposite it. */
.dialog.booking
  [data-booking-contact]
  .field:has([aria-invalid="true"]):not(:focus-within)
  > span:not([data-error]) {
  opacity: 0;
}

.dialog.booking [data-booking-contact] .field > [data-error] {
  position: absolute;
  top: auto;
  right: auto;
  left: auto;
  inset-inline-start: var(--s-16);
  inset-inline-end: auto;
  inset-block-start: 50%;
  max-width: calc(100% - 2 * var(--s-16));
  margin: 0;
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.2;
  color: var(--form-error-color, var(--color-danger, #b42318));
  pointer-events: none;
  transform: translateY(-50%);
  animation: booking-field-error-in 0.18s ease both;
}

.dialog.booking
  [data-booking-contact]
  .field:has(:is(input, textarea):not(:placeholder-shown))
  > [data-error],
.dialog.booking [data-booking-contact] .field:focus-within > [data-error] {
  inset-block-start: 0.55rem;
  font-size: var(--font-size-xs);
  transform: none;
}

.dialog.booking [data-booking-contact] .field:focus-within > [data-error] {
  inset-inline-start: auto;
  inset-inline-end: var(--s-16);
  text-align: right;
}

@keyframes booking-field-error-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .dialog.booking [data-booking-contact] .field > [data-error] {
    animation: none;
  }
}

/* In the stacked layout, the overview reads as a large summary field after
   the form. Its heading remains visible when it becomes a sidebar. */
.dialog.booking .overview {
  grid-area: overview;
  display: grid;
  justify-self: center;
  gap: var(--s-16);
  width: 100%;
  margin: var(--s-40) auto 0;
  background: color-mix(in srgb, var(--wp--preset--color--warm-brown) 4%, transparent);
}

.dialog.booking .overview h5 {
  margin: 0;
  font-size: var(--font-size-sm);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: color-mix(in srgb, var(--wp--preset--color--warm-brown) 70%, transparent);
}

/* Shared icon + text rows used by the overview and confirmation. */
.dialog.booking .details {
  display: grid;
  gap: var(--s-16);
  list-style: none;
}

.dialog.booking .details li {
  display: grid;
  grid-template-columns: 1.5rem 1fr;
  gap: var(--s-12);
  align-items: start;
}

.dialog.booking .details .icon {
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  color: var(--wp--preset--color--warm-brown);
}

.dialog.booking .details .icon > svg {
  width: 1.5rem;
  height: 1.5rem;
}

.dialog.booking .details .text {
  display: grid;
  align-content: start;
  gap: var(--s-4);
  min-width: 0;
}

.dialog.booking .details .meta {
  display: grid;
  gap: 0;
  font-size: var(--font-size-sm);
  font-weight: 400;
  color: color-mix(in srgb, var(--wp--preset--color--warm-brown) 80%, transparent);
}

.dialog.booking .details .meta .line {
  display: block;
}

.dialog.booking .overview .details {
  margin: 0;
  padding: 0;
}

.dialog.booking .overview .details .label {
  display: block;
}

@media (min-width: 40rem) {
  .dialog.booking .overview {
    width: fit-content;
  }

  .dialog.booking [data-booking-contact] .fields {
    grid-template-columns: 1fr 1fr;
  }

  .dialog.booking [data-booking-contact] .fields > .field {
    border-inline-end: 1px solid var(--border-divider);
  }

  .dialog.booking [data-booking-contact] .fields > .field:nth-child(2n) {
    border-inline-end: none;
  }

  .dialog.booking [data-booking-contact] .fields > .field.full {
    grid-column: 1 / -1;
    border-inline-end: none;
  }
}

@media (min-width: 64rem) {
  .dialog.booking .section.step[data-booking-step="3"] > .container {
    --mw: var(--mw-lg);
  }

  .dialog.booking .compose {
    grid-template-areas: "form overview";
    grid-template-columns: minmax(0, var(--mw-md)) minmax(14rem, 18rem);
    align-items: start;
    justify-content: center;
    gap: var(--s-40);
  }

  .dialog.booking .overview {
    position: sticky;
    top: var(--s-16);
    margin-top: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .dialog.booking [data-booking-contact] .field > span {
    transition: none;
  }
}

/* After a successful book, step 3 is read-only if history still lands here. */
.dialog.booking [data-booking-already] {
  margin: 0;
  padding: var(--s-12) var(--s-16);
  border: 1px solid var(--border-divider);
  border-radius: var(--border-radius-media);
  background: color-mix(in srgb, var(--wp--preset--color--warm-brown) 6%, transparent);
  color: var(--wp--preset--color--warm-brown);
}

.dialog.booking [data-booking-already][hidden] {
  display: none;
}

.dialog.booking .submit-error {
  grid-column: 1 / -1;
  width: 100%;
  margin: 0;
  padding: var(--s-12) var(--s-16);
  border: 1px solid currentColor;
  border-radius: var(--border-radius-media);
  background: color-mix(in srgb, var(--form-error-color, #b42318) 6%, transparent);
  color: var(--form-error-color, var(--color-danger, #b42318));
}

.dialog.booking .submit-error[hidden] {
  display: none;
}

.dialog.booking [data-booking-contact].is-complete .fields,
.dialog.booking [data-booking-contact].is-complete .privacy {
  opacity: 0.55;
}

.dialog.booking [data-booking-contact].is-complete [type="submit"] {
  display: none;
}

/* === Confirmation ======================================================== */

.dialog.booking .confirmation {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: 0 1 auto;
  gap: var(--s-12);
  width: 100%;
  min-height: 0;
  max-width: var(--mw-sm);
  margin-inline: auto;
  text-align: center;
}

.dialog.booking .section.step[data-booking-view="confirm"] > .container {
  justify-content: center;
}

.dialog.booking .confirmation h4 {
  margin: 0;
}

.dialog.booking .confirmation .success-icon {
  display: grid;
  place-items: center;
  width: 3.5rem;
  height: 3.5rem;
  margin-bottom: var(--s-4);
  color: var(--wp--preset--color--warm-brown);
}

.dialog.booking .confirmation .success-icon svg {
  display: block;
  width: 100%;
  height: 100%;
}

.dialog.booking .confirmation .success-icon :is(circle, polyline) {
  stroke-width: 10;
}

.dialog.booking .confirmation .greeting {
  font-weight: 500;
  margin-top: calc(var(--s-8) * -1);
}

.dialog.booking .confirmation > p {
  margin: 0;
}

.dialog.booking .confirmation .details {
  width: 100%;
  margin-block: var(--s-20);
  margin-inline: 0;
  text-align: left;
}

@media (min-width: 40rem) {
  .dialog.booking .confirmation {
    margin-bottom: 12vh;
  }

  .dialog.booking .confirmation .greeting {
    max-width: none;
    white-space: nowrap;
  }
}

/* Smaller återförsäljare-style maps CTA under the address. */
.dialog.booking .confirmation .details .link-wrap {
  margin-top: var(--s-8);
}

.dialog.booking .confirmation .details .link-wrap a {
  --link-current-size: var(--font-size-sm);
  --link-current-weight: 400;
  gap: 0.25em;
}

.dialog.booking .confirmation .details .link-wrap a > span + svg {
  inline-size: 0.75rem;
  block-size: 0.75rem;
}

/* === Step transition =====================================================
   JS sets --step-shift per direction (forward: enter from the right,
   back: enter from the left) and adds .is-entering for one animation. */

.dialog.booking .step.is-entering {
  animation: booking-step-in 0.25s ease both;
}

@keyframes booking-step-in {
  from {
    opacity: 0;
    transform: translateX(var(--step-shift, 1rem));
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .dialog.booking .step.is-entering {
    animation: none;
  }
}

/* === Dock (floating snackbar + action bar) ===============================
   Outside .body. Snackbar floats above the bar (positioned, out of flow)
   so it never shifts actions or step content. Soft danger tint — readable,
   not alarmist. Enter: M3-like swift rise from below
   (emphasized-decelerate, ~250ms). */

.dialog.booking > .dock {
  position: relative;
  z-index: 1;
  display: grid;
  gap: 0;
  background: var(--wp--preset--color--off-white, #fff);
}

.dialog.booking > .dock[hidden] {
  display: none;
}

.dialog.booking .snackbar {
  --snackbar-bg: color-mix(
    in srgb,
    var(--color-danger) 9%,
    var(--wp--preset--color--off-white)
  );
  --snackbar-border: color-mix(in srgb, var(--color-danger) 28%, transparent);
  --snackbar-fg: color-mix(
    in srgb,
    var(--color-danger) 78%,
    var(--wp--preset--color--warm-brown)
  );
  /* M3 emphasized decelerate (enter) / accelerate (exit). */
  --snackbar-ease-in: cubic-bezier(0.05, 0.7, 0.1, 1);
  --snackbar-ease-out: cubic-bezier(0.3, 0, 0.8, 0.15);

  position: absolute;
  left: 50%;
  bottom: calc(100% + var(--s-12));
  z-index: 2;
  width: max-content;
  max-width: min(24rem, calc(100% - 2em));
  margin: 0;
  padding: 0;
  pointer-events: none;
  transform: translateX(-50%);
}

.dialog.booking .snackbar[hidden] {
  display: none;
}

.dialog.booking .snackbar .panel {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  column-gap: var(--s-24);
  width: fit-content;
  max-width: min(24rem, 100%);
  padding: var(--s-8) var(--s-12);
  border: 1px solid var(--snackbar-border);
  border-radius: var(--border-radius-media);
  background: var(--snackbar-bg);
  color: var(--snackbar-fg);
  pointer-events: auto;
  box-shadow: 0 var(--s-4) var(--s-16)
    color-mix(in srgb, var(--wp--preset--color--warm-brown) 12%, transparent);
}

.dialog.booking .snackbar.is-entering {
  animation: booking-snackbar-in 0.25s var(--snackbar-ease-in) both;
}

.dialog.booking .snackbar.is-leaving {
  pointer-events: none;
  animation: booking-snackbar-out 0.18s var(--snackbar-ease-out) both;
}

@keyframes booking-snackbar-in {
  from {
    opacity: 0;
    transform: translate(-50%, 100%);
  }
  to {
    opacity: 1;
    transform: translate(-50%, 0);
  }
}

@keyframes booking-snackbar-out {
  from {
    opacity: 1;
    transform: translate(-50%, 0);
  }
  to {
    opacity: 0;
    transform: translate(-50%, 100%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .dialog.booking .snackbar.is-entering,
  .dialog.booking .snackbar.is-leaving {
    animation: none;
  }
}

.dialog.booking .snackbar .panel > p {
  margin: 0;
  font-size: var(--font-size-xs);
  font-weight: 400;
  line-height: 1.35;
}

/* Smaller than .btn.icon.sm — compact dismiss on a dense toast. */
.dialog.booking .snackbar .dismiss {
  width: 1.5rem;
  height: 1.5rem;
  min-width: 1.5rem;
  min-height: 1.5rem;
  padding: 0;
  color: inherit;
}

.dialog.booking .snackbar .dismiss > svg {
  width: 0.875rem;
  height: 0.875rem;
}

/* Action bar — no standing top border; upward shadow when content can
   still scroll under the dock (.is-scroll-more). */
.dialog.booking .actions {
  padding-block: var(--s-16);
  padding-bottom: calc(var(--s-16) + env(safe-area-inset-bottom, 0px));
  background: var(--wp--preset--color--off-white, #fff);
  transition: box-shadow 0.25s ease;
}

.dialog.booking.is-scroll-more > .dock {
  box-shadow: 0 calc(var(--s-4) * -1) var(--s-20)
    color-mix(in srgb, var(--wp--preset--color--warm-brown) 14%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .dialog.booking .actions {
    transition: none;
  }
}

.dialog.booking .actions[hidden] {
  display: none;
}

.dialog.booking .actions > .container {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  justify-content: stretch;
  gap: var(--s-12);
  width: 100%;
}

.dialog.booking .actions .btn {
  min-height: 3rem; /* 48px */
  flex: 1 1 auto;
}

.dialog.booking .actions [data-booking-primary] {
  position: relative;
}

.dialog.booking .actions [data-booking-primary][aria-busy="true"] {
  color: transparent;
  opacity: 1;
  pointer-events: none;
}

.dialog.booking.is-submitting > .body {
  opacity: 0.55;
  pointer-events: none;
}

.dialog.booking .actions [data-booking-primary][aria-busy="true"]::after {
  content: "";
  position: absolute;
  inset: 50% auto auto 50%;
  width: 1.125rem;
  height: 1.125rem;
  border: 2px solid color-mix(
    in srgb,
    var(--wp--preset--color--off-white, #fff) 40%,
    transparent
  );
  border-top-color: var(--wp--preset--color--off-white, #fff);
  border-radius: 50%;
  transform: translate(-50%, -50%);
  animation: booking-submit-spinner 0.75s linear infinite;
}

@keyframes booking-submit-spinner {
  to {
    transform: translate(-50%, -50%) rotate(1turn);
  }
}

.dialog.booking .actions .btn[hidden] {
  display: none;
}

/* Virtual keyboard: collapse actions (keep dock wrapper for snackbar). */
.dialog.booking.is-keyboard .actions {
  display: none;
}

@media (min-width: 40rem) {
  .dialog.booking .actions > .container {
    justify-content: center;
  }

  .dialog.booking .actions .btn {
    flex: 0 1 auto;
    min-width: 10rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .dialog.booking .actions [data-booking-primary][aria-busy="true"]::after {
    animation-duration: 1.5s;
  }
}
