/*
 * Step 4 design translation — Hotel Riverside, Skopje.
 * Reference: One&Only Cape Town (oneandonlyresorts.com/cape-town), proprietary/all-rights-reserved
 * (see NOTES.md at the clone mirror). SOURCE MODE C — reference-derived rebuild: this file is an
 * ORIGINAL implementation of the reference's measured layout DNA (type scale, spacing rhythm,
 * color roles, section anatomy, interaction language) — no compiled CSS, class names, selectors,
 * or source code from the reference were copied. Every token below was measured via computed-style
 * probes against the local reproduction mirror (…/oneandonly-cape-town-clone/site/, served on
 * :8125), not guessed or copied from a design description. See CANONICAL_RELEASE.md for the full
 * measurement log and the visible-difference ledger.
 *
 * This stylesheet REPLACES `.hotel-template-*` styling for the marketing pages it is adopted on
 * (home, stays index/detail, restaurant, gallery, contact, availability/booking/confirmation) —
 * those views use ONLY `.oo-*` classes below, never `.hotel-template-*`. Untouched marketing pages
 * (spa/weddings/offers/discover/menu — no verified field data, see CANONICAL_RELEASE.md's coverage
 * manifest) keep the template's existing `.hotel-template-*` skeleton unchanged.
 *
 * Fonts: Cormorant Garamond (display/serif) + DM Sans (body/UI) — both Google Fonts, SIL Open Font
 * License 1.1, the SAME families the reference uses (themselves freely licensed; not a substitution
 * — see the font-license ledger in CANONICAL_RELEASE.md). Self-hosted is unnecessary; loaded via
 * Google Fonts `<link>` in app/views/layouts/public.html.erb.
 */

:root {
  /* --- measured type scale (Cormorant Garamond / DM Sans, 1440px viewport) --- */
  --oo-font-display: "Cormorant Garamond", Georgia, serif;
  --oo-font-body: "DM Sans", -apple-system, BlinkMacSystemFont, sans-serif;

  /* --- measured color roles (neutral / photography-led palette; no accent hue detected) --- */
  --oo-ink: #212121;          /* headings on light */
  --oo-ink-secondary: #424242; /* body copy / secondary text on light */
  --oo-ink-strong: #000000;    /* CTAs, eyebrows on light, primary button fill */
  --oo-paper: #ffffff;
  --oo-paper-soft: #f7f6f4;
  --oo-line: #e2e0dc;
  --oo-on-photo: #ffffff;      /* text over a dark-scrim photo (hero) */
  --oo-scrim: rgba(0, 0, 0, 0.34);
  /* fix-cycle-3 (2026-07-27): footer tokens, measured via Playwright computed-style probe against
     the mirror's SiteFooter-module__footer__ on :8125 (rgb() values converted to hex below).
     Reference footer is LIGHT (rgb(245,245,245)), not dark — see the fix-cycle-3 ledger entry. */
  --oo-footer-bg: #f5f5f5;         /* measured rgb(245,245,245) */
  --oo-ink-muted: #757575;         /* measured rgb(117,117,117) — Kerzner's own "role-subtle" copyright/legal tone */
  /* fix-cycle (2026-07-27): --oo-ink-muted is calibrated 4.61:1 against WHITE surfaces; on
     --oo-footer-bg (#f5f5f5) it drops to 4.23:1, which fails AA (≥4.5:1) for the footer locale
     switcher's 12px inactive-link text. Scoped override (footer context only) — do NOT change
     --oo-ink-muted itself, which is correct everywhere else it's used on white. 4.68:1 on
     --oo-footer-bg. */
  --oo-footer-locale-muted: #6e6e6e;

  /* --- measured spacing / grid --- */
  --oo-container: 1440px;
  --oo-gutter: 72px;
  --oo-section-pad: 64px;
  --oo-section-pad-lg: 96px;
  --oo-radius: 0px; /* sharp corners throughout — measured 0px on every card/button/image */

  /* --- measured header --- */
  --oo-header-h: 96px;
}

/* ============================= base / reset (scoped) ============================= */
.oo-scope {
  font-family: var(--oo-font-body);
  font-size: 14px;
  line-height: 24px;
  color: var(--oo-ink-secondary);
  background: var(--oo-paper);
}
.oo-scope * { box-sizing: border-box; }
.oo-scope img { max-width: 100%; display: block; }
/* :not(.oo-btn) — cycle-2 root fix (2026-07-27): this base reset is the ROOT cause of a
   site-wide latent bug — `.oo-scope a`'s specificity (0,1,1) beats `.oo-btn--primary`'s own
   `color: var(--oo-paper)` rule (0,1,0) everywhere EXCEPT inside `.oo-hero`/`.oo-hero--band` (which
   happen to set their own inherited white text, masking the bug by coincidence). Any `.oo-btn`
   link on a light background — the availability-results room-card "Book now", the mobile drawer's
   CTA — silently had its intended white text repainted to the inherited dark ink, i.e. invisible
   dark-on-black button text. Excluding `.oo-btn` here (plus the same exclusion on `.oo-drawer a`,
   `.oo-header-nav a`, `.oo-header-lang a`, `.oo-footer a`, and `.oo-stack-card a` below) makes
   `.oo-btn`'s own color/font/padding rules authoritative everywhere, regardless of container. */
.oo-scope a:not(.oo-btn) { color: inherit; text-decoration: none; }
.oo-scope ul { list-style: none; margin: 0; padding: 0; }

