/* ==========================================================================
   UniDovo — home.css
   Homepage-only sections, built to the approved mockup:

     1. Process flow        — four steps joined by arrows
     2. Why Malaysia        — copy over a dark skyline photograph
     3. Partner universities— full-width navy logo/photo showcase
     4. Services            — six-item grid
     5. Closing CTA         — full-width photo with navy scrim

   Loaded only by main/home_v2.html. Shared primitives (.btn, .card, .wrap,
   .section) live in site.css.
   ========================================================================== */

/* ==================================================================== *
 * 1. Process flow
 * ==================================================================== */
.section-head--left { margin-bottom: clamp(1.5rem, 3vw, 2.25rem); }
.section-head--left p { max-width: 42rem; }

/* ==================================================================== *
 * PROCESS — "the route"
 *
 * The four steps are drawn as an actual journey: a dashed flight path with
 * four numbered stations, each dropping a short stem into its own card. It
 * reuses the paper-plane + dotted-path motif the hero, the masthead
 * flightline and the student-story tape already share, so the section reads
 * as part of the page rather than as a generic 4-up feature grid.
 * ==================================================================== */
/* The route box holds the list plus three decorative layers — the rail, the
   part of it already flown, and the plane. They live here rather than on
   .route so the <ol> keeps only list items as children, and they share its
   box, so one set of measurements lines all of them up. */
.route-stage {
  --route-gap: clamp(1rem, 2vw, 1.75rem);
  /* Centre of the first and last station, derived from the grid itself
     rather than assumed at 12.5% — that was 8px short, because the gaps
     between the columns pull the real centres inwards. */
  --route-start: calc((100% - 3 * var(--route-gap)) / 8);
  --route-span: calc(100% - 2 * var(--route-start));
  /* How far the plane has flown. It pulls up short of a station instead of
     parking on top of it, so the numerals stay readable at both ends. */
  --route-pad: 30px;
  --route-travel: calc(var(--rp, 0) * (var(--route-span) - 2 * var(--route-pad)));
  position: relative;
}

.route {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--route-gap);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The rail: white dashes over a blue→red gradient, so the gradient only
   survives in the gaps and the line arrives pre-dashed without an SVG. */
.route-rail {
  position: absolute;
  z-index: 1;
  top: 21px;
  left: var(--route-start);
  width: var(--route-span);
  height: 2px;
  background-image:
    repeating-linear-gradient(90deg, #fff 0 7px, transparent 7px 15px),
    linear-gradient(90deg, var(--brand-600) 0%, var(--brand-500) 45%, var(--sec, var(--accent-500)) 100%);
  opacity: 0.55;
}

/* The ground already covered fills the dashes solid. Clipped by a wrapper so
   the gradient stays anchored to the full rail instead of re-ramping as the
   bar grows — otherwise it would be red a quarter of the way in. */
.route-rail__fill {
  position: absolute;
  z-index: 1;
  top: 20px;
  left: var(--route-start);
  width: calc(var(--route-pad) + var(--route-travel));
  height: 4px;
  border-radius: 2px;
  overflow: hidden;
  pointer-events: none;
}
.route-rail__fill::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: var(--route-span);
  background-image: linear-gradient(90deg, var(--brand-600) 0%, var(--brand-500) 45%, var(--sec, var(--accent-500)) 100%);
}

/* The plane. Sits above the stations so it is never half-swallowed, and
   waits just off the departure station rather than on top of it. */
.route-plane {
  position: absolute;
  z-index: 3;
  top: 21px;
  left: calc(var(--route-start) + var(--route-pad) + var(--route-travel));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: 0 0 0 3px var(--surface), 0 4px 12px rgba(15, 23, 42, 0.16);
  color: var(--brand-600);
  font-size: 14px;
  /* climbs out of the departure station, then levels off on the line */
  transform: translate(-50%, -50%) rotate(calc(-14deg + var(--rp, 0) * 14deg));
  transition: color 0.35s var(--ease);
  pointer-events: none;
  will-change: left;
}

