/* Keepp page runtime — bento grid + block styles. Theme vars (--kp-bg, --kp-fg,
   --kp-brand) are injected per-page by renderDocument's <style id="kp-theme">. */
:root {
  --kp-gap: 14px; --kp-pad: 20px; --kp-page-max: 480px;
  /* Rating sentiment palette — fixed (not theme-derived) so it reads the same
     everywhere: negative (red) → neutral (amber) → positive (green), plus gold
     stars. */
  --kp-rate-pos: #1fa565; --kp-rate-mid: #e0a12e; --kp-rate-neg: #de5a52; --kp-star: #f5b301;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body { margin: 0; min-height: 100dvh; background: #f3f4f6; }

/* Free-plan footer badge — injected by the Worker (frontend/web/worker/lib.js),
   never by renderDocument.js: it must reflect the business's CURRENT plan on
   every request, not whatever was true at publish time. Deliberately outside
   .kp-frame and its theme scope (no --kp-* vars used) — this is Keepp's own
   brand mark, not page content, so it stays visually neutral across every
   theme rather than blending into it. Sits in normal document flow after the
   frame (.kp-frame already forces min-height:100dvh, so this only becomes
   visible once a visitor scrolls to the true end of the page) — never fixed,
   so it can't collide with .kp-toast's fixed bottom position. */
.kp-powered-badge {
  display: block; max-width: var(--kp-page-max); margin: 20px auto 28px; padding: 0 20px;
  text-align: center; font-size: 0.82rem; line-height: 1.6; color: #6b7280; text-decoration: none;
}
.kp-powered-badge:hover, .kp-powered-badge:focus-visible { color: #374151; }
.kp-powered-badge strong { font-weight: 800; color: #1f2937; }
.kp-powered-dot { color: #d7494c; }

/* Form controls do not inherit font-family — the UA stylesheet pins them to a
   system face — so the owner's chosen pairing stopped dead at every <button>,
   <input>, <select> and <textarea> that had not been individually opted in.
   That opt-in used to be a per-component `font: inherit`, i.e. an allowlist
   somebody had to remember to extend, and four buttons were missed:
   .kp-dialog-x, .kp-dialog-share, .kp-form-done and .kp-form-refill all
   rendered in Arial next to text in the chosen font.
   Doing it once, here, covers every control a future block introduces.
   Family only, deliberately: `font: inherit` would also reset size and weight,
   which the per-component rules set on purpose and which would move layout. */
.kp-frame button, .kp-frame input, .kp-frame select, .kp-frame textarea,
.kp-frame optgroup { font-family: inherit; }
.kp-frame {
  --kp-frame-pad-inline: var(--kp-pad);
  --kp-bg: #faf7f2; --kp-fg: #2d2018; --kp-brand: #d7494c; --kp-on-brand: #fff;
  --kp-dialog-bg: #faf7f2; --kp-dialog-fg: #2d2018;
  --kp-dialog-surface: color-mix(in srgb, var(--kp-dialog-bg) 62%, #ffffff);
  --kp-radius: 22px; --kp-radius-sm: 14px; --kp-radius-btn: 8px;
  --kp-surface-mix: #ffffff;
  --kp-surface: color-mix(in srgb, var(--kp-bg) 62%, var(--kp-surface-mix));
  --kp-shadow: 0 2px 10px rgb(0 0 0 / 0.06); --kp-bg-image: none;
  --kp-bg-position: center center; --kp-bg-veil-opacity: 0%;
  width: 100%; min-height: 100dvh; margin: 0 auto; isolation: isolate;
  contain: paint; overflow: clip;
  background-color: var(--kp-bg);
  background-image:
    linear-gradient(rgb(0 0 0 / var(--kp-bg-veil-opacity)), rgb(0 0 0 / var(--kp-bg-veil-opacity))),
    var(--kp-bg-image);
  background-position: center, var(--kp-bg-position);
  background-size: auto, cover; background-repeat: no-repeat, no-repeat;
  background-attachment: fixed, fixed;
  color: var(--kp-fg);
  font-family: var(--kp-font-body, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
}

/* — grid frame — */
.kp-page { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--kp-gap);
  align-items: stretch; width: 100%; max-width: 100%; margin: 0 auto;
  padding: var(--kp-pad) var(--kp-frame-pad-inline); }
.kp-cell--full { grid-column: 1 / -1; }
.kp-cell--half { grid-column: span 1; min-width: 0; }
.kp-cell { scroll-margin-top: 72px; }
.kp-cell--half.kp-lone--center,
.kp-cell--half.kp-lone--right { grid-column: 1 / -1; width: calc((100% - var(--kp-gap)) / 2); }
.kp-cell--half.kp-lone--center { justify-self: center; }
.kp-cell--half.kp-lone--right { justify-self: end; }
.kp-align--center { text-align: center; }
.kp-align--right { text-align: right; }
.kp-cell > :not(dialog) { height: 100%; }

/* A background override recolors the block's OWN surface, never the grid cell.
   It sets --kp-surface (+ --kp-fg) on the cell; blocks that already paint a
   surface — link, preview-link, card, nav tab — read var(--kp-surface) and so
   recolor in place, hugging their own shape, while the empty area of the cell
   stays page background. The CTA button defaults to --kp-brand, so it opts in
   to the override here. Bare-text blocks (header/subheader/text) have no surface
   of their own, so an override gives them a self-hugging tile that wraps the
   text box, not the cell. */
/* Deliberately narrow: only the form trigger/submit opt into a block
   background override. A Buy/Book/etc. brand CTA (.kp-pill--brand generally)
   must keep its brand fill even when the block itself has a background
   override, or the CTA drains to the same color as its own card and stops
   reading as an action. Do not widen this back to .kp-pill--brand. */
.kp-cell--filled .kp-profile-cta-btn,
.kp-cell--filled .kp-form-trigger { background: var(--kp-surface); }
/* Brand-colored buttons hardcode white text, which beats the inherited
   --kp-fg. When the block carries a text override (or an auto-contrast color
   derived from a background override), honor it so the label stays legible on
   the chosen surface instead of staying white. */
.kp-cell--fg .kp-profile-cta-btn,
.kp-cell--fg .kp-card-cta,
.kp-cell--fg .kp-form-trigger,
.kp-cell--fg .kp-form-submit { color: var(--kp-fg); }
.kp-cell--filled .kp-header,
.kp-cell--filled .kp-subheader,
.kp-cell--filled .kp-text {
  background: var(--kp-surface); padding: 14px 16px; border-radius: var(--kp-radius);
  box-shadow: var(--kp-shadow); margin-top: 0; }
/* Consumes the cell-scoped --kp-fg. Redefining the custom property alone paints
   nothing — descendants inherit body's already-resolved color. */
.kp-cell--fg { color: var(--kp-fg); }

/* — profile — */
.kp-profile { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 8px 0; }
.kp-profile-image { width: 104px; height: 104px; object-fit: cover; box-shadow: var(--kp-shadow); }
.kp-profile--circle .kp-profile-image { border-radius: 50%; }
.kp-profile-name { font-size: 1.7rem; font-family: var(--kp-font-display, inherit); font-weight: var(--kp-font-display-weight, 800); letter-spacing: -0.02em; margin: 12px 0 0; }
.kp-profile-slug { margin: 2px 0 0; opacity: 0.65; font-weight: 600; }
.kp-profile-about { margin: 10px 0 0; line-height: 1.5; max-width: 34ch; }

/* — profile: cover variant — full-width banner image, name/handle overlaid bottom-left — */
.kp-profile--cover { align-items: stretch; width: 100%; padding: 0; }
.kp-profile-cover { position: relative; width: 100%; aspect-ratio: 3 / 2; border-radius: var(--kp-radius); overflow: hidden; box-shadow: var(--kp-shadow); }
.kp-profile-cover-image { width: 100%; height: 100%; object-fit: cover; display: block; }
.kp-profile-cover-overlay { position: absolute; left: 12px; bottom: 12px; max-width: calc(100% - 24px);
  background: rgb(0 0 0 / 0.45); backdrop-filter: blur(6px); border-radius: var(--kp-radius-sm);
  padding: 8px 14px; text-align: left; }
.kp-profile--cover .kp-profile-name { color: #fff; margin: 0; font-size: 1.35rem; }
.kp-profile--cover .kp-profile-slug { color: #fff; opacity: 0.8; margin: 2px 0 0; }
.kp-profile--cover .kp-profile-about { text-align: center; margin-top: 12px; }

/* — shape primitives —
   One pill (+ brand variant), consumed by every block that wants a pill
   shape. These replaced three drifted rulesets (.kp-link, .kp-form-trigger,
   .kp-btn) that disagreed on padding, radius, weight, shadow and press
   feedback, so a link and a form trigger stacked on a page did not match. Any
   new block picks a primitive; it never writes its own. The other shape
   primitive, .kp-card, already existed below in "— cards + dialog —" — reused
   as-is rather than duplicated here. */
/* font: inherit is load-bearing, not decorative: a <button> does not inherit
   font by default (UA stylesheet forces ~13px system Arial), so without this
   every brand-filled button (form trigger/submit, booking, product, map) would
   render in system-default Arial while the rest of the page uses the chosen
   font. .kp-booking-back / .kp-booking-slot / .kp-rate-btn already do this for
   the same reason — do not remove it as "redundant". */
.kp-pill { display: flex; align-items: center; gap: 12px; width: 100%;
  background: var(--kp-surface); border-radius: var(--kp-radius);
  padding: 16px 18px; text-decoration: none; color: inherit;
  font: inherit; font-weight: 700; border: 0; cursor: pointer;
  /* text-align: inherit, not unset: a <button> is centred by the UA stylesheet,
     so without this the same .kp-pill reads centre as a <button> (card /
     product / booking / form) and start as an <a> or <div> (link / promo-code /
     showcase) — identical classes, different alignment. `inherit` makes all
     three element forms take the cell's alignment, which is also what lets the
     Align control act on a pill. */
  text-align: inherit;
  box-shadow: var(--kp-shadow); transition: transform 120ms ease; }
.kp-pill:active { transform: scale(0.98); }
/* A pill rendered as a bare <div> is inert — the click behaviour lives on a
   nested element (promo-code's copy button) or nowhere (a showcase with no
   link). Without this it would still show a pointer cursor and press-animate,
   advertising an interaction it does not have. */
/* .kp-cell > :not(dialog) { height: 100% } exists so a card fills its row when
   paired with a taller sibling. A pill has no interior to fill — stretching it
   just marooned its one line of text in the middle of a tall empty box when a
   half pill sat beside a half card. Keep its natural height and top-align it.
   Higher specificity (0,2,0) than the cell rule (0,1,1), so this wins. */
.kp-cell > .kp-pill { height: auto; align-self: start; }
.kp-pill:not(a):not(button) { cursor: default; }
.kp-pill:not(a):not(button):active { transform: none; }
/* Brand-filled variant: the treatment form triggers and .kp-btn already had.
   Centered because it reads as an action, not a destination. A brand button is
   a tighter, squarer pill than the surface-filled default — that's a real
   variant, not a leak, so it deliberately overrides .kp-pill's radius and
   padding. Do not "simplify" these two overrides away: dropping them silently
   reintroduces the 22px corner these were added to prevent. */
.kp-pill--brand { background: var(--kp-brand); color: var(--kp-on-brand);
  font-weight: 800; box-shadow: none; justify-content: center; text-align: center;
  border-radius: var(--kp-radius-btn); padding: 14px 16px; }

/* A pill whose trailing meta (price, count) sits hard right of the title. */
.kp-pill-title { font-family: var(--kp-font-display, inherit); font-weight: var(--kp-font-display-weight, inherit);
  flex: 1 1 auto; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.kp-pill-meta { flex: 0 0 auto; font-weight: 800; opacity: 0.75; }

/* — links — */
/* Grows to fill the pill so the cell's text-align actually has room to act —
   without this the title is content-width and `align: center` is a silent
   no-op, while .kp-pill-title (card/product/booking pills) already grows and
   does centre. Two pills answering the same control differently is exactly the
   drift .kp-pill exists to remove. min-width:0 keeps it inside a half-width cell (it wraps rather
   than truncating — unlike .kp-pill-title, a link title is the whole point of
   the block, so losing its tail would cost more than an extra line). */
.kp-link-title { font-family: var(--kp-font-display, inherit); font-weight: var(--kp-font-display-weight, inherit); flex: 1 1 auto; min-width: 0; }
.kp-link-icon { width: 28px; height: 28px; border-radius: var(--kp-radius-sm); object-fit: cover; }
svg.kp-link-icon { width: 28px; height: 28px; flex: 0 0 auto; }
/* Match the OG link-preview standard (1200×630) so link cards show their
   og:image uncropped — including our own brand card. */
.kp-preview-media { aspect-ratio: 1200 / 630; overflow: hidden; }
.kp-preview-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.kp-preview-title { padding: 14px 16px; font-weight: 800; }

/* — text family — */
.kp-header { font-size: 1.25rem; font-family: var(--kp-font-display, inherit); font-weight: var(--kp-font-display-weight, 800); letter-spacing: -0.01em; margin: 14px 0 0; }
.kp-subheader { font-size: 1.02rem; font-family: var(--kp-font-display, inherit); font-weight: var(--kp-font-display-weight, 700); opacity: 0.8; margin: 4px 0 0; }
.kp-header--eyebrow, .kp-subheader--eyebrow {
  font-size: 0.78rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.12em; opacity: 0.75;
}
.kp-header--divider, .kp-subheader--divider {
  display: flex; align-items: center; gap: 12px; text-align: center;
}
.kp-header--divider::before, .kp-header--divider::after,
.kp-subheader--divider::before, .kp-subheader--divider::after {
  content: ""; flex: 1; height: 1px; background: currentColor; opacity: 0.25;
}
.kp-text { line-height: 1.55; margin: 0; }
.kp-spacer { height: 24px; }

/* — ticker — full-bleed promotional text is intentionally not a pill/card.
   It bleeds to the frame edge, never the browser viewport. The duplicated text moves exactly one
   copy-width, producing a seamless loop; character count approximates width
   well enough to keep short and long messages at a comparable reading pace. */
.kp-ticker { width: calc(100% + (var(--kp-frame-pad-inline) * 2));
  margin-inline: calc(var(--kp-frame-pad-inline) * -1); overflow: hidden;
  background: var(--kp-surface); color: var(--kp-fg); }
.kp-ticker-track { display: flex; width: max-content; animation: kp-ticker-scroll linear infinite;
  animation-duration: calc(var(--kp-ticker-chars) * var(--kp-ticker-sec-per-char)); }
.kp-ticker-message { flex: 0 0 auto; padding: 12px 28px; white-space: nowrap; font-weight: 800; }
.kp-ticker--rtl { --kp-ticker-sec-per-char: 0.16s; }
.kp-ticker--ltr { --kp-ticker-sec-per-char: 0.16s; }
.kp-ticker--ltr .kp-ticker-track { animation-direction: reverse; }
.kp-ticker--slow { --kp-ticker-sec-per-char: 0.24s; }
.kp-ticker--fast { --kp-ticker-sec-per-char: 0.10s; }
.kp-ticker:not(.kp-ticker--no-pause):hover .kp-ticker-track { animation-play-state: paused; }
@keyframes kp-ticker-scroll { to { transform: translateX(-50%); } }

/* — menu / services — the trigger consumes the shared pill/card primitives;
   only the card's compact image/fallback and the dialog's price-list layout
   are menu-specific. */
.kp-menu-trigger { justify-content: center; }
.kp-menu-card { width: 100%; border: 0; padding: 0; font: inherit; text-align: left; cursor: pointer; }
.kp-menu-image { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block; }
.kp-menu-card-body { display: flex; flex-direction: column; gap: 4px; padding: 14px 16px; }
.kp-menu-card-title { font-family: var(--kp-font-display, inherit); font-weight: var(--kp-font-display-weight, 800); }
.kp-menu-card-meta { font-size: 0.82rem; font-weight: 700; opacity: 0.62; }
.kp-menu-title, .kp-menu-subtitle { margin: 0; }
.kp-menu-subtitle { font-family: var(--kp-font-display, inherit); opacity: 0.72; line-height: 1.45; }
.kp-menu-filters { display: flex; gap: 8px; overflow-x: auto; padding-block: 2px; scrollbar-width: none; }
.kp-menu-filters::-webkit-scrollbar { display: none; }
.kp-menu-chip { flex: 0 0 auto; border: 1px solid color-mix(in srgb, var(--kp-fg) 18%, transparent);
  border-radius: 999px; background: var(--kp-surface); color: inherit; font: inherit; font-size: 0.82rem;
  font-weight: 700; padding: 7px 11px; cursor: pointer; }
.kp-menu-chip--active { background: var(--kp-brand); border-color: var(--kp-brand); color: var(--kp-on-brand); }
.kp-menu-items { display: flex; flex-direction: column; gap: 0; margin: 0; padding: 0; list-style: none; }
.kp-menu-item { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
  padding: 14px 0; border-bottom: 1px solid color-mix(in srgb, var(--kp-fg) 12%, transparent); }
.kp-menu-item[hidden] { display: none; }
.kp-menu-item-main { min-width: 0; }
.kp-menu-item-name { font-family: var(--kp-font-display, inherit); display: block; font-weight: var(--kp-font-display-weight, 800); }
.kp-menu-item-description { margin: 3px 0 0; line-height: 1.45; opacity: 0.72; }
.kp-menu-item-price { flex: 0 0 auto; font-weight: 800; color: var(--kp-brand); }
.kp-menu-item-tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.kp-menu-item-tag { border-radius: 999px; background: color-mix(in srgb, var(--kp-fg) 8%, transparent); font-size: 0.72rem; font-weight: 700; padding: 3px 7px; }

/* — social icons — */
.kp-social-icons { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.kp-social-icon { display: grid; place-items: center; width: 44px; height: 44px;
  border-radius: 50%; text-decoration: none; }
.kp-social-svg { width: 55%; height: 55%; }

/* — profile CTA (+ sticky mirror injected by runtime) — */
.kp-profile-cta { display: flex; justify-content: center; }
.kp-profile-cta-btn { display: inline-block; background: var(--kp-brand); color: var(--kp-on-brand);
  font-weight: 800; text-decoration: none; padding: 14px 34px;
  border-radius: var(--kp-radius-btn); box-shadow: var(--kp-shadow); }
/* Docked at the bottom (thumb-reachable, never collides with the top nav).
   Padding respects the iOS home-indicator safe area. */
.kp-sticky-cta { position: fixed; bottom: 0; left: 0; right: 0; z-index: 20;
  display: none; justify-content: center;
  padding: 10px 10px calc(10px + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--kp-bg) 82%, transparent);
  backdrop-filter: blur(10px);
  box-shadow: 0 -1px 10px rgb(0 0 0 / 0.08); }
.kp-sticky-cta.kp-sticky-cta--visible { display: flex; }
.kp-sticky-cta .kp-profile-cta-btn { width: min(100%, var(--kp-page-max)); text-align: center; }

/* — nav — */
.kp-nav { position: sticky; top: 0; z-index: 10; display: flex; gap: 8px;
  overflow-x: auto; padding: 10px 2px;
  background: color-mix(in srgb, var(--kp-bg) 85%, transparent);
  backdrop-filter: blur(10px); }
.kp-nav-tab { white-space: nowrap; text-decoration: none; color: inherit; font-family: var(--kp-font-display, inherit); font-weight: var(--kp-font-display-weight, 700);
  padding: 8px 16px; border-radius: var(--kp-radius-btn); background: var(--kp-surface); box-shadow: var(--kp-shadow); }
/* Alignment shifts the tab row (text-align can't move flex items). The tabs
   still scroll horizontally when they overflow, whatever the alignment. */
.kp-align--center .kp-nav { justify-content: center; }
.kp-align--right .kp-nav { justify-content: flex-end; }

/* — cards + dialog — */
.kp-card { display: flex; flex-direction: column; background: var(--kp-surface);
  border-radius: var(--kp-radius); overflow: hidden; box-shadow: var(--kp-shadow); height: 100%;
  color: inherit; text-decoration: none; }
a.kp-card { cursor: pointer; }
.kp-card[data-action="open-dialog"] { cursor: pointer; }
.kp-card-media { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; display: block; }
.kp-card-media--portrait { aspect-ratio: 4 / 5; }
/* On the card face the media/carousel absorbs any extra height from a stretched
   grid row, so the body below stays a fixed footer instead of ballooning. */
.kp-card > .kp-card-media, .kp-card > .kp-carousel { flex: 1 1 auto; }
.kp-card-body { display: flex; flex-direction: column; gap: 6px; padding: 12px 14px 14px; flex: 0 0 auto; }
/* Title is always one line — truncate rather than wrap, so cards stay uniform. */
.kp-card-title { font-family: var(--kp-font-display, inherit); font-weight: var(--kp-font-display-weight, 800); margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Price/code sit opposite the CTA on one row; on a half card the CTA drops to
   its own full-width line. */
.kp-card-meta { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.kp-card-price { font-weight: 700; color: var(--kp-brand); margin: 0; }
.kp-card-additional { font-size: 0.88rem; opacity: 0.7; margin: 10px 0 0; white-space: pre-line; }
.kp-card-code { align-self: flex-start; border: 2px dashed var(--kp-brand); color: var(--kp-brand);
  background: none; font-weight: 800; letter-spacing: 0.06em; padding: 8px 14px;
  border-radius: var(--kp-radius-sm); cursor: pointer; }
.kp-card-code.kp-copied { background: var(--kp-brand); color: var(--kp-on-brand); }
.kp-card-cta { text-align: center; background: var(--kp-brand); color: var(--kp-on-brand); white-space: nowrap;
  font-weight: 800; text-decoration: none; border: 0; padding: 10px 16px;
  border-radius: var(--kp-radius-btn); cursor: pointer; }
.kp-cell--half .kp-card-meta { flex-direction: column; align-items: stretch; }
.kp-cell--half .kp-card-cta { width: 100%; }

/* — map (card face is an inert preview; the dialog re-enables pan/zoom) — */
.kp-card--map { cursor: pointer; }
.kp-map-embed { width: 100%; aspect-ratio: 4 / 3; border: 0; display: block; pointer-events: none; }
.kp-map-embed--interactive { pointer-events: auto; aspect-ratio: 16 / 10; }
.kp-map-fallback { width: 100%; aspect-ratio: 4 / 3; display: flex; align-items: center; justify-content: center; padding: 16px; text-align: center; opacity: 0.7; }
.kp-map-title { font-family: var(--kp-font-display, inherit); font-weight: var(--kp-font-display-weight, 700); margin: 12px 0 4px; }
.kp-map-address { margin: 4px 0; }
.kp-map-hours { margin: 8px 0 12px; white-space: normal; }
.kp-map-open { display: inline-block; margin-top: 8px; }

/* — youtube (card face is an inert thumbnail; the dialog holds the player) — */
.kp-card--youtube { cursor: pointer; }
.kp-card > .kp-yt-cover { flex: 1 1 auto; }
.kp-yt-cover { position: relative; display: block; width: 100%; }
.kp-yt-thumb { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block; }
/* The brand path punches a transparent triangle, so the plate behind it is what
   makes the triangle read white instead of showing the thumbnail through. */
.kp-yt-play { position: absolute; inset: 0; margin: auto; width: 56px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  background: #fff; border-radius: 10px; }
.kp-yt-play svg { width: 100%; height: 100%; display: block; }
.kp-yt-embed { width: 100%; aspect-ratio: 16 / 9; border: 0; display: block; }
.kp-yt-title { font-family: var(--kp-font-display, inherit); font-weight: var(--kp-font-display-weight, 700); margin: 12px 0 4px; }
.kp-yt-description { margin: 8px 0 12px; white-space: normal; }
.kp-yt-open { display: inline-block; margin-top: 8px; }

/* — photo carousel (used on the card face and inside the modal) — */
.kp-carousel { position: relative; width: 100%; aspect-ratio: 1 / 1; overflow: hidden; }
.kp-carousel--portrait { aspect-ratio: 4 / 5; }
.kp-carousel-track { display: flex; width: 100%; height: 100%; overflow-x: auto;
  scroll-snap-type: x mandatory; scrollbar-width: none; -ms-overflow-style: none; }
.kp-carousel-track::-webkit-scrollbar { display: none; }
.kp-carousel-slide { flex: 0 0 100%; width: 100%; height: 100%; object-fit: cover;
  display: block; scroll-snap-align: center; }
.kp-carousel-arrow { position: absolute; top: 50%; transform: translateY(-50%);
  display: grid; place-items: center; width: 34px; height: 34px; border: 0; border-radius: 50%;
  background: rgb(0 0 0 / 0.4); color: #fff; font-size: 20px; line-height: 1; cursor: pointer;
  opacity: 0.65; transition: opacity 140ms ease; }
.kp-carousel:hover .kp-carousel-arrow { opacity: 1; }
.kp-carousel-prev { left: 8px; }
.kp-carousel-next { right: 8px; }
.kp-carousel-dots { position: absolute; left: 0; right: 0; bottom: 8px;
  display: flex; justify-content: center; gap: 6px; }
.kp-carousel-dot { width: 7px; height: 7px; padding: 0; border: 0; border-radius: 50%;
  background: rgb(255 255 255 / 0.55); box-shadow: 0 0 2px rgb(0 0 0 / 0.4); cursor: pointer; }
.kp-carousel-dot--active { background: #fff; }
.kp-media-fallback { width: 100%; display: grid; place-items: center;
  background: color-mix(in srgb, var(--kp-brand) 10%, var(--kp-surface));
  color: color-mix(in srgb, var(--kp-brand) 72%, var(--kp-fg)); }
/* Full-screen sheet: forms and card details get the whole viewport with a
   readable centered column, instead of a cramped little modal. */
.kp-dialog { width: 100vw; max-width: 100vw; height: 100dvh; max-height: 100dvh;
  margin: 0; border: 0; border-radius: 0; padding: 0; overflow-y: auto;
  background: var(--kp-dialog-bg); color: var(--kp-dialog-fg);
  --kp-surface: var(--kp-dialog-surface); --kp-fg: var(--kp-dialog-fg); }
.kp-dialog::backdrop { background: rgb(0 0 0 / 0.45); }
/* Top action bar: share + close, grouped right, sticky over scroll. They are
   one cluster of window controls — spreading them to opposite corners read as
   two unrelated buttons. */
.kp-dialog-top { position: sticky; top: 0; z-index: 1; display: flex; align-items: center;
  justify-content: flex-end; gap: 8px; padding: 10px 10px 0; }
.kp-dialog-x, .kp-dialog-share { display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border: 0; border-radius: 50%;
  background: var(--kp-surface); color: var(--kp-fg); cursor: pointer; box-shadow: var(--kp-shadow); }
.kp-dialog-x { font-size: 22px; line-height: 1; }
.kp-dialog-share .kp-icon { width: 20px; height: 20px; }
/* Desktop copy feedback (mobile opens the native share sheet instead). */
.kp-dialog-share.kp-copied { background: var(--kp-brand); color: var(--kp-on-brand); }
/* — page share — the button is a direct <body> child, but it has to line up
   with .kp-page, a max-width column centred in the viewport. It cannot do that
   alone: aligning it to the body's own right edge puts it out in the empty
   desktop margin beside the page card, reading as browser chrome. So the bar
   takes .kp-page's max-width and centring, and pushes the button to its right
   edge. The negative bottom margin pulls the following content back up, so the
   bar occupies no vertical space of its own; pointer-events:none stops that
   overlap from swallowing clicks meant for the first block. */
.kp-page-chrome { position: sticky; top: 12px; z-index: 2;
  display: flex; justify-content: flex-end;
  max-width: var(--kp-page-max); margin: 0 auto -44px;
  padding-inline: var(--kp-frame-pad-inline); pointer-events: none; }
.kp-page-share { pointer-events: auto;
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; flex: 0 0 auto;
  border: 0; border-radius: 50%; background: var(--kp-surface); color: var(--kp-fg);
  cursor: pointer; box-shadow: var(--kp-shadow); font: inherit; }
.kp-page-share .kp-icon { width: 20px; height: 20px; }
/* — toast — one element, reused. Bottom-centre so it never covers the
   top-right share button that triggered it. */
.kp-toast { position: fixed; left: 50%; bottom: calc(24px + env(safe-area-inset-bottom));
  transform: translateX(-50%) translateY(8px); z-index: 10;
  padding: 10px 16px; border-radius: 999px;
  background: var(--kp-fg); color: var(--kp-bg);
  font-size: 0.9rem; font-weight: 600;
  opacity: 0; pointer-events: none; transition: opacity 160ms ease, transform 160ms ease; }
.kp-toast.kp-toast--on { opacity: 1; transform: translateX(-50%) translateY(0); }

@media (min-width: 600px) {
  body { padding: 44px 16px; }
  .kp-frame {
    --kp-frame-pad-inline: 22px;
    max-width: var(--kp-page-max); min-height: 0;
    border: 1px solid color-mix(in srgb, var(--kp-fg) 12%, transparent);
    border-radius: 28px; box-shadow: 0 12px 44px rgb(0 0 0 / 0.14);
  }
  .kp-page { padding-block: 28px; }
  .kp-nav { position: static; border-radius: 14px; }
  .kp-dialog { width: 100%; max-width: var(--kp-page-max); height: calc(100dvh - 88px);
    max-height: calc(100dvh - 88px); margin: auto; border-radius: var(--kp-radius); }
  .kp-sticky-cta { left: 50%; right: auto; width: 100%; max-width: var(--kp-page-max);
    transform: translateX(-50%); }
}

/* — QR sheet — */
.kp-qr-body { display: flex; flex-direction: column; align-items: center; gap: 14px;
  padding: 8px 20px calc(32px + env(safe-area-inset-bottom)); }
.kp-qr-title { margin: 0; font-size: 1.1rem; font-family: var(--kp-font-display, inherit);
  font-weight: var(--kp-font-display-weight, 800); }
/* White plate behind the code: a QR must stay dark-on-light to scan, whatever
   the page theme is. */
.kp-qr-plate { background: #fff; padding: 14px; border-radius: var(--kp-radius-sm); line-height: 0; }
.kp-qr-plate svg, .kp-qr-plate img { display: block; width: 200px; height: 200px; }
.kp-qr-url { margin: 0; font-size: 0.9rem; opacity: 0.75; word-break: break-all; text-align: center; }
.kp-qr-copy { max-width: 240px; }
.kp-dialog-body { max-width: var(--kp-page-max); margin: 0 auto;
  padding: 4px 20px calc(32px + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; gap: 12px; }
/* Card detail dialog title: same face as the card it opened from (the "for" var
   fallback), so a chosen display face doesn't stop at the card and skip the
   modal. Size/margin are left to the UA h2 default, matching the pre-typography
   rendering exactly when no pairing is set. */
.kp-card-dialog-title { font-family: var(--kp-font-display, inherit); font-weight: var(--kp-font-display-weight, 700); }

/* — data collection form (dialog) — */
.kp-form-dialog .kp-form { display: flex; flex-direction: column; gap: 16px; }
.kp-form-title { font-family: var(--kp-font-display, inherit); font-weight: var(--kp-font-display-weight, 700); margin: 0; }
/* Page header — gives context to a form opened straight from a shared link. */
.kp-form-page { display: flex; align-items: center; gap: 10px; padding-bottom: 2px; }
.kp-form-page-avatar { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; box-shadow: var(--kp-shadow); }
.kp-form-page-name { font-family: var(--kp-font-display, inherit); font-weight: var(--kp-font-display-weight, 800); }
.kp-form-row { display: flex; flex-direction: column; gap: 7px; }
.kp-form-label { font-weight: 700; font-size: 0.95rem; }
.kp-form-row input[type="text"], .kp-form-row input[type="email"], .kp-form-row input[type="tel"],
.kp-form-row input:not([type]), .kp-form-row textarea, .kp-form-other, .kp-form-verify input[data-code] {
  font: inherit; padding: 13px 14px; border-radius: var(--kp-radius-sm);
  border: 1.5px solid color-mix(in srgb, var(--kp-fg) 16%, transparent);
  background: var(--kp-surface); color: inherit;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.kp-form-row input:focus, .kp-form-row textarea:focus, .kp-form-other:focus, .kp-form-verify input[data-code]:focus {
  outline: none; border-color: var(--kp-brand);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--kp-brand) 22%, transparent);
}
.kp-form-row textarea { min-height: 84px; resize: vertical; }
.kp-form-opt { display: flex; align-items: center; gap: 9px; font-weight: 500; padding: 4px 0; }

/* Ratings — scale points tinted by sentiment (red → amber → green); stars gold. */
.kp-rating { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.kp-rate-btn { font: inherit; font-weight: 800; min-width: 42px; padding: 9px 12px; cursor: pointer;
  border-radius: var(--kp-radius-sm); border: 1.5px solid currentColor; background: none; line-height: 1;
  transition: background 120ms ease, color 120ms ease; }
.kp-rate-btn--pos { color: var(--kp-rate-pos); }
.kp-rate-btn--mid { color: var(--kp-rate-mid); }
.kp-rate-btn--neg { color: var(--kp-rate-neg); }
.kp-rate-btn[data-selected] { color: #fff; }
.kp-rate-btn--pos[data-selected] { background: var(--kp-rate-pos); }
.kp-rate-btn--mid[data-selected] { background: var(--kp-rate-mid); }
.kp-rate-btn--neg[data-selected] { background: var(--kp-rate-neg); }
.kp-rating--thumb .kp-rate-btn { font-size: 1.35rem; padding: 8px 20px; }
.kp-rating--stars { gap: 4px; }
.kp-star { font: inherit; font-size: 2rem; line-height: 1; padding: 2px; border: 0; background: none;
  cursor: pointer; color: color-mix(in srgb, var(--kp-fg) 22%, transparent); transition: color 90ms ease; }
.kp-star[data-selected] { color: var(--kp-star); }

.kp-form-submit { margin-top: 4px; }

/* Card mode's trigger: a .kp-card face with direct edge-to-edge media and the
   standard padded .kp-card-body, opening the same dialog as the brand pill. */
/* width: 100% is required here: this is the one .kp-card site that's a
   <button>, and a <button> resolves width: auto to shrink-to-fit regardless
   of display, unlike the <article>/<a>/<div> elements every other .kp-card
   site uses (which are block-level and full-width by default). Do not treat
   this as redundant with .kp-card. */
.kp-form-card { width: 100%; padding: 0; text-align: left; font: inherit; border: 0; cursor: pointer; }
.kp-form-card-title { font-family: var(--kp-font-display, inherit); font-weight: var(--kp-font-display-weight, 800); }
.kp-form-card-desc { margin: 0; font-size: 0.9rem; opacity: 0.72; }
.kp-form-card-meta { margin-top: 4px; font-size: 0.8rem; font-weight: 700;
  opacity: 0.6; }

/* A form that verifies its email says so above the submit button. */
.kp-form-verify-note { margin: -4px 0 0; font-size: 0.85rem;
  color: color-mix(in srgb, var(--kp-fg) 62%, transparent); }

/* Verify + outcome panels (check-email / success / recently filled). */
.kp-form-verify, .kp-form-success, .kp-form-recent { display: flex; flex-direction: column;
  align-items: center; text-align: center; gap: 10px; padding: 28px 8px 12px; }
.kp-form-verify[hidden], .kp-form-success[hidden], .kp-form-recent[hidden], .kp-form[hidden] { display: none; }
.kp-form-badge { display: grid; place-items: center; width: 60px; height: 60px; border-radius: 50%;
  background: color-mix(in srgb, var(--kp-rate-pos) 16%, transparent); color: var(--kp-rate-pos);
  font-size: 30px; font-weight: 900; }
.kp-form-badge--info { background: color-mix(in srgb, var(--kp-brand) 15%, transparent); color: var(--kp-brand); }
/* Big centered one-time-code input. */
.kp-form-code { font: inherit; font-size: 1.4rem; font-weight: 800; letter-spacing: 8px; text-align: center;
  width: min(220px, 100%); padding: 12px 14px; border-radius: var(--kp-radius-sm);
  border: 1.5px solid color-mix(in srgb, var(--kp-fg) 16%, transparent); background: var(--kp-surface); color: inherit;
  transition: border-color 120ms ease, box-shadow 120ms ease; }
.kp-form-code:focus { outline: none; border-color: var(--kp-brand);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--kp-brand) 22%, transparent); }
.kp-form-verify-btn { width: auto; min-width: 160px; }
.kp-form-outcome-title { font-family: var(--kp-font-display, inherit); margin: 4px 0 0; font-size: 1.35rem; font-weight: var(--kp-font-display-weight, 800); }
.kp-form-outcome-msg { margin: 0; opacity: 0.72; max-width: 32ch; line-height: 1.5; }
.kp-form-done { margin-top: 6px; min-width: 140px; background: var(--kp-brand); color: var(--kp-on-brand); font-weight: 800;
  border: 0; padding: 12px 20px; border-radius: var(--kp-radius-btn); cursor: pointer; }
.kp-form-refill { margin-top: 6px; background: none; border: 2px solid var(--kp-brand); color: var(--kp-brand);
  font-weight: 800; padding: 10px 18px; border-radius: var(--kp-radius-sm); cursor: pointer; }
.kp-form-error { margin: 0; font-size: 0.85rem; font-weight: 600; color: #d33; }

/* Preview is visually faithful but inert. The page notice owns the global
   contract; dialog explanations keep disabled final actions understandable. */
.kp-preview-notice {
  margin: 0 16px 12px; padding: 10px 12px; border-radius: var(--kp-radius-sm);
  background: color-mix(in srgb, var(--kp-brand) 12%, var(--kp-bg));
  color: var(--kp-fg); font-size: 0.86rem; font-weight: 700; line-height: 1.4;
}
.kp-preview-explanation {
  margin: 0 0 12px; padding: 10px 12px; border-radius: var(--kp-radius-sm);
  background: color-mix(in srgb, var(--kp-brand) 12%, var(--kp-dialog-bg));
  color: var(--kp-dialog-fg); font-size: 0.86rem; font-weight: 700; line-height: 1.4;
}
.kp-preview-action-explanation {
  margin: 6px 0 0; color: color-mix(in srgb, var(--kp-dialog-fg) 68%, transparent);
  font-size: 0.8rem; font-weight: 650; line-height: 1.4;
}
[data-preview-disabled="1"] { opacity: 0.55; cursor: not-allowed; }

/* — booking dialog — */
.kp-booking-pick, .kp-booking-details, .kp-booking-success { display: flex; flex-direction: column; gap: 14px; }
.kp-booking-pick[hidden], .kp-booking-details[hidden], .kp-booking-success[hidden] { display: none; }
.kp-booking-title { font-family: var(--kp-font-display, inherit); margin: 0; font-size: 1.35rem; font-weight: var(--kp-font-display-weight, 800); }
.kp-booking-description { margin: 0; line-height: 1.5; opacity: 0.8; }
.kp-booking-dialog input[type="date"] {
  font: inherit; padding: 13px 14px; border-radius: var(--kp-radius-sm);
  border: 1.5px solid color-mix(in srgb, var(--kp-fg) 16%, transparent);
  background: var(--kp-surface); color: inherit;
  transition: border-color 120ms ease, box-shadow 120ms ease; }
.kp-booking-dialog input[type="date"]:focus { outline: none; border-color: var(--kp-brand);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--kp-brand) 22%, transparent); }
/* Slot grid: a wrap of pill buttons; the picked one fills with brand. */
.kp-booking-slots { display: flex; flex-wrap: wrap; gap: 8px; }
.kp-booking-slot { font: inherit; font-weight: 700; padding: 10px 14px; cursor: pointer;
  border-radius: var(--kp-radius-sm); border: 1.5px solid color-mix(in srgb, var(--kp-fg) 20%, transparent);
  background: var(--kp-surface); color: inherit; line-height: 1;
  transition: background 120ms ease, color 120ms ease, border-color 120ms ease; }
.kp-booking-slot[data-selected] { background: var(--kp-brand); color: var(--kp-on-brand); border-color: var(--kp-brand); }
.kp-booking-note, .kp-booking-nights-status { margin: 0; font-size: 0.88rem;
  color: color-mix(in srgb, var(--kp-fg) 65%, transparent); }
.kp-booking-note[hidden] { display: none; }
.kp-btn:disabled { opacity: 0.45; cursor: default; }
.kp-booking-location { margin: 0; font-size: 0.9rem; opacity: 0.7; }
.kp-booking-policy { display: flex; flex-direction: column; gap: 6px; }
.kp-booking-policy:empty { display: none; }
.kp-booking-policy-price { margin: 0; font-weight: 800; color: var(--kp-brand); }
.kp-booking-policy-text { margin: 0; font-size: 0.88rem; line-height: 1.5;
  color: color-mix(in srgb, var(--kp-fg) 72%, transparent); }
.kp-booking-policy-link { font-size: 0.88rem; font-weight: 700; color: var(--kp-brand); }
.kp-booking-agree-row[hidden] { display: none; }
.kp-booking-actions { display: flex; gap: 10px; align-items: stretch; }
.kp-booking-back { font: inherit; font-weight: 800; padding: 14px 18px; cursor: pointer;
  border-radius: var(--kp-radius-btn); border: 2px solid var(--kp-brand);
  background: none; color: var(--kp-brand); }
.kp-booking-actions .kp-btn { flex: 1; width: auto; }
/* Success panel — centered outcome, mirroring .kp-form-success. */
.kp-booking-success { align-items: center; text-align: center; gap: 10px; padding: 28px 8px 12px; }
.kp-booking-success[hidden] { display: none; }
.kp-booking-summary { font-weight: 800; font-size: 1.1rem; line-height: 1.4; }
.kp-booking-check-email { margin: 0; opacity: 0.72; max-width: 32ch; line-height: 1.5; }
.kp-booking-manage-link { margin-top: 6px; font-weight: 800; color: var(--kp-brand); }

/* — quality floor — */
/* Keyboard focus: a visible ring on every interactive surface. .kp-pill is
   listed on its own (not just via the legacy hook classes below) so any
   future block that emits .kp-pill alone — without also carrying one of the
   legacy classes — still gets a visible ring instead of silently shipping
   with none. */
.kp-pill:focus-visible,
.kp-link:focus-visible, .kp-preview-link:focus-visible, .kp-card:focus-visible,
.kp-nav-tab:focus-visible, .kp-social-icon:focus-visible, .kp-profile-cta-btn:focus-visible,
.kp-card-cta:focus-visible, .kp-card-code:focus-visible, .kp-form-trigger:focus-visible,
.kp-form-submit:focus-visible, .kp-rating button:focus-visible,
.kp-form-done:focus-visible, .kp-form-refill:focus-visible, .kp-dialog-share:focus-visible,
.kp-btn:focus-visible, .kp-booking-slot:focus-visible, .kp-booking-back:focus-visible {
  outline: 3px solid var(--kp-brand); outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .kp-ticker { overflow-x: auto; }
  .kp-ticker-track { animation: none; }
}
