/* Near Korea Education public styles — THREE TONES, in a fixed hierarchy.
   v16 rebuilt the site as one hue (198deg) top to bottom, which made every deep
   surface read teal and left eight dark bands on the home page. v17 keeps what
   v16 got right — the sticky header IS the logo's own sky field, with the white
   wordmark on it — and fixes the rest:

     WHITE is the base. Most of the page is white or a sky wash of it.
     SKY (#20A8E2 and its tints) is the majority brand voice: the header, the
       loud bands (the stats card and both CTA bands), every eyebrow, glyph,
       ring, hover and hairline accent.
     NAVY is contrast ONLY, and it is rationed: THREE navy surfaces per page,
       never more. On every page two of them are the top bar and the footer; the
       third is the one dark band that page has earned (the gateway on the home
       page, the hero on every inner page). Everything v16 painted dark that is
       not on that list is now white, a sky wash, or a solid sky field.

   Two contrast rules the palette exists to enforce, both measured in the test
   harness rather than trusted:
     - EVERY solid --sky surface carries WHITE ink, and only white ink (owner
       directive 2026-07-26, v21; v19 had granted this to the header alone).
       White measures 2.71:1 on --sky, below the 4.5:1 AA asks of text, and the
       owner asked for it across the site anyway so a sky field always reads as
       one white lockup rather than a header that speaks one language and five
       bands below it that speak another. The exemption is bounded by SATURATION,
       not by section: it covers --sky fills only. The pale washes (--sky-tint,
       --sky-band) keep their dark ink, and so does everything on white.
     - --sky is an accent for --navy and deeper (3.41:1 as a glyph, 7.17:1 on
       --navy-deep). On a pale band or on white it measures 2.71:1, so there the
       accent token is --sky-ink (5.30:1 on white, 4.65:1 on --sky-band). */
:root {
  /* The brand voice. Sky carries the identity and most of the colour on screen. */
  --sky: #20A8E2;         /* THE logo blue: the header field, solid brand bands, glyphs on navy */
  --sky-ink: #16739B;     /* accent TEXT/borders on white and the pale bands (5.30:1 on white) */
  --sky-tint: #E4F4FB;    /* pale sky chip + hover wash (~12% sky on white) */
  --sky-band: #E6F2F8;    /* the pale sky band: why-choose, team, steps */

  /* The contrast. Rationed to three surfaces a page. */
  --navy: #394282;        /* the brand navy: primary buttons, the comparison table head */
  --navy-dark: #2C3468;   /* primary hover */
  --navy-mid: #454FA0;    /* the light lobe of a navy radial (inner-page + university heroes) */
  --navy-deep: #00043C;   /* the deep surfaces: top bar, gateway, heroes, footer */

  --ink: #000000;
  --ink-soft: #28323A;
  /* Deepened from the #6B7280 v15 and v16 shipped: on the pale --sky-band it
     measured 4.24:1 and missed AA under .step-text, and on white it sat on the
     4.83:1 floor. This lands at 5.80:1 on white and 5.08:1 on --sky-band. */
  --grey: #5D6675;
  --line: #E3E9ED;
  --off: #F8FBFD;
  --white: #FFFFFF;
  --danger: #C1121F;      /* semantic validation error (not brand) */
  --danger-bg: #FDECEF;
  --check: #12946A;       /* comparison "yes" — brand-harmonised green */
  --cross: #D06A73;       /* comparison "no" — a muted, never-harsh red */
  --radius: 14px;
  --shadow: 0 12px 34px rgba(0, 4, 60, 0.12);
  --font: "Poppins", system-ui, -apple-system, "Segoe UI", sans-serif;
  --section-pad: 100px;
  /* What a pale wash closes on when the next section is a saturated band, in
     place of the full --section-pad it would otherwise carry (v22). See the
     "Section rhythm" rule below for why. Restated once per phone breakpoint. */
  --wash-close: 56px;

  /* ---------- Motion (v28) ----------
     ONE curve and ONE step for the whole site, so every move on every page
     reads as the same hand. The curve is a quintic ease-out: it leaves at
     once and lands slowly, which is what makes a move read as weight settling
     rather than as a timer running out. The distances are deliberately short
     - 20px is a settle, 60px is a slide, and a slide is what makes a page
     feel like a template. None of these tokens paints anything, so a browser
     that ignores custom properties simply renders the static page it always
     did. Restated once, calmer, in the phone layer. */
  --ease-out-quint: cubic-bezier(.22, 1, .36, 1);
  --reveal-rise: 20px;      /* how far a revealing block travels */
  --reveal-dur: .72s;       /* how long it takes to land */
  --reveal-step: 70ms;      /* the gap between one block and the next */
  --lift-dur: .2s;          /* a pointer affordance: fast enough to feel direct */
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body { font-family: var(--font); color: var(--ink); background: var(--white); line-height: 1.65; font-size: 16px; }
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
ul { list-style: none; }
.container { width: min(1340px, 92%); margin: 0 auto; }
section { overflow: hidden; }

/* ---------- Buttons + chips ---------- */
/* v28 named the properties the pill transitions instead of animating `all`.
   `all` also animates whatever a variant adds later (a border width, a
   letter-spacing), which is how a control picks up a wobble nobody asked
   for; and it makes the browser watch every property on the busiest element
   on the page. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 15px 34px; font-weight: 500; font-size: 15px; letter-spacing: .04em;
  border: 2px solid transparent; border-radius: 999px; cursor: pointer;
  transition:
    background var(--lift-dur) ease, color var(--lift-dur) ease,
    border-color var(--lift-dur) ease,
    transform var(--lift-dur) var(--ease-out-quint),
    box-shadow var(--lift-dur) var(--ease-out-quint);
  text-transform: uppercase; font-family: var(--font);
}
/* The lift is a POINTER affordance and is asked for rather than assumed: on a
   touch screen :hover latches after a tap, and a button left standing 2px
   proud with no way to put it back down is worse than no lift at all. The
   colour change on each variant below is unconditional, so a phone still gets
   the full state change, just without the raise. */
@media (hover: hover) {
  .btn:hover { transform: translateY(-2px); box-shadow: 0 10px 24px rgba(0, 4, 60, .18); }
}
/* Pressing puts it back down, faster than it came up: a press should feel like
   the finger won, not like the animation is still catching up. */
.btn:active { transform: translateY(0); box-shadow: 0 2px 8px rgba(0, 4, 60, .16); transition-duration: 90ms; }
/* Every button is a fully rounded pill (v4). These modifiers now only tune padding. */
.btn-square { padding: 17px 36px; }
.btn-pill { padding: 15px 36px; }
/* The primary pill is navy on white: 9.23:1 under its label, and the one place
   the contrast tone is allowed to appear away from the three navy surfaces,
   because a button is a control and not a surface. */
/* The -2px these two used to carry themselves now belongs to .btn, so every
   pill on the site rises by the same amount rather than two of them rising
   and the other three staying put (v28). */
.btn-red { background: var(--navy); color: var(--white); }
.btn-red:hover { background: var(--navy-dark); }
.btn-blue { background: var(--navy); color: var(--white); }
.btn-blue:hover { background: var(--navy-dark); }
.btn-grey { background: var(--sky-tint); color: var(--ink); }
.btn-grey:hover { background: var(--navy); color: var(--white); }

.chip {
  display: inline-block; font-size: 15px; padding: 6px 18px; border-radius: 100px; margin-bottom: 18px;
}
.chip-tint { background: var(--sky-tint); color: var(--ink); }
/* A dark-surface component: white hairline, white label. It rides the gateway,
   both photographic heroes and the university hero, all of which are navy. */
.chip-outline { border: 1px solid rgba(255, 255, 255, .35); color: var(--white); }

.eyebrow {
  display: inline-block; color: var(--sky-ink); font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; font-size: 13px; margin-bottom: 12px;
}
.section-title { font-size: clamp(26px, 3vw, 33px); font-weight: 600; line-height: 1.25; }
/* .section-title-light and .btn-glass were retired in v17. Both existed for one
   band, the why-choose section, and that band is no longer dark: its heading is
   plain ink and its call to action is the site's primary navy pill. No dark band
   left on the site carries a .section-title or a ghost button. */

/* ---------- Top info bar ---------- */
/* Navy surface 1 of 3. --navy-deep rather than --navy: the bar carries --sky
   glyphs and a --sky hover on the phone number, and --sky measures 3.41:1 on
   --navy (a graphic passes, text does not) against 7.17:1 here. It also makes
   the stack read as a deliberate descent — near-black strip, the logo's sky
   field, then white. */
.topbar { background: var(--navy-deep); color: var(--white); font-size: 13px; }
/* The bar is two blocks that can never trade space (v19). Until v19 the info
   groups were a nowrap flex item with min-width:0 beside a social block that
   was free to shrink: past the point where the four groups stopped fitting,
   their nowrap values simply overflowed and printed straight through the icon
   tiles. Now the social block is flex:0 0 auto (it is a fixed-size object and
   gives up nothing), the groups take only what is left over, and they shed
   room in the order a reader would: the address wraps to a second line first,
   and only if that is still not enough does a whole group wrap below. */
.topbar-inner { display: flex; justify-content: space-between; align-items: stretch; flex-wrap: nowrap; gap: 20px; }
.topbar-groups { display: flex; align-items: stretch; flex-wrap: wrap; flex: 0 1 auto; min-width: 0; }
.topbar-group { display: flex; align-items: center; gap: 11px; padding: 10px 24px; border-left: 1px solid rgba(255, 255, 255, .16); }
.topbar-groups > .topbar-group:first-child { border-left: none; padding-left: 0; }
.topbar-ic { display: inline-flex; color: var(--sky); flex-shrink: 0; }
.topbar-ic svg { width: 19px; height: 19px; display: block; }
.topbar-lines { display: flex; flex-direction: column; line-height: 1.28; min-width: 0; }
.topbar-lbl { font-size: 11px; color: rgba(255, 255, 255, .62); white-space: nowrap; }
.topbar-val { font-size: 13px; font-weight: 500; color: var(--white); white-space: nowrap; }
/* The address is the one value the owner can make arbitrarily long, and the
   only one with natural break points, so it is the only group that gives way:
   min-width:0 lets flexbox shrink it, and the value wraps inside. Every other
   group keeps min-width:auto, which is what stops flexbox squeezing a phone
   number or an email address narrower than its own text and printing it
   through its neighbour. If even that is not enough room, the wrap on
   .topbar-groups drops a whole group to a second row rather than overlapping
   anything. The floor keeps the address to a readable measure instead of
   letting it crush to one word per line. */
.topbar-group--address { min-width: 0; }
.topbar-group--address .topbar-lines { min-width: 132px; max-width: 220px; }
.topbar-group--address .topbar-val { white-space: normal; overflow-wrap: break-word; }
.topbar-group-link { transition: color .2s ease; }
.topbar-group-link:hover .topbar-val, .topbar-group-link:focus-visible .topbar-val { color: var(--sky); }
.topbar-group-link:focus-visible { outline: 2px solid var(--sky); outline-offset: -2px; }
/* Its own right-hand block: the label sits over the tiles rather than beside
   them, which is what lets the whole thing stay one fixed-width object no
   matter how long the label or the info groups get. Stacking makes it the
   tallest thing in the bar, so it carries the same 10px of vertical padding
   the info groups do rather than running to the edges: the bar settles at
   71px against the 51px it stood at while the label was inline, the group
   dividers run its full height, and the four groups centre inside it. */
.topbar-social {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  flex: 0 0 auto; padding: 10px 0 10px 26px; border-left: 1px solid rgba(255, 255, 255, .16);
}
.topbar-social-lbl { font-size: 11px; line-height: 1; color: rgba(255, 255, 255, .82); white-space: nowrap; }
.topbar-social-row { display: flex; align-items: center; gap: 8px; }
/* White rounded-square tiles: the one shared social-icon look used on every
   navy/navy-deep surface (top bar here, plus .footer-socials a below, which
   also covers the footer and the consultation page's "Reach us directly"
   panel) so the icon language reads as one system site-wide. Flat at rest,
   a soft lift + sky glyph on hover is the only affordance change. The top
   bar sits at the tight end of the size range (34px) since it shares one
   line with four other groups down to a 1024px floor; the footer/consult
   tiles have the room to sit at a slightly roomier 36px (below). */
.topbar-social-ic, .footer-socials a {
  border-radius: 10px; display: inline-grid; place-items: center;
  background: var(--white); color: var(--navy);
  transition: color .2s ease, transform .2s ease, box-shadow .2s ease;
}
.topbar-social-ic { width: 34px; height: 34px; }
.topbar-social-ic svg, .footer-socials a svg { width: 16px; height: 16px; display: block; }
.topbar-social-ic:hover, .topbar-social-ic:focus-visible,
.footer-socials a:hover, .footer-socials a:focus-visible {
  color: var(--sky-ink); transform: translateY(-2px); box-shadow: 0 6px 14px rgba(0, 4, 60, .28);
}
.topbar-social-ic:focus-visible, .footer-socials a:focus-visible { outline: 2px solid var(--sky); outline-offset: 2px; }

/* ---------- Header (the logo's field) ---------- */
/* v16's one great idea, kept intact: the sticky header IS the logo lockup, full
   width, the owner's white wordmark on the owner's sky.
   THE HEADER IS WHITE-ON-SKY (owner directive 2026-07-25, shipped in v19).
   White measures 2.71:1 on --sky, under the 4.5:1 AA wants for text, and the
   owner asked for it anyway so the bar reads as one white lockup with the
   wordmark instead of a white mark beside navy labels. v21 EXTENDED THAT
   DIRECTIVE TO EVERY SOLID SKY SURFACE (owner directive 2026-07-26), so the
   header is no longer the exception: it is the pattern the whole site now
   follows, and the bands below it were flipped to match rather than the other
   way round. Contrast is bought back where it can be: the labels are
   500-weight uppercase, and state is a solid white rule under the label
   rather than a colour shift. */
.site-header { background: var(--sky); position: sticky; top: 0; z-index: 60; transition: box-shadow .28s var(--ease-out-quint); }
/* v28: once the bar has actually left the top of the document it earns a
   shadow, so it reads as a layer over the page instead of a band painted onto
   it. Nothing about its SIZE changes: a sticky element still occupies its own
   space in normal flow, so shrinking it mid-scroll would pull every section
   below it upwards by the difference, which is a jump the eye reads as a
   glitch. The state is set by an IntersectionObserver on the header itself
   (see js/site.js), so the page carries no scroll listener. */
.site-header.is-stuck { box-shadow: 0 10px 30px rgba(0, 4, 60, .22); }
/* One gap value governs the whole bar (v19): 32px between the wordmark, the
   links and the language pill, with 26px between the links themselves so the
   nav still reads as one group rather than six separate items. */
.header-inner { display: flex; align-items: center; gap: 32px; padding: 12px 0; }
.brand-logo { height: 48px; width: auto; }
.main-nav { margin-left: auto; }
.nav-list { display: flex; gap: 26px; align-items: center; font-weight: 500; font-size: 14px; text-transform: uppercase; color: var(--white); }
.nav-list a { color: var(--white); padding: 6px 0; }
/* White is the ceiling of the palette on this field, so there is no brighter
   ink to signal state with. State is a rule under the label instead:
   half-strength under the cursor, solid on the current page. That is a shape
   change rather than colour alone, which is what 1.4.1 wants, and box-shadow
   paints inside the existing 6px padding without ever taking part in layout,
   so the bar costs the nav nothing. */
.nav-list a:hover { box-shadow: inset 0 -2px 0 rgba(255, 255, 255, .5); }
.nav-list a.active { box-shadow: inset 0 -2px 0 var(--white); }
.caret { font-size: 10px; }
.has-dropdown { position: relative; }
.dropdown {
  position: absolute; top: calc(100% + 16px); left: 0; background: var(--white); min-width: 260px;
  border-radius: 8px; box-shadow: var(--shadow); padding: 8px 0; opacity: 0; visibility: hidden;
  transform: translateY(8px); z-index: 70; text-transform: none;
}
.has-dropdown:hover .dropdown, .has-dropdown:focus-within .dropdown { opacity: 1; visibility: visible; transform: translateY(0); }
.dropdown a { display: block; padding: 10px 20px; font-size: 14px; color: var(--ink); }
.dropdown a:hover { background: var(--off); color: var(--sky-ink); }
/* v19 removed .header-cta. The desktop bar used to end on a navy consultation
   pill, which made the header the only place on the site where a solid navy
   control sat on the sky field, and pushed the links left of centre. The
   drawer keeps its own CTA (a different surface, with room for it), the mega
   menu keeps its Expert Advantage pill, and every page still ends on a
   consultation band, so nothing was lost but the crowding. */

/* ---------- Partners Universities mega menu (desktop) ---------- */
/* The li stays static so the panel's containing block is the sticky header,
   letting the card span the full header width and align to the site container. */
.has-mega { position: static; }
.mega {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 70;
  padding-top: 12px; text-transform: none;
  opacity: 0; visibility: hidden; transform: translateY(8px); pointer-events: none;
  transition: opacity .22s ease, transform .22s ease, visibility .22s ease;
}
/* .mega-open (JS-managed) is the ONLY opener. There used to be a parallel
   :focus-within opener here for keyboard support, but :focus-within does not
   distinguish mouse-sourced focus from keyboard focus: a click landing on
   anything inside the panel (the scrollable .mega-tracks column grabs focus
   on click/drag in Chrome and Firefox even without a tabindex, same as any
   link) left it permanently pinned open, since mouseleave only ever removed
   .mega-open and had no effect on :focus-within (v15 fix). Keyboard access is
   preserved: site.js opens on a real :focus-visible focusin and closes on
   focusout-to-outside, Escape, and mouseleave. */
.has-mega.mega-open > .mega {
  opacity: 1; visibility: visible; transform: translateY(0); pointer-events: auto;
}
.mega-inner {
  display: grid; grid-template-columns: minmax(0, 1fr) 296px; gap: 30px;
  background: var(--white); color: var(--ink); border: 1px solid var(--line);
  /* 26px rather than 30 since v22: it is four pixels nobody can see and eight
     the panel needs, and it is what leaves the foot clear of the bottom of a
     768px screen instead of three pixels short of it. */
  border-radius: var(--radius); box-shadow: var(--shadow); padding: 26px 32px;
}
.mega-head { margin-bottom: 16px; }
.mega-eyebrow {
  display: block; color: var(--sky-ink); font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; font-size: 12px;
}
.mega-sub { color: var(--grey); font-size: 14px; margin-top: 4px; }
/* FOUR equal columns since v21, laid out as an explicit grid rather than four
   flex items: `1fr` per track guarantees the gutters are identical and the
   columns line up whatever a track happens to hold, which is what stops the
   short KLP column from stretching the two beside it. A track with nothing in
   it is not rendered at all, so grid-auto-columns handles the three- and
   two-column cases without a second rule.
   On short laptop viewports the columns still get their own scroll so the panel
   header, footer pills and Expert Advantage aside always stay on screen below
   the sticky header; on tall screens everything fits and there is no scrollbar.

   v22 RETUNED THE RESERVE, because the panel was still scrolling on ordinary
   laptops. Every number below was measured in a browser, not guessed. With the
   v21 cap of five the tallest column (five universities plus a View all) stood
   at 426px, while the reserve of 366px meant a viewport had to be 792px tall
   before that column fitted — taller than a maximised window on a 1080p screen
   once the browser chrome is taken off, which is exactly where the owner found
   the scrollbar. v22 closes the gap from both ends. 25px comes off the column: a
   shorter logo tile, and 2-4px each off the track title and the View all row.
   18px comes off the panel's own chrome: the head and foot margins, and four
   pixels of the panel padding. The reserve lands at 358px against a tallest
   column of 401px, so the column fits at any viewport 759px or taller, and the
   whole panel — foot pills and Expert Advantage included — closes 11px clear of
   the bottom of a 768px screen. That covers every window the desktop panel is
   ever opened in, 1366x768 included; below it the column still scrolls,
   gracefully, rather than pushing the foot off screen. Every number here was
   measured in a browser, and the harness pins them. */
.mega-tracks {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 20px;
  max-height: calc(100vh - 358px); overflow-y: auto; overscroll-behavior: contain;
}
.mega-track { min-width: 0; }
.mega-track-title {
  font-size: 12.5px; font-weight: 700; letter-spacing: .04em; color: var(--ink-soft);
  padding-bottom: 8px; margin-bottom: 4px; border-bottom: 1px solid var(--line);
}
.mega-list { display: flex; flex-direction: column; gap: 2px; }
.mega .mega-uni {
  display: flex; align-items: center; gap: 12px; padding: 7px; color: var(--ink);
  border-radius: 12px; transition: background .18s ease, color .18s ease;
}
/* The card IS the tile: one 13:8 box, a hairline border and a radius, no white
   panel floating inside another white panel. The v19 roster replaced the old
   pre-composed 520x320 logo cards with the universities' own marks, which run
   from square seals to wide wordmarks to thin lockups, so the image is CONTAIN
   rather than cover: a wordmark is never cropped and a seal is never stretched.
   tools/korea-art/uni_logos.py centres each shipped mark on a 13:8 canvas at a
   consistent optical area, so the row reads evenly; the padding here is the
   safety margin that keeps an owner-uploaded logo off the border. The initials
   chip still fills the box edge to edge, since padding sits on the image.
   v21 put a fourth column in the same panel, so the tile is no longer a fixed
   130px: it tracks the viewport between 60px and 88px and the name takes what
   is left. The clamp is on the PANEL, not on each column, so all four tiles
   compute to the same width and the rows stay on one baseline grid. The floor
   is set by the longest unbreakable word on the roster, "(Yeongdong)": at
   1280px the tile has to give the name that much or the row spills out of its
   own hover box, which the harness measures rather than assumes.
   v22 BROUGHT THE CEILING DOWN from 88px to 78px, which is what makes the row
   height one number at every width. A 78px tile is 48px tall at 13:8, and the
   name box beside it is 50.7px (3.9em at 13px), so the NAME governs the row
   from the 60px floor to the ceiling and back: 64.7px, on every screen the
   panel opens on. At 88px the tile was 54.2px and took the row over from the
   name past ~1800px wide, which made a 1920 panel 17px taller than a 1440 one
   for no reason anybody could see, and cost five rows exactly the height that
   put the panel into a scrollbar. The name also gains every pixel the tile
   gives up, so the "(Yeongdong)" floor is further away than it was. */
.mega-uni-logo {
  width: clamp(60px, 4.7vw, 78px); aspect-ratio: 13 / 8; flex-shrink: 0; overflow: hidden;
  border: 1px solid rgba(0, 4, 60, .12); border-radius: 10px; background: var(--white);
}
.mega-uni-logo img { width: 100%; height: 100%; object-fit: contain; padding: 5px; display: block; }
.mega-uni-logo .uni-initial-tile--logo { width: 100%; height: 100%; border-radius: 0; font-size: 22px; }
/* Every row is the height of the tallest name a column can hold, three lines at
   13.5/1.3, so all four columns share ONE baseline grid. Without this a wrapped
   "Catholic Kwandong University" in the first column pushed everything under it
   five pixels out of step with the three columns beside it, which is exactly
   the kind of drift a four-column panel makes visible. Shorter names are
   centred in the same box rather than shrinking it. */
.mega-uni-name {
  font-size: 13px; font-weight: 500; line-height: 1.3;
  display: flex; align-items: center; min-height: 3.9em;
}
.mega .mega-uni:hover, .mega .mega-uni:focus-visible { background: var(--off); color: var(--sky-ink); outline: none; }
.mega .mega-uni:focus-visible { box-shadow: inset 0 0 0 2px var(--sky-ink); }
.mega .mega-viewall { display: inline-flex; align-items: center; gap: 6px; padding: 6px 8px; font-size: 13.5px; font-weight: 600; color: var(--sky-ink); }
.mega-foot { display: flex; gap: 12px; margin-top: 18px; padding-top: 18px; border-top: 1px solid var(--line); }
.mega .mega-pill {
  display: inline-flex; align-items: center; padding: 8px 18px; border-radius: 999px;
  border: 1.5px solid var(--line); font-size: 13px; font-weight: 600; color: var(--ink-soft);
  transition: border-color .18s ease, color .18s ease, background .18s ease;
}
.mega .mega-pill:hover, .mega .mega-pill:focus-visible { border-color: var(--sky-ink); color: var(--sky-ink); background: var(--off); }
/* The one dark rectangle that is not a page surface: a transient panel inside a
   dropdown. Its light lobe is --navy rather than --navy-mid so the --sky benefit
   glyphs still clear the 3:1 a graphic needs (on --navy-mid they measure 2.70). */
.mega-aside {
  background: radial-gradient(circle at top left, var(--navy) 0%, var(--navy-deep) 70%);
  color: var(--white); border-radius: var(--radius); padding: 26px 24px;
  display: flex; flex-direction: column;
}
.mega-aside-title { font-size: 17px; font-weight: 600; margin-bottom: 16px; }
.mega-benefits { display: flex; flex-direction: column; gap: 14px; margin-bottom: 22px; }
.mega-benefits li { display: flex; align-items: flex-start; gap: 12px; font-size: 13.5px; line-height: 1.4; color: rgba(255, 255, 255, .92); }
.mega-benefit-ic { flex-shrink: 0; color: var(--sky); margin-top: 1px; }
.mega-benefit-ic svg { width: 21px; height: 21px; display: block; }
.mega .mega-cta { margin-top: auto; width: 100%; background: var(--white); color: var(--navy); border-color: var(--white); }
/* The one control on the site whose HOVER surface is a sky fill, so its label
   follows the same rule the bands do: ink on --sky is white (v21). */
.mega .mega-cta:hover { background: var(--sky); color: var(--white); border-color: var(--sky); }
.mega-caret { transition: transform .2s ease; }
.has-mega.mega-open > .mega-trigger .mega-caret { transform: rotate(180deg); }

@media (prefers-reduced-motion: reduce) {
  .mega { transition: opacity .01ms linear; transform: none; }
  .mega-caret { transition: none; }
}

/* Language picker (v29, replacing the two-way globe pill): ONE segmented
   control, not three loose flags. Three flags set side by side on the sky field
   read as clutter and say nothing about which of them you are currently
   reading, so they are held inside a single soft translucency, and the language
   in force sits on a solid white chip inside it. That chip is the whole signal:
   position and surface, not colour, which is what lets it work on a field where
   white is already the ceiling of the palette.
   What the inactive flags do NOT do is fade. Dimming artwork on a saturated blue
   field lets the blue through it, and the flags stop being true: at .58 the
   Vietnamese star came out green and the Union Flag came out purple. They are
   held back by a tenth instead, and the state is carried entirely by the chip,
   which is a stronger signal than a fade was anyway. Hovering an inactive
   language shows a ghost of the chip it is about to become.
   The flags are exempt from the white-on-sky law by definition: they are artwork
   rather than ink, and a flag repainted to match a header is not a flag. Each
   one carries a hairline edge so all three read as objects of the same weight -
   without it Korea's white field has no edge at all and dissolves into the white
   chip the moment it becomes the current language. */
.lang-picker {
  display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0;
  padding: 4px; border-radius: 10px;
  background: rgba(255, 255, 255, .16);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .24);
}
.lang-flag {
  display: flex; align-items: center; justify-content: center; position: relative;
  width: 34px; height: 26px; border-radius: 6px; opacity: .9;
  transition: opacity var(--lift-dur) var(--ease-out-quint),
              transform var(--lift-dur) var(--ease-out-quint),
              background-color var(--lift-dur) var(--ease-out-quint);
}
.lang-flag .flag-svg {
  display: block; width: 24px; height: 16px; border-radius: 2.5px;
  box-shadow: 0 0 0 1px rgba(0, 4, 60, .18);
}
/* Scoped away from the current language: that one is a <span>, not a link, and
   a control that lifts under the cursor while doing nothing is a lie. */
