/* fonts.css is linked directly in <head>, ahead of this file, so the
   browser discovers @font-face src URLs on the first pass instead of
   waiting on this stylesheet to parse and then fetch an @import chain. */

/* ---------------------------------------------------------------
   Design tokens
   Single light theme, deliberate choice: this is a credible local
   tutor's site, not a product with a dark-mode audience. Palette is
   neutral off-white + off-black with one accent (deep emerald),
   locked across every page and section.
--------------------------------------------------------------- */
:root {
  --bg: #fafafa;
  --surface: #ffffff;
  --surface-sunken: #f2f1ee;
  --ink: #15171a;
  --ink-soft: #52565d;
  --ink-faint: #6b6e75;
  --line: #e3e2de;
  --line-strong: #cfcdc7;

  --accent: #0f6b4c;
  --accent-dark: #0b4f39;
  --accent-soft: #e6f0ea;
  --accent-ink: #ffffff;

  --font-display: 'Space Grotesk', ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-body: 'IBM Plex Sans', ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, 'SFMono-Regular', monospace;

  --radius-control: 4px;
  --radius-sharp: 0px;

  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);

  --container: 1200px;
  --nav-h: 72px;
}

/* ---------------------------------------------------------------
   Reset
--------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
html, body { height: 100%; }
body {
  font-family: var(--font-body);
  color: var(--ink);
  background: var(--bg);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
img, picture, svg { display: block; max-width: 100%; }
input, button, textarea, select { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }
ul { list-style: none; padding: 0; }
h1, h2, h3, h4 { font-family: var(--font-display); font-weight: 600; line-height: 1.1; letter-spacing: -0.01em; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------------------------------------------------------------
   Scroll reveal (used sparingly: benefit tiles, subject cards, etc.)
   Progressive enhancement: content is fully visible by default.
   JS only hides an element (adds .reveal-armed) at the moment it
   starts observing it, so a no-JS or slow-JS visitor never loses
   content permanently.
--------------------------------------------------------------- */
[data-reveal].reveal-armed {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 500ms var(--ease-out), transform 500ms var(--ease-out);
  transition-delay: calc(var(--reveal-index, 0) * 60ms);
}
[data-reveal].reveal-armed[data-revealed="true"] {
  opacity: 1;
  transform: translateY(0);
}

/* ---------------------------------------------------------------
   Layout helpers
--------------------------------------------------------------- */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: 1.5rem;
}
@media (min-width: 768px) {
  .container { padding-inline: 2.5rem; }
}

.section {
  /* fixed 96px rhythm, same on every breakpoint, per the design's spacing scale */
  padding-block: 96px;
}
.section--tight { padding-block: 64px; }
.section--sunken { background: var(--surface-sunken); }
/* z-index: 0 (not just position: relative) is required here so this
   element actually establishes a stacking context: without it, a
   z-index: -1 child escapes to the nearest ancestor that does have
   one instead of staying local, and renders behind unrelated page
   content instead of just this section's own background. */
.section--clip { position: relative; z-index: 0; overflow: hidden; }
section[id] { scroll-margin-top: calc(var(--nav-h) + 12px); }

.section-head {
  max-width: 42rem;
  margin-bottom: 3rem;
}
@media (min-width: 900px) {
  .section-head { margin-bottom: 4rem; }
}
.section-head h2 {
  font-size: clamp(2rem, 1.3rem + 2.6vw, 2.875rem);
  margin-bottom: 1rem;
}
.section-head p {
  color: var(--ink-soft);
  font-size: 1.0625rem;
  max-width: 38rem;
}
/* At the widths where this heading wraps, the greedy default break
   left "record" orphaned alone on its own line. text-wrap: balance
   keeps it on one line wherever there's room, and otherwise splits it
   evenly ("Qualifications and" / "track record") instead. Degrades to
   the normal (previous) wrap in browsers that don't support it yet. */
#credentials-heading {
  text-wrap: balance;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent-dark);
  margin-bottom: 1.25rem;
}
.eyebrow::before {
  content: '';
  width: 0.4rem;
  height: 0.4rem;
  background: var(--accent);
  border-radius: 1px;
  flex-shrink: 0;
}

/* ---------------------------------------------------------------
   Icons
--------------------------------------------------------------- */
.icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25em;
  height: 1.25em;
  flex-shrink: 0;
}
.icon svg { width: 100%; height: 100%; display: block; }

/* ---------------------------------------------------------------
   Buttons
--------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  white-space: nowrap;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.9375rem;
  padding: 0.85rem 1.5rem;
  border-radius: var(--radius-control);
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform 160ms var(--ease-out), background-color 160ms var(--ease-out), border-color 160ms var(--ease-out), box-shadow 160ms var(--ease-out);
}
.btn:active { transform: scale(0.97); }

.btn--primary {
  background: var(--accent);
  color: var(--accent-ink);
}
.btn--primary:hover { background: var(--accent-dark); }

.btn--ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--line-strong);
}
.btn--ghost:hover { border-color: var(--ink); background: var(--surface); }

.btn--on-dark {
  background: var(--accent-ink);
  color: var(--ink);
}
.btn--on-dark:hover { background: #eceeec; }

.btn .icon { width: 1.1em; height: 1.1em; }

.link-inline {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-weight: 600;
  color: var(--accent-dark);
  border-bottom: 1px solid var(--line-strong);
  padding-bottom: 0.15rem;
  transition: border-color 160ms var(--ease-out), color 160ms var(--ease-out);
}
.link-inline:hover { border-color: var(--accent-dark); }
.link-inline .icon { width: 1em; height: 1em; }

/* ---------------------------------------------------------------
   Navigation
--------------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  background: rgba(250, 250, 250, 0.86);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  border-bottom: 1px solid var(--line);
}
.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
.brand {
  display: flex;
  flex-direction: column;
  justify-content: center;
  line-height: 1;
  gap: 0.3rem;
  min-width: 0;
}
.brand__name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.125rem;
  letter-spacing: -0.01em;
  white-space: nowrap;
}
.brand__tag {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* below ~400px the tagline crowds the hamburger button; drop it and
   keep just the wordmark, since the tagline is decorative, not wayfinding */
