/*
Theme Name: Golden Fern World School
Theme URI: https://goldenfernworldschool.example
Author: Golden Fern World School
Description: Custom theme for Golden Fern World School — spatial hero scene, GSAP scroll reveals, forest-green/warm-gold design system. See /DESIGN.md, /ANIMATION.md, /WP-ARCHITECTURE.md in the project root for the full spec this implements.
Version: 1.0.0
Requires at least: 6.0
Requires PHP: 7.4
Text Domain: golden-fern
*/

/* Tailwind utility classes are loaded via the play-CDN script (see functions.php).
   Everything below is the hand-written CSS that Tailwind can't express as utilities —
   copied from the approved prototype (_prototype/css/styles.css) unchanged. */

body {
  background-color: #FAF7F0;
  color: #2A2A28;
  overflow-x: hidden;
}

body.loading {
  overflow: hidden;
}

#preloader {
  position: fixed;
  inset: 0;
  background-color: #FAF7F0;
  z-index: 9999;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

/* The logo's gold, cut to the logo's shape by masking with the white logo,
   and hidden behind a clip that animations.js opens left to right. */
.preloader-logo-fill {
  position: absolute;
  inset: 0;
  background-color: #E0B655;
  -webkit-mask: url('assets/img/logo-white.png') center / 100% 100% no-repeat;
  mask: url('assets/img/logo-white.png') center / 100% 100% no-repeat;
  clip-path: inset(0% 100% 0% 0%);
}

/* Second act of the intro: sits just under #preloader so the preloader
   slides up off it, and starts hidden because animations.js is what decides
   whether it plays at all (reduced motion skips it entirely). */
#intro-video {
  position: fixed;
  inset: 0;
  z-index: 9998;
  display: none;
  background-color: #12352B;
}

#intro-video video {
  width: 100%;
  height: 100%;
  /* Fill the frame: this is a phone-shot clip, so letterboxing it into a
     desktop viewport would show more background than video. */
  object-fit: cover;
}

/* Sphere-to-grid hero (ANIMATION.md §2): on load, each .tile animates from
   a 3D sphere position down to rotation:0 — its natural spot in the flat
   CSS grid underneath (see animations.js → initHeroScene). Click-and-drag
   pans the settled grid afterward. perspective/preserve-3d only matter
   during the sphere phase; once flattened they're inert. */
#hero-scene-section {
  /* The drag gesture owns this area on touch: without touch-action:none the
     browser claims the pointer stream for its own panning and the drag dies
     after the first move, which is why tap-and-drag did nothing on a
     touchscreen while a mouse worked fine. */
  touch-action: none;
  /* No perspective here: animations.js sets it from the globe's radius
     (CAMERA), so the depth is the same at every screen size. */
}

/* Sixteen across: the flat canvas you pan around after the globe settles
   should be a wide landscape, not a tall column — dragging sideways is the
   natural gesture and there's no page scroll here to do the vertical.
   Sixteen is the globe's own meridian count (animations.js), so the card
   count divides evenly either way it's laid out: 80 / 16 = 5 rows. */
.tile-grid {
  display: grid;
  grid-template-columns: repeat(16, 300px);
  grid-auto-rows: 340px;
  /* Wide pitch on purpose: about 520px between card centres puts 3-4 cards
     on screen at a time with real space around them, which is what the flat
     canvas is meant to feel like — somewhere you move through, not a
     contact sheet. At the old 32px gap six cards tiled edge to edge and it
     read as a grid of results. */
  column-gap: 220px;
  row-gap: 180px;
  /* Even cards ride 90px lower (below) via `top`, which moves them without
     moving the grid's own box — so the last row hung 90px past the padding
     and, because animations.js derives the pan limits from offsetHeight, you
     could never pan far enough to clear it: the bottom row ended flush
     against the viewport edge and under the persona bar while every other
     edge had 120px of air. The extra 90px here is that overhang, so the
     bottom margin matches the other three again. Keep it equal to the
     `top` offset below. */
  padding: 120px 120px 210px;
  transform-style: preserve-3d;
}

/* Alternate columns ride lower, so the canvas scatters instead of lining up
   into rows. `top` rather than a transform: .tile's transform belongs to
   GSAP (sphere placement, flatten, focus zoom) and a second writer on it
   would fight those. It also costs the sphere nothing — each tile's sphere
   offset is measured from its own rect, so a displaced card is compensated
   for exactly. */
.tile:nth-child(even) {
  top: 90px;
}