.lang-flag:not(.is-active):hover { opacity: 1; background: rgba(255, 255, 255, .26); transform: translateY(-1px); }
.lang-flag:not(.is-active):active { transform: translateY(0) scale(.96); }
.lang-flag:focus-visible { outline: 2px solid var(--white); outline-offset: 1px; opacity: 1; }
/* The current language. A solid white chip under one short shadow: enough to
   read as sitting above the track, not enough to become a button. */
.lang-flag.is-active {
  opacity: 1; background: var(--white); cursor: default;
  box-shadow: 0 1px 2px rgba(0, 4, 60, .16);
}
/* The header names each language for a screen reader without showing the word:
   the flag is the visible label, and three endonyms spelled out would be the
   clutter the picker exists to avoid. The drawer shows them (mobile layer). */
.lang-flag-sr {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

@media (prefers-reduced-motion: reduce) {
  .lang-flag { transition: none; }
  .lang-flag:not(.is-active):hover, .lang-flag:not(.is-active):active { transform: none; }
}

.hamburger { display: none; background: none; border: 0; width: 42px; height: 38px; flex-direction: column; justify-content: center; gap: 6px; cursor: pointer; }
/* White, like everything else the header puts on the sky field (v19). On a
   phone the bar is only the white wordmark and this control, so painting it
   navy would make the one thing you tap the one thing that does not belong. */
.hamburger span { display: block; height: 2.5px; background: var(--white); border-radius: 2px; transition: transform .25s var(--ease-out-quint), opacity .2s ease, background .2s ease; }

/* ---------- Hero ---------- */
.hero { background: var(--white) center / cover no-repeat; position: relative; }
.hero::before {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, rgba(255,255,255,.94) 0%, rgba(255,255,255,.82) 38%, rgba(255,255,255,.25) 62%, rgba(255,255,255,0) 100%);
}
.hero-inner { display: grid; grid-template-columns: 1.05fr .95fr; align-items: center; gap: 40px; padding: 72px 0; position: relative; }
.hero-title { font-size: clamp(30px, 4.4vw, 41px); line-height: 1.24; font-weight: 600; letter-spacing: -0.05em; margin: 6px 0 16px; }
.hero-subtitle { color: var(--ink-soft); max-width: 540px; margin-bottom: 30px; }
.hero-ctas { display: flex; gap: 14px; flex-wrap: wrap; }
.hero-media img { margin-left: auto; }
/* v27: on the desktop/tablet overlay layout (>980px, where the cutout sits
   beside the copy on top of the background photo) the cutout is anchored to
   the true bottom of the hero section, not just to its own grid row. Before
   this, the image was centred (align-items: center, inherited from
   .hero-inner) inside a row sized to the taller column; whichever column
   that was, the image's own bottom edge landed .hero-inner's padding above
   the section's actual bottom, leaving a band of the plain background photo
   exposed below it with the image's own hard (rounded-rect) edge cutting
   across it. align-self: stretch pins the media cell to the full row height
   regardless of which column is taller; the image is then absolutely
   positioned inside that cell and grown by exactly the section's own bottom
   padding, so its bottom edge always lands flush with .hero-inner's true
   bottom edge, at any copy length. Cropped on the sides (object-fit: cover),
   never top-to-bottom, since the artwork has generous side margin inside its
   rounded card and neither the tower nor the flag/caps sit close enough to
   the edge to be touched by it.
   Scoped to min-width: 981px (the exact complement of the max-width: 980px
   breakpoint below): under that width the cutout drops into normal flow
   below the copy, not overlaid on the photo, so there is no equivalent gap
   to close there - the small space under the image on a phone is ordinary
   section padding, not this bug. */
@media (min-width: 981px) {
  .hero-media { position: relative; align-self: stretch; }
  .hero-media img {
    position: absolute; top: 0; right: 0; margin-left: 0;
    width: 478px; height: calc(100% + 72px);
    object-fit: cover; object-position: center bottom;
  }
}

/* ---------- Partners + stats ---------- */
.partners { padding: var(--section-pad) 0 70px; }
.partners-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 30px; margin-bottom: 36px; flex-wrap: wrap; }
.partners-sub { color: var(--grey); max-width: 420px; }
/* Was a dark radial card, one of eight dark surfaces on the home page. It is now
   the first solid sky field after the header: the four numbers the agency is
   proudest of. v21 flipped the whole card to white ink, numbers and captions
   alike, and took the dividers with it (owner directive 2026-07-26): a sky
   field is a white field. The caption keeps its full white rather than the .85
   it carried under navy — a dimmed white on this ground reads as a printing
   error — and buys its legibility back the way the header does, with weight
   (500) instead of colour. Hierarchy is carried by size and weight alone:
   25px/600 over 14.5px/500. */
.stats-band {
  background: var(--sky);
  border-radius: var(--radius); color: var(--white);
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px;
  padding: 38px 34px; margin-bottom: 46px;
}
.stat { text-align: center; border-right: 1px solid rgba(255, 255, 255, .35); }
.stat:last-child { border-right: none; }
.stat-value { font-size: 25px; font-weight: 600; }
.stat-label { font-size: 14.5px; font-weight: 500; }

/* ---------- University tabs ---------- */
.uni-tab-buttons { display: flex; gap: 12px; margin-bottom: 30px; flex-wrap: wrap; }
.uni-tab {
  border: 1.5px solid var(--line); background: var(--white); color: var(--ink);
  font-family: var(--font); font-size: 16px; font-weight: 500; padding: 15px 26px;
  border-radius: 6px; cursor: pointer;
}
.uni-tab.active { background: var(--navy); border-color: var(--navy); color: var(--white); }
.uni-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 22px; }
.uni-card {
  background: var(--white); border: 1px solid var(--line); border-radius: 12px; overflow: hidden;
  display: flex; flex-direction: column;
}
.uni-card:hover { box-shadow: var(--shadow); transform: translateY(-4px); }
/* The card's image is the university's own mark (v19), so it is contained on a
   white field rather than cropped to fill: cover would slice the sides off a
   wordmark and the top off a tall seal. The hover zoom is kept, scaled from the
   contained mark rather than from a crop. */
.uni-card-media { aspect-ratio: 16 / 10; overflow: hidden; background: var(--white); }
.uni-card-media img { width: 100%; height: 100%; object-fit: contain; padding: 14px; }
.uni-card:hover .uni-card-media img { transform: scale(1.05); }
.uni-card-body { padding: 16px 18px 18px; }
.uni-card-title { font-size: 18px; font-weight: 600; color: var(--navy); line-height: 1.3; margin-bottom: 6px; }
.uni-card-city { font-size: 13.5px; color: var(--grey); }

/* ---------- University marquee (home) ---------- */
.uni-marquee { display: flex; flex-direction: column; gap: 0; }
.uni-marquee-row { overflow: hidden; padding: 16px 0; }
.uni-marquee-row:hover .uni-marquee-track,
.uni-marquee-row:focus-within .uni-marquee-track { animation-play-state: paused; }
.uni-marquee-track { display: flex; width: max-content; animation: uni-marquee-scroll linear infinite; }
/* Track holds 4 identical sets; shifting by -25% (one set width) keeps the loop seamless
   and the wide track keeps every viewport (up to 1920px) gap-free even with few logos. */
@keyframes uni-marquee-scroll { from { transform: translateX(0); } to { transform: translateX(-25%); } }
.uni-marquee-track-reverse { animation-direction: reverse; }
.uni-marquee-set { display: flex; flex-shrink: 0; }
/* Card IS the tile (matches the mega menu): one 13:8 box, hairline border and
   radius, the mark contained inside it (v19, see .mega-uni-logo above). */
.uni-marquee-card {
  width: 170px; aspect-ratio: 13 / 8; margin-right: 18px; flex-shrink: 0;
  background: var(--white); border: 1px solid rgba(0, 4, 60, .12); border-radius: 12px;
  overflow: hidden; display: block; outline-offset: 3px;
}
.uni-marquee-card img { width: 100%; height: 100%; object-fit: contain; padding: 8px; filter: grayscale(35%) opacity(.75); transition: filter .25s ease; display: block; }
.uni-marquee-card .uni-initial-tile--marquee { width: 100%; height: 100%; border-radius: 0; }
.uni-marquee-card:hover,
.uni-marquee-card:focus-visible {
  transform: translateY(-4px) scale(1.05);
  box-shadow: 0 12px 28px rgba(0, 4, 60, .22);
  border-color: transparent;
}
.uni-marquee-track-reverse .uni-marquee-card:hover,
.uni-marquee-track-reverse .uni-marquee-card:focus-visible {
  box-shadow: 0 12px 28px rgba(32, 168, 226, .28);
}
.uni-marquee-card:hover img,
.uni-marquee-card:focus-visible img { filter: grayscale(0%) opacity(1); }
@media (prefers-reduced-motion: reduce) {
  .uni-marquee-track { animation: none; }
  .uni-marquee-row { overflow-x: auto; }
  .uni-marquee-set:not(:first-child) { display: none; }
}

/* ---------- Dream CTA ---------- */
.dream { background: var(--off); padding: 46px 0; }
.dream-inner { display: flex; justify-content: space-between; align-items: center; gap: 24px; flex-wrap: wrap; }
.dream-title { font-size: 25px; font-weight: 600; color: var(--navy); }
.dream-sub { color: var(--grey); }

/* ---------- Why choose (the pale sky band) ---------- */
/* This was the darkest surface on the site through v16, and on the home page it
   was the third dark band inside the first two screens. It carries a photograph
   and a two-column checklist, both of which read better on a light ground than a
   solid sky field would allow, so it takes the pale band tone: airy, still
   unmistakably a band, and it costs nothing from the three-surface navy budget.
   Shared verbatim by the About page, which reuses this markup. */
.why-choose { background: var(--sky-band); color: var(--ink); padding: var(--section-pad) 0; }
.why-inner { display: grid; grid-template-columns: 1fr 1.1fr; gap: 56px; align-items: center; }
.why-media { position: relative; }
/* height:auto is load-bearing: the img carries width/height attributes so its
   box is reserved before it decodes (no layout shift), and without this the
   attribute's presentational height would beat the aspect ratio and stretch
   the photo. The rendered result once loaded is unchanged. */
.why-media img { border-radius: var(--radius); height: auto; }
.why-media-badge {
  position: absolute; left: 18px; bottom: 18px; right: 18px; background: rgba(0, 4, 60, .92);
  color: var(--white); padding: 12px 18px; border-radius: 10px; font-size: 14.5px; font-weight: 500;
}
.why-text { color: var(--ink-soft); margin: 14px 0 22px; max-width: 520px; }
/* Bullets flow down the first column, then the second: --check-rows = ceil(count/2)
   is set inline per render, so a 7-item list splits 4 left / 3 right and any other
   count balances the same way, all sharing one row baseline grid. */
.check-list {
  display: grid; grid-auto-flow: column;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-rows: repeat(var(--check-rows, 1), auto);
  column-gap: 44px; row-gap: 16px; margin-bottom: 30px;
}
.check-list li { padding-left: 32px; position: relative; font-weight: 500; line-height: 1.45; }
/* A sky disc with a WHITE tick (v21). The disc is a sky-filled shape, so the
   glyph drawn inside it follows the same rule as the bands: ink on --sky is
   white, wherever that sky appears and however small it is (owner directive
   2026-07-26). The label beside it is unaffected — it sits on the pale band or
   on white, not on the disc, so it keeps its dark ink. */
.check-list li::before {
  content: "\2713"; position: absolute; left: 0; top: 0; width: 22px; height: 22px;
  background: var(--sky); color: var(--white); border-radius: 50%; display: inline-flex;
  align-items: center; justify-content: center; font-size: 12px; font-weight: 700;
}

/* ---------- Services ---------- */
.services { padding: var(--section-pad) 0; }
.services-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 26px; margin-bottom: 40px; flex-wrap: wrap; }
.service-list { border-top: 1px solid var(--line); }
.service-row {
  display: grid; grid-template-columns: 90px 1fr auto; gap: 26px; align-items: center;
  padding: 30px 8px; border-bottom: 1px solid var(--line); transition: background .2s ease;
}
.service-row:hover { background: var(--off); }
.service-num { font-size: 20px; font-weight: 500; color: var(--grey); }
.service-title { font-size: 22px; font-weight: 600; margin-bottom: 6px; }
.service-text { color: var(--grey); font-size: 15px; max-width: 640px; }
.service-link { color: var(--sky-ink); font-weight: 600; font-size: 14.5px; text-transform: uppercase; letter-spacing: .06em; white-space: nowrap; }
.service-link:hover { text-decoration: underline; }

/* ---------- Consultation CTA band ---------- */
/* One of the site's two full-width asks, and with .cta-band it defines the
   single CTA-band language: a solid sky field, --navy-deep copy, a white pill.
   Sky is what the eye should catch when the page asks for something, and using
   colour instead of darkness here is what freed the navy budget for the gateway. */