.oo-wrap {
  max-width: var(--oo-container);
  margin: 0 auto;
  padding: 0 72px;
}
@media (max-width: 1023px) { .oo-wrap { padding: 0 24px; } }

.oo-eyebrow {
  font-family: var(--oo-font-body);
  font-size: 16px;
  font-weight: 400;
  letter-spacing: 4px;
  text-transform: uppercase;
  display: block;
  margin-bottom: 14px;
}
.oo-eyebrow--on-light { color: var(--oo-ink-strong); }
.oo-eyebrow--on-photo { color: var(--oo-on-photo); }

.oo-scope h1 {
  font-family: var(--oo-font-display);
  font-weight: 400;
  font-size: 72px;
  line-height: 90px;
  letter-spacing: -1px;
  margin: 0;
}
.oo-scope h2, .oo-scope h3 {
  font-family: var(--oo-font-display);
  font-weight: 400;
  margin: 0;
  color: var(--oo-ink);
}
.oo-scope h2 {
  font-size: 56px;
  line-height: 72px;
  letter-spacing: -1px;
}
.oo-scope h3 {
  font-size: 40px;
  line-height: 52px;
}
.oo-scope h4 {
  font-family: var(--oo-font-display);
  font-weight: 400;
  font-size: 28px;
  line-height: 1.25;
  margin: 0;
  color: var(--oo-ink);
}
.oo-scope p { margin: 0; }
.oo-meta { font-size: 12px; letter-spacing: 0.3px; color: var(--oo-ink-secondary); }

@media (max-width: 1023px) {
  .oo-scope h1 { font-size: 48px; line-height: 60px; letter-spacing: -1px; }
  .oo-scope h2 { font-size: 40px; line-height: 52px; letter-spacing: -1px; }
  .oo-scope h3 { font-size: 28px; line-height: 36px; }
  .oo-scope h4 { font-size: 24px; line-height: 32px; }
}

/* ============================= buttons / links ============================= */
.oo-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: var(--oo-font-body);
  font-size: 14px;
  letter-spacing: 0.5px;
  padding: 16px 48px;
  border-radius: var(--oo-radius);
  border: 1px solid var(--oo-ink-strong);
  cursor: pointer;
  transition: background-color 300ms ease-in-out, color 300ms ease-in-out, border-color 300ms ease-in-out;
  white-space: nowrap;
}
.oo-btn--primary { background: var(--oo-ink-strong); color: var(--oo-paper); }
.oo-btn--primary:hover { background: #2b2b2b; }
.oo-btn--ghost { background: transparent; color: var(--oo-on-photo); border-color: var(--oo-on-photo); }
.oo-btn--ghost:hover { background: rgba(255, 255, 255, 0.12); }
.oo-btn--outline { background: transparent; color: var(--oo-ink-strong); border-color: var(--oo-ink-strong); }
.oo-btn--outline:hover { background: var(--oo-ink-strong); color: var(--oo-paper); }
.oo-btn--sm { padding: 12px 28px; }

.oo-link {
  font-size: 14px;
  color: var(--oo-ink-secondary);
  border-bottom: 1px solid transparent;
  padding-bottom: 2px;
  transition: border-color 300ms ease-in-out, color 300ms ease-in-out;
}
.oo-link:hover { border-color: currentColor; color: var(--oo-ink-strong); }
.oo-link--on-photo { color: var(--oo-on-photo); }
.oo-btn:focus-visible,
.oo-link:focus-visible,
.oo-header-hamburger:focus-visible {
  outline: 1px solid currentColor;
  outline-offset: 4px;
}

/* ============================= header + drawer =============================
   fix-cycle-4 (2026-07-27): ported from the clone's Header-module 3-column anatomy
   (site/cape-town/index.html <header class="Header-module__header__…">, measured live at :8125) —
   left column = hamburger (+ the reference's own "Destinations" pill, DROPPED here: a fake dropdown
   for a single-property site would be non-functional chrome, ledgered), center = logo, right =
   Book. The reference's hamburger is visible AT ALL VIEWPORTS (there is no separate "flat nav bar"
   resting state) — that's the structural fix: the old always-visible `.oo-header-nav` link bar is
   GONE, replaced by the drawer being the only nav surface, same as the reference. */
.oo-header {
  position: sticky;
  top: 0;
  z-index: 60;
  height: var(--oo-header-h);
  background: var(--oo-paper);
  border-bottom: 1px solid var(--oo-line);
  display: flex;
  align-items: center;
  transition: background-color 500ms cubic-bezier(.4, 0, .2, 1),
              border-color 500ms cubic-bezier(.4, 0, .2, 1);
}
.oo-header .oo-wrap {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  width: 100%;
}
.oo-header-logo { justify-self: center; }
.oo-header-logo img { height: 44px; width: auto; }
.oo-header-left { justify-self: start; display: flex; align-items: center; }
.oo-header-right { justify-self: end; display: flex; align-items: center; gap: 18px; }
.oo-header-lang a:not(.oo-btn) { font-size: 12px; letter-spacing: 0.5px; color: var(--oo-ink-secondary); margin-right: 8px; }
.oo-header-lang a.active { color: var(--oo-ink-strong); font-weight: 700; }
.oo-header-book { display: inline-flex; }
/* Hamburger is the ONLY nav affordance in the resting header, at every viewport (measured: the
   reference's IconButton-module__icon-button in NavToolbar-module__nav-toolbar__menu is present in
   the DOM at 1440px and 390px alike — no responsive display:none). */
.oo-header-hamburger {
  display: inline-flex;
  border: 0; background: none; cursor: pointer; padding: 8px;
  color: var(--oo-ink);
}

@media (max-width: 1023px) {
  .oo-header-logo img { height: 34px; }
  .oo-header-right { gap: 8px; }
  .oo-header-book { padding-left: 16px; padding-right: 16px; }
}

/* ---- drawer: ported from NavSidebar-module__sidenav (site/cape-town/index.html, hamburger-open
   DOM captured via Playwright against :8125 — client-rendered, not present in the raw HTML source).
   Anatomy: a fixed-width white panel sliding in from the left (measured 616px @ 1440px viewport,
   full-width below ~640px), full viewport height, ABOVE a 30%-black scrim
   (NavSidebar-module__sidenav__backdrop, measured rgba(0,0,0,0.3)) that dims the page behind it —
   "split-pane/dim" treatment. Close X top-left inside the panel (a distinct element from the
   header's own hamburger button, per the measured DOM). Content: primary nav list, a divider
   (<hr>), then a secondary list — ours folds Book + EN/MK into the secondary group (the reference
   keeps those in its always-visible header instead; our reduced 4-link nav has no equivalent
   persistent surface once the drawer covers the header, so this is a deliberate, ledgered
   inclusion, not a 1:1 copy). */
.oo-drawer-scrim {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.3);
  z-index: 54;
  opacity: 0;
  pointer-events: none;
  transition: opacity 320ms ease;
}
.oo-drawer-scrim.is-open { opacity: 1; pointer-events: auto; }