.route__stop {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* station ------------------------------------------------------------ */
.route__node {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 2px solid var(--brand-600);
  border-radius: 50%;
  background: var(--surface);
  color: var(--brand-700);
  font-family: var(--font-display);
  font-size: 0.8125rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  transition: background-color 0.22s var(--ease), border-color 0.22s var(--ease),
              color 0.22s var(--ease), transform 0.22s var(--ease),
              box-shadow 0.35s var(--ease);
}
/* the last station is the destination */
.route__stop:last-child .route__node {
  border-color: var(--sec, var(--accent-500));
  color: var(--sec, var(--accent-600));
}

/* stem from the rail down into the card ------------------------------- */
.route__stem {
  width: 2px;
  height: clamp(1.25rem, 2.4vw, 1.75rem);
  background-image: repeating-linear-gradient(180deg, var(--line-2) 0 4px, transparent 4px 9px);
}

/* card ---------------------------------------------------------------- */
.route__card {
  position: relative;
  width: 100%;
  overflow: hidden;
  padding: 1.5rem 1.25rem 1.375rem;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--surface);
  box-shadow: var(--shadow-xs);
  text-align: left;
  transition: border-color 0.22s var(--ease), box-shadow 0.22s var(--ease),
              transform 0.22s var(--ease);
}
/* oversized ghost numeral, clipped by the card */
.route__ghost {
  position: absolute;
  top: -0.75rem;
  right: 0.375rem;
  color: var(--brand-950);
  font-family: var(--font-display);
  font-size: 4.5rem;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.05em;
  opacity: 0.055;
  pointer-events: none;
  user-select: none;
}
.route__icon {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-bottom: 0.875rem;
  border: 1px solid var(--brand-100);
  border-radius: 12px;
  background: var(--brand-50);
  color: var(--brand-700);
  font-size: 1rem;
  transition: border-color 0.22s var(--ease), background-color 0.22s var(--ease);
}
.route__card h3 {
  position: relative;
  margin-bottom: 0.3125rem;
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.route__card p {
  position: relative;
  font-size: 0.875rem;
  line-height: 1.55;
  color: var(--body);
}

/* the whole stop reacts as one --------------------------------------- */
.route__stop:hover .route__node,
.route__stop:focus-within .route__node {
  border-color: var(--brand-700);
  background: var(--brand-700);
  color: #fff;
  transform: translateY(-2px);
}
.route__stop:hover .route__card,
.route__stop:focus-within .route__card {
  border-color: var(--brand-200);
  box-shadow: var(--shadow);
  transform: translateY(-3px);
}
.route__stop:hover .route__icon,
.route__stop:focus-within .route__icon {
  border-color: var(--brand-200);
  background: var(--brand-100);
}

/* tablet + phone: the rail can no longer line up with the stations, so it,
   the flown part and the plane all drop away and the route becomes an
   ordinary grid — with a static plane at the departure station instead */
@media (max-width: 899px) {
  .route { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.25rem; }
  .route-rail,
  .route-rail__fill,
  .route-plane { display: none; }
  .route__stem { display: none; }
  .route__node { margin-bottom: 0.875rem; }
  .route__stop:first-child .route__node::before {
    content: '\f1d8';
    position: absolute;
    left: -48px;
    top: 50%;
    transform: translateY(-50%) rotate(-14deg);
    color: var(--brand-500);
    font-family: 'Font Awesome 6 Free';
    font-weight: 900;
    font-size: 15px;
  }
}
@media (max-width: 559px) {
  .route { grid-template-columns: 1fr; }
  .route__stop { align-items: stretch; }
  .route__node { align-self: flex-start; }
  .route__stop:first-child .route__node::before { display: none; }
}

/* ==================================================================== *
 * PROCESS — pinned on scroll
 *
 * The section holds still while the visitor scrolls and the plane flies the
 * route, then releases and the page carries on.
 *
 * This is `position: sticky` over a taller box — NOT scroll-jacking. The
 * scroll position stays the visitor's own, nothing is intercepted or locked,
 * and there is no way to get stuck. Everything below is gated on .is-armed,
 * which only the script sets, and only above 900px, without reduced motion,
 * and when the stage genuinely fits the window — so with no script, on a
 * phone, or on a short window this stays the plain static route.
 * ==================================================================== */
.route-pin.is-armed {
  padding-block: 0;
  min-height: calc(100vh + var(--route-run, 90vh));
  /* Belt and braces. custom.css previously set `overflow-x: hidden` on every
     <section>, which made each one its own scrollport and stopped a sticky
     child from ever sticking to the window. That is now `clip`, but this
     section must never create a scroll container, so say so plainly. */
  overflow: visible;
}
.route-pin.is-armed > .route-pin__stage {
  position: sticky;
  top: 0;
  display: grid;
  align-content: start;
  /* NOT 100vh. The stage only has to hold the route, not blank the whole
     window: the page is a single white sheet, so the empty band under the
     stage is indistinguishable from the stage's own background — while a
     100vh floor padded the section's tail with however much slack the window
     had left over (80px at 717, 310px at 900) and turned it all into air
     between the last card and the next section. Pulling the floor short keeps
     the pinned content exactly where it was; it just stops the section box
     from claiming the rest of the screen.
     The strip the stage exposes below itself is EMPTY for the whole of the
     pin — it is still inside #process's own box until the sticky releases — so
     shortening the stage costs nothing there: it only decides how much of the
     next section is on screen at the instant it releases, and that is ordinary
     scroll-in from below, the same as any other section.
     10.25rem, measured at the client's 1507x910 window: the air under the last
     route card goes 231px -> 163px, so the gap down to the founder's eyebrow
     goes 271px -> 203px — the 25% cut that was asked for. The floor still
     keeps the stage at least content + 3rem, which is what stops .route-cue
     landing on the last card. */
  min-height: calc(100vh - 10.25rem);
  /* The header is fixed and comes back the moment the visitor scrolls up,
     so the pinned content has to sit clear of it from the outset rather
     than being centred and hoping. --pin-top is measured by the script,
     because the header is taller than --header-h alone (that only covers
     the nav, not the utility bar above it). The script also refuses to arm
     unless the whole stage genuinely fits below that clearance.
     The tail is 3rem because .route-cue sits 0.75rem off the stage's bottom
     edge and is ~13px tall — shave it and the cue lands on top of the cards
     once the stage stops filling the window. */
  padding-block: var(--pin-top, calc(var(--header-h) + 1.5rem)) 3rem;
}

/* stations the plane has not reached yet are dormant... */
.route-pin.is-armed .route__node {
  border-color: var(--line-2);
  color: var(--muted);
}
.route-pin.is-armed .route__stop.is-reached .route__node {
  border-color: var(--brand-600);
  color: var(--brand-700);
  box-shadow: 0 0 0 5px rgba(37, 89, 232, 0.10);
}
/* ...but the destination reads as the destination from the moment it lands
   on screen, reached or not */
.route-pin.is-armed .route__stop:last-child .route__node {
  border-color: var(--sec, var(--accent-500));
  color: var(--sec-ink, var(--accent-600));
  box-shadow: none;
}
.route-pin.is-landed .route__stop:last-child .route__node {
  box-shadow: 0 0 0 6px rgba(255, 23, 31, 0.12);
}

/* A card only comes up to full strength once its station is reached.
   Opacity and filter only — never transform, because the hover lift owns
   that and would otherwise be overridden. */
.route-pin.is-armed .route__stop .route__card {
  opacity: 0.4;
  filter: grayscale(0.35);
  transition: opacity 0.45s var(--ease), filter 0.45s var(--ease),
              border-color 0.22s var(--ease), box-shadow 0.22s var(--ease);
}
.route-pin.is-armed .route__stop.is-reached .route__card {
  opacity: 1;
  filter: none;
}

/* the plane turns red as it lands */
.route-pin.is-landed .route-plane { color: var(--sec, var(--accent-500)); }

/* A section that has stopped moving can read as a section that has
   finished. This says otherwise — and is gone the moment the plane moves.
   Out of flow, so it costs the pinned stage no height. */
.route-cue {
  position: absolute;
  right: 0;
  /* 0.75rem, not the 1.75rem this started at: the stage no longer fills the
     window (see min-height above), so a taller offset would drop the cue into
     the last card's lap on a short window. This keeps a clear 23px between
     them even when the content box is the binding constraint. */
  bottom: 0.75rem;
  left: 0;
  display: none;
  margin: 0;
  color: var(--muted);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-align: center;
}
.route-cue i { margin-left: 0.5rem; animation: route-cue-bob 1.7s var(--ease) infinite; }
.route-pin.is-armed .route-cue {
  display: block;
  opacity: 1;
  transition: opacity 0.35s var(--ease);
}
.route-pin.is-armed.is-rolling .route-cue { opacity: 0; }

@keyframes route-cue-bob {
  0%, 100% { transform: translateY(-2px); opacity: 0.5; }
  50%      { transform: translateY(2px);  opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .route-cue i { animation: none; }
}

@media (min-width: 900px) {
  .flow { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 2.5rem; }
  /* the connecting arrow sits in the gutter between steps */
  .flow__step:not(:last-child)::after {
    content: '\f061';
    position: absolute;
    top: 14px;
    right: -1.5rem;
    transform: translateX(50%);
    color: var(--line-2);
    font-family: 'Font Awesome 6 Free';
    font-weight: 900;
    font-size: 0.875rem;
  }
}

/* ==================================================================== *
 * 2. Why Malaysia — dark patterned band
 *
 * Full-bleed band sitting directly beneath the hero with no vertical gap and
 * no outer radius, so hero → Why Malaysia → Partner universities still reads
 * as one continuous dark block. Two columns: copy on the left, four
 * translucent glass cards on the right.
 *
 * The backdrop is generated rather than photographic — a blueprint texture
 * over a deep navy wash: a fine grid, a dot sitting on every intersection,
 * and two soft brand glows for depth. Nothing to download, crisp at every
 * width, and it never competes with the copy. Layers are painted back to
 * front, so the grid and dots stay sharp where they cross the glow.
 * ==================================================================== */
.why-malaysia {
  position: relative;
  width: 100%;
  min-height: clamp(300px, 24vw, 350px);
  overflow: hidden;
  background-color: var(--surface, #FFFFFF);
  background-image:
    radial-gradient(circle at 0 0, var(--bp-dot) 0.9px, transparent 2.4px),
    linear-gradient(var(--bp-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--bp-line) 1px, transparent 1px),
    radial-gradient(circle at 86% 14%, rgba(37, 89, 232, 0.30), transparent 58%),
    radial-gradient(circle at 4% 98%, rgba(255, 23, 31, 0.14), transparent 52%);
  background-size: var(--bp-size) var(--bp-size), var(--bp-size) var(--bp-size),
                   var(--bp-size) var(--bp-size), 100% 100%, 100% 100%;
  background-position: -1px -1px, -1px -1px, -1px -1px, center, center;
  background-repeat: repeat, repeat, repeat, no-repeat, no-repeat;
}

/* shallow vignette top and bottom so the pattern fades into the hero above
   and the navy showcase below instead of stopping on a hard line */
.why-malaysia::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* was a dark vignette; on a white band a whisper of ink keeps the pattern
     from stopping on a hard edge at top and bottom */
  background: linear-gradient(
    180deg,
    rgba(15, 23, 42, 0.04) 0%,
    rgba(15, 23, 42, 0) 28%,
    rgba(15, 23, 42, 0) 68%,
    rgba(15, 23, 42, 0.04) 100%
  );
}

.why-malaysia__inner {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 0.95fr 1.05fr;
  align-items: center;
  gap: clamp(18px, 2vw, 28px);
  width: 100%;
  max-width: 1440px;
  min-height: inherit;
  margin: 0 auto;
  padding: clamp(24px, 2.4vw, 34px) clamp(32px, 4.2vw, 60px);
}

/* left column --------------------------------------------------------- */
.why-malaysia__eyebrow {
  display: inline-flex;
  align-items: center;
  width: fit-content;
  padding: 5px 12px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
}
.why-malaysia__title {
  max-width: 420px;
  margin: 11px 0 8px;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: clamp(25px, 2.35vw, 31px);
  font-weight: 800;
  line-height: 1.12;
  letter-spacing: -0.7px;
}
.why-malaysia__text {
  max-width: 350px;
  margin: 0 0 12px;
  color: var(--body);
  font-size: clamp(12px, 0.95vw, 13px);
  line-height: 1.45;
}
.why-malaysia__cta {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 40px;
  padding: 0 18px;
  border-radius: 999px;
  background: var(--brand-700);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  transition: background-color 0.18s var(--ease), color 0.18s var(--ease);
}
.why-malaysia__cta i { transition: transform 0.18s var(--ease); }
.why-malaysia__cta:hover { background: var(--brand-800); color: #fff; }
.why-malaysia__cta:hover i { transform: translateX(3px); }

/* right column: 2 x 2 glass cards ------------------------------------- */
.why-malaysia__benefits {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.why-malaysia__card {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  min-width: 0;
  min-height: 90px;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface-2);
}
.why-malaysia__card-icon {
  flex: none;
  width: 30px;
  font-size: 22px;
  line-height: 1.05;
  text-align: center;
  color: var(--brand-700);
}
.why-malaysia__card.is-red .why-malaysia__card-icon { color: var(--accent-500); }
/* was #20C6B4 — a teal that belonged to no part of the brand palette */
.why-malaysia__card.is-teal .why-malaysia__card-icon { color: var(--brand-500); }

.why-malaysia__card h3 {
  margin: 0 0 5px;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: clamp(13px, 0.95vw, 14px);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.2px;
}
.why-malaysia__card p {
  margin: 0;
  color: var(--body);
  /* was clamp(10px, 0.8vw, 11px): at 360px that floor made every card a
     paragraph of 10px body copy, which is unreadable on a phone. */
  font-size: clamp(13px, 0.9vw, 14px);
  line-height: 1.45;
}

/* tablet ------------------------------------------------------------- */
@media (max-width: 1023px) {
  /* Below 1024px the two side-by-side columns squeeze the cards down to
     ~175px, which reads as cramped. Stack instead: the copy sits above a
     full-width card grid, so the cards stay roomy and the 2x2 grid holds. */
  .why-malaysia__inner {
    grid-template-columns: 1fr;
    padding: 28px 32px;
    gap: 20px;
  }
  .why-malaysia__title {
    max-width: 30ch;
    font-size: clamp(25px, 3vw, 27px);
  }
  .why-malaysia__text {
    max-width: 52ch;
    margin-bottom: 14px;
  }
  .why-malaysia__card { padding: 14px 15px; }
}

/* mobile ------------------------------------------------------------- */
@media (max-width: 767px) {
  .why-malaysia {
    min-height: 0;
    /* a tighter grid so the pattern still reads on a narrow screen */
    background-size: 34px 34px, 34px 34px, 34px 34px, 100% 100%, 100% 100%;
  }
  .why-malaysia__inner {
    min-height: 0;
    padding: 30px 24px;
    gap: 22px;
  }
  .why-malaysia__benefits { gap: 10px; }
}

@media (max-width: 419px) {
  .why-malaysia__benefits { grid-template-columns: 1fr; }
  .why-malaysia__card { min-height: 0; }
}

/* ==================================================================== *
 * 3. Partner universities — full-width navy showcase
 *
 * Sits directly beneath "Why Malaysia": copy, CTA and carousel arrows on the
 * left; four logo-over-photograph cards on the right.
 *
 * The reference geometry is a 155 x 186 card (a 155 x 72 logo panel on a
 * 155 x 114 photograph). Rather than freeze those px values, each half owns
 * its aspect ratio, so the card keeps its exact proportions as it grows to
 * its ~220-240px ceiling at wider desktop widths.
 * ==================================================================== */
/* RETIRED — the split card row for partner universities. Superseded by the
   .pu-* hairline matrix at the foot of this file; kept only until the block
   is deleted. Nothing references these classes any more. */
.partner-universities {
  position: relative;
  width: 100%;
  overflow: hidden;
  background: #03264B;
  color: #fff;
}
.partner-universities__inner {
  display: grid;
  grid-template-columns: 0.95fr 2.05fr;
  align-items: center;
  gap: 32px;
  width: 100%;
  max-width: 1440px;
  /* 300px is the intended band height at 1440; scaling it down keeps the
     reference-scale section from padding out with dead space. */
  min-height: clamp(230px, 20.8vw, 300px);
  margin: 0 auto;
  padding: 32px 52px;
}

/* left column --------------------------------------------------------- */
.partner-universities__copy { min-width: 0; }
.partner-universities__eyebrow {
  display: block;
  margin-bottom: 10px;
  color: #fff;
  font-size: clamp(9px, 0.88vw, 12px);
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
}
.partner-universities__title {
  max-width: 290px;
  margin: 0 0 10px;
  color: #fff;
  font-family: var(--font-display);
  font-size: clamp(26px, 2.9vw, 32px);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -0.7px;
}
.partner-universities__text {
  max-width: 270px;
  margin: 0 0 12px;
  color: #DCE6F2;
  font-size: clamp(12px, 0.95vw, 13px);
  line-height: 1.45;
}

/* CTA + carousel arrows ----------------------------------------------- */
.partner-universities__actions {
  display: flex;
  align-items: center;
  gap: 12px;
}
.partner-universities__cta {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: 34px;
  padding: 0 16px;
  border-radius: 999px;
  background: #fff;
  color: #075CE5;
  font-size: clamp(11px, 0.9vw, 12px);
  font-weight: 600;
  transition: background-color 0.18s var(--ease), color 0.18s var(--ease);
}
.partner-universities__cta i { transition: transform 0.18s var(--ease); }
.partner-universities__cta:hover { background: #E8F0FF; color: #0057E7; }
.partner-universities__cta:hover i { transform: translateX(3px); }

.partner-universities__nav {
  display: flex;
  gap: 6px;
}
.partner-universities__arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.45);
  border-radius: 50%;
  background: transparent;
  color: #fff;
  font-size: 13px;
  cursor: pointer;
  transition: background-color 0.18s var(--ease), border-color 0.18s var(--ease),
              opacity 0.18s var(--ease);
}
.partner-universities__arrow:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.16);
  border-color: #fff;
}
/* nothing to scroll (the desktop layout and the very ends of the strip) */
.partner-universities__arrow:disabled { opacity: 0.35; cursor: default; }