.consult-band { background: var(--sky); color: var(--white); padding: 72px 0; }
.consult-band-inner { display: flex; align-items: center; justify-content: space-between; gap: 30px 52px; flex-wrap: wrap; }
.consult-band-copy { max-width: 690px; }
.consult-band-title { font-size: clamp(26px, 3.4vw, 38px); font-weight: 700; line-height: 1.2; }
/* Solid white, not a dimmed white. Half a flip is worse than no flip: a white
   heading over a translucent sub-line would read as two different inks on one
   surface, which is exactly what v21 set out to end. The step down to the
   sub-line is size and weight (38/700 over 16/400), not colour. */
.consult-band-text { margin-top: 14px; color: var(--white); font-size: 16px; line-height: 1.6; max-width: 620px; }
.consult-band-cta { flex-shrink: 0; }
/* The pill is a CONTROL, not ink: it keeps its own white surface and inverts to
   navy when lit, which is still the strongest state change available on this
   ground. Its focus ring is white (v21) and rides 3px of bare sky, so the ring
   and the pill never touch and the halo stays legible as a shape. */
.consult-band-btn { background: var(--white); color: var(--navy); }
.consult-band-btn:hover, .consult-band-btn:focus-visible { background: var(--navy); color: var(--white); }
.consult-band-btn:focus-visible { outline: 2px solid var(--white); outline-offset: 3px; }
/* Scroll reveal: heading, then sub-text, then the button. The rules live in
   ONE place now (see "MOTION SYSTEM (v28)" near the foot of this layer);
   the order is the order they are written in the markup. */

/* ---------- Success stories & student reviews (v8) ---------- */
.success { padding: var(--section-pad) 0; background: var(--off); }
.success-head { text-align: center; margin-bottom: 42px; }
.success-title { font-size: clamp(27px, 3.2vw, 36px); }
/* One shared height drives both columns: the intro card and every review card
   are exactly --ss-card-h tall, so they line up as clean equal rectangles and
   the 3:4 cards crop the 480x640 art not at all. */
.success-inner { --ss-card-h: clamp(322px, 27vw, 374px); display: grid; grid-template-columns: minmax(0, 358px) minmax(0, 1fr); gap: 26px; align-items: center; }
/* Left intro card: a pale sky card rather than the dark gradient it was, so the
   only saturated things in this section are the review cards themselves, which
   is where the eye belongs. min-height locks it to the card height for normal
   copy (so both columns are equal rectangles) yet lets an unusually long intro
   grow the card instead of spilling outside it. */
.success-intro {
  background: var(--sky-band);
  color: var(--ink); border-radius: var(--radius); padding: 40px 38px; min-height: var(--ss-card-h);
  display: flex; flex-direction: column; justify-content: center;
}
.success-quote { font-family: Georgia, "Times New Roman", serif; font-size: 74px; line-height: 1; color: var(--sky-ink); height: 40px; overflow: hidden; }
.success-intro-text { margin-top: 20px; font-size: 16.5px; line-height: 1.72; color: var(--ink-soft); }
/* Right strip: a right-to-left marquee. Track holds 4 identical sets; shifting
   by -25% (one set width) keeps the loop seamless, matching the logo marquee. */
.success-strip { overflow: hidden; border-radius: var(--radius); }
.success-strip:hover .success-track,
.success-strip:focus-within .success-track { animation-play-state: paused; }
.success-track { display: flex; width: max-content; animation: success-scroll linear infinite; }
@keyframes success-scroll { from { transform: translateX(0); } to { transform: translateX(-25%); } }
.success-set { display: flex; flex-shrink: 0; }
/* Card IS the tile (card-is-the-tile law): a 3:4 portrait picture fills the card
   edge to edge, one hairline border + radius, no inner padding. */
.success-card {
  height: var(--ss-card-h); width: calc(var(--ss-card-h) * 0.75); margin-right: 18px; flex-shrink: 0;
  border-radius: var(--radius); overflow: hidden; border: 1px solid rgba(0, 4, 60, .10);
  background: var(--white); box-shadow: 0 8px 22px rgba(0, 4, 60, .07);
}
.success-card img { width: 100%; height: 100%; object-fit: cover; display: block; }
.success-card:hover { transform: translateY(-6px); box-shadow: 0 16px 34px rgba(0, 4, 60, .20); }
/* Scroll reveal: heading, then the intro card, then the strip. See "MOTION
   SYSTEM (v28)". */
/* Reduced motion: no drift, the strip becomes a static horizontal scroller. */
@media (prefers-reduced-motion: reduce) {
  .success-track { animation: none; }
  .success-strip { overflow-x: auto; }
  .success-set:not(:first-child) { display: none; }
}
@media (max-width: 860px) {
  .success-inner { grid-template-columns: 1fr; gap: 22px; }
  .success-intro { height: auto; padding: 32px 28px; text-align: left; }
}

/* ---------- Popular Programs (v22) ---------- */
/* Nine tiles on a 3x3 grid, sitting on the pale sky band. The band is what
   keeps this section from merging into "Why we are different" below it, which
   is white; the reference this came from used 3D clipart on a grey field, and
   neither belongs here. Every tile is the site's own card: white, one hairline,
   one radius, a line icon on a pale sky disc and a navy label. The last tile is
   the "explore all" one and takes the brand's saturated sky, so its ink is
   white — the same rule every solid sky surface on this site follows. */
.programs { padding: var(--section-pad) 0; background: var(--sky-band); }
.programs-head { text-align: center; max-width: 660px; margin: 0 auto 42px; }
.programs-text { color: var(--ink-soft); margin-top: 12px; }
/* Three across, but the grid is capped rather than stretched to the 1340px
   container: nine tiles at a third of the full width are 420px boxes holding
   one word each, which reads as nine empty rows. At 1080 the tile is a card. */
.programs-grid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px;
  max-width: 1080px; margin: 0 auto;
}
.program-tile {
  display: flex; flex-direction: column; align-items: center; text-align: center; gap: 16px; min-width: 0;
  background: var(--white); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 30px 20px;
}
.program-tile:hover, .program-tile:focus-visible { transform: translateY(-4px); box-shadow: var(--shadow); border-color: transparent; }
.program-tile:focus-visible { outline: 2px solid var(--sky-ink); outline-offset: 2px; }
/* The disc is a PALE sky tint, not a sky fill, so the glyph on it stays dark:
   --sky-ink is the accent token the palette reserves for pale grounds. */
.program-ic {
  width: 62px; height: 62px; flex-shrink: 0; border-radius: 50%;
  display: inline-grid; place-items: center;
  background: var(--sky-tint); color: var(--sky-ink);
  transition: background .25s ease, color .25s ease;
}
.program-ic svg { width: 30px; height: 30px; display: block; }
.program-label { font-size: 17px; font-weight: 600; color: var(--navy); line-height: 1.3; min-width: 0; }
/* The arrow rides the explore tile's own label, on its one line, so the tile
   reads as a sentence ("Popular Programs, that way") rather than as a card with
   a stray mark under it. The other eight carry no arrow at all. */
.program-go { margin-left: 6px; font-size: 17px; line-height: 1; }
/* The explore-all tile: the same card, filled with the brand sky. White ink,
   white disc-on-sky glyph, white arrow — a sky field is a white field. */
.program-tile-feature { background: var(--sky); border-color: var(--sky); color: var(--white); }
.program-tile-feature .program-label { color: var(--white); }
.program-tile-feature .program-ic { background: rgba(255, 255, 255, .18); color: var(--white); }
.program-tile-feature:hover, .program-tile-feature:focus-visible { border-color: var(--sky); }
.program-tile-feature:focus-visible { outline-color: var(--navy); }
/* Scroll reveal: heading, then the grid, whose nine tiles cascade one after
   another (data-reveal-items). See "MOTION SYSTEM (v28)". */

/* ---------- Why we are different (v9) ---------- */
.different { padding: var(--section-pad) 0; background: var(--white); }
.different-inner { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 46%); gap: 60px; align-items: center; }
.different-title { max-width: 16ch; }
.different-subheading { margin-top: 14px; color: var(--sky-ink); font-weight: 600; font-size: 17px; }
.different-text { margin-top: 20px; display: grid; gap: 14px; color: var(--grey); font-size: 15.5px; line-height: 1.72; max-width: 560px; }
/* Three ring stats: a navy arc draws over a pale navy track while the centred %
   counts up in sync. pathLength=100 lets the offset be a plain 100 - percent. */
.ring-stats { margin-top: 42px; display: flex; gap: 30px; flex-wrap: wrap; }
.ring-stat { display: flex; flex-direction: column; align-items: center; text-align: center; width: 132px; }
.ring { position: relative; width: 118px; height: 118px; }
.ring-svg { width: 100%; height: 100%; display: block; }
.ring-track { fill: none; stroke: rgba(32, 168, 226, .20); stroke-width: 9; }
.ring-fill {
  fill: none; stroke: var(--navy); stroke-width: 9; stroke-linecap: round; stroke-dasharray: 100;
  transform-box: fill-box; transform-origin: center; transform: rotate(-90deg);
  transition: stroke-dashoffset 1.15s var(--ease-out-quint);
}
.ring-value { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-weight: 700; color: var(--navy); }
.ring-num { font-size: 27px; letter-spacing: -.01em; font-variant-numeric: tabular-nums; }
.ring-unit { font-size: 16px; font-weight: 600; margin-left: 1px; }
.ring-label { margin-top: 14px; font-size: 13.5px; font-weight: 500; color: var(--ink-soft); line-height: 1.3; max-width: 12ch; }
/* The photo dissolves into the white section, top and bottom, like the reference. */
.different-media { position: relative; }
.different-media img { width: 100%; height: 100%; max-height: 560px; object-fit: cover; border-radius: 20px; }
.different-media::after {
  content: ""; position: absolute; inset: 0; border-radius: 20px; pointer-events: none;
  background: linear-gradient(to bottom, var(--white) .5%, rgba(255, 255, 255, 0) 20%, rgba(255, 255, 255, 0) 74%, var(--white) 99.5%);
}
/* Scroll reveal: heading, sub-line, paragraphs, rings, then the photograph.
   See "MOTION SYSTEM (v28)". The rings are the one thing that does NOT ride
   the band: they draw and count on an observer of their own, so the numbers
   run while they are on screen rather than finishing under the fold of a tall
   section (js/site.js). */
/* Reduced motion: rings + numbers render at their final value, no drawing. */
@media (prefers-reduced-motion: reduce) {
  .ring-fill { transition: none; }
}
@media (max-width: 900px) {
  .different-inner { grid-template-columns: 1fr; gap: 34px; }
  .different-media { order: -1; }
  .different-media img { max-height: 420px; }
  .different-text { max-width: none; }
}
@media (max-width: 520px) {
  .ring-stats { gap: 12px; justify-content: space-between; }
  .ring-stat { width: 31%; }
  .ring { width: 90px; height: 90px; }
  .ring-num { font-size: 21px; }
  .ring-unit { font-size: 13px; }
}

/* ---------- Why we are the better choice (comparison table, v9) ---------- */
.compare { padding: var(--section-pad) 0; background: var(--off); }
.compare-head { text-align: center; margin-bottom: 42px; }
.compare-title { margin: 0 auto; max-width: 22ch; }
.compare-table-wrap {
  max-width: 940px; margin: 0 auto; background: var(--white);
  border: 1px solid var(--line); border-radius: 18px; overflow: hidden;
  box-shadow: 0 14px 40px rgba(0, 4, 60, .07);
}
.compare-table { width: 100%; border-collapse: collapse; font-size: 15.5px; }
.compare-table thead th {
  background: var(--navy); color: var(--white); font-weight: 600; font-size: 15px;
  padding: 18px 24px; text-align: center; white-space: nowrap;
}
.compare-table thead th.compare-col-service { text-align: left; }
/* Our column is the hero: a sky accent line under the header + a pale sky tint. */
.compare-col-us { position: relative; }
.compare-col-us::after { content: ""; position: absolute; inset: 0 0 auto 0; height: 3px; background: var(--sky); }
.compare-table tbody th, .compare-table tbody td { padding: 15px 24px; border-top: 1px solid var(--line); }
.compare-service { text-align: left; font-weight: 500; color: var(--ink-soft); }
.compare-cell { text-align: center; }
.compare-cell-us { background: rgba(32, 168, 226, .07); }
.compare-mark { display: inline-flex; line-height: 0; }
.compare-mark svg { width: 24px; height: 24px; }
.compare-yes svg circle { fill: var(--check); }
.compare-no svg circle { fill: var(--cross); }
/* Reveal: heading first, then the whole table. The twelve rows deliberately do
   NOT animate one by one: a table is read as a block, and staggering its rows
   turns an argument into a slot machine. See "MOTION SYSTEM (v28)". */
/* Mobile: the table scrolls horizontally inside its card, first column pinned. */
@media (max-width: 640px) {
  .compare-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .compare-table { min-width: 560px; }
  .compare-col-service, .compare-service { min-width: 220px; }
  .compare-table thead th.compare-col-service, .compare-table tbody th.compare-service {
    position: sticky; left: 0;
  }
  .compare-table thead th.compare-col-service { z-index: 2; }
  .compare-table tbody th.compare-service { z-index: 1; background: var(--white); box-shadow: 1px 0 0 var(--line); }
}

/* ---------- Process ---------- */
.process { padding: 120px 0; }
.process-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 26px; margin-bottom: 42px; flex-wrap: wrap; }
.process-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 24px; }
.process-card { display: flex; flex-direction: column; }
.process-media { border-radius: var(--radius); overflow: hidden; aspect-ratio: 4 / 3.4; margin-bottom: 18px; background: var(--off); }
.process-media img { width: 100%; height: 100%; object-fit: cover; }
.process-card:hover .process-media img { transform: scale(1.06); }
.process-title { font-size: 20px; font-weight: 600; margin-bottom: 8px; }
.process-text { color: var(--grey); font-size: 14.5px; }

/* ---------- Gateway ---------- */
/* Navy surface 2 of 3 on the home page, and the only dark band between the
   header and the footer. It earns it: this is the section built for a dark
   ground — the glass form panel, the rotating ring of type, the overlapping
   avatars whose borders are cut out of the band itself, the amber rating. Flat
   rather than a radial, because the .60 white captions in here need the whole
   band to be --navy-deep (7.09:1) to clear AA. */
.gateway { background: var(--navy-deep); color: var(--white); padding: var(--section-pad) 0; position: relative; }
@keyframes marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.gateway-circle { position: relative; width: 160px; height: 160px; margin-top: 42px; }
.gateway-circle-svg { width: 100%; height: 100%; animation: spin 18s linear infinite; }
/* The ring reads the company's own name twice around a 128px-diameter path,
   402.12 user units long. v19 tuned the size and tracking until the type came
   close to that number; v21 stopped guessing and let the browser do it, with
   textLength on the textPath in the markup (see views/pages/home.php). The type
   metrics below now only set the WEIGHT of the ring, never its fit, so a font
   swap or an owner edit to the stored string can no longer open a gap in it. */
.gateway-circle-text textPath { fill: rgba(255,255,255,.85); font-size: 11.5px; letter-spacing: .13em; text-transform: uppercase; }
.gateway-circle-mark { position: absolute; inset: 0; margin: auto; width: 44px; height: 44px; }
@keyframes spin { to { transform: rotate(360deg); } }
.gateway-image { border-radius: 12px; margin-bottom: 20px; width: 100%; aspect-ratio: 16 / 8; object-fit: cover; }
.gateway-avatars { display: flex; }
.gateway-avatars img { width: 42px; height: 42px; border-radius: 50%; border: 2px solid var(--navy-deep); object-fit: cover; margin-right: -10px; }
.gateway-inner { display: grid; grid-template-columns: 1.15fr .85fr; gap: 60px; align-items: center; }
.gateway-title { font-size: clamp(28px, 3.6vw, 38px); font-weight: 600; line-height: 1.25; margin: 18px 0 14px; }
.gateway-text { color: rgba(255,255,255,.75); max-width: 520px; margin-bottom: 28px; }
.gateway-cta-row { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.gateway-since { color: rgba(255,255,255,.6); font-size: 14.5px; }
.gateway-panel { background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.12); border-radius: 18px; padding: 30px; }
.gateway-form-title { font-size: 19px; font-weight: 600; margin-bottom: 16px; }
.gateway-form { display: flex; gap: 10px; }
.gateway-form input {
  flex: 1; min-width: 0; background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.2);
  border-radius: 6px; padding: 14px 16px; color: var(--white); font-family: var(--font); font-size: 15px;
}
.gateway-form input::placeholder { color: rgba(255,255,255,.5); }
.gateway-form .btn { padding: 14px 20px; }
.gateway-note { font-size: 13.5px; min-height: 20px; margin-top: 8px; color: #7CF5A2; }
.gateway-rating { border-top: 1px solid rgba(255,255,255,.12); margin-top: 18px; padding-top: 18px; display: flex; gap: 18px; align-items: center; }
.gateway-stars { color: #FFC531; letter-spacing: 4px; margin-bottom: 4px; }
.gateway-rating-label { color: rgba(255,255,255,.6); font-size: 13.5px; }
.gateway-success { margin-top: 16px; }
.gateway-success h4 { font-size: 16px; margin-bottom: 4px; }
.gateway-success p { color: rgba(255,255,255,.65); font-size: 14px; }

/* ---------- Team ---------- */
.team { background: var(--sky-band); padding: var(--section-pad) 0; }
.team-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 26px; margin-bottom: 42px; flex-wrap: wrap; }
.team-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 22px; }
.team-media { position: relative; border-radius: var(--radius); overflow: hidden; aspect-ratio: 3 / 3.6; background: var(--white); }
.team-media > img { width: 100%; height: 100%; object-fit: cover; }
.team-overlay {
  position: absolute; inset: auto 0 0 0; padding: 46px 18px 16px;
  background: linear-gradient(to top, rgba(0, 4, 60, .97) 0%, rgba(0, 4, 60, .86) 70%, rgba(0, 4, 60, 0) 100%); color: var(--white);
}
.team-name { font-size: 20px; font-weight: 600; }
.team-role { font-size: 13.5px; opacity: .8; margin-bottom: 10px; }
.team-socials { display: flex; gap: 8px; }
.team-socials a {
  width: 30px; height: 30px; border-radius: 50%; border: 1px solid rgba(255,255,255,.4);
  display: inline-flex; align-items: center; justify-content: center; font-size: 12.5px; font-weight: 600;
}
.team-socials a:hover { background: var(--navy); border-color: var(--navy); }

/* ---------- Blog preview ---------- */
.blog-preview { padding: var(--section-pad) 0; }
.blog-head { text-align: center; margin-bottom: 44px; }
.blog-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
.blog-card { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--white); display: flex; flex-direction: column; }
.blog-card:hover { box-shadow: var(--shadow); transform: translateY(-4px); }
.blog-card-media { aspect-ratio: 16 / 9.5; overflow: hidden; background: var(--off); display: block; }
.blog-card-media img { width: 100%; height: 100%; object-fit: cover; }
.blog-card:hover .blog-card-media img { transform: scale(1.05); }
.blog-card-placeholder { width: 100%; height: 100%; background: linear-gradient(135deg, var(--sky-band), var(--off)); }
.blog-card-body { padding: 20px 22px 24px; display: flex; flex-direction: column; gap: 8px; flex: 1; }
.blog-card-meta { font-size: 13px; color: var(--grey); }
.blog-card-title { font-size: 20px; font-weight: 600; line-height: 1.35; }
.blog-card-title a:hover { color: var(--sky-ink); }
.blog-card-excerpt { color: var(--grey); font-size: 14.5px; flex: 1; }
.blog-card-more { color: var(--sky-ink); font-weight: 600; font-size: 14.5px; }

/* ---------- FAQ (v10) ---------- */
.faq { padding: var(--section-pad) 0; background: var(--off); }
.faq-head { text-align: center; margin: 0 auto 46px; }
.faq-title { margin: 0 auto; max-width: 30ch; }
.faq-title-accent { color: var(--sky-ink); }
/* Split layout: a ~40/60 illustration/accordion pair, vertically centred
   against each other so neither column feels taller or shorter than it is. */
.faq-body { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); gap: 56px; align-items: center; }
.faq-illustration { text-align: center; }
.faq-illustration img { width: 100%; max-width: 420px; height: auto; margin: 0 auto; display: block; }
.faq-list { display: flex; flex-direction: column; gap: 14px; }
/* Each question is its own rounded card (site radius, hairline border) rather
   than a hairline-separated list, matching the reference and our card idiom. */
