/* ============================================================================
   premium.css — the "feel" layer.

   Loads last, after editorial.css, and is purely additive: every rule here
   either introduces a new component or refines one the earlier layers already
   drew. Deleting this file (and premium.js) returns the site to its previous
   appearance with nothing else to unpick.

   The brief was a premium, learner-attracting surface in the vein of the
   Driftly template. Four things carry that feeling and they are what this file
   spends its budget on:

     1. COMPOUND SHADOW  — one blurry drop shadow reads as a 2014 card. Depth
        that reads as expensive is six or seven stacked layers, each roughly
        double the last's blur and half its alpha, so the falloff matches how
        light actually behaves.
     2. CONFIDENT TYPE   — large display sizes with genuinely negative tracking
        (-0.04em, not -0.01em), set against tiny wide-tracked labels. The
        contrast between the two is the whole effect.
     3. EXPO EASING      — cubic-bezier(0.16, 1, 0.3, 1). Motion that leaves
        fast and arrives slowly feels like weight; the linear-ish easings that
        ship by default feel like a slideshow.
     4. MASKED REVEALS   — content arriving from behind an edge rather than
        fading in. Fades read as "loading"; masks read as "composed".

   Everything is gated behind prefers-reduced-motion at the foot of the file.
   ========================================================================= */

/* ── 1. tokens ─────────────────────────────────────────────────────────── */
:root {
  /* Brand orange stays the primary. The deep and ember stops give gradients
     and pressed states somewhere to go without introducing a second hue. */
  --px-orange: #f7921e;
  --px-orange-deep: #d97512;
  --px-orange-soft: #ffb757;
  --px-ember: #ff6a13;

  --px-ink: #0b1b54;          /* brand navy — all display type */
  --px-ink-70: rgba(11, 27, 84, 0.70);
  --px-ink-55: rgba(11, 27, 84, 0.55);
  --px-ink-40: rgba(11, 27, 84, 0.40);

  --px-paper: #ffffff;
  --px-sand: #faf7f2;         /* warmer than the old cream — reads richer */
  --px-sand-deep: #f3ede4;
  --px-hair: rgba(11, 27, 84, 0.09);
  --px-hair-soft: rgba(11, 27, 84, 0.055);

  /* Compound depth. Each step keeps every layer of the one before it and adds
     a wider, softer pair on top, so a card growing from rest to hover reads as
     rising through the same light rather than swapping shadows. */
  --px-sh-1:
    0 1px 1px -0.5px rgba(11, 27, 84, 0.055),
    0 2px 2px -1px   rgba(11, 27, 84, 0.05),
    0 4px 4px -2px   rgba(11, 27, 84, 0.04);
  --px-sh-2:
    0 1px 1px -0.5px rgba(11, 27, 84, 0.05),
    0 2px 2px -1px   rgba(11, 27, 84, 0.045),
    0 4px 4px -2px   rgba(11, 27, 84, 0.04),
    0 8px 8px -4px   rgba(11, 27, 84, 0.035),
    0 16px 16px -8px rgba(11, 27, 84, 0.03);
  --px-sh-3:
    0 1px 1px -0.5px rgba(11, 27, 84, 0.05),
    0 2px 2px -1px   rgba(11, 27, 84, 0.045),
    0 4px 4px -2px   rgba(11, 27, 84, 0.04),
    0 8px 8px -4px   rgba(11, 27, 84, 0.04),
    0 16px 16px -8px rgba(11, 27, 84, 0.04),
    0 32px 32px -16px rgba(11, 27, 84, 0.05),
    0 48px 48px -24px rgba(247, 146, 30, 0.10);
  /* A hairline drawn as a shadow rather than a border, so it never adds to the
     box and never fights a border-radius. */
  --px-ring: inset 0 0 0 1px rgba(11, 27, 84, 0.07);
  --px-ring-hot: inset 0 0 0 1px rgba(247, 146, 30, 0.32);

  /* Expo-out. The single most important value in this file. */
  --px-ease: cubic-bezier(0.16, 1, 0.3, 1);
  --px-ease-io: cubic-bezier(0.65, 0, 0.35, 1);
  --px-ease-back: cubic-bezier(0.34, 1.4, 0.64, 1);
  --px-t-1: 0.28s;
  --px-t-2: 0.5s;
  --px-t-3: 0.8s;
  --px-t-4: 1.15s;

  --px-r-sm: 12px;
  --px-r: 18px;
  --px-r-lg: 26px;
  --px-r-xl: 34px;

  --px-odo-h: 1.16em;   /* height of one odometer digit window */
  --px-roll-h: 1.35em;  /* height of one line in a rolling button label */
  --px-rail-h: 84px;    /* the slot every partner logo is fitted into */
}

/* ── 2. reveal primitives ──────────────────────────────────────────────── */
/*
   Word masking. premium.js wraps each word of a display heading in
   .px-w (the mask) around .px-wi (the traveller). The mask carries a little
   vertical padding with matching negative margin so descenders in "g", "y",
   "p" are not sheared off by overflow:hidden — the usual giveaway that a
   split-text effect was bolted on.
*/
.px-w {
  display: inline-block;
  overflow: hidden;
  padding: 0.12em 0.02em 0.18em;
  margin: -0.12em -0.02em -0.18em;
  vertical-align: bottom;
}

.px-wi {
  display: inline-block;
  transform: translateY(105%) rotate(2.5deg);
  opacity: 0;
  transition:
    transform var(--px-t-4) var(--px-ease),
    opacity 0.55s linear;
  transition-delay: var(--px-d, 0ms);
  will-change: transform;
}

.px-split.is-in .px-wi {
  transform: translateY(0) rotate(0deg);
  opacity: 1;
  /* Released once the word has landed. `will-change` promotes each word to its
     own compositor layer, and a page holding a couple of hundred of those for
     the rest of the session costs real memory for no further benefit. */
  will-change: auto;
}

/* Blocks that follow a masked heading: a short, low-amplitude lift. Long
   travel on body copy reads as cheap, so this moves 14px, not 40. */
[data-px-rise] {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity 0.7s var(--px-ease),
    transform 0.9s var(--px-ease);
  transition-delay: var(--px-d, 0ms);
}

[data-px-rise].is-in {
  opacity: 1;
  transform: none;
}

/* Media arriving from behind a wipe, with the picture itself over-scaled and
   settling back. Two speeds moving together is what sells the depth.

   The clip-path sits on the picture, never on the element being observed:
   Chromium folds a target's own clip-path into the rectangle it hands
   IntersectionObserver, so an element clipped to zero height reports an
   intersection ratio of 0 forever and can never trigger its own reveal. */
[data-px-curtain] > img,
[data-px-curtain] > picture > img {
  clip-path: inset(0 0 100% 0);
  transform: scale(1.14);
  transition:
    clip-path var(--px-t-4) var(--px-ease),
    transform 1.6s var(--px-ease);
  transition-delay: var(--px-d, 0ms);
}

[data-px-curtain].is-in > img,
[data-px-curtain].is-in > picture > img {
  clip-path: inset(0 0 0 0);
  transform: scale(1);
}

/* A rule that draws itself in — used under section heads and inside cards. */
[data-px-draw] {
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--px-t-3) var(--px-ease);
  transition-delay: var(--px-d, 0ms);
}

[data-px-draw].is-in { transform: scaleX(1); }

/* ── 3. display typography ─────────────────────────────────────────────── */
body.home-2026 .px-display,
body.inner-page .px-display {
  font-family: var(--heading-font);
  font-weight: 800;
  font-size: clamp(2.7rem, 6.1vw, 5.4rem);
  line-height: 0.99;
  letter-spacing: -0.042em;
  color: var(--px-ink);
  margin: 0;
}

/* Section heads get the same treatment one tier down. The old head was 700 at
   -0.015em, which at 2.8rem still reads as body type scaled up. */
body.home-2026 .hx-section-head h2,
body.inner-page .hx-section-head h2 {
  font-weight: 800;
  font-size: clamp(2.05rem, 3.9vw, 3.35rem);
  line-height: 1.05;
  letter-spacing: -0.035em;
}

body.home-2026 .hx-section-head p,
body.inner-page .hx-section-head p {
  color: var(--px-ink-55);
  font-size: clamp(1.02rem, 0.98rem + 0.28vw, 1.16rem);
  line-height: 1.72;
  letter-spacing: -0.004em;
}

/*
   The eyebrow, with actual presence. It used to be 10.5px at 0.2em tracking
   led by a pulsing dot — the stock "micro label" recipe, which at that size
   reads as a caption apologising for itself rather than as the opening line
   of a section. It is now large enough to be read as type, and the pill
   itself carries a solid tint so the shape does the work the letterspacing
   was straining to do.
*/
body.home-2026 .hx-pill,
body.inner-page .hx-pill {
  font-family: var(--heading-font);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.06em;
  padding: 9px 20px;
  gap: 0;
  border-color: transparent;
  background: color-mix(in srgb, currentColor, transparent 88%);
  backdrop-filter: blur(6px);
}

/* A single word of a display heading, set in orange, is the cheapest way to
   make a headline look art-directed rather than typed. */
.px-hot { color: var(--px-orange); }