@media (max-width: 400px) {
  .brand__tag { display: none; }
  .brand { justify-content: center; }
}

.nav__links {
  display: none;
  align-items: center;
  gap: 2.25rem;
}
.nav__links a {
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--ink-soft);
  transition: color 160ms var(--ease-out);
  position: relative;
}
.nav__links a:hover { color: var(--ink); }
.nav__links a[aria-current="page"] { color: var(--ink); }
.nav__links a[aria-current="page"]::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -0.6rem;
  height: 2px;
  background: var(--accent);
}

.nav__actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-shrink: 0;
}
.nav__cta { display: none; }

.nav__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  background: transparent;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-control);
}
.nav__toggle .icon { width: 1.35rem; height: 1.35rem; }
.nav__toggle .icon--close { display: none; }
.nav__toggle[aria-expanded="true"] .icon--open { display: none; }
.nav__toggle[aria-expanded="true"] .icon--close { display: block; }

@media (min-width: 900px) {
  .nav__links { display: flex; }
  .nav__cta { display: inline-flex; }
  .nav__toggle { display: none; }
}

.mobile-menu {
  position: fixed;
  inset: var(--nav-h) 0 0 0;
  z-index: 49;
  background: var(--bg);
  padding: 2rem 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 2rem;
  transform: translateY(-8px);
  opacity: 0;
  visibility: hidden;
  transition: transform 220ms var(--ease-out), opacity 220ms var(--ease-out), visibility 220ms;
}
.mobile-menu[data-open="true"] {
  transform: translateY(0);
  opacity: 1;
  visibility: visible;
}
.mobile-menu__links {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.mobile-menu__links a {
  font-family: var(--font-display);
  font-size: 1.75rem;
  font-weight: 600;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
}
.mobile-menu__links a[aria-current="page"] { color: var(--accent-dark); }
.mobile-menu__meta {
  color: var(--ink-soft);
  font-size: 0.9375rem;
}
.mobile-menu__meta a { color: var(--accent-dark); font-weight: 600; }

@media (min-width: 900px) {
  .mobile-menu { display: none; }
}

/* ---------------------------------------------------------------
   Hero
--------------------------------------------------------------- */
.hero {
  position: relative;
  z-index: 0;
  padding-top: clamp(1.75rem, 1rem + 2vw, 3.5rem);
  padding-bottom: clamp(3.5rem, 2rem + 5vw, 7rem);
}
/* Full-bleed classroom photo behind the hero, breaking out of .hero's
   own centered container width (left: 50% + translateX(-50%) centers
   a 100vw-wide layer regardless of the parent's own width). Kept
   faint and scrimmed so it reads as atmosphere, not a second subject
   competing with the headline or with Mr Soon's portrait. */
.hero__bg {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 100vw;
  transform: translateX(-50%);
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
}
.hero__bg-photo {
  position: absolute;
  inset: 0;
  background-image: url('../assets/hero-classroom-bg.webp');
  background-size: cover;
  background-position: center 30%;
  opacity: 0.22;
}
.hero__bg-scrim {
  position: absolute;
  inset: 0;
  background: var(--bg);
  opacity: 0.55;
}
.hero__grid {
  display: grid;
  gap: 3rem;
  /* Top-aligned, not centered: the text column is naturally shorter
     than the photo column, and centering it left a dead gap above the
     headline. Starting both at the same line reads as one deliberate
     composition instead. */
  align-items: start;
}
@media (min-width: 1000px) {
  .hero__grid {
    grid-template-columns: 1.25fr 0.75fr;
    gap: 4rem;
  }
}
@media (min-width: 1300px) {
  .hero__grid { gap: 5rem; }
}
.hero__eyebrow {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-faint);
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
}
.hero__eyebrow .icon { width: 0.95em; height: 0.95em; color: var(--accent); }
.hero h1 {
  font-size: clamp(2.5rem, 1.9rem + 2.2vw, 3.375rem);
  max-width: 19ch;
  margin-bottom: 1rem;
}
.hero h1 em {
  font-style: normal;
  color: var(--accent-dark);
}
.hero p.lede {
  font-size: 1.125rem;
  color: var(--ink-soft);
  max-width: 42ch;
  margin-bottom: 2.75rem;
}
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.5rem;
}

.hero__visual {
  position: relative;
  border: 1px solid var(--line);
  background: var(--surface);
  padding: 1.25rem;
}
.hero__visual-label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 0 0.25rem 1rem;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.hero__visual-label strong {
  color: var(--ink);
  font-weight: 600;
}
.hero__photo-stack {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--surface-sunken);
}
.hero__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
}
/* Static default: first photo shown, no motion. This is the complete
   experience for reduced-motion users and covers the case before the
   enhancement below is evaluated. */
