/* Owner portal — shares its style guide with the agency portal (platform.css);
   see /portal-style-guide for the shared reference. This file only adds
   owner-portal-specific components (dashboard nav, booking table, calendar,
   analytics charts) layered on top of platform.css's base tokens, buttons,
   fields and modal — it never redefines those, and never touches
   styles.css / the wood-forest-gold tokens, which belong to the hotel guest
   site only (see public/style-guide.html). Login (.login-wrap/.login-card/
   .login-msg/.login-hint) is platform.css's base — this file only layers a
   body.admin-scoped background glow on top of it (see .login-logo below),
   since /platform's own login isn't branded to any one client. */
body.admin { background: var(--pf-bg); }

/* Login background — a soft glow behind the card in the hotel's own two
   brand colors (accent blue + the mark's gold) instead of a flat blank
   page. Admin-only: layered on the shared .login-wrap/.login-card from
   platform.css the same way body.admin already overrides shadows below,
   never touching the agency's own /platform login.
   There's no header bar on this page (see the login-screen note in
   admin.html) — this page is opened in its own tab from the client's site,
   so closing the tab is the way back, not a link competing with it here —
   so .login-wrap just reclaims the full viewport height instead of
   platform.css's shared calc(100vh - 62px), which assumed a header that
   isn't here. */
body.admin .login-wrap { position: relative; overflow: hidden; min-height: 100vh; }
body.admin .login-wrap::before,
body.admin .login-wrap::after {
  content: ""; position: absolute; border-radius: 50%;
  filter: blur(64px); z-index: 0; pointer-events: none;
}
body.admin .login-wrap::before {
  width: 560px; height: 560px; top: -200px; left: -160px;
  background: radial-gradient(circle, rgba(0, 113, 227, 0.34), transparent 70%);
}
body.admin .login-wrap::after {
  width: 500px; height: 500px; bottom: -180px; right: -140px;
  background: radial-gradient(circle, rgba(199, 154, 83, 0.42), transparent 70%);
}
body.admin .login-card { position: relative; z-index: 1; }
.login-logo svg { display: block; width: 52px; height: 52px; margin-bottom: 20px; }
/* A client's own uploaded logo, swapped in by loadSiteInfo() in admin.js —
   same rounded-square treatment as the sidebar's .dash-nav-client-mark-wrap
   img, just bigger to match this card's own SVG mark above. */
.login-logo img { display: block; width: 52px; height: 52px; margin-bottom: 20px; border-radius: 11px; object-fit: cover; }
.login-lang-switch { position: absolute; top: 20px; right: 20px; z-index: 2; }
/* "Forgot password?" and the reset panel's own "Back to sign in" — a quiet
   centered text link, same treatment as .dash-nav-link. */
.login-forgot-link {
  display: block; width: 100%; margin-top: 14px; text-align: center;
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--pf-text-secondary); font-family: var(--pf-font); font-size: 0.9rem; font-weight: 600;
}
.login-forgot-link:hover { color: var(--pf-accent); }
/* The display:block above would otherwise defeat the [hidden] attribute —
   same fix as .nav-item-badge[hidden] and friends elsewhere in this file. */
.login-forgot-link[hidden] { display: none; }
.login-forgot { margin-top: 4px; }
.login-forgot .login-forgot-link { margin-top: 16px; }
body.admin .login-forgot #forgotFormWrap p.muted { margin-bottom: 16px; font-size: 0.88rem; line-height: 1.5; }
body.admin #forgotSuccessMsg { color: var(--pf-success); font-size: 0.92rem; line-height: 1.5; text-align: center; padding: 8px 0; }
/* A bright, purely-decorative amber for chart fills (status breakdown bar/dot)
   — distinct from --pf-warning, which is a dark brown tuned for AAA text
   contrast, not for reading clearly as a solid fill. */