.tile {
  position: relative;
  /* A card is a drag surface first. Without these, a mouse drag starts the
     browser's native link-drag (ghost image, no pan) and a touch drag
     selects the card's text. */
  user-select: none;
  -webkit-user-drag: none;
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 0.75rem;
  /* Flat: the fill, bow, frame and text rasterise into ONE layer per card.
     preserve-3d here (so .tile-content's backface-visibility could hide the
     text on cards turned away) made every child its own coplanar layer in
     the scene's 3D sort — 324 layers for 80 cards, several hundred MB of
     texture at phone pixel density. Mobile Chrome evicted them mid-spin:
     cards lost their fills and the headline over the globe flickered. A card
     turned away shows its face through the back, mirrored, as the
     reference's do. */
  transform-style: flat;
  /* Two-sided: a card on the far side of the globe is facing away, and
     hiding backfaces here is what made that whole half vanish. */
  backface-visibility: visible;
  overflow: visible;
  box-shadow: 0 24px 48px -16px rgba(0, 0, 0, 0.5);
  /* The WebGL flight lands cards without shadows; the DOM cards that take
     over let theirs settle in rather than appear in one frame. */
  transition: box-shadow 450ms ease;
}

/* No card shadows while the globe is turning. A 48px blur on 84 overlapping
   cards costs ~25% of the frame rate, and on the globe you can't see them
   anyway — the cards sit against each other, not against the background.
   They earn their keep once the grid is flat, so the class comes off then
   (animations.js -> introTl onComplete). */
#scene.is-sphere .tile {
  box-shadow: none;
}

/* The card's curve across the sphere, as shading rather than geometry:
   bright along the crown, falling away at both vertical edges, which is
   the cue the eye actually reads as a bowed surface. It replaces 8
   3D-transformed strips per card — those were a real bend, but each one
   forces its own render surface and they cost ~10x the frame rate to
   express ~5px of on-screen curvature. --bow fades to 0 as the sphere
   flattens into the grid, where a bowed card would be wrong. */
.tile::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: var(--bow, 1);
  background-image: linear-gradient(
    90deg,
    rgba(0, 0, 0, 0.38) 0%,
    rgba(0, 0, 0, 0.12) 22%,
    rgba(255, 255, 255, 0.07) 50%,
    rgba(0, 0, 0, 0.12) 78%,
    rgba(0, 0, 0, 0.38) 100%
  );
}

/* The same bow, a third of the weight, for the cream cards. The gradient
   above is tuned for a near-black card, where 0.38 black down the vertical
   edges reads as curvature; on a cream one it just drains the warmth out and
   the card turns grey on the globe. */
.tile--light::after {
  background-image: linear-gradient(
    90deg,
    rgba(0, 0, 0, 0.15) 0%,
    rgba(0, 0, 0, 0.05) 22%,
    rgba(255, 255, 255, 0.10) 50%,
    rgba(0, 0, 0, 0.05) 78%,
    rgba(0, 0, 0, 0.15) 100%
  );
}

.tile-frame {
  position: absolute;
  inset: 0.625rem;
  z-index: 1;
  border-radius: 0.375rem;
  pointer-events: none;
}

/* The bent-card globe (sphere-gl.js). Over the DOM globe it stands in for,
   under the headline; it never takes a pointer. */