/* ── 4. buttons ────────────────────────────────────────────────────────── */
/*
   Two things happen on hover and they have to happen together: a solid colour
   climbs from the bottom edge, and the label rolls up to a duplicate of itself.
   The duplicate is generated by premium.js from the button's own text, so no
   markup changes were needed anywhere on the site.
*/
body.home-2026 .hx-btn-primary,
body.inner-page .hx-btn-primary {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  padding: 16px 32px;
  font-weight: 700;
  letter-spacing: -0.01em;
  box-shadow:
    0 1px 2px rgba(11, 27, 84, 0.10),
    0 6px 14px -4px rgba(247, 146, 30, 0.40),
    inset 0 1px 0 rgba(255, 255, 255, 0.28);
  transition:
    transform var(--px-t-2) var(--px-ease),
    box-shadow var(--px-t-2) var(--px-ease),
    background-color var(--px-t-2) var(--px-ease);
}

/* The climbing fill. inset:auto 0 0 pins it to the bottom edge so growing its
   height moves the top edge upward — a wipe, not a scale. */
body.home-2026 .hx-btn-primary::before,
body.inner-page .hx-btn-primary::before {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  z-index: -1;
  height: 0;
  background: var(--px-ink);
  transition: height var(--px-t-2) var(--px-ease);
}

body.home-2026 .hx-btn-primary:hover,
body.inner-page .hx-btn-primary:hover {
  transform: translateY(-3px);
  box-shadow:
    0 2px 4px rgba(11, 27, 84, 0.10),
    0 14px 28px -8px rgba(11, 27, 84, 0.35),
    inset 0 1px 0 rgba(255, 255, 255, 0.18);
}

body.home-2026 .hx-btn-primary:hover::before,
body.inner-page .hx-btn-primary:hover::before { height: 100%; }

body.home-2026 .hx-btn-primary:active,
body.inner-page .hx-btn-primary:active { transform: translateY(-1px); }

/* Label roll. .px-roll is the clipping frame premium.js inserts; .px-roll-i
   holds the label twice, stacked, and slides up exactly one label height. */
.px-roll {
  display: block;
  position: relative;
  /* The frame has to be exactly one label tall or both copies show at once.
     Its height and the height of each copy are driven by the same token, so
     the -50% travel always lands the second copy precisely where the first
     was, whatever the button's own line-height happens to be. */
  height: var(--px-roll-h);
  overflow: hidden;
}

.px-roll-i {
  display: block;
  transition: transform var(--px-t-2) var(--px-ease);
}

.px-roll-i > span {
  display: block;
  height: var(--px-roll-h);
  line-height: var(--px-roll-h);
}

.px-has-roll:hover .px-roll-i { transform: translateY(-50%); }

/* The round companion button. Rest is a filled disc; hover flips to paper and
   the arrow makes a quarter turn — small, but it is the kind of detail people
   read as care. */
body.home-2026 .hx-round-btn,
body.inner-page .hx-round-btn {
  position: relative;
  overflow: hidden;
  box-shadow:
    0 1px 2px rgba(11, 27, 84, 0.10),
    0 6px 14px -4px rgba(247, 146, 30, 0.36);
  transition:
    transform var(--px-t-2) var(--px-ease-back),
    box-shadow var(--px-t-2) var(--px-ease),
    background-color var(--px-t-1) var(--px-ease),
    color var(--px-t-1) var(--px-ease);
}

body.home-2026 .hx-round-btn:hover,
body.inner-page .hx-round-btn:hover {
  transform: rotate(45deg) scale(1.08);
  box-shadow:
    0 2px 6px rgba(11, 27, 84, 0.12),
    0 14px 26px -8px rgba(11, 27, 84, 0.30);
}

/* Magnetic pull. premium.js writes --px-mx/--px-my from the pointer; the
   element only ever reads them, so if the script is absent nothing shifts. */
[data-px-magnet] {
  transform: translate3d(var(--px-mx, 0px), var(--px-my, 0px), 0);
  transition: transform var(--px-t-2) var(--px-ease);
}

[data-px-magnet].is-pulling { transition: transform 0.14s linear; }

/* A text link that underlines itself from the left and, on the way out,
   leaves from the right rather than retracting — reads as directional. */
.px-link {
  position: relative;
  display: inline-block;
  padding-bottom: 2px;
}

.px-link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1.5px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right center;
  transition: transform var(--px-t-2) var(--px-ease);
}

.px-link:hover::after {
  transform: scaleX(1);
  transform-origin: left center;
}

/* ── 5. the card substrate ─────────────────────────────────────────────── */
/*
   A soft radial follows the pointer across any element carrying .px-lit. It is
   painted by a pseudo-element at 0 opacity so it costs nothing until hovered,
   and it uses the brand orange at very low alpha — enough to warm the surface,
   never enough to tint the content.
*/
.px-lit { position: relative; }

.px-lit::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  opacity: 0;
  border-radius: inherit;
  background: radial-gradient(
    340px circle at var(--px-px, 50%) var(--px-py, 50%),
    rgba(247, 146, 30, 0.16),
    rgba(247, 146, 30, 0.04) 42%,
    transparent 70%
  );
  transition: opacity var(--px-t-2) var(--px-ease);
}

.px-lit:hover::after { opacity: 1; }

/* ── 6. course cards ───────────────────────────────────────────────────── */
/*
   The course card is the single most important object on the site: it is what
   a prospective learner actually decides against. The rebuild leads with the
   picture, puts the social proof (enrolments) where the eye lands after the
   title, and pins price and a go-arrow to a footer that is visually separated
   by a hairline so it reads as an action strip rather than more text.
*/
body .uj-card {
  border: 0;
  border-radius: var(--px-r);
  background: var(--px-paper);
  box-shadow: var(--px-ring), var(--px-sh-1);
  transition:
    transform var(--px-t-2) var(--px-ease),
    box-shadow var(--px-t-2) var(--px-ease);
}

body .uj-card:hover {
  transform: translateY(-6px);
  box-shadow: var(--px-ring-hot), var(--px-sh-3);
}

/* An orange hairline that draws across the top edge on hover — a progress bar
   for a course, which is the right metaphor and costs one pseudo-element. */
body .uj-card::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  z-index: 4;
  background: linear-gradient(90deg, var(--px-orange), var(--px-ember));
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--px-t-3) var(--px-ease);
}

body .uj-card:hover::before { transform: scaleX(1); }

body .uj-card__media {
  aspect-ratio: 16 / 10;
  background: linear-gradient(150deg, var(--px-sand-deep), var(--px-sand));
}

body .uj-card__media img {
  transition: transform 1.1s var(--px-ease);
}

body .uj-card:hover .uj-card__media img { transform: scale(1.07); }

/* A scrim that only exists at the foot of the image, so badges stay legible
   over a bright photograph without greying the whole picture. */
body .uj-card__media::after {
  content: "";
  position: absolute;
  inset: 40% 0 0 0;
  background: linear-gradient(180deg, transparent, rgba(7, 18, 54, 0.42));
  opacity: 0;
  transition: opacity var(--px-t-2) var(--px-ease);
}

body .uj-card:hover .uj-card__media::after { opacity: 1; }

/* The play affordance. Rests hidden, arrives with a little overshoot. */
body .uj-card__play {
  position: absolute;
  right: 14px;
  bottom: 14px;
  z-index: 3;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: var(--px-ink);
  background: rgba(255, 255, 255, 0.94);
  backdrop-filter: blur(10px);
  font-size: 17px;
  opacity: 0;
  transform: translateY(10px) scale(0.8);
  transition:
    opacity var(--px-t-1) var(--px-ease),
    transform var(--px-t-2) var(--px-ease-back),
    background-color var(--px-t-1) var(--px-ease),
    color var(--px-t-1) var(--px-ease);
}

body .uj-card:hover .uj-card__play {
  opacity: 1;
  transform: translateY(0) scale(1);
  background: var(--px-orange);
  color: #ffffff;
}

body .uj-card__body { padding: 18px 18px 0; gap: 0; }

body .uj-card__title {
  font-size: clamp(1rem, 0.96rem + 0.24vw, 1.12rem);
  font-weight: 800;
  line-height: 1.32;
  letter-spacing: -0.022em;
  color: var(--px-ink);
  min-height: 2.64em;
}

body .uj-card:hover .uj-card__title { color: var(--px-ink); }

body .uj-card__subtitle {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-top: 9px;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.005em;
  color: var(--px-ink-55);
}

/* The instructor's initial, as a disc. Cheaper than an avatar we do not have,
   and it stops the byline reading as another line of grey text. */
body .uj-card__who {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 10.5px;
  font-weight: 800;
  color: var(--px-orange-deep);
  background: color-mix(in srgb, var(--px-orange), transparent 86%);
}

body .uj-card__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 14px;
  margin-top: 14px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.055em;
  text-transform: uppercase;
  color: var(--px-ink-40);
}

body .uj-card__meta .uj-meta-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

body .uj-card__meta .uj-meta-item i {
  font-size: 13px;
  color: var(--px-orange);
}

/* Social proof. Deliberately the only coloured line in the body: for someone
   deciding whether to enrol, "412 learners" outranks the module count. */
body .uj-card__proof {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-top: 12px;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: -0.005em;
  color: var(--px-orange-deep);
}

body .uj-card__proof i { font-size: 13.5px; }

body .uj-card__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 16px;
  padding: 15px 18px;
  border-top: 1px solid var(--px-hair-soft);
  background: linear-gradient(180deg, transparent, rgba(250, 247, 242, 0.7));
}

body .uj-price {
  font-size: 17px;
  font-weight: 800;
  letter-spacing: -0.028em;
  color: var(--px-ink);
}

body .uj-price-was {
  margin-left: 8px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--px-ink-40);
  text-decoration: line-through;
}