.hero__photo--a { opacity: 1; }

/* Entrance: the photo unveils once on load, like a folded corner
   lifting away, then settles into a slow ambient crossfade between
   three poses. Both effects are decorative only, so both are removed
   entirely under reduced motion rather than just shortened. */
@media (prefers-reduced-motion: no-preference) {
  .hero__photo-stack {
    animation: hero-photo-reveal 700ms var(--ease-out) both;
  }
  .hero__photo--a { animation: hero-crossfade-a 9s ease-in-out infinite; }
  .hero__photo--b { animation: hero-crossfade-b 9s ease-in-out infinite; }
  .hero__photo--c { animation: hero-crossfade-c 9s ease-in-out infinite; }
}

@keyframes hero-photo-reveal {
  from { clip-path: polygon(0% 100%, 100% 82%, 100% 100%, 0% 100%); }
  to { clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%); }
}
@keyframes hero-crossfade-a {
  0%, 22.222% { opacity: 1; }
  33.333%, 88.889% { opacity: 0; }
  100% { opacity: 1; }
}
@keyframes hero-crossfade-b {
  0%, 22.222% { opacity: 0; }
  33.333%, 55.556% { opacity: 1; }
  66.667%, 100% { opacity: 0; }
}
@keyframes hero-crossfade-c {
  0%, 55.556% { opacity: 0; }
  66.667%, 88.889% { opacity: 1; }
  100% { opacity: 0; }
}

/* ---------------------------------------------------------------
   Reviews grid
   A plain responsive grid — 1 column mobile, 2 tablet, 4 desktop —
   matching the breakpoint convention used by .subjects. Each card's
   quote is clamped to 5 lines by default; JS reveals a "See more"
   toggle only on cards where that clamp actually truncated something
   (see the reviews-grid block in main.js), so short reviews never
   show a toggle they don't need.
--------------------------------------------------------------- */
.reviews-grid { margin-top: 3rem; }
.reviews-grid__list {
  display: grid;
  gap: 1.25rem;
  grid-template-columns: 1fr;
}
@media (min-width: 640px) {
  .reviews-grid__list { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 980px) {
  .reviews-grid__list { grid-template-columns: repeat(4, 1fr); }
}
.review-card {
  background: var(--surface);
  border: 1px solid var(--line);
  padding: 1.75rem;
  display: flex;
  flex-direction: column;
}
.review-card__quote {
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--ink);
  margin-bottom: 1rem;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 5;
  overflow: hidden;
}
.review-card--expanded .review-card__quote {
  -webkit-line-clamp: unset;
  display: block;
  overflow: visible;
}
.review-card__toggle {
  align-self: flex-start;
  background: none;
  border: none;
  padding: 0;
  margin-bottom: 1.25rem;
  font-family: var(--font-body);
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--accent-dark);
  cursor: pointer;
}
.review-card__toggle:hover { text-decoration: underline; }
.review-card__meta {
  margin-top: auto;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.review-card__name { font-weight: 600; font-size: 0.9375rem; color: var(--ink); }
.review-card__role { font-weight: 400; color: var(--ink-faint); font-size: 0.8125rem; }
.review-card__source {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  white-space: nowrap;
}
.review-card__source .icon { width: 0.9em; height: 0.9em; color: var(--accent); }

/* ---------------------------------------------------------------
   Credentials (bento: photo + fact tiles)
--------------------------------------------------------------- */
.credentials-layout {
  display: grid;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  grid-template-columns: 1fr;
}
@media (min-width: 860px) {
  .credentials-layout { grid-template-columns: 1fr 1.4fr; }
}
.credentials-layout__photo {
  background: var(--surface);
  min-height: 320px;
}
.credentials-layout__photo img {
  width: 100%;
  height: 100%;
  min-height: 320px;
  object-fit: cover;
  display: block;
}
.credentials-right {
  display: grid;
  gap: 1px;
  background: var(--line);
}
.credentials-stat {
  background: var(--accent-soft);
  padding: 1.75rem 2rem;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem 1rem;
}
.credentials-stat__number {
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: clamp(2.5rem, 2rem + 1.5vw, 3.25rem);
  line-height: 1;
  color: var(--accent-dark);
}
.credentials-stat__label {
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--ink);
}

.credentials-facts {
  display: grid;
  gap: 1px;
  background: var(--line);
  grid-template-columns: 1fr;
}
@media (min-width: 560px) {
  .credentials-facts { grid-template-columns: 1fr 1fr; }
}
.credentials-fact {
  background: var(--surface);
  padding: 2rem;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}
.credentials-fact__icon {
  width: 2.5rem;
  height: 2.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-soft);
  color: var(--accent-dark);
  border-radius: var(--radius-control);
  flex-shrink: 0;
}
.credentials-fact__icon .icon { width: 1.25rem; height: 1.25rem; }
.credentials-fact h3 {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 1.0625rem;
}
.credentials-fact p { color: var(--ink-soft); font-size: 0.9375rem; }