.oo-drawer {
  position: fixed;
  inset: 0 auto 0 0;
  width: 100%;
  max-width: 616px;
  background: var(--oo-paper);
  z-index: 55;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  transform: translateX(-100%);
  visibility: hidden;
  transition: transform 320ms ease, visibility 0s linear 320ms;
}
.oo-drawer.is-open {
  transform: translateX(0);
  visibility: visible;
  transition: transform 320ms ease, visibility 0s linear 0s;
}
@media (prefers-reduced-motion: reduce) {
  .oo-drawer, .oo-drawer-scrim { transition-duration: 0s; }
}

.oo-drawer-top {
  display: flex;
  align-items: center;
  height: var(--oo-header-h);
  padding: 0 24px;
  flex: 0 0 auto;
}
.oo-drawer-close { border: 0; background: none; cursor: pointer; padding: 8px; margin-left: -8px; color: var(--oo-ink); }

.oo-drawer-primary, .oo-drawer-secondary {
  display: flex;
  flex-direction: column;
  padding: 4px 24px;
}
.oo-drawer a:not(.oo-btn) {
  display: block;
  font-family: var(--oo-font-display);
  font-size: 28px;
  padding: 14px 0;
  color: var(--oo-ink);
}
.oo-drawer-secondary a:not(.oo-btn) { font-family: var(--oo-font-body); font-size: 16px; padding: 12px 0; }
.oo-drawer-divider { border: 0; border-top: 1px solid var(--oo-line); margin: 8px 24px; }
.oo-drawer .oo-btn { margin: 16px 0 8px; align-self: flex-start; }
.oo-drawer-lang { margin: 8px 0 24px; display: flex; gap: 16px; }
.oo-drawer-lang a { font-size: 13px; letter-spacing: 0.5px; color: var(--oo-ink-secondary); }
.oo-drawer-lang a.active { color: var(--oo-ink-strong); font-weight: 700; }
@media (max-width: 640px) { .oo-drawer { max-width: none; } }

/* ============================= section / widget rhythm =============================
   DEFENSIVE NOTE (fix-cycle-4, 2026-07-27): `.oo-section` provides vertical rhythm padding;
   `.oo-wrap` provides the max-width + horizontal gutter. They are DESIGNED TO NEST — `.oo-wrap`
   INSIDE `.oo-section` — never combined on the same element. Combining them
   (`class="oo-section oo-wrap"`) makes `.oo-section`'s vertical padding apply to the SAME box
   `.oo-wrap` constrains horizontally, which reads fine at 1440px but silently loses the horizontal
   gutter at narrow viewports on any element whose content can't shrink (a 3-col form row, an
   unbreakable control) — see CANONICAL_RELEASE.md fix-cycle-4 item 7 for the concrete /contact
   regression this caused. Every current usage was swept and fixed this cycle
   (availability/search.html.erb, payments/returns/{ok,fail}.html.erb); if you're adding a new page,
   nest the two classes, don't combine them. */
.oo-section { padding: var(--oo-section-pad) 0; }
.oo-section--lg { padding: var(--oo-section-pad-lg) 0; }
.oo-section--tight { padding: 32px 0; }
.oo-section--soft { background: var(--oo-paper-soft); }
.oo-section-head { max-width: 720px; margin-bottom: 40px; }
.oo-section-head--center { margin-left: auto; margin-right: auto; text-align: center; }
.oo-section-head p { margin-top: 16px; font-size: 16px; line-height: 26px; color: var(--oo-ink-secondary); }
.oo-section-head .oo-link { display: inline-block; margin-top: 20px; }