body .uj-price-free {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #15803d;
  padding: 6px 12px;
  border-radius: 999px;
  background: rgba(21, 128, 61, 0.09);
  box-shadow: inset 0 0 0 1px rgba(21, 128, 61, 0.22);
}

/* The footer arrow. Grows into a labelled pill on hover so the card states its
   action outright instead of relying on the visitor guessing the card clicks. */
body .uj-card__go {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0;
  height: 38px;
  padding: 0 11px;
  border-radius: 999px;
  color: var(--px-ink);
  background: color-mix(in srgb, var(--px-ink), transparent 94%);
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.02em;
  overflow: hidden;
  transition:
    gap var(--px-t-2) var(--px-ease),
    padding var(--px-t-2) var(--px-ease),
    background-color var(--px-t-2) var(--px-ease),
    color var(--px-t-2) var(--px-ease);
}

body .uj-card__go span {
  max-width: 0;
  opacity: 0;
  white-space: nowrap;
  transition:
    max-width var(--px-t-2) var(--px-ease),
    opacity var(--px-t-1) var(--px-ease);
}

body .uj-card:hover .uj-card__go {
  gap: 7px;
  padding: 0 15px;
  background: var(--px-orange);
  color: #ffffff;
}

body .uj-card:hover .uj-card__go span {
  max-width: 120px;
  opacity: 1;
}

body .uj-card__go i {
  font-size: 15px;
  transition: transform var(--px-t-2) var(--px-ease);
}

body .uj-card:hover .uj-card__go i { transform: translateX(1px); }

/* Badges over the media, on glass rather than flat colour. */
body .uj-card__badges { left: 14px; top: 14px; gap: 7px; }

body .uj-badge {
  padding: 5px 11px;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  backdrop-filter: blur(10px);
  box-shadow: 0 2px 8px rgba(7, 18, 54, 0.16);
}

/* ── 7. product cards ──────────────────────────────────────────────────── */
body.home-2026 .hx-shop-card {
  border: 0;
  border-radius: var(--px-r);
  box-shadow: var(--px-ring), var(--px-sh-1);
  transition:
    transform var(--px-t-2) var(--px-ease),
    box-shadow var(--px-t-2) var(--px-ease);
}

body.home-2026 .hx-shop-card:hover {
  transform: translateY(-7px);
  box-shadow: var(--px-ring-hot), var(--px-sh-3);
}

/* Product photography is nearly always shot on white, so the tile needs a
   tinted ground or the picture dissolves into the card. A soft radial keeps
   the goods sitting in light rather than floating on a flat swatch. */
body.home-2026 .hx-shop-card-media {
  aspect-ratio: 1 / 1;
  padding: 14px;
  background:
    radial-gradient(120% 90% at 50% 12%, #ffffff 0%, var(--px-sand) 55%, var(--px-sand-deep) 100%);
}

body.home-2026 .hx-shop-card-media img {
  transition: transform 0.9s var(--px-ease);
  filter: drop-shadow(0 10px 18px rgba(11, 27, 84, 0.12));
}

body.home-2026 .hx-shop-card:hover .hx-shop-card-media img {
  transform: scale(1.06) translateY(-4px);
}

body.home-2026 .hx-shop-view {
  position: absolute;
  inset: auto 0 0 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 11px 0;
  background: var(--px-ink);
  color: #ffffff;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  transform: translateY(100%);
  transition: transform var(--px-t-2) var(--px-ease);
}

body.home-2026 .hx-shop-card:hover .hx-shop-view { transform: translateY(0); }

body.home-2026 .hx-shop-badge {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 2;
  padding: 5px 11px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #ffffff;
  background: var(--px-ink);
  box-shadow: 0 4px 12px rgba(7, 18, 54, 0.22);
}

body.home-2026 .hx-shop-badge--rank {
  background: linear-gradient(120deg, var(--px-orange), var(--px-ember));
}

body.home-2026 .hx-shop-badge--sale { background: #d92d20; }
body.home-2026 .hx-shop-badge--new  { background: #15803d; }

body.home-2026 .hx-shop-card-cat {
  font-size: 10px;
  letter-spacing: 0.14em;
  color: var(--px-orange-deep);
}

body.home-2026 .hx-shop-card-body h3 {
  font-size: 14.5px;
  font-weight: 800;
  letter-spacing: -0.018em;
  line-height: 1.42;
}

body.home-2026 .hx-shop-card-price {
  font-size: 18px;
  font-weight: 800;
  letter-spacing: -0.03em;
}

body.home-2026 .hx-shop-stars { color: var(--px-orange); font-size: 11.5px; letter-spacing: 0.5px; }
body.home-2026 .hx-shop-rating { display: flex; align-items: center; gap: 6px; margin-top: 8px; min-height: 18px; }
/* Nothing has been reviewed yet on most products, and the store renders the
   row regardless. An empty row reserving 26px between the title and the price
   reads as a missing element rather than as breathing space. */
body.home-2026 .hx-shop-rating:empty { display: none; }
body.home-2026 .hx-shop-rating small { color: var(--px-ink-40); font-size: 11px; font-weight: 600; }

body.home-2026 .hx-shop-stock {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #15803d;
}

body.home-2026 .hx-shop-stock::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

body.home-2026 .hx-shop-stock--low { color: var(--px-orange-deep); }
body.home-2026 .hx-shop-stock--out { color: var(--px-ink-40); }

/* ── 8. service tiles ──────────────────────────────────────────────────── */
body.home-2026 .hx-service {
  border-radius: var(--px-r-lg);
  box-shadow: var(--px-sh-1);
  transition:
    transform var(--px-t-2) var(--px-ease),
    box-shadow var(--px-t-2) var(--px-ease);
}

body.home-2026 .hx-service:hover {
  transform: translateY(-6px);
  box-shadow: var(--px-sh-3);
}

body.home-2026 .hx-service img { transition: transform 1.4s var(--px-ease); }
body.home-2026 .hx-service:hover img { transform: scale(1.08); }

body.home-2026 .hx-service .hx-service-icon {
  box-shadow: 0 6px 16px -4px rgba(247, 146, 30, 0.55);
}

/* ── 9. marquee ────────────────────────────────────────────────────────── */
/*
   A logo wall in a static grid says "here are our partners". The same logos
   drifting past continuously say "there are more of us than fit". premium.js
   duplicates the track so the loop is seamless; the mask fades both ends so
   logos dissolve rather than being guillotined by the viewport.
*/
.px-marquee {
  position: relative;
  overflow: hidden;
  padding: 10px 0;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
}

.px-marquee__track {
  display: flex;
  width: max-content;
  align-items: center;
  gap: clamp(38px, 5vw, 82px);
  animation: px-drift var(--px-marquee-dur, 42s) linear infinite;
}

.px-marquee:hover .px-marquee__track { animation-play-state: paused; }

@keyframes px-drift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

/* Every logo arrives at a different aspect ratio. Giving each slot the same
   box and letting the artwork fit inside it is what stops a wide wordmark from
   dwarfing a square crest — sizing by height alone does not, because a short
   wide logo hits the width limit long before the height one. */
.px-marquee__item {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: clamp(140px, 11vw, 190px);
  height: var(--px-rail-h);
}

/* Logos arrive in a dozen different colourways. Desaturating them at rest and
   restoring colour under the pointer is what turns a ransom note into a wall. */
/* Each logo is wrapped in its own link in the source markup, so the anchor —
   not the image — is the grid child. */
.px-marquee__item > a {
  display: grid;
  place-items: center;
  width: 100%;
}

/* The cap is stated in pixels rather than as `max-height: 100%`. A percentage
   max-height resolves against the containing block only when that block's own
   height is definite; the anchor's is not, so the percentage was being treated
   as `none` and every tall logo overflowed the rail and was sheared off by the
   band's mask. */
.px-marquee__item img {
  max-width: 100%;
  max-height: var(--px-rail-h);
  width: auto;
  height: auto;
  object-fit: contain;
  transition: transform var(--px-t-2) var(--px-ease);
}

.px-marquee__item:hover img {
  transform: scale(1.06);
}

/* ── 10. odometer counters ─────────────────────────────────────────────── */
/*
   Numbers that tick up are common; numbers whose digits roll into place on
   separate reels are not, and the difference is a couple of transforms. Each
   digit is a column 0-9 inside a fixed-height window, translated by
   -(digit x 10%) when the block comes into view.
*/
.px-odo {
  display: inline-flex;
  align-items: flex-start;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.px-odo__d {
  display: block;
  overflow: hidden;
  height: 1em;
}

.px-odo__r {
  display: block;
  transform: translateY(0);
  transition: transform 1.5s var(--px-ease);
  transition-delay: var(--px-d, 0ms);
}

.px-odo__r > span {
  display: block;
  height: 1em;
  line-height: 1;
}

.px-odo__s { display: block; }

/*
   main.css already styles every `span` inside a stats item (`.counts
   .stats-item span { display: block }`), and that selector outranks the plain
   class rules above — which laid the reels out one under another instead of
   side by side. These re-state the layout at a specificity that clears it, and
   pin the reel's internals to inherit their size so the fixed 48px from that
   same rule cannot desynchronise a digit's height from its 1em window.
*/
body #counts .stats-item .px-odo {
  display: inline-flex;
  align-items: flex-start;
  font-family: var(--heading-font);
  font-weight: 800;
  font-size: clamp(2.6rem, 4.4vw, 3.9rem);
  line-height: 1;
  letter-spacing: -0.05em;
  /* Orange is the brand's primary and these are the site's proudest numbers;
     the label beneath carries the navy, so the pair still reads as a unit. */
  color: var(--px-orange);
}

body #counts .stats-item .px-odo__d {
  display: block;
  /* Slightly taller than 1em so a numeral's ascender is not shaved by the
     window. The reel travels in percentages of its own height, so the window
     and its cells only have to agree with each other, not with 1em. */
  height: var(--px-odo-h);
  overflow: hidden;
  font-size: inherit;
  line-height: var(--px-odo-h);
}

body #counts .stats-item .px-odo__r,
body #counts .stats-item .px-odo__s {
  display: block;
  font-size: inherit;
  line-height: 1;
  color: inherit;
}

body #counts .stats-item .px-odo__r > span {
  display: block;
  height: var(--px-odo-h);
  font-size: inherit;
  line-height: var(--px-odo-h);
  color: inherit;
}