/* ---------------------------------------------------------------
   Achievements strip
   Deliberately not a four-equal-boxes grid: one wide, tinted,
   large-numeral "featured" stat carries the most visual weight via a
   tinted background and a wider column, not a bigger number — all
   three numerals share one font size so the row reads as consistent
   at a glance. Hairline dividers use a simple 1px-line technique
   (full-width horizontal until the layout goes row-wise, then a
   vertical hairline between items).
--------------------------------------------------------------- */
.achievements-strip {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  background: var(--surface);
}
@media (min-width: 900px) {
  .achievements-strip { flex-direction: row; align-items: stretch; }
}
.achievements-strip__item {
  padding: 2.25rem 2.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  justify-content: center;
  flex: 1 1 0%;
}
@media (min-width: 900px) {
  .achievements-strip__item--featured { flex: 1.4 1 0%; }
}
.achievements-strip__item--featured {
  background: var(--accent-soft);
}
.achievements-strip__divider {
  width: 100%;
  height: 1px;
  background: var(--line);
}
@media (min-width: 900px) {
  .achievements-strip__divider { width: 1px; height: auto; }
}
.achievements-strip__number {
  font-family: var(--font-mono);
  font-weight: 600;
  line-height: 1;
  color: var(--accent-dark);
  font-size: clamp(2.75rem, 2.2rem + 1.6vw, 3.5rem);
}
.achievements-strip__label {
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--ink);
}
.achievements-strip__detail {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  color: var(--ink-soft);
}

/* ---------------------------------------------------------------
   Level pills (Subjects section)
--------------------------------------------------------------- */
.level-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 2.5rem;
}
.level-pill {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 0.4rem 0.85rem;
  background: var(--accent-soft);
  color: var(--accent-dark);
  border-radius: var(--radius-control);
}
/* ---------------------------------------------------------------
   Testimonials (chat-bubble style, no literal screenshots)
--------------------------------------------------------------- */
/* Soft blurred fields, same accent as the rest of the page at very
   low opacity, negative z-index so they sit behind the section's own
   background but still under the bubbles. Static (no motion) so
   there's no reduced-motion concern and no repaint cost. */
.testimonials-bg {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
}
.testimonials-bg__blob {
  position: absolute;
  border-radius: 50%;
  background: var(--accent);
  filter: blur(70px);
}
.testimonials-bg__blob--1 {
  width: 26rem;
  height: 26rem;
  top: -10rem;
  left: -8rem;
  opacity: 0.14;
}
.testimonials-bg__blob--2 {
  width: 22rem;
  height: 22rem;
  bottom: -9rem;
  right: -6rem;
  opacity: 0.1;
}

/* Ghost bubbles: faint, blurred duplicates of the real testimonials,
   scattered around the foreground grid to read as ambient depth
   rather than content. Each one stacks two quote variants and
   crossfades between them, staggered per bubble so they don't all
   swap in unison. Static (first variant only) by default, matching
   the reduced-motion request to freeze rather than remove them. */
.ghost-bubble {
  position: absolute;
  display: grid;
  width: 210px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 1.1rem;
  padding: 1.1rem 1.25rem;
  filter: blur(1.5px);
  opacity: 0.2;
  /* Base (also the reduced-motion state): just the hand-picked tilt,
     read from --rot. The float keyframes below layer translation on
     top of this same variable, since a static transform and an
     animated one on the same property can't otherwise coexist. */
  transform: rotate(var(--rot, 0deg));
}
/* 13 bubbles, scattered across the full section (top/middle/bottom,
   both sides) rather than pushed to the edges: every position is a
   plain positive percentage within the section's own bounds, so
   nothing relies on overflow to peek in from off-canvas. Varied
   width, rotation and depth (blur/opacity) keep it from reading as a
   repeated tile. --rot/--fx/--fy/--fdur/--fdelay feed the float
   animation further down. */
