/* Enlivn brand layer over Bootstrap 5.
   Typography: "Plus Jakarta Sans" for display headings, Inter for body —
   the pairing (plus real font smoothing and a tighter scale) is what gives
   the premium feel the plain Inter-everywhere pass lacked. */

:root {
  --enlivn-primary: #0190ef;          /* hsl(204 99% 47%) */
  --enlivn-primary-rgb: 1, 144, 239;
  --enlivn-primary-hover: #0079c9;
  --enlivn-accent: #05bbfe;
  --enlivn-ink: #0b1220;
  --enlivn-muted: #64748b;
  --enlivn-tint: rgba(1, 144, 239, 0.08);
  --enlivn-surface: #f8fafc;
  --enlivn-shadow-sm: 0 1px 2px rgba(11, 18, 32, 0.05);
  --enlivn-shadow: 0 10px 30px -12px rgba(11, 18, 32, 0.18);
  --enlivn-shadow-btn: 0 6px 16px -6px rgba(1, 144, 239, 0.55);

  /* The public navbar's "Account" button is the app's reference control: every
     chrome button and every filter is sized from these two numbers, so nothing
     in the furniture ever outgrows it. Derived from that button's own recipe —
     0.875rem text in Bootstrap's default .btn padding (36px tall, ~123px wide
     with its icon, label and caret) — rounded to the nearest sane step. */
  --enlivn-control-h: 2.25rem;
  --enlivn-control-w: 7.5rem;

  /* Admin/portal tokens — the literal values the Next.js shadcn/Tailwind admin
     resolved to, so the Django panel matches rather than merely resembles. */
  /* The Bootstrap portals' left menu (corporate + instructor). It used to
     borrow the STAFF panel's green-50, which made the two products look like
     one; the portals get the lightest periwinkle instead, a tint of the brand
     blue rather than a second unrelated hue. */
  --portal-sidebar-bg: #f4f6ff;     /* lightest purplish blue */
  --admin-brand: #1e40af;           /* Tailwind blue-800  (text-blue-800) */
  --admin-brand-badge: #bfdbfe;     /* Tailwind blue-200  (bg-blue-200) */
  --admin-muted-bg: #f1f5f9;        /* shadcn --muted */
  --admin-border: #e2e8f0;          /* shadcn --border */
  --admin-radius: 0.5rem;           /* shadcn --radius */
  --admin-topbar-h: 76px;

  /* Credits are GOLD, everywhere they print (19 Sep 2026, lead's call). Gold
     rather than green because green is already spoken for in this app —
     badge_variant paints success/paid with it, so a green balance sitting
     beside a booking's status would read as "paid" instead of "in hand".
     Gold also just reads as currency. Three tokens so the chip, the sidebar
     subscript and the dashboard tile can never drift into three golds. */
  --enlivn-credit: #a16207;         /* amber-700 — the figure; AA on white */
  --enlivn-credit-soft: #fef3c7;    /* amber-100 — chip + icon backing */
  --enlivn-credit-border: #fcd34d;  /* amber-300 — chip hairline */

  /* Bootstrap overrides */
  --bs-primary: var(--enlivn-primary);
  --bs-primary-rgb: var(--enlivn-primary-rgb);
  --bs-body-color: #334155;
  --bs-body-font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --bs-border-radius: 0.625rem;
  --bs-link-color: var(--enlivn-primary);
}