body.home-2026 #counts .stats-item,
body.inner-page #counts .stats-item {
  position: relative;
  padding: 26px 18px;
}

/* A hairline between the figures rather than a boxed card each. Rules divide;
   boxes fragment. */
body.home-2026 #counts .col-lg-3 + .col-lg-3 .stats-item::before,
body.inner-page #counts .col-lg-3 + .col-lg-3 .stats-item::before {
  content: "";
  position: absolute;
  left: 0;
  top: 18%;
  bottom: 18%;
  width: 1px;
  background: var(--px-hair);
}

body.home-2026 #counts .stats-item p,
body.inner-page #counts .stats-item p {
  margin: 12px 0 0;
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--px-ink-40);
}

@media (max-width: 991.98px) {
  body.home-2026 #counts .col-lg-3 + .col-lg-3 .stats-item::before,
  body.inner-page #counts .col-lg-3 + .col-lg-3 .stats-item::before { display: none; }
}

/* ── 11. the journey band ──────────────────────────────────────────────── */
/*
   Previously a 100vh sticky stage that held still while a tall column of
   words scrolled through it — roughly a full viewport of scroll distance
   per word, five words deep. That read as a wall of near-empty space
   between this section and its neighbours rather than as a deliberate
   effect. This is the same five-step story (Explore → Learn → Build →
   Compete → Lead) as one compact row that fits its own height on the page,
   revealed with an ordinary staggered fade-up as it scrolls into view.
*/
.px-journey {
  position: relative;
  background: var(--px-paper);
  padding-block: clamp(56px, 7vw, 96px);
}

.px-journey__label {
  text-align: center;
  margin-bottom: clamp(32px, 4vw, 52px);
}

.px-journey__label .hx-pill { color: var(--px-orange); }

.px-journey__label h2 {
  margin: 16px 0 0;
  font-family: var(--heading-font);
  font-weight: 800;
  font-size: clamp(1.6rem, 2.6vw, 2.4rem);
  letter-spacing: -0.032em;
  color: var(--px-ink);
}

.px-journey__reel {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: clamp(16px, 2vw, 28px);
}

.px-journey__word {
  display: block;
  text-align: center;
  padding: clamp(20px, 2.4vw, 32px) clamp(10px, 1.4vw, 18px);
  border-radius: var(--px-r-lg, 20px);
  background: var(--px-sand, #fbf3ea);
  font-family: var(--heading-font);
  font-weight: 800;
  font-size: clamp(1.5rem, 2.6vw, 2.1rem);
  line-height: 1.1;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  color: var(--px-orange);
}

/* Every other word is drawn as an outline on a filled tile instead of a solid
   fill, so neighbours in the row still read as alternating rather than as one
   flat strip of five identical tiles. */
.px-journey__word:nth-child(even) {
  color: transparent;
  -webkit-text-stroke: 1.6px var(--px-orange);
  background: var(--px-orange);
  background: color-mix(in srgb, var(--px-orange), transparent 92%);
}

.px-journey__word small {
  display: block;
  margin-top: 8px;
  font-size: clamp(0.66rem, 1vw, 0.78rem);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: none;
  color: var(--px-ink-55);
  -webkit-text-stroke: 0;
}

@media (max-width: 767.98px) {
  .px-journey__reel {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 480px) {
  .px-journey__reel {
    grid-template-columns: 1fr;
  }
}

/* ── 12. pointer ───────────────────────────────────────────────────────── */
/*
   A two-part cursor: a solid dot that tracks the pointer exactly, and a ring
   that lags a few frames behind. The lag is the whole trick — a ring locked to
   the dot is invisible, a ring that trails reads as inertia. Fine pointers
   only, and the native cursor is never hidden, so nothing is lost if a frame
   is dropped or the script fails.
*/
.px-cursor,
.px-cursor-ring {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 99999;
  pointer-events: none;
  border-radius: 50%;
  opacity: 0;
  transition: opacity 0.3s var(--px-ease);
}

.px-cursor {
  width: 7px;
  height: 7px;
  margin: -3.5px 0 0 -3.5px;
  background: var(--px-orange);
}

.px-cursor-ring {
  width: 36px;
  height: 36px;
  margin: -18px 0 0 -18px;
  border: 1.5px solid rgba(247, 146, 30, 0.55);
  transition:
    opacity 0.3s var(--px-ease),
    width 0.35s var(--px-ease),
    height 0.35s var(--px-ease),
    margin 0.35s var(--px-ease),
    background-color 0.35s var(--px-ease),
    border-color 0.35s var(--px-ease);
}

.px-cursor.is-on,
.px-cursor-ring.is-on { opacity: 1; }

/* Over anything clickable the ring opens up and fills faintly, and the dot
   disappears into it. */
.px-cursor-ring.is-hot {
  width: 64px;
  height: 64px;
  margin: -32px 0 0 -32px;
  background: rgba(247, 146, 30, 0.12);
  border-color: rgba(247, 146, 30, 0.75);
}

.px-cursor.is-hot { opacity: 0; }

/* ── 13. entrance ──────────────────────────────────────────────────────── */
/*
   The site already removes a #preloader on load. This adds the last beat: a
   navy sheet that lifts off the page from the bottom edge, so the first thing
   the visitor sees is a deliberate move rather than content popping in.
*/
.px-curtain {
  position: fixed;
  inset: 0;
  z-index: 9998;
  background: var(--px-ink);
  display: grid;
  place-items: center;
  transform: translateY(0);
  transition: transform 0.9s var(--px-ease);
}

.px-curtain.is-up { transform: translateY(-101%); }

.px-curtain__mark {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.16);
  border-top-color: var(--px-orange);
  animation: px-spin 0.85s linear infinite;
}

@keyframes px-spin { to { transform: rotate(360deg); } }

/* Reading progress, restyled to match: a hairline in brand orange rather than
   the default bar. */
.ux-progress__bar {
  background: linear-gradient(90deg, var(--px-orange), var(--px-ember));
  box-shadow: 0 0 12px rgba(247, 146, 30, 0.55);
}

/* ── 14. section rhythm ────────────────────────────────────────────────── */
body.home-2026 .hx-services {
  background: var(--px-sand);
  border-radius: var(--px-r-xl);
  box-shadow: var(--px-ring);
}

body.home-2026 .hx-section-head { margin-bottom: clamp(48px, 5.5vw, 76px); }

/* ── 15. reduced motion ────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .px-wi,
  [data-px-rise],
  [data-px-draw],
  [data-px-curtain] > img,
  [data-px-curtain] > picture > img {
    transform: none !important;
    opacity: 1 !important;
    clip-path: none !important;
    transition: none !important;
  }

  /* The odometer is the one exception: its reel MUST keep the transform that
     parks the right digit in the window, or every figure on the page reads
     zero. Only the travel is suppressed, so the number is simply there on
     arrival rather than rolling into place. */
  .px-odo__r { transition: none !important; }

  .px-marquee__track { animation: none; }
  .px-cursor,
  .px-cursor-ring,
  .px-curtain { display: none !important; }

  .px-journey__stage { position: static; height: auto; padding: 40px 0; }
  .px-journey__reel { margin-top: 0; padding-bottom: 0; }
  .px-journey__veil { display: none; }

  body.home-2026 .hx-btn-primary:hover,
  body.inner-page .hx-btn-primary:hover,
  body.home-2026 .hx-round-btn:hover,
  body.inner-page .hx-round-btn:hover,
  body .uj-card:hover,
  body.home-2026 .hx-shop-card:hover,
  body.home-2026 .hx-service:hover { transform: none; }

  /* The services grid's hover moves are state changes, not scroll reveals,
     so they aren't covered by the [data-px-rise] rule above and need their
     own lines here. */
  .hx-ed-media img { transition: none; }
  .hx-ed-item:hover .hx-ed-media img { transform: none; }
}

/* ── 16. the partner rail ──────────────────────────────────────────────── */
/*
   Once the partner grid becomes a drifting band it is only 60-odd pixels tall,
   which left it wedged between the "why us" cards and the team heading with
   nothing to say it was its own thing. Hairlines above and below turn it into
   a deliberate credentials rail — the same device a masthead uses.
*/
body.home-2026 #partners {
  padding: clamp(30px, 3.6vw, 54px) 0;
}

body.home-2026 #partners .container {
  border-top: 1px solid var(--px-hair-soft);
  border-bottom: 1px solid var(--px-hair-soft);
  padding-top: clamp(22px, 2.4vw, 34px);
  padding-bottom: clamp(22px, 2.4vw, 34px);
}

body.home-2026 .px-rail-label {
  display: block;
  margin: 0 0 clamp(16px, 1.8vw, 26px);
  text-align: center;
  font-family: var(--nav-font);
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--px-ink-40);
}