:root { --pf-chart-pending: #eea114; }

/* Flat — no drop shadows anywhere on the owner portal. Zeroing the shared
   --pf-shadow-* tokens here (scoped to body.admin only) flattens every card,
   stat tile, chart panel, the login card and modals in one shot, without
   touching the agency dashboard (body.pf, no .admin), which keeps its shadows.
   Cards still separate from the page via the white-on-pf-bg color step.
   Focus glows/outlines are NOT shadows and stay — they're the a11y indicators. */
body.admin { --pf-shadow-sm: none; --pf-shadow-md: none; --pf-shadow-lg: none; }
/* The two hardcoded (non-token) shadows also go flat on the owner portal. */
body.admin .pf-toggle-knob { box-shadow: none; }

/* Typography — match the marketing site (see marketing/styles.css:
   --ds-font-display "Fraunces" for headings + --ds-font-body "Inter" for body).
   Scoped to body.admin so ONLY the owner portal picks up the pairing; the
   standalone agency page (body.pf, no .admin) and the style-guide pages keep
   the shared system-font stack. Both families are loaded in admin.html's <head>
   (the same Google Fonts request the portal already used for Inter).

   Redefining --pf-font here re-points every var(--pf-font) in the portal (body,
   buttons, inputs, nav) to Inter in one shot — the same one-line technique the
   shadow tokens above use. Headings then take the Fraunces display face via the
   rule below, whose `body.admin h1..h4` (0,0,1,2) outranks every component
   heading rule in platform.css (all `.pf-* hN`, 0,0,1,1), so card/detail/modal
   titles all pick it up too, exactly like the marketing page. */
body.admin {
  --pf-font: "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --pf-font-display: "Fraunces", "Iowan Old Style", Georgia, "Times New Roman", serif;
}
body.admin h1, body.admin h2, body.admin h3, body.admin h4 {
  font-family: var(--pf-font-display);
  /* The base body.pf heading rule tightens tracking (-0.015em) for a
     sans-serif; Fraunces reads better untracked, as it does on the marketing
     site (which sets no letter-spacing on its display headings). */
  letter-spacing: normal;
}

/* Base site chrome (.wrap) — styles.css defined this for the hotel guest
   site, but this page no longer loads styles.css, so it's redefined here
   instead, matching platform.css's own recipe (.pf-wrap) rather than the
   hotel's wood/serif version. */
.wrap { width: 100%; max-width: 1160px; margin: 0 auto; padding: 0 20px; }

/* Inline Material icons from js/icons.js — platform.css doesn't define these
   since the agency portal doesn't use icons.js at all. */
.mi { width: 1.15em; height: 1.15em; fill: currentColor; vertical-align: -0.18em; flex: none; }

/* ---- Dashboard shell: left nav + main ---- */
/* No header bar above this — the sidebar runs the full viewport height, and
   main's own max-width (below) is what keeps booking rows from sprawling on
   ultra-wide screens without needing the shell itself centered. */
.dash-shell { display: flex; align-items: flex-start; min-height: 100vh; }

/* Left navigation (Bookings / Availability / Testimonials) — replaces the old
   top view tabs so the only tabs left are the per-view filters (no tabs on tabs).
   No border/shadow between sidebar and main — the white-vs-pf-bg background
   step alone reads as the boundary, same idea as platform.css's cards. */
.dash-nav {
  flex: none; width: 236px; align-self: stretch;
  position: sticky; top: 0; height: 100vh;
  background: var(--pf-surface);
  padding: 14px 16px; display: flex; flex-direction: column; gap: 10px;
}
/* The portal's own name/mark — Openfront's own branding on the master portal,
   or a client's own logo (set at onboarding) or monogram fallback everywhere
   else (see applyNavBranding in admin.js), sitting at the top of the sidebar. */
.dash-nav-brand { display: flex; align-items: center; gap: 10px; min-width: 0; padding: 4px 4px 14px; }
.dash-nav-brand .dash-nav-client-name { font-size: 1.05rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dash-nav-client-mark-wrap {
  width: 28px; height: 28px; flex: none; border-radius: 7px; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
/* A client's own uploaded logo fills the square (like an avatar); the
   Openfront arrow icon stays its own small size and just sits centered in
   the (separately dark-filled) square — see applyNavBranding in admin.js. */
.dash-nav-client-mark-wrap img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* The monogram fallback — a client's first initial on their own brand color,
   used when they haven't uploaded a logo. */
.dash-nav-client-monogram {
  width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;
  font-family: var(--pf-font); font-weight: 700; font-size: 0.92rem; color: #fff;
}
.dash-nav-client-name { font-family: var(--pf-font); font-weight: 600; font-size: 1.02rem; line-height: 1.1; color: var(--pf-text); }

.dash-nav-list { display: flex; flex-direction: column; gap: 4px; }
/* Sets Master Clients off from every product nav item below it — only ever
   shown alongside that nav item itself (kept in sync by applyFeatureGates in
   admin.js), so it only ever appears on the master portal. */
.dash-nav-divider { border: none; border-top: 1px solid var(--pf-border-soft); margin: 6px 4px 8px; }
.nav-item {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 11px 14px; border: none; border-radius: var(--pf-radius-sm); background: none;
  cursor: pointer; font-family: var(--pf-font); font-weight: 600; font-size: 0.95rem;
  color: var(--pf-text); transition: background 0.15s, color 0.15s;
}
.nav-item:hover { background: var(--pf-neutral-soft); }
.nav-item.active { background: var(--pf-accent); color: #fff; }
.nav-item-ico { display: inline-flex; flex: none; }
.nav-item-ico .mi { width: 20px; height: 20px; }
.nav-item-label { flex: 1; }
.nav-item-badge {
  flex: none; min-width: 20px; height: 20px; padding: 0 6px; border-radius: var(--pf-radius-sm);
  background: var(--pf-danger); color: #fff; font-size: 0.72rem; font-weight: 700; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center; text-align: center;
}
.nav-item.active .nav-item-badge { background: var(--pf-white); color: var(--pf-accent); }
/* The display:inline-flex above would otherwise defeat the [hidden] attribute. */
.nav-item-badge[hidden] { display: none; }
.dash-nav-foot { margin-top: auto; display: grid; gap: 12px; }
/* Language switch — same two-pill pattern as the marketing site, sized down
   to sit quietly among the plain text links in this footer. */
.dash-nav-lang { display: flex; background: var(--pf-surface-2); border-radius: 999px; padding: 3px; gap: 2px; width: fit-content; }
.dash-nav-lang-btn {
  font-family: var(--pf-font); font-size: 0.78rem; font-weight: 600; color: var(--pf-text-secondary);
  background: transparent; border: none; border-radius: 999px; padding: 5px 11px; cursor: pointer; transition: 0.15s;
}
.dash-nav-lang-btn:hover { color: var(--pf-text); }
.dash-nav-lang-btn.on { background: var(--pf-white); color: var(--pf-text); box-shadow: 0 1px 3px rgba(0,0,0,0.08); }
.dash-nav-link { font-size: 0.85rem; color: var(--pf-text-secondary); text-decoration: underline; text-underline-offset: 2px; }
/* A <button> in this list ("Product tour", "Log out") needs the same plain
   text-link look a plain <a> gets for free — strip all browser button
   chrome so it reads as one quiet, equally-weighted line among the rest,
   not a real button competing for attention with the nav items above. */
button.dash-nav-link { background: none; border: none; padding: 0; margin: 0; width: auto; text-align: left; cursor: pointer; font-family: var(--pf-font); font-weight: 400; }
.dash-nav-logout:hover { color: var(--pf-danger); }

.dash-main { flex: 1; min-width: 0; padding: 26px 32px 56px; }

.dash-head { display: flex; align-items: center; gap: 14px; margin-bottom: 22px; }
.dash h1 { font-size: 1.8rem; }
.dash-date {
  display: inline-flex; align-items: center; gap: 7px; flex: none; margin-left: auto;
  color: var(--pf-text-secondary); font-size: 0.9rem; font-weight: 600; white-space: nowrap;
}

/* The cross-widget activity bell — see the doc comment on its markup in
   admin.html. Sits in .dash-head, right after .dash-date, so both stay
   pinned to the right edge as a group (dash-date's own margin-left:auto
   already claims the space before it). */
.notif-bell-wrap { position: relative; flex: none; }
.notif-bell-btn {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 50%; border: 1px solid var(--pf-border-soft);
  background: var(--pf-surface); color: var(--pf-text-secondary); cursor: pointer; transition: 0.15s;
}
.notif-bell-btn:hover { background: var(--pf-surface-2); color: var(--pf-text); }
.notif-bell-btn .mi { width: 19px; height: 19px; }
.notif-bell-badge {
  position: absolute; top: -3px; right: -3px; min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: 999px; background: var(--pf-danger); color: #fff; font-size: 0.66rem; font-weight: 700;
  line-height: 17px; text-align: center; border: 2px solid var(--pf-surface);
}
.notif-bell-badge[hidden] { display: none; }
.notif-bell-panel {
  position: absolute; top: calc(100% + 10px); right: 0; width: 340px; max-width: calc(100vw - 32px);
  max-height: 420px; display: flex; flex-direction: column; background: var(--pf-surface);
  border: 1px solid var(--pf-border-soft); border-radius: var(--pf-radius); box-shadow: var(--pf-shadow-lg);
  z-index: 60; overflow: hidden;
}
.notif-bell-panel[hidden] { display: none; }
.notif-bell-panel-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 12px 14px; border-bottom: 1px solid var(--pf-border-soft); font-weight: 700; font-size: 0.92rem;
}
.notif-bell-markall {
  background: none; border: none; padding: 0; cursor: pointer; color: var(--pf-accent);
  font-family: var(--pf-font); font-size: 0.8rem; font-weight: 600;
}
.notif-bell-list { overflow-y: auto; }
.notif-bell-item {
  display: block; width: 100%; text-align: left; background: none; border: none; cursor: pointer;
  padding: 11px 14px; border-bottom: 1px solid var(--pf-border-soft); font-family: var(--pf-font);
}
.notif-bell-item:last-child { border-bottom: none; }
.notif-bell-item:hover { background: var(--pf-surface-2); }
.notif-bell-item.unread { background: #f2f8ff; }
.notif-bell-item-title { display: flex; align-items: center; gap: 7px; font-size: 0.86rem; font-weight: 600; color: var(--pf-text); }
.notif-bell-item.unread .notif-bell-item-title::before {
  content: ""; flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--pf-accent);
}
.notif-bell-item-body { margin-top: 2px; font-size: 0.8rem; color: var(--pf-text-secondary); }
.notif-bell-item-time { margin-top: 4px; font-size: 0.72rem; color: var(--pf-text-secondary); }
.notif-bell-empty { padding: 26px 14px; text-align: center; font-size: 0.84rem; color: var(--pf-text-secondary); }
.notif-bell-empty[hidden] { display: none; }
/* The mobile sticky header — hamburger + client mark/name on one solid/
   blurred bar (see below) that stays fixed above the page regardless of
   scroll or drawer state. display:none by default (desktop); only shown at
   the mobile breakpoint below, where the sidebar becomes a drawer. The bar
   itself carries the fixed position now, not its children, so it reads as
   one header rather than two floating icons. Same translucent-blur recipe
   as .pf-header in platform.css. */
.dash-mobile-topbar {
  display: none; position: fixed; top: 0; left: 0; right: 0; z-index: 61;
  align-items: center; gap: 10px; padding: 14px 16px;
  background: rgba(251, 251, 253, 0.85);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  box-shadow: var(--pf-shadow-sm);
}
.dash-hamburger {
  display: none;
  flex: none; width: 40px; height: 40px; border-radius: var(--pf-radius-sm);
  border: none; background: transparent; color: var(--pf-text); cursor: pointer;
  align-items: center; justify-content: center;
}
.dash-hamburger .mi { width: 22px; height: 22px; }
.dash-backdrop { display: none; }
/* Mirrors the sidebar's own .dash-nav-brand (mark + name) — display:none by
   default (desktop, where the sidebar is always visible and already shows
   this); only shown at the mobile breakpoint below, inside .dash-mobile-topbar. */
.dash-mobile-brand { display: none; align-items: center; gap: 8px; min-width: 0; }
.dash-mobile-brand .dash-nav-client-mark-wrap { width: 32px; height: 32px; box-shadow: var(--pf-shadow-sm); }
.dash-mobile-brand .dash-nav-client-name { font-size: 0.95rem; }

/* Sits directly above .stats — changing it re-scopes the boxes below (pending
   requests, upcoming revenue), so it reads as "controls these" rather than
   being buried in the bookings-list toolbar further down the page. */
.stats-toolbar { display: flex; justify-content: flex-start; margin-bottom: 12px; }
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 30px; }
.stat { background: var(--pf-surface); border-radius: var(--pf-radius); padding: 18px 20px; box-shadow: var(--pf-shadow-sm); }
.stat .num { font-family: var(--pf-font); font-size: 2rem; font-weight: 600; color: var(--pf-text); line-height: 1; display: flex; align-items: center; gap: 8px; }
.stat .lbl { color: var(--pf-text-secondary); font-size: 0.85rem; margin-top: 6px; }
/* Pending requests need attention — a small amber bell next to the count. */
.stat-alert { display: inline-flex; color: var(--pf-warning); }
.stat-alert .mi { width: 20px; height: 20px; }
/* Only applied when guests actually arrive today (see renderStats) — a filled
   accent tile with a white number so the day's arrivals clearly stand out. */
.stat.accent { background: var(--pf-accent); }
.stat.accent .num { color: var(--pf-white); }
.stat.accent .lbl { color: rgba(255, 255, 255, 0.85); }

.toolbar { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-bottom: 18px; }
.tabs { display: flex; gap: 6px; background: var(--pf-surface); padding: 5px; border-radius: 999px; box-shadow: var(--pf-shadow-sm); flex-wrap: nowrap; }
.tab { border: none; background: none; padding: 8px 16px; border-radius: 999px; cursor: pointer; font-family: var(--pf-font); font-weight: 600; font-size: 0.9rem; color: var(--pf-text-secondary); white-space: nowrap; }
.tab.active { background: var(--pf-accent); color: var(--pf-white); }
/* Stays lit while any testimonial is pending, even on the active tab — same
   "still shows, just recolored for contrast" idea as .nav-item-badge. An
   actual SVG <circle> rather than a border-radius div — at this small a size
   border-radius rendering can come out visibly squarish rather than round,
   an SVG circle can't. */
.tab-dot { display: inline-block; width: 8px; height: 8px; margin-left: 6px; vertical-align: middle; flex: none; }
.tab-dot circle { fill: var(--pf-danger); }
.tab.active .tab-dot circle { fill: var(--pf-white); }
.tab-dot[hidden] { display: none; }
.search-box { flex: 1; min-width: 180px; }
/* Not wrapped in .field, so it doesn't inherit platform.css's field recipe
   automatically — mirrored here by hand (fill background, blue focus glow,
   no border), same shape (--pf-radius-sm) as every other field. */
.search-box input {
  width: 100%; padding: 10px 16px 10px 40px; border: none; border-radius: var(--pf-radius-sm);
  font-family: var(--pf-font); font-size: 0.95rem; background-color: var(--pf-surface); color: var(--pf-text);
  /* A search glyph on the left so this reads as a search bar, not just a blank
     white field. --pf-text-tertiary (#86868b) baked into the SVG stroke. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none'%3E%3Ccircle cx='11' cy='11' r='7' stroke='%2386868b' stroke-width='2'/%3E%3Cpath d='M21 21l-4.3-4.3' stroke='%2386868b' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: left 15px center;
  transition: background-color 0.15s ease, box-shadow 0.15s ease;
}
.search-box input:focus { outline: none; background-color: var(--pf-surface); box-shadow: 0 0 0 3.5px var(--pf-accent-soft); }
/* Date-range filter — a segmented control (Bookings toolbar above, and the
   Stats view below). The *selected* option needs to visually contrast
   against its unselected siblings — a fill-only pill group does that with
   a stronger fill instead of a border, matching platform.css's no-outline
   convention (see /portal-style-guide). */
.range-btns { display: flex; gap: 8px; flex-wrap: wrap; }
.range-btn {
  background: var(--pf-neutral-soft); color: var(--pf-text); border: none;
  font-family: var(--pf-font); font-weight: 600; font-size: 0.85rem;
  padding: 9px 16px; border-radius: var(--pf-radius-sm); cursor: pointer; white-space: nowrap;
  transition: background 0.15s, color 0.15s;
}
.range-btn:hover { background: var(--pf-border-soft); }
.range-btn.active { background: var(--pf-accent); color: var(--pf-white); }
.range-btn.active:hover { background: var(--pf-accent-hover); }
.range-btn:focus-visible { outline: 3px solid var(--pf-accent); outline-offset: 2px; }

/* ---- Booking table (desktop) / cards (mobile) ---- */
.bookings { display: grid; gap: 12px; }
.testimonials { display: grid; gap: 12px; }
/* Star ratings use a literal gold, not a --pf- token — filled stars read as
   gold/yellow everywhere on the web regardless of brand palette; tying them
   to the blue accent would look like a bug, not a rating. */
.t-stars { display: flex; gap: 2px; color: #f5a623; margin-bottom: 6px; }
.t-stars .mi { width: 16px; height: 16px; }
.t-stars .mi.off { color: var(--pf-border-soft); }
.testimonial .t-quote { font-size: 0.95rem; color: var(--pf-text); line-height: 1.5; }
/* Per-category scores the guest gave, shown under the quote as small chips. */
.t-cats { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.t-cat { display: inline-flex; align-items: center; gap: 4px; font-size: 0.76rem; color: var(--pf-text-secondary); background: var(--pf-neutral-soft); padding: 3px 9px; border-radius: 999px; }
.t-cat b { display: inline-flex; align-items: center; gap: 1px; color: var(--pf-text); font-weight: 700; }
.t-cat b .mi { width: 12px; height: 12px; }
.verified-chip { display: inline-flex; align-items: center; gap: 4px; margin-top: 8px; font-size: 0.74rem; font-weight: 700; color: var(--pf-success); background: var(--pf-success-soft); padding: 3px 9px; border-radius: 999px; }
.verified-chip .mi { width: 13px; height: 13px; }

/* Review QR entry point (Testimonials view) — the QR code itself lives in a
   modal (#qrModalOverlay) so it doesn't compete for space with the
   testimonials, which are the main point of this view. Sits in the same
   .toolbar row as the filter tabs (margin-left:auto pushes it to the far
   right) instead of its own row, so it doesn't strand whitespace above the
   tabs. Styled as a real CTA (not a quiet pill) since it's the way owners
   get set up to collect reviews in the first place. */
.qr-link-btn {
  display: inline-flex; align-items: center; gap: 9px; margin-left: auto;
  border: none; background: var(--pf-accent); color: var(--pf-white);
  font-family: var(--pf-font); font-weight: 700; font-size: 0.95rem;
  padding: 12px 20px; border-radius: 999px; cursor: pointer; transition: background 0.15s;
}
.qr-link-btn:hover { background: var(--pf-accent-hover); }
.qr-link-btn .mi { width: 19px; height: 19px; }

/* Reviews layout picker — sits in the same toolbar row as the QR CTA (its
   margin-left:auto pushes both to the right, the QR button following it), but
   is deliberately styled as a SETTING, not a second CTA: a muted label + a
   bordered dropdown on the surface color, so the two never read as twin
   buttons. This is the top-of-page "how are my reviews arranged" control. */
.review-layout-setting { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; }
.review-layout-setting label { font-size: 0.85rem; font-weight: 600; color: var(--pf-text-secondary); white-space: nowrap; }
.review-layout-setting select {
  font-family: var(--pf-font); font-size: 0.9rem; font-weight: 600; color: var(--pf-text);
  padding: 9px 12px; border: 1px solid var(--pf-border-soft); border-radius: 10px;
  background: var(--pf-surface); cursor: pointer;
}
.review-layout-setting select:hover { border-color: var(--pf-accent); }
.review-layout-setting select:focus-visible { outline: 2px solid var(--pf-accent); outline-offset: 1px; }
/* When the setting is present it owns the single margin-left:auto that pushes
   the right-hand group over; drop the QR button's own auto-margin so the two
   sit together (one toolbar gap apart) at the far right instead of splitting
   the free space between them. */
.review-layout-setting + .qr-link-btn { margin-left: 0; }

.qr-card { display: flex; gap: 24px; align-items: center; }
.qr-visual { flex: none; width: 132px; height: 132px; }
.qr-visual svg { width: 100%; height: 100%; display: block; }
/* "body.admin" prefix — same fix as .av-hint/.chart-card h3 above, needed to
   outrank platform.css's `body.pf p { margin: 0 }` reset. */
body.admin .qr-body p { color: var(--pf-text-secondary); font-size: 0.92rem; margin-bottom: 12px; }
.qr-url { display: inline-block; font-size: 0.82rem; color: var(--pf-text); background: var(--pf-bg); padding: 5px 10px; border-radius: var(--pf-radius-sm); margin-bottom: 14px; word-break: break-all; }
.qr-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.act-btn.ghost { background: var(--pf-neutral-soft); color: var(--pf-text); }
.act-btn.ghost:hover { background: var(--pf-border-soft); }

/* Owner onboarding — a spotlight ring around the current stop's real nav
   item plus a small tooltip anchored beside it (see #onboardSpotlightRing /
   #onboardTooltip in admin.html and the positioning math in admin.js). The
   real page underneath stays visible and interactive; the ring dims
   everything ELSE, not the page itself, so the owner is looking at the
   live feature while the tooltip explains it. */
#onboardSpotlightRing {
  position: fixed; z-index: 998; border-radius: var(--pf-radius-sm);
  box-shadow: 0 0 0 4px var(--pf-accent), 0 0 0 2000px rgba(20, 20, 26, 0.55);
  pointer-events: none;
  transition: top 0.25s ease, left 0.25s ease, width 0.25s ease, height 0.25s ease;
}
#onboardSpotlightRing[hidden] { display: none; }
#onboardTooltip {
  position: fixed; z-index: 999; width: 340px; max-width: calc(100vw - 32px);
  background: var(--pf-surface); border-radius: var(--pf-radius-lg); box-shadow: var(--pf-shadow-lg);
  animation: pf-pop 0.2s ease;
}
#onboardTooltip[hidden] { display: none; }
/* Fallback for a stop whose nav item can't be found (a gating edge case, or
   a narrow/mobile layout with the drawer closed) — no ring to anchor to, so
   just center it like the old modal did rather than pointing at nothing. */
#onboardTooltip.onboard-tooltip-centered { top: 50% !important; left: 50% !important; transform: translate(-50%, -50%); }
.onboard-tooltip-head { display: flex; align-items: center; gap: 12px; padding: 20px 20px 0; }
.onboard-tooltip-icon { display: inline-flex; flex: none; width: 36px; height: 36px; align-items: center; justify-content: center; border-radius: 999px; background: var(--pf-accent-soft); color: var(--pf-accent); }
.onboard-tooltip-icon .mi { width: 20px; height: 20px; }
.onboard-tooltip-head h3 { flex: 1; min-width: 0; font-size: 1.05rem; }
.onboard-tooltip-head .modal-close { flex: none; width: 28px; height: 28px; }
body.admin .onboard-tooltip-body { padding: 12px 20px 4px; color: var(--pf-text); font-size: 0.92rem; line-height: 1.5; }
.onboard-foot { padding: 14px 20px 20px; display: flex; flex-direction: column; gap: 12px; }
.onboard-dots { display: flex; justify-content: center; gap: 6px; }
.onboard-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--pf-border-soft); }
.onboard-dot.active { background: var(--pf-accent); width: 16px; border-radius: 3px; transition: width 0.15s, background 0.15s; }
.onboard-actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.onboard-nav-btns { display: flex; gap: 8px; }

@media (max-width: 560px) {
  .qr-card { flex-direction: column; align-items: flex-start; gap: 16px; }
  /* Keep "Print QR poster" / "Copy review link" side by side rather than
     stacked — each takes an equal half of the row, with tighter padding/type
     and wrapping allowed within a button (overriding .btn's white-space:nowrap)
     so a two-line label never forces the row itself to wrap. */
  .qr-actions { flex-wrap: nowrap; width: 100%; }
  .qr-actions .btn { flex: 1 1 0; min-width: 0; padding: 12px 10px; font-size: 0.86rem; white-space: normal; text-align: center; }
}
.booking {
  background: var(--pf-surface); border-radius: var(--pf-radius);
  box-shadow: var(--pf-shadow-sm);
  padding: 18px 20px;
  display: flex; flex-direction: column; gap: 12px; position: relative;
}
.b-main { display: grid; grid-template-columns: 1.4fr 1.6fr 1fr auto; gap: 16px; align-items: start; }
/* Guest name is the card title; the reservation number sits quietly beneath it. */
.booking .b-guest { font-family: var(--pf-font); font-weight: 600; font-size: 24px; line-height: 1.25; color: var(--pf-text); }
.booking .b-ref { font-size: 0.85rem; color: var(--pf-text-secondary); margin-top: 2px; }
.booking .b-room { font-weight: 600; font-size: 16px; }
.booking .b-meta { font-size: 0.85rem; color: var(--pf-text-secondary); margin-top: 2px; }
.booking .b-dates { font-size: 0.92rem; }
/* Stay length — its own quiet section under the room info, dates stacked
   below the nights count rather than run together on one line. */
.booking .b-stay { margin-top: 10px; }
.booking .b-stay-nights { font-weight: 700; font-size: 0.9rem; color: var(--pf-text); }
.booking .b-stay-dates { font-size: 0.85rem; color: var(--pf-text-secondary); margin-top: 2px; }
.booking .b-total { font-weight: 700; font-size: 16px; color: var(--pf-text); }
.booking .b-actions { display: flex; flex-direction: column; gap: 10px; align-items: flex-end; }

/* Status badge */
.status { display: inline-flex; align-items: center; padding: 6px 13px; border-radius: 999px; font-size: 0.82rem; font-weight: 700; }
.status.pending { background: var(--pf-warning-soft); color: var(--pf-warning); }
.status.confirmed { background: var(--pf-success-soft); color: var(--pf-success); }
.status.cancelled { background: var(--pf-danger-soft); color: var(--pf-danger); }

/* Action buttons */
.action-btns { display: flex; gap: 8px; }
.act-btn { border: none; padding: 9px 16px; border-radius: 999px; font-size: 0.85rem; font-weight: 700; cursor: pointer; font-family: var(--pf-font); white-space: nowrap; transition: background 0.15s; }
.act-btn.confirm { background: var(--pf-accent); color: var(--pf-white); }
.act-btn.confirm:hover { background: var(--pf-accent-hover); }
.act-btn.cancel { background: transparent; color: var(--pf-danger); }
.act-btn.cancel:hover { background: var(--pf-danger-soft); }
/* Amber ties Restore back to the "Pending" status it returns the booking to,
   instead of the same neutral fill every other secondary action uses. */
.act-btn.restore { background: var(--pf-warning-soft); color: var(--pf-warning); }
.act-btn.restore:hover { background: var(--pf-warning); color: var(--pf-white); }

/* Payment + contact lines */
.b-pay { display: inline-flex; align-items: center; gap: 5px; font-size: 0.78rem; font-weight: 600; margin-top: 4px; }
/* Paid bookings get a filled badge — the owner shouldn't have to read the
   label to see money is already in hand. Unpaid stays quiet, plain text. */
.b-pay.paid { color: var(--pf-success); background: var(--pf-success-soft); padding: 5px 12px; border-radius: 999px; font-weight: 700; }
.b-pay.unpaid { color: var(--pf-text-secondary); }
.b-contact { font-size: 0.82rem; color: var(--pf-text-secondary); margin-top: 2px; }
.b-contact a { color: var(--pf-accent); text-decoration: underline; }

/* Note / reschedule lines — quiet inline text, not filled boxes */
.b-note-box { display: flex; gap: 8px; align-items: flex-start; font-size: 0.86rem; padding: 2px 0; line-height: 1.45; color: var(--pf-text-secondary); }
.b-note-box .b-note-ico { flex: none; opacity: 0.7; }
.b-note-box strong { color: var(--pf-text); font-weight: 600; }
.b-note-box.note { margin-top: 10px; }
.b-note-box.resched { color: var(--pf-warning); }
.b-note-box.resched strong { color: var(--pf-warning); }

/* Cancelled bookings — muted, not alarming */
.booking.cancelled .b-dates, .booking.cancelled .b-stay, .booking.cancelled .b-total { text-decoration: line-through; opacity: 0.6; }
.booking.cancelled .b-main { opacity: 0.85; }

/* Dashboard footer (log out) */
.dash-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 32px; padding-top: 20px; color: var(--pf-text-secondary); font-size: 0.9rem; flex-wrap: wrap; }

