/*
 * Q-kid International Nursery — a lullaby.
 *
 * The school takes children from eighteen months, so the page is set at the
 * hour those children are being carried to bed: deep blue, a moon, a few
 * stars, and ivory light instead of white glare. The mark is theirs, drawn by
 * hand in ink, and on this ground it becomes moonlight.
 *
 * Laid out the way the Promkun site is — a fixed bar, a hero, bands of colour
 * that change as you scroll, a footer carrying the mark — so the two schools
 * read as one family without wearing the same clothes. The bands walk through
 * a night: deep dark at the top, dusk in the middle, one lit band at dawn
 * where the practical details live, then back to dark to say goodnight.
 */

:root {
  --night: #0C1630;        /* the ground: deep blue, well past dusk */
  --night-2: #16264F;      /* the band that follows it */
  --dusk: #1A2A55;         /* raised surfaces */
  --dawn: #F6F2E9;         /* the one lit band, where the details live */

  --moon: #F5F0E4;         /* warm ivory: all light text */
  --moonlight: #BCCAEB;    /* secondary text on dark */
  --ink: #14224A;          /* text on the lit band */
  --ink-soft: #46527A;

  --star: #F2D08A;         /* the one warm accent */
  --star-dim: rgba(242, 208, 138, 0.34);

  --display: 'Mali', 'IBM Plex Sans Thai', cursive;
  --body: 'IBM Plex Sans Thai', system-ui, sans-serif;

  /* Thai stacks a tone mark on top of an above-vowel — เมื่อ is เ + ม + ◌ื + ◌่ —
     and Mali, being handwriting, draws those marks tall and loopy. Measured in
     the browser across every base × above-vowel × tone combination: Mali 500's
     ink runs 1.415em above the baseline (ฮื้) and 0.412em below it (งู), so two
     lines of Thai collide at any leading under 1.83 — 1.92 if you allow mai tri
     and mai chattawa, which this copy happens not to use. The hero heading was
     overlapping itself by 21px before this. Do not tighten it without measuring
     again; nothing carrying Thai should go under 1.55 even in the body face,
     where IBM Plex needs 1.50. */
  --display-leading: 1.94;

  --wrap: 1080px;
  --band: clamp(64px, 8vw, 96px);
  --bar: 74px;             /* what the sticky bar covers; anchors clear it */
}

/* The colour does not move. Promkun is the daylight school and this is the
   night one — the two sites are one idea in two halves, and a switch here gave
   that away exactly as a switch there did. A day palette was built and taken
   back out on purpose; the footer link is where the two meet. */

* { margin: 0; padding: 0; box-sizing: border-box; }

html { scroll-behavior: smooth; }
/* every heading these links point at used to land underneath the sticky bar */
section[id], header[id] { scroll-margin-top: var(--bar); }

body {
  background: var(--night);
  color: var(--moon);
  transition: background-color .45s ease, color .45s ease;
  font-family: var(--body);
  font-size: 17px;
  line-height: 1.85;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 28px; }
.wrap.narrow { max-width: 680px; }
/* A reading measure that keeps the left edge where the rest of the page put it.
   .narrow re-centres, which is right for the closing section and wrong for a
   paragraph that follows a left-aligned heading — the text visibly stepped
   inward halfway down the page. */
.measure { max-width: 62ch; }

/* English is the second voice, never a second page: half the families here
   read one and half the other. */
.en { display: block; font-size: .86em; color: var(--moonlight); line-height: 1.62; margin-top: 8px; }
.band-dawn .en { color: var(--ink-soft); }

.skip-link {
  position: absolute; left: -9999px;
  background: var(--star); color: var(--night);
  padding: 10px 18px; border-radius: 999px; z-index: 200;
}
.skip-link:focus { left: 20px; top: 14px; }

:focus-visible { outline: 2px solid var(--star); outline-offset: 3px; border-radius: 4px; }

/* ---- the bar ----------------------------------------------------------- */

.topbar {
  position: sticky; top: 0; z-index: 100;
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px;
  padding: 13px 28px;
  background: rgba(12, 22, 48, 0.86);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid rgba(188, 202, 235, 0.15);
}
.brand { display: flex; align-items: center; gap: 12px; text-decoration: none; color: inherit; }
.brand img { width: 42px; height: auto; }
.brand-text { font-family: var(--display); font-weight: 600; font-size: 21px; line-height: 1.15; }
/* The second line is Thai at 11.5px and it sits directly under the wordmark,
   so it gets ordinary Thai leading rather than the 1.1 a Latin lockup would
   take — otherwise the marks in เนชั่น and เซอรี่ climb into the Q-kid above. */
.brand-text small {
  display: block; font-family: var(--body); font-weight: 400;
  font-size: 11.5px; line-height: 1.55; letter-spacing: .01em;
  color: var(--moonlight); margin-top: 1px;
}
.topbar-links { display: flex; align-items: center; gap: 22px; }
.topbar-links a { text-decoration: none; }
.topbar-links a:not(.btn) { color: var(--moonlight); font-size: 15.5px; transition: color .18s ease; }
.topbar-links a:not(.btn):hover { color: var(--moon); }

/* ---- the bar's right-hand cluster ---------------------------------------- */

.topbar-right { display: flex; align-items: center; gap: 14px; }

/* Three lines that become a cross. Only ever shown on small screens, where the
   links have nowhere to sit. */
.menu-btn {
  display: none; width: 38px; height: 38px; flex: none; padding: 0;
  background: none; border: 1px solid rgba(188, 202, 235, .3); border-radius: 50%;
  cursor: pointer; position: relative;
}
.menu-btn i, .menu-btn::before, .menu-btn::after {
  content: ''; position: absolute; left: 11px; right: 11px; height: 1.5px; border-radius: 2px;
  background: var(--moon);
  transition: transform .28s cubic-bezier(.22,.61,.36,1), opacity .18s ease;
}
.menu-btn::before { top: 13px; }
.menu-btn i { top: 18.2px; }
.menu-btn::after { bottom: 13px; }
.menu-btn[aria-expanded="true"]::before { transform: translateY(5.2px) rotate(45deg); }
.menu-btn[aria-expanded="true"] i { opacity: 0; }
.menu-btn[aria-expanded="true"]::after { transform: translateY(-5.2px) rotate(-45deg); }