.ghost-bubble--1  { top: 3%;  left: 4%;   width: 170px; --rot: -4deg; --fx: 6px;  --fy: -12px; --fdur: 3.5s;   --fdelay: 0s; }
.ghost-bubble--2  { top: 2%;  right: 8%;  width: 210px; --rot: 3deg;  --fx: -8px; --fy: 10px;  --fdur: 4.5s;   --fdelay: -3s; }
.ghost-bubble--3  { top: 15%; left: 34%;  width: 160px; --rot: 5deg;  --fx: 7px;  --fy: -9px;  --fdur: 4s;   --fdelay: -6s;  opacity: 0.16; filter: blur(2px); }
.ghost-bubble--4  { top: 24%; left: 6%;   width: 190px; --rot: -3deg; --fx: -6px; --fy: 12px;  --fdur: 5s;  --fdelay: -9s; }
.ghost-bubble--5  { top: 22%; right: 5%;  width: 175px; --rot: 4deg;  --fx: 9px;  --fy: -11px; --fdur: 5.5s;  --fdelay: -12s; opacity: 0.24; filter: blur(1px); }
.ghost-bubble--6  { top: 44%; left: 3%;   width: 200px; --rot: -5deg; --fx: -7px; --fy: 8px;   --fdur: 3.75s; --fdelay: -1s; }
.ghost-bubble--7  { top: 46%; right: 4%;  width: 165px; --rot: 5deg;  --fx: 8px;  --fy: -10px; --fdur: 4.75s; --fdelay: -4s;  opacity: 0.16; filter: blur(2px); }
.ghost-bubble--8  { top: 64%; left: 8%;   width: 195px; --rot: -2deg; --fx: -9px; --fy: 13px;  --fdur: 4.25s; --fdelay: -7s; }
.ghost-bubble--9  { top: 66%; right: 9%;  width: 210px; --rot: 3deg;  --fx: 6px;  --fy: -8px;  --fdur: 5.75s; --fdelay: -10s; opacity: 0.24; filter: blur(1px); }
.ghost-bubble--10 { bottom: 14%; left: 30%;  width: 185px; --rot: -3deg; --fx: -8px; --fy: 11px; --fdur: 3.5s;   --fdelay: -13s; }
.ghost-bubble--11 { bottom: 12%; right: 28%; width: 200px; --rot: 2deg;  --fx: 7px;  --fy: -9px; --fdur: 5.25s; --fdelay: -2s;  opacity: 0.16; filter: blur(2px); }
.ghost-bubble--12 { bottom: 3%;  left: 6%;   width: 170px; --rot: 4deg;  --fx: -6px; --fy: 10px; --fdur: 4s;   --fdelay: -5s; }
.ghost-bubble--13 { bottom: 2%;  right: 6%;  width: 230px; --rot: -2deg; --fx: 8px;  --fy: -12px; --fdur: 4.5s;   --fdelay: -8s; opacity: 0.24; filter: blur(1px); }
.ghost-bubble--14 { top: 8%;  left: 58%;  width: 150px; --rot: -3deg; --fx: 6px;  --fy: -10px; --fdur: 5s;  --fdelay: -0.5s; }
.ghost-bubble--15 { top: 9%;  right: 32%; width: 175px; --rot: 4deg;  --fx: -7px; --fy: 9px;   --fdur: 3.75s; --fdelay: -3.5s; opacity: 0.16; filter: blur(2px); }
.ghost-bubble--16 { top: 33%; left: 20%;  width: 160px; --rot: -5deg; --fx: 8px;  --fy: -11px; --fdur: 5.5s;  --fdelay: -6.5s; }
.ghost-bubble--17 { top: 35%; right: 22%; width: 185px; --rot: 3deg;  --fx: -6px; --fy: 12px;  --fdur: 4.25s; --fdelay: -9.5s; opacity: 0.24; filter: blur(1px); }
.ghost-bubble--18 { top: 55%; left: 24%;  width: 170px; --rot: -2deg; --fx: 9px;  --fy: -8px;  --fdur: 4.75s; --fdelay: -12.5s; }
.ghost-bubble--19 { top: 57%; right: 28%; width: 195px; --rot: 5deg;  --fx: -8px; --fy: 10px;  --fdur: 3.5s;   --fdelay: -1.5s; opacity: 0.16; filter: blur(2px); }
.ghost-bubble--20 { top: 76%; left: 20%;  width: 165px; --rot: -4deg; --fx: 7px;  --fy: -9px;  --fdur: 5.25s; --fdelay: -4.5s; }
.ghost-bubble--21 { top: 78%; right: 24%; width: 180px; --rot: 2deg;  --fx: -9px; --fy: 11px;  --fdur: 4s;   --fdelay: -7.5s; opacity: 0.24; filter: blur(1px); }
.ghost-bubble--22 { bottom: 20%; left: 52%;  width: 200px; --rot: -3deg; --fx: 6px;  --fy: -12px; --fdur: 5.75s; --fdelay: -10.5s; }
.ghost-bubble--23 { bottom: 18%; right: 48%; width: 155px; --rot: 4deg;  --fx: -7px; --fy: 8px;   --fdur: 4.5s;   --fdelay: -13.5s; opacity: 0.16; filter: blur(2px); }
.ghost-bubble__quote {
  display: block;
  grid-area: 1 / 1;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--ink);
  opacity: 0;
}
.ghost-bubble__quote--a { opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
  .ghost-bubble {
    animation: ghost-float var(--fdur, 20s) ease-in-out infinite;
    animation-delay: var(--fdelay, 0s);
    will-change: transform;
  }
  .ghost-bubble__quote--a,
  .ghost-bubble__quote--b {
    animation-duration: 11s;
    animation-timing-function: ease-in-out;
    animation-iteration-count: infinite;
    will-change: opacity;
  }
  .ghost-bubble__quote--a { animation-name: ghost-fade-a; }
  .ghost-bubble__quote--b { animation-name: ghost-fade-b; }
  .ghost-bubble--2  .ghost-bubble__quote--a, .ghost-bubble--2  .ghost-bubble__quote--b { animation-delay: -0.8s; }
  .ghost-bubble--3  .ghost-bubble__quote--a, .ghost-bubble--3  .ghost-bubble__quote--b { animation-delay: -1.7s; }
  .ghost-bubble--4  .ghost-bubble__quote--a, .ghost-bubble--4  .ghost-bubble__quote--b { animation-delay: -2.5s; }
  .ghost-bubble--5  .ghost-bubble__quote--a, .ghost-bubble--5  .ghost-bubble__quote--b { animation-delay: -3.4s; }
  .ghost-bubble--6  .ghost-bubble__quote--a, .ghost-bubble--6  .ghost-bubble__quote--b { animation-delay: -4.2s; }
  .ghost-bubble--7  .ghost-bubble__quote--a, .ghost-bubble--7  .ghost-bubble__quote--b { animation-delay: -5.1s; }
  .ghost-bubble--8  .ghost-bubble__quote--a, .ghost-bubble--8  .ghost-bubble__quote--b { animation-delay: -5.9s; }
  .ghost-bubble--9  .ghost-bubble__quote--a, .ghost-bubble--9  .ghost-bubble__quote--b { animation-delay: -6.8s; }
  .ghost-bubble--10 .ghost-bubble__quote--a, .ghost-bubble--10 .ghost-bubble__quote--b { animation-delay: -7.6s; }
  .ghost-bubble--11 .ghost-bubble__quote--a, .ghost-bubble--11 .ghost-bubble__quote--b { animation-delay: -8.5s; }
  .ghost-bubble--12 .ghost-bubble__quote--a, .ghost-bubble--12 .ghost-bubble__quote--b { animation-delay: -9.3s; }
  .ghost-bubble--13 .ghost-bubble__quote--a, .ghost-bubble--13 .ghost-bubble__quote--b { animation-delay: -10.2s; }
  .ghost-bubble--14 .ghost-bubble__quote--a, .ghost-bubble--14 .ghost-bubble__quote--b { animation-delay: -11.0s; }
  .ghost-bubble--15 .ghost-bubble__quote--a, .ghost-bubble--15 .ghost-bubble__quote--b { animation-delay: -0.4s; }
  .ghost-bubble--16 .ghost-bubble__quote--a, .ghost-bubble--16 .ghost-bubble__quote--b { animation-delay: -1.3s; }
  .ghost-bubble--17 .ghost-bubble__quote--a, .ghost-bubble--17 .ghost-bubble__quote--b { animation-delay: -2.1s; }
  .ghost-bubble--18 .ghost-bubble__quote--a, .ghost-bubble--18 .ghost-bubble__quote--b { animation-delay: -3.0s; }
  .ghost-bubble--19 .ghost-bubble__quote--a, .ghost-bubble--19 .ghost-bubble__quote--b { animation-delay: -3.8s; }
  .ghost-bubble--20 .ghost-bubble__quote--a, .ghost-bubble--20 .ghost-bubble__quote--b { animation-delay: -4.7s; }
  .ghost-bubble--21 .ghost-bubble__quote--a, .ghost-bubble--21 .ghost-bubble__quote--b { animation-delay: -5.5s; }
  .ghost-bubble--22 .ghost-bubble__quote--a, .ghost-bubble--22 .ghost-bubble__quote--b { animation-delay: -6.3s; }
  .ghost-bubble--23 .ghost-bubble__quote--a, .ghost-bubble--23 .ghost-bubble__quote--b { animation-delay: -7.2s; }
}
@keyframes ghost-fade-a {
  0%, 40% { opacity: 1; }
  50%, 90% { opacity: 0; }
  100% { opacity: 1; }
}
@keyframes ghost-fade-b {
  0%, 40% { opacity: 0; }
  50%, 90% { opacity: 1; }
  100% { opacity: 0; }
}
/* Gentle bob: mostly vertical drift with a slight horizontal component,
   like floating in water. Each bubble keeps its own hand-picked tilt
   (--rot) throughout, since rotate() has to stay in the same
   transform value as the translate for both to apply together. */