.empty { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; min-height: 360px; padding: 60px 20px; color: var(--pf-text-secondary); background: var(--pf-surface); border-radius: var(--pf-radius); box-shadow: var(--pf-shadow-sm); }
.empty svg { margin-bottom: 14px; opacity: 0.5; }
.empty .mi { width: 46px; height: 46px; }
.empty p { margin: 0; }
.empty .btn { margin-top: 14px; }

@media (max-width: 860px) {
  /* 24px gutters — .wrap's default 20px felt tight once cards go edge to edge. */
  body.admin .wrap { padding-left: 24px; padding-right: 24px; }

  /* The section nav becomes an off-canvas drawer opened by the hamburger.
     padding-top clears .dash-mobile-topbar (which sits above it, z-index 61
     vs. this drawer's 60) so its first nav item isn't hidden underneath. */
  .dash-nav {
    position: fixed; top: 0; left: 0; height: 100%; width: 268px; z-index: 60;
    transform: translateX(-100%); transition: transform 0.22s ease;
    padding-top: 82px;
  }
  .dash-nav.open { transform: none; }
  .dash-backdrop {
    display: block; position: fixed; inset: 0; z-index: 55;
    background: rgba(0, 0, 0, 0.42); opacity: 0; pointer-events: none;
    transition: opacity 0.22s ease;
  }
  .dash-backdrop.open { opacity: 1; pointer-events: auto; }
  .dash-mobile-topbar { display: flex; }
  .dash-hamburger { display: inline-flex; }
  .dash-mobile-brand { display: flex; }
  /* The sidebar's own brand row is redundant once .dash-mobile-topbar is
     showing the same mark + name next to the hamburger — without this the
     two would sit on top of each other when the drawer opens (the topbar
     stays above the drawer via z-index). */
  .dash-nav-brand { display: none; }
  /* Extra top padding (was 68px) — the fixed hamburger + brand row (top:14px,
     40px tall) only clears the page's own h1 by 14px at 68px, which read as
     cramped; this gives the heading real breathing room below it. */
  .dash-main { padding: 96px 24px 48px; }
  .dash-date { display: none; }

  .stats { grid-template-columns: repeat(2, 1fr); }
  /* Squeezed side-by-side with the tabs, the search box shrinks too far to
     show its placeholder — stack both full-width instead, same as mobile. */
  .tabs { width: 100%; gap: 4px; }
  .tab { flex: 1; padding: 8px 9px; text-align: center; }
  .search-box { width: 100%; }
  .qr-link-btn { width: 100%; justify-content: center; margin-left: 0; }
  .review-layout-setting { width: 100%; margin-left: 0; justify-content: space-between; }
  .review-layout-setting select { flex: 1; margin-left: 10px; }
  .range-btns { width: 100%; }
  .range-btn { flex: 1; text-align: center; padding-left: 10px; padding-right: 10px; }
  /* The three info blocks (guest, room, price) stack directly on top of one
     another at this width — a bigger gap keeps them readable as sections
     instead of running together. */
  .b-main { grid-template-columns: 1fr; gap: 22px; }
  /* Match the room→nights gap to the grid's own row gap, so the space before
     and after the nights section reads as equal to the section after it. */
  .booking .b-stay { margin-top: 22px; }
  /* Pending/Confirmed/Cancelled reads as this card's headline state, so pin
     it to the same top-right corner it occupies on desktop instead of
     letting it fall to the bottom of the stack. */
  .booking .b-actions { position: absolute; top: 18px; right: 20px; width: auto; }
  /* Room for the corner badge so the reference/guest lines don't run under it. */
  .b-main > div:first-child { padding-right: 96px; }
}
@media (max-width: 480px) {
  .stats { grid-template-columns: 1fr 1fr; gap: 10px; }
  .stat { padding: 14px; }
  .stat .num { font-size: 1.6rem; }
  /* Stack the footer so the message pill gets its own row, with even 12px
     spacing between the pill and the actions below it instead of a divider
     line. The .booking prefix outranks the base .b-footer rule (defined
     earlier in the file) so these actually win. */
  .booking .b-footer { flex-direction: column; align-items: stretch; gap: 0; margin-top: 0; padding-top: 0; }
  /* margin-bottom matches the card's own bottom padding (18px), so the space
     above Confirm/Decline equals the space below them. */
  .booking .b-footer .msg-status { align-self: stretch; justify-content: center; margin-bottom: 18px; }
  .booking .b-footer .action-btns { margin-top: 0; padding-top: 0; width: 100%; }
  /* Confirm/Decline share the full row evenly instead of shrink-wrapping. */
  .booking .b-footer .act-btn { flex: 1; text-align: center; }
}