.faq-item { background: var(--white); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.faq-q { margin: 0; }
.faq-question {
  width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 20px 24px; background: none; border: 0; text-align: left; cursor: pointer;
  font-family: var(--font); transition: background .2s ease;
}
.faq-question:hover { background: rgba(32, 168, 226, .06); }
.faq-question:focus-visible { outline: 2px solid var(--sky-ink); outline-offset: -2px; }
.faq-question-text { font-size: 16.5px; font-weight: 600; color: var(--ink); line-height: 1.4; }
.faq-question:hover .faq-question-text, .faq-question:focus-visible .faq-question-text { color: var(--sky-ink); }
/* A plus glyph (two bars) that rotates 45deg into a close (x) mark when open. */
.faq-icon { position: relative; width: 20px; height: 20px; flex-shrink: 0; transition: transform .34s var(--ease-out-quint); }
.faq-icon::before, .faq-icon::after { content: ""; position: absolute; background: var(--sky-ink); border-radius: 2px; }
.faq-icon::before { left: 0; top: 50%; width: 100%; height: 2px; margin-top: -1px; }
.faq-icon::after { top: 0; left: 50%; width: 2px; height: 100%; margin-left: -1px; }
.faq-item.is-open .faq-icon { transform: rotate(45deg); }
/* Height animates via the CSS grid 0fr/1fr technique: no JS measuring, no
   max-height guesswork. The inner clip keeps the collapsed content from
   leaking padding. */
.faq-answer-wrap { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .42s var(--ease-out-quint); }
.faq-item.is-open .faq-answer-wrap { grid-template-rows: 1fr; }
.faq-answer-inner { overflow: hidden; min-height: 0; }
.faq-answer-text { padding: 0 24px 22px; color: var(--grey); font-size: 15px; line-height: 1.72; }
/* Reveal: heading, then the illustration, then the question list; individual
   questions do not stagger, because the list is one object you scan, not ten
   things arriving. See "MOTION SYSTEM (v28)". */
/* Reduced motion: the accordion opens/closes instantly, no drawn height. */
@media (prefers-reduced-motion: reduce) {
  .faq-answer-wrap, .faq-icon { transition: none; }
}
@media (max-width: 980px) {
  .faq-body { grid-template-columns: 1fr; gap: 32px; }
  .faq-illustration img { max-width: 60%; }
}
@media (max-width: 600px) {
  .faq-illustration img { max-width: 46%; }
  .faq-question { padding: 17px 18px; gap: 14px; }
  .faq-answer-text { padding: 0 18px 18px; }
}

/* ---------- Footer ---------- */
.footer-cta { background: var(--navy-deep); color: var(--white); padding: 46px 0; border-bottom: 1px solid rgba(255,255,255,.12); }
.footer-cta-inner { display: flex; justify-content: space-between; align-items: center; gap: 24px; flex-wrap: wrap; }
.footer-cta-title { font-size: clamp(20px, 2.6vw, 28px); max-width: 640px; font-weight: 600; }
.footer-cta-mail { display: flex; flex-direction: column; }
.footer-cta-mail-label { font-size: 13px; opacity: .75; }
.footer-cta-mail-value { font-weight: 700; font-size: 18px; color: var(--sky); }
.site-footer { background: var(--navy-deep); color: rgba(255,255,255,.75); padding: 64px 0 0; font-size: 14.5px; }
.footer-grid { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1.2fr; gap: 36px; padding-bottom: 44px; }
/* A fifth column (the "Find us" map) only exists when mapQuery is set, so its
   track is additive: the four-column rhythm above is untouched when the map
   is hidden, and this widens the row for it when present (v10). */
.footer-grid.has-map { grid-template-columns: 1.25fr 0.8fr 0.8fr 1.05fr 1.2fr; }
.footer-logo { height: 50px; width: auto; margin-bottom: 18px; }
.footer-col h3 { color: var(--white); font-size: 15px; letter-spacing: .1em; text-transform: uppercase; margin-bottom: 18px; }
.footer-col ul li { margin-bottom: 10px; }
.footer-col a:hover { color: var(--white); }
/* Tile look (white rounded-square, navy glyph) is shared with the top bar and
   the consultation panel's .consult-socials; see .topbar-social-ic, .footer-socials a
   in the top-bar section above for the full recipe. Sized here (36px, a touch
   roomier than the top bar's 34px) since the footer has the width to spare. */
.footer-socials { display: flex; gap: 10px; margin-top: 18px; }
.footer-socials a { width: 36px; height: 36px; }
.footer-news-form { display: flex; gap: 8px; margin: 12px 0; }
.footer-news-form input {
  flex: 1; min-width: 0; background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.2);
  border-radius: 6px; padding: 12px 16px; color: var(--white); font-family: var(--font);
}
.footer-news-form input::placeholder { color: rgba(255,255,255,.5); }
.footer-news-form .btn { padding: 12px 24px; }
.footer-news-note { font-size: 13px; opacity: .7; }
/* "Find us" footer map (v10): a hairline card matching the site radius, so it
   reads as one more quiet footer block rather than an embedded widget. */
.footer-map { border-radius: var(--radius); overflow: hidden; border: 1px solid rgba(255, 255, 255, .14); background: rgba(255, 255, 255, .04); }
.footer-map iframe { display: block; width: 100%; height: 230px; border: 0; }
.footer-map-link { display: inline-block; margin-top: 10px; font-size: 13px; font-weight: 600; color: var(--sky); }
.footer-map-link:hover, .footer-map-link:focus-visible { color: var(--white); text-decoration: underline; }
.footer-bottom {
  display: flex; justify-content: space-between; align-items: center; gap: 12px 32px; flex-wrap: wrap;
  border-top: 1px solid rgba(255,255,255,.12); padding: 20px 0;
}
/* v24: the closing row carries two groups now, the legal line on the left and
   the build credit on the right, so each side is its own flex box and the row
   itself is left to do nothing but space the two apart. */
.footer-legal { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* A 3px dot, not a pipe. The row already has a full-width rule along its top;
   a second vertical stroke 20px under it reads as a second border. */
.footer-legal-dot { width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: .5; flex: none; }
.footer-legal a:hover, .footer-legal a:focus-visible { color: var(--white); }
/* Wraps as a whole rather than mid-phrase: on a 320px screen the introduction
   keeps its line and the lockup drops under it, which is the one break of the
   three possible here that still reads as a credit. */
.footer-credit { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px 10px; transition: opacity .2s ease; }
/* No colour of its own: it inherits the .75 white the copyright opposite it is
   already written in, so the row closes in one voice and only the lockup, which
   is a size and a weight louder, speaks up. */
.footer-credit-by { font-size: 13px; white-space: nowrap; }
.footer-credit-lockup { display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; }
/* Sized by its own ink: the viewBox is cropped to the mark's bounds, so 14px of
   height is 14px of drawn glyph standing beside a 15px word, not a line box
   with the glyph floating somewhere inside it. */
.footer-credit-mark { height: 14px; width: auto; flex: none; }
.footer-credit-name { font-size: 15px; font-weight: 600; color: var(--white); }
.footer-credit-name-accent { color: #FA641F; }
/* The colours here are the studio's, not ours, so the hover recedes the whole
   lockup rather than repainting any part of it. */
.footer-credit:hover { opacity: .72; }
.footer-credit:focus-visible { outline: 2px solid var(--sky); outline-offset: 4px; border-radius: 4px; }

/* ---------- Page hero (inner pages) ---------- */
.page-hero { background: var(--navy-deep); color: var(--white); padding: 64px 0; text-align: center; }
.page-hero-title { font-size: clamp(30px, 4vw, 42px); font-weight: 600; margin-bottom: 8px; }
.breadcrumb { color: rgba(255,255,255,.78); font-size: 14.5px; }
.breadcrumb a:hover { color: var(--white); }

/* ---------- About: marquee ---------- */
.about-marquee { background: var(--sky); color: var(--white); padding: 14px 0; overflow: hidden; }
.about-marquee .marquee-track { display: inline-flex; white-space: nowrap; animation: marquee 26s linear infinite; font-weight: 600; letter-spacing: .1em; font-size: 15px; }
.about-marquee-item { margin-right: 26px; }
/* Ornament, not copy: it inherits the band's white and keeps its .7 recession,
   the same reduced-alpha white the owner's directive allows for rules and
   dividers. Without it the star shouts as loudly as the words it separates. */
.marquee-star { opacity: .7; margin-left: 26px; }

/* ---------- About: leadership ---------- */
.leadership { padding: var(--section-pad) 0; }
.leadership-head { text-align: center; margin-bottom: 48px; }
.leadership-list { display: flex; flex-direction: column; gap: 64px; }
.leader-row { display: grid; grid-template-columns: 1.2fr .8fr; gap: 52px; align-items: center; }
.leader-row-flip { direction: rtl; }
.leader-row-flip > * { direction: ltr; }
.leader-heading { font-size: clamp(22px, 2.6vw, 28px); font-weight: 600; margin-bottom: 14px; }
.leader-text { color: var(--grey); margin-bottom: 24px; }
.leader-media img { border-radius: var(--radius); aspect-ratio: 4 / 3.4; object-fit: cover; width: 100%; }
.leader-name { margin-top: 12px; font-weight: 600; }
.leader-name span { color: var(--grey); font-weight: 400; font-size: 14px; margin-left: 8px; }

/* ---------- About: partners preview ---------- */
.partners-preview { padding: var(--section-pad) 0; background: var(--off); }

/* ---------- About: since band ---------- */
.since-band { background: var(--sky); color: var(--white); padding: 64px 0; }
/* The one sky field that carries a .chip-outline. Through v20 this band
   inverted the chip to a navy hairline with navy ink; v21 deleted that override
   outright (owner directive 2026-07-26) so the pill takes the base
   .chip-outline recipe — white hairline, white label — and the band reads as a
   single white lockup with the heading under it. Nothing replaces the rule:
   the absence IS the fix, and the harness pins it that way. */
.since-inner { text-align: center; }
.since-title { font-size: clamp(26px, 3.4vw, 36px); font-weight: 600; margin-top: 12px; }

/* ---------- About: why us ---------- */
.why-us { padding: var(--section-pad) 0; }
.why-us-head { text-align: center; max-width: 640px; margin: 0 auto 44px; }
.why-us-text { color: var(--grey); margin-top: 12px; }
.why-us-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 22px; }
.why-us-point { border: 1px solid var(--line); border-radius: var(--radius); padding: 26px 22px; font-weight: 500; }
.why-us-point:hover { box-shadow: var(--shadow); transform: translateY(-4px); }
.why-us-num { display: block; color: var(--sky-ink); font-weight: 700; font-size: 22px; margin-bottom: 10px; }

/* ---------- About: steps ---------- */
.steps { background: var(--sky-band); padding: var(--section-pad) 0; }
.steps-head { text-align: center; margin-bottom: 44px; }
.steps-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 22px; }
.step-card { background: var(--white); border-radius: var(--radius); padding: 30px 26px; box-shadow: var(--shadow); }
.step-label { color: var(--sky-ink); font-weight: 700; font-size: 13px; letter-spacing: .12em; }
.step-title { font-size: 19px; font-weight: 600; margin: 10px 0 8px; }
.step-text { color: var(--grey); font-size: 14.5px; }

/* ---------- About: testimonials ---------- */
.testimonials { padding: var(--section-pad) 0; }
.testimonials-head { text-align: center; margin-bottom: 44px; }
.testimonials-rating { color: var(--grey); margin-top: 10px; font-size: 14.5px; }
.testimonial-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 22px; }
.testimonial-card { border: 1px solid var(--line); border-radius: var(--radius); padding: 30px 26px; background: var(--white); }
.testimonial-stars { margin-bottom: 12px; letter-spacing: 3px; }
.star-on { color: #FFC531; }
.star-off { color: var(--line); }
.testimonial-text { color: var(--ink-soft); margin-bottom: 16px; }
.testimonial-name { font-weight: 600; }

/* ---------- Services page ---------- */
.services-detail { padding: var(--section-pad) 0; }
.svc-section { display: grid; grid-template-columns: .9fr 1.1fr; gap: 56px; align-items: center; padding: 56px 0; border-bottom: 1px solid var(--line); scroll-margin-top: 110px; }
.svc-section:last-child { border-bottom: none; }
.svc-flip { direction: rtl; }
.svc-flip > * { direction: ltr; }
.svc-media img { border-radius: var(--radius); aspect-ratio: 4 / 3; object-fit: cover; width: 100%; }
.svc-index { color: var(--sky-ink); font-weight: 700; font-size: 15px; letter-spacing: .1em; }
.svc-title { font-size: clamp(24px, 2.8vw, 30px); font-weight: 600; margin: 8px 0 14px; }
.svc-text { color: var(--grey); margin-bottom: 14px; }
.check-list-dark li { color: var(--ink); }
.svc-copy .btn { margin-top: 12px; }

/* ---------- Consultation page ---------- */
.consult { padding: var(--section-pad) 0; }
.consult-inner { display: grid; grid-template-columns: 1.35fr .65fr; gap: 48px; align-items: start; }
.consult-form { margin-top: 26px; }
.consult-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 20px; }
.consult-field { margin-bottom: 18px; }
.consult-field label { display: block; font-weight: 500; font-size: 14.5px; margin-bottom: 7px; }
.req { color: var(--sky-ink); }
.consult-field input, .consult-field select, .consult-field textarea {
  width: 100%; padding: 13px 15px; border: 1.5px solid var(--line); border-radius: 8px;
  font-family: var(--font); font-size: 15px; background: var(--white); color: var(--ink);
}
.consult-field input:focus, .consult-field select:focus, .consult-field textarea:focus {
  outline: none; border-color: var(--sky-ink);
}
.consult-field textarea { resize: vertical; }
.consult-field input[type="file"] { padding: 10px; }
.consult-hint { color: var(--grey); font-size: 13px; margin-top: 6px; }
.consult-submit { margin-top: 8px; }
.consult-note { min-height: 22px; margin-top: 12px; font-weight: 500; }
.consult-note.ok { color: #146C43; }
.consult-note.error { color: var(--danger); }
.hp-field { position: absolute; left: -9999px; top: -9999px; height: 1px; width: 1px; overflow: hidden; }
.consult-side { display: flex; flex-direction: column; gap: 22px; }
.consult-badges { display: flex; flex-direction: column; gap: 14px; }
.consult-badge { border: 1px solid var(--line); border-left: 4px solid var(--sky-ink); border-radius: 10px; padding: 18px 20px; background: var(--white); }
.consult-badge h3 { font-size: 16.5px; margin-bottom: 2px; }
.consult-badge p { color: var(--grey); font-size: 14px; }
/* Was the consultation page's fourth dark rectangle. A form page should be the
   airiest page on the site, so the panel takes the pale band tone and the whole
   page runs on the three-surface budget (top bar, hero, footer) with nothing
   dark between them. */
.consult-contact { background: var(--sky-band); color: var(--ink); border-radius: var(--radius); padding: 26px 24px; }
.consult-contact h3 { font-size: 17px; margin-bottom: 14px; }
.consult-contact ul { display: flex; flex-direction: column; gap: 10px; font-size: 14.5px; }
.consult-contact a { color: var(--sky-ink); font-weight: 500; }
.consult-contact a:hover { color: var(--navy); text-decoration: underline; }
/* The shared white tile would vanish on a pale card, so inside this panel the
   same tile inverts: solid navy, white glyph, 9.23:1 either way round. */
.consult-contact .footer-socials a { background: var(--navy); color: var(--white); }
.consult-contact .footer-socials a:hover, .consult-contact .footer-socials a:focus-visible { background: var(--navy-dark); color: var(--white); }
.consult-socials { margin-top: 18px; }

/* ---------- CTA band (home, between team and blog) ---------- */
/* The home page's second ask, and it speaks the same sentence as .consult-band:
   solid sky, navy ink, a white pill that inverts to navy. Two identical asks
   seven sections apart is a rhythm; two different ones would have been noise. */
.cta-band { background: var(--sky); color: var(--white); padding: 52px 0; }
.cta-band .btn-red { background: var(--white); color: var(--navy); }
.cta-band .btn-red:hover { background: var(--navy); color: var(--white); }
.cta-band-inner { display: flex; justify-content: space-between; align-items: center; gap: 26px; flex-wrap: wrap; }
.cta-band-title { font-size: clamp(22px, 3vw, 30px); font-weight: 600; max-width: 720px; }

/* ---------- Section rhythm: a pale wash closing onto a saturated band (v22) ---------- */
/* A pale band's closing padding is normally the air that separates it from the
   white section under it. When the next section is a SATURATED band instead,
   that air stops doing any work: the eye reads the pale strip and the colour
   below it as one seam, and a full --section-pad of empty --sky-band above a
   --sky field is just a hole. On the home page that is exactly what sat between
   the team grid and the "Guiding Bangladeshi Students" band, which is the gap
   the owner measured. The wash keeps its opening padding (it still has to clear
   the band above it) and closes on a little over half of it.
   Written as adjacency rather than as a one-off on .team, so the rule states
   the principle and follows the sections if they are ever reordered: every pale
   --sky-band surface the site has, meeting every saturated surface it can meet.
   Browsers without :has() simply keep the old rhythm, which is only ever a few
   spare pixels, never a broken layout.
   The closing value is a token so the phone layer can restate it in one line
   instead of repeating this selector: see --wash-close below. */
:is(.why-choose, .team, .steps, .programs):has(+ :is(.cta-band, .consult-band, .since-band, .about-marquee, .gateway, .footer-cta, .page-hero, .post-hero, .up-hero, .uni-hero)) {
  padding-bottom: var(--wash-close);
}

/* ---------- Student video carousel (v22) ---------- */
/* The third strip on the site, and it speaks the same language as the other
   two: one set of cards rendered four times, the track shifted by exactly one
   set width (25%), paused on hover and on keyboard focus, and a static
   swipeable row under prefers-reduced-motion and on phones.
   The one difference is the DIRECTION. The owner asked for left to right, so
   the track starts one set to the LEFT and travels back to zero, which moves
   the content rightward; the logo and review strips travel the other way. That
   is why this has its own keyframes rather than borrowing animation-direction:
   the intent is written down, not inferred from a reversed clock. */
.vstories { padding: var(--section-pad) 0; background: var(--off); }
.vstories-head { text-align: center; margin-bottom: 40px; }
.vstories-title { margin: 0 auto; max-width: 26ch; }
.vstories-strip { overflow: hidden; }
.vstories-strip:hover .vstories-track,
.vstories-strip:focus-within .vstories-track { animation-play-state: paused; }
.vstories-track { display: flex; width: max-content; animation: vstories-scroll linear infinite; }
@keyframes vstories-scroll { from { transform: translateX(-25%); } to { transform: translateX(0); } }
.vstories-set { display: flex; flex-shrink: 0; }
/* Card IS the tile: a 16:9 thumbnail fills it edge to edge, one hairline and
   the site radius, the play glyph and the caption riding on top of the picture.
   The navy backing is what shows for the split second before the image decodes,
   so a card is never a white hole in the row. */
.vstories-card {
  position: relative; display: block; width: 320px; aspect-ratio: 16 / 9;
  margin-right: 20px; flex-shrink: 0; border-radius: var(--radius); overflow: hidden;
  background: var(--navy-deep); border: 1px solid rgba(0, 4, 60, .10);
  box-shadow: 0 8px 22px rgba(0, 4, 60, .07);
}
.vstories-card img { width: 100%; height: 100%; object-fit: cover; display: block; }
.vstories-card:hover, .vstories-card:focus-visible { transform: translateY(-6px); box-shadow: 0 16px 34px rgba(0, 4, 60, .20); }
.vstories-card:focus-visible { outline: 2px solid var(--sky-ink); outline-offset: 3px; }
/* The play glyph is what makes the card read as video rather than as a photo.
   It is drawn inline (no external asset, nothing to hotlink) and it centres in
   the picture ABOVE the caption band, not in the whole card, so the two never
   crowd each other. Sky disc, white triangle: ink on sky is white. */
.vstories-play {
  position: absolute; inset: 0 0 30px 0; margin: auto; width: 52px; height: 52px;
  display: grid; place-items: center; pointer-events: none; transition: transform .3s ease;
}
.vstories-play svg { width: 52px; height: 52px; display: block; filter: drop-shadow(0 5px 14px rgba(0, 4, 60, .38)); }
.vstories-play svg circle { fill: var(--sky); }
.vstories-card:hover .vstories-play, .vstories-card:focus-visible .vstories-play { transform: scale(1.08); }
.vstories-label {
  position: absolute; inset: auto 0 0 0; padding: 36px 16px 14px;
  background: linear-gradient(to top, rgba(0, 4, 60, .95) 0%, rgba(0, 4, 60, .74) 58%, rgba(0, 4, 60, 0) 100%);
  color: var(--white); font-size: 14.5px; font-weight: 500; line-height: 1.35;
}
/* Reveal: heading, then the strip. See "MOTION SYSTEM (v28)". */
/* Reduced motion: no drift, the strip becomes a static horizontal scroller. */
@media (prefers-reduced-motion: reduce) {
  .vstories-track { animation: none; }
  .vstories-strip { overflow-x: auto; }
  .vstories-set:not(:first-child) { display: none; }
  .vstories-play { transition: none; }
}

/* ---------- Universities index ---------- */
.partners-sub-center { text-align: center; max-width: 560px; margin: 0 auto 36px; }
.page-hero-text { color: rgba(255,255,255,.8); max-width: 640px; margin: 0 auto 10px; }
.up-hero { background: var(--navy-deep); color: var(--white); padding: 72px 0 64px; text-align: center; background-size: cover; background-position: center; }
.up-hero .chip { margin-bottom: 14px; }
.up-eyebrow { display: block; }
.up-features { display: flex; justify-content: center; gap: 34px; flex-wrap: wrap; margin: 34px 0 8px; text-align: left; }
.up-feature { display: flex; gap: 12px; align-items: center; }
.up-feature-icon { font-size: 28px; }
.up-feature h3 { font-size: 15.5px; font-weight: 600; }
.up-feature p { font-size: 13.5px; color: rgba(255,255,255,.7); }
.up-stats { display: flex; justify-content: center; gap: 44px; flex-wrap: wrap; margin-top: 26px; padding-top: 24px; border-top: 1px solid rgba(255,255,255,.14); }
.up-stat { display: flex; flex-direction: column; }
.up-stat strong { font-size: 24px; font-weight: 700; }
.up-stat span { font-size: 13.5px; color: rgba(255,255,255,.7); }
.up-hero + .partners { padding-top: 64px; }

/* ---------- University detail ---------- */
.uni-hero { background: radial-gradient(circle at 30% 20%, var(--navy-mid) 0%, var(--navy-deep) 70%); color: var(--white); padding: 72px 0; }
.uni-hero-title { font-size: clamp(28px, 4vw, 40px); font-weight: 600; margin: 12px 0 6px; max-width: 760px; }
.uni-hero-meta { color: rgba(255,255,255,.75); margin-bottom: 28px; }
.uni-hero-stats { display: flex; gap: 42px; flex-wrap: wrap; }
.uni-stat { display: flex; flex-direction: column; }
.uni-stat strong { font-size: 24px; font-weight: 700; }
.uni-stat span { font-size: 13.5px; color: rgba(255,255,255,.7); }
.uni-detail { padding: var(--section-pad) 0; }
.uni-detail-inner { display: grid; grid-template-columns: 1fr 330px; gap: 48px; align-items: start; }
.uni-apply { margin: 22px 0 8px; }
.uni-sub-title { margin-top: 46px; }
.rich-text { color: var(--ink-soft); }
.rich-text p { margin: 14px 0; }
.rich-text h2, .rich-text h3, .rich-text h4 { margin: 24px 0 10px; line-height: 1.3; }
.rich-text ul, .rich-text ol { margin: 14px 0 14px 22px; }
.rich-text ul { list-style: disc; }
.rich-text a { color: var(--sky-ink); text-decoration: underline; }
.rich-text img { border-radius: 10px; margin: 18px 0; }
.rich-text blockquote { border-left: 4px solid var(--sky-ink); padding: 8px 18px; background: var(--off); border-radius: 8px; margin: 18px 0; }
.program-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; margin-top: 22px; }
.program-card { border: 1px solid var(--line); border-radius: 12px; padding: 22px 20px; }
/* -4px, like every other card on the site since v28: a lone -3px card was a
   difference nobody could name and everybody could feel. */