.sphere-gl {
  position: absolute;
  inset: 0;
  z-index: 10;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.tile-content {
  position: absolute;
  inset: 0;
  z-index: 1;
}

@media (max-width: 768px) {
  .tile-grid {
    grid-template-columns: repeat(16, 220px);
    grid-auto-rows: 240px;
    column-gap: 130px;
    row-gap: 110px;
    /* Same deal as the desktop grid — the 55px the even cards are pushed
       down by, plus the bottom margin. The margin is 110px rather than the
       70px on the other sides because the persona bar sits over the bottom
       ~80px of a phone screen; at a flat 70px the last row of cards ended
       underneath it. */
    padding: 70px 70px 165px;
  }

  .tile:nth-child(even) {
    top: 55px;
  }
}

/* The canvas cursor. animations.js draws the position, size and stretch every
   frame (transforms on .canvas-cursor, __fill and __glyph); everything else
   here is a CSS transition off data-state, on elements that loop never
   writes to. */
.canvas-cursor {
  position: fixed;
  top: 0;
  left: 0;
  /* Over the persona bar and header (z-50) so it's never cut off at their
     edges — it hides itself over them anyway. */
  z-index: 55;
  pointer-events: none;
  will-change: transform;
  transition: opacity 200ms var(--ease-out);
}

.canvas-cursor[data-state='hidden'] {
  opacity: 0;
}

/* Both layers sit centred on the pointer: .canvas-cursor is a zero-size
   point, these hang off it by half their size. */
.canvas-cursor__fill,
.canvas-cursor__glyph {
  position: absolute;
  top: -2.75rem;
  left: -2.75rem;
  width: 5.5rem;
  height: 5.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 9999px;
  will-change: transform;
}

.canvas-cursor__fill {
  background-color: #FAF7F0;
  box-shadow: 0 12px 32px -8px rgba(0, 0, 0, 0.45);
  transition: background-color 250ms ease;
}

/* Gold over a card: it's the one thing on the canvas that does something
   when clicked, and the disc should say so before you commit. */
.canvas-cursor[data-state='card'] .canvas-cursor__fill {
  background-color: #C9A24B;
}

.canvas-cursor__glyph {
  color: #12352B;
}

.canvas-cursor__icon,
.canvas-cursor__label {
  position: absolute;
  transition: opacity 200ms var(--ease-out), transform 400ms var(--ease-out);
}

.canvas-cursor__icon {
  overflow: visible;
}

/* The arrowheads drift outward and back at rest, an invitation to drag, and
   pull all the way out while you do. Each head carries its own outward
   direction as --dx/--dy (front-page.php); SVG lengths here are viewBox
   units, so 2px on a 24-unit box scaled to 32px is about 2.7 screen px. */
.canvas-cursor__axes,
.canvas-cursor__head {
  transform-origin: 12px 12px;
  transition: transform 400ms var(--ease-out);
}

.canvas-cursor[data-state='move'] .canvas-cursor__head {
  animation: canvas-cursor-nudge 1.8s cubic-bezier(0.77, 0, 0.175, 1) infinite;
}

@keyframes canvas-cursor-nudge {
  50% { transform: translate(var(--dx), var(--dy)); }
}

/* Heads out by 2 units, axes scaled 1.2 about the centre: 10 units from
   centre becomes 12, so the lines still meet the tips. */
.canvas-cursor[data-state='press'] .canvas-cursor__head {
  transform: translate(calc(var(--dx) * 2), calc(var(--dy) * 2));
}

.canvas-cursor[data-state='press'] .canvas-cursor__axes {
  transform: scale(1.2);
}

.canvas-cursor__label {
  /* Tracking adds space after the last letter too; this puts it back on
     centre. */
  padding-left: 0.2em;
  font: 700 0.75rem/1 Nunito, sans-serif;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  opacity: 0;
  transform: scale(0.6);
}

/* Over a card the arrows turn away as the label comes up through them. */
.canvas-cursor[data-state='card'] .canvas-cursor__icon {
  opacity: 0;
  transform: scale(0.5) rotate(-45deg);
}

.canvas-cursor[data-state='card'] .canvas-cursor__label {
  opacity: 1;
  transform: scale(1);
}

@media (prefers-reduced-motion: reduce) {
  .canvas-cursor[data-state='move'] .canvas-cursor__head {
    animation: none;
  }
}

/* The native pointer goes only where the disc stands in for it. The tiles
   carry Tailwind's cursor-pointer, hence naming them; the persona bar is
   real buttons over the canvas and keeps the real cursor. */
#hero-scene-section.has-custom-cursor,
#hero-scene-section.has-custom-cursor .tile {
  cursor: none;
}

#hero-scene-section.has-custom-cursor #persona-bar {
  cursor: auto;
}

/* Chrome that belongs to the flat canvas rather than to the globe: the
   header's utility pill and menu button (homepage only) and the persona
   bar. All start hidden and animations.js fades them in together when the
   flatten completes (revealChrome). visibility as well as opacity, so none
   of it can be clicked or tabbed to while invisible. */
.gf-after-continue {
  opacity: 0;
  visibility: hidden;
}

/* Full-screen intro overlay (headline + Continue), sits above the card
   scene and fades out on Continue rather than the page scrolling. */
#hero-intro {
  position: absolute;
  inset: 0;
  z-index: 50;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  pointer-events: none;
  /* A subtle localized wash behind the text only — NOT a near-opaque
     full-screen scrim. That was the actual bug behind "the globe barely
     shows": at 0.92 alpha by 75% radius, this was blacking out nearly the
     entire viewport, leaving only a small lit patch dead center where the
     globe could be seen at all. */
  background: radial-gradient(ellipse 700px 420px at center, rgba(18, 53, 43, 0.55) 0%, transparent 70%);
  /* Its own layer, so fading it out on Continue is a compositor opacity
     change rather than a repaint of large drop-shadowed type every frame of
     the flight — the one thing on a phone that would have stuttered it. */
  will-change: opacity;
}