.oo-grid { display: grid; gap: var(--oo-gutter); }
.oo-grid--2 { grid-template-columns: repeat(2, 1fr); }
.oo-grid--3 { grid-template-columns: repeat(3, 1fr); }
.oo-grid--4 { grid-template-columns: repeat(4, 1fr); }
/* fix-cycle-4 (2026-07-27, /contact 390px overflow): a `1fr` grid track's default min-width
   is `auto`, i.e. the min-CONTENT width of whatever's inside it — so a wide child (a 3-column
   form row, a long word, an unbreakable control) can force the whole track past its intended
   share of the row, blowing out every `.oo-grid` ancestor up to the viewport. This is the CSS
   root cause, not a per-page one-off: any current or future `.oo-grid` child inherits the fix. */
.oo-grid > * { min-width: 0; }
@media (max-width: 1023px) {
  .oo-grid--2, .oo-grid--3, .oo-grid--4 { grid-template-columns: 1fr; gap: 28px; }
}

/* ============================= hero (ProductHeroWidget / InspirationalHeroWidget) ======== */
/* fix-cycle-4 (2026-07-27, operator-measured refinement): `align-items` corrected from `flex-end`
   to `center` — measured against the reference's actual ProductHeroWidget (site/cape-town/index.html,
   :8125, Playwright getBoundingClientRect): the text block's vertical CENTER (eyebrow-top to
   intro-bottom midpoint) landed at y=498, and the hero content region (header-bottom to
   viewport-bottom) spans 96–900, whose own midpoint is ALSO 498 — an exact match, i.e. the text
   is vertically centered in the hero, not bottom-anchored. The bottom-anchored + `padding-bottom`
   treatment this rule had before measured 709 vs a region-center of 492 — visibly low. Horizontal
   centering was already correct (block midX == viewport center at both 1440/390, confirmed via the
   same probe) — only the vertical rhythm was wrong. */
.oo-hero {
  position: relative;
  min-height: 640px;
  display: flex;
  align-items: center;
  color: var(--oo-on-photo);
  overflow: hidden;
}
.oo-hero--tall { min-height: 88vh; }
.oo-hero-media { position: absolute; inset: 0; z-index: 0; }
.oo-hero-media > div,
.oo-hero-media picture,
.oo-hero-media img,
.oo-hero-override-media {
  width: 100%;
  height: 100%;
}
.oo-hero-media picture { display: block; }
.oo-hero-media img { object-fit: cover; }
.oo-hero-media::after { content: ""; position: absolute; inset: 0; background: linear-gradient(180deg, rgba(0,0,0,0.10) 0%, rgba(0,0,0,0.50) 100%); }
/* Centered per the reference's ProductHeroWidget (StandardTextVariation — every hero instance
   measured uses Typography--align-center on the eyebrow/H1/intro; see site/cape-town/index.html).
   Also drops the home hero's 2-CTA row entirely (fix-cycle-4 item 3) — the reference's hero
   carries ZERO <a>/<button> elements at any measured route; a global "Book now" lives in the
   header instead (unchanged). */
.oo-hero-content { position: relative; z-index: 1; width: 100%; text-align: center; }
.oo-hero-content p.oo-hero-intro { margin: 20px auto 0; max-width: 620px; font-size: 16px; line-height: 26px; }
@media (max-width: 1023px) {
  .oo-hero { min-height: 480px; }
}

/* Inline (non full-bleed) hero variant used on interior pages with a shorter photo band */
.oo-hero--band { min-height: 420px; }

/* fix-cycle-4 (2026-07-27): the /rooms index header has NO photo on the reference — measured
   data-color-scheme-from="light" on the ExpandableFiltersWidget (site/cape-town/stays/index.html,
   :8125): plain white background, centered eyebrow + H1, no hero media, no scrim. A dedicated
   variant rather than reusing `.oo-hero` (which always assumes a photo + dark-text-on-light would
   be wrong contrast-wise there). */
.oo-hero-plain { padding: 64px 0 40px; text-align: center; }
.oo-hero-plain .oo-eyebrow, .oo-hero-plain h1 { color: var(--oo-ink-strong); }
@media (max-width: 1023px) { .oo-hero-plain { padding: 40px 0 28px; } }

/* ============================= banner (SectionBannerWidget — full-bleed breather) ======== */
.oo-banner { width: 100%; height: 60vh; min-height: 380px; max-height: 620px; overflow: hidden; }
.oo-banner img { width: 100%; height: 100%; object-fit: cover; }
/* fix-cycle-4: `_hero_photo` (the owner-override-aware partial) wraps its <picture> in a plain
   div — needs an explicit height chain to fill `.oo-banner` (percentage heights need every
   ancestor sized), unlike the ratio-driven `marketing_media` figure this class was originally
   paired with. */
.oo-banner > div, .oo-banner picture { height: 100%; }
@media (max-width: 1023px) { .oo-banner { height: 40vh; min-height: 260px; } }

/* fix-cycle-4: the room-detail "signature-suites dark banner" adaptation (task item 5) — same
   SectionBannerWidget anatomy as `.oo-banner` above (the reference's own instance of this widget on
   a room-detail page, e.g. stays/rooms/marina-room, IS present but renders with an EMPTY
   text-on-media slot at the measured route — see CANONICAL_RELEASE.md). Riverside has no
   suites/penthouse tier to cross-sell into, so this reuses the SAME widget shell with real content
   (the property's other room types), rather than an invented "signature suite" that doesn't exist. */
