/* ============================================================================
   Lee Tennis Co.
   ----------------------------------------------------------------------------
   Presence comes from scale, colour and photography — not from decoration.
   One motion idea (fade + lift), applied consistently with a stagger.

   Everything adjustable is in :root below.
   ========================================================================== */

:root {
  /* Strictly black and white. The only "colour" decisions here are how far
     down from white the greys sit. */
  --bg:      #ffffff;
  --bg-soft: #f4f4f2;   /* the alternating band */
  --card:    #fafafa;   /* card fill on white sections */
  --ink:     #0d0d0d;   /* the full-bleed black blocks */
  --accent:  #111111;   /* accent marks — black, not a colour */
  --text:    #111111;
  --muted:   #5e5e5e;
  --line:    #e5e5e3;

  /* System UI font — native-looking, instant, zero network requests. */
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;

  --wrap: 1140px;
  /* At least the safe area, so on an iPhone held sideways (viewport-fit=cover)
     no text sits under the notch or the rounded corner. */
  --pad: max(clamp(1.25rem, 5vw, 2.5rem), env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px));
  --section-y: clamp(4rem, 9vw, 7rem);

  --radius: 6px;

  /* Motion. This curve moves most of the distance early and settles slowly,
     which is what makes a fade feel smooth rather than mechanical. */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --dur-fade: .8s;    /* text and blocks arriving */
  --dur-img: 1.15s;   /* photos settling out of a slight zoom */
  --dur-ui: .3s;      /* hovers */
}