.program-card:hover { box-shadow: var(--shadow); transform: translateY(-4px); }
.program-card h3 { font-size: 17px; font-weight: 600; margin-bottom: 6px; color: var(--navy); }
.program-card p { color: var(--grey); font-size: 14px; }
.uni-facts { background: var(--off); border: 1px solid var(--line); border-radius: var(--radius); padding: 28px 26px; position: sticky; top: 96px; }
.uni-logo { height: 64px; width: auto; margin-bottom: 16px; object-fit: contain; }
.uni-facts h3 { font-size: 19px; margin-bottom: 14px; }
.uni-facts dl div { display: flex; justify-content: space-between; gap: 14px; padding: 10px 0; border-bottom: 1px solid var(--line); font-size: 14px; }
.uni-facts dt { color: var(--grey); }
.uni-facts dd { font-weight: 500; text-align: right; max-width: 60%; overflow-wrap: anywhere; }
.uni-tuition { color: var(--sky-ink); font-weight: 700; }
.uni-facts a:not(.btn) { color: var(--sky-ink); }
.uni-facts-cta { width: 100%; margin-top: 18px; }

/* ---------- Admission requirements panel ---------- */
.uni-admissions { background: var(--off); border: 1px solid var(--line); border-radius: var(--radius); padding: 22px 26px; margin-top: 22px; }
.uni-admissions dl { display: grid; gap: 0; }
.uni-admissions dl div { display: flex; justify-content: space-between; align-items: baseline; gap: 24px; padding: 12px 0; border-bottom: 1px solid var(--line); font-size: 14.5px; }
.uni-admissions dl div:last-child { border-bottom: none; }
.uni-admissions dt { color: var(--grey); flex-shrink: 0; max-width: 40%; }
.uni-admissions dd { font-weight: 500; text-align: right; overflow-wrap: anywhere; }
@media (max-width: 560px) {
  .uni-admissions dl div { flex-direction: column; gap: 3px; }
  .uni-admissions dt, .uni-admissions dd { max-width: 100%; text-align: left; }
}

/* ---------- Initial-tile fallback (university with no image) ---------- */
.uni-initial-tile {
  display: flex; align-items: center; justify-content: center; border: none;
  background: var(--navy); color: var(--white); font-family: var(--font);
  font-weight: 700; line-height: 1; letter-spacing: .04em; text-transform: uppercase; user-select: none;
}
.uni-initial-tile--marquee { align-self: stretch; width: 100%; border-radius: 8px; font-size: 30px; }
.uni-initial-tile--card { width: 100%; height: 100%; font-size: clamp(30px, 7vw, 52px); }
.uni-initial-tile--logo { width: 92px; height: 64px; border-radius: 8px; font-size: 24px; }

/* ---------- Consult CTA band (shared) ---------- */
.consult-cta-band { background: var(--off); border-block: 1px solid var(--line); padding: 44px 0; }
.consult-cta-inner { display: flex; justify-content: space-between; align-items: center; gap: 24px; flex-wrap: wrap; }
.consult-cta-title { font-size: 24px; font-weight: 600; color: var(--navy); }
.consult-cta-sub { color: var(--grey); }

/* ---------- Blog ---------- */
.blog-index { padding: var(--section-pad) 0; }
.blog-empty { text-align: center; color: var(--grey); padding: 60px 0; }
.pagination { display: flex; gap: 8px; justify-content: center; margin-top: 44px; }
.page-link { width: 40px; height: 40px; border: 1px solid var(--line); border-radius: 8px; display: inline-flex; align-items: center; justify-content: center; font-weight: 500; }
.page-link.active, .page-link:hover { background: var(--navy); border-color: var(--navy); color: var(--white); }
.post-hero { background: var(--navy-deep); color: var(--white); padding: 64px 0; }
.post-meta { color: rgba(255,255,255,.78); font-size: 14px; margin-bottom: 10px; }
.post-title { font-size: clamp(26px, 3.6vw, 38px); font-weight: 600; line-height: 1.3; max-width: 820px; }
.post-body-wrap { max-width: 820px; padding: 56px 0 var(--section-pad); }
.post-cover { border-radius: var(--radius); margin-bottom: 32px; width: 100%; }
.post-body { font-size: 16.5px; }
.post-back { display: inline-block; margin-top: 36px; color: var(--sky-ink); font-weight: 600; }

/* ---------- Legal ---------- */
.legal { padding: var(--section-pad) 0; }
.legal-inner { max-width: 820px; }