/* 1080, and the number is measured rather than chosen. The bar holds the
   brand, five links, the language switch and the visit button, and what that
   needs depends on which language is in it — summing the parts at a width
   where nothing wraps gives 1096px on /en, the widest of the three, because
   the English labels are the longest. Below that the row wraps to a second
   line and the bar grows from 71px to 95px, which puts every anchor 21px
   out, since --bar is 74. The burger handles this range perfectly well, so
   it takes over sooner rather than the row quietly breaking.
 *
   This number has moved three times and every move was the content getting
   wider: 860 with four links and no switch, 940 with five, 1080 once the
   switch arrived, and 1100 after the switch was made large enough to
   actually see. Re-measure rather than guess whenever either changes. */
@media (max-width: 1100px) {
  .topbar { padding: 11px 18px; }
  .brand-text small { display: none; }
  .menu-btn { display: block; }
  .topbar-right > .btn { display: none; }
  .topbar-links {
    position: absolute; left: 0; right: 0; top: 100%;
    flex-direction: column; align-items: stretch; gap: 0;
    padding: 6px 18px 18px;
    /* opaque, and from the palette so it follows the theme: `inherit` took the
       background of .topbar-right, which has none, and the menu opened
       see-through onto the moon */
    background: var(--night);
    border-bottom: 1px solid rgba(188, 202, 235, .16);
    /* closed by default and inert, so a tab never lands in a hidden menu */
    display: none;
  }
  .topbar-links.open { display: flex; }
  .topbar-links a:not(.btn) { padding: 13px 2px; border-bottom: 1px solid rgba(188, 202, 235, .12); font-size: 16px; }
  .topbar-links .btn { margin-top: 14px; }
}
@media (min-width: 1101px) { .topbar-links .btn.menu-cta { display: none; } }

/* ---- buttons ----------------------------------------------------------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 13px 26px; border-radius: 999px;
  font-size: 16px; font-weight: 500; line-height: 1.6; text-decoration: none;
  border: 1px solid transparent;
  transition: background-color .18s ease, color .18s ease, border-color .18s ease, transform .18s ease;
}
.btn-star { background: var(--star); color: #2A2008; border-color: var(--star); }
.btn-star:hover { background: #F7DFA9; transform: translateY(-1px); }
.btn-ghost { color: var(--moon); border-color: rgba(188, 202, 235, 0.42); }
.btn-ghost:hover { border-color: var(--moon); background: rgba(245, 240, 228, 0.07); }
.topbar .btn { padding: 9px 20px; font-size: 15px; }

/* ---- the moon ---------------------------------------------------------- */

/* One light source for the whole page. It rises above the mark in the hero and
   returns, smaller and dimmer, where the reader is asked to visit. It is a lit
   body, not a flat disc, so the highlight sits off-centre and the glow is cast
   by shadows rather than painted as a ring. */