/* ── 17. the manifesto band ────────────────────────────────────────────── */
/*
   Mission and vision, on the brand orange.

   The type on this band is navy, not white. White on #f7921e measures 2.3:1,
   which fails WCAG AA outright and looks washed out at any size; the brand
   navy on the same orange measures 7:1 and passes comfortably for every size.
   It is also the more designed choice — two brand colours at full strength
   rather than the default white-on-colour every template reaches for. White is
   kept for the decorative numerals and rules, where it is ornament and carries
   no information.
*/
.px-manifesto {
  position: relative;
  overflow: hidden;
  /* main.css carries a blanket `section { padding: 60px 0 }` that inner-pages
     already neutralises. Left alone it made the band 120px taller on the home
     page than on the about page for no reason anyone could see; the band's own
     breathing room comes from `.px-manifesto__inner` and nowhere else. */
  padding: 0;
  /* No band, so no full-bleed either: the negative margins existed to push a
     coloured surface out to the window edges, and without one they would only
     hang the panels wider than every other section on the page. */
  border-radius: 0;
  /* The orange belongs to the two panels. Running it across the full width as
     well left them the same colour as what they sat on, so the cards had
     nothing to stand against. */
  background: transparent;
  color: var(--px-ink);
}

body.home-2026 .px-manifesto {
  margin-top: var(--nv-section-gap);
}

body.inner-page .px-manifesto {
  margin-top: clamp(28px, 3vw, 48px);
  margin-bottom: clamp(28px, 3vw, 48px);
}

/* A very soft light source behind the top-left corner. Costs one gradient and
   stops the band from reading as a solid rectangle of colour. */
.px-manifesto::before {
  content: none;
}

.px-manifesto__inner {
  position: relative;
  z-index: 1;
  max-width: 1320px;
  margin: 0 auto;
  /* The inline padding is copied from `.hx-inner` verbatim — 1320px wide with
     24px either side — so these panels line up with the sections above and
     below them.

     The vertical padding used to be the band's breathing room, holding the
     copy off a coloured edge. With the band gone that edge is the page, and
     the panels carry their own padding, so what is left is ordinary spacing
     between sections. */
  padding-block: clamp(8px, 1.5vw, 20px);
  padding-inline: 24px;
}

.px-manifesto__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(24px, 3vw, 40px);
  margin-top: clamp(34px, 3.6vw, 56px);
}

/* Each panel is its own card — a soft light fill, its own rounded border and
   padding on all sides — rather than one continuous band split by a single
   rule. Two distinct cards read immediately as "Mission" and "Vision" as two
   separate things; a shared background with only a divider between them read
   as one block of text that happened to have a line down the middle. */
/* The panels carry the orange now that the band behind them is gone. Lit from
   the top left the way the band was, so each reads as a surface rather than a
   flat fill; the white border went with the band, since on a white page it
   only cut the panel off from its own edge. */
.px-manifesto__panel {
  background: linear-gradient(135deg, #ffa63d 0%, var(--px-orange, #f7921e) 52%, #ef8410 100%);
  border: 0;
  border-radius: var(--px-r-lg, 20px);
  padding: clamp(28px, 3.2vw, 44px);
}

/* The numeral is ornament: outlined in white, sitting behind its heading and
   hidden from assistive technology, so it adds weight to the composition
   without adding a thing to read. */
.px-manifesto__no {
  display: block;
  font-family: var(--heading-font);
  font-size: clamp(3.2rem, 5.4vw, 5rem);
  font-weight: 800;
  line-height: 0.86;
  letter-spacing: -0.05em;
  color: transparent;
  -webkit-text-stroke: 2px rgba(255, 255, 255, 0.72);
  margin-bottom: clamp(14px, 1.2vw, 18px);
}

.px-manifesto__panel h2 {
  margin: 0;
  font-family: var(--heading-font);
  font-weight: 800;
  font-size: clamp(1.8rem, 3vw, 2.65rem);
  line-height: 1.06;
  letter-spacing: -0.038em;
  color: var(--px-ink);
}

.px-manifesto__panel h2::after {
  content: "";
  display: block;
  width: 58px;
  height: 3px;
  margin-top: clamp(16px, 1.6vw, 24px);
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.85);
}

/* Set as a statement, not as body copy — large, tight and on a short measure,
   so it is read in one breath rather than skimmed. */
.px-manifesto__panel p {
  margin: clamp(18px, 1.8vw, 26px) 0 0;
  max-width: 37ch;
  font-size: clamp(1.08rem, 0.98rem + 0.55vw, 1.42rem);
  font-weight: 500;
  line-height: 1.56;
  letter-spacing: -0.014em;
  color: #ffffff;
}

@media (max-width: 767px) {
  /* `.hx-inner` drops to 16px here. With the shell's 12px gutter on top of it
     that puts neighbouring copy 28px from the window edge, and the band has to
     follow it down. */
  .px-manifesto__inner { padding-inline: 28px; }
}

@media (max-width: 767.98px) {
  .px-manifesto__grid { grid-template-columns: 1fr; }
}

/* ── 18. services editorial grid ───────────────────────────────────────── */
/*
   Replaces the hover-swapped photo/list band this section used to be with an
   editorial, magazine-style staggered grid — six offers on a 12-column grid
   in alternating 7/5 spans, each pair offset vertically so no two rows line
   up. The point of the offsets is that the eye travels down the page on a
   diagonal, reading one offer at a time, instead of scanning a tidy matrix
   and taking in none of them; for a section whose whole job is to get a
   learner to pick something, one-at-a-time is worth more than density.

   The band itself steps back to the site's sand surface rather than the
   full-strength orange the hover list carried. That orange only worked
   because the list was six words of giant type on a flat field; behind six
   photographs it would fight every one of them. The orange instead
   concentrates in the hover "View" bubble, which is where a learner is
   actually being asked to act — so the accent still appears in this section,
   but as a call to action rather than as wallpaper. That also returns the
   manifesto band to being the single full-orange moment on the page.

   Markup: index.html for the shape, site-content.js's renderServices() for
   what the admin API repaints it with. Both have to agree on the same class
   names and on the six-item span cycle.
*/
body.home-2026 .hx-services.hx-svc-band {
  /* Requested as a specific flat grey (#F9F9F9) rather than the site's own
     warm --px-sand — close in value but visibly cooler, so it is its own
     literal rather than a reuse of that token. */
  background: #f9f9f9;
  border-radius: var(--px-r-xl);
  box-shadow: var(--px-ring);
  padding-block: clamp(72px, 8vw, 140px);
  margin-top: 0;
}

body.home-2026 .hx-services.hx-svc-band > .hx-inner {
  max-width: 1300px;
}

/* The head is the site's own centred `.hx-section-head`, not a bespoke one:
   with the CTA moved below the grid there is no second element to balance a
   split header against, and every other section on this page introduces
   itself the same centred way. Only the measure is capped here. */
.hx-svc-head {
  max-width: 720px;
  margin-inline: auto;
  text-align: center;
}

.hx-svc-head .hx-pill {
  margin-inline: auto;
}

/* ── the services grid ──
   Originally a staggered 12-column "magazine" layout with alternating 7/5
   spans, vertical offsets and three cycling photo aspect ratios. On a
   desktop viewport that made every card a different size and put them at
   different heights, which read as broken rather than intentional — so this
   is now a plain uniform 3-up grid: one card size, one photo ratio, three per
   row, same as an ordinary card wall. */
.hx-editorial {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(28px, 3.4vw, 44px) clamp(20px, 2.2vw, 32px);
  align-items: start;
}

.hx-ed-item {
  display: flex;
  flex-direction: column;
  gap: 18px;
  min-width: 0;
}

/* The media is the link, not the whole card: a learner clicking a photo
   expects to go somewhere, and wrapping the body text in the same anchor
   would make the descriptive line unselectable for no gain. */
.hx-ed-item__media {
  position: relative;
  display: block;
  border-radius: var(--px-r-lg);
  overflow: hidden;
  box-shadow: var(--px-sh-2);
  text-decoration: none;
  /* main.css's blanket `transition: 0.3s` on every anchor would otherwise
     run a colour transition on this wrapper alongside the image's own
     transform — two animations on overlapping layers for no visible gain. */
  transition: none;
}

.hx-ed-media {
  position: relative;
  overflow: hidden;
  background: var(--px-sand-deep);
}

.hx-ed-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transform: scale(1.001);
  transition: transform 0.9s var(--px-ease);
}

.hx-ed-item:hover .hx-ed-media img,
.hx-ed-item__media:focus-visible .hx-ed-media img {
  transform: scale(1.06);
}

/* One shared photo ratio for every card now that the grid is a uniform 3-up
   wall rather than a staggered layout — mixing ratios only made sense when
   neighbouring cards were different sizes on purpose. */
.ar-32,
.ar-34,
.ar-43 {
  aspect-ratio: 4 / 3;
}

/* ── the body copy ── */
.hx-ed-item__body {
  display: flex;
  flex-direction: column;
  gap: 9px;
}

/* The category sat hard against the right edge, pushed there by
   space-between, leaving a wide gap mid-line — "Coding & AI" and "Training"
   read as two separate things rather than one label. They sit together now,
   wrapping only if the pair runs out of room. */
.hx-ed-item__head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 10px;
}

.hx-ed-item__title {
  font-family: var(--heading-font);
  font-weight: 800;
  font-size: clamp(1.25rem, 1.9vw, 1.6rem);
  line-height: 1.14;
  letter-spacing: -0.03em;
  color: var(--px-ink);
  margin: 0;
}