/* ---------- Error pages + flash ---------- */
.error-page { padding: 110px 0; text-align: center; }
.error-code { font-size: 88px; font-weight: 800; color: var(--sky-ink); line-height: 1; }
.error-inner h1 { font-size: 30px; margin: 8px 0 10px; }
.error-inner p { color: var(--grey); margin-bottom: 26px; }
.flash { padding: 14px 20px; border-radius: 10px; margin: 16px auto; width: min(720px, 92%); text-align: center; font-weight: 500; }
.flash-ok { background: #E8F7EE; color: #146C43; }
.flash-error { background: var(--danger-bg); color: var(--danger); }

/* ==========================================================================
   INNER-PAGE POLISH (v14)
   --------------------------------------------------------------------------
   The home page was designed over many rounds; the inner pages were ported
   1:1 from the old theme and never got the same pass. Everything below brings
   them onto the home page's language: the same navy radial gradient family for
   dark bands, the same pill/rounded-card idiom, the same kicker + title
   rhythm, the same scroll-reveal. Nothing here selects anything the home page
   renders, so the signed-off home layout is untouched:
     .page-hero / .post-hero / .up-* / .uni-hero / .uni-detail / .uni-facts /
     .svc-* / .legal / .error-* / .pagination / .consult-cta-band / .rich-text /
     .uni-tab / .testimonial-* are inner-page-only selectors, and the reveal
     rules only bite once JS has added .reveal-ready, which it does only for
     sections carrying data-reveal-band (no home section gained one).
   ========================================================================== */

/* ---------- Inner page hero (v14) ---------- */
/* Was a flat rectangle on six pages. It now uses the same radial the
   university hero uses, so every dark band on the site belongs to one family.
   v16 moved that family onto one teal hue; v17 moves it back to navy, where
   the recipe reads --navy-mid to --navy-deep. On an inner page this hero IS
   the third navy surface — the top bar and the footer are the other two, and
   there is no other dark band anywhere on the page. Pages that set their own
   background-image inline (About) keep their photo: an inline background-image
   outranks this. */
.page-hero, .post-hero {
  background: radial-gradient(circle at 24% 0%, var(--navy-mid) 0%, var(--navy-deep) 62%);
}
.page-hero { padding: 76px 0 72px; }
.page-hero-title { letter-spacing: -0.02em; }
/* The crumb becomes a quiet rule-topped line rather than loose text under the
   title, which is what made the old hero read as an unfinished box. */
.page-hero .breadcrumb, .post-hero .breadcrumb {
  display: inline-flex; align-items: center; gap: 9px; margin-top: 18px;
  padding-top: 16px; border-top: 1px solid rgba(255, 255, 255, .18);
  /* The flat band this replaced carried the crumb's .65 white at 7.3:1. On a
     gradient the light lobe is what has to clear AA, and there .65 white
     measures 4.18:1; .78 measures 5.28:1 and still reads as secondary. */
  color: rgba(255, 255, 255, .78);
}
.page-hero .breadcrumb a, .post-hero .breadcrumb a { color: rgba(255, 255, 255, .8); transition: color .2s ease; }
/* v16: the hover was --sky, which measures 2.77:1 against the hero's light
   lobe. White is the only lift available on a band this bright. */
.page-hero .breadcrumb a:hover, .post-hero .breadcrumb a:hover { color: var(--white); }
.page-hero-text { margin-top: 14px; }

/* ---------- Universities index (v14) ---------- */
/* --sky-ink is the light-background accent; on the dark hero it measured
   3.66:1 and failed AA. --sky is the token the palette reserves for dark
   surfaces and lands at 7.17:1 on the v17 --navy-deep hero. */
.up-hero .eyebrow { color: var(--sky); }
.up-hero .page-hero-title { margin-top: 4px; }
/* One fixed glyph box per feature, so three captions of different lengths
   still line their icons and text up on the same two edges. */
.up-feature-icon {
  width: 38px; height: 38px; flex-shrink: 0; display: inline-grid; place-items: center;
  font-size: 22px; line-height: 1; border-radius: 10px; background: rgba(255, 255, 255, .1);
}
/* Tabs join the pill language the rest of the site speaks (they were the only
   6px radius on the page) and the resting state gets enough presence to read
   as a control rather than an outline on white. */
.uni-tab {
  border-radius: 999px; padding: 13px 26px; font-size: 15px; background: var(--off);
  border-color: var(--line); color: var(--ink-soft);
}
.uni-tab:hover { border-color: var(--sky-ink); color: var(--sky-ink); background: var(--white); }
.uni-tab.active { background: var(--navy); border-color: var(--navy); color: var(--white); }
.uni-tab:focus-visible { outline: 2px solid var(--sky-ink); outline-offset: 2px; }
/* Between the phone grid (two columns, below) and the desktop row, four
   content-sized pills no longer fit on one line: at 800px the fourth dropped
   onto a second row on its own, at a different width from the three above it,
   which is the same accident the phone grid was written to end. Four equal
   columns keep the whole track strip on one row all the way down to the phone
   breakpoint, and the tighter padding is what buys the room. */
@media (min-width: 641px) and (max-width: 1080px) {
  .uni-tab-buttons { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
  .uni-tab { padding: 12px 14px; min-height: 52px; font-size: 14px; line-height: 1.25; }
}
/* A four-track grid holding five partners left one card alone beside three
   empty cells on every tab. Wrapping flex with a centred final row keeps the
   same four-up rhythm and makes any leftover count look deliberate. Scoped
   above the tablet breakpoint so every existing narrower grid rule still
   applies exactly as before. */
@media (min-width: 1081px) {
  .uni-grid { display: flex; flex-wrap: wrap; justify-content: center; }
  .uni-card { flex: 0 1 calc(25% - 16.5px); min-width: 0; }
}

/* ---------- University detail (v14) ---------- */
/* The hero carries the partner's mark: the same card-is-the-tile treatment as
   the mega menu and the logo marquee (13:8 tile, the mark contained inside it,
   one hairline + radius). This is what replaced the old .uni-photo, which
   letterboxed the same logo inside a padded bordered panel at 1.8x upscale,
   printed it twice on one page, and left the hero's right half empty. */
.uni-hero-inner { display: flex; align-items: center; gap: 48px; }
.uni-hero-copy { min-width: 0; flex: 1; }
.uni-hero-mark {
  /* 340px against a 520px-wide source canvas is ~1.5x of device pixels, so the
     mark still resolves crisply on a retina screen while carrying enough
     weight to balance the identity block, the way the home hero's artwork
     balances its copy. Contained, not cropped, since v19 (see .mega-uni-logo). */
  width: 340px; aspect-ratio: 13 / 8; flex-shrink: 0; overflow: hidden;
  border-radius: 14px; background: var(--white); border: 1px solid rgba(255, 255, 255, .18);
  box-shadow: 0 18px 40px rgba(0, 4, 60, .38);
}
.uni-hero-mark img { width: 100%; height: 100%; object-fit: contain; padding: 18px; display: block; }
.uni-hero-mark .uni-initial-tile { width: 100%; height: 100%; font-size: 54px; }
.uni-hero-title { margin-top: 12px; }
/* Document sections inside one column, not page-level headings: the 33px
   page scale shouted over the body copy it introduces. */
.uni-detail .section-title { font-size: clamp(22px, 2.1vw, 26px); margin-bottom: 14px; }
.uni-sub-title { margin-top: 44px; padding-top: 34px; border-top: 1px solid var(--line); }
/* 1, 2 and 4 programs all sat in a rigid three-track grid, so every university
   showed empty cells. Two tracks divide all three counts cleanly. */
.program-grid { grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); }
/* Label above value, the same shape the admissions table already takes on a
   phone. Side by side in a 330px column, label and value competed for the same
   line: ten of the fifteen partners broke BOTH, so "Language of / Instruction"
   sat beside "English / Korean / Track" ragged-right. Stacked, every label
   holds one line and every value reads as one phrase. */
.uni-facts dl div { flex-direction: column; align-items: flex-start; gap: 2px; }
.uni-facts dd { max-width: 100%; text-align: left; }
/* The stored website is a full URL with a path; printing the path right-aligned
   broke it mid-segment across two lines. The href is untouched. */
.uni-facts .uni-site { white-space: nowrap; }

/* ---------- Services page (v14) ---------- */
/* Sources run 5:3, 16:9, 3:2 and 1:1. A hard 4:3 crop cut the lettering off
   the Master's banner and blew the 335px square illustration up 1.7x. A 3:2
   box crops far less of the wide sources, and the tile treatment (radius +
   hairline + off backing) means an illustration that does not fill it reads as
   a deliberate card rather than a stretched photo. */
.svc-media {
  border-radius: var(--radius); overflow: hidden; background: var(--off);
  border: 1px solid var(--line);
}
.svc-media img { border-radius: 0; aspect-ratio: 3 / 2; object-fit: cover; display: block; }
/* The bundled EAP artwork is a 335px square whose border is a checkerboard
   baked in by a bad export (the file has no alpha at all: every pixel is
   opaque, and the outer ring is literally grey and white squares). The tile
   is tinted to the artwork's own pale sky and the image is inset past that
   damaged border, so what shows is the illustration and nothing else. A clean
   replacement asset would render identically minus the inset.
   TODO(owner): re-export service-working-visa.webp without the checkerboard. */
.svc-media--art { background: var(--sky-tint); }
.svc-media--art img { transform: scale(1.16); }
.svc-index { display: block; margin-bottom: 2px; }

/* ---------- Blog (v14) ---------- */
/* Posts with no cover painted a large empty tinted rectangle. On the index the
   box collapses instead, so a text card reads as a text card. The home page's
   blog preview shares this markup and is deliberately left as it is. */
.blog-index .blog-card-media:has(.blog-card-placeholder) { display: none; }
.pagination { gap: 10px; }
.page-link {
  border-radius: 999px; color: var(--ink-soft); font-size: 15px;
  transition: background .2s ease, border-color .2s ease, color .2s ease;
}
.page-link:focus-visible { outline: 2px solid var(--sky-ink); outline-offset: 2px; }

/* ---------- Blog post (v14) ---------- */
.post-hero { padding: 76px 0 72px; }
/* The hero ran the full container width while the article below it sits in an
   820px reading column, so a post's own title did not line up with its own
   text. One measure for the whole article. */
.post-hero-inner { max-width: 820px; }
.post-meta { display: block; }
/* An owner-uploaded cover has no known intrinsic size, so the box is reserved
   up front and the article text below it cannot jump when the image decodes. */
.post-cover { aspect-ratio: 16 / 9; object-fit: cover; background: var(--off); }
.post-back {
  border-top: 1px solid var(--line); padding-top: 24px; width: 100%;
}

/* ---------- Rich text (v14) ---------- */
/* Terms, Privacy and every owner-authored body are written as <h4> + <p>. With
   no size set they rendered at body size, so those pages were one flat wall of
   text with no hierarchy at all. */
.rich-text h2 { font-size: clamp(21px, 2vw, 24px); font-weight: 600; margin-top: 38px; }
.rich-text h3 { font-size: 19px; font-weight: 600; margin-top: 32px; }
.rich-text h4 { font-size: 17.5px; font-weight: 600; margin-top: 30px; color: var(--ink); }
.rich-text > :first-child { margin-top: 0; }
.rich-text li { margin-bottom: 6px; }

/* ---------- Legal (v14) ---------- */
/* Terms and Privacy are authored as a flat run of <h4> + <p>. Those h4s are the
   document's own section headings, so on these two pages they take a heading's
   weight and a heading's air; anywhere else in .rich-text an h4 stays a minor
   sub-heading under the h2/h3 above it. */
.legal-inner { line-height: 1.75; }
.legal-inner p { margin: 12px 0; }
.legal-inner h4 {
  font-size: clamp(19px, 1.7vw, 21px); margin-top: 46px; padding-top: 26px;
  border-top: 1px solid var(--line);
}
.legal-inner > h4:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

/* ---------- Testimonials (v14) ---------- */
/* Every other card family on the site lifts on hover; this one did not. Its
   transition moved to the MOTION SYSTEM block in v28 with the rest of them. */
/* Quotes run to different lengths, so the four names sat on four different
   baselines inside four equal-height cards. The quote takes the slack and the
   names line up along the bottom of the row. */
.testimonial-card { display: flex; flex-direction: column; }
.testimonial-text { flex: 1; }
.testimonial-card:hover { box-shadow: var(--shadow); transform: translateY(-4px); }
/* An unlit star in --line is invisible on white, so a four-star review read as
   four stars with no scale behind them. */
.star-off { color: #D5DEE4; }

/* ---------- 404 (v14) ---------- */
/* A dead end with one button. The page's whole job is to put the visitor back
   on a route, so it now offers the same destinations the header does. */
/* .error-code is a <p>, so the plain class above always lost to `.error-inner p`
   and the numeral has been painting in body grey since the port. Matching the
   containing selector is what finally lets the brand colour through. */
.error-inner .error-code { color: var(--navy); letter-spacing: -0.04em; }
.error-inner { max-width: 620px; margin: 0 auto; }
.error-links {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 10px;
  margin-top: 34px; padding-top: 30px; border-top: 1px solid var(--line);
}
.error-link {
  display: inline-flex; align-items: center; padding: 9px 20px; border-radius: 999px;
  border: 1.5px solid var(--line); font-size: 14px; font-weight: 500; color: var(--ink-soft);
  transition: border-color .18s ease, color .18s ease, background .18s ease;
}
.error-link:hover, .error-link:focus-visible { border-color: var(--sky-ink); color: var(--sky-ink); background: var(--off); }
.error-link:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(22, 115, 155, .28); }

/* ---------- Inner-page scroll reveal (v14, folded into the system in v28) ----
   Through v27 this was a ninth copy of the same eight declarations, listing
   every inner-page band by name beside seven more copies scattered up the
   file. Every one of them said exactly the same thing, so v28 says it once:
   see "MOTION SYSTEM (v28)" below. A band opts in by carrying
   data-reveal-band in the markup, which is the only list there needs to be. */

/* ---------- Why choose contrast fix (v15, re-based in v17) ---------- */
/* v15 moved this eyebrow to --sky because the band was black and --sky-ink
   measured 3.96:1 there. v17 turned the band pale, which inverts the problem:
   --sky is 2.37:1 on --sky-band and --sky-ink is 4.65:1. The fix's intent is
   unchanged — this eyebrow always takes whichever accent token clears AA on the
   band underneath it. Shared by the About page's reused .why-choose section
   (same markup, same class), so one rule still fixes both. */
.why-choose .eyebrow { color: var(--sky-ink); }

/* ==========================================================================
   MOTION SYSTEM (v28)
   --------------------------------------------------------------------------
   Everything the site moves, in one place, on one curve (--ease-out-quint),
   at one step (--reveal-step). Three rules govern all of it:

   ONE. NOTHING IS EVER HIDDEN BY CSS ALONE. The start state of a reveal is
   written under .reveal-ready, and .reveal-ready is added by js/site.js and
   only when it is about to animate. No JavaScript, an IntersectionObserver
   the browser does not have, a stylesheet that arrives without its script:
   in every one of those cases the page renders finished. That is the whole
   reason the class exists, and it is why no rule below may ever be rewritten
   to hide something from a plain selector.

   TWO. THE TRANSITION LIVES ON THE FINISHED STATE, NOT THE START STATE.
   Through v27 the start state carried the transition, so the instant JS added
   .reveal-ready an already-painted band FADED OUT over .6s before fading back
   in. Declaring the transition on .is-revealed instead makes the hide instant
   (nothing to transition from) and the show animated, which is the only order
   that cannot flash.

   THREE. THE SCROLL REVEALS ANIMATE `translate` AND `opacity`, AND NOTHING
   ELSE. Both move on the compositor, so a band arriving costs no layout and
   no repaint however many pieces it has. `translate` rather than `transform`
   so the reveal and the hover family never write to the same property; see
   the rule itself for why that matters.
   Nothing anywhere in this stylesheet animates a property that would force
   layout (a width, a height, a margin, a font size); the hover and focus
   states below do repaint, which is the right trade for something that
   happens under one cursor at a time and never while the page is scrolling.
   will-change is deliberately absent: opacity and transform are composited
   without it in every browser this site supports, and a permanent
   will-change on forty elements a page buys layers nobody needs.

   Reduced motion: everything that TRAVELS - the reveals, the hero entrance -
   sits inside a (prefers-reduced-motion: no-preference) query, so a reader
   who asks for less gets the finished page with no start state to escape
   from. The catch-all at the very foot of the stylesheet then flattens the
   hover, focus and state transitions below, which are stated unconditionally
   because a colour change is not motion.
   ========================================================================== */

@media (prefers-reduced-motion: no-preference) {
  /* --- Section reveals ------------------------------------------------- */
  /* A band opts in with data-reveal-band and marks the pieces that arrive
     with data-reveal. site.js numbers those pieces in document order and
     writes the number to --reveal-i, so the stagger is the reading order and
     no rule has to name a section, a class, or a delay ever again. */
  /* THE RISE IS `translate`, NOT `transform`, AND THAT IS THE WHOLE TRICK.
     A settled reveal has to end on a written-down finished value, and a
     descendant selector like this one out-specifies `.program-tile:hover`.
     Written as `transform: none` it therefore pinned every card in a staged
     grid flat and killed its hover lift outright - caught in the browser, not
     guessed at. The independent `translate` property is composed with
     `transform` rather than overriding it (translate is applied first, then
     rotate, then scale, then transform), so the reveal owns one channel, the
     hover family owns the other, and the two can never collide however the
     selectors line up. It composites identically. A browser too old for it
     simply fades without the rise, which is a softer effect, not a broken
     one. */
  .reveal-ready [data-reveal] { opacity: 0; translate: 0 var(--reveal-rise); }
  .reveal-ready.is-revealed [data-reveal] {
    opacity: 1; translate: none;
    transition:
      opacity var(--reveal-dur) var(--ease-out-quint),
      translate var(--reveal-dur) var(--ease-out-quint);
    transition-delay: calc(var(--reveal-i, 0) * var(--reveal-step));
  }

  /* A grid that stages its own cards hands the motion to them and keeps none
     of it: two fades laid over one another read as fog rather than as a
     cascade, and the second one dims the first at exactly the wrong moment.
     The cards continue the band's own count, so a heading, its sub-line and
     then its cards arrive as one sentence. */
  .reveal-ready [data-reveal][data-reveal-items] { opacity: 1; translate: none; }
  .reveal-ready [data-reveal-items] > * { opacity: 0; translate: 0 var(--reveal-rise); }
  .reveal-ready.is-revealed [data-reveal-items] > * {
    opacity: 1; translate: none;
    transition:
      opacity var(--reveal-dur) var(--ease-out-quint),
      translate var(--reveal-dur) var(--ease-out-quint);
    transition-delay: calc(var(--reveal-i, 0) * var(--reveal-step));
  }

  /* --- The home hero, once per load ------------------------------------ */
  /* The one thing on the site that moves without being scrolled to. It is a
     CSS animation and not a JS-driven reveal on purpose: an animation needs
     no script to finish, and the elements carry no opacity of their own, so
     the ONLY thing that can ever hide them is a running animation, for at
     most 780ms. The copy rises line by line at a 70ms step and the artwork
     eases in beside it; the whole entrance is over before a reader has
     finished the headline, which is the point. Anything slower stops being an
     entrance and starts being a wait. */
  @keyframes hero-rise {
    from { opacity: 0; transform: translateY(18px); }
    to { opacity: 1; transform: none; }
  }
  @keyframes hero-media-in {
    from { opacity: 0; transform: translateY(16px) scale(1.015); }
    to { opacity: 1; transform: none; }
  }
  .hero-copy > * { animation: hero-rise .56s var(--ease-out-quint) both; }
  .hero-copy > *:nth-child(2) { animation-delay: 70ms; }
  .hero-copy > *:nth-child(3) { animation-delay: 140ms; }
  .hero-copy > *:nth-child(4) { animation-delay: 210ms; }
  .hero-media img { animation: hero-media-in .68s var(--ease-out-quint) 100ms both; }
}

/* --- Micro-interactions ------------------------------------------------ */
/* The card family, spoken once. Every card on the site rises by 4px and
   deepens its shadow; the two picture-first strips (reviews, video stories)
   float 6px because they are bigger objects and already carry a shadow at
   rest. The named properties replace the `all` these four used to transition
   for the same reason .btn stopped: `all` animates whatever a later rule
   happens to add. */
.uni-card, .blog-card, .why-us-point, .program-card {
  transition:
    transform .28s var(--ease-out-quint),
    box-shadow .28s var(--ease-out-quint),
    border-color .28s ease;
}
.program-card:hover { transform: translateY(-4px); }
.testimonial-card { transition: transform .28s var(--ease-out-quint), box-shadow .28s var(--ease-out-quint); }
.program-tile { transition: transform .28s var(--ease-out-quint), box-shadow .28s var(--ease-out-quint), border-color .28s ease; }
.success-card, .vstories-card { transition: transform .3s var(--ease-out-quint), box-shadow .3s var(--ease-out-quint); }
.uni-marquee-card { transition: transform .28s var(--ease-out-quint), box-shadow .28s var(--ease-out-quint), border-color .28s ease; }
/* The picture inside a card moves on its own, slower than the card, which is
   what reads as depth rather than as a card that grew. */
.uni-card-media img, .blog-card-media img, .process-media img { transition: transform .55s var(--ease-out-quint); }
.uni-tab { transition: background .2s ease, border-color .2s ease, color .2s ease; }
.dropdown { transition: opacity .22s var(--ease-out-quint), transform .22s var(--ease-out-quint), visibility .22s linear; }

/* An underline that DRAWS instead of appearing. Pointer widths only: below
   981px these links are 44px-tall tap rows, and a line pinned to the bottom
   of a 44px box sits nowhere near the words it belongs to. */
@media (min-width: 981px) {
  .service-link, .blog-card-more, .footer-map-link {
    background-image: linear-gradient(currentColor, currentColor);
    background-repeat: no-repeat; background-position: 0 100%; background-size: 0 1.5px;
    transition: background-size .3s var(--ease-out-quint), color .2s ease;
  }
  .service-link:hover, .service-link:focus-visible,
  .blog-card-more:hover, .blog-card-more:focus-visible,
  .footer-map-link:hover, .footer-map-link:focus-visible {
    background-size: 100% 1.5px; text-decoration: none;
  }
}
.blog-card-title a, .footer-col a, .footer-legal a, .breadcrumb a { transition: color .2s ease; }

/* Fields answer the cursor: the border firms up and a soft sky halo opens
   around it. The halo is the focus indicator itself, so removing the browser
   outline costs nothing (it is 3px of colour where the outline would have
   been 1px of grey). The phone layer already stated this at <= 980px; it is
   the same recipe, now on every width. */
.consult-field input, .consult-field select, .consult-field textarea {
  transition: border-color .2s ease, box-shadow .2s ease;
}
.consult-field input:focus, .consult-field select:focus, .consult-field textarea:focus {
  box-shadow: 0 0 0 3px rgba(32, 168, 226, .18);
}
/* The two newsletter fields sit on navy, where a sky halo would disappear, so
   theirs is drawn in the same reduced-alpha white the panels use for rules. */
.gateway-form input, .footer-news-form input {
  transition: border-color .2s ease, box-shadow .2s ease, background .2s ease;
}
.gateway-form input:focus, .footer-news-form input:focus {
  outline: none; border-color: rgba(255, 255, 255, .55);
  background: rgba(255, 255, 255, .14); box-shadow: 0 0 0 3px rgba(255, 255, 255, .16);
}

/* ---------- Responsive ---------- */
/* Top info bar progressively sheds groups so it never wraps into a tall stack.
   The hours group now drops at 1300px rather than 1200px (v13): the white
   tile treatment on the social row is wider than the old outline circles, so
   the old threshold started crowding "Find Us On Social" at 1280px, a common
   laptop width. */
@media (max-width: 1300px) {
  .topbar-group--hours { display: none; }
}
@media (max-width: 1040px) {
  .topbar-group--address { display: none; }
  .topbar-group--phone { border-left: none; padding-left: 0; }
}
@media (max-width: 880px) {
  .topbar-group--email { display: none; }
  .topbar-social-lbl { display: none; }
  .topbar-social { padding-left: 0; border-left: none; }
}
@media (max-width: 560px) {
  /* Below iPhone width the bar sheds its padding and tightens. What it does
     NOT do any more is hide the phone group's label: v23 restored that label
     as part of the two-line lockup, and the rule that used to remove it is
     gone rather than left here to be overridden further down the file. Sizes
     for the social row are set in the v23 block in the mobile layer. */
  .topbar-inner { padding: 8px 0; gap: 10px; }
  .topbar-group { padding: 0; }
  .topbar-social { gap: 8px; }
  .topbar-social-row { gap: 6px; }
}

@media (max-width: 1080px) {
  .uni-grid { grid-template-columns: repeat(3, 1fr); }
  .process-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 980px) {
  :root { --section-pad: 72px; }
  .main-nav {
    position: fixed; inset: 0 0 0 auto; width: min(340px, 86vw); background: var(--navy-deep);
    padding: 90px 30px 30px; transform: translateX(100%); transition: transform .3s ease;
    box-shadow: var(--shadow); overflow-y: auto; margin-left: 0; z-index: 75;
  }
  .main-nav.open { transform: translateX(0); }
  .nav-list { flex-direction: column; align-items: flex-start; gap: 16px; }
  .dropdown { position: static; opacity: 1; visibility: visible; transform: none; box-shadow: none; padding-left: 14px; display: none; background: transparent; }
  .dropdown a { color: rgba(255,255,255,.8); }
  .has-dropdown.open .dropdown { display: block; }
  /* Desktop-only mega menu: on mobile "Partners Universities" is a plain link. */
  .mega { display: none; }
  .mega-caret { display: none; }
  .hamburger { display: flex; z-index: 80; }
  .brand-logo { height: 40px; }
  .hero-inner { grid-template-columns: 1fr; padding: 52px 0; }
  .hero-media { display: none; }
  .hero::before { background: rgba(255, 255, 255, .9); }
  .stats-band { grid-template-columns: 1fr 1fr; }
  .stat:nth-child(2n) { border-right: none; }
  .why-inner { grid-template-columns: 1fr; gap: 36px; }
  .gateway-inner { grid-template-columns: 1fr; gap: 36px; }
  .uni-grid { grid-template-columns: repeat(2, 1fr); }
  .blog-grid { grid-template-columns: 1fr; }
  .consult-band-inner { flex-direction: column; align-items: flex-start; gap: 26px; }
  /* .has-map repeated here: it is two classes (0,0,2,0) so it would otherwise
     outrank the single-class .footer-grid rule above regardless of media
     query, keeping the wide desktop track count on tablet. */
  .footer-grid, .footer-grid.has-map { grid-template-columns: 1fr 1fr; }
  /* The map becomes a full-width band under the two-column link grid instead
     of an orphaned lone cell in a 5-into-2 layout. */
  .footer-grid.has-map .footer-col-map { grid-column: 1 / -1; }
  .footer-map iframe { height: 210px; }
  .service-row { grid-template-columns: 1fr; gap: 8px; }
  .leader-row, .svc-section { grid-template-columns: 1fr; gap: 28px; }
  .why-us-grid, .steps-grid { grid-template-columns: 1fr 1fr; }
  .consult-inner { grid-template-columns: 1fr; }
  .consult-grid { grid-template-columns: 1fr; }
  .uni-detail-inner { grid-template-columns: 1fr; }
  .uni-facts { position: static; }
  .program-grid { grid-template-columns: 1fr; }
}
/* The Why-choose checklist drops from two columns to one on small screens. */
@media (max-width: 720px) {
  .check-list { grid-auto-flow: row; grid-template-columns: 1fr; grid-template-rows: none; }
}
@media (max-width: 560px) {
  .uni-grid { grid-template-columns: 1fr; }
  .process-grid { grid-template-columns: 1fr; }
  .footer-grid, .footer-grid.has-map { grid-template-columns: 1fr; }
  .footer-map iframe { height: 220px; }
  .team-grid { grid-template-columns: 1fr 1fr; }
  .why-us-grid, .steps-grid { grid-template-columns: 1fr; }
  .uni-marquee-card { width: 130px; margin-right: 12px; }
}

/* ==========================================================================
   MOBILE-FIRST REIMAGINING (v12)
   --------------------------------------------------------------------------
   Everything below lives at <= 1023px, so the desktop site (>= 1024px) that
   the owner signed off is byte-for-byte untouched. The layer is ordered
   foundations -> chrome -> sections -> pages, and each breakpoint means one
   thing:
     <= 1023px  tablet + phone envelope (typography, Korean word wrapping)
     <=  980px  the drawer breakpoint the site already switches nav at
     <=  640px  phone: one spacing scale, one type scale, stacked patterns
     <=  560px  small phone: densities that only make sense below iPhone width
   ========================================================================== */

/* ---------- Foundations ---------- */
@media (max-width: 1023px) {
  /* Hangul has no inter-word breaking by default, so headlines split mid-word
     (한국 유|학의). keep-all breaks at spaces like Latin text does; the
     break-word companion keeps a single very long token from overflowing. */
  html:lang(ko) body,
  html:lang(ko) .section-title,
  html:lang(ko) h1, html:lang(ko) h2, html:lang(ko) h3, html:lang(ko) h4 {
    word-break: keep-all;
    overflow-wrap: break-word;
  }
}

@media (max-width: 980px) {
  :root { --section-pad: 64px; }
  /* The pale-wash closing padding follows --section-pad down, keeping the same
     ratio it holds on the desktop (a little over half a full section pad), so
     the seam is tightened on a phone exactly as deliberately as on a laptop and
     never crushed to nothing. See "Section rhythm" in the desktop layer. */
  :root { --wash-close: 38px; }
  /* MOTION IS CALMER ON A SMALL SCREEN (v28). A phone shows one column, so a
     20px rise that reads as a settle on a 1440px page reads as a lurch when it
     is the only thing on screen, and a 70ms step between four stacked cards
     turns a cascade into a queue. The distance comes down to 14px and the step
     to 50ms; the curve and the duration are the site's, unchanged, so the
     motion is the same language spoken more quietly. Tokens only - not one
     motion RULE lives in this layer, so the >= 1024px page cannot move. */
  :root { --reveal-rise: 14px; --reveal-step: 50ms; }
  /* Sections that hardcode their own padding are pulled onto the same scale so
     the page has one vertical rhythm instead of six competing ones. */
  .process { padding: var(--section-pad) 0; }
  .consult-band { padding: var(--section-pad) 0; }
  .page-hero, .post-hero, .since-band { padding: 56px 0; }
  .uni-hero { padding: 56px 0; }
  .up-hero { padding: 56px 0 48px; }
  .up-hero + .partners { padding-top: 48px; }
  .dream, .footer-cta { padding: 40px 0; }
  .consult-cta-band { padding: 40px 0; }
  .error-page { padding: 80px 0; }
  .leadership-list { gap: 48px; }
  .svc-section { padding: 40px 0; scroll-margin-top: 84px; }
  .post-body-wrap { padding: 40px 0 var(--section-pad); }
}

@media (max-width: 640px) {
  :root { --section-pad: 56px; --wash-close: 32px; }
  /* A deliberate 20px phone gutter instead of a percentage that drifts with
     the viewport. Border-box keeps every .container-based flex/grid intact.
     THE GUTTER IS THE SYSTEM (v23). Every rule below this line that sets
     vertical padding on a .container element writes it as `padding-block`,
     never as the `padding` shorthand: a shorthand sets the horizontal
     longhands too, and because those rules sit further down the file they were
     silently zeroing this gutter. That is exactly what put the header logo,
     the hamburger, the whole hero and the footer's copyright row hard against
     the edge of the phone while every section between them was inset by 20 —
     the misalignment the owner photographed. */
  .container { width: 100%; padding-inline: 20px; }

  /* Type floor: nothing below 14px, body copy at 16px, comfortable leading. */
  body { line-height: 1.6; }
  .service-text, .different-text, .faq-answer-text, .svc-text,
  .why-us-point p, .testimonial-text, .partners-sub, .why-us-text,
  .leader-text, .dream-sub, .consult-cta-sub, .gateway-text,
  .why-text, .page-hero-text, .success-intro-text, .hero-subtitle,
  .programs-text { font-size: 16px; }
  .site-footer { font-size: 15px; }
  .process-text, .step-text, .blog-card-excerpt, .stat-label,
  .uni-card-city, .team-role, .blog-card-meta, .ring-label,
  .gateway-rating-label, .footer-news-note, .footer-cta-mail-label,
  .consult-hint, .up-feature p, .up-stat span, .uni-stat span,
  .breadcrumb, .post-meta, .success-quote + .success-intro-text { font-size: 14px; }
  .ring-unit { font-size: 14px; }
  .consult-field label { font-size: 15px; }

  /* Heading scale for a 390px screen: deliberate steps, not squeezed desktop.
     Each one is fluid down to a 320px floor, because at fixed sizes the long
     headlines gained a whole extra line on the smallest phones.

     v23 SPREAD THE SCALE. It used to run 30 / 25 / 22 / 16, and a 30px hero
     over a 25px section title is a five-pixel difference nobody reads as a
     difference: the home page looked like six headings of the same rank
     stacked down a column. It now runs, at 390px:

       33   the page's one h1 (the home hero)
       29   an inner page's h1, one clear step under it
       24   a section title, and every band heading, which is the same rank
       21   the closing-band and item headings
       19   a list row's title
       16   body

     Every neighbouring pair is at least a 1.14x step and the pair the eye
     actually compares — hero against section title — is 1.375x. */
  .hero-title { font-size: clamp(29px, 8.4vw, 33px); line-height: 1.16; letter-spacing: -0.03em; }
  .section-title { font-size: clamp(21px, 6vw, 24px); line-height: 1.24; }
  .page-hero-title { font-size: clamp(25px, 7.4vw, 29px); }
  .consult-band-title, .gateway-title, .uni-hero-title,
  .post-title, .since-title, .success-title { font-size: clamp(21px, 6vw, 24px); line-height: 1.24; }
  .footer-cta-title, .cta-band-title, .dream-title, .consult-cta-title { font-size: clamp(19px, 5.4vw, 21px); line-height: 1.3; }
  .svc-title, .leader-heading { font-size: clamp(19px, 5.4vw, 21px); }
  .service-title { font-size: 19px; }
  .faq-question-text { font-size: 16px; }

  /* Buttons become thumb-shaped: full width, one per row, generous height. */
  .hero-ctas, .hero-ctas .btn { width: 100%; }
  .btn { min-height: 52px; }
}

/* ---------- Top info bar: the desktop bar, miniaturised (v23) ---------- */
@media (max-width: 560px) {
  /* v12 stripped this bar down to one centred phone number with no label and
     nothing beside it, which is how the owner found it: a number adrift in the
     middle of a dark strip, with no reason for the strip to be there. v23 puts
     the SAME shape the desktop bar has on the phone. Left: the phone group's
     own two-line lockup — glyph, "Request a Call", the number — exactly as it
     reads at 1440. Right: the social tiles, which are a fixed-size object and
     therefore belong on the far edge. Both blocks now sit on the container's
     20px gutter, so the strip lines up with every section under it.
     v12 hid the tiles because five fixed 26px circles beside a phone number
     overflowed at 320px. They no longer are fixed: see the shrink floor below,
     which is what lets them come back and still fit six networks on a 360px
     screen. */
  .topbar { padding-top: env(safe-area-inset-top); }
  /* padding-BLOCK, not the shorthand: the shorthand would zero the .container
     gutter this bar now sits on. */
  .topbar-inner { padding-block: 5px; gap: 12px; justify-content: space-between; align-items: center; }
  /* An owner who clears every social link leaves the bar carrying one block,
     and one block belongs in the middle rather than adrift against one edge. */
  .topbar-inner:not(:has(.topbar-social)) { justify-content: center; }
  .topbar-groups { flex-wrap: nowrap; justify-content: flex-start; }
  .topbar-group { padding: 0; }
  .topbar-group--phone { min-height: 44px; gap: 10px; }
  .topbar-group--phone .topbar-lbl { display: block; font-size: 10.5px; }
  .topbar-val { font-size: 13.5px; }
  .topbar-ic svg { width: 17px; height: 17px; }
  .topbar-social {
    display: flex; flex-direction: row; align-items: center;
    flex: 0 1 auto; min-width: 0; gap: 0; padding: 0; border-left: 0;
  }
  .topbar-social-lbl { display: none; }
  .topbar-social-row { gap: 6px; min-width: 0; }
  /* The tile holds 28px while there is room and gives way to a 22px floor when
     there is not — a sixth network, or a 360px screen. Six tiles at the floor
     plus their gaps measure 162px, which is what is left over beside the phone
     lockup at 360px, so the row can never wrap and can never push the page
     sideways. The height follows the width rather than being set, so a tile
     that shrinks stays square. */
  .topbar-social-ic {
    width: auto; height: auto; flex: 0 1 28px; min-width: 22px;
    aspect-ratio: 1; border-radius: 8px;
  }
  .topbar-social-ic svg { width: 55%; height: 55%; }
}
@media (max-width: 980px) and (min-width: 561px) {
  /* Tablet keeps both groups; the icon tile is already a real touch size
     (34px) at every width down to here, so there is nothing to grow. */
  .topbar-group--phone { min-height: 44px; }
}

/* ---------- Header: compact, stable, safe-area aware ---------- */
@media (max-width: 980px) {
  .site-header { padding-top: env(safe-area-inset-top); }
  /* padding-BLOCK (v23). Written as the `padding` shorthand this rule also set
     the horizontal longhands to zero, and because it sits below the .container
     gutter rule it won: the wordmark and the hamburger were pinned to the two
     edges of the phone while every section under them was inset by 20px. That
     is the misalignment the owner photographed. */
  .header-inner { padding-block: 10px; gap: 16px; }
  /* The logo is the home link; a 34px-tall mark is not a 44px target. */
  .brand { display: inline-flex; align-items: center; min-height: 44px; }
  .brand-logo { height: 34px; }
  /* v29: the flags stay in the phone header, which is where the owner asked for
     them. They take the free space in the middle of the bar (the margin the
     hamburger used to hold), so the wordmark keeps the left gutter and the
     hamburger keeps the right one exactly as v23 set them, and the control
     itself only tightens: 21x14 flags in 30x24 cells rather than 24x16 in 34x26.
     The bar's own height is untouched - the picker is 30px inside a row the
     44px brand and hamburger already define. */
  .header-lang { margin-left: auto; gap: 3px; padding: 3px; border-radius: 9px; }
  .header-lang .lang-flag { width: 30px; height: 24px; border-radius: 6px; }
  .header-lang .lang-flag .flag-svg { width: 21px; height: 14px; }
  /* The cell is 30x24 for the eye and 32x44 for the thumb. The same trick the
     top bar's social tiles use: the target is grown with a pseudo-element that
     takes no part in layout, vertically into the header's own padding where
     there is room, and horizontally only as far as the half-gap, so two
     neighbouring targets never overlap. The drawer copy of the picker is a full
     56px row, one tap away. */
  .header-lang .lang-flag::after { content: ""; position: absolute; inset: -10px -1px; }
  .hamburger {
    width: 44px; height: 44px; gap: 5px; padding: 0 9px; border-radius: 10px;
    align-items: stretch; justify-content: center;
    /* The control is 44px wide for the thumb, but its BARS are what the eye
       aligns, and they sit 9px inside it. Pulling the box that same 9px into
       the gutter lands the bars exactly on the content edge the wordmark
       starts from, so the header reads as one measure rather than two. */
    margin-right: -9px;
  }
  .hamburger span { transform-origin: center; }
  /* The bars fold into a cross while the panel is open, so the control always
     shows what it will do next. */
  .hamburger[aria-expanded="true"] span:nth-child(1) { transform: translateY(7.5px) rotate(45deg); }
  .hamburger[aria-expanded="true"] span:nth-child(2) { opacity: 0; transform: scaleX(.4); }
  .hamburger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7.5px) rotate(-45deg); }
}