.moon {
  position: absolute; border-radius: 50%;
  background: radial-gradient(circle at 36% 31%, #FFFCF2 0%, #F7EBCB 42%, #ECDAAE 68%, #DCC894 100%);
  box-shadow:
    0 0 50px 12px rgba(247, 235, 203, .26),
    0 0 150px 56px rgba(188, 202, 235, .13);
  pointer-events: none;
}

/* ---- hero -------------------------------------------------------------- */

.hero {
  position: relative; overflow: hidden;
  padding: clamp(52px, 6.4vw, 80px) 0 clamp(52px, 6.4vw, 80px);
  background:
    radial-gradient(130% 92% at 78% -8%, rgba(26, 42, 85, .9) 0%, rgba(12, 22, 48, 0) 62%),
    var(--night);
}
/* a scatter of stars, fixed rather than twinkling: a nursery at night, not a
   screensaver */
.hero::before {
  content: ''; position: absolute; inset: -90px 0; pointer-events: none;
  transform: translate3d(0, var(--star-shift, 0px), 0);
  background-image:
    radial-gradient(1.7px 1.7px at 12% 22%, var(--star-dim) 50%, transparent 51%),
    radial-gradient(1.4px 1.4px at 26% 66%, var(--star-dim) 50%, transparent 51%),
    radial-gradient(2px 2px at 44% 15%, rgba(242, 208, 138, .5) 50%, transparent 51%),
    radial-gradient(1.3px 1.3px at 62% 80%, var(--star-dim) 50%, transparent 51%),
    radial-gradient(1.5px 1.5px at 72% 38%, var(--star-dim) 50%, transparent 51%),
    radial-gradient(1.3px 1.3px at 88% 72%, var(--star-dim) 50%, transparent 51%),
    radial-gradient(1.2px 1.2px at 34% 90%, var(--star-dim) 50%, transparent 51%),
    radial-gradient(1.6px 1.6px at 55% 48%, var(--star-dim) 50%, transparent 51%);
}
/* the ground the night sits on: a wide, very soft lift along the bottom edge */
.hero::after {
  content: ''; position: absolute; left: -12%; right: -12%; bottom: -180px; height: 300px;
  background: radial-gradient(58% 100% at 50% 0%, rgba(188, 202, 235, .15), transparent 72%);
  pointer-events: none;
}
.hero-grid {
  position: relative; z-index: 2;
  display: grid; grid-template-columns: minmax(0, 1.04fr) minmax(0, .96fr);
  gap: 48px; align-items: center;
}

.eyebrow { font-size: 14.5px; color: var(--moonlight); margin-bottom: 18px; display: flex; align-items: flex-start; gap: 10px; }
/* flex-start, not center: when the line wraps on a phone the rule belongs
   beside the first line, not floating between the two */
.eyebrow::before { content: ''; width: 22px; height: 1px; margin-top: .85em; background: var(--star); flex: none; }

h1 {
  font-family: var(--display); font-weight: 500;
  font-size: clamp(29px, 4.4vw, 45px); line-height: var(--display-leading);
  letter-spacing: -.005em; text-wrap: balance;
}
.hero-sub { margin-top: 20px; max-width: 42ch; color: var(--moon); }
.hero-cta { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 30px; }
/* Three things a parent wants before anything else — age, hours, where you are
   — said once in the hero and again, in full, in the lit band below. */
.hero-facts {
  list-style: none; display: grid; grid-template-columns: repeat(3, auto);
  justify-content: start; gap: 0 26px; margin-top: 34px;
  font-size: 14.5px; line-height: 1.62; color: var(--moon);
}
.hero-facts li { padding-left: 15px; border-left: 1px solid rgba(188, 202, 235, .28); }
.hero-facts li:first-child { padding-left: 0; border-left: 0; }
.hero-facts .en { font-size: 12.5px; margin-top: 3px; }
@media (max-width: 620px) {
  .hero-facts { grid-template-columns: 1fr; gap: 14px; }
  .hero-facts li { padding-left: 15px; border-left: 1px solid rgba(188, 202, 235, .28); }
}

/* The moon rises above the mark, never across it: both are ivory, and where
   they overlapped the Q simply dissolved into the light. They are two rows of
   the same grid rather than a disc positioned by hand, so no viewport width
   can push one onto the other. */
.hero-mark { display: grid; justify-items: center; gap: clamp(20px, 3.4vw, 40px); }
.hero-mark .moon {
  position: relative; justify-self: end;
  width: clamp(96px, 12vw, 150px); aspect-ratio: 1; margin-right: 9%;
}
.hero-mark img {
  width: min(248px, 54vw); height: auto;
  filter: drop-shadow(0 0 30px rgba(188, 202, 235, .26));
}

@media (max-width: 880px) {
  .hero-grid { grid-template-columns: 1fr; gap: 34px; }
  .hero-mark { order: -1; gap: 18px; }
  .hero-mark img { width: 172px; }
  .hero-mark .moon { margin-right: 14%; }
  .hero-sub { max-width: none; }
}

/* ---- bands ------------------------------------------------------------- */

.band { padding: var(--band) 0; position: relative; overflow: hidden; }
.band-night { background: var(--night); }
.band-dusk { background: var(--night-2); }
/* the one lit band: first light along its top edge, so the page reads as a
   night that breaks rather than a dark page with a white hole in it */
.band-dawn {
  background: var(--dawn); color: var(--ink);
  box-shadow: inset 0 46px 52px -46px rgba(242, 208, 138, .75);
}

.kicker { font-size: 14px; color: var(--star); margin-bottom: 10px; letter-spacing: .01em; }
/* on the lit band this gold sits on ivory, where #9A7418 measured only 3.85:1 */
.band-dawn .kicker { color: #8A5F06; }

h2 {
  font-family: var(--display); font-weight: 500;
  font-size: clamp(22px, 2.9vw, 31px); line-height: var(--display-leading);
  margin-bottom: 22px; text-wrap: balance;
}

.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 22px 44px; max-width: 900px; }
@media (max-width: 780px) { .two-col { grid-template-columns: 1fr; } }

em { font-style: normal; font-family: var(--display); font-weight: 500; color: var(--star); }
.band-dawn em { color: #8A5F06; }

/* ---- the four pillars -------------------------------------------------- */

.pillars {
  list-style: none; display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 14px; margin-top: 38px;
}
.pillars li {
  position: relative; overflow: hidden;
  background: var(--dusk); border: 1px solid rgba(188, 202, 235, .14);
  border-radius: 16px; padding: 22px 20px 20px;
  font-size: 17px;
}
/* a little sky inside each tile, brightest at the star */
.pillars li::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(70% 60% at 22% 0%, rgba(242, 208, 138, .13), transparent 70%);
}
.pillars .en { font-size: 12.5px; margin-top: 2px; }
.star {
  position: relative; display: block; width: 14px; height: 14px; margin-bottom: 12px;
  background: var(--star);
  clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
}
@media (max-width: 780px) { .pillars { grid-template-columns: repeat(2, 1fr); } }

/* ---- the name ---------------------------------------------------------- */

/* One quiet beat: the school's own explanation of what it is called, set as a
   line of handwriting rather than a paragraph. */
.name-line {
  font-family: var(--display); font-weight: 500;
  font-size: clamp(20px, 2.6vw, 27px); line-height: var(--display-leading);
  max-width: 22ch;
}
.name-line span { color: var(--star); }

/* ---- the lit band ------------------------------------------------------ */

.facts { margin-top: 6px; max-width: 760px; }
.facts > div {
  display: grid; grid-template-columns: 11rem 1fr; gap: 4px 24px;
  padding: 17px 0; border-bottom: 1px solid rgba(20, 34, 74, .13);
}
.facts > div:last-child { border-bottom: 0; }
.facts dt { color: var(--ink-soft); font-size: 15.5px; }
.facts dd { color: var(--ink); }
@media (max-width: 560px) { .facts > div { grid-template-columns: 1fr; gap: 2px; } }

/* ---- closing ----------------------------------------------------------- */

.cta { text-align: center; }
.cta .kicker { text-align: center; }
.cta .hero-cta { justify-content: center; }
.cta p { max-width: 52ch; margin-left: auto; margin-right: auto; }
.cta .moon { width: clamp(64px, 8vw, 96px); aspect-ratio: 1; right: 7%; top: 44px; }
.map { margin-top: 22px; font-size: 15px; }
.map a { color: var(--star); }

/* ---- footer ------------------------------------------------------------ */

footer {
  background: #08102A; padding: 44px 0 52px; text-align: center;
  border-top: 1px solid rgba(188, 202, 235, .12);
}
.f-mark { width: 60px; height: auto; opacity: .88; margin-bottom: 14px; }
.f-name { font-size: 15.5px; }
.f-quiet { font-size: 14px; color: var(--moonlight); margin-top: 5px; }
/* Links inside running text take the colour of the text around them. The two
   in the facts list were still browser-default blue — which passed on the ivory
   band by luck and read at 1.57:1 once that band inverted for daylight. */
.facts a, .f-quiet a {
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
  transition: text-decoration-color .2s ease;
}
.facts a:hover, .f-quiet a:hover { text-decoration-color: currentColor; }

/* A phone number that wraps mid-number reads as two numbers. */
a[href^="tel:"], a[href^="mailto:"] { white-space: nowrap; }

/* ---- the sky is not a photograph --------------------------------------- */

/* Four stars that breathe, on top of the fixed field. Four, not forty: a
   nursery at night, still not a screensaver. The periods are prime-ish and
   unequal so they never fall into step with each other. */
.twinkle {
  position: absolute; border-radius: 50%; background: #FFF6DF;
  width: 2.5px; height: 2.5px; pointer-events: none; opacity: .45;
  box-shadow: 0 0 5px 1px rgba(255, 246, 223, .6);
  animation: breathe 5.5s ease-in-out infinite;
}
.twinkle:nth-of-type(1) { left: 17%; top: 26%; animation-duration: 5.5s; }
.twinkle:nth-of-type(2) { left: 39%; top: 13%; animation-duration: 7.3s; animation-delay: -2.1s; width: 3px; height: 3px; }
.twinkle:nth-of-type(3) { left: 58%; top: 70%; animation-duration: 6.1s; animation-delay: -4.4s; }
.twinkle:nth-of-type(4) { left: 84%; top: 58%; animation-duration: 8.2s; animation-delay: -1.3s; }
@keyframes breathe {
  0%, 100% { opacity: .22; transform: scale(.85); }
  50%      { opacity: .95; transform: scale(1.15); }
}

/* A shooting star, rarely. Its whole job is that someone who stays on the page
   for half a minute sees one and is pleased. Positioned and fired from script
   so it never runs while the hero is off screen. */
.shoot {
  position: absolute; left: 0; top: 0; width: 3px; height: 3px; border-radius: 50%;
  background: #FFF8E6; opacity: 0; pointer-events: none;
  box-shadow: 0 0 8px 2px rgba(255, 248, 230, .8);
}
/* The trail runs along +x, bright at the head and fading out behind it. The
   star flies down and to the left, so site.js rotates the whole element by
   the negative of the flight angle, which swings this back up and to the
   right. It used to extend along -x under a +20deg tilt, which laid the
   trail out ahead of the star — it read as flying backwards. */
.shoot::after {
  content: ''; position: absolute; left: 2px; top: 50%; width: 104px; height: 1.5px;
  transform: translateY(-50%);
  background: linear-gradient(to right, rgba(255, 248, 230, .9), rgba(255, 248, 230, 0));
  border-radius: 2px;
}

/* The moon and the field drift at different rates as the page moves, which is
   the whole of the depth. Transform only — no layout, no paint. */
.hero-mark .moon { transform: translate3d(var(--moon-x, 0px), var(--moon-y, 0px), 0); will-change: transform; }
.hero-mark img { transform: translate3d(0, var(--mark-y, 0px), 0); }

/* ---- how far through you are -------------------------------------------- */

.read-bar {
  position: fixed; left: 0; top: 0; z-index: 120;
  width: 100%; height: 2px; transform: scaleX(0); transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--star), #FFF3D2);
  pointer-events: none;
}