.oo-banner-cta { position: relative; width: 100%; height: 52vh; min-height: 340px; max-height: 520px; overflow: hidden; color: var(--oo-on-photo); }
.oo-banner-cta img { width: 100%; height: 100%; object-fit: cover; }
.oo-banner-cta::after { content: ""; position: absolute; inset: 0; background: linear-gradient(180deg, rgba(0,0,0,0.05) 0%, rgba(0,0,0,0.55) 100%); }
.oo-banner-cta-content { position: absolute; inset: auto 0 0 0; z-index: 1; padding: 40px; text-align: center; }
.oo-banner-cta-content .oo-eyebrow { color: var(--oo-on-photo); }
.oo-banner-cta-content h3 { color: var(--oo-on-photo); }
.oo-banner-cta-content .oo-btn { margin-top: 18px; }
@media (max-width: 1023px) { .oo-banner-cta { height: 42vh; min-height: 300px; } .oo-banner-cta-content { padding: 24px; } }

/* ============================= card-with-accordion (room-detail amenities) =============
   fix-cycle-4: ported from CardWithAccordionWidget (site/cape-town/stays/rooms/marina-room/index.html,
   :8125 — "Designed with you in mind" / EXCLUSIVE AMENITIES): left = eyebrow+H3+intro, right =
   a vertical accordion, first panel expanded by default. Grouped from Riverside's own real
   RoomType#facilities (Facility rows, seeded from the verified Booking.com highlightsForAllRooms
   cross-reference — see CANONICAL_RELEASE.md "Real per-room facility data") — never invented copy. */
.oo-accordion-item { border-bottom: 1px solid var(--oo-line); }
.oo-accordion-item:first-child { border-top: 1px solid var(--oo-line); }
.oo-accordion-trigger {
  display: flex; align-items: center; justify-content: space-between; width: 100%;
  padding: 18px 0; background: none; border: 0; cursor: pointer; text-align: left;
  font-family: var(--oo-font-display); font-size: 20px; color: var(--oo-ink);
}
.oo-accordion-trigger svg { transition: transform 220ms ease; flex-shrink: 0; }
.oo-accordion-item.is-open .oo-accordion-trigger svg { transform: rotate(180deg); }
.oo-accordion-body { overflow: hidden; max-height: 0; transition: max-height 260ms ease; }
.oo-accordion-item.is-open .oo-accordion-body { max-height: 320px; }
.oo-accordion-body ul { padding: 0 0 18px; display: grid; gap: 8px; }
.oo-accordion-body li { font-size: 14px; color: var(--oo-ink-secondary); }
@media (prefers-reduced-motion: reduce) { .oo-accordion-body { transition-duration: 0s; } .oo-accordion-trigger svg { transition-duration: 0s; } }

/* ============================= feature band (FeatureCardsWidget) ============================= */
.oo-feature { display: grid; grid-template-columns: 1fr 1fr; gap: var(--oo-gutter); align-items: center; }
.oo-feature-copy p { margin-top: 16px; font-size: 16px; line-height: 26px; color: var(--oo-ink-secondary); }
.oo-feature-copy .oo-link { display: inline-block; margin-top: 22px; }
.oo-feature-media figure, .oo-feature-media img { width: 100%; }
@media (max-width: 1023px) { .oo-feature { grid-template-columns: 1fr; } }

.oo-feature-cols { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--oo-gutter); }
.oo-feature-col h3 { font-size: 28px; line-height: 1.25; }
.oo-feature-col p { margin-top: 14px; font-size: 15px; line-height: 24px; color: var(--oo-ink-secondary); }
.oo-feature-col .oo-link { display: inline-block; margin-top: 16px; }
@media (max-width: 1023px) { .oo-feature-cols { grid-template-columns: 1fr; gap: 36px; } }

/* ============================= card (CardCarouselWidget adopted as a static grid — see
   CANONICAL_RELEASE.md ledger: reduced from a JS carousel, item counts here (1-4) don't need
   horizontal scrolling; anatomy/content order preserved) ============================= */
.oo-card { display: flex; flex-direction: column; }
.oo-card-media { aspect-ratio: 4/3; overflow: hidden; background: var(--oo-paper-soft); }
.oo-card-media img { width: 100%; height: 100%; object-fit: cover; }
.oo-card-body { padding-top: 20px; }
.oo-card-body .oo-eyebrow { font-size: 12px; letter-spacing: 3px; margin-bottom: 8px; }
.oo-card-meta { margin-top: 10px; display: flex; flex-wrap: wrap; gap: 6px 10px; font-size: 12px; color: var(--oo-ink-secondary); }
.oo-card-meta span.oo-sep { color: var(--oo-line); }
.oo-card-actions { margin-top: 16px; display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
/* fix-cycle-4 (2026-07-27): matches the reference's own card CTA pair exactly — "Explore Room" is
   Button--primary-small (SOLID), "Book Now" is Button--tertiary (a plain underlined text link) —
   measured on site/cape-town/stays/index.html's FeatureCardV2 actions-slot. The old build had this
   INVERTED (View room as a plain link, Book now as the solid button). Scoped to card actions only
   — `.oo-link` elsewhere (e.g. "See all rooms") keeps its underline-on-hover-only treatment. */
.oo-card-actions .oo-link { text-decoration: underline; text-underline-offset: 3px; }

/* Room card = same anatomy, slightly larger media */
.oo-room-card .oo-card-media { aspect-ratio: 3/2; }
.oo-room-card .oo-card-body p.oo-room-desc { margin-top: 10px; font-size: 14px; line-height: 22px; color: var(--oo-ink-secondary); }

/* ============================= facts strip (restaurant At-a-glance — unchanged this cycle) === */
.oo-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 28px; border-top: 1px solid var(--oo-line); border-bottom: 1px solid var(--oo-line); padding: 32px 0; }
.oo-facts h4 { font-size: 16px; letter-spacing: 1px; text-transform: uppercase; font-family: var(--oo-font-body); color: var(--oo-ink-strong); margin-bottom: 8px; }
.oo-facts p { font-size: 14px; color: var(--oo-ink-secondary); line-height: 22px; }