/* ---------- Navigation drawer ---------- */
/* The panel's own chrome (brand row, close control, action footer) and its
   scrim exist only below the drawer breakpoint. At desktop widths .main-nav is
   the plain horizontal bar it has always been. */
.nav-drawer-top, .nav-actions, .nav-backdrop { display: none; }

@media (max-width: 980px) {
  .nav-backdrop {
    display: block; position: fixed; inset: 0; z-index: 70;
    background: rgba(0, 4, 60, .62);
    opacity: 0; visibility: hidden; pointer-events: none;
    transition: opacity .34s ease, visibility .34s ease;
  }
  .nav-backdrop.is-visible {
    opacity: 1; visibility: visible; pointer-events: auto;
    touch-action: none;
    -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  }
  /* Scroll lock while the panel is open. overflow:hidden stops wheel and key
     scrolling; the scrim's touch-action:none stops the finger drag on iOS.
     Nothing is repositioned, so the header cannot jump. */
  html.nav-open, body.nav-open { overflow: hidden; }

  .main-nav {
    position: fixed; inset: 0 0 0 auto; width: min(400px, 90vw);
    background: var(--navy-deep); z-index: 75; margin-left: 0;
    padding: env(safe-area-inset-top) 0 0;
    display: flex; flex-direction: column; overflow: hidden;
    transform: translateX(100%); visibility: hidden;
    box-shadow: -24px 0 64px rgba(0, 4, 60, .55);
    transition: transform .38s cubic-bezier(.32, .72, 0, 1), visibility .38s linear;
  }
  .main-nav.open { transform: translateX(0); visibility: visible; }

  .nav-drawer-top {
    display: flex; align-items: center; justify-content: space-between;
    gap: 16px; height: 64px; flex-shrink: 0; padding: 0 12px 0 24px;
    border-bottom: 1px solid rgba(255, 255, 255, .1);
  }
  .nav-drawer-label {
    color: rgba(255, 255, 255, .55); font-size: 12px; font-weight: 600;
    letter-spacing: .16em; text-transform: uppercase;
  }
  /* The panel and its scrim now own the whole screen, so the header's own
     hamburger must not float above them as a second close mark. */
  .hamburger { z-index: auto; }
  .nav-close {
    position: relative; width: 44px; height: 44px; flex-shrink: 0;
    background: none; border: 0; border-radius: 10px; cursor: pointer;
  }
  .nav-close span {
    position: absolute; left: 12px; top: 50%; width: 20px; height: 2.5px;
    background: var(--white); border-radius: 2px;
  }
  .nav-close span:first-child { transform: translateY(-50%) rotate(45deg); }
  .nav-close span:last-child { transform: translateY(-50%) rotate(-45deg); }
  .nav-close:hover span, .nav-close:focus-visible span { background: var(--sky); }
  .nav-close:focus-visible { outline: 2px solid var(--sky); outline-offset: -2px; }

  /* The drawer states its own ink rather than inheriting the header's. Since
     v19 the header is white-on-sky too, so this reads as a repeat, but the
     drawer's copy sits on --navy-deep and must not follow the header if that
     field is ever re-toned again. The section label sits at .55, which
     measures 6.04:1 on this band. */
  .nav-list, .nav-list a { color: var(--white); }
  .nav-list {
    flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
    flex-direction: column; align-items: stretch; gap: 0;
    padding: 8px 0 16px; text-transform: none; font-size: 17px;
  }
  .nav-list > li { border-bottom: 1px solid rgba(255, 255, 255, .07); }
  .nav-list > li:last-child { border-bottom: 0; }
  .nav-list > li > a {
    display: flex; align-items: center; min-height: 56px;
    padding: 8px 24px; font-size: 17px; font-weight: 500; letter-spacing: 0;
  }
  /* The drawer marks the current page with --sky on its dark panel, which is a
     loud enough signal on its own; the header's underline rule belongs to the
     sky field and would only crowd a row that already has its own divider.
     Since v19 that rule paints white, so the hover half of it is cancelled
     here too rather than left to draw a bar across a drawer row. */
  .nav-list > li > a.active { color: var(--sky); box-shadow: none; }
  .nav-list > li > a:hover { box-shadow: none; }
  .nav-list > li > a:active { background: rgba(255, 255, 255, .06); }
  .caret { font-size: 15px; margin-left: auto; opacity: .55; transition: transform .25s ease, opacity .25s ease; }
  .has-dropdown.open > a .caret { transform: rotate(180deg); opacity: 1; color: var(--sky); }
  .dropdown { padding: 0 0 8px; }
  .dropdown a {
    display: flex; align-items: center; min-height: 48px;
    padding: 6px 24px 6px 40px; font-size: 15px; color: rgba(255, 255, 255, .72);
  }
  .dropdown a:hover, .dropdown a:active { background: rgba(255, 255, 255, .06); color: var(--sky); }

  .nav-actions {
    display: flex; flex-direction: column; gap: 12px; flex-shrink: 0;
    padding: 16px 24px calc(20px + env(safe-area-inset-bottom));
    border-top: 1px solid rgba(255, 255, 255, .1);
  }
  .nav-drawer-cta { width: 100%; min-height: 52px; }

  /* The drawer's copy of the picker. It exists because the open panel covers the
     header on a phone, and a visitor who cannot read the menu must still be able
     to leave it. Here there is room to say the words, so each flag carries its
     language's own name under it: three equal columns, full width of the footer,
     a 56px row apiece. The track is a whisper of white on the navy panel rather
     than the header's brighter one, and the chip under the current language is
     the same solid white it is upstairs, so the two copies read as one control
     seen twice. */
  .lang-picker-in-nav {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px;
    padding: 4px; border-radius: 14px;
    background: rgba(255, 255, 255, .06);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .12);
  }
  /* min-width: 0 rather than the grid item's default auto: it is what lets the
     three columns stay equal 1fr thirds instead of a long endonym widening its
     own track and pushing the picker past the panel. The name wraps inside its
     cell if a future language brings a longer one; today none of the three do. */
  .lang-picker-in-nav .lang-flag {
    width: auto; height: auto; min-width: 0; min-height: 56px; border-radius: 10px;
    flex-direction: column; gap: 7px; padding: 9px 4px;
  }
  .lang-picker-in-nav .lang-flag .flag-svg { box-shadow: 0 0 0 1px rgba(255, 255, 255, .22); }
  .lang-picker-in-nav .lang-flag.is-active .flag-svg { box-shadow: 0 0 0 1px rgba(0, 4, 60, .18); }
  .lang-flag-name {
    font-size: 12px; font-weight: 500; line-height: 1.2; letter-spacing: 0;
    color: var(--white); text-align: center;
  }
  /* The picker names every language in its own words, so an English page has to
     draw "한국어" and a Korean page has to draw "Tiếng Việt" - and in each case the
     page's own font can draw neither. Poppins has no Hangul at all and stops short
     of the Vietnamese vowel block (latin-ext ends at U+1E9F and resumes at U+1EF2,
     leaving U+1EA0-U+1EF1 empty); Noto Sans KR has no Vietnamese; Be Vietnam Pro
     has no Hangul. Left to itself the browser picks a fallback PER CHARACTER, so
     "Tiếng Việt" comes out with its ế and its ệ borrowed from a stranger and the
     rest in the page face: one word, two typefaces.
     Each endonym therefore names its own stack. The self-hosted face leads, and
     wins on that language's own pages, where its stylesheet is the one loaded. On
     every OTHER page that face is not declared at all, so it cannot resolve and
     the label falls WHOLE to the next entry, which is the face that platform
     actually ships for the script (Windows, macOS/iOS, then the generic, which is
     Roboto/Noto on Android). Either way the label is set in one typeface, which is
     the only thing that matters here, and no page downloads a font for three
     words. */
  .lang-flag-name:lang(ko) { font-family: "Noto Sans KR", "Apple SD Gothic Neo", "Malgun Gothic", sans-serif; }
  .lang-flag-name:lang(vi) { font-family: "Be Vietnam Pro", "Segoe UI", "Helvetica Neue", Arial, sans-serif; }
  .lang-picker-in-nav .lang-flag.is-active .lang-flag-name { color: var(--navy-deep); }
  .lang-picker-in-nav .lang-flag:focus-visible { outline-color: var(--sky); }
}

/* The panel itself slides; its rows follow in a short stagger so opening reads
   as one motion rather than a block appearing. Reduced motion gets neither. */
@media (max-width: 980px) and (prefers-reduced-motion: no-preference) {
  .main-nav .nav-list > li, .main-nav .nav-actions {
    opacity: 0; transform: translateX(16px);
    transition: opacity .34s ease, transform .34s cubic-bezier(.32, .72, 0, 1);
  }
  .main-nav.open .nav-list > li, .main-nav.open .nav-actions { opacity: 1; transform: none; }
  .main-nav.open .nav-list > li:nth-child(1) { transition-delay: .10s; }
  .main-nav.open .nav-list > li:nth-child(2) { transition-delay: .15s; }
  .main-nav.open .nav-list > li:nth-child(3) { transition-delay: .20s; }
  .main-nav.open .nav-list > li:nth-child(4) { transition-delay: .25s; }
  .main-nav.open .nav-list > li:nth-child(5) { transition-delay: .30s; }
  .main-nav.open .nav-list > li:nth-child(6) { transition-delay: .35s; }
  .main-nav.open .nav-actions { transition-delay: .40s; }
  .hamburger span { transition: transform .28s cubic-bezier(.32, .72, 0, 1), opacity .2s ease; }
}
@media (max-width: 980px) and (prefers-reduced-motion: reduce) {
  .main-nav { transition: none; }
  .nav-backdrop { transition: none; }
  .hamburger span, .caret { transition: none; }
}

/* ---------- Hero: a composed first screen ---------- */
@media (max-width: 980px) {
  /* The wash lifts off the artwork towards the bottom so the skyline reads
     behind the call to action instead of the hero being a flat white box. */
  .hero::before {
    background: linear-gradient(180deg, rgba(255, 255, 255, .96) 0%, rgba(255, 255, 255, .92) 46%, rgba(255, 255, 255, .58) 100%);
  }
  /* padding-BLOCK, for the reason spelled out on .header-inner above: written
     as the shorthand this rule zeroed the phone gutter, and the whole hero —
     chip, headline, paragraph and button — ran edge to edge while everything
     below it was inset. */
  .hero-inner { padding-block: 32px 30px; gap: 0; }
  .hero-subtitle { margin-bottom: 24px; max-width: none; }
  .hero-title { margin: 4px 0 14px; }
  /* The brand illustration comes back, sized off its width so it reads as the
     subject of the hero rather than a stamp, and sitting clear of the section
     edge instead of running into it. */
  .hero-media { display: block; margin-top: 24px; }
  .hero-media img {
    margin: 0 auto; width: min(280px, 72%); height: auto; max-width: 100%;
  }
}
@media (max-width: 640px) {
  .hero-inner { padding-block: 24px 26px; }
  .hero-media { margin-top: 22px; }
  .hero-media img { width: min(248px, 66%); }
  .chip { font-size: 14px; padding: 7px 16px; margin-bottom: 14px; }
}

/* ---------- Swipeable strips (logo marquee + student reviews) ---------- */
/* On a phone an auto-drifting row fights the finger. Both strips become real
   scroll-snap carousels: full-bleed to the screen edge, momentum scrolling,
   one card per snap, and a soft edge fade that says "there is more". */