/* right column: four identical cards ---------------------------------- */
.partner-universities__grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
  min-width: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}
.partner-universities__card {
  min-width: 0;
  overflow: hidden;
  border-radius: 12px;
  background: #fff;
}
/* the two halves meet cleanly — no gap, no divider */
.partner-universities__logo { display: block; background: #fff; }
/* the image *is* the white panel: it carries the panel's 155:72 shape and
   the padding, then object-fit keeps the mark itself undistorted and centred
   — which matters because the four logos range from 1:1 to 4.8:1 wide. */
.partner-universities__logo img {
  display: block;
  width: 100%;
  aspect-ratio: 155 / 72;
  padding: 11px;
  object-fit: contain;
}
/* the photograph doubles as the caption's positioning context, and clips the
   hover zoom */
.partner-universities__image {
  position: relative;
  display: block;
  overflow: hidden;
}
.partner-universities__image img {
  display: block;
  width: 100%;
  aspect-ratio: 155 / 114;
  object-fit: cover;
  object-position: center;
}

/* ---- card link: the whole card is one target -------------------------
   The card carries the caption, so the card must be a single clickable box
   rather than a picture next to an unexplained logo. */
.partner-universities__link {
  display: flex;
  flex-direction: column;
  height: 100%;
  color: inherit;
  transition: transform 0.22s var(--ease);
}
.partner-universities__card {
  transition: box-shadow 0.22s var(--ease);
}
.partner-universities__link:hover { transform: translateY(-4px); }
.partner-universities__card:hover {
  box-shadow: 0 16px 34px rgba(2, 12, 26, 0.38);
}
/* outline draws inside the card, so it stays visible on the white panel */
.partner-universities__link:focus-visible {
  outline: 3px solid var(--brand-500);
  outline-offset: -3px;
}

/* ---- caption, sat over the foot of the photograph --------------------
   Absolute so the card keeps the reference 155 x 186 proportions and the
   four captions can differ in length without knocking the cards out of
   alignment. The scrim only ramps up where the type actually sits. */
.partner-universities__image img { transition: transform 0.4s var(--ease); }
.partner-universities__card:hover .partner-universities__image img {
  transform: scale(1.06);
}
.partner-universities__image::after {
  content: '';
  position: absolute;
  inset: auto 0 0 0;
  height: 70%;
  background: linear-gradient(
    180deg,
    rgba(4, 16, 31, 0) 0%,
    rgba(4, 16, 31, 0.22) 42%,
    rgba(4, 16, 31, 0.88) 100%
  );
}
.partner-universities__caption {
  position: absolute;
  inset: auto 0 0 0;
  z-index: 1;
  padding: 9px 10px 10px;
  text-align: left;
}
.partner-universities__name {
  /* two lines maximum keeps the four captions optically aligned */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  color: #fff;
  font-family: var(--font-display);
  font-size: clamp(11px, 0.9vw, 12.5px);
  font-weight: 800;
  line-height: 1.22;
  letter-spacing: -0.01em;
}
.partner-universities__meta {
  display: block;
  margin-top: 3px;
  overflow: hidden;
  color: rgba(255, 255, 255, 0.76);
  font-size: clamp(10px, 0.78vw, 11px);
  font-weight: 600;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* tablet + mobile ------------------------------------------------------
   Stacked, exactly like "Why Malaysia" above it, and the card row becomes a
   swipeable strip the arrows (or a finger) can drive. */
@media (max-width: 1023px) {
  .partner-universities__inner {
    grid-template-columns: 1fr;
    gap: 22px;
    min-height: 0;
    padding: 28px;
  }
  .partner-universities__title {
    max-width: 30ch;
    font-size: clamp(25px, 3vw, 27px);
  }
  .partner-universities__text { max-width: 52ch; }

  .partner-universities__grid {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: minmax(150px, 190px);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    scrollbar-width: thin;
    padding-bottom: 2px;
  }
  .partner-universities__card { scroll-snap-align: start; }
}

@media (max-width: 767px) {
  .partner-universities__inner { padding: 24px 20px; gap: 20px; }
  /* roughly two cards in view, never narrow enough to lose a logo */
  .partner-universities__grid { grid-auto-columns: clamp(150px, 44vw, 190px); }
}

@media (max-width: 419px) {
  .partner-universities__actions { flex-wrap: wrap; }
}

/* ==================================================================== *
 * 4. Services — three staged panels
 *
 * The six services are really three stages of one journey, so they are laid
 * out as phases — before you apply → getting approved → once you land —
 * rather than a flat card grid. Each phase is its own tinted panel with a
 * numbered rail and its own accent, which makes the structure readable at a
 * glance and keeps this section from repeating the grids used elsewhere.
 *
 * No `overflow: hidden` on the panel: the dashed connector between stages
 * lives outside the box, in the gutter.
 * ==================================================================== */
.phases {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;
}
.phase {
  position: relative;
  padding: 1.375rem 1.25rem 1.25rem;
  border: 1px solid var(--phase-line);
  border-radius: 20px;
  background-color: #FFFFFF;
  /* a soft wash of the phase colour across the top of the panel */
  background-image: radial-gradient(135% 62% at 50% 0%, var(--phase-ghost), transparent 72%);
  background-repeat: no-repeat;
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04),
              0 20px 38px -24px rgba(15, 23, 42, 0.3);
}
/* one accent per stage, all drawn from the brand palette:
   blue (brand-600) → red (accent) → deep navy (brand-800).
   The third stage used to be a teal (#0E9384) that appeared nowhere else
   in the brand. */
.phase:nth-child(1) {
  --phase: #2559E8;
  --phase-ink: #1B3FAF;
  --phase-soft: #EAF1FE;
  --phase-line: rgba(37, 89, 232, 0.18);
  --phase-ghost: rgba(37, 89, 232, 0.10);
}
.phase:nth-child(2) {
  --phase: #FF171F;
  --phase-ink: #B80B12;
  --phase-soft: #FFDCDC;
  --phase-line: rgba(255, 23, 31, 0.18);
  --phase-ghost: rgba(255, 23, 31, 0.09);
}
.phase:nth-child(3) {
  --phase: #1B3FAF;
  --phase-ink: #132453;
  --phase-soft: #EFF5FF;
  --phase-line: rgba(27, 63, 175, 0.18);
  --phase-ghost: rgba(27, 63, 175, 0.10);
}

/* phase rail --------------------------------------------------------- */
.phase__head {
  display: flex;
  align-items: center;
  gap: 0.6875rem;
  padding-bottom: 0.875rem;
  border-bottom: 1px dashed var(--phase-line);
}
.phase__num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: 10px;
  background: var(--phase);
  color: #FFFFFF;
  font-family: var(--font-display);
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  box-shadow: 0 8px 18px -9px var(--phase);
}
.phase__kicker {
  color: var(--phase-ink);
  font-family: var(--font-display);
  font-size: 0.8125rem;
  font-weight: 800;
  line-height: 1.2;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}
.phase__count {
  margin-left: auto;
  color: var(--muted);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* the services inside a phase ---------------------------------------- */
.phase__list {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
}
.phase__item {
  position: relative;
  padding: 0.9375rem 0 0.9375rem 2.9375rem;
  border-bottom: 1px solid var(--line);
  transition: transform 0.2s var(--ease);
}
.phase__item:last-child { border-bottom: 0; padding-bottom: 0.25rem; }
.phase__icon {
  position: absolute;
  left: 0;
  top: 0.9375rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--phase-line);
  border-radius: 11px;
  background: var(--phase-soft);
  color: var(--phase-ink);
  font-size: 0.875rem;
  transition: background-color 0.2s var(--ease), border-color 0.2s var(--ease),
              color 0.2s var(--ease);
}
.phase__item h4 {
  font-family: var(--font-display);
  font-size: 0.9375rem;
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.01em;
}
.phase__item p {
  margin-top: 0.25rem;
  color: var(--body);
  font-size: var(--fs-xs);
  line-height: 1.55;
}
/* hovering a service fills its icon with the phase colour */
.phase__item:hover { transform: translateX(2px); }
.phase__item:hover .phase__icon {
  border-color: var(--phase);
  background: var(--phase);
  color: #FFFFFF;
}
.phase__item:hover h4 { color: var(--phase-ink); }

/* narrow phones: the count drops below the phase name, aligned with it */
@media (max-width: 479px) {
  .phase__head { flex-wrap: wrap; row-gap: 0.375rem; }
  .phase__count { margin-left: 0; padding-left: 2.6875rem; }
}

/* desktop: three across, with a dashed connector showing the progression */
@media (min-width: 900px) {
  .phases { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.5rem; }
  .phase:not(:first-child)::before {
    content: '';
    position: absolute;
    top: 2.3rem;
    left: -1.5rem;
    width: 1.5rem;
    height: 2px;
    background-image: linear-gradient(90deg, var(--line-2) 45%, transparent 0);
    background-size: 8px 2px;
    background-repeat: repeat-x;
  }
}

/* ==================================================================== *
 * 5. Closing CTA — the last band, and the last photograph
 *
 * Rebuilt around the closing band on jupy.llc (the layout reference for
 * this page): a kicker row ruled off from the ask, then the copy and the
 * buttons on one line. It is deliberately the shortest band on the page —
 * everything above it has already made the argument, so this one only has
 * to ask.
 *
 * NB on the name: this is `.cta-photo`, not `.cta-band`. `.cta-band` is the
 * shared navy CTA card in site.css §23 that the About and Contact pages use;
 * two different components cannot share a name.
 *
 * The band keeps its full-width photograph — it is one of the three
 * picture-led bands that were deliberately kept — but the scrim no longer
 * flattens it. Before, a 95%-opaque navy on the left ramped to 15% on the
 * right, so the picture only existed on the right half and the copy sat on
 * a solid slab. The horizontal ramp is gentler now, and a second vertical
 * ramp darkens just the strip the facts row occupies — that is what lets
 * 13px white type sit over a bright sky at all.
 *
 * The slogan is the brand line "Different cultures, one bright tomorrow"
 * and this is the only place on the site it appears. It used to run down
 * the right of the band in pink, straight over the students' faces; here
 * it is a line of the copy column, in the logo red.
 * ==================================================================== */