/*
   Sentence case in the heading face, not 11px letterspaced caps. The
   original treatment — tiny, weight 800, 0.14em tracking, all caps — is the
   default "small label" recipe, and next to a 26px display title it read as
   a system-generated tag rather than something written.

   Sized to sit as the title's equal partner rather than as a footnote
   beside it: the two now read as one line of type in one family, which is
   what makes the pairing feel composed instead of assembled.
*/
.hx-ed-item__cat {
  flex: 0 0 auto;
  font-family: var(--heading-font);
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--px-orange-deep);
  white-space: nowrap;
}

/* Start and Path used to run together as one paragraph, so they wrapped into
   each other and the two halves of the story were hard to tell apart. Each
   gets its own line. */
.hx-ed-item__path {
  font-size: 0.97rem;
  line-height: 1.62;
  color: var(--px-ink-55);
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.hx-ed-item__line {
  display: block;
}

.hx-ed-item__path b {
  color: var(--px-ink);
  font-weight: 700;
}

/* ── uniform sizing ──
   All six cards are the same grid cell now, with no vertical offset, so a
   1440px-wide viewport shows two even rows of three identically sized
   cards instead of the old asymmetric stagger. */
.hx-ed-a,
.hx-ed-b,
.hx-ed-c,
.hx-ed-d,
.hx-ed-e,
.hx-ed-f {
  grid-column: span 1;
  margin-top: 0;
}

/* ── the closing CTA ── */
/*
   The button sits under the grid rather than beside the heading. At the top
   it would ask a learner to leave before they have seen a single offer; at
   the bottom it catches whoever has read all six and found the one they
   want. It keeps its default orange fill — the band behind it is sand now,
   so there is nothing to clash with.
*/
.hx-svc-foot {
  display: flex;
  justify-content: center;
  margin-top: clamp(48px, 5.5vw, 76px);
}

body.home-2026 .hx-services .hx-svc-cta.hx-btn-primary {
  flex: 0 0 auto;
}

/* ── responsive ──
   Three columns on desktop, two on tablet, one (a plain vertical stack) on
   phones — the grid's own column count changes at each breakpoint, so every
   card just stays "span 1" throughout. */
@media (max-width: 991px) {
  .hx-editorial {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 575.98px) {
  .hx-editorial {
    grid-template-columns: 1fr;
  }

  .hx-ed-a,
  .hx-ed-b,
  .hx-ed-c,
  .hx-ed-d,
  .hx-ed-e,
  .hx-ed-f {
    grid-column: span 1;
  }
  /* One shared shape once the cards are full width — the varied ratios exist
     to break up a row, and there are no rows left to break. */
  .ar-32,
  .ar-34,
  .ar-43 {
    aspect-ratio: 4 / 3;
  }
}

/* ── 20. footer ────────────────────────────────────────────────────────── */
/*
   Replaces the stock template footer — three uneven Bootstrap columns, dead
   `href="#"` links, an `#service` anchor that didn't exist on inner pages,
   and a lower-case, oddly-punctuated copyright line — with the site's own
   design language. It was the one section on every single page that had
   never been touched: no hx- or px- class anywhere in it, sized and coloured
   entirely from main.css's generic template variables.

   A first pass at this sat the footer on a near-white tint of the page
   background — which read as unfinished rather than minimal, because it gave
   the section no edge against whatever sits above it (the testimonials band
   is a nearly identical grey) and no weight of its own. A footer is the one
   place on the page allowed to look different from everything above it — it
   is where the site visibly ends — so this goes the other way: full brand
   navy, the same ink used for display type everywhere else on the site,
   with a slim orange-to-transparent seam along the very top so the
   transition still reads as deliberate rather than as a hard stop. Quiet
   comes from restraint in what's on the dark field — three link columns and
   one compact inline form, generous line-height, no boxes — not from low
   contrast with the page.

   Scoped to `.footer` directly rather than gated behind `body.home-2026` or
   `body.inner-page` — both bodies already define an identical set of
   --nv-* tokens (home.css / inner-pages.css), and the --px-* tokens this
   block actually uses live on bare :root, so one rule set here covers the
   footer wherever it appears without needing to know which page it's on.
*/
.footer.hx-footer {
  position: relative;
  background: var(--px-ink);
  color: rgba(255, 255, 255, 0.62);
  font-size: 15px;
  padding: 0;
  overflow: clip;
}

.hx-footer-top {
  position: relative;
  z-index: 1;
}

/* Both home.css and inner-pages.css carry a `body.home-2026 .hx-inner` /
   `body.inner-page .hx-inner` rule that sets the shorthand `padding: 0 24px`
   (and `0 16px` again under their own mobile breakpoints) — one element type
   plus one class more specific than a bare `.hx-footer-top`, so the vertical
   padding meant to sit the brand row and column heads off the top seam kept
   losing the cascade and collapsing to 0 however this was written. Matching
   that specificity here, under both body classes, is what actually wins. */
body.home-2026 .hx-footer-top,
body.inner-page .hx-footer-top {
  padding-top: clamp(32px, 3.5vw, 52px);
  padding-bottom: clamp(22px, 2.5vw, 34px);
}

.hx-footer-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(0, 0.75fr) minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: clamp(24px, 3vw, 40px);
}

/* ── brand column ── */
.hx-footer-brand {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
}

.hx-footer-logo {
  font-family: var(--heading-font);
  font-weight: 800;
  font-size: 1.35rem;
  letter-spacing: -0.02em;
  color: #ffffff;
  text-decoration: none;
}

.hx-footer-tag {
  margin: 0;
  max-width: 32ch;
  line-height: 1.7;
  color: rgba(255, 255, 255, 0.5);
}

.hx-footer-contact {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 4px;
}

.hx-footer-contact a,
.hx-footer-contact .hx-footer-loc {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  color: rgba(255, 255, 255, 0.78);
  text-decoration: none;
  font-size: 14.5px;
  transition: color var(--px-t-1) var(--px-ease);
}

.hx-footer-contact a:hover {
  color: var(--px-orange);
}

.hx-footer-contact i {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.07);
  color: var(--px-orange);
  font-size: 13px;
  flex: none;
}

.hx-footer-social {
  display: flex;
  gap: 10px;
  margin-top: 8px;
}

.hx-footer-social a {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: rgba(255, 255, 255, 0.8);
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.12);
  font-size: 15px;
  transition:
    color var(--px-t-1) var(--px-ease),
    background-color var(--px-t-1) var(--px-ease),
    border-color var(--px-t-1) var(--px-ease),
    transform var(--px-t-1) var(--px-ease);
}

.hx-footer-social a:hover {
  color: #ffffff;
  background: var(--px-orange);
  border-color: var(--px-orange);
  transform: translateY(-3px);
}

/* ── link columns ── */
.hx-footer-col h4 {
  font-family: var(--heading-font);
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.4);
  margin: 0 0 14px 0;
}

.hx-footer-col ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Ten links stacked single-file made this the tallest thing in the footer's
   grid row at every width — under CSS Grid every column stretches to match
   its tallest neighbour, so this one column's length alone set the height
   of the whole footer, and at the width the two-column mobile layout gives
   it, it was still taller than the other three columns combined. Wrapped
   into two rows of five here instead, at any width, without dropping a
   single link — the column is always wide enough for two short link labels
   side by side, from a 320px phone up. */
.hx-footer-col[aria-label="Explore"] ul {
  flex-direction: row;
  flex-wrap: wrap;
  column-gap: 16px;
  row-gap: 10px;
}

.hx-footer-col[aria-label="Explore"] ul li {
  flex: 0 0 calc(50% - 8px);
}

.hx-footer-col ul a {
  color: rgba(255, 255, 255, 0.78);
  text-decoration: none;
  font-size: 14.5px;
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  transition: color var(--px-t-1) var(--px-ease), transform var(--px-t-1) var(--px-ease);
}

/* A short comma-orange tick that slides in from the left on hover, rather
   than an underline — on a dark field a full-width rule reads heavier than
   intended, where a single mark beside the word reads as a cursor picking
   it out. */
.hx-footer-col ul a::before {
  content: "";
  width: 0;
  height: 1px;
  background: var(--px-orange);
  transition: width var(--px-t-1) var(--px-ease);
}

.hx-footer-col ul a:hover {
  color: #ffffff;
  transform: translateX(2px);
}

.hx-footer-col ul a:hover::before {
  width: 12px;
}

/* ── newsletter ── */
.hx-footer-news p {
  margin: 0 0 14px 0;
  font-size: 14px;
  line-height: 1.65;
  color: rgba(255, 255, 255, 0.5);
  max-width: 30ch;
}

.hx-footer-form {
  position: relative;
  display: flex;
  align-items: center;
  gap: 4px;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 999px;
  padding: 5px 5px 5px 20px;
  max-width: 340px;
  transition: border-color var(--px-t-1) var(--px-ease), background-color var(--px-t-1) var(--px-ease);
}

.hx-footer-form:focus-within {
  border-color: var(--px-orange);
  background: rgba(255, 255, 255, 0.1);
}

.hx-footer-form input[type="email"] {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  font-size: 14px;
  color: #ffffff;
  padding: 10px 0;
}

.hx-footer-form input[type="email"]::placeholder {
  color: rgba(255, 255, 255, 0.4);
}

.hx-footer-form input[type="email"]:focus {
  outline: none;
}

.hx-footer-form button {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--px-orange);
  color: #ffffff;
  font-size: 15px;
  cursor: pointer;
  transition:
    background-color var(--px-t-1) var(--px-ease),
    transform var(--px-t-1) var(--px-ease);
}