/* ---- Card footer: message pill (left) + booking actions (right) ---- */
.b-footer {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; margin-top: 4px; padding-top: 12px;
}
/* Testimonial cards only ever put one thing in .b-footer (the action
   buttons) — space-between has nothing to push against, so it collapses to
   flex-start. flex-end moves it to the right instead. */
.testimonial .b-footer { justify-content: flex-end; }

.msg-status {
  display: inline-flex; align-items: center; gap: 7px;
  border: none; cursor: pointer; font-family: var(--pf-font);
  font-weight: 600; font-size: 0.82rem;
  padding: 6px 13px; border-radius: 999px;
  background: var(--pf-neutral-soft); color: var(--pf-text);
  transition: background 0.15s, color 0.15s;
}
.msg-status:hover { background: var(--pf-border-soft); }
.msg-status .mi { width: 16px; height: 16px; }
/* Unread — the guest sent the last message and is waiting on a reply. */
.msg-status.awaiting { background: var(--pf-danger-soft); color: var(--pf-danger); }

/* ---- Post-stay review follow-up (replaces the message pill once a guest
   has checked out) ---- */
.review-followup { flex: 1; display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; min-width: 0; }
.rf-steps { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rf-step { display: inline-flex; align-items: center; gap: 6px; font-size: 0.82rem; font-weight: 600; color: var(--pf-text-secondary); }
.rf-step i { width: 8px; height: 8px; border-radius: 50%; background: var(--pf-border-soft); flex: none; }
.rf-step.done { color: var(--pf-text); }
.rf-step.done i { background: var(--pf-accent); }
.rf-step.rated.done { color: var(--pf-warning); }
.rf-step.rated.done i { background: var(--pf-warning); }
.rf-sep { color: var(--pf-border-soft); }
.rf-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* ---- Conversation modal ---- */
.msg-modal-body { display: flex; flex-direction: column; }
.msg-modal-body .res-thread {
  flex: 1; min-height: 220px; max-height: min(50vh, 440px);
}
/* Quiet chrome: the header blends into the sheet and the composer floats
   free (no divider line above it) — matches platform.css's own modal-head. */
#msgModalOverlay .modal-head { padding: 20px 24px 6px; }
.msg-modal-body .res-composer { padding-top: 0; }
.res-panel { margin-top: 0; }
.res-thread {
  display: flex; flex-direction: column; gap: 10px;
  max-height: 320px; overflow-y: auto;
  padding: 4px 4px 8px;
  margin-bottom: 12px;
}
.res-thread-empty { color: var(--pf-text-secondary); font-size: 0.88rem; padding: 6px 0; }
.res-msg-bubble { max-width: 78%; padding: 9px 13px; border-radius: var(--pf-radius); background: var(--pf-neutral-soft); }
.res-msg-bubble.mine { align-self: flex-end; background: var(--pf-accent-soft); }
.res-msg-bubble.theirs { align-self: flex-start; background: var(--pf-neutral-soft); }
.res-msg-meta { font-size: 0.72rem; font-weight: 600; color: var(--pf-text-secondary); margin-bottom: 3px; text-transform: uppercase; letter-spacing: 0.03em; }
.res-msg-text { font-size: 0.92rem; color: var(--pf-text); overflow-wrap: anywhere; }
.res-msg-text a { color: var(--pf-accent); text-decoration: underline; }
.res-msg-img { display: block; max-width: 100%; border-radius: var(--pf-radius-sm); margin-top: 6px; }
/* Several photos on one message sit side by side as thumbnails, instead of
   stacking as separate full-width images. */
.res-msg-images { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.res-msg-images .res-msg-img { width: 96px; height: 96px; object-fit: cover; margin-top: 0; flex: none; }

.res-composer { padding-top: 12px; }
.res-composer textarea {
  width: 100%; resize: vertical; font-family: var(--pf-font); font-size: max(16px, 0.95rem);
  padding: 10px 14px; border: none; border-radius: var(--pf-radius-sm);
  background-color: var(--pf-bg); color: var(--pf-text); caret-color: var(--pf-accent);
  margin-bottom: 10px;
  transition: background-color 0.15s ease, box-shadow 0.15s ease;
}
.res-composer textarea:focus {
  outline: none;
  background-color: var(--pf-surface);
  box-shadow: 0 0 0 3.5px var(--pf-accent-soft);
}
.res-composer-row { display: flex; align-items: center; gap: 10px; }
/* Send is the primary action in the composer — solid accent blue, not the
   neutral fill .btn-outline it shares markup with elsewhere. */
.res-composer-row .btn.btn-outline { background: var(--pf-accent); color: var(--pf-white); }
.res-composer-row .btn.btn-outline:hover { background: var(--pf-accent-hover); }
.res-attach-btn {
  flex: none; display: flex; align-items: center; justify-content: center;
  width: 42px; height: 42px; border-radius: 50%; background: var(--pf-neutral-soft);
  cursor: pointer; font-size: 1.1rem; transition: background 0.15s;
}
.res-attach-btn:hover { background: var(--pf-border-soft); }
.res-attach-preview { flex: 1; display: flex; flex-wrap: wrap; gap: 6px; }
.res-attach-chip { display: inline-flex; align-items: center; gap: 6px; background: var(--pf-neutral-soft); border-radius: var(--pf-radius-sm); padding: 4px; }
.res-attach-chip img { width: 32px; height: 32px; object-fit: cover; border-radius: 6px; }
.res-attach-chip button { border: none; background: none; cursor: pointer; color: var(--pf-text-secondary); font-size: 0.9rem; padding: 4px; }
.res-attach-chip button:hover { color: var(--pf-danger); }
.res-composer-row .btn { margin-left: auto; }

/* ======================================================================
   Availability view — Bookings/Availability switch, calendar & editor
   ===================================================================== */

/* Calendar toolbar */
/* Availability month nav is centered (the month label reads as the calendar's
   title) — a plain flex row with nothing else in it centers for free. Team
   Calendar reuses this same bar but adds a "+ New event" button; naively
   centering a flex row with an extra element in it just centers the pair
   together, dragging the month label off-center. .tc-bar below switches to a
   3-column grid instead — center column truly centered on the bar's own
   width, button pinned to the right column — so the month label lands in
   the exact same spot both views. */
.av-bar { display: flex; align-items: center; justify-content: center; gap: 16px; flex-wrap: wrap; margin-bottom: 8px; }
.av-monthnav { display: flex; align-items: center; gap: 12px; }
.av-monthnav strong { font-family: var(--pf-font); font-size: 1.2rem; min-width: 148px; text-align: center; }
.av-nav { width: 36px; height: 36px; border-radius: 999px; border: none; background: var(--pf-neutral-soft); cursor: pointer; font-size: 1.1rem; color: var(--pf-text); }
.av-nav:hover { background: var(--pf-border-soft); }
/* Always the first line right under the "Owner Portal" h1 (Availability/Reviews
   both open with one) — pulled up tight under the heading it explains, and
   pushed well clear of the tabs/toolbar below so it doesn't read as part of
   that row. Negative margin-top eats into .dash-head's 22px margin-bottom
   (they collapse: 22 + -14 = 8px gap to the heading).
   Selector needs the "body.admin" prefix to outrank platform.css's global
   `body.pf p { margin: 0 }` reset — a bare `.av-hint` has lower specificity
   and silently loses the margin properties to it. */
body.admin .av-hint { color: var(--pf-text-secondary); font-size: 0.9rem; margin-top: -14px; margin-bottom: 24px; }

/* Agency Dashboard, embedded — platform.css's .pf-wrap/.pf-main were sized
   for the old standalone /platform page (full browser width, no sidebar),
   capping content at 1160px and adding their own 40/64px vertical padding.
   Nested inside .dash-main now, that cap left a large unused gray margin on
   wide screens (dash-main has no cap of its own — every other owner-portal
   view already fills it edge to edge) and doubled up on padding dash-main
   already provides. Let it fill dash-main exactly like every other view. */
body.admin .pf-wrap { max-width: none; margin: 0; padding: 0; }
body.admin .pf-main { padding: 0; }

/* Calendar grid */
.av-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 8px; }
.av-dows { margin-bottom: 6px; }
.av-dow { text-align: center; font-size: 0.75rem; font-weight: 600; color: var(--pf-text-secondary); text-transform: uppercase; letter-spacing: 0.04em; }
/* State reads from fill color alone, never a border — see /portal-style-guide's
   Shape & elevation rule. */
/* min-width:0 overrides the grid item's default min-width:auto — without it,
   an unbreakable word in .av-state (e.g. "Closed", vs. the wrappable "9/9
   open") forces its whole column's 1fr track wider than the rest, pushing
   later columns past the container edge on narrow screens. */
.av-day { position: relative; min-width: 0; display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-start; gap: 4px; padding: 8px 10px; border-radius: var(--pf-radius); border: none; background: var(--pf-surface); cursor: pointer; font-family: var(--pf-font); text-align: left; transition: transform 0.1s ease, box-shadow 0.1s ease; }
/* >=561px (complements the max-width:560px mobile block below): the day grid
   fills whatever viewport height remains below the fixed chrome above it, so
   the full month is visible with no page scroll on common desktop heights —
   same calc(100vh - Npx) idiom as .login-wrap in platform.css. */
@media (min-width: 561px) {
  .av-days {
    grid-template-rows: repeat(6, 1fr);
    row-gap: 6px;
    height: clamp(300px, calc(100vh - 260px), 660px);
    height: clamp(300px, calc(100dvh - 260px), 660px);
    overflow-y: auto;
  }
  .av-day { height: 100%; }
}
.av-day:hover:not(:disabled) { transform: translateY(-2px); box-shadow: var(--pf-shadow-sm); }
.av-day:disabled { cursor: default; }
.av-day.outside { visibility: hidden; }
.av-day.past { background: var(--pf-neutral-soft); opacity: 0.5; }
.av-num { font-family: var(--pf-font); font-size: 1.1rem; font-weight: 600; color: var(--pf-text); }
.av-state { font-size: 0.72rem; font-weight: 600; color: var(--pf-success); }
.av-day.full { background: var(--pf-border-soft); }
.av-day.full .av-state { color: var(--pf-text-secondary); }
.av-day.closed { background: var(--pf-danger-soft); }
.av-day.closed .av-state { color: var(--pf-danger); }
/* Closed days show a compact ✕ rather than the word "Closed", which was too
   wide for a narrow day cell (the label still reads "Closed" to screen readers
   via the cell's aria-label). Inherits the danger colour from the rule above. */
.av-closed-x { display: inline-flex; }
.av-closed-x .mi { width: 20px; height: 20px; }
.av-day.restricted { background: var(--pf-warning-soft); }
.av-day.restricted .av-state { color: var(--pf-warning); }
.av-hours-dot { position: absolute; top: 6px; right: 6px; color: var(--pf-accent); display: inline-flex; }
.av-hours-dot .mi { width: 15px; height: 15px; }
.av-empty { text-align: center; padding: 40px; color: var(--pf-text-secondary); }

/* Day editor modal */
.av-editor .av-toggle { display: flex; align-items: flex-start; gap: 12px; padding: 14px 16px; border-radius: var(--pf-radius); background: var(--pf-neutral-soft); cursor: pointer; margin-bottom: 20px; }
.av-editor .av-toggle input { margin-top: 3px; width: 18px; height: 18px; flex: none; accent-color: var(--pf-danger); cursor: pointer; }
.av-editor .av-toggle strong { display: block; color: var(--pf-text); }
.av-editor .av-toggle small { color: var(--pf-text-secondary); font-size: 0.85rem; }
.av-section { margin-bottom: 20px; }
/* "body.admin" prefix — same fix as .av-hint/.chart-card h3 above. */
body.admin .av-section h4 { font-size: 1rem; margin-bottom: 4px; }
.av-section-sub { color: var(--pf-text-secondary); font-size: 0.85rem; margin-bottom: 12px; }
.av-optional { color: var(--pf-text-secondary); font-weight: 400; font-size: 0.82rem; }
.av-section.disabled { opacity: 0.45; pointer-events: none; }
/* 32px = "between component groups" — each room type is its own group, so
   it needs that gap, not the 16px default. */
.av-rooms { display: grid; gap: 32px; }
/* Empty state when this client has no bookable units at all yet — spans the
   full width of the modal (not the tall, page-level .empty treatment, which
   would dominate a modal that has more sections below it), with its own
   left/right padding so the message/button never touch the modal edges. */
.av-rooms-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; padding: 28px 20px; background: var(--pf-neutral-soft);
  border-radius: var(--pf-radius); color: var(--pf-text-secondary);
}
.av-rooms-empty .mi { width: 32px; height: 32px; opacity: 0.5; margin-bottom: 10px; }
.av-rooms-empty p { margin: 0 0 14px; font-size: 0.92rem; }
.av-room-group-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
.av-room-group-name { font-weight: 600; font-size: 0.9rem; color: var(--pf-text); }
.av-group-btn { border: none; background: var(--pf-border-soft); color: var(--pf-text); font-family: var(--pf-font); font-weight: 600; font-size: 0.78rem; padding: 5px 12px; border-radius: 999px; cursor: pointer; }
.av-group-btn:hover { background: var(--pf-warning-soft); color: var(--pf-warning); }
.av-group-btn:disabled { opacity: 0.4; cursor: default; }
.av-room-units { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 8px; }
.av-room { display: flex; flex-direction: column; gap: 8px; padding: 10px 14px; border-radius: var(--pf-radius); background: var(--pf-neutral-soft); cursor: pointer; font-size: 0.92rem; position: relative; }
.av-room.booked { opacity: 0.6; cursor: default; }
/* The real checkbox stays in the DOM (focusable, keyboard/SR-operable) but is
   visually replaced by the switch below it — standard "sr-only" hiding, not
   display:none, so Tab and Space still reach it. */