/* ---- things that answer when you touch them ----------------------------- */

.pillars li {
  transition: transform .26s cubic-bezier(.22,.61,.36,1), border-color .26s ease, background-color .26s ease;
}
.pillars li:hover { transform: translateY(-4px); border-color: var(--star-dim); }
.pillars li .star { transition: transform .26s cubic-bezier(.34,1.56,.64,1), filter .26s ease; }
.pillars li:hover .star { transform: scale(1.35) rotate(18deg); filter: drop-shadow(0 0 7px var(--star)); }

.facts > div { transition: background-color .2s ease; border-radius: 8px; }
.facts > div:hover { background: rgba(20, 34, 74, .045); }

.topbar-links a:not(.btn) { position: relative; }
.topbar-links a:not(.btn)::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -5px; height: 1px;
  background: var(--star); transform: scaleX(0); transform-origin: 100% 50%;
  transition: transform .28s cubic-bezier(.22,.61,.36,1);
}
.topbar-links a:not(.btn):hover::after { transform: scaleX(1); transform-origin: 0 50%; }

.map a { position: relative; }
.map a:hover { text-decoration: underline; text-underline-offset: 4px; }

/* ---- arriving ---------------------------------------------------------- */

/* Sections settle in as you reach them. The class is added by script, so with
   no script — or with motion turned down — everything is simply there. */
.js-reveal .reveal { opacity: 0; transform: translateY(16px); }
.js-reveal .reveal.seen {
  opacity: 1; transform: none;
  transition: opacity .7s ease, transform .7s cubic-bezier(.22, .61, .36, 1);
  transition-delay: var(--stagger, 0ms);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { transition: none !important; animation: none !important; }
  .js-reveal .reveal { opacity: 1; transform: none; }
  .twinkle { opacity: .45; }
  .shoot { display: none; }
  .read-bar { display: none; }
}

/* ---- where the night hands back to the day ----
   The same strip Promkun's footer carries, pointing the other way and lit by
   the other body: a sun here, a moon there. Q-kid takes them from eighteen
   months and Promkun from three, so the link is a real answer to "what happens
   next" as much as it is the seam between two sites. */
.f-sister {
  display: inline-flex; align-items: center; gap: 14px;
  margin: 26px auto 0; padding: 12px 24px 12px 18px;
  border: 1px solid rgba(188, 202, 235, .18); border-radius: 999px;
  text-decoration: none; color: inherit; text-align: left;
  transition: transform .28s cubic-bezier(.22,.61,.36,1), border-color .28s ease, background-color .28s ease;
}
.f-sister:hover { transform: translateY(-2px); border-color: rgba(242, 208, 138, .5); background: rgba(245, 240, 228, .04); }
.f-sister-orb {
  width: 26px; height: 26px; border-radius: 50%; flex: none;
  background: radial-gradient(circle at 38% 34%, #FFF6DC 0%, #FBDB93 44%, #F3C766 70%, #E9B647 100%);
  box-shadow: 0 0 16px 3px rgba(243, 199, 102, .34);
  transition: box-shadow .28s ease;
}
.f-sister:hover .f-sister-orb { box-shadow: 0 0 22px 6px rgba(243, 199, 102, .48); }
.f-sister-text { display: block; line-height: 1.5; }
.f-sister-label { display: block; font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--star); }
.f-sister-name { display: block; font-size: 15px; }
.f-sister-note { display: block; font-size: 12.5px; color: var(--moonlight); }
.f-sister-note .en { font-size: 11.5px; margin-top: 1px; }
@media (max-width: 480px) { .f-sister { padding: 12px 18px 12px 14px; gap: 11px; } .f-sister-name { font-size: 14px; } }