@keyframes ghost-float {
  0%   { transform: rotate(var(--rot, 0deg)) translate(0, 0); }
  50%  { transform: rotate(var(--rot, 0deg)) translate(var(--fx, 0), var(--fy, 0)); }
  100% { transform: rotate(var(--rot, 0deg)) translate(0, 0); }
}

.testimonials {
  position: relative;
  /* True independent-flowing columns (CSS multi-column), not a grid:
     a 2-column grid forces both columns onto the same row boundaries,
     so a short card next to a tall one leaves a dead gap before the
     next row starts. Columns let each side stack with its own uniform
     rhythm regardless of what the other side is doing. Single column
     below 700px keeps the original 1-2-3-4-5 reading order intact. */
  columns: 1;
  column-gap: 1.5rem;
}
@media (min-width: 700px) {
  .testimonials { columns: 2; }
}
.testimonial {
  /* Deliberate exception to the page's otherwise-sharp corner system:
     a chat bubble reads as a chat bubble because it's rounded. This
     is documented and applied consistently to both the foreground
     testimonials and the ghost-bubble background layer, nowhere else. */
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 1.25rem;
  padding: 1.75rem 1.75rem 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-bottom: 1.5rem;
  break-inside: avoid;
  page-break-inside: avoid;
}
.testimonial:last-child { margin-bottom: 0; }
.testimonial::after {
  content: '';
  position: absolute;
  bottom: -9px;
  left: 32px;
  width: 16px;
  height: 16px;
  background: var(--surface);
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  border-radius: 5px;
  transform: rotate(45deg);
}
.testimonial p {
  font-size: 1.0625rem;
  color: var(--ink);
}
.testimonial__label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.testimonial__label .icon { width: 1em; height: 1em; }

/* Photo variant: a real WhatsApp screenshot standing in for a quote.
   Same rounded card frame as the text bubbles (so it still reads as
   part of this set), but no padding, no speech-bubble tail (it's a
   whole screenshot, not an attributed line), and overflow hidden so
   the image's own corners pick up the card's radius.
   The source screenshots are full, tall phone captures (status bar to
   keyboard); shown at their natural aspect ratio they ran 3-4x the
   height of a text bubble and dominated the section. A fixed 4:3 crop
   window (object-fit: cover) caps the display height to roughly a
   bubble's scale regardless of viewport width, while --photo-pos
   (set per card below) aims that window at each screenshot's actual
   message content instead of an arbitrary center. */