/* Continue's hover fill. Same sweep as .btn (a scaleX'd ::before, in from
   the left, back out to the right), just slower — this one sits alone on a
   dark full-screen scene with nothing competing for the eye, so it can take
   its time where a button in a dense page can't. Tailwind's
   hover:bg-warm-gold was an instant swap with no direction to it. */
#hero-continue {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  transition: color 700ms var(--ease-organic), border-color 700ms var(--ease-organic);
}

#hero-continue::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-color: #C9A24B;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 700ms var(--ease-organic);
}

#hero-continue:hover::before,
#hero-continue:focus-visible::before {
  transform: scaleX(1);
  transform-origin: left;
}

@media (prefers-reduced-motion: reduce) {
  #hero-continue::before { transition-duration: 1ms; }
}

/* --- Persona bar badge ----------------------------------------------------
   The gold disc between the bar's two links. front-page.php draws the fern;
   animations.js adds .is-in to the bar when it appears (revealChrome).
     - In: the disc pops up with a small overshoot and the frond draws
       itself, stem first, leaflets following it up.
     - At rest: the frond sways like it's outdoors, and a glint crosses the
       gold every few seconds.
     - Pointing at a link: the frond leans toward it.
   Every loop is a transform on its own element, so none of it repaints. */
.persona-badge {
  position: relative;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 9999px;
  color: #12352B;
  /* Lit from the top left — a pale highlight, the brand gold through the
     middle, bronze where it turns away — with a rim of light above and
     shade below: a struck coin rather than a flat sticker. */
  background: radial-gradient(circle at 32% 26%, #F6E2A6 0%, #E0B655 34%, #C9A24B 60%, #8F6F28 100%);
  box-shadow:
    inset 0 1px 1px rgba(255, 255, 255, 0.55),
    inset 0 -3px 6px rgba(92, 64, 14, 0.35),
    0 8px 20px -6px rgba(201, 162, 75, 0.6);
  opacity: 0;
  transform: scale(0.6);
  /* easeOutBack: the one overshoot on the bar, for the thing that arrives. */
  transition: transform 550ms cubic-bezier(0.34, 1.56, 0.64, 1) 200ms, opacity 250ms var(--ease-out) 200ms;
}

#persona-bar.is-in .persona-badge {
  opacity: 1;
  transform: none;
}

.persona-badge__lean {
  display: grid;
  place-items: center;
  transform-origin: 50% 85%;
  transition: transform 500ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

@media (hover: hover) and (pointer: fine) {
  #persona-bar:has(.persona-pick--start .persona-pill:hover) .persona-badge__lean { transform: rotate(-18deg); }
  #persona-bar:has(.persona-pick--end .persona-pill:hover) .persona-badge__lean { transform: rotate(18deg); }
}

/* Keyboard focus and an open picker hold the lean, so the frond points at
   whichever side you're choosing on. */
#persona-bar:has(.persona-pick--start :focus-visible) .persona-badge__lean,
#persona-bar[data-open='who'] .persona-badge__lean { transform: rotate(-18deg); }
#persona-bar:has(.persona-pick--end :focus-visible) .persona-badge__lean,
#persona-bar[data-open='topic'] .persona-badge__lean { transform: rotate(18deg); }

.persona-badge__fern {
  width: 1.125rem;
  height: 1.625rem;
  overflow: visible;
  transform-origin: 50% 96%;
  animation: persona-fern-sway 4.5s cubic-bezier(0.77, 0, 0.175, 1) infinite alternate;
}

@keyframes persona-fern-sway {
  from { transform: rotate(-5deg); }
  to { transform: rotate(5deg); }
}

/* pathLength="1" on every stroke, so 1 is its whole length whatever its size.
   The opacity rides along only to hide the round cap a zero-length dash can
   leave as a dot before its draw starts. */
.persona-badge__fern path {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  opacity: 0;
}

#persona-bar.is-in .persona-badge__fern path {
  stroke-dashoffset: 0;
  opacity: 1;
  transition: stroke-dashoffset 700ms var(--ease-out), opacity 1ms;
  transition-delay: calc(350ms + var(--i) * 80ms);
}

.persona-badge__sheen {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  overflow: hidden;
  pointer-events: none;
}

.persona-badge__sheen::before {
  content: '';
  position: absolute;
  top: -25%;
  bottom: -25%;
  left: -60%;
  width: 40%;
  background: linear-gradient(90deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.7), rgba(255, 255, 255, 0));
  transform: translateX(0) skewX(-20deg);
  animation: persona-badge-sheen 6s cubic-bezier(0.77, 0, 0.175, 1) 2s infinite;
}