/* ================= and here the sun comes up =================
   The mirror of Promkun's footer. There the day ends in a sunset that leaves
   you on this site's night blue; here the night ends in a dawn that leaves you
   on that site's cream, which is what you land on when you follow the link
   back. The two footers are one continuous hour: dusk at the bottom of the
   day, first light at the bottom of the night. */
.f-dawn {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  opacity: var(--dawn, 0);
  /* The light is spent by a fifth of the way up, because that is where the
     text starts: composited at full dawn, the link's note measured 3.99:1
     against this gradient before the stops were pulled down. First light sits
     below the words, not on them. */
  background: linear-gradient(0deg,
    rgba(255, 233, 196, .58) 0%,
    rgba(246, 176, 120, .30) 10%,
    rgba(198, 118, 132, .15) 24%,
    rgba(120, 104, 168, .08) 45%,
    transparent 70%);
}
footer { position: relative; }
footer > *:not(.f-dawn) { position: relative; z-index: 1; }
/* the sun in the link warms as the light arrives */
.f-sister-orb { box-shadow: 0 0 calc(10px + var(--dawn, 0) * 16px) calc(2px + var(--dawn, 0) * 5px) rgba(243, 199, 102, calc(.2 + var(--dawn, 0) * .32)); }

@media (prefers-reduced-motion: reduce) {
  /* the morning still comes, it just does not follow the scroll */
  .f-dawn { opacity: .78; }
}

/* ---- the crossing, as a thing you can press ----
   The mirror of Promkun's: a filled button in that site's own cream, carrying
   its sun, sitting in the dawn it starts. Pressing it brings the morning up
   over the whole screen and lands you there. */