.testimonial--photo {
  padding: 0;
  overflow: hidden;
  /* The crop ratio lives on the card, not the <img>: an <img> that
     carries width/height attributes (kept here for layout-shift
     prevention) has its own intrinsic ratio, and a bare
     aspect-ratio: <ratio> on a replaced element is shorthand for
     auto <ratio> — "auto" means prefer the intrinsic ratio when the
     element has one, so it would have silently won over this. Setting
     it on the plain (non-replaced) card container instead sidesteps
     that entirely. */
  aspect-ratio: 4 / 3;
}
.testimonial--photo::after { display: none; }
.testimonial--photo img {
  /* Absolutely positioned against the card (already position: relative,
     see .testimonial above) rather than sized with height: 100%: as a
     flex item inside an aspect-ratio'd column-flex container, a
     percentage height didn't reliably resolve, so the image kept
     rendering at its own intrinsic height with nothing for object-fit
     to actually crop — overflow: hidden was just clipping the top of
     an oversized box, making object-position a no-op. inset: 0 pins
     the image's box to the card's box unconditionally. */
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--photo-pos, center);
}

/* ---------------------------------------------------------------
   Photo gallery (real photos of Mr Soon with students)
   Same fixed-aspect cover-crop treatment as .testimonial--photo above,
   but sharp-cornered like the rest of the page's cards (no chat-bubble
   exception here) and laid out as a plain grid rather than columns,
   since every card is the same size.
--------------------------------------------------------------- */
.photo-gallery {
  margin-top: 3rem;
  display: grid;
  gap: 1.25rem;
  grid-template-columns: 1fr;
}
@media (min-width: 640px) {
  .photo-gallery { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 980px) {
  .photo-gallery { grid-template-columns: repeat(3, 1fr); }
}
.photo-gallery__item {
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  background: var(--surface);
  border: 1px solid var(--line);
}
.photo-gallery__item img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--photo-pos, center);
}

/* ---------------------------------------------------------------
   Results depth carousel
   Cards are positioned entirely by JS (main.js) via inline transform
   + filter, computed from each card's distance from the focused
   index. This stylesheet only sets up the 3D stage, the resting
   look of a card, and the controls; --carousel-scale is written by
   a ResizeObserver in JS so the whole fan shrinks to fit narrow
   viewports instead of overflowing.
--------------------------------------------------------------- */
.depth-carousel {
  margin-top: 3rem;
}
.depth-carousel__stage {
  position: relative;
  height: 320px;
  overflow: hidden;
  perspective: 1400px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: grab;
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
  outline: none;
}
.depth-carousel__stage.is-dragging { cursor: grabbing; }
.depth-carousel__stage:focus-visible {
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent);
}
.depth-carousel__track {
  position: relative;
  width: 360px;
  height: 225px;
  transform-style: preserve-3d;
  transform: scale(var(--carousel-scale, 1));
}
@media (min-width: 700px) {
  .depth-carousel__stage { height: 440px; }
  .depth-carousel__track { width: 480px; height: 300px; }
}
@media (min-width: 1000px) {
  .depth-carousel__stage { height: 560px; }
  .depth-carousel__track { width: 640px; height: 400px; }
}
.depth-carousel__card {
  position: absolute;
  inset: 0;
  border: 1px solid var(--line);
  background: var(--surface);
  padding: 0.5rem;
  backface-visibility: hidden;
  will-change: transform, filter, opacity;
}
.depth-carousel__card img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: var(--surface-sunken);
  display: block;
  pointer-events: none;
}
.depth-carousel__card--center { cursor: default; }
.depth-carousel__card--clickable { cursor: pointer; }

/* ---------------------------------------------------------------
   CTA band
--------------------------------------------------------------- */
.cta-band {
  position: relative;
  background: var(--ink);
  color: var(--surface);
  padding-block: 4.5rem;
  overflow: hidden;
}
/* Optional WebGL wave texture (see js/gradient-waves.js). Sits behind
   the content at low opacity; if it never mounts (no WebGL2, reduced
   motion, or JS disabled) the section is still just the solid --ink
   background underneath, so nothing depends on it visually. */