.cta-photo {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: flex;
  align-items: center;
  background: var(--mast-deep);
}
.cta-photo__bg {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 68% center;
}
.cta-photo__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(
      100deg,
      rgba(4, 16, 31, 0.94) 0%,
      rgba(4, 16, 31, 0.82) 40%,
      rgba(4, 16, 31, 0.46) 68%,
      rgba(4, 16, 31, 0.22) 100%
    ),
    linear-gradient(
      180deg,
      rgba(4, 16, 31, 0.10) 0%,
      rgba(4, 16, 31, 0) 34%,
      rgba(4, 16, 31, 0.50) 100%
    );
}
.cta-photo .wrap { padding-block: clamp(1.75rem, 3.2vw, 2.5rem); }

/* 1 — kicker row: the shout, then the office hours, ruled off from the ask */
.cta-photo__top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.25rem;
  padding-bottom: 0.875rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.16);
}
.cta-photo__kicker {
  display: inline-flex;
  align-items: center;
  gap: 0.5625rem;
  color: #FFFFFF;
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.cta-photo__kicker::before {
  content: '';
  width: 22px;
  height: 2px;
  border-radius: 2px;
  background: var(--accent-500);
}
.cta-photo__note {
  margin-left: auto;
  color: rgba(255, 255, 255, 0.66);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* 2 — the ask: copy on the left, the two buttons on the right */
.cta-photo__body {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1.5rem 2rem;
  padding-top: clamp(1.125rem, 2vw, 1.5rem);
}
.cta-photo__copy { max-width: 34rem; }
.cta-photo__title {
  margin-bottom: 0.75rem;
  color: #FFFFFF;
  font-size: clamp(1.625rem, 3.1vw, 2.375rem);
  line-height: 1.08;
  letter-spacing: -0.03em;
}
.cta-photo__lede {
  margin-bottom: 0;
  color: rgba(255, 255, 255, 0.78);
  font-size: 0.9375rem;
  line-height: 1.65;
}
.cta-photo__slogan {
  display: block;
  margin-top: 0.875rem;
  color: var(--accent-500);
  font-family: var(--font-script);
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1.2;
}

/* 3 — the actions */
.cta-photo__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  padding-top: 0.25rem;
}
.cta-photo__actions .btn { border-radius: 999px; }
/* the red pill is kept for this band only: it is the one dark surface with a
   single action, and red on navy is the masthead's own pairing */
.cta-photo .btn-brand {
  background: var(--script);
  color: #fff;
  box-shadow: 0 8px 22px rgba(255, 23, 31, 0.32);
}
.cta-photo .btn-brand:hover {
  background: #E30F17;
  color: #fff;
  box-shadow: 0 12px 28px rgba(255, 23, 31, 0.4);
}
.cta-photo .btn--outline {
  border-color: rgba(255, 255, 255, 0.42);
  background: transparent;
  color: #fff;
}
.cta-photo .btn--outline:hover {
  border-color: #fff;
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
}

/* 4 — stacked, the two buttons own the full width and read as one pair */
@media (max-width: 899px) {
  .cta-photo__actions { width: 100%; padding-top: 0; }
  .cta-photo__actions .btn { flex: 1 1 100%; justify-content: center; }
}
@media (max-width: 599px) {
  .cta-photo__note { display: none; }
  .cta-photo__slogan { font-size: 1.375rem; }
}

/* ==================================================================== *
 * 6. Hero action bar — RETIRED
 *
 * The trust chips + CTAs that used to close the hero were removed at the
 * client's request, and the 75px empty spacer that held their footprint has
 * gone with them: on a white page it was 75px of nothing between the banner
 * and "Why Malaysia", stacked on top of the lead-in that section already
 * pays. The band now ends on the photograph.
 *
 * The bar's own styles were deleted too (nothing referenced them any more).
 * Restoring the bar means putting the markup back inside .hero and re-adding
 * the .hero-actions* rules here.
 * ==================================================================== */

/* anchors must clear the fixed masthead */
main [id] { scroll-margin-top: calc(var(--header-h) + 1rem); }

/* ==================================================================== *
 * 7. Process step number — rendered as an accent pill (see §10b)
 * ==================================================================== */
.flow__number {
  position: absolute;
  top: 0;
  right: 0;
  color: var(--line-2);
  font-family: var(--font-display);
  font-size: 0.8125rem;
  font-weight: 800;
  letter-spacing: 0.06em;
}

/* ==================================================================== *
 * 8. Fixes to earlier sections
 * ==================================================================== */

/* Above 1024px all four partner cards are already in view, so the carousel
   arrows have nothing to do — hiding them reads better than two permanently
   greyed-out buttons. */
@media (min-width: 1024px) {
  .partner-universities__nav { display: none; }
}

/* the closing CTA gained a WhatsApp fallback next to the booking button —
   the rules moved into §5 when the band was rebuilt */

/* ==================================================================== *
 * 9. Modern light-section treatment
 *
 * The sections below the navy bands were flat white / flat #F8FAFC with 1px
 * hairline borders and pastel tiles, so the alternation was invisible and the
 * whole light half of the page read as one grey wash.
 *
 * This block gives them their own depth: a tinted gradient with a faint dot
 * matrix that echoes the blueprint in "Why Malaysia", layered shadows in
 * place of hard borders, and soft-UI tiles. Everything is homepage-scoped
 * (home.css is only loaded by home_v2.html), so /about, /contact and
 * /universities keep the plain shared treatment from site.css.
 * ==================================================================== */

/* ---- 9a. Section surfaces ------------------------------------------ *
 * Tinted: a cool gradient wash + a dot matrix, so the white/tinted
 * alternation is actually legible. White: one soft glow behind the heading
 * so it is not dead flat. */
/* ---- 9a. Section surfaces ------------------------------------------ *
 * ONE surface for the whole page: white, with the same faint brand glow
 * everywhere. The per-accent tints (pink behind the process section, blue
 * behind the FAQ) are gone — nothing changes colour as you scroll.
 *
 * The accent still drives the eyebrow, the icons and the cards; it just no
 * longer tints the page. `.section--alt` stays in the markup so the accent
 * hooks keep working, but it is no longer a different surface.
 *
 * The grid texture is NOT here — it is §13's masked ::after, which lets it
 * bloom out of the top of a section and fade instead of tiling edge to edge.
 * ==================================================================== */
.section--alt {
  background-color: var(--surface);
  background-image: radial-gradient(95% 80% at 50% 0%, rgba(37, 89, 232, 0.055), transparent 62%);
  background-repeat: no-repeat;
}
/* white sections share the identical wash, so the two are indistinguishable */
main .section:not(.section--alt):not(.section--ink) {
  background-image: radial-gradient(95% 80% at 50% 0%, rgba(37, 89, 232, 0.055), transparent 62%);
  background-repeat: no-repeat;
}

/* ---- 9b. Section heads --------------------------------------------- */
main .section-head h2 {
  font-size: clamp(1.85rem, 3.8vw, 2.7rem);
  line-height: 1.08;
  letter-spacing: -0.032em;
}
/* a light rim + inner highlight reads as a chip rather than a flat label */
.eyebrow {
  border-color: rgba(37, 89, 232, 0.16);
  background: linear-gradient(180deg, #FFFFFF 0%, var(--brand-50) 100%);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05), inset 0 1px 0 #FFFFFF;
}

/* ---- 9c. Cards ------------------------------------------------------ *
 * Layered two-stop shadows and a roomier radius replace the flat 1px box.
 * Only the interactive ones lift — a plain card is not a link, so it must
 * not advertise itself as clickable. */
.card {
  border-color: rgba(15, 23, 42, 0.06);
  border-radius: 16px;
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04),
              0 12px 26px -16px rgba(15, 23, 42, 0.22);
}
.card--link:hover {
  border-color: rgba(37, 89, 232, 0.30);
  box-shadow: 0 2px 4px rgba(15, 23, 42, 0.05),
              0 22px 42px -20px rgba(37, 89, 232, 0.38);
  transform: translateY(-4px);
}

/* ---- 9d. Track record strip ----------------------------------------- *
 * A deliberately thin white band: one row of editorial numerals sitting
 * between the two dark sections. No card, no panel, no chrome — the point is
 * to be read at a glance and get out of the way, so the only decoration is a
 * short accent rule that marks it as deliberate rather than a gap. */
.stats-strip {
  position: relative;
  background: #FFFFFF;
  padding-block: clamp(1.5rem, 2.6vw, 2rem);
}
.stats-strip::before {
  content: '';
  position: absolute;
  top: 0;
  left: 50%;
  width: 56px;
  height: 3px;
  border-radius: 0 0 3px 3px;
  background: linear-gradient(90deg, var(--accent-500), var(--accent-600));
  transform: translateX(-50%);
}
.strip {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.5rem 0;
  margin: 0;
  padding: 0;
  list-style: none;
}
.strip__item {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3125rem;
  padding-inline: 0.75rem;
  text-align: center;
}
/* hairline dividers, only between items sharing a row */
.strip__item::before {
  content: '';
  position: absolute;
  top: 8%;
  bottom: 8%;
  left: 0;
  width: 1px;
  background: var(--line);
}
.strip__item:nth-child(odd)::before { display: none; }
.strip__num {
  color: var(--ink);
  font-family: var(--font-display);
  font-size: clamp(1.625rem, 3.2vw, 2.125rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.04em;
}
.strip__label {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  color: var(--muted);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.strip__label i { color: var(--accent-500); font-size: 0.6875rem; }

@media (min-width: 800px) {
  .strip { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0; }
  /* restated at the same specificity as the mobile rule above, otherwise the
     :nth-child(odd) reset keeps hiding every other divider on desktop */
  .strip__item::before { display: block; }
  .strip__item:nth-child(odd)::before { display: block; }
  .strip__item:first-child::before { display: none; }
}
@media (max-width: 419px) {
  .strip__label { font-size: 0.625rem; letter-spacing: 0.06em; }
}

/* ---- 9e. Process connector ------------------------------------------ *
 * A dashed track through the gutter reads as a journey, so the arrow glyph
 * is retired — the numbered steps already carry the order. */
@media (min-width: 900px) {
  .flow__step:not(:last-child)::after {
    content: '';
    top: 23px;
    right: -2.25rem;
    width: 2rem;
    height: 2px;
    transform: none;
    background-image: linear-gradient(90deg, var(--brand-300) 55%, transparent 0);
    background-size: 9px 2px;
    background-repeat: repeat-x;
    opacity: 0.8;
  }
}

/* ---- 9g. FAQ — the answer panel ------------------------------------ *
 * Was a simulated support thread: a chat header bar, avatars and bubble
 * pairs. That is 659px for five questions, and most of it is chrome rather
 * than answers — the header bar alone is 74px, and every collapsed question
 * carried a bubble, a face and a dashed divider on top of the question.
 *
 * It is now one framed panel of hairline-separated disclosures. Same native
 * <details> per question, so the accessibility story is unchanged and there
 * is still no JavaScript, but the section loses roughly a third of its
 * height and reads as part of this page: the hairline rules and the tabular
 * numerals are the language the route and the partner matrix already speak.
 *
 * The open state carries the only red on the section — a rule down the side
 * of the answer, next to the brand-blue filled toggle — so the eye can find
 * the one question that is expanded.
 * -------------------------------------------------------------------- */
.qa {
  border: 1px solid var(--line);
  border-radius: 20px;
  background: var(--surface);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04),
              0 24px 50px -34px rgba(15, 23, 42, 0.32);
}
.qa__item + .qa__item { border-top: 1px solid var(--line); }

/* the question row is the hit area, so it stands a real tap target tall */
.qa__q {
  display: flex;
  align-items: center;
  gap: 0.9375rem;
  padding: 0.75rem 1.375rem;
  cursor: pointer;
  list-style: none;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 0.875rem;
  font-weight: 700;
  line-height: 1.45;
  transition: background-color 0.18s var(--ease);
}
.qa__q::-webkit-details-marker { display: none; }
.qa__q:focus-visible {
  outline: 3px solid rgba(37, 89, 232, 0.45);
  outline-offset: -3px;
}
/* fixed-width numeral box, so every question starts on the same left edge
   and the answers can be indented to match it exactly */
.qa__num {
  flex: none;
  width: 1.5rem;
  color: var(--line-2);
  font-size: 0.6875rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
  transition: color 0.18s var(--ease);
}
.qa__text { flex: 1 1 auto; }

/* the +/− is drawn from two bars, not typed, so it cannot fall out of sync
   with the font and it inherits the row's colour for free */
.qa__sign {
  position: relative;
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--brand-50);
  transition: background-color 0.2s var(--ease);
}
.qa__sign::before,
.qa__sign::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 9px;
  height: 1.5px;
  border-radius: 2px;
  background: var(--brand-700);
  transform: translate(-50%, -50%);
}
.qa__sign::after {
  transform: translate(-50%, -50%) rotate(90deg);
  transition: transform 0.22s var(--ease), opacity 0.22s var(--ease);
}