.f-sister {
  position: relative;
  background: linear-gradient(135deg, #FFFCF7 0%, #F3ECDC 100%);
  border-color: rgba(242, 208, 138, .5);
  padding: 14px 52px 14px 20px;
  box-shadow: 0 10px 26px rgba(4, 8, 20, .45);
}
.f-sister:hover {
  background: linear-gradient(135deg, #FFFFFF 0%, #F7F1E3 100%);
  border-color: var(--star);
  box-shadow: 0 14px 32px rgba(4, 8, 20, .55);
}
.f-sister:focus-visible { outline: 3px solid var(--star); outline-offset: 3px; }
.f-sister::after {
  content: '→'; position: absolute; right: 20px; top: 50%;
  transform: translateY(-50%); font-size: 17px; color: #8A5F06;
  transition: transform .28s cubic-bezier(.22,.61,.36,1);
}
.f-sister:hover::after { transform: translateY(-50%) translateX(4px); }
/* the label and note were written for a dark ground; on the button they sit on cream */
.f-sister-label { color: #8A5F06; }
.f-sister-name { color: #13203C; }
.f-sister-note { color: #4E5878; }
/* #6B7490 measured 4.23:1 on the cream fill — under AA for 11.5px */
.f-sister-note .en { color: #5C6480; }

/* ---- and the crossing itself ----
   Ends on Promkun's cream, so the new page arrives on the colour the overlay
   is already showing. Under prefers-reduced-motion the link is just a link. */
.crossing {
  position: fixed; inset: 0; z-index: 9999; pointer-events: none; opacity: 0;
  background: linear-gradient(0deg,
    rgba(255, 244, 224, .98) 0%,
    rgba(250, 200, 140, .97) 28%,
    rgba(206, 126, 134, .96) 54%,
    #FFFCF7 100%);
}
.crossing-orb {
  position: absolute; left: 50%; width: 92px; height: 92px; border-radius: 50%;
  margin-left: -46px; top: 42%;
  background: radial-gradient(circle at 38% 34%, #FFF6DC 0%, #FBDB93 44%, #F3C766 70%, #E9B647 100%);
  box-shadow: 0 0 46px 14px rgba(243, 199, 102, .55);
}

/* ---- arriving from Promkun ----
   The other half of the crossing. Promkun's button ends its sunset on this
   site's night blue and then navigates; without this the new page simply
   appears, and the two halves are a fade-out and a page load rather than one
   move. So this page opens already inside that sunset, with the moon exactly
   where Promkun left it, and the sky clears to reveal the night underneath.

   Painted from <head>, before the first paint, or there is a flash of the
   page before the veil lands. Everything here is CSS: the head snippet sets
   one attribute and the animation does the rest, so a script that never runs
   cannot leave a reader staring at a veil. pointer-events: none for the same
   reason — even stuck, it cannot block anything. */
html[data-arriving]::before {
  content: ''; position: fixed; inset: 0; z-index: 9998; pointer-events: none;
  background: linear-gradient(180deg,
    rgba(255, 146, 74, .96) 0%,
    rgba(206, 86, 118, .97) 26%,
    rgba(86, 66, 138, .98) 52%,
    #0C1630 100%);
  animation: arrive 940ms 140ms cubic-bezier(.4,0,.2,1) forwards;
}
html[data-arriving]::after {
  content: ''; position: fixed; left: 50%; top: 58%; z-index: 9999; pointer-events: none;
  width: 92px; height: 92px; margin-left: -46px; border-radius: 50%;
  background: radial-gradient(circle at 36% 31%, #FFFCF2 0%, #F7EBCB 44%, #E9D5A6 70%, #DCC894 100%);
  box-shadow: 0 0 46px 14px rgba(247, 235, 203, .5);
  animation: arriveOrb 940ms 140ms cubic-bezier(.22,.61,.36,1) forwards;
}
@keyframes arrive { from { opacity: 1; } to { opacity: 0; visibility: hidden; } }
@keyframes arriveOrb {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(-46px) scale(1.1); visibility: hidden; }
}
@media (prefers-reduced-motion: reduce) {
  html[data-arriving]::before, html[data-arriving]::after { display: none; }
}

/* ---- what it costs, and what a week looks like ----
   Both come from the school's own trilingual information sheet. Fees are the
   first thing a parent looks for and the site went a long time without them
   because there was nothing to put there that was not invented. */
.fees { list-style: none; display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin: 0 0 34px; }
.fees li {
  position: relative; overflow: hidden;
  background: var(--dusk); border: 1px solid rgba(188, 202, 235, .14);
  border-radius: 16px; padding: 24px 22px;
  transition: transform .26s cubic-bezier(.22,.61,.36,1), border-color .26s ease;
}
.fees li:hover { transform: translateY(-4px); border-color: var(--star-dim); }
.fees li::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(70% 60% at 22% 0%, rgba(242, 208, 138, .12), transparent 70%);
}
.fee-name { display: block; font-family: var(--display); font-weight: 500; font-size: 18px; line-height: var(--display-leading); }
.fee-days { display: block; font-size: 13px; color: var(--moonlight); margin-top: 2px; }
.fee-price {
  display: block; margin-top: 14px; font-family: var(--display); font-weight: 500;
  font-size: 30px; line-height: var(--display-leading); color: var(--star);
}
.fee-price small { font-family: var(--body); font-size: 12.5px; font-weight: 400; color: var(--moonlight); margin-left: 7px; }
.fees .en { font-size: 12.5px; margin-top: 6px; }

.fee-included { border-top: 1px solid rgba(188, 202, 235, .16); padding-top: 26px; max-width: 62ch; }
.fee-included-head { font-size: 14px; color: var(--star); margin-bottom: 10px; }
.fee-included ul { list-style: none; }
.fee-included li { position: relative; padding-left: 18px; font-size: 15.5px; color: var(--moon); margin-bottom: 7px; }
.fee-included li::before { content: '·'; position: absolute; left: 4px; color: var(--star); }
.fee-uniform { margin-top: 16px; font-size: 14.5px; color: var(--moonlight); }

.week { list-style: none; counter-reset: none; display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; }
.week li {
  background: var(--dusk); border: 1px solid rgba(188, 202, 235, .14);
  border-radius: 14px; padding: 18px 16px; font-size: 14.5px;
  transition: transform .26s cubic-bezier(.22,.61,.36,1), border-color .26s ease;
}
.week li:hover { transform: translateY(-4px); border-color: var(--star-dim); }
.week li b { display: block; font-family: var(--display); font-weight: 500; font-size: 16px; color: var(--star); line-height: var(--display-leading); margin-bottom: 4px; }
.week .en { font-size: 12px; margin-top: 4px; }

@media (max-width: 860px) { .fees { grid-template-columns: 1fr; } .week { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 460px) { .week { grid-template-columns: 1fr; } }

/* ---- Photographs ---------------------------------------------------- */
/* Daylight photographs on a night ground. Left alone they glare: the page
   sits at #0C1630 and a bright classroom shot at full strength reads as a
   hole punched through it, which also drags the eye off the text. A small
   brightness and saturation trim settles them into the palette without going
   murky. The lit band needs none of it, so .band-dawn opts out. */
.shot { margin: 0; }

/* The frame carries the shape, not the image, so the photograph can move
   inside it on hover without escaping the rounded corners. Every photograph
   on the page is already wrapped in <picture> for the AVIF source, so there
   is no extra element to add. */
.shot picture {
  display: block;
  overflow: hidden;
  border-radius: 18px;
  background: var(--dusk);            /* holds the box while the file loads */
  border: 1px solid rgba(188, 202, 235, .16);
  box-shadow: 0 18px 40px -24px rgba(0, 0, 0, .85);
  transition: border-color .4s ease, box-shadow .4s ease;
}
.shot img {
  display: block; width: 100%; height: auto; object-fit: cover;
  filter: brightness(.93) saturate(.94);
  transition: transform .5s cubic-bezier(.22, .61, .36, 1);
}
.band-dawn .shot img { filter: none; }

/* One shape per slot. These photographs were found rather than commissioned
   and arrive in 4:3, 3:2 and 1.72 — left at their own aspects a run of them
   reads as a pile instead of a set. The grid ones are all 4:3 already so that
   costs no cropping at all; the wide ones are cut to a band, and the one
   where the centre is the wrong place to cut gets an object-position. */
.shots .shot img { aspect-ratio: 4 / 3; }
.shot-wide img { aspect-ratio: 16 / 9; }
@media (max-width: 700px) { .shot-wide img { aspect-ratio: 4 / 3; } }

/* Measured rather than guessed. Mean luminance across the nine, sampled at
   160px wide, is 156; these four sat at 117, 129, 188 and 174 — the staff
   photograph is in shade, the whole-school one is backlit, and the
   covered-yard one had 21.8% of its pixels within a whisker of blown out.
   Corrected at render time instead of re-encoding, so the originals on disk
   stay honest. Brightness alone pushed the staff photograph to 5.4% clipped
   highlights, which is why it is paired with a contrast drop; at 1.20/0.90 it
   lifts to 128 with nothing clipped. Scoped by the src attribute, which is
   the JPEG fallback and therefore matches whichever srcset candidate the
   browser actually chose. */
.shot img[src*="/everyone-"]    { filter: brightness(.93) saturate(.94) brightness(1.14); }
.shot img[src*="/outdoor-"]     { filter: brightness(.93) saturate(.94) brightness(.92) contrast(1.03); }
.shot img[src*="/circle-wide-"] { filter: brightness(.93) saturate(.94) brightness(.95); }

.shot figcaption {
  margin-top: 12px;
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--moonlight);
}
.band-dawn .shot figcaption { color: var(--ink-soft); }
.shot figcaption .en { display: block; }

.shot-wide { margin-top: 42px; }
.shot-wide figcaption { max-width: 62ch; }

.shots { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-top: 42px; }

/* Three photographs in two columns leaves an orphan on the second row, so
   the last one runs the full width rather than sitting half-empty. It is the
   reason dessert and outdoor carry a 1600px tier the other four do not. */
@media (max-width: 860px) {
  .shots { grid-template-columns: 1fr 1fr; }
  .shots .shot:last-child { grid-column: 1 / -1; }
}
@media (max-width: 560px) {
  .shots { grid-template-columns: 1fr; }
  .shots .shot:last-child { grid-column: auto; }
}

/* ---- the gallery ---------------------------------------------------- */
/* Twelve photographs at one size and one shape. They are all 4:3 sources
   framed 4:3, so unlike the wide slots nothing here is cropped — the grid
   just gets smaller cells as the screen narrows. Two columns on a phone
   rather than one: at 375px that is a 151px cell, which keeps the whole
   gallery under 90KB while still being legible, and a single column would
   make the section twelve screens long. */
.gallery-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-top: 40px; }
.gallery-grid .shot img { aspect-ratio: 4 / 3; }
@media (max-width: 860px) { .gallery-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 560px) { .gallery-grid { grid-template-columns: repeat(2, 1fr); gap: 12px; } }

/* Only true once the viewer exists, so a page with no script never tells
   anyone to tap something that will not respond. */
.lb-hint { display: none; }
.js-lb .lb-hint { display: inline; color: var(--star); }

/* Touch screens have no hover and a sticky :hover there leaves a photograph
   stuck mid-zoom after a tap. */
@media (hover: hover) {
  .shot:hover picture, .shot:focus-within picture {
    border-color: var(--star-dim);
    box-shadow: 0 22px 48px -22px rgba(0, 0, 0, .92);
  }
  .shot:hover img { transform: scale(1.035); }
}

/* ---- the photographs, full size ----------------------------------------
   Only wired up once the script has built the viewer, so a page with no JS
   never offers a zoom cursor that does nothing. */
.js-lb .shot picture { cursor: zoom-in; }
.js-lb .shot picture:focus-visible { outline: 2px solid var(--star); outline-offset: 3px; }

.lb {
  position: fixed; inset: 0; z-index: 200; display: none;
  flex-direction: column; align-items: center; justify-content: center;
  gap: 16px; padding: 28px;
  background: rgba(5, 10, 24, .95);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
}
.lb.open { display: flex; }
.lb figure { margin: 0; }
.lb img {
  display: block; max-width: min(1200px, 94vw); max-height: 76vh;
  object-fit: contain; border-radius: 12px;
  transition: opacity .2s ease;
}
/* Held at reduced opacity between the click and the next photograph
   decoding, so a slow frame reads as a turn rather than as a stall. */
.lb img.is-turning { opacity: .35; }

/* The exposure corrections follow the photograph into the viewer, because
   they fix how it was shot. The global brightness/saturation trim does not:
   that one exists to stop a bright photograph punching a hole in the night
   palette, and on a near-black backdrop there is no palette to protect. */
.lb img[src*="/everyone-"] { filter: brightness(1.14); }
.lb img[src*="/outdoor-"]  { filter: brightness(.92) contrast(1.03); }

.lb-cap {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: center;
  gap: 4px 14px; max-width: 90vw; text-align: center;
  color: var(--moon); font-size: 15px; line-height: 1.5;
}
.lb-count { font-size: 13px; color: var(--moonlight); font-variant-numeric: tabular-nums; }

.lb-x, .lb-nav {
  position: absolute; display: flex; align-items: center; justify-content: center;
  cursor: pointer; color: var(--moon);
  background: rgba(12, 22, 48, .55); border: 1px solid rgba(245, 240, 228, .26);
  border-radius: 50%; width: 46px; height: 46px;
  transition: background-color .2s ease, border-color .2s ease;
}
.lb-x:hover, .lb-nav:hover { background: rgba(12, 22, 48, .85); border-color: var(--star); }
.lb-x:focus-visible, .lb-nav:focus-visible { outline: 2px solid var(--star); outline-offset: 3px; }
.lb-x { top: 16px; right: 18px; font-size: 26px; line-height: 1; padding-bottom: 3px; }
.lb-nav { top: 50%; margin-top: -23px; }
.lb-nav svg { width: 20px; height: 20px; stroke: currentColor; fill: none; stroke-width: 2.2; }
.lb-prev { left: 20px; }
.lb-next { right: 20px; }
@media (max-width: 560px) {
  .lb { padding: 16px; }
  .lb-nav { width: 40px; height: 40px; margin-top: -20px; }
  .lb-prev { left: 8px; }
  .lb-next { right: 8px; }
  .lb img { max-height: 68vh; }
}

@media (prefers-reduced-motion: reduce) {
  .shot picture, .shot img, .lb img, .lb-x, .lb-nav { transition: none; }
  .shot:hover img { transform: none; }
}

/* ---- the language switch ---------------------------------------------- */
/* Three links, not buttons. /en/ and /ja/ are real pages, so switching is a
   navigation — it needs no script, works with JS off, and can be opened in a
   new tab like any other link.
 *
 * One segmented control rather than three loose words, because three labels
 * in three scripts have three different visual weights and only a shared
 * frame makes them read as one thing.
 *
 * The first cut of this was too quiet to find. Avoiding the star gold was
 * right — gold is the call to action sitting immediately to its right, and
 * two gold things side by side left neither looking like the thing to
 * press — but the answer to that is a control you can see in a neutral
 * tone, not a fainter one. The frame, the fill behind it and the type are
 * all up; the current language is a solid pale chip rather than a hint. */
.lang-switch {
  display: inline-flex; align-items: center; flex: none;
  background: rgba(245, 240, 228, .06);
  border: 1px solid rgba(188, 202, 235, .3);
  border-radius: 999px; padding: 3px;
}
.lang-switch a {
  /* Not var(--body): on /ja that is Noto Sans JP, which has no Thai, so the
     ไทย label fell back to whatever the system had and stopped matching the
     two beside it. The switch names each language in its own script, so it
     needs a face that covers all three wherever it appears. */
  font-family: 'IBM Plex Sans Thai', system-ui, sans-serif;
  font-size: 13px; font-weight: 600; line-height: 1;
  text-decoration: none; white-space: nowrap;
  color: var(--moonlight); padding: 7px 12px; border-radius: 999px;
  transition: color .2s ease, background-color .2s ease;
}
.lang-switch a:hover { color: var(--moon); background: rgba(245, 240, 228, .1); }
.lang-switch a[aria-current="true"] {
  color: var(--night); background: var(--moon);
}
.lang-switch a[aria-current="true"]:hover { background: var(--moon); }
.lang-switch a:focus-visible { outline: 2px solid var(--star); outline-offset: 2px; }

@media (max-width: 420px) {
  .lang-switch a { padding: 7px 9px; font-size: 12.5px; }
}

/* ---- the other two languages ------------------------------------------- */
/* --display-leading is 1.94 because Mali draws Thai tone marks tall and two
   lines collide under it. Neither English nor Japanese stacks anything above
   the line, so inheriting that number just leaves the headings looking
   windswept. Measured minimums do not travel between scripts. */
html[lang="en"] { --display-leading: 1.28; }
html[lang="ja"] { --display-leading: 1.45; }

/* Mali has no kana and no kanji, so a Japanese heading in it silently falls
   back to whatever the system picks and stops matching the rest of the page.
   Zen Maru Gothic is the closest rounded companion to Mali's handwriting;
   Noto Sans JP carries the body. Both are linked from the /ja tree only. */
html[lang="ja"] {
  --display: 'Zen Maru Gothic', 'Noto Sans JP', sans-serif;
  --body: 'Noto Sans JP', system-ui, sans-serif;
}
/* Japanese already breaks between most characters by its own rules, and the
   browser knows those rules — which pairs may not be split, where a small
   kana may not start a line. `overflow-wrap: anywhere` overrides all of it
   and was breaking 自分 / で across a line in the hero, so it is not used
   here. The line-height is raised instead, because kanji need more room
   between lines than the Latin default assumes. */
html[lang="ja"] body { line-height: 1.85; }
html[lang="ja"] .hero-sub, html[lang="ja"] .measure { max-width: 46ch; }

/* A Japanese glyph fills its em where a Latin one does not, so the same
   clamp reads much heavier. Stepped down until the hero heading holds the
   two lines its <br> asks for. */
html[lang="ja"] h1 { font-size: clamp(26px, 3.5vw, 37px); letter-spacing: 0; }
html[lang="ja"] h2 { letter-spacing: 0; }

/* ---- a page that is not the home page ---------------------------------- */
/* The gallery has its own URL now, so it needs a head of its own: the hero
   belongs to the home page and its moon, stars and shooting star are a lot
   of furniture to carry into a page that is really a wall of photographs.
   This is the same opening at a quieter volume — kicker, heading, one line —
   and it clears the sticky bar the way the anchors do. */
.page-head {
  background: var(--night);
  /* Plain values, not calc(var(--bar) + n). The top bar is position:sticky,
     so it is still in the flow and this element already begins underneath
     it — adding --bar again counted the bar twice and left 108px of empty
     night between the nav and the kicker. --bar is for scroll-margin on
     anchor targets, which the bar really does cover. */
  padding: 56px 0 10px;
}
.page-head h1 { margin: 6px 0 16px; }
.page-head .measure { color: var(--moonlight); }
@media (max-width: 700px) { .page-head { padding-top: 36px; } }

/* The home page keeps one line about the photographs and sends you to them,
   rather than carrying a second grid of its own — there are already eight
   photographs above it, woven into what the page is arguing. */
.gallery-cta { margin-top: 26px; }

/* The current page, so the nav says where you are and not only where you
   can go. Anchors within a page are not marked; a different page is. */
.topbar-links a[aria-current="page"] { color: var(--star); }

/* ---- the gallery tabs --------------------------------------------------- */
/* Twenty-five photographs is more than anyone scans, and they fall into five
   real groups the school actually ran — a topic week on Thailand, a nature
   session, the dye-spray morning, a cooking lesson, ordinary classroom time.
   Filtering by those is more use than one long wall.
 *
   Hidden until the script says otherwise, for the same reason the viewer's
   hint is: without JS every photograph is already on the page, and a row of
   buttons that filters nothing is worse than no row at all. */
.gal-tabs { display: none; }
.js-tabs .gal-tabs {
  display: flex; flex-wrap: wrap; gap: 8px; margin-top: 34px;
}
.gal-tabs button {
  font: inherit; font-size: 14px; font-weight: 500; line-height: 1;
  cursor: pointer; white-space: nowrap;
  color: var(--moonlight); background: transparent;
  border: 1px solid rgba(188, 202, 235, .22);
  border-radius: 999px; padding: 9px 16px;
  transition: color .2s ease, background-color .2s ease, border-color .2s ease;
}
.gal-tabs button:hover { color: var(--moon); border-color: var(--star-dim); }
.gal-tabs button[aria-selected="true"] {
  color: var(--night); background: var(--star); border-color: var(--star);
  font-weight: 600;
}
.gal-tabs button:focus-visible { outline: 2px solid var(--star); outline-offset: 3px; }
.js-tabs .gallery-grid { margin-top: 20px; }
.shot[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) { .gal-tabs button { transition: none; } }

/* ---- saying what the tabs are for -------------------------------------- */
/* A row of pills is only obviously a filter once you have worked out that it
   is one. The line above says so, and the number on each says how many you
   will get — both so that nobody has to click one to find out what clicking
   does. Hidden with the tabs when there is no script to make them work. */
.gal-tabs-lead { display: none; }
.js-tabs .gal-tabs-lead {
  display: block; margin-top: 34px; margin-bottom: 12px;
  font-size: 14px; color: var(--moonlight);
}
.js-tabs .gal-tabs { margin-top: 0; }
.gal-n {
  font-variant-numeric: tabular-nums; font-size: 12px; font-weight: 600;
  opacity: .62; margin-left: 1px;
}
.gal-tabs button[aria-selected="true"] .gal-n { opacity: .72; }