.av-room-input {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.av-room-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.av-room-name { font-weight: 600; color: var(--pf-text); }
/* Toggle switch — a styled sibling of the checkbox, flipped via the sibling
   combinator so clicking/keying the (hidden) input drives it with no JS.
   Checked here means "taken offline" (a warning action, not the usual
   positive "on"), so the checked track stays amber rather than the
   platform-wide blue-for-checked used elsewhere (e.g. the Free subscription
   toggle in platform.css). */
.av-toggle-switch { flex: none; width: 42px; height: 24px; border-radius: 999px; background: #e9e9eb; position: relative; transition: background 0.15s ease; }
.av-toggle-knob { position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 50%; background: var(--pf-white); transition: transform 0.15s ease; }
.av-room-input:checked ~ .av-room-top .av-toggle-switch { background: var(--pf-warning); }
.av-room-input:checked ~ .av-room-top .av-toggle-knob { transform: translateX(18px); }
.av-room-input:focus-visible ~ .av-room-top .av-toggle-switch { outline: 3px solid var(--pf-accent); outline-offset: 2px; }
.av-room.booked .av-toggle-switch { background: var(--pf-border-soft); }
/* Status pill — exactly one of the three is shown at a time, chosen purely by
   the checkbox's checked/disabled state (live, no re-render needed), so the
   room's current status is never ambiguous the way a bare checkbox was. */
.av-room-status { display: none; align-self: flex-start; font-style: normal; font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; padding: 2px 8px; border-radius: 999px; }
.av-room-status.status-available { color: var(--pf-text-secondary); background: var(--pf-border-soft); }
.av-room-status.status-offline { color: var(--pf-warning); background: var(--pf-warning-soft); }
.av-room-status.status-booked { color: var(--pf-success); background: var(--pf-success-soft); }
.av-room:not(.booked) .status-available { display: inline-flex; }
.av-room:has(.av-room-input:checked):not(.booked) .status-available { display: none; }
.av-room:has(.av-room-input:checked):not(.booked) .status-offline { display: inline-flex; }
.av-room.booked .status-booked { display: inline-flex; }
.av-room:has(.av-room-input:checked):not(.booked) { background: var(--pf-warning-soft); }
.av-time-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.av-editor input[type="text"], .av-editor input[type="time"], .av-editor input[type="number"] { width: 100%; max-width: 100%; min-width: 0; box-sizing: border-box; padding: 11px 14px; border: none; border-radius: var(--pf-radius-sm); background: var(--pf-bg); font-family: var(--pf-font); font-size: 0.95rem; color: var(--pf-text); }
/* The iOS date/time appearance reset that keeps these fields inside the card now
   lives globally in platform.css (body.pf .field input, scoped to iOS), so it
   applies to every modal — not just this one. */
.av-editor input:focus-visible { outline: 3px solid var(--pf-accent); outline-offset: 2px; }
.av-msg { display: none; padding: 10px 14px; border-radius: var(--pf-radius-sm); font-size: 0.9rem; margin-bottom: 14px; }
.av-msg.err { display: block; background: var(--pf-danger-soft); color: var(--pf-danger); }
.av-actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
/* Reset day is a low-stakes, infrequent escape hatch — a plain text link reads
   as secondary next to Save's solid button, instead of competing with it. */
.av-link-btn { background: none; border: none; padding: 0; cursor: pointer; font-family: var(--pf-font); font-size: 0.9rem; font-weight: 600; color: var(--pf-text-secondary); text-decoration: underline; text-underline-offset: 2px; }
.av-link-btn:hover { color: var(--pf-danger); }

/* ---- Photo dropzone (Add/Edit room & product modals) — a standard
   drag-and-drop upload well: dashed border, centered icon + copy, a
   highlighted state while a file's dragged over it, and a thumbnail grid
   below once photos are added. See makePhotoUploader() in admin.js. */
.photo-dropzone {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  text-align: center; padding: 28px 20px; border: 1.5px dashed var(--pf-border-soft);
  border-radius: var(--pf-radius); background: var(--pf-bg); cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}
.photo-dropzone:hover, .photo-dropzone:focus-visible { border-color: var(--pf-accent); background: var(--pf-accent-soft); }
.photo-dropzone:focus-visible { outline: none; }
.photo-dropzone.dragover { border-style: solid; border-color: var(--pf-accent); background: var(--pf-accent-soft); }
.photo-dropzone-ico { width: 26px; height: 26px; color: var(--pf-text-tertiary); }
.photo-dropzone:hover .photo-dropzone-ico, .photo-dropzone.dragover .photo-dropzone-ico { color: var(--pf-accent); }
.photo-dropzone strong { font-size: 0.9rem; font-weight: 600; color: var(--pf-text); }
.photo-dropzone span { font-size: 0.78rem; color: var(--pf-text-secondary); }

.photo-thumbs { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 12px; }
.photo-thumb { position: relative; width: 68px; height: 68px; border-radius: var(--pf-radius-sm); overflow: hidden; box-shadow: var(--pf-shadow-sm); }
.photo-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.photo-remove {
  position: absolute; top: 3px; right: 3px; width: 20px; height: 20px; border-radius: 50%;
  border: none; background: rgba(0, 0, 0, 0.6); color: #fff; font-size: 0.8rem; line-height: 1;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.photo-remove:hover { background: var(--pf-danger); }
.photo-count-note { margin: 8px 0 0; font-size: 0.78rem; color: var(--pf-text-secondary); }

/* Room pricing — list in the Dashboard + its own edit modal */
.av-pricing { margin-top: 32px; }
/* "body.admin" prefix — same fix as .av-hint/.chart-card h3 above. */
body.admin .av-pricing h4 { font-size: 1rem; margin-bottom: 4px; }
/* Title+copy on the left, "+ Add room" on the right — the 28px bottom margin
   is the gap the copy needed before the list below it. */
.av-pricing-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 28px; }
.av-pricing-list { display: grid; gap: 10px; }
.av-price-row { display: flex; align-items: center; gap: 14px; padding: 18px 18px; border-radius: var(--pf-radius); background: var(--pf-surface); box-shadow: var(--pf-shadow-sm); }
/* The room/table/service/product's own first photo — same rounded-square
   treatment as the sidebar's client-logo mark, just bigger since this is the
   list's own thumbnail rather than a small nav mark. A light border keeps a
   light-colored photo (or the empty tile) from just melting into the white
   row behind it. A plain empty tile (no icon) when nothing's been uploaded
   yet, so every row still lines up whether or not it has a photo. */
.av-price-thumb { width: 56px; height: 56px; flex: none; border-radius: var(--pf-radius-sm); border: 1px solid var(--pf-border-soft); object-fit: cover; background: var(--pf-neutral-soft); }
/* The empty tile is itself a button — hovering it (or tapping it on a
   touchscreen, where there's no hover to reveal anything first) opens the
   editor straight on its photo dropzone. The camera icon stays invisible
   until hover/focus so the row still reads as a plain placeholder at rest,
   matching the filled-thumbnail rows around it. */
.av-price-thumb-empty {
  display: flex; align-items: center; justify-content: center;
  padding: 0; background: var(--pf-neutral-soft); color: var(--pf-text-secondary); cursor: pointer;
}
.av-price-thumb-ico { width: 22px; height: 22px; opacity: 0; transition: opacity 0.15s ease; }
.av-price-thumb-empty:hover, .av-price-thumb-empty:focus-visible { border-color: var(--pf-accent); color: var(--pf-accent); }
.av-price-thumb-empty:hover .av-price-thumb-ico, .av-price-thumb-empty:focus-visible .av-price-thumb-ico { opacity: 1; }
/* Tighter than the base .empty's own padding — this one sits inside an
   already-padded panel, not taking up the full page on its own. */
.av-pricing-list .empty { padding: 32px 16px; }
.av-price-name { font-weight: 600; color: var(--pf-text); flex: 1; min-width: 0; }
/* Pronounced on purpose — this is the number an owner scans the list for,
   not a secondary detail, so it gets full text color, weight and a size step
   up from the room name rather than reading as muted metadata. Shares the
   row's flexible space evenly with the name (flex: 1 on both) and centers
   itself within that half, so it lands roughly in the middle of the row —
   between the name and the action buttons — instead of hugging the buttons. */
.av-price-summary { flex: 1; text-align: center; color: var(--pf-text); font-size: 1.05rem; font-weight: 700; }
.av-price-modes { display: grid; gap: 10px; margin-bottom: 20px; }
.av-price-mode { display: flex; align-items: flex-start; gap: 12px; padding: 14px 16px; border-radius: var(--pf-radius); background: var(--pf-neutral-soft); cursor: pointer; }
.av-price-mode input { margin-top: 3px; width: 18px; height: 18px; flex: none; accent-color: var(--pf-accent); cursor: pointer; }
.av-price-mode strong { display: block; color: var(--pf-text); }
.av-price-mode small { color: var(--pf-text-secondary); font-size: 0.85rem; }
.av-price-days { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

@media (max-width: 560px) {
  .av-price-row { flex-wrap: wrap; }
  .av-price-days { grid-template-columns: 1fr; }
}

/* Dashboard's add-on grid — one card per catalog entry (platform.js
   AVAILABLE_FEATURES), "body.admin" prefix on the h4 for the same reason as
   .av-pricing h4 above. */
.addon-section { margin-top: 32px; }
body.admin .addon-section h4 { font-size: 1rem; margin-bottom: 4px; }
.addon-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 10px; margin-top: 14px; }
/* Deliberately quieter than the marketing site's own pricing cards (same
   catalog data, see AVAILABLE_FEATURES in platform.js) — this grid is a
   glance-and-move-on "what's on" reference an owner sees every time they
   land on the dashboard, not a sales pitch, so it stays compact rather than
   full hero-priced cards. */
.addon-card {
  display: flex; flex-direction: column; gap: 6px;
  padding: 12px 14px; border-radius: var(--pf-radius); background: var(--pf-surface); box-shadow: var(--pf-shadow-sm);
  border: 1px solid transparent;
}
/* Active add-ons get a quiet accent-tinted border so the grid reads at a
   glance as "these are on" vs. "these are upsells" without relying on the
   badge text alone. */
.addon-card.on { border-color: var(--pf-accent-soft); }
.addon-card-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.addon-name { font-weight: 600; color: var(--pf-text); font-size: 0.92rem; }
.addon-badge { flex: none; font-size: 0.7rem; font-weight: 700; padding: 2px 9px; border-radius: 999px; }
.addon-badge.on { background: var(--pf-success-soft); color: var(--pf-success); }
.addon-badge.off { background: var(--pf-neutral-soft); color: var(--pf-text-secondary); }
/* Clamped to 2 lines regardless of the underlying copy length — keeps every
   card the same compact height instead of the tallest description setting
   it for the whole row. */
.addon-desc {
  color: var(--pf-text-secondary); font-size: 0.82rem; line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.addon-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 2px; }
.addon-foot .btn { flex: none; padding: 6px 14px; font-size: 0.85rem; }
.addon-price { font-family: var(--pf-font); font-weight: 700; color: var(--pf-text); font-size: 1.05rem; letter-spacing: -0.01em; }

@media (max-width: 560px) {
  .av-grid { gap: 6px; }
  .av-day { min-height: 66px; padding: 8px 8px; border-radius: var(--pf-radius-sm); }
  .av-num { font-size: 1.05rem; }
  .av-state { font-size: 0.74rem; line-height: 1.25; }
  .av-time-grid { grid-template-columns: 1fr; gap: 0; }
}

/* ======================================================================
   Team Calendar view — the internal planning calendar (the "calendar" add-on).
   Reuses .av-grid / .av-dow from the Availability calendar above; adds its own
   day cell (.tc-day) that stacks event chips, a legend, and the subscribe/
   embed connect panel. Category colour is the only visual coding.
   ===================================================================== */
.tc-connect { background: var(--pf-surface); border-radius: var(--pf-radius); padding: 20px 22px; margin-bottom: 28px; }
.tc-connect-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; flex-wrap: wrap; }
.tc-connect-text strong { font-size: 0.95rem; }
body.admin .tc-connect-text p { margin: 4px 0 0; font-size: 0.82rem; }
.tc-connect-actions { display: flex; gap: 8px; flex-shrink: 0; }
.tc-feed-url { display: block; margin-top: 16px; padding: 8px 10px; background: var(--pf-bg); border-radius: var(--pf-radius-sm); font-size: 0.78rem; word-break: break-all; color: var(--pf-text-secondary); }
.tc-embed-details { margin-top: 18px; }
.tc-embed-details summary { cursor: pointer; font-size: 0.85rem; font-weight: 600; color: var(--pf-accent); }
.tc-embed-details pre { margin: 10px 0; background: var(--pf-bg); border-radius: var(--pf-radius-sm); padding: 10px; overflow-x: auto; }
.tc-embed-code { font-size: 0.76rem; white-space: pre; color: var(--pf-text); }
.tc-bar { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 12px; margin-bottom: 6px; }
.tc-bar .av-monthnav { grid-column: 2; justify-self: center; }
.tc-bar #tcNewBtn { grid-column: 3; justify-self: end; }
@media (max-width: 640px) {
  /* Below where a 3-column grid has room to breathe, drop back to a simple
     stacked flex column — matches how the rest of the toolbar already wraps
     on narrow screens (see .av-bar's flex-wrap above). */
  .tc-bar { display: flex; flex-direction: column; align-items: center; }
  .tc-bar .av-monthnav, .tc-bar #tcNewBtn { grid-column: auto; justify-self: auto; }
}
.tc-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 14px 0 20px; }
.tc-legend-item { display: inline-flex; align-items: center; gap: 6px; font-size: 0.78rem; color: var(--pf-text-secondary); }
.tc-swatch { width: 11px; height: 11px; border-radius: 3px; flex-shrink: 0; }