/* the answer, indented to the question's left edge */
.qa__a {
  padding: 0 1.375rem 1rem calc(1.375rem + 1.5rem + 0.9375rem);
  color: var(--body);
  font-size: 0.875rem;
  line-height: 1.65;
}
.qa__a p {
  padding-left: 0.875rem;
  border-left: 2px solid var(--brand-100);
}

/* states ---------------------------------------------------------------- */
.qa__q:hover { background: var(--brand-50); }
.qa__q:hover .qa__num { color: var(--brand-500); }
.qa__item[open] .qa__num { color: var(--brand-600); }
.qa__item[open] .qa__sign { background: var(--brand-600); }
.qa__item[open] .qa__sign::before { background: #FFFFFF; }
.qa__item[open] .qa__sign::after {
  background: #FFFFFF;
  transform: translate(-50%, -50%) rotate(90deg) scaleX(0);
  opacity: 0;
}
.qa__item[open] .qa__a p { border-left-color: var(--accent-500); }
/* the answer slides in rather than just appearing */
.qa__item[open] .qa__a { animation: qa-in 0.28s var(--ease); }
@keyframes qa-in {
  from { opacity: 0; transform: translateY(-5px); }
  to   { opacity: 1; transform: none; }
}

/* the panel's closing row, tinted so the panel ends on an invitation rather
   than stopping dead after the fifth answer */
.qa__more {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  min-height: 44px;
  padding: 0.625rem 1.375rem;
  border-top: 1px solid var(--line);
  border-radius: 0 0 19px 19px;
  background: linear-gradient(180deg, var(--brand-50) 0%, #FFFFFF 100%);
  color: var(--brand-800);
  font-family: var(--font-display);
  font-size: 0.8125rem;
  font-weight: 700;
  transition: background-color 0.18s var(--ease);
}
.qa__more i:first-child { color: var(--brand-500); }
.qa__more i:last-child {
  margin-left: auto;
  font-size: 0.75rem;
  transition: transform 0.18s var(--ease);
}
.qa__more:hover { background: var(--brand-100); }
.qa__more:hover i:last-child { transform: translateX(3px); }

/* narrow phones: the numeral box spends width the question needs */
@media (max-width: 559px) {
  .qa { border-radius: 16px; }
  .qa__num { display: none; }
  .qa__q { padding: 0.75rem 1.125rem; }
  .qa__a { padding: 0 1.125rem 1rem; }
  .qa__more { padding: 0.625rem 1.125rem; border-radius: 0 0 15px 15px; }
}

/* ---- 9h. Student stories — the journey tape ------------------------- *
 * Was a three-card grid ~576px tall. It is now a single band: one
 * edge-to-edge tape of testimonial cards that never stops moving, which
 * takes the section down to roughly a third of its height and adds the one
 * thing the page was missing — motion.
 *
 * The `country` field already arrives as "Malaysia → Australia", so each
 * card draws the student's actual route as a dashed flight path. That
 * echoes the plane on the header rule and says "study abroad" far better
 * than a line of plain text.
 *
 * Seamless loop: the track renders the list twice and translates by exactly
 * -50%. That only lands perfectly when the spacing is a margin on each card
 * rather than a `gap` on the track — a gap would leave one fewer gap in the
 * duplicate and the loop would visibly jump every cycle.
 * ---------------------------------------------------------------------- */
.stories-tape {
  border-top: 1px solid var(--line);
  background: #FFFFFF;
  padding-block: clamp(1.375rem, 2.5vw, 1.875rem);
}

/* compact one-line head ---------------------------------------------- */
.tape__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  margin-bottom: clamp(0.875rem, 1.6vw, 1.25rem);
}
.tape__head .eyebrow { margin-bottom: 0; }
.tape__head h2 {
  font-size: clamp(1.0625rem, 1.6vw, 1.25rem);
  line-height: 1.2;
  letter-spacing: -0.02em;
}
/* Pause control. Auto-moving content has to be pausable, and a hover-only
   affordance is no use to touch or keyboard users (WCAG 2.2.2). */
.tape__toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  margin-left: auto;
  min-height: 36px;
  padding: 0.25rem 0.75rem;
  border: 1px solid var(--line-2);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--muted);
  font-family: var(--font-sans);
  font-size: 0.75rem;
  font-weight: 700;
  cursor: pointer;
  transition: border-color 0.18s var(--ease), color 0.18s var(--ease);
}
.tape__toggle:hover {
  border-color: var(--brand-300);
  color: var(--brand-700);
}
.tape__toggle i { font-size: 0.6875rem; }