.cta-band__waves {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
.cta-band__grid {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 2rem;
  align-items: flex-start;
  justify-content: space-between;
}
@media (min-width: 800px) {
  .cta-band__grid { flex-direction: row; align-items: center; }
}
.cta-band h2 {
  font-size: clamp(2rem, 1.5rem + 2vw, 2.625rem);
  max-width: 18ch;
  margin-bottom: 0.75rem;
  color: var(--surface);
}
.cta-band p {
  color: #c7cac8;
  max-width: 34ch;
}
.cta-band__actions { flex-shrink: 0; }

.gradient-waves-container {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

/* ---------------------------------------------------------------
   Footer
--------------------------------------------------------------- */
.site-footer {
  padding-block: 4rem 2rem;
  border-top: 1px solid var(--line);
}
.footer__grid {
  display: grid;
  gap: 2.5rem;
  margin-bottom: 3rem;
}
@media (min-width: 700px) {
  .footer__grid { grid-template-columns: 1.3fr 1fr 1fr; gap: 2rem; }
}
.footer__brand p {
  color: var(--ink-soft);
  max-width: 32ch;
  margin-top: 1rem;
}
.footer__heading {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 1rem;
}
.footer__list { display: flex; flex-direction: column; gap: 0.65rem; }
.footer__list a, .footer__list span {
  color: var(--ink-soft);
  font-size: 0.9375rem;
}
.footer__list a:hover { color: var(--ink); }
.footer__list .icon { width: 1em; height: 1em; margin-right: 0.5rem; vertical-align: -0.15em; color: var(--ink-faint); }
.footer__bottom {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding-top: 2rem;
  border-top: 1px solid var(--line);
  font-size: 0.8125rem;
  color: var(--ink-faint);
}
@media (min-width: 700px) {
  .footer__bottom { flex-direction: row; justify-content: space-between; }
}

/* ---------------------------------------------------------------
   Subjects grid
--------------------------------------------------------------- */
.subjects {
  display: grid;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  grid-template-columns: 1fr;
}
@media (min-width: 640px) {
  .subjects { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 980px) {
  /* 5 subject cards: 3 across the first row, 2 across the second.
     A 6-column track lets both rows fill edge to edge with no
     leftover cell (3x2-col cards, then 2x3-col cards). */
  .subjects { grid-template-columns: repeat(6, 1fr); }
  .subject-card { grid-column: span 2; }
  .subject-card--wide { grid-column: span 3; }
}
.subject-card {
  background: var(--surface);
  padding: 2rem 2rem 2rem 1.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  border-left: 3px solid var(--accent);
  transition: transform 180ms var(--ease-out), box-shadow 180ms var(--ease-out), border-left-color 180ms var(--ease-out);
}
/* SPM cards get the same treatment in a neutral tone, echoing the
   muted vs. accent tag colors already used to tell the two exam
   boards apart. */
.subject-card--wide { border-left-color: var(--line-strong); }
@media (hover: hover) and (pointer: fine) {
  .subject-card:hover {
    transform: translateY(-3px);
    box-shadow: 0 12px 24px -16px rgba(21, 23, 26, 0.22);
  }
  .subject-card:hover.subject-card--wide { border-left-color: var(--ink-faint); }
}
.subject-card__code {
  align-self: flex-start;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  padding: 0.3rem 0.6rem;
  background: var(--accent-soft);
  color: var(--accent-dark);
  border-radius: var(--radius-control);
}
.subject-card__code--muted {
  background: var(--surface-sunken);
  color: var(--ink-soft);
}
.subject-card h3 { font-size: 1.1875rem; }
.subject-card p { color: var(--ink-soft); font-size: 0.9375rem; }

.reach-callout {
  margin-top: 2rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  background: var(--accent-soft);
  padding: 1.75rem 2rem;
}
.reach-callout__label {
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 1rem;
}
.reach-callout__list {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
}
@media (min-width: 560px) {
  .reach-callout__list { flex-direction: row; gap: 2.5rem; }
}
.reach-callout__list li {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--ink-soft);
  font-size: 0.9375rem;
}
.reach-callout__list .icon {
  width: 1.1rem;
  height: 1.1rem;
  color: var(--accent-dark);
  flex-shrink: 0;
}
.reach-callout__list strong { color: var(--ink); font-weight: 600; }

/* ---------------------------------------------------------------
   Approach timeline
--------------------------------------------------------------- */
.approach {
  display: flex;
  flex-direction: column;
}
.approach__step {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 1.5rem;
  padding-block: 2rem;
  border-bottom: 1px solid var(--line);
}
.approach__step:first-child { padding-top: 0; }
.approach__step:last-child { border-bottom: none; padding-bottom: 0; }
.approach__marker {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.approach__number {
  color: var(--accent-dark);
  background: var(--accent-soft);
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.approach__number .icon { width: 1.5rem; height: 1.5rem; }
.approach__line {
  width: 1px;
  flex: 1;
  background: var(--line);
  margin-top: 0.75rem;
}
.approach__step h3 {
  font-size: 1.3125rem;
  margin-bottom: 0.6rem;
}
.approach__step p { color: var(--ink-soft); max-width: 52ch; }

/* ---------------------------------------------------------------
   Location and hours
--------------------------------------------------------------- */
.location {
  display: grid;
  gap: 2rem;
}
@media (min-width: 900px) {
  .location { grid-template-columns: 1fr 1fr; align-items: stretch; }
}
.location__details {
  border: 1px solid var(--line);
  background: var(--surface);
  padding: 2.25rem;
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}
.location__row { display: flex; gap: 1rem; }
.location__row .icon { color: var(--accent); flex-shrink: 0; margin-top: 0.2rem; }
.location__row h4 {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.9375rem;
  margin-bottom: 0.3rem;
}
.location__row p { color: var(--ink-soft); font-size: 0.9375rem; }
.location__row a { color: var(--accent-dark); font-weight: 600; }
.location__map {
  border: 1px solid var(--line);
  min-height: 260px;
  background: var(--surface-sunken);
}
.location__map iframe {
  width: 100%;
  height: 100%;
  min-height: 260px;
  border: 0;
  display: block;
}

.online-note {
  border: 1px solid var(--line);
  background: var(--surface);
  padding: 2rem 2.25rem;
  display: flex;
  gap: 1.25rem;
  align-items: flex-start;
}
.online-note .icon { color: var(--accent); margin-top: 0.2rem; flex-shrink: 0; }
.online-note p { color: var(--ink-soft); }
.online-note strong { color: var(--ink); }