.tc-day { position: relative; min-width: 0; display: flex; flex-direction: column; align-items: stretch; gap: 4px; padding: 6px 7px; border-radius: var(--pf-radius); border: none; background: var(--pf-surface); cursor: pointer; font-family: var(--pf-font); text-align: left; min-height: 84px; transition: transform 0.1s ease, box-shadow 0.1s ease; }
.tc-day:hover { transform: translateY(-2px); box-shadow: var(--pf-shadow-sm); }
.tc-day.outside { opacity: 0.45; }
.tc-day.today { outline: 2px solid var(--pf-accent); outline-offset: -2px; }
.tc-num { font-size: 0.82rem; font-weight: 700; color: var(--pf-text-secondary); }
.tc-day.today .tc-num { color: var(--pf-accent); }
.tc-chips { display: flex; flex-direction: column; gap: 2px; overflow: hidden; }
.tc-chip { display: block; font-size: 0.68rem; line-height: 1.35; padding: 1px 5px; border-radius: 4px; background: var(--tc-c, var(--pf-accent)); color: var(--pf-white); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; }
.tc-chip:hover { filter: brightness(1.08); }
.tc-chip-booking { background: var(--pf-neutral-soft); color: var(--pf-text-secondary); border-left: 3px solid var(--tc-c, var(--pf-success)); border-radius: 0 4px 4px 0; cursor: default; }
.tc-more { font-size: 0.68rem; color: var(--pf-text-tertiary); padding-left: 3px; }
@media (min-width: 561px) {
  .tc-days { grid-template-rows: repeat(6, 1fr); row-gap: 6px; }
  .tc-day { height: 100%; }
}