/* ============================= list-banner (room-detail description + facts) =============
   fix-cycle-4 item 8 (2026-07-27, operator-added) — ported from ListBannerWidget, the reference's
   ACTUAL room-detail facts anatomy (site/cape-town/stays/rooms/marina-room/index.html, measured
   live at :8125 — NOT the ProductHeroWidget-style "at a glance" strip the previous pass invented).
   Anatomy: 2-col (5/7 split) — left = the room's description (styled as a large serif lede) + a
   solid Book Now button; right = a 3-col grid of fact groups (Size/Occupancy/Beds/View/Highlights),
   each a plain H4 label + stacked text lines. NO PRICE anywhere in this widget — confirmed by the
   measured DOM; pricing lives in the availability/booking flow only (removed here, ledgered). */
.oo-list-banner { background: var(--oo-paper-soft); padding: 56px 0; }
.oo-list-banner-grid { display: grid; grid-template-columns: 5fr 7fr; gap: 48px; align-items: start; }
.oo-list-banner-desc { font-family: var(--oo-font-display); font-size: 24px; line-height: 1.4; color: var(--oo-ink); font-weight: 400; }
.oo-list-banner-desc .oo-btn { margin-top: 22px; }
.oo-list-banner-facts { display: grid; grid-template-columns: repeat(3, 1fr); gap: 28px 24px; }
.oo-list-banner-facts h4 { font-size: 16px; font-weight: 400; font-family: var(--oo-font-body); color: var(--oo-ink); margin-bottom: 8px; }
.oo-list-banner-facts p { font-size: 14px; font-weight: 300; color: var(--oo-ink-secondary); line-height: 22px; margin-bottom: 2px; }
@media (max-width: 1023px) {
  .oo-list-banner-grid { grid-template-columns: 1fr; gap: 32px; }
  .oo-list-banner-facts { grid-template-columns: 1fr 1fr; }
}

/* ============================= photo carousel (room-detail gallery) =============
   fix-cycle-4 item 8 — the reference uses FoldingCarouselWidget + a second ImmersiveCarouselWidgetStatic
   on this route (two distinct carousels); adopted here as ONE carousel (reduction — a 4-8 photo set
   doesn't need two different carousel treatments; ledgered) with the reference's core interaction
   pattern: horizontal slide track, prev/next arrows, a dot pager — replacing the old flat 2x3 photo
   grid entirely. Own Stimulus controller (Mode C — no source reused). */
.oo-carousel { position: relative; }
.oo-carousel-viewport { overflow: hidden; }
.oo-carousel-track { display: flex; transition: transform 380ms ease; }
.oo-carousel-slide { flex: 0 0 100%; aspect-ratio: 16 / 9; background: var(--oo-paper-soft); }
.oo-carousel-slide img { width: 100%; height: 100%; object-fit: cover; }
.oo-carousel-arrow {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  width: 44px; height: 44px; display: flex; align-items: center; justify-content: center;
  background: var(--oo-paper); border: 0; cursor: pointer; color: var(--oo-ink);
}
.oo-carousel-arrow--prev { left: 16px; }
.oo-carousel-arrow--next { right: 16px; }
.oo-carousel-arrow[disabled] { opacity: 0.35; cursor: default; }
.oo-carousel-dots { display: flex; justify-content: center; gap: 8px; margin-top: 16px; }
.oo-carousel-dots button { width: 8px; height: 8px; border-radius: 50%; background: var(--oo-line); border: 0; padding: 0; cursor: pointer; }
.oo-carousel-dots button.is-active { background: var(--oo-ink-strong); }
@media (prefers-reduced-motion: reduce) { .oo-carousel-track { transition-duration: 0s; } }

/* ============================= gallery (GalleryWidget) =============================
   fix-cycle-4 (2026-07-27): REPLACES the old always-visible dense flat grid. Ported from the
   reference's actual /gallery first fold (site/cape-town/gallery/index.html, live-measured
   bounding boxes at :8125): a 5-photo ASYMMETRIC collage, then a "View Gallery" outlined button
   that opens a lightbox DIALOG (white surface, large image, thumbnail strip, close X, prev/next)
   over the full photo set — 45 real Riverside photos here vs. the reference's own set. Column/row
   spans below are engineered from the measured bounding-box geometry (not literal copied CSS —
   Mode C ships no compiled reference CSS); see CANONICAL_RELEASE.md fix-cycle-4 for the measurement
   table. */