/* the moving band ---------------------------------------------------- */
.tape {
  overflow: hidden;
  /* A short soft edge rather than a long fade: a wide gradient leaves the half
     cards at both ends ghosted and they read as broken instead of cropped. */
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 2.5%, #000 97.5%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 2.5%, #000 97.5%, transparent 100%);
}
.tape__track {
  display: flex;
  width: max-content;
  /* Cards stay equal height. Note the trade-off: `width: max-content` runs an
     intrinsic pass where the meta row is measured at full max-content width.
     If that exceeds the card the meta "wraps" in that pass only, and the
     track ends up ~1 line taller than the cards — harmless here because the
     cards stretch and their content is centred, but it is why the meta must
     be given headroom to fit on one line (see .pax width below). */
  align-items: stretch;
  animation: tape-run 48s linear infinite;
}
.tape:hover .tape__track,
.tape.is-paused .tape__track { animation-play-state: paused; }
@keyframes tape-run {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* one story ----------------------------------------------------------- */
.pax {
  display: flex;
  flex: none;
  /* centred, not top-aligned: whatever height the row settles on, the avatar
     and the text sit as one block rather than pinned to the top */
  align-items: center;
  gap: 0.875rem;
  /* deliberately wider than the text needs: the meta row below has to fit
     origin + path + destination + university on ONE line, and at 36rem it
     only cleared that by 2px */
  width: min(38rem, 84vw);
  margin-right: 0.875rem;
  padding: 0.9375rem 1.125rem;
  border: 1px solid rgba(15, 23, 42, 0.07);
  border-radius: 16px;
  background: #FFFFFF;
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04),
              0 16px 30px -22px rgba(15, 23, 42, 0.34);
}
.pax__avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 46px;
  height: 46px;
  overflow: hidden;
  border: 1px solid var(--accent-200);
  border-radius: 50%;
  background: linear-gradient(150deg, #FFFFFF 0%, var(--accent-50) 100%);
  color: var(--accent-700);
  font-family: var(--font-display);
  font-size: 1.0625rem;
  font-weight: 800;
}
.pax__avatar img { width: 100%; height: 100%; object-fit: cover; }
.pax__body { min-width: 0; }
/* two lines of quote, never more — this is what keeps the band short */
.pax__quote {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  color: var(--ink-2);
  font-size: 0.875rem;
  line-height: 1.5;
}
.pax__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.1875rem 0.625rem;
  margin-top: 0.5rem;
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 600;
}
.pax__meta .stars { font-size: 0.6875rem; }
.pax__name { color: var(--ink); font-weight: 700; }
.pax__route {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}
/* the dashed flight path between origin and destination */
.pax__path {
  position: relative;
  flex: none;
  width: 30px;
  height: 1px;
  /* --accent-200 is too pale to read as a line at 1px, so the dashes are
     mixed from the accent at ~40% instead */
  background-image: linear-gradient(90deg, rgba(255, 23, 31, 0.4) 55%, transparent 0);
  background-size: 6px 1px;
  background-repeat: repeat-x;
}
.pax__path i {
  position: absolute;
  top: 50%;
  left: 50%;
  /* the white gutters stop the dashes striking through the aircraft */
  padding: 0 3px;
  background: #FFFFFF;
  color: var(--accent-500);
  font-size: 0.5625rem;
  transform: translate(-50%, -50%) rotate(45deg);
}
.pax__uni {
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.pax__uni::before {
  content: '·';
  margin-right: 0.5rem;
  color: var(--line-2);
}
/* once the meta row stacks, the separator would start the university's line */
@media (max-width: 619px) {
  .pax__uni::before { display: none; }
}

/* motion is decoration: without it the tape becomes manually scrollable */
@media (prefers-reduced-motion: reduce) {
  .tape__track { animation: none; }
  .tape {
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: thin;
  }
}

/* ==================================================================== *
 * 10. Section accents, dark bands and split layouts
 *
 * §9 gave the light sections depth, but they were still all the same shape
 * in almost the same colour — centred chip, heading, grid of white cards,
 * seven times over. This block adds a per-section accent colour, two dark
 * bands to punctuate the run, and two alternative head layouts.
 * ==================================================================== */

/* ---- 10a. Accent tokens --------------------------------------------- *
 * Every .section declares an accent; the accent-* classes on the markup
 * only re-point the tokens. Everything accent-coloured below reads from
 * them, so a new section needs one class and nothing else. */
.section {
  --sec: var(--brand-600);
  --sec-ink: var(--brand-800);
  --sec-soft: var(--brand-100);
  --sec-line: rgba(37, 89, 232, 0.18);
  --sec-glow: rgba(37, 89, 232, 0.6);
  --sec-tint: rgba(37, 89, 232, 0.06);
  --sec-wash: rgba(37, 89, 232, 0.10);
  --sec-bg1: #F3F7FE;
  --sec-bg2: #E6EDF8;
}

.accent-blue {
  --sec: #2559E8;
  --sec-ink: #1B3FAF;
  --sec-soft: #DBE8FE;
  --sec-line: rgba(37, 89, 232, 0.2);
  --sec-glow: rgba(37, 89, 232, 0.6);
  --sec-tint: rgba(37, 89, 232, 0.07);
  --sec-wash: rgba(37, 89, 232, 0.12);
  --sec-bg1: #F3F7FE;
  --sec-bg2: #E5ECF9;
}
.accent-red {
  --sec: #FF171F;
  --sec-ink: #B80B12;
  --sec-soft: #FFDCDC;
  --sec-line: rgba(255, 23, 31, 0.22);
  --sec-glow: rgba(255, 23, 31, 0.5);
  --sec-tint: rgba(255, 23, 31, 0.07);
  --sec-wash: rgba(255, 23, 31, 0.13);
}
.accent-violet {
  /* re-pointed to brand blue — violet was a fourth hue */
  --sec: #2559E8;
  --sec-ink: #1B3FAF;
  --sec-soft: #DBE8FE;
  --sec-line: rgba(37, 89, 232, 0.2);
  --sec-glow: rgba(37, 89, 232, 0.6);
  --sec-tint: rgba(37, 89, 232, 0.07);
  --sec-wash: rgba(37, 89, 232, 0.12);
}
.accent-teal {
  /* re-pointed to the logo red — teal was a fourth hue */
  --sec: #FF171F;
  --sec-ink: #B80B12;
  --sec-soft: #FFDCDC;
  --sec-line: rgba(255, 23, 31, 0.22);
  --sec-glow: rgba(255, 23, 31, 0.5);
  --sec-tint: rgba(255, 23, 31, 0.07);
  --sec-wash: rgba(255, 23, 31, 0.13);
}

/* ---- 10b. Accent recolouring ---------------------------------------- *
 * One place recolours every accent-bearing element in a section. */
.section .eyebrow {
  border-color: var(--sec-line);
  background: linear-gradient(180deg, #FFFFFF 0%, var(--sec-soft) 100%);
  color: var(--sec-ink);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05), inset 0 1px 0 #FFFFFF;
}
.section .eyebrow i { color: var(--sec); }

/* ---- 10c. One page, one pattern ------------------------------------- *
 * The homepage was built in two passes and it showed. Everything above the
 * partner-universities band used a 1440px inner grid with its own padding,
 * a bare uppercase eyebrow and a translucent pill; everything below used
 * the 1180px .wrap grid, --section-y rhythm and the accent pill eyebrow.
 * Three content widths and three eyebrow styles read as two different
 * sites stitched together. These rules collapse them into one.
 *
 * Exceptions are deliberate and still full-bleed: the hero banner (which
 * opens the page), and the thin stats / stories strips.
 * ------------------------------------------------------------------- */

/* 1 — the same content width as .wrap, everywhere */
.why-malaysia__inner,
.partner-universities__inner {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* 2 — the same vertical rhythm as .section */
.why-malaysia,
.why-malaysia__inner,
.partner-universities__inner {
  min-height: 0;
}
.why-malaysia__inner,
.partner-universities__inner {
  padding-block: var(--section-y);
}

/* 3 — one eyebrow: the accent pill used by .section, restyled for these two
 *     bands, which are white now. (The closing band used to need a third
 *     variant for its dark photograph; it has no pill any more — see §5.) */
.why-malaysia__eyebrow,
.partner-universities__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  width: fit-content;
  margin-bottom: 1rem;
  padding: 0.375rem 0.875rem;
  border: 1px solid var(--brand-100);
  border-radius: var(--r-pill);
  background: var(--brand-50);
  color: var(--brand-800);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 1.4;
  text-transform: uppercase;
}
.why-malaysia__eyebrow i,
.partner-universities__eyebrow i {
  color: var(--brand-600);
  font-size: 0.75rem;
}

/* 4 — the stories strip sits between three white sections now, so it is white
 *     too and relies on its hairline rules to read as a separator. */
.stories-tape {
  background: var(--surface);
  border-block: 1px solid var(--line);
}
.section .flow__icon {
  border-color: var(--sec-line);
  background: linear-gradient(150deg, #FFFFFF 0%, var(--sec-soft) 100%);
  color: var(--sec-ink);
  box-shadow: inset 0 1px 0 #FFFFFF, 0 8px 16px -10px var(--sec-glow);
}

/* the step number becomes an accent pill rather than grey type */
.section .flow__number {
  position: absolute;
  top: 0;
  right: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  height: 22px;
  padding: 0 8px;
  border-radius: var(--r-pill, 999px);
  background: var(--sec-soft);
  color: var(--sec-ink);
  font-family: var(--font-display);
  font-size: 0.6875rem;
  font-weight: 800;
  letter-spacing: 0.06em;
}

/* ---- 10c. Dark bands ------------------------------------------------- *
 * The same blueprint language as "Why Malaysia", so every dark stretch on
 * the page reads as one family. */
.section--ink {
  position: relative;
  background-color: #071A33;
  background-image:
    radial-gradient(circle at 0 0, var(--bp-dot) 0.9px, transparent 2.4px),
    linear-gradient(var(--bp-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--bp-line) 1px, transparent 1px),
    radial-gradient(circle at 86% 10%, rgba(37, 89, 232, 0.30), transparent 58%),
    radial-gradient(circle at 4% 100%, rgba(255, 23, 31, 0.13), transparent 52%);
  background-size: var(--bp-size) var(--bp-size), var(--bp-size) var(--bp-size),
                   var(--bp-size) var(--bp-size), 100% 100%, 100% 100%;
  background-position: -1px -1px, -1px -1px, -1px -1px, center, center;
  background-repeat: repeat, repeat, repeat, no-repeat, no-repeat;
  color: #FFFFFF;
}
.section--ink .section-head h2,
.section--ink h3 { color: #FFFFFF; }
.section--ink .section-head p { color: rgba(255, 255, 255, 0.76); }
.section--ink .section-head p a { color: var(--accent-bright); }
.section--ink .eyebrow {
  border-color: rgba(255, 255, 255, 0.22);
  background: rgba(255, 255, 255, 0.10);
  color: #FFFFFF;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18);
}
.section--ink .eyebrow i { color: var(--mast-accent); }

/* glass cards on the dark bands */
.section--ink .card {
  border-color: rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.07);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18), 0 20px 40px -24px rgba(0, 0, 0, 0.7);
}
.section--ink .card:hover {
  border-color: rgba(255, 255, 255, 0.3);
  background: rgba(255, 255, 255, 0.11);
}
.section--ink .card__text { color: rgba(255, 255, 255, 0.76); }

/* ---- 10d. Split head + FAQ rail -------------------------------------- *
 * Two sections break the centred stack: Services puts its way-out beside
 * the copy, FAQ keeps a sticky rail on the left. */
.split-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.25rem 2rem;
  margin-bottom: clamp(2rem, 4vw, 3rem);
}
.split-head .section-head { max-width: 42rem; margin-bottom: 0; }
.split-head__aside { flex: none; }
/* "See the full Study in Malaysia service" is long enough to push past a
   320px viewport and create horizontal scroll, so on narrow screens the
   aside drops to its own full-width row and wraps instead. */
@media (max-width: 719px) {
  .split-head__aside { flex: 1 1 100%; }
}

.faq-layout { display: grid; gap: clamp(1.75rem, 3vw, 3rem); }
.faq-layout .section-head { margin-bottom: 0; }

/* ---- the FAQ intro column -------------------------------------------- *
 * The copy on its own read as a wall of type: in a 368px column the headline
 * orphaned "most" onto a third line and the paragraph ran to four, so the
 * column was all text and no structure.
 *
 * Three fixes. `text-wrap: balance` evens the headline to two lines and
 * kills the orphan. "most" is set in Caveat and the logo red — the same
 * device the hero banner uses on "Malaysia", so the section reads as this
 * brand rather than as a stock accordion. And a hairline splits the copy
 * from the action, which gives the column an order you can see at a glance:
 * label, headline, explanation, rule, do-something.
 * --------------------------------------------------------------------- */
/* NB: `main .section-head h2` is (0,1,2), so a bare `.faq-intro h2` (0,1,1)
   loses to it however late it appears. Two classes outrank it.
   text-wrap: balance does nothing at this width — "Questions students" alone
   is ~385px and this column is 368, so a two-line split does not exist. It
   still earns its place at ≤899px, where the column becomes the full 736px
   and the headline would otherwise strand one word. Tightened leading does
   the real work: three lines at 1.04 instead of 1.08, which is also simply
   better for a heading this size. */
.section-head.faq-intro h2 {
  text-wrap: balance;
  line-height: 1.04;
}
.faq-intro p { text-wrap: pretty; }
/* The <em> is the real emphasis of the sentence anyway — this just draws it.
   line-height is held under the heading's own so the taller script glyph
   cannot open the line box up and push the column taller. */
.section-head.faq-intro h2 em {
  padding-right: 0.04em;
  color: var(--script);
  font-family: var(--font-script);
  font-size: 1.25em;
  font-style: normal;
  font-weight: 700;
  line-height: 0.78;
  letter-spacing: 0;
}
/* The rule is what gives the column an order you can read at a glance:
   label, headline, explanation, rule, do-something. Button and note share a
   row so the block stays inside the height the panel sets. */
.faq-intro__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1rem;
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
}
.faq-intro__note {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 600;
}
.faq-intro__note i { flex: none; color: var(--brand-500); font-size: 0.75rem; }

@media (min-width: 900px) {
  .faq-layout {
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.5fr);
    align-items: start;
  }
  .faq-layout .section-head {
    position: sticky;
    top: calc(var(--header-h) + 2.5rem);
  }
}

/* ==================================================================== *
 * 11. PARTNER UNIVERSITIES — hairline matrix
 *
 * A centred head above an edge-to-edge matrix of tile + label cells, where
 * the 1px grid gap IS the divider rule. Replaces the split card row
 * (copy left, four photo cards right). Rendered in UniDovo's own navy band
 * rather than the reference's light treatment, so it keeps its place in the
 * page rhythm set by section 10c.
 * ==================================================================== */