/* ---------------------------------------------------------------- Reset -- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 1.0625rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; height: auto; display: block; }
h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -0.03em; line-height: 1.15; }
p { margin: 0 0 1rem; }
p:last-child { margin-bottom: 0; }
ul { margin: 0; padding: 0; list-style: none; }
a { color: inherit; }
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
dl, dd { margin: 0; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* The accent is black, so the ring vanishes on the black bands. */
.contact :focus-visible, .footer :focus-visible, .statement :focus-visible,
.lightbox :focus-visible { outline-color: #fff; }
::selection { background: var(--ink); color: #fff; }
.contact ::selection, .footer ::selection, .statement ::selection { background: #fff; color: var(--ink); }

/* Sides are logical (inline-start/end) throughout, so the Persian page,
   which runs right to left, mirrors without a second set of rules. */
.skip-link {
  position: absolute; inset-inline-start: -9999px; top: 0; z-index: 100;
  background: var(--text); color: #fff; padding: .7rem 1.1rem; text-decoration: none;
}
.skip-link:focus { inset-inline-start: 0; }

.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--pad); }
.section { padding-block: var(--section-y); }
.band { background: var(--bg-soft); }

/* --------------------------------------------------------------- Header -- */
.header {
  position: sticky; top: 0; z-index: 50;
  /* Nearly opaque: at .88 the black bands blurred up through it into a
     muddy grey bar. */
  background: rgba(255, 255, 255, .97);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
.header-row { display: flex; align-items: center; gap: 1.5rem; min-height: 66px; }
.brand {
  display: inline-flex; align-items: center; flex-shrink: 0;
  text-decoration: none; margin-inline-end: auto; padding-block: .25rem;
}
.brand-logo { width: 168px; height: 56px; }
.nav { display: flex; gap: 1.7rem; }
/* The stacked mobile menu's first row; the desktop header has its own button. */
.nav-book { display: none; }
.nav a {
  font-size: .9375rem; color: var(--muted); text-decoration: none;
  white-space: nowrap;   /* a label never breaks; the header folds instead */
  transition: color var(--dur-ui) var(--ease);
}
.nav a:hover { color: var(--text); }

/* The only nav item that leaves the page, so it gets a divider to sit
   apart from the in-page anchors beside it. */
.nav-portfolio {
  display: inline-flex; align-items: center; gap: .45rem;
  padding-inline-start: 1.7rem;
  border-inline-start: 1px solid var(--line);
}
/* Hover: the muted label slides up and its black twin slides in beneath it,
   while the arrow draws itself left to right. The arrow keeps its width at
   rest so nothing beside it moves; only its stroke is withheld. */
.np-label { display: block; overflow: hidden; line-height: 1.3; }
.np-text {
  display: block; position: relative;
  transition: transform .45s var(--ease);
}
.np-text::after {
  content: attr(data-text);
  position: absolute; inset-inline-start: 0; top: 100%;
  color: var(--text); white-space: nowrap;   /* same weight: a heavier twin is wider and wraps */
}
.np-arrow { flex: 0 0 auto; color: var(--text); }
/* Arrows point the way the text reads. */
[dir="rtl"] .np-arrow, [dir="rtl"] .ask-send svg { transform: scaleX(-1); }
.np-arrow path {
  stroke-dasharray: 1; stroke-dashoffset: 1;
  transition: stroke-dashoffset .45s var(--ease) .05s;
}
.nav-portfolio:hover .np-text,
.nav-portfolio:focus-visible .np-text { transform: translateY(-100%); }
.nav-portfolio:hover .np-arrow path,
.nav-portfolio:focus-visible .np-arrow path { stroke-dashoffset: 0; }

.nav-toggle {
  display: none;
  flex-direction: column; justify-content: center; gap: 5px;
  width: 44px; height: 44px;
  border: 1px solid var(--line); border-radius: var(--radius);
}
.nav-toggle span {
  display: block; width: 16px; height: 1.5px; margin-inline: auto;
  background: var(--text); transition: transform var(--dur-ui) var(--ease);
}
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(3.25px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { transform: translateY(-3.25px) rotate(-45deg); }

/* Language menu, beside the Book button: a globe and the page's language
   code, opening the list of translations, each named in its own language.
   A <details>, so it works with no script; tennis.js adds the closing on a
   tap outside and on Escape. */
.lang { position: relative; flex: 0 0 auto; }
.lang summary {
  display: inline-flex; align-items: center; gap: .35rem;
  min-height: 2.75rem; padding-inline: .35rem;
  font-size: .875rem; font-weight: 500; color: var(--muted);
  list-style: none; cursor: pointer; border-radius: var(--radius);
  transition: color var(--dur-ui) var(--ease);
}
.lang summary::-webkit-details-marker { display: none; }
.lang summary:hover, .lang[open] summary { color: var(--text); }
.lang-globe { flex: 0 0 auto; }
.lang-list {
  position: absolute; top: calc(100% + .35rem); inset-inline-end: 0; z-index: 2;
  min-width: 11.5rem; padding: .35rem;
  background: var(--bg);
  border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 12px 30px rgba(0, 0, 0, .1);
  animation: langIn .22s var(--ease);
}
@keyframes langIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.lang-list a {
  display: flex; align-items: center; gap: .75rem;
  min-height: 2.75rem; padding-inline: .75rem;
  font-size: .9375rem; color: var(--text); text-decoration: none;
  border-radius: calc(var(--radius) - 2px);
}
.lang-list a:hover { background: var(--bg-soft); }
.lang-list a[aria-current="page"] { font-weight: 600; }
.lang-list a[aria-current="page"]::after {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--text); margin-inline-start: auto;
}

/* -------------------------------------------------------------- Buttons -- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  padding: .8rem 1.5rem;
  font-size: .9375rem; font-weight: 500;
  border-radius: var(--radius);
  border: 1px solid transparent;
  text-decoration: none;
  transition: background-color var(--dur-ui) var(--ease),
              border-color var(--dur-ui) var(--ease),
              color var(--dur-ui) var(--ease),
              transform var(--dur-ui) var(--ease);
}
.btn:hover { transform: translateY(-1px); }

.btn-primary { background: var(--text); color: #fff; }
.btn-primary:hover { background: #383838; }   /* accent is black now, so lift the hover instead */

.btn-quiet { border-color: var(--line); color: var(--text); background: var(--bg); }
.btn-quiet:hover { border-color: var(--text); }

/* For use on the black blocks (contact, statement) */
.btn-invert { background: #fff; color: var(--ink); }
.btn-invert:hover { background: #f0efe9; }
.btn-outline { border-color: rgba(255, 255, 255, .38); color: #fff; }
.btn-outline:hover { border-color: #fff; background: rgba(255, 255, 255, .08); }

.btn-lg { padding: .95rem 1.8rem; font-size: 1rem; }
.btn-sm { padding: .55rem 1.1rem; font-size: .875rem; }
.header-cta { white-space: nowrap; flex: 0 0 auto; }
.header-cta .cta-short { display: none; }

.actions { display: flex; flex-wrap: wrap; gap: .7rem; margin-top: 2rem; }

.link-btn {
  display: inline-flex; align-items: center; min-height: 2.75rem;   /* 44px hit area */
  padding: 0; font-size: .9375rem; font-weight: 500;
  color: var(--text);
  text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 1px;
  transition: color var(--dur-ui) var(--ease);
}
.link-btn:hover { color: var(--muted); }

/* Small label above each heading. A short black rule instead of the
   wide-tracked uppercase that every template reaches for. */
.eyebrow {
  display: flex; align-items: center; gap: .6rem;
  font-size: .875rem; color: var(--muted); margin-bottom: .9rem;
}
.eyebrow::before {
  content: ""; width: 22px; height: 2px; background: var(--accent); flex: 0 0 auto;
}

/* ----------------------------------------------------------------- Hero -- */
.hero {
  /* Tight at the top: on a laptop the prices have to make the first screen. */
  padding-block: clamp(1.5rem, 3vw, 2.5rem) clamp(3rem, 7vw, 5rem);
  overflow: hidden;               /* guards the bleed against side-scroll */
}
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 42%);
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
}
/* The text stays aligned to the container, but the photo runs off the right
   edge of the screen. Breaking the grid on one side only is what stops the
   layout feeling like a row of boxes. */
.hero-text {
  padding-inline-start: max(var(--pad), calc((100vw - var(--wrap)) / 2 + var(--pad)));
  padding-inline-end: var(--pad);
}
.hero h1 {
  /* Sized by the shorter of width and height, so a short laptop screen
     still gets the prices and the Book button in the first view. */
  font-size: clamp(3rem, min(7.5vw, 9.5vh), 5.5rem);
  letter-spacing: -0.055em;
  line-height: .92;
  margin-bottom: 1.4rem;
}
.hero-sub { font-size: 1.1875rem; color: var(--muted); max-width: 42ch; }

/* Isaac's emoji, right after his name in the intro (images/isaac-emoji.svg,
   drawn from the hero photo). It sits like a glyph: em-sized, on the text's
   baseline, never splitting from the name across a line break. It holds
   still. Decorative: the name is already text. */
.me { white-space: nowrap; color: var(--text); }
.me-emoji {
  display: inline-block;
  width: 1.45em; height: 1.45em;
  margin-inline-start: .22em;
  vertical-align: -.36em;
}

.hero-facts {
  display: flex; flex-wrap: wrap; gap: 2.5rem;
  margin-top: 1.75rem; padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}
.hero-facts + .actions { margin-top: 1.5rem; }

/* "No card, no deposit" under the Book buttons: the page's best
   reassurance, said where the decision is made. */
.book-note { margin: .8rem 0 0; font-size: .875rem; color: var(--muted); }
.contact .book-note { color: rgba(255, 255, 255, .72); }
.book-note .link-btn { display: flex; width: fit-content; margin-top: .15rem; font-size: .875rem; }
.contact .link-btn { color: #fff; }
.contact .link-btn:hover { color: rgba(255, 255, 255, .72); }
.hero-facts dt { font-size: .8125rem; color: var(--muted); margin-bottom: .3rem; }
.hero-facts dd { font-size: 1.6rem; font-weight: 600; letter-spacing: -0.03em; line-height: 1; }
.hero-facts dd span { font-size: .875rem; font-weight: 400; color: var(--muted); letter-spacing: 0; }

/* No rounded corner on the bleeding side — it has to meet the edge cleanly. */
.hero-photo { margin: 0; overflow: hidden; position: relative; }
.hero-photo img { width: 100%; aspect-ratio: 3/4; object-fit: cover; }

/* The looping clip (hero.js), when there is one: it sits over the photo
   and fades in only once it is actually playing, so a slow connection
   just keeps seeing the photograph. */
.hero-loop {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity 1.2s var(--ease);
  pointer-events: none;
}
.hero-photo.has-loop .hero-loop { opacity: 1; }

/* ------------------------------------------------------------ Statement -- */
.statement {
  background: var(--ink);
  color: #fff;
  padding-block: clamp(3.5rem, 8vw, 6.5rem);
}
.statement blockquote {
  margin: 0;
  max-width: 36ch;                /* three lines, breaking at the full stop */
  text-wrap: pretty;
  font-size: clamp(1.85rem, 5.2vw, 3.75rem);
  font-weight: 600;
  letter-spacing: -0.045em;
  line-height: 1.06;
}

/* White brackets on the black band; the unfocused words go soft but stay
   legible, so the sentence never becomes a grey smear. */
.statement [data-focus-reveal] { --fr-color: #fff; --fr-dim: .38; --fr-blur: .055em; --fr-stroke: 2px; }

/* --------------------------------------------------------- Shared blocks -- */
.two-col {
  display: grid;
  grid-template-columns: .8fr 1.2fr;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
}
.section-head { max-width: 40rem; margin-bottom: clamp(2.5rem, 5vw, 3.5rem); }
.section-head h2, .about-text h2, .contact-text h2 {
  font-size: clamp(1.75rem, 3.4vw, 2.5rem);
}
.section-sub { color: var(--muted); margin-top: 1rem; }

/* ---------------------------------------------------------------- About -- */
.about-photo { margin: 0; border-radius: var(--radius); overflow: hidden; }
.about-photo img { width: 100%; aspect-ratio: 3/2; object-fit: cover; }
/* The clinic frame is wider than 3/2, so a fifth of it is cropped. The crop
   comes off the right: the coach is at the left edge of the photograph and
   is the point of putting it here; the right edge only held a court sign
   and half of a bystander. */
.about-photo img { object-position: 0% 50%; }
.about-text h2 { margin-bottom: 1.4rem; }
.about-text p { color: var(--muted); max-width: 60ch; }

.facts {
  display: flex; flex-wrap: wrap; gap: 2.5rem;
  margin-top: 2.25rem; padding-top: 1.6rem;
  border-top: 1px solid var(--line);
}
.facts dt { font-size: .8125rem; color: var(--muted); margin-bottom: .25rem; }
.facts dd { font-weight: 600; font-size: 1.125rem; }

/* -------------------------------------------------------------- Lessons -- */
.lesson-list {
  display: grid; grid-template-columns: repeat(2, 1fr);
  gap: clamp(1rem, 2vw, 1.5rem);
}
.lesson {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(1.5rem, 2.5vw, 2rem);
  transition: transform var(--dur-ui) var(--ease),
              border-color var(--dur-ui) var(--ease),
              box-shadow var(--dur-ui) var(--ease);
}
.lesson:hover {
  transform: translateY(-3px);
  border-color: #d6d3cb;
  box-shadow: 0 12px 30px rgba(0, 0, 0, .06);
}
.lesson h3 { font-size: 1.125rem; }
.lesson p { color: var(--muted); font-size: 1rem; }
.lesson-actions {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .5rem 1.25rem; margin-top: 1.25rem;
}

/* Lesson matcher (match.js), under the cards. One plain field and the
   page's own button; the answer is a sentence and the matched card's real
   Book control. The card it picks gets a black edge and a label, the same
   weight as a hover, so the other three stay readable rather than being
   greyed out. */
.matcher {
  max-width: 40rem;
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
}
.matcher[hidden] { display: none; }
.matcher-label { display: block; font-weight: 600; margin-bottom: .6rem; }
.matcher-row { display: flex; gap: .6rem; }
.matcher-input {
  flex: 1 1 auto; min-width: 0;
  padding: .8rem 1rem;
  font: inherit; font-size: 1rem; color: var(--text);
  background: var(--bg);
  border: 1px solid #c9c9c6;
  border-radius: var(--radius);
  transition: border-color var(--dur-ui) var(--ease);
}
.matcher-input::placeholder { color: var(--muted); opacity: 1; }
.matcher-input:hover { border-color: var(--muted); }
.matcher-input:focus { border-color: var(--text); }
.matcher-row .btn { flex: 0 0 auto; }
.matcher-row .btn:disabled { cursor: progress; background: #383838; transform: none; }
.matcher-result:not(:empty) { margin-top: 1.1rem; }
.matcher-result p { margin-bottom: .5rem; }
.matcher-result p + p { color: var(--muted); }
.matcher-actions {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .5rem 1.25rem; margin-top: .9rem;
}
.matcher-note { margin-top: .9rem; font-size: .8125rem; color: var(--muted); max-width: 60ch; }

/* ----------------------------------------------------------- Book FX -- */
/* The stick-figure server on a Book button (bookfx.js). One fixed layer
   above everything, the calendar's popup included, that never takes a
   click; the script draws and animates the pieces. The optic yellow ball
   is the page's one deliberate colour, and only for the second it's in
   the air. */
.bookfx {
  position: fixed; inset: 0; z-index: 2147483647;
  pointer-events: none; overflow: hidden;
}
.bookfx > span { position: absolute; display: block; will-change: transform, opacity; }
.bookfx-ring {
  width: 120px; height: 120px;
  border: 2px solid; border-radius: 50%;
}
.bookfx-fuzz {
  width: 6px; height: 6px; border-radius: 50%;
  background: #d9ef3f;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .08);
}
.bookfx-fuzz:nth-child(3n) { background: #fff; }

/* The one-time note after the first Book press that makes a sound: says
   the sound is on and offers the off switch where it happened. Ink bar,
   bottom centre, above the calendar popup. */
.sound-note {
  position: fixed; z-index: 2147483647;
  left: 50%; bottom: max(1rem, env(safe-area-inset-bottom));
  transform: translateX(-50%);
  display: flex; align-items: center; gap: 1rem;
  white-space: nowrap;   /* one line, even at 390px */
  padding-block: .45rem; padding-inline: 1rem .5rem;
  background: var(--ink); color: #fff;
  border-radius: var(--radius);
  box-shadow: 0 10px 28px rgba(0, 0, 0, .28);
  font-size: .875rem;
  animation: noteIn .35s var(--ease) both;
  transition: opacity .3s var(--ease), transform .3s var(--ease);
}
.sound-note button {
  min-height: 2.25rem; padding: 0 .6rem;
  color: #fff; font-weight: 600;
  text-decoration: underline; text-underline-offset: 3px;
  border-radius: calc(var(--radius) - 2px);
}
.sound-note button:hover { background: rgba(255, 255, 255, .12); }
.sound-note button:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.sound-note.is-out { opacity: 0; transform: translate(-50%, 8px); }
@keyframes noteIn { from { opacity: 0; transform: translate(-50%, 8px); } to { opacity: 1; transform: translateX(-50%); } }
/* The stick-figure server is drawn into one viewport-sized SVG per press. */
.bookfx-court { position: absolute; left: 0; top: 0; overflow: visible; }

/* --------------------------------------------------------- Ask Isaac -- */
/* The chat in the corner (ask.js). A black launcher that reads like the
   page's own Book button, and a white panel with the card border and the
   one floating shadow on the page: it is the only thing that sits above
   the content, so it is the only thing that casts one. Isaac's messages
   sit on the soft band grey, the visitor's on black. */
.ask {
  position: fixed; z-index: 60;
  inset-inline-end: max(1rem, env(safe-area-inset-right));
  bottom: max(1rem, env(safe-area-inset-bottom));
  display: flex; flex-direction: column; align-items: flex-end; gap: .75rem;
}
.ask[hidden], .ask-panel[hidden] { display: none; }

.ask-launch {
  display: inline-flex; align-items: center; gap: .5rem;
  min-height: 2.75rem; padding: .7rem 1.15rem;
  font-size: .9375rem; font-weight: 500;
  background: var(--text); color: #fff;
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, .16);
  transition: background-color var(--dur-ui) var(--ease), transform var(--dur-ui) var(--ease);
}
.ask-launch:hover { background: #383838; transform: translateY(-1px); }
.ask.is-open .ask-launch { background: #383838; }
/* Over the black bands it inverts, like the page's own buttons do there. */
.ask.on-dark .ask-launch { background: #fff; color: var(--ink); box-shadow: 0 8px 24px rgba(0, 0, 0, .45); }
.ask.on-dark .ask-launch:hover { background: #f0efe9; }

.ask-panel {
  display: flex; flex-direction: column;
  width: min(23.5rem, calc(100vw - 2rem));
  height: min(38rem, calc(100dvh - 6.5rem));
  background: var(--bg);
  border-radius: var(--radius);
  /* Depth from the shadow alone; a hairline on top of a wide shadow is a
     belt and braces that reads as a template. */
  box-shadow: 0 16px 40px rgba(0, 0, 0, .16), 0 2px 6px rgba(0, 0, 0, .06);
  overflow: hidden;
  animation: askIn .28s var(--ease);
}
@keyframes askIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

.ask-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem;
  padding-block: 1rem .9rem; padding-inline: 1.25rem 1rem;
  border-bottom: 1px solid var(--line);
}
.ask-head h2 { font-size: 1.0625rem; letter-spacing: -0.02em; }
.ask-head p { margin: .15rem 0 0; font-size: .8125rem; color: var(--muted); }
.ask-tools { display: flex; flex: 0 0 auto; margin-block-start: -.5rem; margin-inline-end: -.5rem; }
.ask-close, .ask-sound {
  width: 2.75rem; height: 2.75rem;
  display: grid; place-items: center;
  font-size: 1.5rem; line-height: 1; color: var(--muted); border-radius: var(--radius);
  transition: color var(--dur-ui) var(--ease), background-color var(--dur-ui) var(--ease);
}
.ask-close:hover, .ask-sound:hover { color: var(--text); background: var(--bg-soft); }
.ask-sound .ask-sound-off { display: none; }
.ask-sound.is-off .ask-sound-on { display: none; }
.ask-sound.is-off .ask-sound-off { display: inline; }

.ask-log {
  position: relative;   /* offsetTop of a message is measured from here */
  flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain;
  padding: 1.1rem 1.25rem;
  display: flex; flex-direction: column; gap: .8rem;
}
.ask-log:focus { outline: none; }

.ask-msg {
  max-width: 88%;
  padding: .7rem .9rem;
  border-radius: var(--radius);
  font-size: .9375rem; line-height: 1.55;
}
.ask-msg p { margin: 0; }
.ask-msg p + p { margin-top: .6rem; }
.ask-msg--isaac { align-self: flex-start; background: var(--bg-soft); color: var(--text); }
.ask-msg--visitor { align-self: flex-end; background: var(--text); color: #fff; }
/* The whole message for screen readers, speaker first; the visible copy
   types itself out and is hidden from them. */
.ask-sr {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap;
}
/* Typing out: every word is laid out from the start and only fades in, so
   the bubble is full size at once and nothing below it jumps. */
.ask-w { opacity: 0; transition: opacity .18s var(--ease); }
.ask-w.is-in { opacity: 1; }
/* Buttons and follow-up chips arrive once the text has finished. */
.ask-arrive { animation: askArrive .32s var(--ease) both; }
@keyframes askArrive { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.ask-msg--visitor { animation: askArrive .22s var(--ease) both; }
.ask-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .8rem; }
.ask-actions .btn { min-height: 2.5rem; }

.ask-chips { display: flex; flex-wrap: wrap; gap: .5rem; }
.ask-chip {
  padding: .5rem .8rem; min-height: 2.5rem;
  font-size: .875rem; color: var(--text);
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg);
  transition: border-color var(--dur-ui) var(--ease);
}
.ask-chip:hover { border-color: var(--text); }
.ask-chip:active { background: var(--bg-soft); }
/* Follow-ups sit on Isaac's side, under the answer they belong to. */
.ask-chips { max-width: 92%; }

.ask-typing { display: flex; gap: .3rem; padding: .95rem .9rem; }
.ask-dot {
  width: .4rem; height: .4rem; border-radius: 50%; background: var(--muted);
  animation: askDot 1s var(--ease) infinite;
}
.ask-dot:nth-child(2) { animation-delay: .15s; }
.ask-dot:nth-child(3) { animation-delay: .3s; }
@keyframes askDot { 0%, 60%, 100% { opacity: .35; } 30% { opacity: 1; } }

.ask-form {
  display: flex; gap: .5rem;
  padding: .75rem;
  border-top: 1px solid var(--line);
}
.ask-input {
  flex: 1 1 auto; min-width: 0;
  padding: .7rem .85rem;
  font: inherit; font-size: 1rem; color: var(--text);
  background: var(--bg);
  border: 1px solid #c9c9c6; border-radius: var(--radius);
  transition: border-color var(--dur-ui) var(--ease);
}
.ask-input::placeholder { color: var(--muted); opacity: 1; }
/* The chat puts focus back in the field after every answer, so the page's
   2px outline would sit round it permanently; a black border doubled with
   an inset ring marks focus as clearly and quieter. */
.ask-input:focus { border-color: var(--text); }
.ask-input:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--text); }
.ask-send {
  flex: 0 0 auto; width: 2.75rem; height: 2.75rem;
  display: grid; place-items: center;
  background: var(--text); color: #fff; border-radius: var(--radius);
  transition: background-color var(--dur-ui) var(--ease);
}
.ask-send:hover { background: #383838; }
.ask-send:disabled { background: #383838; cursor: progress; }

/* Phones: the panel becomes a sheet across the bottom of the screen, and
   the launcher steps aside while it is open (the sheet has its own close). */
@media (max-width: 560px) {
  .ask { left: 0; right: 0; bottom: 0; align-items: stretch; gap: 0; pointer-events: none; }
  .ask > * { pointer-events: auto; }
  .ask-launch {
    align-self: flex-end;
    margin: 0;
    margin-inline-end: max(1rem, env(safe-area-inset-right));
    margin-block-end: max(1rem, env(safe-area-inset-bottom));
  }
  .ask.is-open .ask-launch { display: none; }
  /* Room at the very bottom of the page, so the last line of the footer can
     scroll clear of the launcher instead of ending underneath it. */
  .footer { padding-bottom: calc(4.5rem + env(safe-area-inset-bottom)); }
  /* A compact square, so it covers as little as possible of the full-width
     buttons it floats over; the label stays for screen readers. While the
     hero's own Book button is on screen the launcher steps out entirely. */
  .ask-launch { width: 3rem; height: 3rem; padding: 0; justify-content: center; }
  .ask-launch span {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0, 0, 0, 0); white-space: nowrap;
  }
  .ask-launch svg { width: 20px; height: 20px; }
  .ask.is-tucked .ask-launch { opacity: 0; transform: translateY(8px); pointer-events: none; visibility: hidden; }
  .ask-launch { transition: opacity var(--dur-ui) var(--ease), transform var(--dur-ui) var(--ease), visibility 0s linear 0s; }
  .ask.is-tucked .ask-launch { transition: opacity var(--dur-ui) var(--ease), transform var(--dur-ui) var(--ease), visibility 0s linear var(--dur-ui); }
  .ask-panel {
    width: 100%; height: min(78dvh, 36rem);
    border-radius: var(--radius) var(--radius) 0 0;
    border-bottom: 0;
    padding-bottom: env(safe-area-inset-bottom);
  }
}

@media print { .ask { display: none; } }

.lesson.is-match { border-color: var(--text); box-shadow: 0 0 0 1px var(--text); }
.lesson.is-match h3 { display: flex; flex-wrap: wrap; align-items: baseline; gap: .35rem .75rem; }
.lesson.is-match h3::after {
  content: attr(data-match);   /* set by match.js, in the page's language */
  font-size: .8125rem; font-weight: 500; letter-spacing: 0;
  color: var(--muted);
}

/* One treatment for every figure on the page: the hero facts, the card
   prices and the modal prices. Selectors carry a class ahead of .price so
   the card and modal paragraph rules (.lesson p, .modal-card > p), which
   mute and shrink body copy, cannot outrank it — that is how every price
   on the page ended up 16px grey. */
.price, .lesson .price, .modal-card .price {
  color: var(--text);
  font-size: 1.6rem; font-weight: 600; letter-spacing: -0.03em;
  margin: .5rem 0 .9rem; line-height: 1.2;
}
.price span { font-size: .875rem; font-weight: 400; color: var(--muted); letter-spacing: 0; }

/* --------------------------------------------------------------- Photos -- */
.photos { padding-block: var(--section-y); }
.photos .section-head { margin-bottom: clamp(2rem, 4vw, 3rem); }

/* Edge to edge, hairline gaps. No container, no rounded corners — the run of
   images reads as one band rather than three cards. */
.photo-strip {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2px;
}
.photo {
  position: relative;
  display: block; padding: 0;
  overflow: hidden;
  background: var(--ink);
}
.photo img {
  width: 100%; aspect-ratio: 4/5; object-fit: cover;
  transition: transform .6s var(--ease), filter .5s var(--ease), opacity .5s var(--ease);
}
.photo:hover img { transform: scale(1.04); }

/* ---------------------------------------------------- Page transitions --
   Going between the tennis page and the portfolio (same site, both opt in)
   is one continuous move instead of a hard reload: the old page lifts and
   fades, the new one rises in under it. Browsers without cross-document
   view transitions just navigate normally. */
@view-transition { navigation: auto; }
::view-transition-old(root) { animation: vtOut .32s cubic-bezier(.4, 0, 1, 1) both; }
::view-transition-new(root) { animation: vtIn .55s cubic-bezier(.22, 1, .36, 1) .06s both; }
@keyframes vtOut { to { opacity: 0; transform: translateY(-2.5vh) scale(.985); } }
@keyframes vtIn { from { opacity: 0; transform: translateY(4vh); } }
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

/* Caption sits on the image and slides up on hover. */
.photo-tag {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 2.5rem 1rem .9rem;
  font-size: .8125rem; font-weight: 500; color: #fff;
  background: linear-gradient(transparent, rgba(0, 0, 0, .65));
  opacity: 0; transform: translateY(6px);
  transition: opacity var(--dur-ui) var(--ease), transform var(--dur-ui) var(--ease);
}
.photo:hover .photo-tag, .photo:focus-visible .photo-tag { opacity: 1; transform: none; }

/* -------------------------------------------------------------- Photos --
   Photographs run in their own colour. The zoom and fade transitions stay,
   so the hover and reveal motion is unchanged. */
.hero-photo img,
.about-photo img,
.photo img {
  transition: transform .6s var(--ease), opacity .5s var(--ease);
}

/* ------------------------------------------------------- Before you book -- */
.notes-section { padding-block: clamp(2.5rem, 5vw, 4rem); border-top: 1px solid var(--line); }
.notes-section h2 { font-size: clamp(1.4rem, 2.4vw, 1.75rem); letter-spacing: -0.02em; margin-bottom: 1.5rem; }
.notes {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 1.5rem 2rem; margin: 0;
}
.notes dt { font-size: .8125rem; color: var(--muted); margin-bottom: .3rem; }
.notes dd { margin: 0; font-size: 1rem; line-height: 1.55; }

/* -------------------------------------------------------------- Reviews -- */
.review-list {
  display: grid; grid-template-columns: repeat(2, 1fr);
  gap: clamp(1rem, 2vw, 1.75rem);
}
.review {
  margin: 0;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(1.5rem, 2.5vw, 2.25rem);
}
.review blockquote { margin: 0 0 1.5rem; font-size: 1.0625rem; }
.review figcaption {
  display: flex; align-items: center; gap: .85rem;
  font-size: .9375rem; color: var(--muted);
  padding-top: 1.25rem; border-top: 1px solid var(--line);
}
.review figcaption strong { display: block; color: var(--text); font-weight: 600; }


.avatar {
  flex: 0 0 auto; display: grid; place-items: center;
  width: 46px; height: 46px; border-radius: 50%;
  overflow: hidden; background: #ebe9e3;
}
.avatar img { width: 100%; height: 100%; object-fit: cover; }
.avatar-initials { font-size: .8125rem; font-weight: 600; color: var(--muted); }

/* -------------------------------------------------------------- Contact -- */
.contact { background: var(--ink); color: #fff; }
.contact-grid {
  display: grid; grid-template-columns: 1.15fr .85fr;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}
.contact-text h2 { margin-bottom: 1.2rem; }
.contact-text p { color: rgba(255, 255, 255, .76); max-width: 44ch; }

.contact-details { display: grid; gap: 1.4rem; }
.contact-details dt { font-size: .8125rem; color: rgba(255, 255, 255, .58); margin-bottom: .2rem; }
.contact-details dd { font-weight: 500; }
.contact-details a {
  position: relative;
  text-decoration: none;
  border-bottom: 1px solid rgba(255, 255, 255, .3);
  transition: border-color var(--dur-ui) var(--ease);
}
/* Inline links stay inline; the tap target grows through a pseudo-element
   so nothing in the text reflows. */
.contact-details a::before, .footer-row a::before {
  content: ""; position: absolute; inset: -.7rem 0;   /* 23px text + 22px = a 45px target */
}
.footer-row a { position: relative; }
.contact-details a:hover { border-bottom-color: #fff; }

/* Inside the popups the details sit on white, so recolour them back. */
.modal-details dt { color: var(--muted); }
.modal-details a { border-bottom-color: var(--line); }
.modal-details a:hover { border-bottom-color: var(--text); }

/* --------------------------------------------------------------- Footer -- */
.footer { background: var(--ink); color: rgba(255, 255, 255, .6); }
.footer-row {
  display: flex; flex-wrap: wrap; gap: .5rem 1.5rem; justify-content: space-between;
  padding-block: 1.75rem;
  border-top: 1px solid rgba(255, 255, 255, .13);
  font-size: .9375rem;
}
.footer-row p { margin: 0; }
.footer-row a { text-decoration: none; transition: color var(--dur-ui) var(--ease); }
.footer-row a:hover { color: #fff; }
.footer-links { display: flex; flex-wrap: wrap; gap: .5rem 1.5rem; }

/* ==========================================================================
   MOTION
   Two variants of one idea. --d staggers siblings; everything is scoped to
   .js so the page is never blank if the script fails to load.
   ========================================================================== */
.js .fade {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity var(--dur-fade) var(--ease),
              transform var(--dur-fade) var(--ease);
  transition-delay: var(--d, 0s);
}
.fade.is-in { opacity: 1; transform: none; }

/* The hero headline drops line by line instead of lifting as one block. Each
   line clips its own word, so the word arrives from behind the line above.
   The padding/margin pair keeps that clip off the letterforms — at
   line-height 1 a plain overflow:hidden shaves the bottom of the glyphs. */
.drop-line {
  display: block; overflow: hidden;
  padding-block: .16em; margin-block: -.16em;
}
.js .hero-drop .drop-word {
  display: inline-block;
  transform: translateY(125%);
  opacity: 0;
  transition: transform var(--dur-fade) var(--ease),
              opacity calc(var(--dur-fade) * .7) var(--ease);
  transition-delay: var(--d, 0s);
}
/* Carries the .js prefix so it outranks the hidden state above rather than
   relying on source order to break a specificity tie. */
.js .hero-drop.is-in .drop-word { transform: none; opacity: 1; }

/* The "o" of "lessons" is a tennis ball (ballo.js). The letter stays in
   the text, transparent, holding its exact width; the ball is drawn over
   it, sized and placed by the script from the font's own metrics. The
   ball is the page's one colour, the same one that comes off a Book
   button. Once it has dropped in, the line's clip is lifted so a hop can
   clear the line above. */
.ball-o { position: relative; display: inline-block; }
.ball-o.is-ball { color: transparent; }
.ball-o-probe { display: inline-block; width: 0; height: 0; vertical-align: baseline; }
.ball-o-ball {
  position: absolute;
  overflow: visible;
  transform-origin: 50% 100%;
  pointer-events: none;
  filter: drop-shadow(0 .02em .02em rgba(0, 0, 0, .18));
}
.hero-drop.is-settled .drop-line { overflow: visible; }
/* The translated headlines' full stop: an empty slot the width of the
   ball at the end of the last word (ballo.js puts the ball in it). */
.ball-stop { width: .56em; margin-inline-start: .05em; }

/* Photos fade in while easing back from a slight zoom. */
.fade-img { border-radius: var(--radius); overflow: hidden; }

/* ---------------------------------------------------------- The clip --
   Styled as another frame in the strip rather than as an embed: flush with
   the two photographs above, same 2px seam, same hover zoom and same tag.
   The 16/9 box holds its height from first paint, so the caption below it
   never jumps when the poster or the player arrives. */
.video-frame { margin: clamp(2.5rem, 5vw, 3.5rem) 0 0; }
.video-play {
  position: relative;
  display: block; width: 100%; padding: 0;
  border: 0; cursor: pointer;
  overflow: hidden;
  background: var(--ink);
}
.video-play img {
  display: block; width: 100%;
  aspect-ratio: 16/9; object-fit: cover;
  transition: transform .6s var(--ease), opacity .5s var(--ease);
}
.video-play:hover img { transform: scale(1.04); }

/* The play control. A white disc with a black triangle keeps the page black
   and white; YouTube's red button was the one thing on the page not in the
   scheme. clip-path rather than a border triangle so it stays crisp when
   the disc scales on hover. */
.video-cue {
  position: absolute; top: 50%; left: 50%;
  width: 4.5rem; height: 4.5rem;
  margin: -2.25rem 0 0 -2.25rem;
  border-radius: 50%;
  background: rgba(255, 255, 255, .92);
  transition: transform var(--dur-ui) var(--ease), background var(--dur-ui) var(--ease);
}
.video-cue::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 1.35rem; height: 1.5rem;
  margin: -.75rem 0 0 -.5rem;
  background: var(--ink);
  clip-path: polygon(0 0, 100% 50%, 0 100%);
}
.video-play:hover .video-cue,
.video-play:focus-visible .video-cue { transform: scale(1.08); background: #fff; }

/* Once pressed, the iframe takes the same box the poster held. */
.video-play.is-playing { cursor: default; }
.video-play iframe {
  display: block; width: 100%; aspect-ratio: 16/9; border: 0;
}

.video-frame figcaption {
  margin-top: .9rem;
  color: var(--muted);
  font-size: .9375rem;
  max-width: 65ch;
}
.js .fade-img img {
  opacity: 0;
  transform: scale(1.06);
  transition: opacity var(--dur-fade) var(--ease),
              transform var(--dur-img) var(--ease);
  transition-delay: var(--d, 0s);
}
.fade-img.is-in img { opacity: 1; transform: scale(1); }

/* Cascade grouped items. */
.lesson-list .lesson:nth-child(2)  { --d: .09s; }
.lesson-list .lesson:nth-child(3)  { --d: .18s; }
.lesson-list .lesson:nth-child(4)  { --d: .27s; }
.photo-strip .photo:nth-child(2)   { --d: .09s; }
.photo-strip .photo:nth-child(3)   { --d: .18s; }
.review-list .review:nth-child(2)  { --d: .09s; }

/* ==========================================================================
   POPUPS
   ========================================================================== */
.modal, .lightbox {
  width: 100%; height: 100%; max-width: none; max-height: none;
  padding: 0; border: none; background: transparent;
  display: grid; place-items: center;
  opacity: 0;
  transition: opacity var(--dur-ui) var(--ease),
              display var(--dur-ui) var(--ease) allow-discrete,
              overlay var(--dur-ui) var(--ease) allow-discrete;
}
.modal:not([open]), .lightbox:not([open]) { display: none; }
.modal[open], .lightbox[open] { opacity: 1; }
@starting-style { .modal[open], .lightbox[open] { opacity: 0; } }

.modal::backdrop, .lightbox::backdrop {
  background: rgba(15, 20, 16, 0);
  backdrop-filter: blur(0px);
  transition: background-color var(--dur-ui) var(--ease), backdrop-filter var(--dur-ui) var(--ease);
}
.modal[open]::backdrop, .lightbox[open]::backdrop {
  background: rgba(15, 20, 16, .5);
  backdrop-filter: blur(4px);
}
@starting-style {
  .modal[open]::backdrop, .lightbox[open]::backdrop {
    background: rgba(15, 20, 16, 0); backdrop-filter: blur(0px);
  }
}

.modal-card {
  position: relative;
  width: min(470px, calc(100vw - 2rem));
  max-height: calc(100vh - 2.5rem);
  overflow-y: auto;
  background: var(--bg);
  border-radius: 10px;
  padding: clamp(1.75rem, 4vw, 2.25rem);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .22);
  transform: translateY(10px) scale(.985);
  transition: transform var(--dur-ui) var(--ease);
}
.modal[open] .modal-card { transform: none; }
@starting-style { .modal[open] .modal-card { transform: translateY(10px) scale(.985); } }

.modal-card h3 { font-size: 1.3rem; margin-bottom: .7rem; }
.modal-card h3:focus { outline: none; }   /* programmatic focus for the reader; no ring on the title */
.modal-card > p { color: var(--muted); }
.modal-card ul { margin: 1.25rem 0 0; }
.modal-card li {
  position: relative; padding-block: .6rem; padding-inline-start: 1.2rem;
  color: var(--muted); border-bottom: 1px solid var(--line);
}
.modal-card li::before {
  content: ""; position: absolute; inset-inline-start: 0; top: 1.25em;
  width: 5px; height: 5px; border-radius: 50%; background: var(--accent);
}
.modal-card .contact-details { margin: 1.4rem 0; }

.modal-close {
  position: absolute; top: .5rem; inset-inline-end: .5rem;
  width: 2.75rem; height: 2.75rem; font-size: 1.5rem; line-height: 1;
  color: var(--muted); border-radius: var(--radius);
  transition: color var(--dur-ui) var(--ease), background-color var(--dur-ui) var(--ease);
}
.modal-close:hover { color: var(--text); background: var(--bg-soft); }

.lightbox figure { margin: 0; display: grid; justify-items: center; gap: .75rem; padding: 1.5rem; }
.lightbox img { max-width: min(92vw, 640px); max-height: 82vh; width: auto; border-radius: var(--radius); }
/* On a plate: the backdrop is 50% over whatever the page has behind it,
   which over the white photo section put white text at 3.5:1. */
.lightbox figcaption {
  font-size: .875rem; color: #fff;
  background: rgba(13, 13, 13, .88); padding: .45rem .9rem; border-radius: var(--radius);
}
.lightbox-close { position: fixed; top: 1rem; inset-inline-end: 1rem; color: #fff; }
.lightbox-close:hover { color: #fff; background: rgba(255, 255, 255, .16); }

body.modal-open { overflow: hidden; }

/* ==========================================================================
   LANGUAGES
   The translated pages (/ko/, /zh-hans/, /fa/ ... built by tools/i18n/)
   share every rule above. These only fit the type to scripts the Latin
   settings don't suit: the tight tracking that gives the English
   headlines their weight crushes Hangul, Han and kana and pulls apart
   Persian's joined letters, and a .92 line height clips their taller
   glyphs. Fonts stay the system's own, named so each language gets its
   regional forms (Chinese and Japanese share code points but not shapes).
   ========================================================================== */
:root:lang(ko) { --font: -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Segoe UI", "Malgun Gothic", "Noto Sans KR", Roboto, sans-serif; }
:root:lang(ja) { --font: -apple-system, BlinkMacSystemFont, "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Segoe UI", "Yu Gothic UI", Meiryo, "Noto Sans JP", Roboto, sans-serif; }
:root:lang(zh-Hans) { --font: -apple-system, BlinkMacSystemFont, "PingFang SC", "Segoe UI", "Microsoft YaHei", "Noto Sans SC", Roboto, sans-serif; }
:root:lang(zh-Hant) { --font: -apple-system, BlinkMacSystemFont, "PingFang TC", "Segoe UI", "Microsoft JhengHei", "Noto Sans TC", Roboto, sans-serif; }
:root:lang(fa) { --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Tahoma, "Noto Sans Arabic", Roboto, sans-serif; }

/* Korean breaks lines between words, not inside them. */
:lang(ko) body { word-break: keep-all; overflow-wrap: break-word; }

:root:is(:lang(ko), :lang(ja), :lang(zh)) :is(h2, h3, .statement blockquote) { letter-spacing: -0.01em; line-height: 1.3; }
/* Hangul, Han and kana fill the whole em, so they read a size larger than
   Latin at the same setting, and need the taller line: a step down keeps
   the headline's weight and the prices and Book button on a laptop's
   first screen. */
:root:is(:lang(ko), :lang(ja), :lang(zh)) .hero h1 { font-size: clamp(2.75rem, min(6.6vw, 8.4vh), 4.9rem); }
:root:is(:lang(ja), :lang(zh)) .hero h1 { letter-spacing: 0; line-height: 1.1; }
:root:lang(ko) .hero h1 { letter-spacing: -0.04em; line-height: 1.08; }
:root:lang(vi) .hero h1 { line-height: .98; }

/* Persian: no tracking anywhere (it would break the joins), and more
   line height for the dots and descenders. */
:root:lang(fa) :is(h1, h2, h3, .statement blockquote, .price, .hero-facts dd, .eyebrow) { letter-spacing: 0; }
:root:lang(fa) body { line-height: 1.8; }
:root:lang(fa) :is(h2, h3) { line-height: 1.45; }
:root:lang(fa) .hero h1 { line-height: 1.02; }
:root:lang(fa) .statement blockquote { line-height: 1.5; }

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */
@media (max-width: 940px) {
  .hero-grid, .two-col, .contact-grid { grid-template-columns: 1fr; }
  .hero { padding-block: 2.5rem 1rem; }
  .hero-photo { order: -1; }
  .hero-photo img, .about-photo img { aspect-ratio: 3/2; }
  /* A phone on its side is short and wide: a 3:2 photo there is taller than
     the screen, and the headline starts below the fold. Cap it. */
  @media (orientation: landscape) and (max-height: 520px) {
    .hero-photo img { aspect-ratio: auto; height: 62vh; object-position: 50% 30%; }
  }
  .about-photo { order: -1; }
  .lesson-list, .review-list { grid-template-columns: 1fr; }
  .contact-details { margin-top: 1rem; }
}

/* Short laptop windows: a 1366×768 or 1536×864 screen leaves 650–730px
   under the browser's bars. The same hero, packed tighter, so the prices
   and the Book button still make the first view. The text starts at the
   top instead of centring on the photo: centred against a portrait taller
   than the window, it began a hundred pixels down and pushed Book off the
   bottom. */
@media (min-width: 941px) and (max-height: 820px) {
  .hero { padding-block-start: 1rem; }
  .hero-grid { align-items: start; }
  .hero-text { padding-block-start: clamp(1rem, 4vh, 2.5rem); }
  .hero .eyebrow { margin-bottom: .6rem; }
  .hero h1 { font-size: clamp(2.75rem, 8.6vh, 5rem); margin-bottom: 1rem; }
  :root:is(:lang(ko), :lang(ja), :lang(zh)) .hero h1 { font-size: clamp(2.5rem, 7.6vh, 4.4rem); }
  .hero-sub { font-size: 1.0625rem; max-width: 50ch; }
  .hero-facts { margin-top: 1.25rem; padding-top: 1rem; }
  .hero-facts + .actions { margin-top: 1.1rem; }
}

@media (max-width: 940px) {
  .lesson-actions .btn { min-height: 44px; }
  .notes { grid-template-columns: 1fr 1fr; }
}

/* The header folds its links into the menu earlier than the page stacks:
   wordmark, five links, the language menu and the Book button need about
   1,050px in the longer languages (Spanish, Japanese, Persian), and below
   that the labels wrapped. The wordmark needs room to stay legible beside
   the booking action. */
@media (max-width: 1100px) {
  .nav-toggle { display: flex; }
  .header-row { gap: .75rem; }
  .header-cta { min-height: 44px; }
  .nav {
    display: none;
    position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; gap: 0;
    background: var(--bg); border-bottom: 1px solid var(--line);
    padding: 0 var(--pad) .75rem;
  }
  .nav.is-open { display: flex; animation: navIn .28s var(--ease); }
  @keyframes navIn { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
  .nav a { padding: .85rem 0; border-bottom: 1px solid var(--line); font-size: 1rem; }
  .nav a:last-child { border-bottom: none; }
  .nav-book { display: block; font-weight: 600; color: var(--text); }
  .nav-portfolio { gap: .55rem; }
  .np-arrow path { stroke-dashoffset: 0; }
  /* The desktop divider would read as a stray indent in the stacked menu. */
  .nav-portfolio { padding-inline-start: 0; border-inline-start: none; }
}

@media (max-width: 680px) {
  .photo-strip { grid-template-columns: 1fr 1fr; }
  .photo-strip .photo:first-child { grid-column: 1 / -1; }
  .photo-tag { opacity: 1; transform: none; }   /* no hover on touch */
  .actions .btn { width: 100%; }
  .hero-facts, .facts { gap: 1.5rem 2.25rem; }
  .lesson-actions .btn { width: 100%; }
  /* Price and Book in the first screen on a phone: the photo follows the
     text instead of leading it (cropped from the top so the head stays
     in), and the prices sit just above the buttons. */
  .hero-photo { order: 1; }
  .hero-photo img { object-position: 50% 18%; }
  .hero-facts { margin-top: 1.5rem; padding-top: 1.25rem; }
  .hero-facts dd { font-size: 1.35rem; }
  .matcher-row { flex-direction: column; }
  .matcher-row .btn, .matcher-actions .btn { width: 100%; min-height: 44px; }
}

@media (max-width: 480px) {
  /* The wordmark, the button and the toggle have to share ~335px. */
  .brand-logo { width: 126px; height: 42px; }
  .header-row { gap: .6rem; }
  .header-cta .cta-full { display: none; }
  .header-cta .cta-short { display: inline; }
  /* The globe alone; its name is still read out. */
  .lang-code { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; }
  .notes { grid-template-columns: 1fr; }
}

/* ==========================================================================
   REDUCED MOTION
   ========================================================================== */
/* Reduced motion means "no movement", not "no life". The accessibility
   guidance targets large, sliding, vestibular-triggering motion — a plain
   cross-fade is fine and is explicitly not the concern. So here we strip every
   translate, scale and parallax, and keep a gentle opacity fade.

   This matters more than it sounds: anyone who has Windows animations switched
   off (Settings > Accessibility > Visual effects) lands in this branch. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
  }

  /* Movement off. */
  .js .fade { transform: none; transition: opacity .55s var(--ease); }
  .np-text { transition: none; }
  .nav-portfolio:hover .np-text, .nav-portfolio:focus-visible .np-text { transform: none; color: var(--text); }
  .np-arrow path { transition: none; }
  .js .hero-drop .drop-word { transform: none; transition: opacity .55s var(--ease); }
  .hero-drop.is-in .drop-word { transform: none; }
  .drop-line { overflow: visible; }
  .js .fade-img img { transform: none; transition: opacity .55s var(--ease); }
  .fade.is-in { transform: none; }
  .fade-img.is-in img { transform: none; }

  /* Hover lifts and zooms off too — colour changes are harmless and stay. */
  .btn:hover, .lesson:hover { transform: none; }
  .photo:hover img { transform: none; }
  .modal-card, .modal[open] .modal-card { transform: none; }
}

@media print {
  .header, .nav-toggle, .actions { display: none; }
  .fade, .fade-img img { opacity: 1 !important; transform: none !important; }
  .hero-drop .drop-word { opacity: 1 !important; transform: none !important; }
  .contact, .footer { background: #fff; color: #000; }
}