/* event-editor modal extras */
.tc-check { display: flex; align-items: center; gap: 8px; font-size: 0.88rem; margin: 10px 0; cursor: pointer; }
.tc-check input { width: auto; }
.tc-block-box { background: var(--pf-bg); border-radius: var(--pf-radius-sm); padding: 12px 14px; margin-top: 6px; }
.tc-units { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px; margin-top: 8px; }
.tc-unit { margin: 2px 0; font-size: 0.85rem; }
.tc-actions { display: flex; align-items: center; gap: 12px; }
.tc-delete { margin-right: auto; color: var(--pf-danger); }

/* ======================================================================
   Stats view — bookings/revenue/trend analytics, with a date-range filter
   (presets + custom range) shared visually with the Bookings toolbar above.
   ===================================================================== */

.stats-range-toolbar { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 10px; }
.custom-range-fields { display: flex; align-items: center; gap: 8px; color: var(--pf-text-secondary); font-size: 0.85rem; }
/* display:flex above would otherwise defeat the [hidden] attribute — it only
   shows once "Custom…" is picked (see the click handler in admin.js). */
.custom-range-fields[hidden] { display: none; }
.custom-range-fields input[type="date"] {
  padding: 8px 12px; border: none; border-radius: var(--pf-radius-sm);
  background: var(--pf-bg); font-family: var(--pf-font); font-size: 0.85rem; color: var(--pf-text);
}
.custom-range-fields input[type="date"]:focus-visible { outline: 3px solid var(--pf-accent); outline-offset: 2px; }
.range-apply-btn {
  border: none; background: var(--pf-accent); color: var(--pf-white);
  font-family: var(--pf-font); font-weight: 700; font-size: 0.82rem;
  padding: 8px 16px; border-radius: 999px; cursor: pointer;
}
.range-apply-btn:hover { background: var(--pf-accent-hover); }
/* "body.admin" prefix needed here too — see the .av-hint comment above for why
   a bare class selector loses its margin to platform.css's `body.pf p` reset. */
body.admin .stats-period-label { color: var(--pf-text-secondary); font-size: 0.88rem; font-weight: 600; margin-bottom: 28px; }

/* KPI tiles reuse .stat's white card look; kpi-tile only adds the delta row. */
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 16px; margin-bottom: 16px; }
.kpi-tile .lbl-row { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-top: 6px; }
.kpi-tile .lbl-row .lbl { margin-top: 0; }
.kpi-delta { font-size: 0.72rem; font-weight: 700; padding: 1px 7px; border-radius: 999px; white-space: nowrap; }
.kpi-delta.good { color: var(--pf-success); background: var(--pf-success-soft); }
.kpi-delta.bad { color: var(--pf-danger); background: var(--pf-danger-soft); }
.kpi-delta.flat { color: var(--pf-text-secondary); background: var(--pf-neutral-soft); }

.stats-secondary { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 28px; }
.mini-stat { background: var(--pf-surface); border-radius: var(--pf-radius); padding: 14px 16px; box-shadow: var(--pf-shadow-sm); }
.mini-num { font-family: var(--pf-font); font-size: 1.3rem; font-weight: 600; color: var(--pf-text); line-height: 1.2; }
.mini-lbl { font-size: 0.78rem; color: var(--pf-text-secondary); margin-top: 4px; }

/* Info bubble — next to jargon-y stat labels (RevPAR, ADR…). Hover or focus
   (so it's reachable by keyboard, not just mouse) reveals a plain-language
   explanation. Pure CSS — no JS needed to show/hide it. */
.info-tip { position: relative; display: inline-flex; align-items: center; cursor: help; }
.info-tip .mi { width: 14px; height: 14px; color: var(--pf-text-secondary); opacity: 0.85; }
.info-tip-bubble {
  position: absolute; bottom: calc(100% + 9px); left: 50%; transform: translateX(-50%);
  width: max-content; max-width: 220px;
  background: var(--pf-text); color: var(--pf-white);
  font-size: 0.78rem; font-weight: 500; line-height: 1.45; text-align: left;
  padding: 9px 12px; border-radius: var(--pf-radius-sm); box-shadow: var(--pf-shadow-md);
  opacity: 0; visibility: hidden; transition: opacity 0.12s ease;
  pointer-events: none; z-index: 20;
}
.info-tip-bubble::after {
  content: ""; position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
  border: 5px solid transparent; border-top-color: var(--pf-text);
}
.info-tip:hover .info-tip-bubble, .info-tip:focus-visible .info-tip-bubble {
  opacity: 1; visibility: visible;
}