.oo-collage {
  display: grid;
  grid-template-columns: 1.55fr 1fr 0.72fr;
  gap: 8px;
  height: clamp(480px, 50vw, 730px);
}
.oo-collage button { border: 0; padding: 0; margin: 0; background: none; cursor: zoom-in; overflow: hidden; display: block; }
.oo-collage img { width: 100%; height: 100%; object-fit: cover; transition: transform 400ms ease; }
.oo-collage button:hover img { transform: scale(1.04); }
.oo-collage-col1 { grid-column: 1; grid-row: 1 / -1; display: flex; flex-direction: column; gap: 8px; }
.oo-collage-col1 button:nth-child(1) { flex: 1.2; }
.oo-collage-col1 button:nth-child(2) { flex: 1; }
.oo-collage-item2 { grid-column: 2; grid-row: 1; }
.oo-collage-item3 { grid-column: 3; grid-row: 1; }
.oo-collage-item4 { grid-column: 2 / span 2; grid-row: 2; }
.oo-collage { grid-template-rows: 38% 62%; }
@media (max-width: 1023px) {
  .oo-collage { grid-template-columns: 1fr 1fr; grid-template-rows: repeat(3, 220px); height: auto; }
  .oo-collage-col1 { grid-column: 1 / span 2; grid-row: 1; flex-direction: row; }
  .oo-collage-item2 { grid-column: 1; grid-row: 2; }
  .oo-collage-item3 { grid-column: 2; grid-row: 2; }
  .oo-collage-item4 { grid-column: 1 / span 2; grid-row: 3; }
}
.oo-gallery-cta { margin-top: 24px; text-align: center; }

.oo-lightbox {
  position: fixed; inset: 0; z-index: 90; background: rgba(20, 20, 20, 0.85);
  display: flex; align-items: center; justify-content: center; padding: 40px;
}
.oo-lightbox.hidden { display: none; }
/* White dialog surface (measured: the reference's own gallery lightbox is a white rounded card on
   a dimmed backdrop, not a full-black overlay like the old build). */
.oo-lightbox-dialog {
  position: relative;
  background: var(--oo-paper);
  width: min(760px, 100%);
  max-height: 90vh;
  padding: 48px 24px 24px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  overflow-y: auto;
}
.oo-lightbox-stage { position: relative; width: 100%; display: flex; align-items: center; justify-content: center; }
.oo-lightbox-stage img { max-width: 100%; max-height: 55vh; object-fit: contain; }
.oo-lightbox-close { position: absolute; top: 12px; right: 12px; background: none; border: 0; color: var(--oo-ink); cursor: pointer; padding: 8px; }
.oo-lightbox-arrow {
  background: none; border: 0; color: var(--oo-ink); cursor: pointer; padding: 8px;
  flex-shrink: 0;
}
.oo-lightbox-arrow[disabled] { opacity: 0.25; cursor: default; }
.oo-lightbox-thumbs { display: flex; gap: 8px; overflow-x: auto; max-width: 100%; padding-bottom: 4px; }
.oo-lightbox-thumbs button { border: 2px solid transparent; padding: 0; background: none; cursor: pointer; flex-shrink: 0; width: 64px; height: 64px; overflow: hidden; }
.oo-lightbox-thumbs img { width: 100%; height: 100%; object-fit: cover; display: block; }
.oo-lightbox-thumbs button.is-active { border-color: var(--oo-ink-strong); }
@media (max-width: 640px) { .oo-lightbox { padding: 12px; } .oo-lightbox-dialog { padding: 40px 12px 16px; } }

/* ============================= contact stacked cards (StackedCardsWidget) ============================= */
.oo-stack { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--oo-gutter); }
.oo-stack-card { border: 1px solid var(--oo-line); padding: 32px; }
.oo-stack-card h4 { font-size: 22px; margin-bottom: 12px; }
.oo-stack-card p { font-size: 14px; line-height: 22px; color: var(--oo-ink-secondary); margin-bottom: 4px; }
/* :not(.oo-btn) — cycle-2 fix (2026-07-27): this rule is meant for PLAIN text links inside a
   stack card (e.g. an inline "Send enquiry" link), not styled buttons. Before the exclusion, its
   `.oo-stack-card a` specificity (0,1,1) beat `.oo-btn--primary`'s own `color: var(--oo-paper)`
   rule (0,1,0), silently repainting a primary button's white text back to dark ink — invisible
   dark-on-black text on any `.oo-btn--primary` nested in a `.oo-stack-card` (the availability
   results room-card "Book now" CTA hit this the moment it adopted `.oo-btn`). */
.oo-stack-card a:not(.oo-btn) { color: var(--oo-ink-strong); }
@media (max-width: 1023px) { .oo-stack { grid-template-columns: 1fr; } }

/* ============================= forms (enquiry / availability / booking) ============================= */
.oo-form { max-width: 640px; }
.oo-field { display: block; margin-bottom: 20px; }
.oo-field label { display: block; font-size: 12px; letter-spacing: 1px; text-transform: uppercase; color: var(--oo-ink-secondary); margin-bottom: 8px; }
.oo-field input, .oo-field select, .oo-field textarea {
  width: 100%; border: 1px solid var(--oo-line); border-radius: var(--oo-radius);
  padding: 14px 16px; font-family: var(--oo-font-body); font-size: 14px; color: var(--oo-ink);
  background: var(--oo-paper);
}
.oo-field input:focus, .oo-field select:focus, .oo-field textarea:focus { outline: 2px solid var(--oo-ink-strong); outline-offset: 1px; }
.oo-field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 600px) { .oo-field-row { grid-template-columns: 1fr; } }
/* fix-cycle-4 (2026-07-27): 3-column variant (contact form's check-in/check-out/guests row) —
   was an inline `style="grid-template-columns:1fr 1fr 1fr;"` on the view, which (being an inline
   style) always outranks this file's `@media (max-width: 600px)` rule regardless of viewport,
   so the row never collapsed on mobile. A real class-based modifier lets the stylesheet's own
   breakpoint govern it like every other `.oo-field-row`. */