.partner-universities {
  position: relative;
  width: 100%;
  overflow: hidden;
  /* Head room as usual; the TAIL is only a focus-ring guard. This band sits
     directly above #process, whose pinned stage opens with its own 124px
     lead-in so the eyebrow clears the fixed masthead while the section is
     held. That lead IS the gap, so a tail here just stacks a second one at
     the same seam — at --section-y it read as a ~240px hole. 0.5rem is the
     smallest value that still contains `.btn`'s 3px focus outline + 2px
     offset inside the band's `overflow: hidden`. */
  padding-block: var(--section-y) 0.5rem;
  background: var(--surface);
  color: var(--ink);
}

/* The band is white now, so .section-head h2/p already read correctly from
   site.css. Only the eyebrow needed re-pointing at the brand blue that every
   other section on the page uses. */
.partner-universities .eyebrow {
  border-color: var(--brand-100);
  background: var(--brand-50);
  color: var(--brand-800);
}
.partner-universities .eyebrow i { color: var(--brand-600); }

/* the matrix ------------------------------------------------------- */
.pu-grid {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 1px;
  margin: 0;
  padding: 0;
  list-style: none;
  /* Internal rules ONLY. This colour paints the whole grid box and the opaque
     cells cover it, so it survives only in the 1px gaps — which is what draws
     the dividers. No border, no radius, no overflow clip: the matrix has to
     bleed to its own edges, as in the source. */
  background: var(--line);
}
.pu-cell { background: var(--surface); }
.pu-cell__link {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  min-height: 112px;
  padding: 1.375rem 0.75rem;
  text-align: center;
  color: inherit;
  text-decoration: none;
  transition: background-color 0.2s var(--ease);
}
.pu-cell__link:hover {
  background: var(--surface-2);
  text-decoration: none;
}
.pu-cell__link:focus-visible {
  outline: 2px solid var(--mast-accent);
  outline-offset: -3px;
}

/* Fallback tile with the university's initials — only reached by a record with
   no logo file (SEGi and MAHSA: both sites sit behind a Cloudflare challenge).
   The gradient mirrors the source's dimensional tile; the flat `background` is
   the fallback for browsers without color-mix(). */
.pu-tile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 58px;
  height: 58px;
  border-radius: 17px;
  background: var(--brand-700);
  background-image: linear-gradient(
    145deg,
    color-mix(in srgb, var(--brand-700) 70%, #fff) 0%,
    var(--brand-700) 52%,
    color-mix(in srgb, var(--brand-700) 86%, #000) 100%
  );
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16);
  color: #fff;
  font-family: var(--font-display);
  font-size: 0.9375rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  transition: transform 0.25s var(--ease), box-shadow 0.25s var(--ease);
}
.pu-cell__link:hover .pu-tile {
  transform: translateY(-3px);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16),
              0 12px 26px rgba(0, 0, 0, 0.3);
}

/* Logo arena. Twelve marks run from a 0.9:1 crest to a 5.3:1 wordmark, so
   containing each inside the same box (the first attempt) still left drawn
   heights from 30px to 64px — the row read as uneven. The fix is to pin the
   drawn HEIGHT instead of the box: height is what the eye compares across a
   row. 26px is the tallest height at which the widest wordmark (INTI, 5.3:1)
   still fits a six-column cell on the narrowest desktop, so every mark is
   drawn at exactly the same height and `max-width: 100%` is only a safety
   net. Widths fall where they fall; that is what a logo wall looks like. */
.pu-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 40px;
  transition: transform 0.25s var(--ease);
}
.pu-logo img {
  width: auto;
  height: 26px;
  max-width: 100%;
  object-fit: contain;
}
.pu-cell__link:hover .pu-logo { transform: translateY(-3px); }

.pu-name {
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 0.8125rem;
  font-weight: 700;
  line-height: 1.35;
  letter-spacing: -0.01em;
}

.pu-actions {
  margin-top: clamp(1.75rem, 3vw, 2.5rem);
  text-align: center;
}