.hx-footer-form button:hover {
  background: var(--px-orange-deep);
  transform: scale(1.06);
}

.hx-footer-form .sent-message,
.hx-footer-form .error-message {
  display: none;
  position: absolute;
  top: calc(100% + 12px);
  left: 20px;
  font-size: 13px;
}

.hx-footer-form .sent-message.d-block,
.hx-footer-form .error-message.d-block {
  display: block;
}

.hx-footer-form .sent-message { color: #6fd6a3; }
.hx-footer-form .error-message { color: #ff9a8c; }

/* ── bottom bar ── */
.hx-footer-bottom {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px 24px;
  padding: 16px 0;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  font-size: 13px;
  color: rgba(255, 255, 255, 0.4);
}

.hx-footer-bottom p {
  margin: 0;
}

.hx-footer-legal {
  display: flex;
  gap: 28px;
}

.hx-footer-legal a {
  color: rgba(255, 255, 255, 0.5);
  text-decoration: none;
  transition: color var(--px-t-1) var(--px-ease);
}

.hx-footer-legal a:hover {
  color: var(--px-orange);
}

/* ── responsive ── */
@media (max-width: 991px) {
  .hx-footer-grid {
    grid-template-columns: 1fr 1fr;
    row-gap: 28px;
  }
  .hx-footer-brand {
    grid-column: 1 / -1;
  }
}

@media (max-width: 575.98px) {
  /* Kept at two columns rather than collapsing to one: the Explore column
     alone runs ten links, and stacking every column full-width one under
     another was the single biggest contributor to the footer's height on a
     phone screen — over 1300px of it, more than the entire rest of the page
     chrome combined. Two columns halves that by using the width a phone
     screen still has instead of only its height. */
  .hx-footer-grid {
    grid-template-columns: 1fr 1fr;
    row-gap: 26px;
  }

  .hx-footer-bottom {
    flex-direction: column;
    align-items: flex-start;
  }

  /* The fixed #scroll-top circle (main.css) sits 15px off the right edge at
     this width and would otherwise overlap the copyright line — this bar is
     the one place on the page that runs text all the way out to where that
     button parks itself. Needs body.home-2026/body.inner-page repeated here
     because both home.css and inner-pages.css carry a mobile
     `body.home-2026 .hx-inner`/`body.inner-page .hx-inner { padding: 0 16px }`
     rule whose selector is one element type more specific than a bare
     `.hx-footer-bottom.hx-inner` — without matching that, this rule silently
     lost the cascade on every page despite loading later. */
  body.home-2026 .hx-footer-bottom.hx-inner,
  body.inner-page .hx-footer-bottom.hx-inner {
    padding-right: 76px;
  }
}

/* ── 21. legal pages (terms / privacy) ────────────────────────────────── */
/*
   A plain reading column rather than a card or grid — the two things a
   legal page has to do are be found (the footer link) and be read (once
   someone actually clicks it), and neither benefits from the site's usual
   photography or motion. Built from the same type tokens editorial.css
   already defines for .hx-article-body, so a heading here is the same size
   and weight as a heading anywhere else on an inner page, just laid out for
   continuous prose instead of a card.
*/
body.inner-page .hx-legal {
  padding-bottom: 60px;
}

body.inner-page .hx-legal-updated {
  font-family: var(--nav-font);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--px-ink-40);
  margin: -8px 0 40px 0;
}

body.inner-page .hx-legal-body {
  max-width: var(--nv-measure);
}

body.inner-page .hx-legal-body h2 {
  font-family: var(--nv-display);
  font-size: var(--t-h3);
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: var(--tr-title);
  color: var(--px-ink);
  margin: 48px 0 16px 0;
}

body.inner-page .hx-legal-body h2:first-child {
  margin-top: 0;
}

body.inner-page .hx-legal-body p {
  font-size: var(--t-body);
  line-height: 1.85;
  color: var(--px-ink-70);
  margin: 0 0 18px 0;
}

body.inner-page .hx-legal-body ul {
  margin: 0 0 18px 0;
  padding-left: 22px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

body.inner-page .hx-legal-body ul li {
  font-size: var(--t-body);
  line-height: 1.7;
  color: var(--px-ink-70);
}

body.inner-page .hx-legal-body a {
  color: var(--px-orange-deep);
  text-decoration: underline;
  text-decoration-color: rgba(217, 117, 18, 0.35);
  text-underline-offset: 3px;
}

body.inner-page .hx-legal-body a:hover {
  text-decoration-color: currentColor;
}

body.inner-page .hx-legal-body strong {
  color: var(--px-ink);
}

/* ── 22. showcase rows (events / programmes) ──────────────────────────── */
/*
   A full-width, alternating image/copy row for each event or programme,
   modelled on the layout at tatechhub.com/projects: tags, a title, a short
   description, one CTA, then a concrete "Key Facts" list — rather than the
   card-grid-with-one-paragraph each of these pages carried before. The
   point of alternating sides is the same reason the reference does it: a
   page of identical left-image cards reads as one repeating unit skimmed
   past, where alternating sides makes each row its own beat.

   Shared by events.html (each event) and the programme detail pages (the
   single "more about" block per page, which reads as a one-row list of
   exactly one) — one component rather than the two unrelated ones
   (.hx-event / .hx-article) those pages used to define separately.
*/
body.inner-page .hx-showcase {
  display: flex;
  flex-direction: column;
}

body.inner-page .hx-showcase-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(32px, 5vw, 72px);
  align-items: center;
  padding-block: clamp(48px, 6vw, 84px);
}

body.inner-page .hx-showcase-row + .hx-showcase-row {
  border-top: 1px solid var(--px-hair);
}

/* Odd rows: image left, copy right (the grid order below). Even rows swap —
   copy first in the DOM (so it still reads first for a screen reader and on
   a stacked mobile layout) but placed second visually on a wide screen. */
body.inner-page .hx-showcase-row:nth-child(even) .hx-showcase-media {
  order: 2;
}

/* The photographs are 3:2, and forcing them into a 4:3 frame with
   object-fit: cover cropped a strip off the top and bottom of every one.
   The frame takes the picture's own proportions instead, so what was shot
   is what is shown. */
body.inner-page .hx-showcase-media {
  border-radius: var(--px-r-lg);
  overflow: hidden;
  box-shadow: var(--px-sh-2);
}

body.inner-page .hx-showcase-media img {
  display: block;
  width: 100%;
  height: auto;
}

body.inner-page .hx-showcase-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

/* ── tag row ── */
body.inner-page .hx-showcase-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 18px;
}

body.inner-page .hx-showcase-tags span {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  background: var(--px-sand-deep);
  color: var(--px-ink-70);
  padding: 6px 14px;
  font-family: var(--nav-font);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.03em;
}

body.inner-page .hx-showcase-body h2,
body.inner-page .hx-showcase-body h3 {
  color: var(--px-ink);
  font-family: var(--heading-font);
  font-weight: 800;
  font-size: clamp(1.5rem, 2.2vw, 2rem);
  line-height: 1.22;
  letter-spacing: -0.015em;
  margin: 0 0 16px 0;
}

body.inner-page .hx-showcase-body > p {
  color: var(--px-ink-55);
  font-size: 15.5px;
  line-height: 1.8;
  margin: 0 0 24px 0;
  max-width: 52ch;
}

body.inner-page .hx-showcase-body .hx-cta-group {
  margin-bottom: 34px;
}

/* ── key facts ── */
body.inner-page .hx-showcase-facts {
  width: 100%;
}

body.inner-page .hx-showcase-facts h4 {
  font-family: var(--heading-font);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--px-ink-40);
  margin: 0 0 14px 0;
}

body.inner-page .hx-showcase-facts ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 11px;
}

body.inner-page .hx-showcase-facts li {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--px-ink-70);
}

body.inner-page .hx-showcase-facts li i {
  flex: none;
  margin-top: 2px;
  color: var(--px-orange);
  font-size: 15px;
}

/* ── responsive ── */
@media (max-width: 900px) {
  body.inner-page .hx-showcase-row {
    grid-template-columns: 1fr;
  }
  body.inner-page .hx-showcase-row:nth-child(even) .hx-showcase-media {
    order: 0;
  }
}

/* ── 23. gallery ───────────────────────────────────────────────────────── */
/*
   A plain photo wall — modelled on tatechhub.com/gallery: a uniform grid of
   square photos, no captions competing for attention, no filter bar. The
   point of a gallery page is the photos themselves, so the only job of this
   CSS is to get out of their way — consistent size, a small gap, a soft
   corner radius, and a click-to-enlarge affordance on hover. Orange is the
   one accent, on the hover overlay's expand icon, matching the reference's
   own single-accent restraint.
*/
body.inner-page .hx-gallery {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(14px, 2vw, 24px);
}

body.inner-page .hx-gallery-item {
  position: relative;
  display: block;
  aspect-ratio: 1 / 1;
  border-radius: var(--px-r);
  overflow: hidden;
  box-shadow: var(--px-sh-1);
}

body.inner-page .hx-gallery-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--px-t-3) var(--px-ease);
}

body.inner-page .hx-gallery-item:hover img {
  transform: scale(1.06);
}

/* A dark wash plus a centred expand icon on hover — the same "this opens
   something" cue GLightbox's default skin gives its own trigger, made to
   match this page's own palette instead of the vendor default. */
body.inner-page .hx-gallery-item::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(11, 27, 84, 0) 40%, rgba(11, 27, 84, 0.55) 100%);
  opacity: 0;
  transition: opacity var(--px-t-1) var(--px-ease);
}