body {
  color: var(--bs-body-color);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* Match the old Tailwind container: full-width with 2rem gutters, capped at
   1400px. Bootstrap's stepped cap (1320px + 0.75rem gutters) kept every
   section noticeably further from the viewport edges than the Next.js site. */
.container { max-width: 1400px; padding-inline: 2rem; }
@media (max-width: 575.98px) { .container { padding-inline: 1rem; } }

h1, h2, h3, h4, .fw-display {
  font-family: "Plus Jakarta Sans", "Inter", system-ui, sans-serif;
  color: var(--enlivn-ink);
  font-weight: 800;
  letter-spacing: -0.035em;
}
h2 { font-size: clamp(1.75rem, 3vw, 2.35rem); }

.text-muted-slate { color: var(--enlivn-muted) !important; }

/* Buttons ------------------------------------------------------------------ */
.btn {
  font-weight: 600;
  transition: transform 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease;
}
.btn-primary {
  --bs-btn-bg: var(--enlivn-primary);
  --bs-btn-border-color: var(--enlivn-primary);
  --bs-btn-hover-bg: var(--enlivn-primary-hover);
  --bs-btn-hover-border-color: var(--enlivn-primary-hover);
  --bs-btn-active-bg: var(--enlivn-primary-hover);
  --bs-btn-disabled-bg: var(--enlivn-primary);
  box-shadow: var(--enlivn-shadow-btn);
}
.btn-primary:hover { transform: translateY(-1px); }
.btn-lg { --bs-btn-padding-x: 2rem; --bs-btn-padding-y: 0.7rem; }
.text-brand { color: var(--enlivn-primary) !important; }

/* Log out is the one destructive-ish action in the chrome, and grey-on-grey
   read as disabled — it takes the brand colour on hover in every panel.
   It also wears the Account button's exact metrics: the portal topbar's was a
   full-size .btn (38px) beside a 36px reference, which read as the biggest
   thing in the header. The staff panel's Tailwind twin is h-9/text-sm — the
   same 36px — so all three shells now match. */
.btn-logout {
  height: var(--enlivn-control-h);
  max-width: var(--enlivn-control-w);
  padding: 0.375rem 0.75rem;
  font-size: 0.875rem;
  display: inline-flex; align-items: center; justify-content: center;
}
.btn-logout:hover, .btn-logout:focus-visible {
  color: var(--enlivn-primary);
  border-color: var(--enlivn-primary);
  background-color: var(--enlivn-tint);
}

/* Navbar ------------------------------------------------------------------- */
.navbar .nav-link { color: var(--enlivn-ink); font-weight: 600; font-size: 0.875rem; }
.navbar .nav-link:hover,
.navbar .nav-link.active { color: var(--enlivn-primary); }
.navbar .dropdown-menu { border-radius: 0.75rem; box-shadow: var(--enlivn-shadow); border: 1px solid var(--bs-border-color); }
.navbar .dropdown-item { font-size: 0.875rem; font-weight: 500; color: var(--enlivn-ink); padding: 0.4rem 1.2rem; }
.navbar .dropdown-item:hover { background-color: var(--enlivn-surface); color: var(--enlivn-primary); }
.navbar .dropdown-header { font-size: 0.75rem; color: var(--enlivn-muted); padding: 0.5rem 1.2rem 0.25rem; }
.navbar .btn-outline-secondary { color: var(--enlivn-ink); border-color: #d1d5db; font-size: 0.875rem; font-weight: 600; background: #fff; }
.navbar .btn-outline-secondary:hover, .navbar .btn-outline-secondary.show { background-color: #f3f4f6; color: var(--enlivn-ink); border-color: #d1d5db; }
.brand-logo { height: 2.25rem; width: auto; }

/* Hero --------------------------------------------------------------------- */
.hero {
  /* Fill the first screen below the sticky navbar (~3.3rem) so "Why Choose
     Enlivn?" stays below the fold on load and reveals only once you scroll.
     Subtract slightly less than the navbar so the section can never peek. */
  min-height: calc(100vh - 3rem);
  min-height: calc(100dvh - 3rem);
  display: flex;
  align-items: center;
  padding-block: 2rem;
  background:
    radial-gradient(60rem 30rem at 85% -10%, rgba(5, 187, 254, 0.12), transparent 60%),
    linear-gradient(180deg, var(--enlivn-tint) 0%, #ffffff 100%);
}
.hero-eyebrow {
  display: inline-flex; align-items: center; gap: 0.5rem;
  background: #fff; border: 1px solid var(--bs-border-color);
  color: var(--enlivn-primary);
  border-radius: 999px; padding: 0.35rem 0.9rem;
  font-size: 0.8rem; font-weight: 600; letter-spacing: 0.02em;
  box-shadow: var(--enlivn-shadow-sm);
}
/* Sizes mirror the old Next.js hero: text-5xl headline (3rem) and a 600px-tall art column. */
.hero-headline { font-size: clamp(1.875rem, 3.2vw, 3rem); line-height: 1.08; }
.hero-art { max-height: 37.5rem; width: 100%; object-fit: contain; mix-blend-mode: multiply; }

/* Soft blended button (e.g. hero "Book a Consultation") — ink text + light grey background, no harsh border */
.btn-outline-ink {
  color: var(--enlivn-ink);
  background-color: #f1f5f9;
  border: 1px solid transparent;
  font-weight: 600;
  --bs-btn-color: var(--enlivn-ink);
  --bs-btn-bg: #f1f5f9;
  --bs-btn-border-color: transparent;
  --bs-btn-hover-color: var(--enlivn-ink);
  --bs-btn-hover-bg: #e2e8f0;
  --bs-btn-hover-border-color: transparent;
  --bs-btn-active-color: var(--enlivn-ink);
  --bs-btn-active-bg: #cbd5e1;
  --bs-btn-active-border-color: transparent;
  --bs-btn-focus-shadow-rgb: 100, 116, 139;
}

/* Site-wide invalid-field rule (form_validation.js): a submit attempt paints
   required-but-empty controls red until they're fixed. */
.field-invalid,
.field-invalid:focus { border-color: #dc3545 !important; }

/* Feature cards ------------------------------------------------------------ */
.feature-card {
  border: 1px solid var(--bs-border-color);
  border-radius: 1rem;
  background: var(--card-fill, #fff);
  box-shadow: var(--enlivn-shadow-sm);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.feature-card:hover { transform: translateY(-4px); box-shadow: var(--enlivn-shadow); }
.feature-icon {
  width: 3.25rem; height: 3.25rem;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px;
  background: var(--enlivn-tint);
  color: var(--enlivn-primary);
}
.feature-icon svg { width: 1.5rem; height: 1.5rem; }

/* Scroll reveal: the body copy (and icon) fades in first, then the heading
   grows up out of it. `.reveal-armed` is added by JS on load so the cards
   stay fully visible when JS never runs; `.is-revealed` runs the sequence
   each time the grid scrolls into view (home.js resets it once the section
   is fully off screen, so it replays on every visit). Cards stagger left to
   right; each also carries a very light brand-adjacent fill (blue → cyan →
   mint → lavender). */
[data-feature-reveal] > div:nth-child(1) { --reveal-delay: 0s;    --card-fill: #eef7fe; }  /* primary blue  */
[data-feature-reveal] > div:nth-child(2) { --reveal-delay: 0.15s; --card-fill: #fdf4ec; }  /* warm apricot  */
[data-feature-reveal] > div:nth-child(3) { --reveal-delay: 0.3s;  --card-fill: #effaf3; }  /* wellness mint */
[data-feature-reveal] > div:nth-child(4) { --reveal-delay: 0.45s; --card-fill: #f5f3fe; }  /* calm lavender */
[data-feature-reveal].reveal-armed:not(.is-revealed) .feature-icon,
[data-feature-reveal].reveal-armed:not(.is-revealed) .feature-body {
  opacity: 0; transform: translateY(18px);
}
[data-feature-reveal].reveal-armed:not(.is-revealed) .feature-title {
  opacity: 0; transform: translateY(14px) scale(0.8);
}
[data-feature-reveal] .feature-icon,
[data-feature-reveal] .feature-body {
  transition: opacity 1s ease var(--reveal-delay, 0s),
              transform 1s ease var(--reveal-delay, 0s);
}
[data-feature-reveal] .feature-title {
  transform-origin: center bottom;
  transition: opacity 1.2s ease calc(var(--reveal-delay, 0s) + 0.6s),
              transform 1.2s cubic-bezier(0.22, 1, 0.36, 1) calc(var(--reveal-delay, 0s) + 0.6s);
}
@media (prefers-reduced-motion: reduce) {
  [data-feature-reveal] .feature-icon,
  [data-feature-reveal] .feature-body,
  [data-feature-reveal] .feature-title { transition: none; opacity: 1 !important; transform: none !important; }
}

/* Testimonials ------------------------------------------------------------- */
.section-underline {
  display: block; width: 4rem; height: 4px;
  background: var(--enlivn-accent);
  border-radius: 999px; margin: 0.75rem auto 0;
}
.testimonial-quote {
  font-family: "Plus Jakarta Sans", "Inter", sans-serif;
  font-size: 1.35rem; font-weight: 600; line-height: 1.5;
  color: var(--enlivn-ink);
}
/* Custom flex-track slider (not the Bootstrap carousel) so the motion matches
   the old Next.js page: the whole strip glides -100% per slide over 1s. */
.testimonial-track {
  display: flex;
  transition: transform 1s ease-in-out;
  /* Drag-to-browse (home.js): grabbable, and a horizontal drag must not select
     the quote text. `pan-y` keeps vertical page scrolling on touch while the
     browser hands us the horizontal axis. */
  cursor: grab;
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
}
.testimonial-track:active { cursor: grabbing; }
/* Last→first travels the whole strip, not one slide, so at 1s it reads as a
   long rewind. Same easing, less time. */
.testimonial-track.is-rewinding { transition-duration: 0.45s; }
/* While the pointer is dragging, the track must track the pointer 1:1 — the
   1s ease is what carries it home once the class comes back off. Declared
   AFTER .is-rewinding: both are single-class selectors, so source order is
   what stops a mid-rewind grab from keeping its 0.45s duration. */
.testimonial-track.is-dragging { transition: none; }
.testimonial-slide { flex: 0 0 100%; min-width: 0; }
.testimonial-dot {
  width: 12px; height: 12px; border-radius: 50%; border: 0; padding: 0;
  background-color: #d1d5db;
  transition: background-color 0.3s ease;
}
.testimonial-dot.active { background-color: #1f2937; }

/* Customer logo marquee — full-colour logos in Next.js-sized cards
   (~13.5rem × 7.5rem, 50s loop, card lifts on hover). */
.logo-marquee { overflow: hidden; }
.logo-track {
  display: flex; gap: 1rem; width: max-content; padding: 0.5rem 0;
  animation: logo-scroll 50s linear infinite;
}
.logo-marquee:hover .logo-track { animation-play-state: paused; }
.logo-card {
  flex: 0 0 auto; width: 13.5rem; height: 7.5rem;
  display: flex; align-items: center; justify-content: center;
  background: #fff; border: 1px solid #e5e7eb;
  border-radius: 0.75rem;
  box-shadow: var(--enlivn-shadow-sm);
  transition: transform 0.3s ease;
}
.logo-card:hover { transform: scale(1.05); }
.logo-card img { max-height: 6rem; max-width: 12rem; object-fit: contain; }
@keyframes logo-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* Service cards ------------------------------------------------------------ */
.service-card {
  border: 1px solid var(--bs-border-color);
  border-radius: 1rem; overflow: hidden;
  box-shadow: var(--enlivn-shadow-sm);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
  color: inherit; text-decoration: none;
}
.service-card:hover, .service-card:focus { transform: translateY(-4px); box-shadow: var(--enlivn-shadow); color: inherit; text-decoration: none; }
/* ── Showing a picture WHOLE, in a fixed box ────────────────────────────────
   19 Sep 2026, lead's call: "any dimension uploaded should be visible
   completely", with clean borders, on cards and in drawers.

   The problem: every banner box here has a fixed aspect, but admins upload
   portrait, square, panorama and 4K alike. The three ways out —
     `cover`            fills the box but CROPS; that is the complaint.
     `contain` alone    shows it whole but leaves dead grey bars beside tall
                        images, so a grid reads as broken rather than designed.
     `.media-fit`       contains the media AND fills the box behind it with a
                        blurred, zoomed copy of the same file.
   The third is what ships. Nothing is ever cropped, and the box is still full
   bleed, so a mixed-aspect catalogue looks composed instead of ragged.

   The backdrop is an INLINE background-image on the wrapper (the only per-file
   value), and every other property lives here. `scale(1.1)` hides the blur's
   soft edges, which otherwise feather into the card's own border. The backdrop
   is `aria-hidden` by construction — it is a background, so no screen reader
   ever sees the duplicate.

   Cost: the same file is painted twice. It is ONE decode — browsers share the
   cache entry between the <img> and the CSS background — so this buys the fill
   for a second paint, not a second download. */
.media-fit {
  position: relative;
  width: 100%; height: 100%;
  overflow: hidden;
  background-color: var(--enlivn-surface);
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  /* Keeps the blur from bleeding past a rounded corner it sits inside. */
  isolation: isolate;
}
.media-fit::before {
  content: "";
  position: absolute; inset: 0;
  background: inherit;
  background-color: transparent;
  filter: blur(18px) saturate(1.2) brightness(0.92);
  transform: scale(1.1);
  z-index: 0;
}
/* The real media, whole, centred, above the blur. */
.media-fit > img,
.media-fit > video {
  position: relative; z-index: 1;
  width: 100%; height: 100%;
  object-fit: contain;
  display: block;
}

/* `.is-fitted` is the same effect applied to a container that ALREADY exists
   and already anchors overlays — the corporate card's media box (favourite
   star, play badge) and the offering drawer's hero (promotion flag). Those
   could not be wrapped in `.media-fit` without moving the overlays too, so the
   backdrop rides the container itself and the overlays keep their anchor.
   Their own `position: relative` + `overflow: hidden` are already declared. */
.catalogue-media.is-fitted,
.record-hero.is-fitted {
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
}
.catalogue-media.is-fitted::before,
.record-hero.is-fitted::before {
  content: "";
  position: absolute; inset: 0;
  background: inherit;
  background-color: transparent;
  filter: blur(18px) saturate(1.2) brightness(0.92);
  transform: scale(1.1);
  z-index: 0;
}
/* The card's media element sits above its own backdrop. The overlays carry
   higher z-indexes of their own, so they stay on top of both. */
.catalogue-media.is-fitted > img,
.catalogue-media.is-fitted > video { position: relative; z-index: 1; }

/* The card's banner box. The HEIGHT lives on the wrapper now, so the blurred
   backdrop fills the same 12rem the picture used to crop itself into. */
.service-card .media-fit { height: 12rem; }
.service-card .service-photo {
  height: 12rem; width: 100%; object-fit: cover; object-position: center 20%;
  transition: transform 0.35s ease;
}
.service-card:hover .service-photo { transform: scale(1.04); }
.badge-popular {
  background: #fdf3d7; color: #8a5500;
  border: 1px solid #f0dca3; border-radius: 999px;
  font-weight: 600; padding: 0.35rem 0.9rem;
}
/* Detail-page variant: a highlight sweeps across the badge so the marketing
   signal catches the eye once a cycle. `overflow:hidden` keeps the sweep inside
   the pill; reduced-motion users get the plain badge. */
.badge-popular--shine {
  position: relative;
  overflow: hidden;
  display: inline-block;
  font-size: 0.6875rem;
  padding: 0.26rem 0.7rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.badge-popular--shine::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 -60%;
  width: 55%;
  /* A WHITE sweep is nearly invisible on the badge's cream fill — the sweep is
     tinted with the badge's own amber ink so it actually reads as a shine. */
  background: linear-gradient(100deg, transparent, rgba(138, 85, 0, 0.26), transparent);
  transform: skewX(-18deg);
  animation: badge-shine 2.2s ease-in-out infinite;
}
@keyframes badge-shine {
  0%, 45% { left: -60%; }
  100%    { left: 130%; }
}
@media (prefers-reduced-motion: reduce) {
  .badge-popular--shine::after { animation: none; display: none; }
}

/* Catalogue cards (corporate portal · Browse offerings) ---------------------
   The public service card plus the two things a client needs: a favourite on
   the banner and a price + Book now in the footer. Built on .service-card so
   both catalogues stay one visual family. */
.catalogue-card { position: relative; background: #fff; }
.catalogue-media {
  position: relative;
  height: 11.5rem;
  overflow: hidden;
  /* Shown while a lazy image is still off-screen, and behind a video's first
     frame — a grey void made the grid look broken while scrolling. */
  background: linear-gradient(135deg, #eef6fd, #e2ecf7);
}
.catalogue-media .service-photo { height: 100%; }
.catalogue-media-empty {
  display: flex; align-items: center; justify-content: center;
  height: 100%; color: #9db6cd; font-size: 2rem;
}
.catalogue-fav {
  position: absolute; top: 0.6rem; right: 0.6rem;
  /* Above the media AND its blurred backdrop (`.is-fitted::before`). */
  z-index: 2;
  display: inline-flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem; border: 0; border-radius: 999px;
  background: rgba(255, 255, 255, 0.94); color: #94a3b8;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.12);
  transition: transform 0.15s ease, color 0.15s ease;
}
.catalogue-fav:hover { transform: scale(1.08); color: #f0a92c; }
.catalogue-fav.is-on { color: #f0a92c; }
/* A muted first frame stands in for the artwork; it only plays on hover, so a
   grid of them costs metadata instead of a live decode each (see the memory
   note in the template). */
.catalogue-video { height: 100%; width: 100%; object-fit: contain; }
.catalogue-play {
  position: absolute; inset: 0; z-index: 2; display: flex;
  align-items: center; justify-content: center;
  color: #fff; font-size: 2rem; text-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
  pointer-events: none; transition: opacity 0.2s ease;
}
.catalogue-card:hover .catalogue-play { opacity: 0; }
.catalogue-price { font-weight: 700; color: var(--enlivn-ink, #1f2d3d); }
.catalogue-price small { font-weight: 500; color: var(--enlivn-muted); }

/* Catalogue / list toolbars ------------------------------------------------
   One row: a wide search box, then equal-width menu boxes. Every control is the
   same 2.75rem tall with the app's own radius, so the row reads as one strip
   (the reference the lead supplied). The menus are BUTTONS wearing .form-select
   — the same trick the public enquiry card uses — because a real <select>
   can't hold a checkbox list. */
/* One strip of equal boxes. Every control — search included — is capped at the
   Account button's width, so the toolbar reads as part of the app's furniture
   instead of a banner across the page. */
/* The list-page toolbar row (17 Aug) — what is listed on the left, how it is
   narrowed on the right, mirrored to each edge. It is the staff panel's own
   toolbar shape in the Bootstrap portals' tokens; a page with no tabs simply
   renders the form and the row collapses to it. */
.portal-toolbar {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 0.75rem;
}

/* A segmented control made of LINKS, for a tab that is navigation rather than
   panel switching (the instructor's Bookings | Sessions). Same recipe as
   `.drawer-tabs` — a muted track with the active chip raised in white — but it
   cannot share those rules: those are scoped to a <button> reset, and an <a>
   needs its underline and colour taken off instead. */
.segmented-nav {
  display: inline-flex; align-items: center; gap: 0.125rem;
  padding: 0.25rem; border-radius: 0.5rem;
  background: rgba(15, 23, 42, 0.05);
}
.segmented-link {
  display: inline-flex; align-items: center; gap: 0.4rem;
  padding: 0.4rem 0.9rem; border-radius: 0.375rem;
  font-size: 0.875rem; font-weight: 500; color: var(--enlivn-muted);
  text-decoration: none; white-space: nowrap;
  transition: background 0.15s ease, color 0.15s ease;
}
.segmented-link:hover { color: var(--enlivn-ink); }
.segmented-link.is-active {
  background: #fff; color: var(--enlivn-ink);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.09);
}

.catalogue-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; }
.catalogue-toolbar .catalogue-search { flex: 0 0 var(--enlivn-control-w); }
.catalogue-toolbar .form-control,
.catalogue-toolbar .form-select {
  height: var(--enlivn-control-h);
  width: var(--enlivn-control-w);
  max-width: var(--enlivn-control-w);
  font-size: 0.875rem;
  border-radius: var(--bs-border-radius);
  background-color: #fff;
}
/* The magnifier sits inside a narrow box, so it shrinks with it. */
.catalogue-toolbar .catalogue-search .bi-search { font-size: 0.8rem; }
.catalogue-toolbar .form-select { padding-right: 1.75rem; background-position: right 0.5rem center; }
.catalogue-toolbar .form-control:focus,
.catalogue-toolbar .form-select:focus,
.catalogue-toolbar .form-select:focus-visible {
  border-color: var(--enlivn-primary);
  box-shadow: 0 0 0 0.2rem var(--enlivn-tint);
}
/* The multi-select trigger has to look like the plain menus beside it. */
.catalogue-toolbar .filter-select-toggle {
  text-align: left;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.catalogue-toolbar .filter-select-toggle.is-on {
  border-color: var(--enlivn-primary); color: var(--enlivn-primary);
}
.catalogue-filter-menu { min-width: 14rem; max-height: 22rem; overflow-y: auto; }

/* --- Promotion carousel (corporate Browse offerings, 17 Aug 2026) ---------
   Centre-focused strip: the current tile sits in the middle at full size, its
   neighbours peek in on either side scaled down and dimmed. `--promo-slide` is
   the tile's share of the viewport width — promo_carousel.js reads it to
   compute the centring transform, so the JS and the breakpoints can't drift.
   The track's default transform centres slide 0 server-side, so the first
   paint is already in place and the script's initial render moves nothing. */
.promo-carousel {
  --promo-slide: 0.62;
  --promo-h: 13.5rem;
  position: relative;
}
.promo-viewport { position: relative; overflow: hidden; border-radius: 1rem; }
.promo-track {
  display: flex;
  transform: translateX(calc(50% - var(--promo-slide) * 50%));
  transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
  cursor: grab;
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
}
.promo-track:active { cursor: grabbing; }
.promo-track.is-rewinding { transition-duration: 0.45s; }
.promo-track.is-dragging { transition: none; }
.promo-slide {
  flex: 0 0 calc(var(--promo-slide) * 100%);
  min-width: 0;
  padding: 0 0.5rem;
  display: block;
  color: #fff;
  text-decoration: none;
  transform: scale(0.92);
  opacity: 0.55;
  transition: transform 0.5s ease, opacity 0.5s ease;
}
.promo-slide:hover, .promo-slide:focus-visible { color: #fff; }
.promo-slide.is-current { transform: scale(1); opacity: 1; }
/* The centred tile is the link, so it says so under the pointer. The track's
   own `grab` cursor still wins while a drag is in flight (`:active`), which is
   right: mid-drag the strip is being moved, not followed. */
.promo-slide.is-current { cursor: pointer; }
.promo-slide:focus-visible { outline: none; }
.promo-slide:focus-visible .promo-media { box-shadow: 0 0 0 3px #fff, 0 0 0 5px var(--enlivn-primary); }
/* The tile the page is currently narrowed to (`?promotion=`) wears a brand ring. */
.promo-slide.is-selected .promo-media { box-shadow: 0 0 0 3px var(--enlivn-primary), var(--enlivn-shadow); }
.promo-media {
  position: relative;
  height: var(--promo-h);
  border-radius: 1rem;
  overflow: hidden;
  background: linear-gradient(135deg, #0b3f66 0%, var(--enlivn-primary) 55%, var(--enlivn-accent) 100%);
  box-shadow: var(--enlivn-shadow);
}
.promo-media img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform 0.7s ease;
}
.promo-slide.is-current:hover .promo-media img { transform: scale(1.04); }
.promo-media-empty {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-size: 5rem; color: rgba(255, 255, 255, 0.35);
}
.promo-scrim {
  position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(11, 18, 32, 0.82) 0%, rgba(11, 18, 32, 0.35) 45%, rgba(11, 18, 32, 0.05) 100%);
}
.promo-copy {
  position: absolute; left: 1.5rem; right: 1.5rem; bottom: 1.25rem;
  display: flex; flex-direction: column; align-items: flex-start; gap: 0.35rem;
}
.promo-pill {
  display: inline-flex; align-items: center;
  padding: 0.25rem 0.75rem; border-radius: 999px;
  background: #fff; color: var(--enlivn-primary);
  font-size: 0.75rem; font-weight: 800; letter-spacing: 0.02em; text-transform: uppercase;
  box-shadow: var(--enlivn-shadow-sm);
}
.promo-title {
  font-family: "Plus Jakarta Sans", "Inter", sans-serif;
  font-size: 1.35rem; font-weight: 700; line-height: 1.2; margin: 0;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}
.promo-desc {
  margin: 0; font-size: 0.9rem; line-height: 1.4; opacity: 0.92; max-width: 34rem;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.promo-meta {
  display: flex; flex-wrap: wrap; gap: 0.35rem 1rem;
  font-size: 0.8rem; opacity: 0.9;
}
.promo-cta {
  margin-top: 0.25rem;
  font-size: 0.85rem; font-weight: 600;
  border-bottom: 1px solid rgba(255, 255, 255, 0.6);
  transition: border-color 0.2s ease;
}
/* The WHOLE banner is the link (25 Aug), not just this line — so hovering
   anywhere on the tile lights the CTA, rather than the CTA being the only
   thing that reacts and therefore the only thing that looks pressable. */
.promo-slide.is-current:hover .promo-cta { border-bottom-color: #fff; }
.promo-arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 2.5rem; height: 2.5rem; border-radius: 50%; border: 0;
  background: rgba(255, 255, 255, 0.92); color: var(--enlivn-ink);
  box-shadow: var(--enlivn-shadow-sm);
  display: grid; place-items: center; font-size: 1.1rem;
  transition: background-color 0.2s ease, color 0.2s ease;
  z-index: 2;
}
.promo-arrow:hover { background: var(--enlivn-primary); color: #fff; }
.promo-arrow-prev { left: 0.75rem; }
.promo-arrow-next { right: 0.75rem; }
.promo-dots { display: flex; justify-content: center; gap: 0.5rem; margin-top: 0.75rem; }
.promo-dot {
  width: 0.5rem; height: 0.5rem; border-radius: 999px; border: 0; padding: 0;
  background: #cbd5e1; transition: width 0.3s ease, background-color 0.3s ease;
}
.promo-dot.active { width: 1.5rem; background: var(--enlivn-primary); }
/* One promotion: no neighbours to peek, so the tile takes the strip. */
.promo-carousel:not([data-promo-autoplay]) { --promo-slide: 1; }
.promo-carousel:not([data-promo-autoplay]) .promo-slide { transform: none; opacity: 1; padding: 0; }
@media (max-width: 991.98px) {
  .promo-carousel { --promo-slide: 0.84; --promo-h: 12rem; }
  .promo-title { font-size: 1.15rem; }
  .promo-arrow { display: none; }   /* a thumb swipes; arrows only crowd a phone */
}
@media (max-width: 575.98px) {
  .promo-carousel { --promo-slide: 0.92; --promo-h: 11rem; }
  .promo-copy { left: 1rem; right: 1rem; bottom: 1rem; }
  .promo-desc { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .promo-track, .promo-slide, .promo-media img, .promo-dot { transition: none; }
}
/* The "showing offerings under …" chip beneath the strip. */
.promo-active-chip {
  display: flex; align-items: center; gap: 0.25rem;
  padding: 0.6rem 0.9rem; border-radius: var(--bs-border-radius);
  background: var(--enlivn-tint); color: var(--enlivn-ink);
  border: 1px solid rgba(1, 144, 239, 0.25);
  font-size: 0.9rem;
}

/* The shared city picker (public funnel step 2, Book now, Profile). The menu
   matches the field it hangs off and scrolls internally — a long city list
   otherwise ran taller than the drawer it lives in. */
.city-multiselect { position: relative; }
/* The trigger names every ticked city, so a long selection has to clip rather
   than stretch the field or wrap it into two lines. */
.city-multiselect-toggle {
  overflow: hidden; white-space: nowrap;
}
.city-multiselect-toggle > span {
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.city-multiselect-menu {
  width: 100%; min-width: 12rem;
  max-height: 15rem; overflow-y: auto;
}
.city-multiselect-menu .form-check { margin-bottom: 0; padding: 0.2rem 0.25rem 0.2rem 1.75rem; border-radius: 0.375rem; }
.city-multiselect-menu .form-check:hover { background-color: var(--enlivn-surface); }
.city-multiselect-menu .form-check-label { cursor: pointer; }

/* Book-now drawer (corporate) ------------------------------------------------
   The one portal surface that keeps its own dress: a tinted header band,
   sectioned body and sticky footer. Kept deliberately (1 Aug) after the
   consistency pass tried plain drawer furniture here — this reads better for a
   form you fill in, as opposed to a record you read. Everything else in the
   portals still uses .panel / .drawer-kv. */
.book-drawer { margin: -1.5rem; }            /* bleed past .offcanvas-body padding */
.book-drawer-head {
  padding: 1.5rem;
  background: linear-gradient(135deg, var(--enlivn-tint), rgba(5, 187, 254, 0.06));
  border-bottom: 1px solid var(--bs-border-color);
}
.book-drawer-eyebrow {
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--enlivn-primary);
}
/* The balance, under the price in the Book-now drawer. A quiet gold line: it
   informs the decision without competing with the price above it. */
.book-drawer-credits {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.2rem 0.6rem; border-radius: 999px;
  background: var(--enlivn-credit-soft); color: var(--enlivn-credit);
  font-size: 0.8rem;
}
.book-drawer-body { padding: 1.5rem 1.5rem 0; }
.book-section {
  border: 1px solid var(--bs-border-color);
  border-radius: 0.9rem; padding: 1.1rem 1.25rem 1.25rem;
  margin-bottom: 1rem; background: #fff;
}
.book-section-title {
  display: flex; align-items: center; gap: 0.5rem;
  font-size: 0.95rem; font-weight: 700; margin-bottom: 1rem;
  color: var(--enlivn-ink);
}
.book-section-title i { color: var(--enlivn-primary); }
.book-locked-note {
  margin-left: auto; display: inline-flex; align-items: center; gap: 0.3rem;
  font-size: 0.7rem; font-weight: 600; letter-spacing: 0.02em;
  color: var(--enlivn-muted); background: var(--enlivn-surface);
  border-radius: 999px; padding: 0.15rem 0.6rem;
}
.book-drawer-footer {
  position: sticky; bottom: 0;
  display: flex; justify-content: flex-end; gap: 0.75rem;
  padding: 1rem 0; margin-top: 0.5rem;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.6), #fff 35%);
}

/* Multi-select date calendar (portal.js [data-multidate]) — the Book-now
   drawer's "any dates that could work". Each picked day is a hidden input, so
   the server just reads a list. Two panes: the month grid as a card, and the
   picks as removable chips beside it — the running answer, readable without
   scanning the grid. */
.multidate { display: flex; flex-wrap: wrap; gap: 1rem; align-items: flex-start; }
.multidate-cal {
  flex: 0 0 auto; width: 19.5rem; max-width: 100%;
  background: #fff; border: 1px solid var(--bs-border-color);
  border-radius: 0.9rem; padding: 0.85rem 1rem 1rem;
  box-shadow: var(--enlivn-shadow-sm);
}
.multidate-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 0.4rem;
}
.multidate-title { font-weight: 700; font-size: 0.9rem; }
.multidate-nav {
  border: 1px solid var(--bs-border-color); background: #fff;
  border-radius: 999px; width: 1.8rem; height: 1.8rem;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--enlivn-ink); font-size: 0.75rem;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.multidate-nav:hover:not(:disabled) { border-color: var(--enlivn-primary); color: var(--enlivn-primary); }
.multidate-nav:disabled { opacity: 0.35; }
.multidate-grid {
  display: grid; grid-template-columns: repeat(7, 2.35rem);
  justify-content: space-between; row-gap: 0.15rem;
}
.multidate-dow {
  text-align: center; font-size: 0.68rem; font-weight: 700;
  letter-spacing: 0.02em; text-transform: uppercase;
  color: var(--enlivn-muted); padding-block: 0.3rem;
}
/* Circular day cells — the shape every date picker has taught people to read. */
.multidate-day {
  width: 2.35rem; height: 2.35rem; border: none; background: none;
  border-radius: 999px; font-size: 0.85rem; color: var(--enlivn-ink);
  display: inline-flex; align-items: center; justify-content: center;
  transition: background-color 0.12s ease, color 0.12s ease;
}
.multidate-day:hover:not(:disabled) { background: var(--enlivn-tint); color: var(--enlivn-primary); }
.multidate-day:disabled { color: #cbd5e1; }
.multidate-day.is-today { box-shadow: inset 0 0 0 1.5px var(--enlivn-primary); }
.multidate-day.is-picked {
  background: var(--enlivn-primary); color: #fff; font-weight: 700;
  box-shadow: var(--enlivn-shadow-btn);
}
/* Leaves reuse the grid above (17 Aug) with two additions. A day already SPENT
   — today's or any past one — is on record and cannot be taken back, so it is
   drawn as a filled-but-locked cell rather than left off the calendar: a leave
   you booked has to be visible on the month you booked it for. */
.multidate-day.is-locked {
  background: var(--enlivn-surface); color: var(--enlivn-muted);
  box-shadow: inset 0 0 0 1.5px var(--bs-border-color); cursor: not-allowed;
}
/* Each picked day as its own row, because a leave carries an optional TIME —
   a chip can hold a date, not a date and a field. */
.leave-row {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.5rem 0.65rem; border: 1px solid var(--bs-border-color);
  border-radius: 0.6rem; background: #fff;
}
.leave-row.is-locked { background: var(--enlivn-surface); }
.leave-row-date { font-weight: 600; font-size: 0.85rem; flex: 1 1 auto; }
.leave-row .form-control { width: 6.5rem; flex: 0 0 auto; }
.leave-rows { display: flex; flex-direction: column; gap: 0.5rem; }

.multidate-picked { flex: 1 1 11rem; min-width: 10rem; display: flex; flex-direction: column; gap: 0.4rem; }
.multidate-picked-title {
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--enlivn-muted);
}
.multidate-chip {
  display: inline-flex; align-items: center; gap: 0.45rem; width: fit-content;
  border: 1px solid rgba(1, 144, 239, 0.25); background: var(--enlivn-tint);
  color: var(--enlivn-primary); border-radius: 999px;
  padding: 0.3rem 0.7rem; font-size: 0.8rem; font-weight: 600;
}
.multidate-chip .bi-x-lg { font-size: 0.65rem; opacity: 0.7; }
.multidate-chip:hover { border-color: var(--enlivn-primary); }
.multidate-chip:hover .bi-x-lg { opacity: 1; }

/* Booking drawer + profile ---------------------------------------------------
   Both use the portal's own furniture (.panel, .drawer-kv, plain controls); the
   only additions are the two things the app didn't have a pattern for. */
/* Values we already hold and won't ask for again. */
.field-locked, .field-locked:focus {
  background-color: var(--enlivn-surface);
  color: var(--enlivn-ink); box-shadow: none; cursor: default;
}
/* The verification code opens under the email field, in place, so the rest of
   the form stays on screen with whatever was typed. */
.profile-code {
  margin-top: 0.75rem; padding: 0.85rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  background: var(--enlivn-surface);
}
.profile-verified { display: inline-flex; align-items: center; gap: 0.25rem;
  font-size: 0.8rem; font-weight: 600; color: #147a3d; }
.profile-unverified { display: inline-flex; align-items: center; gap: 0.25rem;
  font-size: 0.8rem; font-weight: 600; color: #8a5500; }

/* CTA ---------------------------------------------------------------------- */
.cta-panel {
  background:
    radial-gradient(40rem 20rem at 90% 0%, rgba(5, 187, 254, 0.14), transparent 55%),
    var(--enlivn-tint);
  border-radius: 1.25rem;
}

/* Footer ------------------------------------------------------------------- */
.site-footer { border-top: 1px solid var(--bs-border-color); background: #fff; }
.site-footer a { color: var(--enlivn-muted); text-decoration: none; }
.site-footer a:hover { color: var(--enlivn-primary); }

/* ==========================================================================
   Portal shell (corporate / instructor / staff)

   Ported 1:1 from the Next.js admin (enlivnFE). The tokens below are the
   literal values that shadcn/Tailwind resolved to, so the Django panel is
   visually identical rather than merely similar:
     primary          hsl(204 99% 47%)  -> #018fee   (--primary)
     sidebar surface  --portal-sidebar-bg -> #f4f6ff (the STAFF panel keeps
                      its own green-50, set in Tailwind on base_admin.html)
     brand wordmark   Tailwind blue-800 -> #1e40af   (text-blue-800)
     brand badge      Tailwind blue-200 -> #bfdbfe   (bg-blue-200)
     radius           0.5rem                          (--radius)
     sidebar width    16rem                           (w-64)
   Interaction contract: left menu -> page body -> drawer -> modal.
   ========================================================================== */
/* Top bar spans the full width, above BOTH sidebar and content. */
.admin-topbar {
  height: var(--admin-topbar-h);
  background: #fff;
  border-bottom: 1px solid var(--admin-border);
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 2rem;
  position: sticky; top: 0; z-index: 1030;
}
.admin-topbar .brand-logo { height: 2.75rem; width: auto; }

/* The signed-in company, beside the logo (19 Sep). `justify-content: space-
   between` on the bar would push a third child into the middle, so the actions
   group carries `ms-auto` and the name sits tight against the divider. */
.topbar-divider {
  width: 1px; height: 1.75rem; margin: 0 1rem;
  background: var(--admin-border); flex-shrink: 0;
}
/* 19 Sep, third pass: matched to the LOGO (lead's "almost equal to the enlivn
   logo"). The logo is 2.75rem tall, but its glyphs are a fraction of that, so
   2rem of cap-height reads as the same size beside it — setting 2.75rem here
   would tower over the wordmark rather than match it. */
.topbar-company {
  font-family: "Plus Jakarta Sans", Inter, sans-serif;
  font-size: 2rem; font-weight: 800; letter-spacing: -0.025em; line-height: 1;
  color: var(--enlivn-ink);
  /* A long company name must never push the bell and Log out off the bar. */
  max-width: 26rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The shine: a highlight band travelling left→right across the letters.
   `background-clip: text` paints the gradient THROUGH the glyphs, so there is
   no overlay element to position and nothing to mis-clip against the ellipsis.

   The gradient is 3 lengths wide and slides one full width, which is what makes
   the band cross every character and leave cleanly rather than flickering at
   the edges. 6s travel + a long idle tail (the highlight sits off-screen for
   most of the cycle) gives the "slowly, after some time, steady" pacing asked
   for — a fast loop on a company name reads as a broken element.

   `color` stays set underneath: if a browser ignores background-clip, the name
   renders in flat ink instead of transparent (invisible). */
.topbar-company--shine {
  background-image: linear-gradient(
    100deg,
    var(--enlivn-ink) 0%, var(--enlivn-ink) 42%,
    #7dd3fc 47%, var(--enlivn-primary) 50%, #7dd3fc 53%,
    var(--enlivn-ink) 58%, var(--enlivn-ink) 100%
  );
  background-size: 300% 100%;
  background-position: 100% 0;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: topbar-shine 9s ease-in-out infinite;
}
@keyframes topbar-shine {
  /* The sweep occupies the first ~2/3; the rest is the pause between passes. */
  0%   { background-position: 100% 0; }
  65%  { background-position: 0 0; }
  100% { background-position: 0 0; }
}
/* Reduced motion: no sweep, and the text must go back to being painted by
   `color` — leaving the fill transparent would hide the name entirely. */
@media (prefers-reduced-motion: reduce) {
  .topbar-company--shine {
    animation: none;
    background-image: none;
    -webkit-text-fill-color: currentColor;
  }
}

@media (max-width: 575.98px) {
  /* Still the biggest thing in the bar, just scaled to the width available
     beside the logo, the bell and Log out. */
  .topbar-company { max-width: 10rem; font-size: 1.15rem; }
  .topbar-divider { margin: 0 0.6rem; }
}

.portal-layout { display: flex; align-items: stretch; }
/* Per-portal sidebar tint (2 Aug). Blue-tinted periwinkle is the corporate
   portal's and green-50 is the staff panel's, so the instructor portal gets its
   own warm amber — the three products stop reading as one. Scoped as a token
   override on the layout, so every sidebar rule below stays portal-agnostic. */
.portal-layout.portal-instructor { --portal-sidebar-bg: #fffbeb; }

.portal-sidebar {
  width: 16rem; flex-shrink: 0;                 /* w-64 */
  background: var(--portal-sidebar-bg);
  border-right: 1px solid var(--admin-border);
  padding: 0 0 1.5rem;
  position: sticky; top: var(--admin-topbar-h);
  height: calc(100vh - var(--admin-topbar-h));
  overflow-y: auto;
  transition: width 0.18s ease;
}
.portal-content {
  flex: 1; min-width: 0;
  background: #fff;
  padding: 2rem;
  min-height: calc(100vh - var(--admin-topbar-h));
}

/* Collapsed rail — icons only. Every label lives in a <span> precisely so this
   rule can hide it; a bare text node can't be targeted, which is why collapsing
   used to shrink the panel while the words stayed and wrapped. */
.portal-sidebar.is-collapsed { width: 4.75rem; }
.portal-sidebar.is-collapsed .sidebar-brand,
.portal-sidebar.is-collapsed .sidebar-heading,
.portal-sidebar.is-collapsed .sidebar-link span,
.portal-sidebar.is-collapsed .sidebar-logout span { display: none; }
.portal-sidebar.is-collapsed .sidebar-link,
.portal-sidebar.is-collapsed .sidebar-logout { justify-content: center; padding-inline: 0; }
.portal-sidebar.is-collapsed .sidebar-nav { padding-inline: 0.5rem; }
/* The collapsed rail has no words, so the icon carries the whole meaning —
   a CSS tooltip built from the link's own label (data-label). */
.portal-sidebar.is-collapsed .sidebar-link { position: relative; }
.portal-sidebar.is-collapsed .sidebar-link:hover::after {
  content: attr(data-label);
  position: absolute; left: calc(100% + 0.5rem); top: 50%;
  transform: translateY(-50%);
  background: var(--enlivn-ink); color: #fff;
  font-size: 0.75rem; font-weight: 600; white-space: nowrap;
  padding: 0.3rem 0.6rem; border-radius: 0.4rem;
  box-shadow: var(--enlivn-shadow); z-index: 20;
}

.sidebar-toggle {
  display: flex; justify-content: flex-end;
  padding: 0.75rem 0.75rem 0.25rem;
}
.sidebar-toggle button {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.75rem; height: 1.75rem;
  border: 0; border-radius: 0.4rem; background: transparent;
  color: #334155; line-height: 1; cursor: pointer;
  transition: color 0.15s ease, background-color 0.15s ease;
}
.sidebar-toggle button:hover { color: var(--enlivn-primary); background: rgba(255, 255, 255, 0.7); }

/* 19 Sep: was `padding: 1.5rem`, which left the lone portal chip stranded in a
   tall empty block reading as a stray label rather than the menu's eyebrow.
   Tightened so it sits just above the first group, like a section heading. */
.sidebar-brand {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.35rem 1.35rem 0.6rem;
  text-decoration: none;
}
.sidebar-brand .brand-word {
  font-size: 1.25rem; font-weight: 700; color: var(--admin-brand);
  font-family: "Plus Jakarta Sans", Inter, sans-serif; letter-spacing: -0.02em;
}
/* The portal tag: a dot + one uppercase word, pill-shaped. It says which of the
   three portals a shared session cookie has landed you in, so it should read as
   a status marker, not as a link or a heading competing with the menu. */
.sidebar-brand .brand-tag {
  display: inline-flex; align-items: center; gap: 0.4rem;
  font-size: 0.68rem; font-weight: 700;
  letter-spacing: 0.09em; text-transform: uppercase;
  background: var(--admin-brand-badge); color: var(--admin-brand);
  padding: 0.28rem 0.7rem; border-radius: 999px;
}
.sidebar-brand .brand-tag i { font-size: 0.4rem; opacity: 0.75; }

/* The company name, superscripted on "My workspace" (19 Sep). It replaces the
   portal chip: one mark saying both which portal this is and whose.

   Deliberately NOT the browser's tiny default <sup>: at 0.68rem-of-0.68rem it
   was unreadable, which is what "doesn't seem nice" meant. It is sized and
   weighted to be read — larger than the heading it rides, in brand blue, with
   the heading's letter-spacing removed so a real name doesn't look spaced out.
   A dotted underline marks it as editable without shouting. */
/* The modifier is declared BEFORE `.sidebar-heading` in this file and carries
   equal specificity (one class), so source order would let the base rule win.
   Qualifying it with the base class raises specificity to two classes, which
   makes the layout hold wherever the two end up in the file. */
.sidebar-heading.sidebar-heading--company {
  display: flex; align-items: baseline; gap: 0.3rem;
  /* The name can be long; let it ellipsis rather than push the row wider. */
  min-width: 0;
}
/* It is a <span>, NOT a <sup> (19 Sep fix). Bootstrap's reset styles `sup` with
   `line-height: 0` + `font-size: .75em` + `position: relative`, and the zero
   line box combined with the `overflow: hidden` this needs for its ellipsis
   clipped the glyphs to nothing: the name rendered INVISIBLE and only its
   dotted underline showed. Fighting three inherited declarations to keep a tag
   whose only job is a visual raise was the wrong trade — the raise is one
   `transform` here, and nothing in the reset can reach it. */
.sidebar-company {
  max-width: 9.5rem;
  display: inline-block; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap;
  /* The superscript lift. `transform` rather than `position: relative; top:`
     so it never participates in layout and cannot nudge the row's height. */
  transform: translateY(-0.28em);
  font-family: "Plus Jakarta Sans", Inter, sans-serif;
  line-height: 1.25;
  font-size: 0.82rem; font-weight: 800;
  letter-spacing: 0;                 /* the heading's 0.09em would space a NAME out */
  text-transform: none;              /* a company name keeps its own casing */
  color: var(--enlivn-primary);
  border-bottom: 1px dotted rgba(1, 143, 238, 0.5);
  cursor: text;
}
.sidebar-company:hover { border-bottom-style: solid; }
.sidebar-company:focus-visible {
  outline: 2px solid var(--enlivn-primary); outline-offset: 2px; border-radius: 0.2rem;
}
/* The in-place editor. Sized like the text it replaces so the row doesn't jump
   when the input appears. */
.sidebar-company-input {
  width: 9.5rem;
  font-family: "Plus Jakarta Sans", Inter, sans-serif;
  font-size: 0.82rem; font-weight: 800; letter-spacing: 0;
  color: var(--enlivn-ink);
  border: 1px solid var(--enlivn-primary); border-radius: 0.3rem;
  padding: 0.05rem 0.3rem; background: #fff;
}
.sidebar-company-input:focus { outline: none; box-shadow: 0 0 0 3px rgba(1, 143, 238, 0.18); }
/* The collapsed rail hides its headings, so the name goes with them. */
.portal-sidebar.is-collapsed .sidebar-company { display: none; }

.sidebar-nav { padding: 0 0.75rem; }
/* Section captions ("My workspace", "Company") — they group the menu, so they
   read as labels, not links. */
.sidebar-heading {
  padding: 1rem 0.85rem 0.4rem;
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.09em;
  text-transform: uppercase; color: #94a3b8;
}
.sidebar-link {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.55rem 0.85rem; margin-bottom: 0.15rem;
  border-radius: 0.65rem;
  color: #475569; font-weight: 500; font-size: 0.875rem;
  text-decoration: none; white-space: nowrap;
  transition: background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
}
.sidebar-link i { font-size: 1.1rem; flex-shrink: 0; opacity: 0.85; }
.sidebar-link:hover { background: #fff; color: var(--enlivn-primary); }
.sidebar-link:hover i { opacity: 1; }
/* The active item is the brand statement of the whole panel: filled, lifted,
   and its icon at full strength. */
.sidebar-link.active {
  background: var(--enlivn-primary); color: #fff; font-weight: 600;
  box-shadow: var(--enlivn-shadow-btn);
}
.sidebar-link.active i { opacity: 1; }

/* Credits in the menu: the word stays the destination, the balance sits under
   it as a subscript (19 Sep, lead's call). The row grows to two lines, so its
   <span> becomes a column — every other link keeps the flex row above. */
.sidebar-link--credit { align-items: center; }
.sidebar-link--credit > span { display: flex; flex-direction: column; line-height: 1.2; }
.sidebar-credit-value {
  font-family: "Plus Jakarta Sans", Inter, sans-serif;
  font-size: 0.78rem; font-weight: 700;
  color: var(--enlivn-credit);
}
.sidebar-link--credit i { color: var(--enlivn-credit); opacity: 1; }
/* On the ACTIVE (brand-filled) row, gold-on-blue fails contrast — the whole row
   is already the emphasis, so the figure rides on white at full opacity. */
.sidebar-link--credit.active .sidebar-credit-value,
.sidebar-link--credit.active i { color: #fff; opacity: 0.95; }

/* Collapsed rail: the label is hidden, so the figure rides the coin as a badge
   — the balance must survive a collapse, or "visible throughout" stops being
   true the moment someone narrows the menu. */
.portal-sidebar.is-collapsed .sidebar-link--credit { position: relative; }
.portal-sidebar.is-collapsed .sidebar-link--credit::before {
  content: attr(data-credit-badge);
  position: absolute; top: 0.1rem; right: 0.55rem;
  min-width: 1.05rem; padding: 0 0.25rem;
  border-radius: 999px;
  background: var(--enlivn-credit); color: #fff;
  font-size: 0.6rem; font-weight: 700; line-height: 1.05rem;
  text-align: center;
}

.sidebar-logout {
  display: flex; align-items: center; gap: 0.75rem;
  margin: 1.5rem 0.75rem 0; padding: 1.5rem 0.75rem 0;
  border-top: 1px solid var(--admin-border);
  color: var(--enlivn-ink); font-weight: 600; font-size: 0.9rem;
  text-decoration: none; background: none; border-left: 0; border-right: 0; border-bottom: 0;
  width: calc(100% - 1.5rem); cursor: pointer;
}
.sidebar-logout:hover { color: var(--enlivn-primary); }

/* Page header — big bold display title + muted subtitle (matches Next.js). */
.page-title {
  font-size: 2.25rem; font-weight: 800; margin-bottom: 0.25rem;
  letter-spacing: -0.035em; color: var(--enlivn-ink);
}
.page-subtitle { color: var(--enlivn-muted); font-size: 1.05rem; margin-bottom: 0; }

/* Segmented tabs — the shadcn TabsList: a muted pill rail whose active tab is
   a raised white chip. */
.seg-tabs {
  display: inline-flex; gap: 0.25rem;
  background: var(--admin-muted-bg);
  border-radius: var(--admin-radius);
  padding: 0.25rem;
}
.seg-tabs a {
  padding: 0.4rem 0.9rem; border-radius: calc(var(--admin-radius) - 2px);
  font-size: 0.875rem; font-weight: 500; color: var(--enlivn-muted);
  text-decoration: none; white-space: nowrap;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.seg-tabs a:hover { color: var(--enlivn-ink); }
.seg-tabs a.active {
  background: #fff; color: var(--enlivn-ink); font-weight: 600;
  box-shadow: 0 1px 2px rgba(11, 18, 32, 0.08);
}

/* Cards / tables */
.panel {
  background: #fff; border: 1px solid var(--admin-border);
  border-radius: 0.75rem; box-shadow: var(--enlivn-shadow-sm);
}
.panel .table { margin-bottom: 0; }
.table thead th {
  font-size: 0.875rem; font-weight: 600; color: var(--enlivn-ink);
  border-bottom: 1px solid var(--admin-border);
  padding: 1rem;
}
.table td { vertical-align: middle; padding: 1rem; }
tr[data-drawer-url], [data-drawer-url] { cursor: pointer; }
tr[data-drawer-url]:hover { background: #f8fafc; }

/* KPI tiles — white card, rounded, soft border; the first two carry a round
   tinted icon badge, the rest are a headline + "(show all)" link. */
.kpi-card { padding: 1.5rem; display: flex; align-items: center; gap: 1rem; }
.kpi-value {
  font-family: "Plus Jakarta Sans", sans-serif;
  font-size: 1.875rem; font-weight: 700; color: var(--enlivn-ink);
  line-height: 1.15;
}
.kpi-label { font-size: 1rem; font-weight: 600; color: var(--enlivn-ink); }
.kpi-icon {
  width: 3rem; height: 3rem; border-radius: 999px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: #e0f2fe; color: var(--enlivn-primary); font-size: 1.35rem;
}
.kpi-link { color: var(--enlivn-primary); font-size: 0.95rem; text-decoration: underline; }

/* The credit colour as a utility, for figures outside a tile (the Credits
   page's own headline). Named for the meaning, not the hue. */
.text-credit { color: var(--enlivn-credit); }

/* The dashboard's credits band (19 Sep, second pass). Wider than a KPI tile on
   purpose: moved out of the topbar, the balance gets room to be read rather
   than glanced past — figure, what a credit is worth, and the way to the
   statement, on one line. */
.credit-panel {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  padding: 1.35rem 1.5rem;
  background: linear-gradient(110deg, #fffbeb 0%, #fff 55%);
  border-color: var(--enlivn-credit-border);
}
.credit-panel-main { display: flex; align-items: center; gap: 1rem; min-width: 0; }
.credit-panel-icon {
  width: 3.25rem; height: 3.25rem; border-radius: 999px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--enlivn-credit-soft); color: var(--enlivn-credit);
  font-size: 1.5rem;
}
.credit-panel-value {
  font-family: "Plus Jakarta Sans", Inter, sans-serif;
  font-size: 2.1rem; font-weight: 800; line-height: 1.1;
  color: var(--enlivn-credit);
}
.credit-panel-unit {
  margin-left: 0.4rem;
  font-size: 0.95rem; font-weight: 600; color: var(--enlivn-credit); opacity: 0.75;
}
.credit-panel-note { font-size: 0.85rem; color: var(--enlivn-muted); }
/* The link keeps the chrome button's metrics, like every other portal control. */
.credit-panel-link {
  height: var(--enlivn-control-h);
  display: inline-flex; align-items: center;
  flex-shrink: 0; white-space: nowrap;
}
.credit-panel-link:hover {
  border-color: var(--enlivn-credit-border);
  background: var(--enlivn-credit-soft);
  color: var(--enlivn-credit);
}

/* Bootstrap's switch, restyled to the shadcn toggle in the offerings table. */
.form-switch .form-check-input {
  width: 2.75rem; height: 1.5rem; cursor: pointer;
  background-color: #cbd5e1; border-color: #cbd5e1;
}
.form-switch .form-check-input:checked {
  background-color: var(--enlivn-primary); border-color: var(--enlivn-primary);
}
.form-switch .form-check-input:focus { box-shadow: none; }

/* Status badges from TextChoices values */
.status-badge {
  display: inline-block; padding: 0.28rem 0.7rem;
  border-radius: 999px; font-size: 0.75rem; font-weight: 600;
  background: #eef2f7; color: #475569; text-transform: capitalize;
}
.status-badge.is-active, .status-badge.is-success, .status-badge.is-confirmed,
.status-badge.is-verified, .status-badge.is-reconciled,
/* BookingAssignment.State — the instructor's own answer to a session. */
.status-badge.is-accepted { background: #e7f6ec; color: #147a3d; }
.status-badge.is-pending, .status-badge.is-unapproved, .status-badge.is-under_review,
.status-badge.is-incomplete, .status-badge.is-issued, .status-badge.is-open,
.status-badge.is-profile_incomplete, .status-badge.is-initial { background: #fdf3d7; color: #8a5500; }
.status-badge.is-rejected, .status-badge.is-blocked, .status-badge.is-cancelled,
.status-badge.is-declined, .status-badge.is-failure { background: #fdeaea; color: #b42318; }
.status-badge.is-completed, .status-badge.is-booking_confirmed { background: #e8effc; color: #1d4ed8; }
/* Credit ledger (19 Aug): credits earned read as money in, credits consumed as
   the balance doing its job — spending is the point of a balance, not a fault,
   so `debit` is neutral rather than red. */
.status-badge.is-credit { background: #e7f6ec; color: #147a3d; }
.status-badge.is-debit { background: #eef2f7; color: #475569; }

/* Drawer + modal */
.portal-drawer { --bs-offcanvas-width: 540px; }
.portal-drawer .offcanvas-body { padding: 1.5rem; }
/* Wide variant (~60vw), matching the staff panel's detail drawers. Applied by
   portal.js when the injected partial's root carries [data-drawer-wide] — set
   the attribute on the partial, never widen the shell. */
.portal-drawer.is-wide { --bs-offcanvas-width: 60vw; }
@media (max-width: 991.98px) { .portal-drawer.is-wide { --bs-offcanvas-width: 100%; } }

/* A drawer opened OVER another slides the one behind slightly left and dims it
   (14 Aug), so the stack reads as depth rather than as a replacement. The
   offcanvas is already `transform: none` when shown, so this composes with
   Bootstrap's own slide — and the transition is declared on the element, not
   the modifier, or the shift would snap on the way back. */
.portal-drawer.is-behind {
  transform: translateX(-2.75rem) scale(0.985);
  filter: brightness(0.94);
  transition: transform 0.3s ease, filter 0.3s ease;
}
.portal-drawer.is-behind .offcanvas-body { overflow: hidden; }
@media (prefers-reduced-motion: reduce) { .portal-drawer.is-behind { transition: none; } }

/* Tab strip inside a portal drawer (14 Aug) — the twin of the staff panel's
   shadcn segmented control: a muted track with the active tab as a raised white
   chip. Bootstrap's nav-pills paint a solid blue chip and blue text links,
   which read as a different product beside every other Enlivn drawer. */
.drawer-tabs {
  display: inline-flex; align-items: center; gap: 0.125rem;
  padding: 0.25rem; border-radius: 0.5rem;
  background: rgba(15, 23, 42, 0.05);
}
.drawer-tab {
  display: inline-flex; align-items: center; gap: 0.4rem;
  border: 0; background: transparent; cursor: pointer;
  padding: 0.4rem 0.75rem; border-radius: 0.375rem;
  font-size: 0.875rem; font-weight: 500; color: var(--enlivn-muted);
  white-space: nowrap; transition: background 0.15s ease, color 0.15s ease;
}
.drawer-tab:hover { color: var(--enlivn-ink); }
.drawer-tab.is-active {
  background: #fff; color: var(--enlivn-ink);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.09);
}
/* The strip scrolls rather than wrapping: four labels in a narrow drawer would
   otherwise stack into two rows and stop reading as one control. */
@media (max-width: 575.98px) {
  .drawer-tabs { display: flex; width: 100%; overflow-x: auto; }
}
.drawer-kv { display: grid; grid-template-columns: 10rem 1fr; gap: 0.45rem 1rem; font-size: 0.925rem; }
.drawer-kv dt { color: var(--enlivn-muted); font-weight: 500; }
.drawer-kv dd { margin-bottom: 0; color: var(--enlivn-ink); }

/* The `.drawer-section*` and `.session-*` families lived here until 17 Aug —
   a sectioned read drawer and the instructor's own header/facts/footer set,
   both saying what `.record-*` (further down) now says once. Three dresses for
   one idea is two too many, so the session drawer was moved onto the shared
   one and these were deleted rather than left as a tempting third option. */

/* Auth pages --------------------------------------------------------------- */
/* A brand-tinted stage: a soft primary glow at the top over the surface grey,
   nothing that competes with the card. Restricted to the palette tokens. */
.auth-glass-body {
  min-height: 100vh;
  background:
    radial-gradient(60rem 30rem at 85% -10%, rgba(5, 187, 254, 0.12), transparent 60%),
    linear-gradient(180deg, var(--enlivn-tint) 0%, #ffffff 100%);
  background-attachment: fixed;
}
.auth-shell {
  display: flex;
  justify-content: center;
  padding-block: clamp(1.75rem, 5vw, 3.5rem);
}
.auth-logo { height: 2.75rem; width: auto; }
.auth-title {
  font-family: "Plus Jakarta Sans", "Inter", system-ui, sans-serif;
  font-weight: 800;
  letter-spacing: -0.035em;
  color: var(--enlivn-ink);
  font-size: clamp(1.6rem, 3.2vw, 2rem);
  margin-bottom: 0.35rem;
}
.auth-subtitle { color: var(--enlivn-muted); margin-bottom: 0; }

.auth-card {
  border: 1px solid rgba(255, 255, 255, 0.6);
  border-radius: 1.25rem;
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
  overflow: hidden;
  background: rgba(255, 255, 255, 0.75);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}
.auth-card-title {
  font-family: "Plus Jakarta Sans", "Inter", system-ui, sans-serif;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--enlivn-ink);
}
.auth-card .form-label { font-weight: 600; color: var(--enlivn-ink); font-size: 0.9rem; margin-bottom: 0.4rem; }
.auth-card .form-control,
.auth-card .form-select {
  background-color: #f8fafc;
  border: 1px solid transparent;
  transition: all 0.2s ease;
}
.auth-card .form-control:focus,
.auth-card .form-select:focus {
  background-color: #ffffff;
  border-color: rgba(var(--enlivn-primary-rgb), 0.55);
  box-shadow: 0 0 0 4px rgba(var(--enlivn-primary-rgb), 0.1);
}
.auth-card .btn-primary {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 4px 10px -4px rgba(var(--enlivn-primary-rgb), 0.6);
}
.auth-card .btn-primary:active {
  transform: scale(0.98);
}
.auth-divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  color: var(--enlivn-muted);
  font-size: 0.8rem;
  margin-block: 1.25rem;
}
.auth-divider::before, .auth-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--bs-border-color);
}
/* One-time-password field: monospaced, wide tracking so 6 digits read cleanly. */
.otp-input {
  font-family: "SFMono-Regular", ui-monospace, Menlo, Consolas, monospace;
  letter-spacing: 0.4em;
  font-weight: 600;
}
.otp-input::placeholder { letter-spacing: normal; font-weight: 400; }

/* Auth card widths (login narrow; single-step signups wider) */
.auth-narrow { max-width: 30rem; }
.auth-wide { max-width: 42rem; }
.auth-xwide { max-width: 48rem; }
/* Single-step signups spread wide so their two columns sit side by side and the
   whole form fits one viewport without scrolling. Everything below is tuned to
   that goal: less chrome, denser fields, a shorter heading. */
.auth-signup { max-width: 62rem; }
.auth-signup-lg { max-width: 74rem; }

/* Signups are dense, so they get less top breathing room than the short login. */
.auth-shell:has(.auth-signup),
.auth-shell:has(.auth-signup-lg) { padding-block: 1.25rem; }

/* A smaller heading on the signups — they need the vertical room for fields. */
.auth-title-sm { font-size: clamp(1.35rem, 2.4vw, 1.6rem); }

/* Compact fields, scoped to the signup cards so the rest of the site is untouched. */
.auth-signup .form-control, .auth-signup .form-select,
.auth-signup-lg .form-control, .auth-signup-lg .form-select {
  padding: 0.35rem 0.6rem;
  font-size: 0.85rem;
  min-height: auto;
}
.auth-signup .form-label, .auth-signup-lg .form-label {
  margin-bottom: 0.15rem;
  font-size: 0.8rem;
}
.auth-signup .form-text, .auth-signup-lg .form-text { font-size: 0.72rem; margin-top: 0.1rem; }

/* Instructor signup: flow the four sections into two columns (masonry-style,
   packed by height) so ~20 fields fit one screen. Each section stays whole. */
.signup-masonry { column-count: 2; column-gap: 1.75rem; }
.signup-section {
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  margin-bottom: 0.85rem;
}
.signup-section-title {
  display: inline-block;
  font-family: "Plus Jakarta Sans", "Inter", system-ui, sans-serif;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--enlivn-primary);
  background: var(--enlivn-tint);
  padding: 0.25rem 0.65rem;
  border-radius: 999px;
  margin-bottom: 0.75rem;
}
@media (max-width: 767.98px) {
  .signup-masonry { column-count: 1; }
}

/* Drag reorder (the ONE place: staff offerings rank) */
.reorder-handle { cursor: grab; color: #cbd5e1; }
.reorder-handle:active { cursor: grabbing; }
.sortable-ghost { opacity: 0.4; background: var(--enlivn-tint) !important; }

/* ==========================================================================
   Offering detail (public) — Booking.com-style gallery + section nav + lightbox
   ========================================================================== */
.offering-title {
  font-size: clamp(1.6rem, 3vw, 2.25rem);
  line-height: 1.15;
  margin: 0;
}
/* Exponent placement: `vertical-align: top` puts the badge's top edge on the
   title's line-box top, then the nudge lifts it clear of the cap-height so it
   reads as a superscript on the name — not as a chip beside it. Zero
   line-height keeps it from stretching the h1's own line box. */
.offering-title-badge {
  vertical-align: top;
  position: relative;
  top: -0.15em;
  margin-left: 0.35rem;
  line-height: 1;
}

/* Duration · group size · price. Default (cards) is the quiet inline row it has
   always been; --hero (detail page) promotes the same three facts to pills,
   because they are the page's marketing payload and read as fine print. */
.offering-meta { gap: 1.5rem; font-size: 0.875rem; color: var(--enlivn-muted); }
.offering-meta--hero { gap: 0.5rem; font-size: 0.8125rem; }
.offering-meta--hero > span {
  display: inline-flex;
  align-items: center;
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  font-weight: 600;
  color: #0b5d97;
  background: var(--enlivn-tint);
  border: 1px solid rgba(var(--enlivn-primary-rgb), 0.18);
}
.offering-meta--hero > span i { color: var(--enlivn-primary); }
/* Price is the one the visitor is hunting for — it gets the filled treatment. */
.offering-meta--hero .offering-meta-price {
  color: #fff;
  background: linear-gradient(135deg, var(--enlivn-primary), var(--enlivn-accent));
  border-color: transparent;
  box-shadow: var(--enlivn-shadow-btn);
}
.offering-meta--hero .offering-meta-price i { color: #fff; }
/* The rating pill keeps the tinted treatment; only its star is amber. */
.offering-meta--hero .offering-meta-rating i { color: #f5b301; }

/* Category chips, now closing the Overview section. */
.offering-tag {
  display: inline-block;
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--enlivn-muted);
  background: var(--enlivn-surface);
  border: 1px solid var(--bs-border-color);
}

/* Gallery: big admin-pinned hero on the left, a 2×2 thumbnail grid on the right.
   2:1 columns + a 22rem band keep every tile landscape-ish (the old 1.6:1 at
   26rem produced awkward portrait thumbs); one wrapper radius + soft shadow
   reads as a single composed unit. */
.offering-gallery {
  display: grid;
  grid-template-columns: 2fr 1.05fr;
  gap: 0.6rem;
  height: 22rem;
  border-radius: 1.1rem;
  overflow: hidden;
  box-shadow: var(--enlivn-shadow-sm);
}
.offering-gallery--solo { grid-template-columns: 1fr; }
.gallery-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 0.6rem;
  min-height: 0;
}
.gallery-tile {
  position: relative;
  padding: 0;
  border: 0;
  overflow: hidden;
  border-radius: 0.6rem;
  /* The tile's own backdrop, seen beside a `contain`ed medium whose aspect
     doesn't match the box. A soft tint rather than flat grey so a portrait
     photo sits on something composed. */
  background: linear-gradient(160deg, #eef2f7 0%, #e2e8f0 100%);
  cursor: pointer;
  min-height: 0;
}
.gallery-hero { border-radius: 0.85rem; }
/* The hero's grid cell: anchors the mute toggle OVER the tile (a button can't
   nest a button, so the toggle is a sibling of the hero, not a child). */
.gallery-hero-cell { position: relative; min-height: 0; display: flex; }
.gallery-hero-cell .gallery-hero { width: 100%; height: 100%; }
.gallery-mute {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  border: 0;
  border-radius: 999px;
  background: rgba(8, 12, 20, 0.55);
  color: #fff;
  font-size: 1rem;
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.gallery-mute:hover { background: rgba(8, 12, 20, 0.8); }
/* The detail gallery shows every medium WHOLE (19 Sep — this is the surface the
   crop was first reported on). `contain` rather than `cover`, over a tinted
   backdrop: no head is cut off and no panorama loses its ends.

   The hero can't use `.media-fit`'s blurred backdrop — its <img> and <video>
   are SWAPPED by the rotation script, so an inline background set at render
   would keep showing the first medium behind the third. A flat tint is the
   honest option here: it never disagrees with what is on the tile. The cards,
   whose media never changes, do get the blur. */
.gallery-tile img,
.gallery-tile video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  transition: transform 0.35s ease;
}
/* 1.03, not 1.05: a CONTAINED image already touches the tile's edges on its
   long axis, so a bigger hover zoom pushes it past them and clips the very
   thing this change exists to show whole. */
.gallery-tile:hover img,
.gallery-tile:hover video { transform: scale(1.03); }
/* Marks a thumbnail tile whose medium is a video. */
.gallery-video-badge {
  position: absolute;
  right: 0.4rem;
  top: 0.4rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 999px;
  background: rgba(8, 12, 20, 0.55);
  color: #fff;
  font-size: 0.9rem;
  pointer-events: none;
}
.gallery-tile:focus-visible { outline: 3px solid var(--enlivn-primary); outline-offset: 2px; }
/* Hero auto-rotation: the countdown bar along the tile's bottom edge (JS
   refills it each cycle) and the cross-fade the image swap rides on. */
.gallery-progress {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 4px;
  background: rgba(255, 255, 255, 0.35);
  z-index: 1;
}
.gallery-progress-bar {
  display: block;
  height: 100%;
  width: 0;
  background: var(--enlivn-primary);
}
.gallery-hero img,
.gallery-hero video { transition: transform 0.35s ease, opacity 0.25s ease; }
.gallery-hero.is-swapping img,
.gallery-hero.is-swapping video { opacity: 0; }
.gallery-more {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(11, 18, 32, 0.55);
  color: #fff;
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: 0.01em;
}
.offering-gallery-empty {
  height: 26rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  border: 1px dashed var(--bs-border-color);
  border-radius: 1rem;
  background: var(--enlivn-surface);
}

/* The enquiry card's header band. With the page's header CTAs removed (29 Jul)
   this heading IS the call to action, so it gets a brand gradient bleeding to
   the card's edges — the negative margins cancel the card's own `p-3`, and the
   top radius matches `.panel`'s 0.75rem so the corners stay clean. */
.enquiry-head {
  margin: -1rem -1rem 1rem;
  padding: 0.9rem 1rem;
  border-radius: 0.75rem 0.75rem 0 0;
  background: linear-gradient(135deg, var(--enlivn-primary), var(--enlivn-accent));
  color: #fff;
}
.enquiry-head-title {
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.25;
  color: #fff;
}
.enquiry-head-sub { font-size: 0.8125rem; color: rgba(255, 255, 255, 0.88); }

/* Compact enquiry card (the right column of the top row). */
.enquiry-card .form-control,
.enquiry-card .form-select {
  padding: 0.4rem 0.65rem;
  font-size: 0.875rem;
  min-height: auto;
}
/* Ride along while the left column scrolls (also why the page has no dead band
   under the gallery — content and form share one row). */
.enquiry-sticky { position: sticky; top: 5.5rem; }

/* Two-step enquiry funnel (FR-PUB-5) ---------------------------------------- */
.enquiry-steps { display: flex; align-items: center; gap: 0.4rem; }
.step-dot {
  width: 1.35rem; height: 1.35rem; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px; font-size: 0.7rem; font-weight: 700;
  background: var(--enlivn-surface); color: var(--enlivn-muted);
  border: 1px solid var(--bs-border-color);
  transition: background-color 0.2s ease, color 0.2s ease;
}
.step-dot.active { background: var(--enlivn-primary); color: #fff; border-color: var(--enlivn-primary); }
/* Step 1 is behind them once the enquiry is posted — a tick, not a number. */
.step-dot.done { background: var(--bs-success, #198754); color: #fff; border-color: transparent; }
.step-name { font-size: 0.72rem; font-weight: 600; color: var(--enlivn-muted); }
.step-line { flex: 1; height: 1px; background: var(--bs-border-color); }

/* Availability: the ONLINE chip is deliberately a different colour from the
   city chips (which are Bootstrap's neutral `text-bg-light`). Online is a
   different KIND of availability, not another place on the same list, so the
   two must not read as one run of identical badges. Green rather than the
   brand blue, which on these pages already means "promoted price". */
.availability-online {
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.35rem 0.7rem; border-radius: 999px;
  border: 1px solid #198754; background: rgba(25, 135, 84, 0.1);
  color: #146c43; font-size: 0.8125rem; font-weight: 600;
}

/* Service pill: the opened offering, locked. The selectable `--on` variant went
   with the add-more-services chips (13 Sep) — an enquiry names ONE service now,
   so the only pill left is the locked one, and a hover/selected state would
   advertise a control that no longer exists. */
.service-pick {
  display: inline-flex; align-items: center;
  padding: 0.25rem 0.7rem; margin: 0;
  border: 1px solid var(--bs-border-color); border-radius: 999px;
  font-size: 0.78rem; font-weight: 600; color: var(--enlivn-muted);
  background: #fff; user-select: none;
}
.service-pick--locked {
  background: var(--enlivn-primary); border-color: var(--enlivn-primary);
  color: #fff; cursor: default;
}

/* City multi-select: a form-select-looking trigger over a checkbox menu. */
.city-toggle { cursor: pointer; font-size: 0.875rem; padding: 0.4rem 0.65rem; }
.city-menu { max-height: 14rem; overflow-y: auto; box-shadow: var(--enlivn-shadow); }
.city-menu .dropdown-item { cursor: pointer; }
.city-menu .dropdown-item:active { background: var(--enlivn-tint); color: inherit; }

/* Inline notes inside the funnel (fork + registered banner). */
.enquiry-note {
  font-size: 0.8rem; font-weight: 500;
  padding: 0.5rem 0.7rem; border-radius: 0.5rem;
  background: var(--enlivn-surface); color: var(--enlivn-muted);
  border: 1px solid var(--bs-border-color);
}
.enquiry-note--good {
  background: #ecfdf5; border-color: #a7f3d0; color: #047857;
}
.enquiry-card fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }

/* Staff-authored rich description (drawer WYSIWYG output: h2/h3, b/i, lists).
   Noto Sans Devanagari carries Hindi/Sanskrit copy that Inter lacks. */
.rich-text {
  font-family: "Inter", "Noto Sans Devanagari", system-ui, sans-serif;
  line-height: 1.7;
}
.rich-text h2, .rich-text h3 {
  color: var(--enlivn-ink);
  margin: 1.25rem 0 0.5rem;
}
.rich-text h2 { font-size: 1.35rem; }
.rich-text h3 { font-size: 1.1rem; }
.rich-text p { margin-bottom: 0.75rem; }
.rich-text ul, .rich-text ol { padding-left: 1.25rem; margin-bottom: 0.75rem; }
/* Article-style clamp: the truncated description's FINAL block renders inline
   so "See more" sits on the same line as the cut-off text (the button is the
   container's last child; :nth-last-child(2) is that final text block). */
.rich-text-clamp > *:nth-last-child(2) { display: inline; }
.see-more-link {
  display: inline;
  padding: 0;
  border: 0;
  background: none;
  color: var(--enlivn-primary);
  font-weight: 600;
  cursor: pointer;
}
.see-more-link:hover { text-decoration: underline; }
.rich-text b, .rich-text strong { color: var(--enlivn-ink); }
.enquiry-tiers {
  background: var(--enlivn-surface);
  border: 1px solid var(--bs-border-color);
  border-radius: 0.6rem;
  padding: 0.5rem 0.75rem;
}

/* Sticky left section menu that highlights the section in view (scrollspy). */
/* A continuous hairline track runs the height of the nav (the ::before), and the
   active link paints a brand-coloured segment over it (the link's ::before) —
   so the menu reads as a progress rail through the page rather than a stack of
   unrelated buttons. The track is on the WRAPPER, not per-link, so the gaps
   between links stay part of the line. */
.section-nav {
  position: sticky;
  top: 5.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding-left: 0.9rem;
}
.section-nav::before {
  content: "";
  position: absolute;
  left: 0; top: 0.35rem; bottom: 0.35rem;
  width: 2px;
  border-radius: 2px;
  background: var(--bs-border-color);
}
.section-nav-link {
  position: relative;
  padding: 0.5rem 0.85rem;
  border-radius: 0.5rem;
  color: var(--enlivn-muted);
  font-weight: 600;
  font-size: 0.9rem;
  text-decoration: none;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.section-nav-link::before {
  content: "";
  position: absolute;
  left: -0.9rem; top: 50%;
  width: 2px; height: 0;
  border-radius: 2px;
  background: var(--enlivn-primary);
  transform: translateY(-50%);
  transition: height 0.25s ease;
}
.section-nav-link:hover { background: var(--enlivn-surface); color: var(--enlivn-ink); }
.section-nav-link.active {
  background: var(--enlivn-tint);
  color: var(--enlivn-primary);
}
/* The marker grows from the link's middle, so switching sections reads as the
   rail travelling with the reader. */
.section-nav-link.active::before { height: 100%; }
@media (prefers-reduced-motion: reduce) {
  .section-nav-link::before { transition: none; }
}

/* Sections: clear the sticky navbar when jumped to, separated by a hairline. */
.offering-section { scroll-margin-top: 5.5rem; }
.offering-section + .offering-section {
  border-top: 1px solid var(--bs-border-color);
  margin-top: 2rem;
  padding-top: 2rem;
}
.benefit-list { list-style: none; padding-left: 0; }
.benefit-list li { margin-bottom: 0.5rem; }

/* YouTube facades: thumbnail + play button per admin-added link; the real
   iframe only ever lives inside #ytModal, injected on click. */
.yt-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  gap: 1rem;
}
.yt-card { position: relative; }
.yt-thumb {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  padding: 0;
  border: 0;
  border-radius: 0.75rem;
  overflow: hidden;
  background: #000;
  cursor: pointer;
}
.yt-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.35s ease;
}
.yt-thumb:hover img { transform: scale(1.05); }
.yt-thumb:focus-visible { outline: 3px solid var(--enlivn-primary); outline-offset: 2px; }
.yt-play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  pointer-events: none;
}
.yt-play i {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  padding-left: 0.2rem;
  border-radius: 999px;
  background: rgba(8, 12, 20, 0.62);
  font-size: 1.5rem;
  transition: background-color 0.15s ease, transform 0.15s ease;
}
.yt-thumb:hover .yt-play i { background: var(--enlivn-primary); transform: scale(1.06); }
.yt-external {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 0.5rem;
  background: rgba(8, 12, 20, 0.55);
  color: #fff;
  font-size: 1.05rem;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.yt-external:hover { background: #ff0000; color: #fff; }

/* Lightbox: full-screen photo viewer opened from any gallery tile. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 1080;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: clamp(1rem, 4vw, 3rem);
  background: rgba(8, 12, 20, 0.92);
}
.lightbox[hidden] { display: none; }
.lightbox-stage {
  margin: 0;
  max-width: 90vw;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
}
.lightbox-stage img,
.lightbox-stage video {
  max-width: 100%;
  /* Leaves room for the doubled thumb strip below without overlapping it. */
  max-height: 66vh;
  object-fit: contain;
  border-radius: 0.5rem;
  box-shadow: 0 20px 60px -20px rgba(0, 0, 0, 0.8);
}
.lightbox-counter { color: rgba(255, 255, 255, 0.85); font-size: 0.85rem; }
/* Bottom strip: every medium as a pickable thumb; the active one is ringed. */
.lightbox-strip {
  position: absolute;
  left: 50%;
  bottom: 1rem;
  transform: translateX(-50%);
  display: flex;
  gap: 0.4rem;
  max-width: 90vw;
  overflow-x: auto;
  padding: 0.25rem;
}
/* 28 Jul: doubled (was 4.25×3rem) — at the old size the strip was hard to read
   as a picker on a large screen. The stage's max-height gives it the room. */
.lightbox-thumb {
  position: relative;
  flex: 0 0 auto;
  width: 8.5rem;
  height: 6rem;
  padding: 0;
  border: 2px solid transparent;
  border-radius: 0.4rem;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.08);
  cursor: pointer;
  opacity: 0.65;
  transition: opacity 0.15s ease, border-color 0.15s ease;
}
.lightbox-thumb:hover { opacity: 1; }
.lightbox-thumb.active { opacity: 1; border-color: var(--enlivn-primary); }
.lightbox-thumb img,
.lightbox-thumb video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.lightbox-thumb-play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 1.6rem;
  background: rgba(8, 12, 20, 0.35);
  pointer-events: none;
}
.lightbox-nav,
.lightbox-close {
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.lightbox-nav { width: 3rem; height: 3rem; font-size: 1.25rem; flex-shrink: 0; }
.lightbox-nav:hover,
.lightbox-close:hover { background: rgba(255, 255, 255, 0.28); }
.lightbox-close {
  position: absolute;
  top: 1.25rem;
  right: 1.25rem;
  width: 2.5rem;
  height: 2.5rem;
  font-size: 1.1rem;
}

/* Responsive: stack the gallery and drop the sticky nav on small screens. */
@media (max-width: 991.98px) {
  .offering-gallery,
  .offering-gallery--solo {
    grid-template-columns: 1fr;
    height: auto;
  }
  .gallery-hero { height: 15rem; }
  .gallery-grid { grid-template-rows: none; grid-auto-rows: 6rem; }
  .lightbox-nav { width: 2.5rem; height: 2.5rem; }
}


/* ── Thank-you modal (Contact) ─────────────────────────────────────────────
   Bootstrap's `.fade` only cross-fades and slides the dialog down a little.
   An acknowledgement should feel like it SETTLES, so the dialog also rises and
   scales the last few percent — the same "arrives, then rests" motion the
   admin panel's modal entrance uses. `prefers-reduced-motion` drops it to a
   plain fade rather than removing the feedback entirely. */
.thanks-modal .modal-content {
  border-radius: 1.25rem;
  box-shadow: 0 1.5rem 3.5rem rgba(15, 23, 42, 0.22);
}
.thanks-modal.fade .modal-dialog {
  transform: translateY(1.5rem) scale(0.96);
  transition: transform 0.32s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.32s ease;
}
.thanks-modal.show .modal-dialog { transform: none; }

/* Wider than modal-sm, narrower than the default — a short note reads badly
   across a full-width dialog. */
.modal-sm-plus { max-width: 26rem; }

.thanks-tick {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 4.5rem;
  height: 4.5rem;
  border-radius: 50%;
  font-size: 2rem;
  color: #fff;
  background: var(--enlivn-primary);
  box-shadow: 0 0 0 0.6rem rgba(var(--enlivn-primary-rgb), 0.12);
}
.thanks-modal.show .thanks-tick { animation: thanks-tick-pop 0.45s 0.1s backwards cubic-bezier(0.22, 1, 0.36, 1); }

@keyframes thanks-tick-pop {
  from { transform: scale(0.4); opacity: 0; }
  to   { transform: scale(1);   opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .thanks-modal.fade .modal-dialog { transition: opacity 0.2s ease; transform: none; }
  .thanks-modal.show .thanks-tick { animation: none; }
}

/* ── Required-field marker ─────────────────────────────────────────────────
   One asterisk, one colour, every Bootstrap-side form (templates/partials/
   required_mark.html). Deliberately NOT `content: "*"` on a `.required` class:
   generated content can't be selected or read by some assistive tech, and the
   partial already hides it from screen readers in favour of the input's own
   `required` attribute. */
.req-mark {
  margin-left: 0.15rem;
  color: #dc3545;
  font-weight: 700;
}

/* --- Corporate albums (16 Aug) ------------------------------------------------
   The album is the client's OWN library, so it gets a real cover and a real
   editing surface rather than a generic icon and an append-only modal. Three
   pieces, shared by the card, the view drawer and the form drawer: the wide
   dropzone, the tile grid, and the banner hero. */

/* The whole panel is the drop target and the file picker (it is a <label>
   wrapping a hidden multi-input), so there is no small "browse" affordance to
   aim at — the wide area IS the control. */
.album-dropzone {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0.35rem; width: 100%; min-height: 11rem;
  padding: 1.5rem; text-align: center; cursor: pointer;
  border: 2px dashed var(--bs-border-color); border-radius: 0.9rem;
  background: var(--enlivn-surface); color: var(--enlivn-ink);
  transition: border-color 0.15s ease, background 0.15s ease;
}
.album-dropzone:hover,
.album-dropzone:focus-visible { border-color: var(--enlivn-primary); background: var(--enlivn-tint); }
/* portal.js adds .is-dragging on dragover — a file held over the panel has to
   say it will land here. */
.album-dropzone.is-dragging {
  border-color: var(--enlivn-primary); background: var(--enlivn-tint);
  border-style: solid;
}
.album-dropzone-icon { font-size: 1.75rem; color: var(--enlivn-primary); }

/* auto-fill, not auto-fit: a single tile keeps its size instead of stretching
   across the whole 60vw drawer. */
.album-tiles {
  display: grid; gap: 0.6rem;
  grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
}
.album-tile {
  position: relative; margin: 0; overflow: hidden;
  aspect-ratio: 1; border-radius: 0.75rem;
  border: 1px solid var(--bs-border-color); background: var(--enlivn-surface);
  display: block;
}
.album-tile img,
.album-tile video { width: 100%; height: 100%; object-fit: cover; display: block; }
.album-tile-kind {
  position: absolute; right: 0.4rem; top: 0.4rem;
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.5rem; height: 1.5rem; border-radius: 999px;
  background: rgba(0, 0, 0, 0.6); color: #fff; font-size: 0.75rem;
}
.album-tile-banner {
  position: absolute; left: 0.4rem; top: 0.4rem;
  display: inline-flex; align-items: center; gap: 0.25rem;
  padding: 0.1rem 0.45rem; border-radius: 999px;
  background: var(--enlivn-primary); color: #fff;
  font-size: 0.68rem; font-weight: 700;
}
/* Session proof (an AlbumMedia carrying a booking): removing it hides it from
   the album but never deletes the record — PRD §11.4. */
.album-tile-proof {
  position: absolute; left: 0.4rem; bottom: 0.4rem;
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.4rem; height: 1.4rem; border-radius: 999px;
  background: rgba(255, 255, 255, 0.9); color: var(--enlivn-primary); font-size: 0.7rem;
}
.album-tile-actions {
  position: absolute; inset-inline: 0; bottom: 0;
  display: flex; justify-content: flex-end; gap: 0.3rem; padding: 0.35rem;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.55), transparent);
  opacity: 0; transition: opacity 0.15s ease;
}
.album-tile:hover .album-tile-actions,
.album-tile:focus-within .album-tile-actions { opacity: 1; }
.album-tile-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.75rem; height: 1.75rem; padding: 0; border: 0; border-radius: 0.45rem;
  background: rgba(255, 255, 255, 0.92); color: var(--enlivn-ink); font-size: 0.8rem;
}
.album-tile-btn:hover { background: #fff; }
.album-tile-btn.is-danger { background: rgba(220, 53, 69, 0.9); color: #fff; }
.album-tile-btn.is-danger:hover { background: #dc3545; }
/* A tile in the read-only drawer has no controls — it is a link to the file. */
.album-tile.is-static:hover { border-color: var(--enlivn-primary); }

/* The view drawer's banner band (the drawer itself wears .record-drawer since 18 Aug). */
.album-hero { position: relative; height: 12rem; overflow: hidden; background: var(--enlivn-surface); }
.album-hero img,
.album-hero video { width: 100%; height: 100%; object-fit: cover; display: block; }
.album-hero-empty {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 100%; font-size: 2.5rem; color: var(--enlivn-primary);
  background: linear-gradient(135deg, var(--enlivn-tint), rgba(5, 187, 254, 0.06));
}
.album-hero-veil {
  position: absolute; inset-inline: 0; bottom: 0; padding: 1.5rem 1.5rem 1rem; color: #fff;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.72), transparent);
}
/* The empty-state hero is light, so white-on-nothing would vanish. */
.album-hero:has(.album-hero-empty) .album-hero-veil {
  color: var(--enlivn-ink);
  background: linear-gradient(to top, rgba(255, 255, 255, 0.85), transparent);
}

/* The listing card: cover on top, meta below, whole card opens the drawer. */
.album-card { padding: 0; overflow: hidden; cursor: pointer; transition: box-shadow 0.15s ease; }
.album-card:hover { box-shadow: 0 8px 24px rgba(2, 32, 71, 0.1); }
.album-card-cover { position: relative; height: 9rem; overflow: hidden; background: var(--enlivn-surface); }
.album-card-cover img,
.album-card-cover video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* --- The record drawer (17 Aug) -----------------------------------------------
   The READ dress for the Bootstrap portals, and the counterpart to
   `.book-drawer*` (the FORM dress). Both share the visual language — tinted
   header band, sectioned cards, sticky footer — because they are the same
   product; what differs is that a record is scanned, so its sections carry
   icon-headed titles and a two-column key/value grid rather than inputs.

   It exists because "an ordinary drawer" (a bare <h4> and one long <dl>) is
   what the corporate's enquiry and booking details had: everything at one
   visual weight, no grouping, nothing to scan for. The staff panel had had
   sectioned detail drawers since 31 Jul; this is that idea in Bootstrap. */
.record-drawer { margin: -1.5rem; display: flex; flex-direction: column; min-height: 100%; }

.record-drawer-head {
  padding: 1.5rem 1.5rem 1.25rem;
  background: linear-gradient(135deg, var(--enlivn-tint), rgba(5, 187, 254, 0.06));
  border-bottom: 1px solid var(--bs-border-color);
}
.record-drawer-eyebrow {
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--enlivn-primary);
}
.record-drawer-title { font-size: 1.3rem; font-weight: 700; margin: 0.15rem 0 0; color: var(--enlivn-ink); }
/* The facts a reader scans for before anything else, as chips under the title. */
.record-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin-top: 0.6rem; }
.record-chip {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.2rem 0.6rem; border-radius: 999px;
  background: rgba(255, 255, 255, 0.75); border: 1px solid var(--bs-border-color);
  font-size: 0.78rem; font-weight: 600; color: var(--enlivn-ink);
}
.record-chip i { color: var(--enlivn-primary); }

/* flex:1 so a short record still pushes its footer to the bottom of the shell. */
.record-drawer-body { flex: 1 1 auto; padding: 1.25rem 1.5rem 0; }

.record-section {
  border: 1px solid var(--bs-border-color); border-radius: 0.9rem;
  background: #fff; margin-bottom: 1rem; overflow: hidden;
}
.record-section-head {
  display: flex; align-items: center; gap: 0.6rem;
  padding: 0.85rem 1.1rem; border-bottom: 1px solid var(--bs-border-color);
  background: var(--enlivn-surface);
}
.record-section-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.9rem; height: 1.9rem; border-radius: 0.6rem; flex: 0 0 auto;
  background: var(--enlivn-tint); color: var(--enlivn-primary); font-size: 0.9rem;
}
.record-section-title {
  font-size: 0.9rem; font-weight: 700; margin: 0; color: var(--enlivn-ink);
}
/* A count beside a section title (participants, photos) — pushed to the far
   edge of the header so the title still reads first. */
.record-count {
  margin-left: auto; padding: 0.1rem 0.5rem; border-radius: 999px;
  background: var(--enlivn-tint); color: var(--enlivn-primary);
  font-size: 0.75rem; font-weight: 700;
}
.record-section-body { padding: 1rem 1.1rem; }
.record-section-body > :last-child { margin-bottom: 0; }

/* Label/value pairs. One column on a phone, label+value on a wide drawer —
   the same shape the staff detail sections use. */
.record-kv { display: grid; gap: 0.35rem 1.25rem; margin: 0; }
@media (min-width: 576px) {
  .record-kv { grid-template-columns: 11rem minmax(0, 1fr); align-items: baseline; }
}
.record-kv dt {
  font-size: 0.78rem; font-weight: 600; color: var(--enlivn-muted);
  text-transform: uppercase; letter-spacing: 0.02em;
}
.record-kv dd { margin: 0 0 0.35rem; color: var(--enlivn-ink); overflow-wrap: anywhere; }
@media (min-width: 576px) { .record-kv dd { margin-bottom: 0.5rem; } }

/* The client's own words, quoted back — a tinted block, not another kv row. */
.record-quote {
  border: 1px solid var(--bs-border-color); border-left: 3px solid var(--enlivn-primary);
  border-radius: 0.6rem; padding: 0.85rem 1rem; background: var(--enlivn-surface);
  white-space: pre-line; margin: 0;
}
/* A row that links out (a booking under an enquiry, a payment proof). */
.record-link-row {
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
  padding: 0.55rem 0; border-bottom: 1px solid var(--bs-border-color);
  text-decoration: none; color: var(--enlivn-ink);
}
.record-link-row:last-child { border-bottom: 0; padding-bottom: 0; }
.record-link-row:hover { color: var(--enlivn-primary); }

/* Sticky like the form drawer's, so the actions stay reachable in a long record. */
/* Star rating — five radios rendered 5→1 and row-reversed, so the "earlier
   siblings" `~` reaches are the LOWER stars: a checked 3 lights 3, 2 and 1.
   Hover previews the same way. No JS; the radios post natively. */
.star-rating { display: inline-flex; flex-direction: row-reverse; gap: 0.15rem; }
.star-rating input { position: absolute; opacity: 0; width: 0; height: 0; }
.star-rating label {
  cursor: pointer; font-size: 1.65rem; line-height: 1; color: #d1d5db;
  transition: color 0.15s ease, transform 0.15s ease;
}
.star-rating label:hover,
.star-rating label:hover ~ label,
.star-rating input:checked ~ label { color: #f5b301; }
.star-rating label:hover { transform: scale(1.12); }
.star-rating input:focus-visible + label { outline: 2px solid var(--enlivn-primary); outline-offset: 2px; border-radius: 4px; }
/* --- The rate-session MODAL (18 Aug) — YouTube-inspired: a short rise-and-
   settle, big radius, no header rule, quiet chrome, text Cancel beside a filled
   Submit. `.rate-modal` on #portalModal is copied from the fragment's
   data-modal-class by portal.js, so only this modal wears it. -------------- */
.rate-modal.fade .modal-dialog {
  transform: translateY(14px) scale(0.965);
  transition: transform 0.24s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.2s ease;
}
.rate-modal.show .modal-dialog { transform: none; }
.rate-modal .modal-content { border: 0; border-radius: 1.1rem; box-shadow: 0 24px 64px -20px rgba(11, 18, 32, 0.45); overflow: hidden; }
.rate-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; padding: 1.35rem 1.5rem 0.75rem; }
.rate-eyebrow { font-size: 0.72rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--enlivn-primary); }
.rate-title { font-size: 1.2rem; font-weight: 700; color: var(--enlivn-ink); }
.rate-sub { font-size: 0.85rem; color: var(--enlivn-muted); }
.rate-close {
  flex: 0 0 auto; width: 2.25rem; height: 2.25rem; border: 0; border-radius: 50%;
  background: transparent; color: var(--enlivn-muted); display: grid; place-items: center;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.rate-close:hover { background: var(--enlivn-surface); color: var(--enlivn-ink); }
.rate-body { padding: 0 1.5rem 0.5rem; max-height: min(70vh, 40rem); overflow-y: auto; }
.rate-lead { font-size: 0.9rem; color: var(--enlivn-muted); margin-bottom: 0.75rem; }
.rate-row { padding: 0.9rem 0; border-top: 1px solid var(--bs-border-color); }
.rate-row-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.rate-row-label { font-weight: 600; color: var(--enlivn-ink); display: flex; align-items: center; gap: 0.5rem; }
.rate-row-label i { color: var(--enlivn-primary); }
.rate-row-hint { font-size: 0.82rem; color: var(--enlivn-muted); margin-top: 0.1rem; }
.rate-row textarea.form-control {
  margin-top: 0.6rem; border-radius: 0.75rem; background: var(--enlivn-surface);
  border-color: transparent; resize: vertical; min-height: 2.75rem; font-size: 0.9rem;
}
.rate-row textarea.form-control:focus { background: #fff; border-color: var(--enlivn-primary); box-shadow: 0 0 0 0.2rem var(--enlivn-tint); }
.rate-quote {
  margin: 0.6rem 0 0; padding: 0.7rem 0.9rem; border-radius: 0.75rem;
  background: var(--enlivn-surface); border-left: 3px solid var(--enlivn-primary); white-space: pre-line; font-size: 0.92rem;
}
.rate-none { margin: 0.5rem 0 0; font-size: 0.85rem; color: var(--enlivn-muted); font-style: italic; }
.rate-foot { display: flex; justify-content: flex-end; gap: 0.5rem; padding: 0.75rem 1.5rem 1.25rem; }
.rate-btn-text { color: var(--enlivn-primary); font-weight: 600; border-radius: 999px; }
.rate-btn-text:hover { background: var(--enlivn-tint); color: var(--enlivn-primary-hover); }
.rate-btn-primary { border-radius: 999px; padding-inline: 1.35rem; font-weight: 600; }
/* Bigger, springier stars in the modal — the whole point of the dialog. */
.rate-modal .star-rating label { font-size: 1.85rem; }
.rate-modal .star-rating label:hover { transform: scale(1.18); }
@media (max-width: 575.98px) {
  .rate-head, .rate-body, .rate-foot { padding-inline: 1.1rem; }
  .rate-modal .star-rating label { font-size: 1.6rem; }
}
@media (prefers-reduced-motion: reduce) {
  .rate-modal.fade .modal-dialog { transform: none; transition: opacity 0.2s ease; }
  .rate-modal .star-rating label { transition: none; }
}

/* Read-only stars (a rating shown, not asked for). */
.stars-static { display: inline-flex; gap: 0.1rem; color: #f5b301; font-size: 0.95rem; }
.stars-static .is-off { color: #d1d5db; }

/* "What clients say" on the offering page (18 Aug). */
.review-list { display: grid; gap: 1rem; }
@media (min-width: 768px) { .review-list { grid-template-columns: 1fr 1fr; } }
.review-card {
  margin: 0; padding: 1.1rem 1.25rem; border-radius: 0.9rem;
  border: 1px solid var(--bs-border-color); background: #fff; box-shadow: var(--enlivn-shadow-sm);
}
.review-quote { font-size: 1rem; line-height: 1.55; color: var(--enlivn-ink); white-space: pre-line; }
.review-author { display: flex; align-items: center; gap: 0.6rem; font-size: 0.9rem; }
.review-avatar { width: 2.25rem; height: 2.25rem; border-radius: 50%; object-fit: cover; }

.record-drawer-footer {
  position: sticky; bottom: 0; z-index: 2;
  display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 0.5rem;
  padding: 0.85rem 1.5rem; margin-top: 0.5rem;
  border-top: 1px solid var(--bs-border-color);
  background: #fff;
}

/* --- Drawer polish (18 Aug) ---------------------------------------------------
   The portals' drawers read one weight lighter than the staff panel's, so:
   · the shell throws a real shadow instead of a hairline, and its close X
     FLOATS over a dressed drawer's head band (the staff drawer's corner X)
     rather than sitting in a bare bordered strip above it;
   · a record can open on a MEDIA HERO (.record-hero — the corporate offering
     and album drawers), with a discount flag on it;
   · sections carry a hover lift, and a few read-only idioms exist so a drawer
     never falls back to a bare <dl>: .record-checklist, .record-plan-row,
     .record-city, .record-prose. */
.portal-drawer.offcanvas {
  border-left: 0;
  box-shadow: -12px 0 40px rgba(2, 32, 71, 0.14);
}
.portal-drawer:has(.record-drawer, .book-drawer) .offcanvas-header {
  position: absolute; top: 0; right: 0; z-index: 4;
  padding: 0.85rem 1rem; border-bottom: 0 !important;
}
.portal-drawer:has(.record-drawer, .book-drawer) .btn-close {
  width: 2rem; height: 2rem; padding: 0; border-radius: 999px;
  background-color: rgba(255, 255, 255, 0.85);
  background-size: 0.75rem; box-shadow: 0 1px 4px rgba(2, 32, 71, 0.15);
  opacity: 1; transition: transform 0.15s ease, background-color 0.15s ease;
}
.portal-drawer:has(.record-drawer, .book-drawer) .btn-close:hover {
  transform: rotate(90deg); background-color: #fff;
}
/* Room for the floating X on the first line of the head band. */
.portal-drawer:has(.record-drawer, .book-drawer) .record-drawer-head,
.portal-drawer:has(.record-drawer, .book-drawer) .book-drawer-head { padding-right: 3.5rem; }
.portal-drawer:has(.record-hero, .album-hero) .record-drawer-head { padding-right: 1.5rem; }

.record-hero { position: relative; height: 13rem; overflow: hidden; background: var(--enlivn-surface); }
.record-hero img,
.record-hero video {
  width: 100%; height: 100%; object-fit: contain; display: block;
  position: relative; z-index: 1;
}
.record-hero::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(2, 32, 71, 0.25), transparent 55%);
  pointer-events: none;
}
.record-hero-flag {
  position: absolute; left: 1rem; bottom: 0.9rem; z-index: 2;
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.3rem 0.7rem; border-radius: 999px;
  background: var(--enlivn-primary); color: #fff;
  font-size: 0.78rem; font-weight: 700; letter-spacing: 0.02em;
  box-shadow: 0 4px 12px rgba(1, 143, 238, 0.35);
}
.record-section { transition: box-shadow 0.15s ease, border-color 0.15s ease; }
.record-section:hover { box-shadow: 0 6px 20px rgba(2, 32, 71, 0.07); }
.record-section-promo { border-color: rgba(1, 143, 238, 0.35); }
.record-section-promo .record-section-head { background: var(--enlivn-tint); }
.record-prose { white-space: pre-line; line-height: 1.6; color: var(--enlivn-ink); }
.record-checklist { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
.record-checklist li { display: flex; gap: 0.5rem; align-items: flex-start; font-size: 0.925rem; }
.record-checklist li i { color: var(--enlivn-primary); flex: 0 0 auto; margin-top: 0.15rem; }
.record-plan-row {
  display: flex; justify-content: space-between; gap: 1rem;
  padding: 0.35rem 0; border-bottom: 1px dashed var(--bs-border-color);
}
.record-plan-row:last-child { border-bottom: 0; }
.record-city {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.3rem 0.7rem; border-radius: 999px;
  background: var(--enlivn-surface); border: 1px solid var(--bs-border-color);
  font-size: 0.85rem; font-weight: 500; color: var(--enlivn-ink);
}
.record-city i { color: var(--enlivn-primary); }
/* Sticky footer with a soft top fade, so content visibly slides under it. */
.record-drawer-footer { box-shadow: 0 -8px 20px rgba(2, 32, 71, 0.05); }

/* Weekday chips (18 Aug) — the instructor's working days, read (page) and
   picked (drawer: a `.btn-check` per day so the checkbox posts natively). */
.weekday-chips { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.weekday-chip {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 3.25rem; padding: 0.4rem 0.75rem; border-radius: 999px;
  border: 1px solid var(--bs-border-color); background: #fff;
  font-size: 0.85rem; font-weight: 600; color: var(--enlivn-muted); margin: 0;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.weekday-chip.is-on,
.btn-check:checked + .weekday-chip {
  background: var(--enlivn-primary); border-color: var(--enlivn-primary); color: #fff;
}
.weekday-chip.is-pick { cursor: pointer; }
.weekday-chip.is-pick:hover { border-color: var(--enlivn-primary); color: var(--enlivn-primary); }
.btn-check:checked + .weekday-chip.is-pick:hover { color: #fff; }
.btn-check:focus-visible + .weekday-chip { outline: 2px solid var(--enlivn-primary); outline-offset: 2px; }

/* --- Promotions on the price (17 Aug 2026) -----------------------------------
   Two pieces, one vocabulary, every portal that loads brand.css:
   .price-was     the struck original beside a promoted starting price
   .promo-chip    the discount pill ("20% off") on a price chip row
*/
.price-was {
  font-weight: 400; font-size: 0.85em; color: var(--enlivn-muted);
  text-decoration-thickness: 1.5px; margin-left: 0.25rem;
}
.promo-chip {
  display: inline-flex; align-items: center; gap: 0.15rem;
  padding: 0.15rem 0.55rem; border-radius: 999px;
  background: var(--enlivn-primary); color: #fff;
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.02em; white-space: nowrap;
  vertical-align: middle;
}
.promo-chip .bi { font-size: 0.8em; }
.promo-chip--hero { font-size: 0.85rem; padding: 0.35rem 0.8rem; margin-left: 0.5rem; }
.offering-meta--hero .price-was { color: rgba(11, 18, 32, 0.55); }
/* The big footer price on the public Services cards (17 Aug) — the number a
   buyer scans for, beside Learn More; promoted or not, one shape. */
.service-price { line-height: 1.15; color: var(--enlivn-ink); }
.service-price strong { font-size: 1.35rem; font-weight: 800; }
.service-price small { color: var(--enlivn-muted); font-size: 0.8rem; }
.service-price .price-was { font-size: 0.85rem; }

/* --- Media skeletons (25 Aug 2026) ---------------------------------------
   The shimmer a tile wears until its own picture has decoded — the pattern
   YouTube, LinkedIn and Facebook use for exactly this wait.

   WHY IT EXISTS: an album is the client's own uploads, full size, with no
   thumbnail pipeline behind them yet. Over a real connection the drawer paints
   instantly and then sits blank while the browser pulls several MB per tile,
   which reads as a hang. The skeleton says "this is arriving" in the tile's
   OWN box, so the grid never reflows as pictures land.

   The placeholder is the CONTAINER's background, not an extra element: an
   overlay would need removing, and anything that fails to remove leaves the
   picture hidden behind it. Here the media simply fades in on top.

   `is-loading` is removed by portal.js on each element's own `load` event
   (`loadeddata` for video). It is also removed on `error`, deliberately: a
   broken file should show the browser's own broken-image mark, because a
   skeleton that shimmers forever reads as "still loading" and nobody reports
   it. */
.media-skeleton {
  position: relative;
  background-color: #e8edf3;
}
.media-skeleton.is-loading::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.65) 50%, rgba(255,255,255,0) 100%);
  background-size: 200% 100%;
  animation: enlivn-shimmer 1.4s ease-in-out infinite;
  pointer-events: none;
  z-index: 1;
}
@keyframes enlivn-shimmer {
  0%   { background-position: 150% 0; }
  100% { background-position: -50% 0; }
}
/* The media itself fades in once it has actually decoded, so a tile never
   flicks from shimmer to picture in one frame. */
.media-skeleton [data-media-skeleton] {
  opacity: 0;
  transition: opacity 0.35s ease;
}
.media-skeleton:not(.is-loading) [data-media-skeleton] { opacity: 1; }
/* An album with no banner has nothing to wait for — it draws its own empty
   state, so the shimmer would be announcing an arrival that never comes. */
.media-skeleton:has(.album-hero-empty) { background-color: transparent; }
.media-skeleton:has(.album-hero-empty)::after { content: none; }
@media (prefers-reduced-motion: reduce) {
  .media-skeleton.is-loading::after { animation: none; }
  .media-skeleton [data-media-skeleton] { transition: none; }
}

/* "Load more" under the album drawer's tile grid — spans the grid so the
   button centres under the tiles rather than sitting in a tile's slot. */
.album-more {
  grid-column: 1 / -1;
  display: flex; justify-content: center;
  padding-top: 0.25rem;
}