@media (max-width: 1023px) {
  .pu-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (max-width: 639px) {
  .pu-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pu-cell__link { min-height: 104px; padding: 1.25rem 0.5rem; }
}

/* ==================================================================== *
 * 12. ONE BLUEPRINT FOR THE WHOLE PAGE
 *
 * "Why Malaysia" set the house texture: a dot matrix sitting on a 46px rule
 * grid, with two soft brand glows behind the dark bands. Every homepage
 * section now draws that same texture, so the page reads as one continuous
 * surface instead of alternating treatments.
 *
 * Kept SOFT on purpose: the dots are drawn with a wide falloff
 * (solid to 0.9px, fully faded by 2.4px) rather than a hard radius, and the
 * rules sit at very low alpha. The result should look like paper stock, not
 * a spec drawing — which is also why the grid is looser than the old 24px
 * dot matrix the tinted sections used.
 *
 * Only .hero and the closing .cta-photo opt out: both are photographs.
 * Every declaration below is consumed by the rules above via var(), so this
 * block is the single place to tune the texture.
 * ==================================================================== */
:root {
  --bp-size: 46px;
  /* ink for the light sections */
  --bp-dot:  rgba(15, 23, 42, 0.075);
  --bp-line: rgba(15, 23, 42, 0.04);
}

/* Only the closing photo band is still dark, so it is the only one that needs
   the inverted ink. */
.section--ink {
  --bp-dot:  rgba(255, 255, 255, 0.14);
  --bp-line: rgba(255, 255, 255, 0.055);
}

/* the grid tightens on small screens, as it did on "Why Malaysia" */
@media (max-width: 767px) {
  :root { --bp-size: 34px; }
}

/* The two thin strips get it too, so nothing on the page is bare. Prose and
   controls sit directly on them, so they stay lighter here. */
.stats-strip,
.stories-tape {
  --bp-dot:  rgba(15, 23, 42, 0.06);
  --bp-line: rgba(15, 23, 42, 0.035);
  background-image:
    radial-gradient(circle at 0 0, var(--bp-dot) 0.9px, transparent 2.4px),
    linear-gradient(var(--bp-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--bp-line) 1px, transparent 1px);
  background-size: var(--bp-size) var(--bp-size);
  background-position: -1px -1px;
  background-repeat: repeat;
}

/* the partner matrix is opaque, so the blueprint only shows in the band
   around it — which is exactly where it is needed */
.partner-universities {
  background-image:
    radial-gradient(circle at 0 0, var(--bp-dot) 0.9px, transparent 2.4px),
    linear-gradient(var(--bp-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--bp-line) 1px, transparent 1px),
    radial-gradient(circle at 88% 12%, rgba(37, 89, 232, 0.14), transparent 58%),
    radial-gradient(circle at 2% 96%, rgba(255, 23, 31, 0.07), transparent 52%);
  background-size: var(--bp-size) var(--bp-size), var(--bp-size) var(--bp-size),
                   var(--bp-size) var(--bp-size), 100% 100%, 100% 100%;
  background-position: -1px -1px, -1px -1px, -1px -1px, center, center;
  background-repeat: repeat, repeat, repeat, no-repeat, no-repeat;
}

/* ==================================================================== *
 * 13. SECTION TEXTURE — 66px rules, masked
 *
 * Reimplemented to match how the reference site does it, and the mask is
 * the point. The grid is NOT tiled across the section: it sits on a
 * ::after that is then MASKED with a radial gradient, so it blooms out of
 * the top centre and dissolves before it reaches an edge or the bottom.
 * Tile the same grid edge to edge and it reads as wallpaper instead.
 *
 * The dot matrix is gone — the reference is rules only, at ~4.3% ink,
 * which is why it registers as texture rather than as a drawn grid.
 *
 * .hero and the closing .cta-photo opt out: both are photographs.
 * ==================================================================== */
:root {
  --tx-size: 66px;
  --tx-line: rgba(15, 23, 42, 0.043);
}

/* The previous tiled dot+rule grid (§12) is retired. Its background-image
   layers are left in place but paint nothing, because these overrides land
   later at equal specificity. Delete the vars AND those layers together in a
   cleanup pass — they are only kept apart to keep this change reversible. */
.section,
.section--alt,
.section--ink,
.why-malaysia,
.partner-universities,
.stats-strip,
.stories-tape {
  --bp-dot: transparent;
  --bp-line: transparent;
  position: relative;
  /* scopes the z-indexes below so one section's content can never paint over
     the next section's texture */
  isolation: isolate;
}
/* the dark band inverts the ink */
.section--ink { --tx-line: rgba(255, 255, 255, 0.06); }

.section::after,
.section--alt::after,
.section--ink::after,
.why-malaysia::after,
.partner-universities::after,
.stats-strip::after,
.stories-tape::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    linear-gradient(var(--tx-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--tx-line) 1px, transparent 1px);
  background-size: var(--tx-size) var(--tx-size);
  -webkit-mask-image: radial-gradient(78% 62% at 50% 0%, #000 0%, transparent 78%);
          mask-image: radial-gradient(78% 62% at 50% 0%, #000 0%, transparent 78%);
}

/* content has to sit above the texture layer. NB: do NOT add overflow:hidden
   to .section — it would break the sticky .section-head in the FAQ layout. */
.section > *,
.section--alt > *,
.section--ink > *,
.why-malaysia > *,
.partner-universities > *,
.stats-strip > *,
.stories-tape > * {
  position: relative;
  z-index: 1;
}

@media (max-width: 767px) {
  :root { --tx-size: 46px; }
}

/* ==================================================================== *
 * 14. ONE BACKGROUND FOR THE WHOLE SITE
 *
 * Every section now paints NOTHING. The single colour comes from body.
 *
 * This removes the last of the per-section backgrounds, all of which were
 * leftovers rather than decisions:
 *   - 30% blue + 14% red radial glows on "Why Malaysia"   (from navy days)
 *   - 14% blue + 7% red glows on the partner band         (from navy days)
 *   - a 5.5% blue glow on every light section             (accent system)
 *   - the dark-ink vignette on .why-malaysia::before
 *   - the dot/rule layers retired when the grid moved to §13's masked ::after
 *
 * The texture in §13 is deliberately NOT touched: that is the masked grid,
 * which is a pattern on the background, not a background of its own.
 *
 * What is still not white on the page: the two PHOTOGRAPHS (hero banner and
 * closing CTA) and the navy masthead. Those are content, not backgrounds —
 * see the note in the handover.
 *
 * This block supersedes the background declarations in §9a, §12 and the
 * .why-malaysia / .partner-universities rules. Those are now dead.
 * ==================================================================== */
body { background-color: var(--surface); }

/* the photograph fills this band, so the base colour is only ever visible
   if an image fails to load — it should be the page colour, not navy */
.hero { background: var(--surface); }

.why-malaysia,
.why-malaysia::before,
.stats-strip,
.partner-universities,
.section,
.section--alt,
.stories-tape {
  background-color: transparent;
  background-image: none;
}
/* §9a's white-section rule is written `main .section:not(...):not(...)` — three
   classes deep — so the plain `.section` above could not outrank it and the
   Services section kept its glow. Repeat the selector verbatim so order wins. */
main .section:not(.section--alt):not(.section--ink) {
  background-color: transparent;
  background-image: none;
}

/* ---- One padded section per seam ------------------------------------ *
 * Every .section carries a full --section-y top AND bottom, so two that touch
 * spend 158-192px saying "new section" on a page where nothing changes colour
 * — the largest hole anywhere on this page. Where two .section s abut, the
 * second keeps only a lead-in; the first one's tail is already half the gap.
 *
 * Exactly two pairs match today: #process → #founder → #services. Worth
 * knowing: .route-pin.is-armed sets `padding-block: 0`, and this rule would
 * outrank it (0,2,1 vs 0,2,0) — the pin is safe only because the section
 * before it is .partner-universities, which carries no .section class.
 * ------------------------------------------------------------------- */
main > .section + .section {
  padding-top: clamp(1.75rem, 3vw, 2.5rem);
}

/* ==================================================================== *
 * 15. FOUNDER — the portrait plate + the record
 *
 * Ported from the "Meet our CEO" block on jupy.llc (this page's layout
 * reference) and rebuilt on UniDovo's tokens, so it reads as a sibling of the
 * route and the FAQ panel instead of a block pasted in from another site.
 *
 * KEPT from the reference: the outline behind the photo sitting out of true,
 * the four corner ticks, the floating badge, the 38px grid + wash inside the
 * frame, the caption over it, and the two cards beneath the spread.
 *
 * CHANGED for this site:
 *   - no monospace. Small text here is uppercase Inter + letter-spacing, the
 *     same as .strip__label, .site-footer__head and the route's micro-labels.
 *   - the signature is Caveat (--font-script), the site's script face, not a
 *     Georgia italic.
 *   - radii come from the --r scale (--r-lg 20 / --r-sm 10), not raw px.
 *   - everything accent-coloured reads from --sec / --sec-line, so §10b's
 *     accent-* classes still drive the whole section.
 *   - the grid is TWO columns while there is room with the cards underneath.
 *     The reference's three-column variant leaves the bio ~340px inside this
 *     site's 1180px wrap, which is well under a readable measure.
 * ==================================================================== */

/* the head --------------------------------------------------------- */
.founder__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem 1.5rem;
  margin-bottom: clamp(1.75rem, 3.4vw, 2.5rem);
}
/* the reference's right-hand standfirst, minus the monospace */
.founder__note {
  color: var(--muted);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/* the spread ------------------------------------------------------- *
 * Three columns once there is room: plate | copy | record. Below that the
 * record drops to a full-width band under the copy, and below 760px the
 * whole thing stacks. See the responsive block at the end of §15 for why the
 * breakpoints are where they are. */
.founder__spread {
  display: grid;
  gap: clamp(1.75rem, 3vw, 2.25rem);
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas:
    "plate"
    "main"
    "panel";
}
.founder__plate { grid-area: plate; }
.founder__main { grid-area: main; }
.founder__panel { grid-area: panel; }

/* ---- 15a. The plate ------------------------------------------------ *
 * Three layers: the outline, the frame, and the marks/badge/caption on top.
 * The frame is 4:5 because the source portrait is 3:4 — cover takes ~63px off
 * the bottom, so object-position is pushed up rather than centred. */
.founder__plate {
  position: relative;
  max-width: 320px;
  margin-inline: auto;
  /* the column is as tall as the copy beside it, so the frame grows into it
     (the same trick jupy uses: a 4:5 flex-basis plus flex-grow). Without the
     grow the photo stopped at 4:5 while the plate box kept the row height, so
     the offset outline below it had nothing to wrap. */
  display: flex;
  flex-direction: column;
}
.founder__plate::before {
  content: '';
  position: absolute;
  inset: 18px -18px -18px 18px;
  z-index: 0;
  border: 1.5px solid var(--sec-line);
  border-radius: calc(var(--r-lg) + 2px);
}
.founder__mark {
  position: absolute;
  z-index: 2;
  width: 14px;
  height: 14px;
  border: 0 solid var(--sec);
}
.founder__mark--tl { top: -8px; left: -8px; border-top-width: 2px; border-left-width: 2px; }
.founder__mark--tr { top: -8px; right: -8px; border-top-width: 2px; border-right-width: 2px; }
.founder__mark--bl { bottom: -8px; left: -8px; border-bottom-width: 2px; border-left-width: 2px; }
.founder__mark--br { bottom: -8px; right: -8px; border-bottom-width: 2px; border-right-width: 2px; }

.founder__frame {
  position: relative;
  z-index: 1;
  flex: 1 1 auto;
  overflow: hidden;
  aspect-ratio: 4 / 5;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--brand-950);
  box-shadow: 0 30px 50px -34px rgba(15, 23, 42, 0.75);
}
.founder__frame img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 14%;
}
/* the faint rule grid over the photo, faded out two thirds down */
.founder__grid {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  opacity: 0.6;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.06) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.06) 1px, transparent 1px);
  background-size: 38px 38px;
  -webkit-mask-image: linear-gradient(#000, transparent 62%);
  mask-image: linear-gradient(#000, transparent 62%);
}
/* the wash the caption sits on — navy, from the same family as the masthead */
.founder__wash {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background: linear-gradient(
    rgba(19, 36, 83, 0) 40%,
    rgba(19, 36, 83, 0.5) 70%,
    rgba(19, 36, 83, 0.92) 100%
  );
}
.founder__cap {
  position: absolute;
  right: 1.25rem;
  bottom: 1.125rem;
  left: 1.25rem;
  z-index: 4;
}
.founder__sig {
  display: block;
  color: #fff;
  font-family: var(--font-script);
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0;
}
.founder__role {
  display: block;
  margin-top: 0.375rem;
  color: rgba(255, 255, 255, 0.66);
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
/* `.founder__badge` (the "10+ years in Malaysia" pill that floated over the
   corner of the photograph) was removed at the client's request. The same
   figure is the first row of `.founder__record`, so nothing was lost. */

/* ---- 15b. The copy ------------------------------------------------ */
.founder__name {
  margin: 0;
  /* A serif, and the only one on the site. The name is a person, not another
     section heading, and every other heading on this page is the display sans
     — set it in Manrope and it reads as one more section title. Lora at 600
     reads as a professional credit beside Inter. */
  font-family: var(--font-serif);
  font-size: clamp(2.1rem, 3.9vw, 2.85rem);
  font-weight: 600;
  line-height: 1.06;
  letter-spacing: -0.015em;
  color: var(--ink);
}
.founder__rule {
  display: block;
  width: 58px;
  height: 3px;
  margin-top: 1.125rem;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--sec), var(--accent-bright));
}
.founder__bio {
  max-width: 44rem;
  margin-top: 1.125rem;
  color: var(--body);
  font-size: 1.0625rem;
  line-height: 1.7;
}
.founder__bio strong { color: var(--ink); font-weight: 800; }

.founder__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 1.375rem 0 0;
  padding: 0;
  list-style: none;
}
.founder__tag {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  padding: 0.4375rem 0.8125rem;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--body);
  font-size: 0.75rem;
  font-weight: 700;
}
.founder__tag i { color: var(--sec); font-size: 0.6875rem; }

/* Was: an uppercase eyebrow, then two 75-character lines each carrying a red
   icon. Three treatments and four wrapped lines in a 300px column is what read
   as noise. Now one quiet label and two lines that each fit on one line, with
   no icons — and it is the card's only divider, so nothing competes. */
.founder__focus {
  margin-top: 0;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}
.founder__focus-label {
  display: block;
  color: var(--ink);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
}
.founder__focus-item {
  margin-top: 0.5rem;
  color: var(--muted);
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.5;
}

/* ---- 15c. The record ----------------------------------------------- *
 * .card, so it inherits the homepage's layered shadow and 16px radius (§9c)
 * rather than re-inventing a box. */
.founder__panel {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.founder__panel-bar {
  position: absolute;
  inset: 0 0 auto;
  height: 3px;
  background: linear-gradient(90deg, var(--sec), var(--accent-bright));
}
/* three ROWS, not the reference's three columns. At this card's ~360px the
   3-up grid pushed every label into a two-line uppercase stack, and holding
   those apart needed a 2px ink rule plus vertical dividers — three rules and
   six lines of shouting in one small card, which is exactly what read as
   noisy. Rows let each label sit on one line with no rules at all. */
.founder__record {
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  margin: 0 0 1.5rem;
}
.founder__rec {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
}
/* the label takes the leftover width, so the three values line up as one
   right-hand column whatever their own length (without this the numerals start
   wherever each label happens to end, which reads as a ragged edge) */
.founder__rec-label {
  flex: 1 1 auto;
  margin: 0;
  color: var(--body);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: 0;
  text-transform: none;
}
.founder__rec-num {
  flex: 0 0 auto;
  margin: 0;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 900;
  line-height: 1;
  letter-spacing: -0.04em;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.founder__rec-num i { color: var(--sec); font-style: normal; }
/* One button and one quiet link. Two full-width stacked buttons at the bottom
   of a 300px card read as a pair of bars; the secondary is not a second ask,
   so it does not need a second pill. The card keeps two rules in total — the
   brand bar and the divider above this block. */
.founder__cta {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-top: auto;
  padding-top: 1.25rem;
}
.founder__cta .btn { justify-content: center; }
.founder__cta-alt {
  justify-content: center;
  min-height: 40px;
  font-size: var(--fs-sm);
}

/* responsive ------------------------------------------------------- *
 * Plate | copy | record needs 1116px of content, which is exactly what the
 * 1180px --wrap gives at its widest — so the third column only appears once
 * the wrap is full. Between 760 and 1180 the record drops to a band under the
 * copy (where it has the whole width and reads as a stat strip), and below
 * 760px everything stacks. Keeping the record out of the column at those
 * widths is the point: at ~420px wide its three labels stack up and the card
 * turns into a tower, which is what the reference's own 741px column avoids
 * by being 741px — not something this wrap can offer alongside a photo. */
@media (min-width: 760px) {
  .founder__spread {
    grid-template-columns: minmax(0, 320px) minmax(0, 1fr);
    grid-template-areas:
      "plate main"
      "panel panel";
  }
  .founder__plate { max-width: none; margin-inline: 0; }
}
@media (min-width: 1180px) {
  .founder__spread {
    grid-template-columns: minmax(0, 320px) minmax(0, 1fr) minmax(0, 360px);
    grid-template-areas: "plate main panel";
  }
}
/* the plate's offset outline and badge hang outside the photo, so give them
   room back on the narrowest screens instead of letting the section clip them */
@media (max-width: 599px) {
  .founder__plate::before { inset: 14px -12px -12px 14px; }
}