/* 450% of its own 40% width carries it clear across the disc; the sweep is
   the first quarter, the rest is the wait before the next. */
@keyframes persona-badge-sheen {
  0% { transform: translateX(0) skewX(-20deg); }
  25%, 100% { transform: translateX(450%) skewX(-20deg); }
}

/* --- Persona picker -------------------------------------------------------
   Each pill opens a stack of choices above it (front-page.php;
   animations.js -> initPersonaPicker sets data-open on the bar). The stack
   rises from the pill: nearest choice first, each one 40ms behind the one
   below, so it reads as unfolding upward out of the button you pressed.
   Closing drops the stagger and goes quickly — nobody watches a menu leave. */
.persona-pick {
  position: relative;
}

.persona-pill {
  color: rgba(255, 255, 255, 0.75);
  transition: background-color 250ms ease, color 250ms ease, transform 160ms var(--ease-out);
}

.persona-pill__prefix {
  font-weight: 400;
  color: rgba(255, 255, 255, 0.5);
  transition: color 250ms ease;
}

@media (hover: hover) and (pointer: fine) {
  .persona-pill:hover {
    background-color: rgba(255, 255, 255, 0.1);
    color: #fff;
  }
}

.persona-pill:active {
  transform: scale(0.96);
}

/* The open pill turns cream, like the reference's white: it is now the
   thing the stack above belongs to. */
#persona-bar[data-open='who'] .persona-pick--start .persona-pill,
#persona-bar[data-open='topic'] .persona-pick--end .persona-pill {
  background-color: #FAF7F0;
  color: #12352B;
}

#persona-bar[data-open='who'] .persona-pick--start .persona-pill__prefix,
#persona-bar[data-open='topic'] .persona-pick--end .persona-pill__prefix {
  color: rgba(18, 53, 43, 0.5);
}

.persona-menu {
  position: absolute;
  bottom: calc(100% + 1.25rem);
  pointer-events: none;
  visibility: hidden;
  /* Stay visible until the choices have faded, then stop taking focus. */
  transition: visibility 0s linear 250ms;
}

.persona-pick--start .persona-menu { left: 0; }
.persona-pick--end .persona-menu { right: 0; }

.persona-menu ul {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.persona-pick--start .persona-menu ul { align-items: flex-start; }
.persona-pick--end .persona-menu ul { align-items: flex-end; }

#persona-bar[data-open='who'] #persona-menu-who,
#persona-bar[data-open='topic'] #persona-menu-topic {
  pointer-events: auto;
  visibility: visible;
  transition-delay: 0s;
}

.persona-option {
  --d: 0ms;
  display: inline-flex;
  align-items: center;
  padding: 0.8125rem 1.5rem;
  border-radius: 9999px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  background-color: rgba(10, 31, 25, 0.6);
  color: rgba(255, 255, 255, 0.9);
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  box-shadow: 0 10px 30px -12px rgba(0, 0, 0, 0.5);
  opacity: 0;
  transform: translateY(0.875rem) scale(0.94);
  /* The stagger delay rides only on the entrance properties: on the colours
     too, it would make hover on the top choice lag by 200ms. */
  transition:
    opacity 200ms var(--ease-out) var(--d),
    transform 320ms var(--ease-out) var(--d),
    background-color 200ms ease,
    color 200ms ease;
}

.persona-pick--start .persona-option { transform-origin: bottom left; }
.persona-pick--end .persona-option { transform-origin: bottom right; }

#persona-bar[data-open='who'] #persona-menu-who .persona-option,
#persona-bar[data-open='topic'] #persona-menu-topic .persona-option {
  --d: calc(var(--n) * 40ms);
  opacity: 1;
  transform: none;
}

@media (hover: hover) and (pointer: fine) {
  .persona-option:hover {
    background-color: rgba(255, 255, 255, 0.16);
    color: #fff;
  }
}

/* As specific as the open rule above, and after it, or `transform: none`
   there would swallow the press. */
#persona-bar[data-open='who'] #persona-menu-who .persona-option:active,
#persona-bar[data-open='topic'] #persona-menu-topic .persona-option:active {
  transform: scale(0.96);
}

.persona-option:focus-visible,
.persona-pill:focus-visible {
  outline: 2px solid #C9A24B;
  outline-offset: 3px;
}

/* The current choice in each stack. */
.persona-option[aria-current='true'],
.persona-option[aria-current='true']:hover {
  background-color: #FAF7F0;
  border-color: transparent;
  color: #12352B;
}