@media (max-width: 980px) {
  .uni-marquee-row, .success-strip, .vstories-strip {
    overflow-x: auto; overflow-y: hidden;
    scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain; scrollbar-width: none;
    margin-inline: -20px; padding-inline: 20px; scroll-padding-inline: 20px;
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 20px, #000 calc(100% - 30px), transparent 100%);
    mask-image: linear-gradient(to right, transparent 0, #000 20px, #000 calc(100% - 30px), transparent 100%);
  }
  .uni-marquee-row::-webkit-scrollbar, .success-strip::-webkit-scrollbar, .vstories-strip::-webkit-scrollbar { display: none; }
  .uni-marquee-track, .success-track, .vstories-track { animation: none; }
  .uni-marquee-set:not(:first-child), .success-set:not(:first-child), .vstories-set:not(:first-child) { display: none; }
  .uni-marquee-card, .success-card, .vstories-card { scroll-snap-align: center; }
  .uni-marquee-card:hover, .success-card:hover, .vstories-card:hover { transform: none; }
  /* The desaturation is a hover effect, and touch has no hover: on a phone it
     would leave every partner logo permanently dimmed with no way back. */
  .uni-marquee-card img { filter: none; }
  .success-strip { border-radius: 0; }
  .success-card { box-shadow: 0 8px 22px rgba(0, 4, 60, .09); }
}
@media (max-width: 640px) {
  /* Cards sized to actually read on a phone: a logo you can identify and a
     review card you can see the face and the stars on. */
  .uni-marquee-row { padding-block: 8px; }
  .uni-marquee-card { width: 168px; margin-right: 14px; }
  .success-inner { --ss-card-h: 340px; }
  .success-card { margin-right: 14px; }
  /* The intro card's min-height exists to make it exactly as tall as the review
     cards BESIDE it. Stacked on a phone there is nothing beside it, so all that
     floor does is hold a 340px box around four lines of type and open a hole
     above and below them. It sizes to its own contents here (v23). */
  .success-intro { min-height: 0; }
  /* One video card, nearly the full phone width, so the thumbnail and its
     caption are actually readable rather than three cropped stamps. */
  .vstories-card { width: min(300px, 78vw); margin-right: 14px; }
  .vstories-play { width: 46px; height: 46px; }
  .vstories-play svg { width: 46px; height: 46px; }
  .vstories-label { padding: 30px 14px 12px; font-size: 14px; }
}
@media (max-width: 560px) {
  /* Overrides the older small-phone shrink now that the row is swipeable. */
  .uni-marquee-card { width: 158px; margin-right: 14px; }
}

/* ---------- Popular Programs on tablets and phones (v22) ---------- */
@media (max-width: 980px) {
  .programs-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
}
@media (max-width: 640px) {
  /* Two across on a phone. The tile keeps its centred column, only tighter: a
     62px disc in a ~165px cell leaves the label nowhere to breathe. */
  .programs-grid { gap: 12px; }
  .program-tile { gap: 12px; padding: 22px 14px; }
  .program-ic { width: 50px; height: 50px; }
  .program-ic svg { width: 25px; height: 25px; }
  .program-label { font-size: 15.5px; }
  .program-go { font-size: 15.5px; }
  /* Nine tiles into two columns leaves the ninth alone in the last row, and a
     lone sky tile beside an empty cell reads as a mistake rather than as a
     destination. The "explore all" tile takes the whole row instead and lies
     down into a bar, which is what it always was: the section's way out. It is
     the tile the owner marks as the feature one, wherever it sits in the list,
     so the rule follows the flag rather than counting to nine. */
  .program-tile-feature {
    grid-column: 1 / -1; flex-direction: row; justify-content: center;
    gap: 14px; padding: 18px 20px;
  }
  .program-tile-feature .program-ic { width: 42px; height: 42px; }
  .program-tile-feature .program-ic svg { width: 22px; height: 22px; }
}

/* ---------- Comparison table becomes one card per service ---------- */
/* The scrolling table hid the "Near Korea Education" column off-screen with no
   affordance, so the section argued the opposite of its point. On a phone each
   service is now a card with both verdicts labelled and visible at once. */
@media (max-width: 640px) {
  .compare-table-wrap {
    overflow: visible; background: none; border: 0; border-radius: 0; box-shadow: none;
  }
  .compare-table { min-width: 0; display: block; font-size: 16px; }
  .compare-table thead { display: none; }
  .compare-table tbody { display: flex; flex-direction: column; gap: 12px; }
  .compare-table tbody tr {
    display: block; background: var(--white); border: 1px solid var(--line);
    border-radius: 16px; overflow: hidden; box-shadow: 0 8px 24px rgba(0, 4, 60, .06);
  }
  .compare-table tbody th.compare-service {
    display: block; position: static; min-width: 0; border-top: 0;
    padding: 14px 16px 11px; font-size: 16.5px; font-weight: 600; line-height: 1.35;
    color: var(--ink); box-shadow: none; background: none;
  }
  .compare-table tbody td.compare-cell {
    display: flex; align-items: center; justify-content: space-between; gap: 16px;
    padding: 10px 16px; border-top: 1px solid var(--line); text-align: left;
  }
  .compare-table tbody td.compare-cell::before {
    content: attr(data-label); font-size: 14.5px; color: var(--grey); font-weight: 500;
  }
  .compare-table tbody td.compare-cell-us::before { color: var(--navy); font-weight: 600; }
  .compare-cell-us { background: rgba(32, 168, 226, .07); }
  .compare-mark svg { width: 22px; height: 22px; }
}

/* ---------- Rings, stats, cards ---------- */
@media (max-width: 640px) {
  /* flex-basis 0 (not a percentage) is what guarantees the three rings share
     one row exactly; percentages plus gaps rounded past 100% and wrapped. */
  .ring-stats { margin-top: 32px; gap: 10px; flex-wrap: nowrap; }
  .ring-stat { flex: 1 1 0; width: auto; min-width: 0; }
  .ring { width: min(94px, 100%); height: auto; aspect-ratio: 1; }
  .ring-num { font-size: 22px; }
  /* Equal label boxes keep the three rings on one baseline whether a label
     runs to one line or two. */
  .ring-label { margin-top: 12px; max-width: none; min-height: 2.6em; }
  /* Desktop caps these measures for a balanced two/three-line wrap in a wide
     column; on a 350px column the same caps force needless extra lines. */
  .different-title, .compare-title, .faq-title { max-width: none; }
  .stats-band { padding: 28px 22px; gap: 24px 12px; }
  .stat-value { font-size: 24px; }
  /* Two across reads as a set; one across made four steps feel like four
     pages. Same for the about-page point and step grids. */
  .process-grid, .why-us-grid, .steps-grid { grid-template-columns: 1fr 1fr; gap: 16px; }
  .process-media { aspect-ratio: 4 / 3.2; margin-bottom: 12px; }
  .process-title { font-size: 16.5px; margin-bottom: 6px; }
  .why-us-point, .step-card { padding: 20px 16px; }
  .why-us-num { font-size: 20px; margin-bottom: 6px; }
  .step-title { font-size: 17px; }
  /* ---- Team cards: give the face back its photograph (v23) ----
     On a phone this card was 161x193 and the scrim over the bottom of it was
     114px: fifty-nine per cent of the picture, painted over the person it is
     a picture of. Three things fix it, and each one is measured rather than
     eyeballed.

     ONE. The card takes the 3:4 a headshot is actually shot at, which is 21px
     of extra height at no cost to the layout.
     TWO. The text block is tightened — a shorter opening pad, real heading
     leading instead of the 1.6 body line-height, and no dead margin under the
     role when the member has no social links to put under it. The scrim is the
     height of its own contents, so tightening the contents IS shortening the
     scrim: 114px becomes 81px, and 59% of the card becomes 38%.
     THREE. The gradient stops are anchored in PIXELS from the top of the
     scrim, not in percentages of it. That is what makes the fade the same
     34px band whether the member carries three social links or none, instead
     of a ramp that stretches with the box and drops the name into the weak
     end of it. Under the name the scrim is .84 of --navy-deep, which measures
     8.9:1 for white even in the worst case the site can produce (a blown-out
     white photograph directly behind it). */
  .team-grid { gap: 14px; }
  .team-media { aspect-ratio: 3 / 4; }
  .team-overlay {
    padding: 30px 13px 12px;
    background: linear-gradient(to top,
      rgba(0, 4, 60, .93) 0,
      rgba(0, 4, 60, .89) calc(100% - 34px),
      rgba(0, 4, 60, .52) calc(100% - 11px),
      rgba(0, 4, 60, 0) 100%);
  }
  .team-name { font-size: 16.5px; line-height: 1.28; letter-spacing: -0.01em; }
  .team-role { line-height: 1.35; opacity: .84; margin-bottom: 0; }
  .team-socials { margin-top: 9px; }
  /* An owner who fills in no links still renders the row, so :empty cannot see
     it through the whitespace. :has(a) can. */
  .team-socials:not(:has(a)) { display: none; }
  .team-socials a { width: 36px; height: 36px; }
  /* Content-sized pills wrapped 2 + 1 and read as an accident. An equal grid
     shows every track at once, aligned, and each cell is a comfortable target.
     v21 took it from three columns to TWO: with a fourth track, three across
     wrapped to 3 + 1 and was the same accident again, while two rows of two
     is a block, and the wider cell holds "Master's Programs" on one line. */
  .uni-tab-buttons { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 24px; }
  .uni-tab { min-height: 54px; padding: 10px 6px; font-size: 14px; line-height: 1.25; }
  .uni-card-title { font-size: 17px; }
  .blog-card-body { padding: 18px 18px 20px; }
  .blog-card-title { font-size: 19px; }
}
@media (max-width: 560px) {
  /* Restores a real two-up rhythm that the older rules flattened to one. */
  .uni-grid, .process-grid, .why-us-grid, .steps-grid { grid-template-columns: 1fr 1fr; }
  .uni-card-title { font-size: 15.5px; }
  .uni-card-body { padding: 12px 12px 14px; }
}

/* ---------- Tap-target floor for text link rows ---------- */
@media (max-width: 980px) {
  .service-row { position: relative; padding: 22px 0; grid-template-columns: 1fr; gap: 6px; }
  .service-num { font-size: 14px; letter-spacing: .08em; }
  /* The whole row becomes the target; the visible link stays as the affordance. */
  .service-link::after { content: ""; position: absolute; inset: 0; }
  .service-link { display: inline-flex; align-items: center; min-height: 44px; }
  .blog-card-more, .post-back, .footer-map-link { display: inline-flex; align-items: center; min-height: 44px; }
  .footer-col ul li { margin-bottom: 0; }
  /* padding keeps two-line labels from butting into their neighbours once the
     rows are sized for thumbs. */
  .footer-col ul a { display: flex; align-items: center; min-height: 44px; padding: 7px 0; line-height: 1.35; }
  .consult-contact ul li { line-height: 1.3; }
  .consult-contact ul a { display: inline-flex; align-items: center; min-height: 44px; }
  /* The university's own website is the one link inside Quick Facts, and at
     19px tall it was the smallest target on the site (v23). */
  .uni-facts dd a { display: inline-flex; align-items: center; min-height: 44px; }
  .page-link { width: 46px; height: 46px; }
  .pagination { gap: 10px; }
  /* Grows the hit area of the top-bar icons without fattening the bar, and the
     same trick on the breadcrumb link, which has to stay on its text line. */
  /* The tile is 28px because six of them share one line with a phone number,
     and no arithmetic makes six 44px targets fit a 360px screen. What DOES fit
     is a 28px tile inside a 44px-tall hit area, extended vertically where the
     bar has room and stopped at the 3px half-gap horizontally so two adjacent
     targets never overlap. The same five links sit in the footer at 44x44, one
     scroll away. */
  .topbar-social-ic { position: relative; }
  .topbar-social-ic::after { content: ""; position: absolute; inset: -8px -3px; }
  .breadcrumb a { position: relative; }
  .breadcrumb a::after { content: ""; position: absolute; inset: -13px -8px; }
  .footer-map-link { font-size: 14px; }
  .footer-socials a { width: 44px; height: 44px; border-radius: 12px; }
  .footer-socials { gap: 12px; }
  .team-socials a { min-width: 36px; min-height: 36px; }
}

/* ---------- Forms ---------- */
@media (max-width: 980px) {
  /* 16px is the floor that stops iOS zooming the page on focus. */
  .consult-field input, .consult-field select, .consult-field textarea,
  .footer-news-form input, .gateway-form input {
    font-size: 16px; min-height: 52px; padding: 14px 16px;
  }
  .consult-field textarea { min-height: 132px; }
  .consult-field { margin-bottom: 20px; }
  .consult-field label { margin-bottom: 8px; }
  .consult-field input:focus, .consult-field select:focus, .consult-field textarea:focus {
    border-color: var(--sky-ink); box-shadow: 0 0 0 3px rgba(32, 168, 226, .18);
  }
  /* The raw file control is a 22px browser button; this turns it into a row
     you can actually hit, with the chosen filename beside it. */
  .consult-field input[type="file"] {
    padding: 10px 12px; line-height: 1.4; cursor: pointer;
    display: flex; align-items: center;
  }
  .consult-field input[type="file"]::file-selector-button {
    min-height: 40px; margin-right: 12px; padding: 0 16px;
    border: 0; border-radius: 999px; cursor: pointer;
    background: var(--navy); color: var(--white);
    font-family: var(--font); font-size: 14px; font-weight: 500;
  }
  .consult-submit, .consult-form .btn[type="submit"] { width: 100%; margin-top: 4px; }
  .consult-note { font-size: 15px; }
  /* Stacked, the desktop column gap became a 48px hole between the submit
     button and the reassurance cards that follow it. */
  .consult-inner { gap: 36px; }
  .consult-side { gap: 18px; }
  /* Newsletter rows stack: it is also what fixed the 320px page overflow, as
     the side-by-side input + button forced a grid track wider than the page. */
  .footer-news-form, .gateway-form { flex-direction: column; gap: 10px; }
  .footer-news-form .btn, .gateway-form .btn { width: 100%; min-height: 52px; padding: 14px 24px; }
  .gateway-form .btn { font-size: 15px; }
  .footer-col { min-width: 0; }
}

/* ---------- Footer: a deliberate mobile order ---------- */
@media (max-width: 640px) {
  .site-footer { padding-top: 48px; }
  /* Brand and social full width, then the two link sets side by side as tap
     rows, then newsletter, then the map. A single column of twelve identical
     links was the least useful shape this could take. Done with grid spans, so
     the desktop markup and its four/five-track rhythm are untouched. */
  .footer-grid, .footer-grid.has-map { grid-template-columns: 1fr 1fr; gap: 30px 24px; padding-bottom: 36px; }
  .footer-grid > .footer-about,
  .footer-grid > .footer-col:nth-child(4),
  .footer-grid.has-map > .footer-col-map { grid-column: 1 / -1; }
  .footer-col h3 { margin-bottom: 10px; font-size: 13px; }
  .footer-about p { font-size: 15px; }
  .footer-logo { height: 42px; margin-bottom: 14px; }
  .footer-map iframe { height: 190px; }
  /* padding-BLOCK again: the copyright and the terms link were the last two
     lines on every page and the only two that touched the edge of the screen. */
  .footer-bottom { flex-direction: column; align-items: flex-start; gap: 4px; padding-block: 18px calc(18px + env(safe-area-inset-bottom)); font-size: 14px; }
  .footer-bottom a { display: inline-flex; align-items: center; min-height: 44px; }
  /* v24: the copyright and the terms link stack the way they always have on a
     phone, and the dot goes with the row it was separating. The credit then
     sits under them as the third line, its own 44px tap row. */
  .footer-legal { flex-direction: column; align-items: flex-start; gap: 0; }
  .footer-legal-dot { display: none; }
  .footer-cta-inner { flex-direction: column; align-items: flex-start; gap: 14px; }
  .footer-cta-mail-value { font-size: 16px; overflow-wrap: anywhere; }
}

/* ---------- Section-level polish ---------- */
@media (max-width: 640px) {
  .partners-head, .services-head, .process-head, .team-head { gap: 18px; margin-bottom: 26px; }
  .services-head .btn, .process-head .btn, .team-head .btn, .partners-head .btn { width: 100%; }
  .dream-inner, .cta-band-inner, .consult-cta-inner, .consult-band-inner { flex-direction: column; align-items: stretch; gap: 18px; }
  .dream-inner .btn, .cta-band-inner .btn, .consult-cta-inner .btn, .consult-band-cta .btn { width: 100%; }
  .consult-band-cta { width: 100%; }
  .success-head, .compare-head, .blog-head, .faq-head, .steps-head, .why-us-head, .testimonials-head, .leadership-head,
  .programs-head, .vstories-head { margin-bottom: 28px; }
  .faq-illustration img { max-width: 56%; }
  .faq-question { padding: 18px; min-height: 60px; }
  .gateway-panel { padding: 22px 18px; }
  .gateway-inner { gap: 32px; }
  .gateway-circle { width: 128px; height: 128px; margin-top: 32px; }
  .gateway-cta-row { gap: 14px; }
  .gateway-cta-row .btn { width: 100%; }
  .why-media-badge { left: 12px; right: 12px; bottom: 12px; padding: 12px 14px; font-size: 14px; }
  .check-list { row-gap: 14px; margin-bottom: 26px; }
  .why-copy .btn, .svc-copy .btn, .leader-copy .btn, .error-inner .btn, .uni-apply { width: 100%; }
  /* Centring three differently-sized rows left their icons on three different
     x positions. One left-aligned column inside a centred block puts every
     icon, title and caption on the same edge. */
  .up-features {
    flex-direction: column; align-items: stretch; gap: 16px;
    margin: 26px auto 4px; max-width: 290px;
  }
  .up-feature { align-items: flex-start; }
  .up-stats { gap: 22px 16px; margin-top: 22px; padding-top: 20px; }
  .up-stat { flex: 1 1 40%; align-items: center; text-align: center; }
  .uni-hero-stats { gap: 24px; }
  .uni-facts { padding: 22px 18px; }
  .program-grid { gap: 14px; }
  .error-code { font-size: 68px; }
  .error-inner h1 { font-size: 25px; }
  .testimonial-card, .consult-badge { padding: 22px 18px; }
  .consult-contact { padding: 22px 18px; }
  /* v14 collapsed the empty cover box on the blog INDEX and deliberately left
     the home preview alone, because on a wide three-up row a tinted rectangle
     still reads as a card. Stacked on a phone it does not: three 205px empty
     panels down the column read as three pictures that failed to load. Below
     the desktop floor the home preview follows the index (v23). */
  .blog-preview .blog-card-media:has(.blog-card-placeholder) { display: none; }
  /* An owner-uploaded cover has no known intrinsic size, so a fixed ratio is
     what keeps the article text from jumping when it finally decodes. */
  .post-cover { aspect-ratio: 16 / 9; object-fit: cover; margin-bottom: 24px; }
  .legal-inner, .post-body { font-size: 16px; }
}

/* ==========================================================================
   INNER-PAGE POLISH ON PHONES (v14)
   --------------------------------------------------------------------------
   The phone half of the v14 pass. Every rule stays inside a max-width query at
   or below the 1023px desktop floor, so the >=1024px layout above is untouched.
   ========================================================================== */

@media (max-width: 980px) {
  /* The hero mark stacks above the name at a size that still reads as an
     identity tile rather than a favicon. */
  .uni-hero-inner { flex-direction: column-reverse; align-items: stretch; gap: 24px; }
  .uni-hero-mark { width: min(200px, 56%); }
  /* Quick Facts is the highest-intent block on the page (tuition, location,
     website, CTA). Stacked, it used to land ~2,600px down, after every word of
     body copy; on a phone it now sits directly under the hero. */
  .uni-detail-inner { display: flex; flex-direction: column; }
  .uni-facts { order: -1; }
  .uni-sub-title { margin-top: 40px; padding-top: 24px; }
  /* The crumb rule is a desktop nicety; on a phone it costs a line for nothing. */
  .page-hero .breadcrumb, .post-hero .breadcrumb {
    margin-top: 12px; padding-top: 12px;
  }
}

@media (max-width: 640px) {
  .page-hero { padding: 56px 0; }
  .post-hero { padding: 56px 0; }
  .uni-hero-mark { width: min(168px, 50%); }
  .uni-hero-inner { gap: 20px; }
  /* Pill tabs keep the equal phone grid the v12 pass introduced (two columns
     since v21's fourth track); only the shape and the resting fill change. */
  .uni-tab { padding: 10px 8px; font-size: 14px; border-radius: 999px; }
  .up-feature-icon { width: 34px; height: 34px; font-size: 20px; }
  .svc-index { margin-bottom: 0; }
  .error-links { gap: 8px; margin-top: 26px; padding-top: 24px; }
  .error-link { min-height: 44px; padding: 9px 18px; }
  .post-back { padding-top: 20px; }
  .rich-text h2 { margin-top: 30px; }
  .rich-text h3, .rich-text h4 { margin-top: 26px; }
}

/* ---------- Reduced motion: the last word (v28) ----------
   Every rule that MOVES anything is already inside a
   (prefers-reduced-motion: no-preference) query or has its own reduce
   counterpart above, so by the time a reader gets here the reveals, the hero
   entrance, the three marquees, the ring draw and the mega panel have all
   opted out. This is the catch-all that takes the rest with them: the hover
   and focus transitions, the accordion's drawn height, the header's shadow,
   and any transition a future round adds without remembering to write its own
   opt-out. Collapsing a duration to .01ms rather than removing the animation
   is deliberate: the END state still applies (so nothing is left mid-flight
   or invisible) and transitionend/animationend still fire, which is what
   stops a script that waits for one from hanging.
   The two hover LIFTS the pill gained in v28 are the one thing a duration
   cannot flatten - an instant 2px jump is still a jump - so they are named
   and cancelled outright. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .btn:hover, .btn:active { transform: none; }
}