.stats-charts { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }
/* min-width:0 overrides the grid item's default min-width:auto — without it,
   a wide bar-chart row (e.g. 31 daily bars) forces this whole grid track wider
   than its column, blowing out the 2-column layout instead of just scrolling
   inside .bar-chart's own overflow-x. Same fix as .av-day elsewhere in this file. */
.chart-card { background: var(--pf-surface); border-radius: var(--pf-radius); padding: 20px 22px; min-width: 0; box-shadow: var(--pf-shadow-sm); }
/* "body.admin" prefix needed here too — a bare `.chart-card h3` loses its
   margin-bottom to platform.css's `body.pf h1, h2, h3, h4 { margin: 0 }`
   reset, which has higher specificity (body + class + element beats class +
   element). Same issue as .av-hint / .stats-period-label above. */
body.admin .chart-card h3 { font-size: 1rem; font-weight: 600; margin-bottom: 24px; color: var(--pf-text); }
.chart-card-wide { grid-column: 1 / -1; }
.chart-empty { color: var(--pf-text-secondary); font-size: 0.88rem; padding: 30px 0; text-align: center; }

/* Bar charts (revenue trend, bookings trend, weekday arrivals) — plain flex
   bars scaled to the tallest value, no chart library needed. */
.bar-chart { overflow-x: auto; }
.bars { display: flex; align-items: flex-end; gap: 10px; padding-bottom: 2px; }
.bar-col { display: flex; flex-direction: column; align-items: center; gap: 6px; flex: none; width: 46px; }
.bar-val { font-size: 0.7rem; font-weight: 700; color: var(--pf-text); white-space: nowrap; }
.bar-track { width: 100%; height: 120px; display: flex; align-items: flex-end; }
.bar-fill { width: 100%; background: var(--pf-accent); border-radius: 6px 6px 2px 2px; min-height: 3px; transition: height 0.25s ease; }
.bar-lbl { font-size: 0.7rem; color: var(--pf-text-secondary); white-space: nowrap; }

/* Revenue by room type — horizontal bars */
.hbar-row { display: grid; grid-template-columns: 132px 1fr auto; align-items: center; gap: 12px; margin-bottom: 12px; font-size: 0.85rem; }
.hbar-row:last-child { margin-bottom: 0; }
.hbar-lbl { color: var(--pf-text); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hbar-track { background: var(--pf-neutral-soft); border-radius: 999px; height: 10px; overflow: hidden; }
.hbar-fill { height: 100%; background: var(--pf-accent); border-radius: 999px; min-width: 3px; }
.hbar-val { text-align: right; color: var(--pf-text-secondary); white-space: nowrap; font-weight: 600; }
.hbar-pct { color: var(--pf-text-secondary); font-weight: 400; }

/* Booking status breakdown — a single segmented bar + legend. Pending gets
   its own fill color rather than reusing --pf-warning: that token is a dark
   brown, deliberately tuned for AAA text contrast on its soft background
   (see platform.css), but as a solid bar/dot fill it just reads as muddy and
   hard to tell apart from Cancelled's red. --pf-chart-pending is a plain
   vivid amber instead — no text sits on it, so contrast math doesn't apply. */
.status-bar { display: flex; height: 14px; border-radius: 999px; overflow: hidden; background: var(--pf-neutral-soft); margin-bottom: 16px; }
.status-seg { height: 100%; }
.status-seg.confirmed { background: var(--pf-success); }
.status-seg.pending { background: var(--pf-chart-pending); }
.status-seg.cancelled { background: var(--pf-danger); }
.status-legend { display: flex; gap: 18px; flex-wrap: wrap; font-size: 0.85rem; color: var(--pf-text-secondary); }
.status-leg-item { display: inline-flex; align-items: center; gap: 7px; }
.status-leg-item .dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.status-leg-item .dot.confirmed { background: var(--pf-success); }
.status-leg-item .dot.pending { background: var(--pf-chart-pending); }
.status-leg-item .dot.cancelled { background: var(--pf-danger); }

@media (max-width: 860px) {
  .kpi-grid { grid-template-columns: repeat(2, 1fr); }
  .stats-secondary { grid-template-columns: repeat(2, 1fr); }
  .stats-charts { grid-template-columns: 1fr; }
  .chart-card-wide { grid-column: auto; }
  /* An odd number of tiles (e.g. Revenue / Bookings / Avg. booking value) would
     leave a dead half-cell at the end of the two-column grid — stretch the lone
     trailing tile across both columns so it fills the row instead. On desktop
     the grids are auto-fit and collapse empty tracks on their own, so this only
     applies here. */
  .kpi-tile:last-child:nth-child(odd),
  .mini-stat:last-child:nth-child(odd) { grid-column: 1 / -1; }
}
@media (max-width: 480px) {
  .kpi-grid, .stats-secondary { grid-template-columns: 1fr 1fr; gap: 10px; }
  .mini-stat { padding: 12px; }
}

/* ================= Floor Plan (restaurant/bar table layout) ============ */
.fp-toolbar { display: flex; gap: 24px; flex-wrap: wrap; align-items: flex-end; margin-bottom: 18px; }
.fp-size-field { margin-bottom: 0; }
.fp-size-field > label { display: block; text-transform: uppercase; letter-spacing: 0.03em; font-size: 0.72rem; font-weight: 600; color: var(--pf-text-tertiary); margin-bottom: 6px; }
.fp-size-inputs { display: flex; align-items: center; gap: 6px; color: var(--pf-text-secondary); font-size: 0.9rem; }
.fp-size-inputs input { width: 72px; padding: 9px 10px; border: none; border-radius: var(--pf-radius-sm); background: var(--pf-bg); font-family: var(--pf-font); font-size: 0.9rem; color: var(--pf-text); }

.fp-bg-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 6px; }
.fp-bg-opacity {
  display: inline-flex; align-items: center; gap: 8px; font-size: 0.8rem; font-weight: 600;
  color: var(--pf-text-secondary); text-transform: uppercase; letter-spacing: 0.03em;
}
.fp-bg-opacity input[type="range"] { width: 110px; accent-color: var(--pf-accent); }
.fp-bg-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: fill; pointer-events: none; }

.fp-palette { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 6px; }
.fp-palette-item {
  display: inline-flex; align-items: center; gap: 8px; border: none; background: var(--pf-surface);
  box-shadow: var(--pf-shadow-sm); padding: 9px 16px; border-radius: 999px; cursor: pointer;
  font-family: var(--pf-font); font-weight: 600; font-size: 0.85rem; color: var(--pf-text);
}
.fp-palette-item:hover { background: var(--pf-neutral-soft); }
.fp-swatch { display: inline-block; width: 16px; height: 16px; flex: none; background: var(--pf-accent-soft); border: 1.5px solid var(--pf-accent); }
.fp-swatch.fp-shape-table-square, .fp-item.fp-shape-table-square { border-radius: 4px; }
.fp-swatch.fp-shape-table-round, .fp-item.fp-shape-table-round { border-radius: 50%; }
.fp-swatch.fp-shape-stool, .fp-item.fp-shape-stool { border-radius: 50%; width: 12px; height: 12px; }
.fp-swatch.fp-shape-other, .fp-item.fp-shape-other { border-radius: 4px; border-style: dashed; }

.fp-canvas-wrap { overflow: auto; max-width: 100%; border-radius: var(--pf-radius); background: var(--pf-neutral-soft); padding: 16px; }
.fp-canvas {
  position: relative; background: var(--pf-surface); border-radius: var(--pf-radius-sm);
  box-shadow: var(--pf-shadow-sm); background-image: linear-gradient(var(--pf-border-soft) 1px, transparent 1px), linear-gradient(90deg, var(--pf-border-soft) 1px, transparent 1px);
  background-size: 24px 24px; user-select: none;
}
.fp-item {
  position: absolute; display: flex; flex-direction: column; align-items: center; justify-content: center;
  background: var(--pf-accent-soft); border: 1.5px solid var(--pf-accent); cursor: grab;
  font-family: var(--pf-font); text-align: center; padding: 4px; box-sizing: border-box;
}
.fp-item:active { cursor: grabbing; }
.fp-item.dragging { z-index: 5; box-shadow: var(--pf-shadow-md); }
.fp-item-name {
  font-size: 0.78rem; font-weight: 700; color: var(--pf-text); background: none; border: none;
  text-align: center; width: 100%; padding: 0; cursor: text; font-family: var(--pf-font);
}
.fp-item-name:focus { outline: none; text-decoration: underline; }
.fp-item-del {
  position: absolute; top: -8px; right: -8px; width: 20px; height: 20px; border-radius: 50%;
  border: none; background: var(--pf-danger); color: var(--pf-white); font-size: 0.7rem; line-height: 1;
  cursor: pointer; display: none; align-items: center; justify-content: center;
}
.fp-item:hover .fp-item-del { display: flex; }
.fp-item-resize {
  position: absolute; right: -4px; bottom: -4px; width: 12px; height: 12px; border-radius: 3px;
  background: var(--pf-accent); cursor: nwse-resize;
}
.fp-canvas-resize {
  position: absolute; right: 2px; bottom: 2px; width: 16px; height: 16px; cursor: nwse-resize;
  border-right: 3px solid var(--pf-text-tertiary); border-bottom: 3px solid var(--pf-text-tertiary); opacity: 0.5;
}
.fp-msg { margin-top: 12px; font-size: 0.88rem; font-weight: 600; min-height: 1.2em; }
.fp-msg.ok { color: var(--pf-success); }
.fp-msg.err { color: var(--pf-danger); }

@media (max-width: 640px) {
  .fp-toolbar { flex-direction: column; align-items: stretch; gap: 12px; }
}