.persona-scrim {
  position: fixed;
  inset: 0;
  z-index: 45;
  background-color: rgba(10, 31, 25, 0.4);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  opacity: 0;
  visibility: hidden;
  transition: opacity 300ms var(--ease-out), visibility 0s linear 300ms;
}

.persona-scrim.is-open {
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}

@media (prefers-reduced-motion: reduce) {
  .persona-badge__fern,
  .persona-badge__sheen::before {
    animation: none;
  }

  /* Choices fade in place instead of rising. */
  .persona-option {
    transform: none;
    --d: 0ms !important;
  }

  .persona-badge { transform: none; }

  .persona-badge__fern path {
    stroke-dashoffset: 0;
    opacity: 1;
  }
}

header {
  transition: background-color 0.4s ease, backdrop-filter 0.4s ease, box-shadow 0.4s ease;
}

header.scrolled {
  background-color: rgba(250, 247, 240, 0.95);
  backdrop-filter: blur(10px);
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
}

.text-balance {
  text-wrap: balance;
}

.placeholder-media {
  background-color: #F0EAD8;
  position: relative;
  overflow: hidden;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 0.5rem;
}

.placeholder-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url('assets/img/fern-watermark.svg');
  background-repeat: no-repeat;
  background-position: center;
  background-size: 30%;
  opacity: 0.1;
  pointer-events: none;
}

.placeholder-label {
  font-family: 'Nunito', sans-serif;
  font-size: 0.875rem;
  color: #C9A24B;
  text-align: center;
  padding: 1rem;
  z-index: 10;
  opacity: 0.7;
}

#mobile-menu {
  transform: translateX(100%);
  transition: transform 0.5s cubic-bezier(0.25, 0.1, 0.25, 1);
}

#mobile-menu.open {
  transform: translateX(0);
}

html {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

.reveal-up {
  opacity: 0;
  transform: translateY(30px);
}

/* Nav links: plain WP menu markup (no custom walker), styled here instead
   of per-anchor Tailwind classes so wp_nav_menu()'s native current-menu-item
   class can drive the active state. */
.site-nav a {
  transition: color 0.2s ease;
}

.site-nav a:hover,
.site-nav .current-menu-item > a {
  color: #C9A24B;
}

/* Tailwind's display utilities (.flex here) are author-level rules, so they
   beat the UA stylesheet's [hidden] { display: none } — which meant the
   mega-menu panels toggled via el.hidden stayed on screen and stacked up in
   the column. Needed once, globally, for the attribute to mean anything on
   a utility-class element. */
[hidden] {
  display: none !important;
}

/* Which middle-column section the right-hand panel is currently showing.
   Set by main.js on hover/focus; also marks the default panel server-side so
   the two columns agree before any JS runs. */
.mm-section[data-active='true'] {
  color: #C9A24B;
}

.mm-section[data-active='true'] svg {
  opacity: 1;
  transform: translateX(0.25rem);
}

.mm-section svg {
  transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1), opacity 0.3s;
}

/* =========================================================================
   Ported pages (template-*.php)

   The hand-written CSS those templates rely on, lifted from the
   golden-fern-theme build. Deliberately NOT copied across: its .reveal-up /
   .stagger-item rules (this theme animates those with GSAP in animations.js,
   and its own CSS above already owns their start state) and its
   .placeholder-media (this theme's version, above, points at its own fern
   watermark). Everything here is new to this theme — nothing below
   redefines a selector that appears earlier in this file.
   ====================================================================== */

:root {
  --color-bg-offwhite:       #FAF6ED;
  --color-bg-offwhite-alt:   #F3EEE2;
  --color-forest-green:      #16453A;
  --color-forest-green-dark: #0E2E27;
  --color-gold:              #C9A227;
  --color-gold-light:        #E4C766;
  --color-text-body:         #2B332F;
  --color-white:             #FFFFFF;

  --shadow-soft: 0 4px 20px rgba(22, 69, 58, 0.08);
  --shadow-lift: 0 12px 32px rgba(22, 69, 58, 0.14);
  --ease-organic: cubic-bezier(0.25, 0.1, 0.25, 1);
  /* Strong ease-out for things that respond to the pointer; --ease-organic
     is plain `ease` and too soft to feel like a response. */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
}

/* --- Type scale ---------------------------------------------------------- */
.t-display { font-size: clamp(2.5rem, 6vw, 3.5rem); line-height: 1.1; }
.t-h1      { font-size: clamp(2rem, 5vw, 3rem);     line-height: 1.15; }
.t-h2      { font-size: clamp(1.75rem, 3.5vw, 2.5rem); line-height: 1.2; }
.t-h3      { font-size: clamp(1.375rem, 2.5vw, 1.75rem); line-height: 1.3; }
.t-lead    { font-size: clamp(1.0625rem, 1.6vw, 1.25rem); line-height: 1.65; }