body.inner-page .hx-gallery-item:hover::after {
  opacity: 1;
}

/* The expand icon sits bottom-right so it never collides with the caption
   occupying the opposite corner — the same split the reference uses. */
body.inner-page .hx-gallery-item__icon {
  position: absolute;
  inset: auto 16px 16px auto;
  z-index: 1;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--px-orange);
  color: #ffffff;
  font-size: 15px;
  opacity: 0;
  transform: translateY(6px);
  transition:
    opacity var(--px-t-1) var(--px-ease),
    transform var(--px-t-1) var(--px-ease);
}

body.inner-page .hx-gallery-item:hover .hx-gallery-item__icon {
  opacity: 1;
  transform: translateY(0);
}

/* The caption itself — hidden until hover, same as the icon, so a photo
   with no caption text (an admin left it blank) simply shows nothing extra
   rather than an empty bar. */
body.inner-page .hx-gallery-item__caption {
  position: absolute;
  inset: auto 60px 16px 16px;
  z-index: 1;
  color: #ffffff;
  font-family: var(--heading-font);
  font-weight: 700;
  font-size: 15px;
  line-height: 1.3;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  opacity: 0;
  transform: translateY(6px);
  transition:
    opacity var(--px-t-1) var(--px-ease),
    transform var(--px-t-1) var(--px-ease);
}

body.inner-page .hx-gallery-item:hover .hx-gallery-item__caption {
  opacity: 1;
  transform: translateY(0);
}

body.inner-page .hx-gallery-item__caption:empty {
  display: none;
}

@media (max-width: 900px) {
  body.inner-page .hx-gallery {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 480px) {
  body.inner-page .hx-gallery {
    grid-template-columns: 1fr;
  }
}

/* Touch has no hover, so the caption and expand icon would never appear —
   keyed on the actual input type rather than a width breakpoint, since a
   touch tablet at a wide viewport has the identical problem. Parked
   permanently visible, with the wash held at a lighter, constant value
   rather than the full hover-strength one so a caption is always legible
   without darkening every photo in the grid to the same degree a deliberate
   hover does. */
@media (hover: none) {
  body.inner-page .hx-gallery-item::after {
    opacity: 1;
    background: linear-gradient(180deg, rgba(11, 27, 84, 0) 55%, rgba(11, 27, 84, 0.6) 100%);
  }
  body.inner-page .hx-gallery-item__caption,
  body.inner-page .hx-gallery-item__icon {
    opacity: 1;
    transform: none;
  }
}

/* ── 24. event dates & status ─────────────────────────────────────────── */
/*
   The date used to sit in the third bullet of a Key Facts list, which is
   the last place an eye lands on one of these rows — a visitor scanning for
   "when is this?" had to read a paragraph and most of a list first. It now
   gets a torn-calendar badge sitting on the photograph itself, which is the
   first thing in the row and the one element guaranteed to be looked at.

   Status ("Upcoming" / "Happening now" / "Past") is never authored by hand:
   events-status.js derives it from each row's own machine-readable
   data-start/data-end, so an event cannot sit on the site marked "upcoming"
   two years after it happened simply because nobody went back to edit it.
*/
body.inner-page .hx-showcase-media {
  position: relative;
}

/* ── the date badge ── */
body.inner-page .hx-event-date {
  position: absolute;
  top: 16px;
  left: 16px;
  z-index: 2;
  min-width: 74px;
  border-radius: var(--px-r-sm);
  overflow: hidden;
  background: #ffffff;
  box-shadow: var(--px-sh-2);
  text-align: center;
  font-family: var(--heading-font);
  line-height: 1;
}

body.inner-page .hx-event-date__month {
  display: block;
  background: var(--px-orange);
  color: #ffffff;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 6px 10px;
}

body.inner-page .hx-event-date__day {
  display: block;
  color: var(--px-ink);
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.02em;
  padding: 8px 10px 2px;
}

body.inner-page .hx-event-date__year {
  display: block;
  color: var(--px-ink-40);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  padding: 0 10px 8px;
}

/* ── the status pill ── */
/*
   Sits in the tag row rather than on the photo, so the photo carries exactly
   one badge and the status reads as a property of the event's description.
   Colour, not just wording, carries the distinction — an "over" event should
   be recognisable at a glance without reading.
*/
body.inner-page .hx-event-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border-radius: 999px;
  padding: 6px 14px;
  font-family: var(--nav-font);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
}

body.inner-page .hx-event-status::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

body.inner-page .hx-event-status--upcoming {
  background: rgba(28, 122, 77, 0.12);
  color: #1c7a4d;
}

body.inner-page .hx-event-status--live {
  background: rgba(247, 146, 30, 0.16);
  color: var(--px-orange-deep);
}

/* A live event's dot pulses — the one place on this page where motion is
   carrying information rather than decoration, so it survives the
   reduced-motion rule below only as a static dot. */
body.inner-page .hx-event-status--live::before {
  animation: hx-event-pulse 1.8s var(--px-ease-io) infinite;
}

@keyframes hx-event-pulse {
  0%        { box-shadow: 0 0 0 0 rgba(247, 146, 30, 0.6); }
  70%, 100% { box-shadow: 0 0 0 7px rgba(247, 146, 30, 0); }
}

body.inner-page .hx-event-status--past {
  background: rgba(11, 27, 84, 0.07);
  color: var(--px-ink-55);
}

/* A finished event used to have its photograph desaturated to 55% so the
   upcoming ones read first. It made every past event's picture look washed
   out and poorly reproduced — the photographs are shown as taken now, and
   the "Past" badge is what marks them as finished. */

/* ── the filter bar ── */
body.inner-page .hx-event-filters {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin-bottom: clamp(28px, 4vw, 44px);
}

body.inner-page .hx-event-filter {
  border: 1px solid var(--px-hair);
  background: #ffffff;
  color: var(--px-ink-70);
  border-radius: 999px;
  padding: 9px 20px;
  font-family: var(--nav-font);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  transition:
    background-color var(--px-t-1) var(--px-ease),
    color var(--px-t-1) var(--px-ease),
    border-color var(--px-t-1) var(--px-ease);
}

body.inner-page .hx-event-filter:hover {
  border-color: var(--px-orange);
  color: var(--px-orange-deep);
}

body.inner-page .hx-event-filter.is-on {
  background: var(--px-orange);
  border-color: var(--px-orange);
  color: #ffffff;
}

body.inner-page .hx-event-filter__count {
  opacity: 0.65;
  margin-left: 5px;
}

/* Rows hidden by a filter. A plain `display: none` on a grid child is what
   keeps the alternating nth-child rhythm of the remaining rows intact — the
   rows that stay visible re-flow as if the hidden ones were never there. */
body.inner-page .hx-showcase-row[hidden] {
  display: none;
}

/* Shown when a filter matches nothing, so the section never just empties. */
body.inner-page .hx-event-empty {
  text-align: center;
  padding: clamp(40px, 6vw, 72px) 0;
  color: var(--px-ink-55);
  font-size: 15.5px;
}

body.inner-page .hx-event-empty[hidden] {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  body.inner-page .hx-event-status--live::before { animation: none; }
}

/* ── UjuziPlus header button ─────────────────────────────────────────────
   The sister learning platform, reachable from every page's action row.
   Defined here rather than in home.css or inner-pages.css because both
   page types load this file last, and the button appears on both.

   Sized as the same 40px circle as the search control beside it so the
   row keeps one height. The full logo stacks its wordmark beneath the
   head mark, which is far too tall for a bar control, so the button
   carries the mark on its own. */
.hx-header .hx-ujuzi-btn {
  width: 40px;
  height: 40px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.06);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background-color 150ms ease-out, transform 150ms ease-out;
}

.hx-header .hx-ujuzi-btn img {
  width: 24px;
  height: 24px;
  object-fit: contain;
  display: block;
}

.hx-header .hx-ujuzi-btn:hover {
  background: rgba(0, 0, 0, 0.12);
  transform: translateY(-2px);
}

/* ── AOS failsafe ────────────────────────────────────────────────────────
   Every [data-aos] element is invisible until AOS initialises, and the
   directional variants also start translated up to 100px away — which is
   how the "What We Offer" copy ended up stranded off the left edge of a
   phone screen. If AOS never runs (its script blocked, a JS error earlier
   on the page, a browser that failed to parse it), that content is not
   merely un-animated, it is permanently unreachable.

   AOS sets `aos-init` on every element it takes ownership of, so anything
   still lacking that class is content AOS never reached: show it, plainly
   and unmoved. Once AOS does initialise, the class lands and its own rules
   take over, so this costs the animation nothing. */
[data-aos]:not(.aos-init) {
  opacity: 1 !important;
  transform: none !important;
}

/* ── reveal failsafe ─────────────────────────────────────────────────────
   [data-px-rise] and [data-px-curtain] hold their element at opacity 0 until
   premium.js adds `.is-in`. That is correct while the script is running, but
   it means a section is invisible — not merely un-animated — if the script
   never gets there: blocked, failed to parse, or an observer callback that
   never fired.

   premium.js sets these attributes itself, and only after it has loaded and
   run, so an element carrying one is proof the script is alive. The `no-js`
   class below is the other half: premium.js removes it on startup, so if the
   script never runs the class stays and everything is shown regardless. */
html.px-no-js [data-px-rise],
html.px-no-js [data-px-curtain] > img,
html.px-no-js [data-px-curtain] > picture > img,
html.px-no-js .px-wi {
  opacity: 1 !important;
  transform: none !important;
  clip-path: none !important;
}