.oo-field-row--3 { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 600px) { .oo-field-row--3 { grid-template-columns: 1fr; } }

/* ============================= footer =============================
   fix-cycle-3 (2026-07-27): retheme from a black footer (defect — the reference's resting footer
   is LIGHT, rgb(245,245,245)/#f5f5f5, verified byte-identical against BOTH the live site and the
   mirror, so this was never a capture gap) to the measured light treatment. Tokens probed via
   Playwright `getComputedStyle` against `SiteFooter-module__footer__` on the mirror (:8125):
   background #f5f5f5, body/link text #424242 (already `--oo-ink-secondary`), heading/brand-link
   text #212121 (already `--oo-ink`), copyright/legal text #757575 (new `--oo-ink-muted`), divider
   border rgb(224,224,224) (close enough to the existing `--oo-line` #e2e0dc to reuse rather than
   add a near-duplicate token). See CANONICAL_RELEASE.md fix-cycle-3 for the full probe. */
.oo-footer { background: var(--oo-footer-bg); color: var(--oo-ink-secondary); border-top: 1px solid var(--oo-line); }
/* fix-cycle-4 (2026-07-27): remapped to the reference's SiteFooter-module__footer column grouping
   (site/cape-town/index.html <footer>, measured live at :8125) — logo column + About / Discover /
   Bookings & Terms / Follow Us, honestly reduced to real Riverside content per column (see
   CANONICAL_RELEASE.md's fix-cycle-4 source-to-output map). A 6th "Language" column is an ADDITION
   beyond the reference's 4 nav columns — the reference is single-language and has no equivalent;
   ledgered, not dropped, since it's a real bilingual-site requirement. */
/* fix-cycle-4 (2026-07-27, operator-measured refinement): logo/type/spacing corrected against
   getComputedStyle + getBoundingClientRect probes of SiteFooter-module__footer on :8125 (values in
   the table below). The first pass got the column NAMES right but not the geometry — this pass
   fixes that.
   | Token | Measured (reference) | Applied |
   |---|---|---|
   | Nav-column gap | 48px | 48px (was 32px) |
   | Logo height | 46px | 46px (was 52px) |
   | Column heading | 14px / weight 400 / rgb(33,33,33) / NOT uppercase, no extra tracking | matches — the reference's "About" etc. are plain body text, not small-caps labels; the old uppercase/13px/1.5px-tracking treatment was invented, not measured |
   | Nav link | 14px / weight 300 / rgb(66,66,66) | matches (DM Sans 300 added to the font `<link>`) |
   | Brand-strip logo height | 32px | 32px (was 22px — too small, read as a stray icon fragment rather than an intentional strip) |
   | Brand-strip padding | 24px 0 | 24px 0 (was 28px 0) | */
.oo-footer-top { padding: 72px 0 48px; display: grid; grid-template-columns: 1.3fr 0.9fr 1fr 1fr 1fr 1fr; gap: 48px 32px; }
.oo-footer-logo img { height: 46px; margin-bottom: 20px; } /* white card behind the logo dropped — logo is full-color, needs no contrast patch on a light footer */
.oo-footer h4 { color: var(--oo-ink); font-family: var(--oo-font-body); font-size: 14px; font-weight: 400; margin-bottom: 18px; }
.oo-footer ul li { margin-bottom: 10px; font-size: 14px; font-weight: 300; }
.oo-footer a:not(.oo-btn) { color: var(--oo-ink-secondary); }
.oo-footer a:not(.oo-btn):hover { color: var(--oo-ink); }
.oo-footer address { font-style: normal; font-size: 14px; font-weight: 300; line-height: 22px; color: var(--oo-ink-secondary); }
/* fix-cycle-4: single-brand wordmark strip — structural substitution for the reference's
   SiteFooter-module__footer__brands (a 5-logo Kerzner/Atlantis/One&Only/Siro/Rare-Finds portfolio
   row); Riverside has no sister-brand portfolio, so the SAME row anatomy carries one wordmark. */
.oo-footer-brands { border-top: 1px solid var(--oo-line); padding: 24px 0; display: flex; align-items: center; }
.oo-footer-brands img { height: 32px; width: auto; }
/* Measured: SiteFooter-module__footer__bottom is `Flex--justify-flex-end` — the copyright line
   sits right-aligned, not left. */
.oo-footer-bottom { padding: 22px 0; font-size: 14px; font-weight: 300; color: var(--oo-ink-muted); text-align: right; }
@media (max-width: 1023px) {
  .oo-footer-top { grid-template-columns: 1fr 1fr 1fr; padding: 48px 0 32px; gap: 32px 24px; }
}
@media (max-width: 560px) {
  .oo-footer-top { grid-template-columns: 1fr; }
}

/* Small reassurance row (booking form) — icon+label chips */
.oo-reassure { display: flex; flex-wrap: wrap; gap: 16px; font-size: 12px; color: var(--oo-ink-secondary); margin: 16px 0; }
.oo-reassure span { display: inline-flex; align-items: center; gap: 6px; }

/* ============================= reveal-on-scroll (kept minimal; matches the reference's plain
   fade-in, not a bespoke motion system) ============================= */
.oo-scope [data-reveal-item] { opacity: 1; }