.eyebrow {
  font-family: Nunito, sans-serif;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  font-size: 0.75rem;
}

/* --- Buttons ------------------------------------------------------------- */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  overflow: hidden;
  isolation: isolate;
  padding: 0.875rem 1.75rem;
  border-radius: 9999px;
  border: 2px solid transparent;
  font-weight: 700;
  font-size: 0.9375rem;
  line-height: 1;
  text-align: center;
  transition: color 0.35s var(--ease-organic), transform 0.35s var(--ease-organic), box-shadow 0.35s var(--ease-organic);
}
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.4s var(--ease-organic);
}
.btn:hover::before, .btn:focus-visible::before { transform: scaleX(1); transform-origin: left; }
.btn:hover { transform: translateY(-2px); box-shadow: var(--shadow-lift); }

.btn-primary { background: var(--color-gold); color: var(--color-forest-green-dark); }
.btn-primary::before { background: var(--color-forest-green-dark); }
.btn-primary:hover, .btn-primary:focus-visible { color: var(--color-gold-light); }

.btn-outline {
  border-color: var(--color-forest-green);
  color: var(--color-forest-green);
}
.btn-outline::before { background: var(--color-forest-green); }
.btn-outline:hover, .btn-outline:focus-visible { color: #fff; }

.btn-on-dark { border-color: #fff; color: #fff; }
.btn-on-dark::before { background: #fff; }
.btn-on-dark:hover, .btn-on-dark:focus-visible { color: var(--color-forest-green-dark); }

/* --- Cards and the fern motif -------------------------------------------- */
.card {
  background: var(--color-white);
  border-radius: 1rem;
  box-shadow: var(--shadow-soft);
  transition: transform 0.4s var(--ease-organic), box-shadow 0.4s var(--ease-organic);
}
.card:hover { transform: translateY(-6px); box-shadow: var(--shadow-lift); }

.fern-rule {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  color: var(--color-gold);
}
.fern-rule::before, .fern-rule::after {
  content: "";
  height: 1px;
  width: clamp(2rem, 8vw, 6rem);
  background: currentColor;
  opacity: 0.5;
}

/* --- Card pop-up (homepage tiles, and any card carrying a description) ---- */
.card-dialog {
  border: 0;
  padding: 0;
  width: min(34rem, 92vw);
  max-height: 88vh;
  overflow: auto;
  border-radius: 1.25rem;
  background: var(--color-bg-offwhite);
  color: var(--color-text-body);
  box-shadow: var(--shadow-lift);
}
.card-dialog::backdrop { background: rgba(14, 46, 39, 0.88); }

.card-dialog__close {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  z-index: 2;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 9999px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(14, 46, 39, 0.55);
  color: #fff;
  transition: background-color 0.25s var(--ease-organic);
}
.card-dialog__close:hover { background: var(--color-forest-green-dark); }

.card-dialog__image {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}
.card-dialog__body { padding: 1.75rem; }
/* pre-line: these descriptions are plain-text fields the client types line
   breaks into, and those breaks are the only structure they have. */
.card-dialog__text { white-space: pre-line; line-height: 1.65; }

/* A tile's pop-up copy. Never rendered — it is only there for the script to
   read, which keeps quotes and line breaks intact in a way a data- attribute
   would not. */
.card-detail { display: none; }

/* --- Gallery ------------------------------------------------------------- */
.masonry { column-gap: 1.25rem; columns: 1; }
@media (min-width: 640px)  { .masonry { columns: 2; } }
@media (min-width: 1024px) { .masonry { columns: 3; } }
.masonry > * { break-inside: avoid; margin-bottom: 1.25rem; }

.gallery-item img,
.gallery-item .placeholder-media { transition: transform 0.5s var(--ease-organic); }
.gallery-item:hover img,
.gallery-item:hover .placeholder-media { transform: scale(1.05); }

dialog#lightbox { border: 0; padding: 0; background: transparent; max-width: 92vw; max-height: 92vh; }
dialog#lightbox::backdrop { background: rgba(14, 46, 39, 0.92); }
dialog#lightbox img { max-width: 92vw; max-height: 80vh; border-radius: 0.75rem; display: block; }

/* --- Testimonials carousel: native scroll-snap, no drag library ---------- */
.carousel {
  display: flex;
  gap: 1.5rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.carousel::-webkit-scrollbar { display: none; }
.carousel > * { scroll-snap-align: center; flex: 0 0 100%; }
@media (min-width: 1024px) { .carousel > * { flex-basis: calc(50% - 0.75rem); } }

/* --- Admissions stepper, and the day-in-the-life timeline ---------------- */
.stepper-track { position: relative; }
.stepper-track::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 2rem;
  height: 2px;
  background: rgba(22, 69, 58, 0.15);
}
.stepper-line-fill {
  position: absolute;
  left: 0; top: 2rem;
  height: 2px;
  width: 0;
  background: var(--color-gold);
}
/* animations.js scrubs this to 100% on scroll; with motion off there is no
   scrub, so it has to start drawn or the stepper reads as broken. */
@media (prefers-reduced-motion: reduce) { .stepper-line-fill { width: 100%; } }
@media (max-width: 767px) { .stepper-track::before, .stepper-line-fill { display: none; } }

.timeline-item { position: relative; padding-left: 2.5rem; }
.timeline-item::before {
  content: "";
  position: absolute;
  left: 0.4375rem; top: 0.75rem; bottom: -1.5rem;
  width: 2px;
  background: rgba(201, 162, 39, 0.35);
}
.timeline-item:last-child::before { display: none; }
.timeline-item::after {
  content: "";
  position: absolute;
  left: 0; top: 0.5rem;
  width: 1rem; height: 1rem;
  border-radius: 9999px;
  background: var(--color-gold);
}

/* --- Enquiry form -------------------------------------------------------- */
.field {
  width: 100%;
  padding: 0.875rem 1.125rem;
  border-radius: 0.75rem;
  border: 1px solid rgba(22, 69, 58, 0.2);
  background: var(--color-white);
  color: var(--color-text-body);
  font: inherit;
  transition: border-color 0.25s var(--ease-organic), box-shadow 0.25s var(--ease-organic);
}
.field:focus {
  border-color: var(--color-gold);
  box-shadow: 0 0 0 3px rgba(201, 162, 39, 0.2);
  outline: none;
}
.field:user-invalid { border-color: #B3261E; }
.field-label { display: block; font-weight: 700; font-size: 0.875rem; margin-bottom: 0.375rem; }
.gfw-hp { position: absolute; left: -9999px; }

.form-notice { border-radius: 0.75rem; padding: 1rem 1.25rem; font-weight: 600; }
.form-notice.is-ok  { background: rgba(22, 69, 58, 0.08); color: var(--color-forest-green); }
.form-notice.is-err { background: rgba(179, 38, 30, 0.08); color: #B3261E; }

/* --- Editor content (the_content) ---------------------------------------- */
/* Measure, not container width: the wrappers are max-w-3xl, which runs to
   roughly 90 characters of Nunito and reads long. */
.prose { max-width: 68ch; }
.prose > * + * { margin-top: 1.25rem; }
/* More air above a heading than below it, so it binds to what it introduces. */
.prose > * + h2 { margin-top: 3rem; }
.prose > * + h3 { margin-top: 2.25rem; }
.prose h2 + *, .prose h3 + * { margin-top: 0.75rem; }
.prose h2 { font-family: Fraunces, Georgia, serif; color: var(--color-forest-green); font-size: clamp(1.75rem, 3.5vw, 2.5rem); margin-top: 2.5rem; }
.prose h3 { font-family: Fraunces, Georgia, serif; color: var(--color-forest-green); font-size: 1.5rem; margin-top: 2rem; }
.prose a { color: var(--color-forest-green); text-decoration: underline; text-decoration-color: var(--color-gold); text-underline-offset: 3px; }
.prose ul { list-style: disc; padding-left: 1.5rem; }
.prose ol { list-style: decimal; padding-left: 1.5rem; }
.prose li + li { margin-top: 0.5rem; }
.prose img { border-radius: 1rem; }
/* A hanging quote mark rather than a coloured bar down the side: it reads as
   typography instead of as an alert. */
.prose blockquote {
  position: relative;
  padding-left: 2rem;
  font-family: Fraunces, Georgia, serif;
  font-size: 1.125rem;
  font-style: italic;
  line-height: 1.6;
  color: var(--color-forest-green);
}
.prose blockquote::before {
  content: "\201C";
  position: absolute;
  left: 0;
  top: -0.35em;
  font-family: Fraunces, Georgia, serif;
  font-size: 3rem;
  line-height: 1;
  color: var(--color-gold);
}
.prose blockquote cite {
  display: block;
  margin-top: 0.75rem;
  font: 700 0.8125rem/1.4 Nunito, sans-serif;
  font-style: normal;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-body);
  opacity: 0.7;
}
