/* ============================================================
   Poppins, self-hosted. Google's CSS loads fine but the font
   files on fonts.gstatic.com were not reaching the browser, so
   the page silently fell back to Arial. Serving them from
   assets/fonts removes the dependency entirely.
   ============================================================ */
@font-face{
  font-family:'Poppins';
  font-style:normal;
  font-weight:400;
  font-display:swap;
  src:url('assets/fonts/poppins-400.woff2') format('woff2');
}
@font-face{
  font-family:'Poppins';
  font-style:normal;
  font-weight:500;
  font-display:swap;
  src:url('assets/fonts/poppins-500.woff2') format('woff2');
}
@font-face{
  font-family:'Poppins';
  font-style:normal;
  font-weight:600;
  font-display:swap;
  src:url('assets/fonts/poppins-600.woff2') format('woff2');
}
@font-face{
  font-family:'Poppins';
  font-style:normal;
  font-weight:700;
  font-display:swap;
  src:url('assets/fonts/poppins-700.woff2') format('woff2');
}
@font-face{
  font-family:'Poppins';
  font-style:normal;
  font-weight:800;
  font-display:swap;
  src:url('assets/fonts/poppins-800.woff2') format('woff2');
}

/* ============================================================
   Infercon Automation — landing page
   Light page, navy hero / bands, orange accent.
   ============================================================ */

:root{
  /* surfaces - dark industrial ground */
  --bg:            #17191c;
  --bg-2:          #1c1f23;   /* tinted / inset ground */
  --panel:         #212429;   /* cards */
  --line:          #2c3036;
  --line-soft:     #23262b;



  /* the "navy" blocks are now the same charcoal as the page, so hero,
     footer and the dark cards read as one continuous surface */
  --brand:         #f5a623;
  --brand-soft:    #ffc978;
  --navy:          #17191c;
  --navy-2:        #212429;
  --navy-line:     #2c3036;

  --accent:        #f5a623;   /* amber - every CTA and marker */
  --accent-hi:     #ffb733;
  --green:         #3ecf8e;

  --text:          #ffffff;
  --text-muted:    #9aa1a9;
  --text-dim:      #6d747c;

  --on-navy:       #ffffff;
  --on-navy-mute:  #9aa1a9;
  --on-navy-dim:   #6d747c;

  --wrap:          1360px;   /* content 1292px: the line every section aligns to */
  --r:             4px;
  --r-sm:          3px;

  --shadow:        0 1px 2px rgba(0,0,0,.30), 0 8px 24px rgba(0,0,0,.30);
  --shadow-hi:     0 2px 6px rgba(0,0,0,.34), 0 16px 40px rgba(0,0,0,.42);

  --sans: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --mono: var(--sans);   /* single family: labels differ by tracking, not face */
}

html{ scroll-behavior:smooth; }
@media (prefers-reduced-motion:reduce){ html{ scroll-behavior:auto; } }

*,*::before,*::after{ box-sizing:border-box; }

::selection{ background:rgba(245,166,35,.30); }

/* one consistent focus ring everywhere, keyboard only */
a:focus-visible,
button:focus-visible,
input:focus-visible{
  outline:2px solid var(--accent);
  outline-offset:3px;
  border-radius:4px;
}

html{
  scroll-behavior:smooth;
  /* scrollbar hidden by request — wheel, touch, keyboard and the
     in-page anchors all still scroll normally */
  scrollbar-width:none;
  -ms-overflow-style:none;
}
html::-webkit-scrollbar,
body::-webkit-scrollbar{ width:0; height:0; display:none; }

body{
  margin:0;
  background:var(--bg);
  color:var(--text);
  font-family:var(--sans);
  font-size:14.5px; line-height:1.65;
  -webkit-font-smoothing:antialiased;
}

h1,h2,h3,h4{ margin:0; letter-spacing:-.025em; }
p{ margin:0; }
ul{ margin:0; padding:0; list-style:none; }
a{ color:inherit; text-decoration:none; }
img{ max-width:100%; display:block; }
button{ font:inherit; }

.wrap{ width:100%; max-width:var(--wrap); margin-inline:auto; padding-inline:34px; }

.mono{
  font-family:var(--sans);
  font-size:10.5px; font-weight:600;
  letter-spacing:.14em; text-transform:uppercase;
}
.accent{ color:var(--accent); }
.dim{ color:var(--text-dim); }

/* ---------- Buttons ---------------------------------------- */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  padding:13px 24px;
  border:1px solid transparent;
  border-radius:var(--r-sm);
  font-size:14px; font-weight:600;
  cursor:pointer;
  transition:background .18s ease, border-color .18s ease,
             color .18s ease, transform .18s ease, box-shadow .18s ease;
}
.btn-sm{ padding:10px 18px; font-size:13px; }
.btn-block{ display:flex; width:100%; }

.btn-primary{
  background:var(--accent); color:#17191c;
  box-shadow:0 6px 18px rgba(245,166,35,.22);
}
.btn-primary:hover{
  background:var(--accent-hi);
  transform:translateY(-1px);
  box-shadow:0 10px 24px rgba(245,166,35,.30);
}

/* ghost sits on the navy hero */
.btn-ghost{
  background:transparent; color:var(--on-navy);
  border-color:rgba(255,255,255,.3);
}
.btn-ghost:hover{ border-color:#fff; background:rgba(255,255,255,.08); }

/* outline on a light ground */
.btn-dark-outline{
  background:transparent; color:var(--navy);
  border-color:rgba(23,25,28,.45);
  padding:12px 22px;
}
.btn-dark-outline:hover{ border-color:var(--navy); background:rgba(23,25,28,.10); }

/* ---------- Header ----------------------------------------- */
.site-header{
  position:sticky; top:0; z-index:40;
  background:rgba(23,25,28,.92);
  backdrop-filter:blur(8px);
  border-bottom:1px solid var(--line-soft);
}
.header-inner{
  display:flex; align-items:center; gap:34px;
  padding-block:15px;
}

.logo{ display:flex; align-items:center; gap:12px; }
.logo-mark{
  width:42px; height:42px;
  flex:none;
  object-fit:contain;
}
.logo-text{
  font-size:15.5px; letter-spacing:.03em; line-height:1.1;
  color:var(--on-navy); font-weight:500;
}
.logo-text strong{ color:var(--accent); font-weight:800; letter-spacing:.01em; }

.nav{ display:flex; gap:30px; margin-left:auto; }
.nav a{
  position:relative;
  display:inline-flex; align-items:center; gap:5px;
  font-size:14.5px; font-weight:500;
  color:var(--on-navy-mute);
  transition:color .18s ease;
}
.nav a::after{
  content:""; position:absolute; left:0; right:0; bottom:-6px;
  height:2px; border-radius:2px;
  background:var(--accent);
  transform:scaleX(0); transform-origin:left;
  transition:transform .22s ease;
}
.nav a:hover{ color:var(--on-navy); }
.nav a:hover::after{ transform:scaleX(1); }

/* pill CTA with the WhatsApp glyph */
.header-cta{ margin-left:30px; border-radius:99px; padding:12px 24px; font-size:13.5px; }
.btn-ico{ flex:none; margin-right:-1px; }

.nav-toggle{
  display:none;
  width:40px; height:36px;
  background:transparent; border:1px solid var(--navy-line);
  border-radius:var(--r-sm);
  padding:9px 10px; cursor:pointer;
}
.nav-toggle span{ display:block; height:1.5px; background:var(--on-navy-mute); margin:3px 0; }

/* ---------- Welcome screen ----------------------------------
   The first thing the page shows: the whole lab as a wall of
   monitors, one per platform. It is a .panel like the rest, so it
   pins and the hero scrolls up over it. */
/* The header sits in the flow above this panel, so a full 100vh here puts
   the panel's last 79px - the scroll cue - below the fold on the one screen
   that is never scrolled to. Later panels pin at top:0 and don't have the
   problem; they clear the header with their own top padding instead. */
/* .panel is defined further down the sheet, so this needs the extra class
   to win the min-height rather than losing the tie on source order. */
.panel.welcome-panel{
  padding-block:0;
  min-height:calc(100vh - 80px);
}

.welcome{
  flex:none;            /* as a flex item it was being shrunk below its
                           content, so the panel stayed at 100vh while the
                           scroll cue spilled out the bottom */
  display:flex;
  flex-direction:column;
  align-items:center;
  text-align:center;
  /* The panel already centres this block and its own height already
     allows for the header, so a big top pad here only pushed everything
     low. The heavier bottom pad lifts the content the ~39px the panel
     itself sits below the middle of the screen. */
  padding-block:clamp(20px, 2.6vh, 30px) clamp(72px, 10vh, 104px);
}

.welcome-kicker{
  color:var(--accent);
  margin-bottom:clamp(10px, 1.6vh, 16px);
}
.welcome-title{
  font-size:clamp(30px, 4.4vw, 52px);
  font-weight:800; line-height:1.06;
  letter-spacing:-.02em;
  color:var(--on-navy);
}
.welcome-lede{
  max-width:56ch;
  margin-top:clamp(10px, 1.6vh, 16px);
  font-size:14.5px; line-height:1.7;
  color:var(--on-navy-mute);
}

/* ---- the monitor wall ---- */
/* One row, bent into an arc the way the consoles curve across the lab:
   the ends stand high and angle inward, the middle sits back and lower.

        [1]                                     [8]
             [2]                           [7]
                  [3]                 [6]
                       [4]       [5]

   The offsets are written out per child. Eight tiles is four symmetric
   pairs, and nth-child keeps the index out of the markup. */
.monitor-wall{
  /* One curved display, not eight cards on a shelf. The panels are tiled
     edge to edge around a cylinder: facet angle 14 deg, radius 4.2540 x the
     panel width, which is what makes neighbouring edges meet instead of
     leaving gaps. --w drives the whole thing, so the wall scales from a
     single value and the geometry stays exact at any size. */
  --w:clamp(70px, 10.4vw, 152px);

  /* --w is the facet pitch - the spacing the geometry is solved for. Each
     panel is drawn a little narrower than that, which opens an even gap
     between neighbours all the way round the curve without disturbing the
     positions. */
  --panel:calc(var(--w) * 0.93);

  position:relative;
  width:100%;
  height:calc(var(--w) * 0.98);
  margin:clamp(16px, 2.8vh, 32px) 0 0;
  padding:0;
  list-style:none;

  perspective:calc(var(--w) * 7.33);

  /* --px / --py are written by the pointer handler in script.js, in the
     range -1..1. Moving perspective-origin moves the camera rather than
     rotating the wall: the screens shift against each other and the far
     ones swing round, so the curve is revealed by looking at it. Tilting
     the whole wall instead would just slide a flat picture about. */
  perspective-origin:
    calc(50% + var(--px, 0) * 16%)
    calc(63% + var(--py, 0) * 9%);   /* eye a little above the wall, so the
                                    far panels settle lower, as in a real
                                    console shot */
  transform-style:preserve-3d;
}

.monitor-wall li{
  position:absolute;
  transition:box-shadow .3s ease, filter .3s ease;
  top:9%;
  left:50%;
  width:var(--panel);
  height:calc(var(--w) * 0.80);
  margin-left:calc(var(--panel) / -2);

  overflow:hidden;
  border-radius:5px;
  background:linear-gradient(180deg, #2b2f36, #1b1e22);
  box-shadow:
    0 18px 40px rgba(0,0,0,.5),
    inset 0 0 0 1px rgba(255,255,255,.05);
  transform-origin:50% 50%;
  backface-visibility:hidden;
  transition:box-shadow .3s ease, filter .3s ease;
}

/* the facets */
.monitor-wall li:nth-child(1){
  transform:translate3d(calc(var(--w) * -3.12378), 0, calc(var(--w) * 1.36637)) rotateY(47.25deg);
}
.monitor-wall li:nth-child(2){
  transform:translate3d(calc(var(--w) * -2.36338), 0, calc(var(--w) * 0.71692)) rotateY(33.75deg);
}
.monitor-wall li:nth-child(3){
  transform:translate3d(calc(var(--w) * -1.47237), 0, calc(var(--w) * 0.26293)) rotateY(20.25deg);
}
.monitor-wall li:nth-child(4){
  transform:translate3d(calc(var(--w) * -0.50000), 0, calc(var(--w) * 0.02949)) rotateY(6.75deg);
}
.monitor-wall li:nth-child(5){
  transform:translate3d(calc(var(--w) * 0.50000), 0, calc(var(--w) * 0.02949)) rotateY(-6.75deg);
}
.monitor-wall li:nth-child(6){
  transform:translate3d(calc(var(--w) * 1.47237), 0, calc(var(--w) * 0.26293)) rotateY(-20.25deg);
}
.monitor-wall li:nth-child(7){
  transform:translate3d(calc(var(--w) * 2.36338), 0, calc(var(--w) * 0.71692)) rotateY(-33.75deg);
}
.monitor-wall li:nth-child(8){
  transform:translate3d(calc(var(--w) * 3.12378), 0, calc(var(--w) * 1.36637)) rotateY(-47.25deg);
}

/* Screens further round the curve catch less light. */
.monitor-wall li:nth-child(4),
.monitor-wall li:nth-child(5){ filter:brightness(.74); }
.monitor-wall li:nth-child(3),
.monitor-wall li:nth-child(6){ filter:brightness(.80); }
.monitor-wall li:nth-child(2),
.monitor-wall li:nth-child(7){ filter:brightness(.90); }

.welcome .monitor-wall li{ cursor:pointer; outline:none; }

/* A grey rim round each monitor on the welcome wall. Drawn as an overlay
   rather than a border: a border would take its width out of the facet,
   and the screen and the sign are sized to fill it exactly (0.62 + 0.30
   of --w), so the sign's last line would be clipped. An inset box-shadow
   does not work either - it paints under the monitor's contents, and the
   picture runs edge to edge, so the rim would vanish round the screen.
   The overlay sits on top of both, and scales with --w so it keeps the
   same weight from a phone to a wide screen. */
.welcome .monitor-wall li::after{
  content:"";
  position:absolute;
  inset:0;
  z-index:1;
  border:max(1.5px, calc(var(--w) * 0.016)) solid #7b818a;
  border-top-color:#989ea7;       /* caught by the light from above */
  border-radius:inherit;
  pointer-events:none;
}

.welcome .monitor-wall li:hover,
.welcome .monitor-wall li:focus-visible,
.wall-static li:hover{
  z-index:2;
  filter:brightness(1.12);
  box-shadow:
    0 22px 48px rgba(0,0,0,.58),
    0 0 30px rgba(245,166,35,.22),
    inset 0 0 0 1px rgba(245,166,35,.65);
}
.welcome .monitor-wall li:focus-visible{
  box-shadow:
    0 22px 48px rgba(0,0,0,.58),
    0 0 0 2px var(--accent),
    inset 0 0 0 1px rgba(245,166,35,.65);
}

/* the screen wakes: the glass glare sweeps and the picture lifts */
.monitor-wall li:hover .screen::after,
.welcome .monitor-wall li:focus-visible .screen::after{
  background:
    linear-gradient(158deg, rgba(255,255,255,.22), rgba(255,255,255,0) 52%),
    repeating-linear-gradient(180deg,
      rgba(0,0,0,.10) 0 1px, rgba(0,0,0,0) 1px 3px);
}
.monitor-wall li:hover img,
.welcome .monitor-wall li:focus-visible img{ transform:scale(1.06); }
.monitor-wall img{ transition:transform .45s cubic-bezier(.2,.7,.3,1); }

.monitor-wall li:hover .bezel b,
.welcome .monitor-wall li:focus-visible .bezel b{ color:var(--accent); }
.monitor-wall li:hover .bezel b,
.welcome .monitor-wall li:focus-visible .bezel b{
  text-shadow:
    0 0 calc(var(--w) * 0.06) rgba(245, 166, 35, .65),
    0 0 calc(var(--w) * 0.15) rgba(245, 166, 35, .35);
}

.monitor-wall .screen{
  position:relative;
  margin:0;
  height:calc(var(--w) * 0.59);
  overflow:hidden;
  background:#000;
}
.monitor-wall img{
  display:block;
  width:100%;
  height:100%;
  object-fit:cover;
}

/* glass: a diagonal glare over the top corner, plus faint scanlines */
.monitor-wall .screen::after{
  content:"";
  position:absolute; inset:0;
  pointer-events:none;
  background:
    linear-gradient(158deg, rgba(255,255,255,.13), rgba(255,255,255,0) 44%),
    repeating-linear-gradient(180deg,
      rgba(0,0,0,.16) 0 1px, rgba(0,0,0,0) 1px 3px);
}

/* ---- the overhead sign ----
   Two lines, as on the signs hanging over the real benches: the maker's
   name set large in its own casing, and PLC SYSTEM small and widely
   tracked underneath. Both are lit rather than merely coloured - the glow
   is what makes it read as signage rather than as a caption. */
.monitor-wall .bezel{
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:calc(var(--w) * 0.012);
  height:calc(var(--w) * 0.21);
  text-align:center;
}
.monitor-wall .bezel b{
  font-size:calc(var(--w) * 0.082);
  font-weight:600;

  /* No uppercasing: the reference keeps each maker's own casing, which is
     what stops the row reading as a run of identical labels. */
  text-transform:none;
  letter-spacing:.005em;
  line-height:1.05;
  color:#eaf6ff;
  text-shadow:
    0 0 calc(var(--w) * 0.05) rgba(150, 225, 255, .55),
    0 0 calc(var(--w) * 0.13) rgba(90, 190, 240, .30);
}
.monitor-wall .bezel .sys{
  display:block;
  font-size:calc(var(--w) * 0.042);
  font-weight:600;
  letter-spacing:.20em;
  text-transform:uppercase;
  color:rgba(196, 232, 250, .78);
  text-shadow:0 0 calc(var(--w) * 0.05) rgba(140, 215, 255, .40);
}

/* the software line stays out; only the two sign lines show */
.monitor-wall .bezel span:not(.sys){ display:none; }

/* Bigger screens on the welcome wall. The span is already within 48px of
   the window edge, so there is nothing to gain sideways - but the panel had
   231px of unused height below the scroll cue, so the tiles grow downward
   instead. Screen height goes from 0.62 to 1.0 of --w, which is about 60%
   more picture at the same wall width.

   Scoped to .welcome on purpose: the same cylinder is used on the combined
   course slides, and those sit inside a card that has to stay within one
   screen. */
/* Screen back to its original 0.62 of --w. The bezel keeps the taller
   0.30 the two-line sign needs, so the tile is 0.92 rather than the 0.84
   it was before the sign, and the container follows at 1.12 (tile x the
   1.22 the near facets are magnified by). */
/* Wider, on the welcome screen only. The wall sat 48px inside each edge of
   a 1440px window; running it out to about 20px makes it read as the whole
   room rather than a strip in the middle. The course walls keep their own
   --w, which is tied to the course title's measure, so this does not reach
   them. */
.welcome .monitor-wall{ --w:clamp(70px, 11vw, 158px); }

.welcome .monitor-wall{ height:calc(var(--w) * 1.12); }
.welcome .monitor-wall li{ height:calc(var(--w) * 0.92); }
.welcome .monitor-wall .screen{ height:calc(var(--w) * 0.62); }
.welcome .monitor-wall .bezel{ height:calc(var(--w) * 0.30); }
.welcome .monitor-wall .bezel b{ font-size:calc(var(--w) * 0.125); }
.welcome .monitor-wall .bezel .sys{ font-size:calc(var(--w) * 0.052); }

/* At phone scale --w is about 42px, so a ratio alone puts the sign under
   4px. These floors have to carry .welcome too - the override above is more
   specific than the plain .monitor-wall rule in the phone block, so a floor
   set there never reached the welcome wall. */
@media (max-width:860px){
  .welcome .monitor-wall .bezel{ height:auto; padding:5px 2px 6px; }
  .welcome .monitor-wall .bezel b{ font-size:max(10px, calc(var(--w) * 0.125)); }
  .welcome .monitor-wall .bezel .sys{ font-size:max(6px, calc(var(--w) * 0.052)); letter-spacing:.08em; }
}

/* ---- scroll cue ---- */
.scroll-cue{
  display:inline-flex; align-items:center; gap:9px;
  margin-top:clamp(18px, 3vh, 34px);
  font-size:12.5px; font-weight:600;
  letter-spacing:.04em;
  color:var(--on-navy-mute);
  transition:color .2s ease;
}
.scroll-cue::after{
  content:"";
  width:7px; height:7px;
  border-right:1.5px solid currentColor;
  border-bottom:1.5px solid currentColor;
  transform:translateY(-2px) rotate(45deg);
  animation:cue-bob 1.8s ease-in-out infinite;
}
.scroll-cue:hover{ color:var(--accent); }

@keyframes cue-bob{
  0%, 100% { transform:translateY(-3px) rotate(45deg); }
  50%      { transform:translateY(2px)  rotate(45deg); }
}

/* Short desktop windows: the headline is sized off vw, so it does not
   shrink when the viewport gets shorter and the cue drops off the end.
   Tighten the vertical rhythm rather than let the welcome screen scroll. */
@media (min-width:861px) and (max-height:840px){
  .welcome{ padding-block:18px 74px; }
  .welcome-title{ font-size:clamp(26px, 3.4vw, 38px); }
  .welcome-lede{ font-size:13.5px; margin-top:8px; }
  .monitor-wall{ --w:clamp(62px, 8.6vw, 118px); margin-top:14px; }
  .scroll-cue{ margin-top:14px; }
}

@media (prefers-reduced-motion:reduce){
  .monitor-wall{ perspective-origin:50% 63%; }
  .monitor-wall img{ transition:none; }
}

/* The phone gets the same curved wall, not a flat grid - the geometry is
   all driven off --w, so one value rescales it. 15.4vw is what keeps the
   outermost facets inside the screen once perspective has pulled them
   back; the desktop floor of 70px would run about 430px wide. */
@media (max-width:860px){
  .welcome{ padding-block:26px clamp(48px, 7vh, 68px); }
  .monitor-wall{ --w:min(10.9vw, 70px); margin-top:clamp(14px, 2.4vh, 26px); }
  /* The .welcome override that widens the wall on desktop is more specific
     than this, so the phone size has to be restated at the same specificity
     - otherwise the wall keeps its 158px floor and runs 628px wide on a
     390px screen. */
  .welcome .monitor-wall{ --w:min(10.9vw, 70px); }
  /* The bezel caption is a fixed ratio of --w, which at phone scale lands
     under 4px. Hold it at a readable floor and give the letters back the
     room by dropping the tracking. */
  .monitor-wall .bezel b{
    font-size:max(9px, calc(var(--w) * 0.082));
    letter-spacing:0;
  }
  .monitor-wall .bezel .sys{
    font-size:max(6px, calc(var(--w) * 0.042));
    letter-spacing:.1em;
  }

}

@media (prefers-reduced-motion:reduce){
  .scroll-cue::after{ animation:none; }
}

/* ---------- Hero ------------------------------------------- */
/* box-shadow + clip-path gives a max-width element a full-bleed background */
.hero{
  position:relative;
  background:var(--navy);
  box-shadow:0 0 0 100vmax var(--navy);
  clip-path:inset(0 -100vmax);
  background-image:
    radial-gradient(120% 90% at 12% 0%, rgba(245,166,35,.10), transparent 58%),
    radial-gradient(90% 70% at 100% 0%, rgba(245,166,35,.05), transparent 60%);
  color:var(--on-navy);
  display:grid;
  grid-template-columns:minmax(0,1fr) 400px;
  gap:60px;
  align-items:start;
  padding-top:78px;
  padding-bottom:64px;
  min-height:520px;
}
.hero > *{ position:relative; z-index:2; }
.hero h1{
  font-size:clamp(36px, 4.6vw, 54px);
  font-weight:800; line-height:1.08;
  color:var(--on-navy);
}
.lede{
  margin-top:20px; max-width:46ch;
  font-size:15.5px; line-height:1.75;
  color:var(--on-navy-mute);
}
.hero-actions{ display:flex; flex-wrap:wrap; gap:12px; margin-top:30px; }
.microcopy{
  display:flex; align-items:center; gap:8px;
  margin-top:16px;
  font-size:12.5px; color:var(--on-navy-dim);
}
.micro-ico{ display:inline-grid; place-items:center; flex:none; color:var(--on-navy-mute); }
.micro-ico svg{ width:15px; height:15px; }

/* live-status panel */
.status-card{
  background:var(--navy-2);
  border:1px solid var(--navy-line);
  border-radius:var(--r);
  padding:22px 24px 8px;
}
.status-head{
  display:flex; justify-content:space-between; align-items:center;
  padding-bottom:16px;
  border-bottom:1px solid var(--navy-line);
  color:var(--on-navy-mute);
}
.status-head .dim{ color:var(--green); }

.status-list li{
  display:flex; align-items:flex-start; gap:13px;
  padding:17px 0;
  border-bottom:1px solid var(--navy-line);
  transition:padding-left .2s ease;
}
.status-list li:hover{ padding-left:4px; }
.status-list li:last-child{ border-bottom:0; }
.status-list b{
  display:block;
  font-size:23px; font-weight:800; letter-spacing:-.03em; line-height:1.15;
  font-variant-numeric:tabular-nums;
  color:var(--on-navy);
}
.status-list small{
  display:block; margin-top:3px;
  font-size:12.5px; color:var(--on-navy-mute);
}

/* round icon badges in the live-status panel */
.stat-ico{
  width:42px; height:42px; flex:none;
  display:grid; place-items:center;
  border-radius:50%;
  margin-top:2px;
}
.ico-green{ background:rgba(47,180,124,.15); color:#3ddb96; }
.ico-amber{ background:rgba(245,166,35,.15); color:var(--accent); }
.ico-blue { background:rgba(245,166,35,.11); color:var(--brand-soft); }

.dot{
  width:9px; height:9px; border-radius:50%;
  flex:none; margin-top:9px;
}
.dot-green{ background:var(--green); box-shadow:0 0 0 3px rgba(47,180,124,.18); }
.dot-amber{ background:var(--accent); box-shadow:0 0 0 3px rgba(245,166,35,.18); }

/* ---------- Brand strip (sits inside the navy block) -------- */
.brands{
  position:relative;
  background:var(--navy);
  box-shadow:0 0 0 100vmax var(--navy);
  clip-path:inset(0 -100vmax);
  padding-top:30px;
  padding-bottom:58px;
  text-align:center;
}
.brands-cap{
  font-size:12.5px; color:var(--on-navy-dim);
  margin-bottom:18px;
}
.brand-row{
  display:inline-flex; flex-wrap:wrap; justify-content:center;
  gap:14px 36px;
  padding:20px 24px;
  border:1px solid var(--navy-line);
  border-radius:var(--r);
  background:var(--navy-2);
}
.brand-row li{
  position:relative;
  font-family:var(--sans);
  font-size:13px; font-weight:600;
  letter-spacing:.15em; text-transform:uppercase;
  color:var(--on-navy-mute);
  transition:color .18s ease;
}
.brand-row li:not(:last-child)::after{
  content:""; position:absolute; right:-19px; top:50%;
  width:3px; height:3px; margin-top:-1.5px;
  border-radius:50%; background:var(--navy-line);
}
.brand-row li:hover{ color:var(--on-navy); }

/* ---------- One fixed screen, content swaps ------------------
   The screen does not move. Each course group is a tall scroll
   track - one screen-height per course - containing a stage that
   sticks to the viewport. The cards are stacked on top of each
   other inside that stage, and scrolling fades one out and the
   next in, so scrolling changes the content in place rather than
   moving the page past it.

   The animation hangs off the track's `contain` range, which is
   exactly the span over which the stage is stuck: card 1 owns the
   first slice of that span, card 2 the next, and so on. */
.tracks,
.group-combined .tracks{
  position:relative;
  display:block;
  /* 145vh per course, not 100vh. At one screen each, a course swapped out
     almost as soon as it had arrived - the transition was over before you
     had felt it. The extra travel is scroll distance only: the stage still
     shows one course at a time, it just takes longer to hand over, so the
     movement is slow enough to watch and stays under your control. */
  height:calc(var(--count, 1) * 118vh);
  height:calc(var(--count, 1) * 118svh);   /* svh fallback for mobile chrome; must match the vh above */
  view-timeline-name:--track;
  view-timeline-axis:block;
}

.tracks .stage{
  position:sticky;
  top:0;
  height:100vh;
  height:100svh;
  overflow:hidden;
}

/* `.stage >` is needed here: the .tracks .card block further down the sheet
   also sets position and min-height, and at equal specificity the later rule
   wins - which left the cards in normal flow, stacked hundreds of pixels
   below the stage instead of layered inside it. */
.tracks .stage > .card{
  position:absolute;
  inset:0;
  min-height:0;
  opacity:0;
  /* Eased, not linear. Driven straight off scroll the fade changed at a
     constant rate and the swap read as a hard mix at the halfway point;
     easing each segment lets it leave and arrive gently. */
  animation:stage-fade cubic-bezier(.37,0,.24,1) both,
            stage-drift linear both;
  animation-timeline:--track, --track;
}

/* The stage does not move, so all the motion has to come from the content.

   Fade and drift are two animations, not one, because they want opposite
   timing. The fade is eased and quick (15% in, 15% out) so only one course
   is legible at a time. The drift is linear and runs corner to corner, so
   the copy keeps moving at a constant rate for as long as you keep
   scrolling - the same treatment the photo already had. Sharing one eased
   keyframe set left the text almost still through the middle of a course
   while the photo carried on, which read as the text being stuck. */
/* The old course fades out over the first two thirds of the overlap with
   the next one (85-95% of its slice), and the new one fades in over the
   last two thirds (5-15% of its own) - so they barely cross, and the next
   course's name comes up on its own rather than on top of the last
   course's photo and curriculum. */
@keyframes stage-fade{
  0%, 5%    { opacity:0; }
  15%       { opacity:1; }
  85%       { opacity:1; }
  95%, 100% { opacity:0; }
}
@keyframes stage-fade-first{
  0%        { opacity:1; }
  85%       { opacity:1; }
  95%, 100% { opacity:0; }
}
@keyframes stage-fade-last{
  0%, 5%    { opacity:0; }
  15%       { opacity:1; }
  100%      { opacity:1; }
}

/* copy drifts 64px, the photo 112px - the difference is what reads as depth */
@keyframes stage-drift{
  0%   { transform:translateY(64px); }
  100% { transform:translateY(-64px); }
}

@keyframes stage-swap-media{
  0%   { transform:translateY(4%) scale(1.1); }
  100% { transform:translateY(-4%) scale(1.1); }
}

/* The first card has nothing before it to cross over from, and the last has
   nothing after it, so they hold instead of fading at those ends - otherwise
   the stage is blank for the opening and closing moments of the track. */


.tracks .stage > .card:first-of-type{ animation-name:stage-fade-first, stage-drift; }
.tracks .stage > .card:last-of-type{ animation-name:stage-fade-last, stage-drift; }

.tracks[style*="--count:8"] .card:nth-child(1){ animation-range:contain 0.000% contain 14.706%, contain 0.000% contain 14.706%; }
.tracks[style*="--count:8"] .card:nth-child(2){ animation-range:contain 12.500% contain 27.206%, contain 12.500% contain 27.206%; }
.tracks[style*="--count:8"] .card:nth-child(3){ animation-range:contain 25.000% contain 39.706%, contain 25.000% contain 39.706%; }
.tracks[style*="--count:8"] .card:nth-child(4){ animation-range:contain 37.500% contain 52.206%, contain 37.500% contain 52.206%; }
.tracks[style*="--count:8"] .card:nth-child(5){ animation-range:contain 50.000% contain 64.706%, contain 50.000% contain 64.706%; }
.tracks[style*="--count:8"] .card:nth-child(6){ animation-range:contain 62.500% contain 77.206%, contain 62.500% contain 77.206%; }
.tracks[style*="--count:8"] .card:nth-child(7){ animation-range:contain 75.000% contain 89.706%, contain 75.000% contain 89.706%; }
.tracks[style*="--count:8"] .card:nth-child(8){ animation-range:contain 87.500% contain 100.000%, contain 87.500% contain 100.000%; }
.tracks[style*="--count:5"] .card:nth-child(1){ animation-range:contain 0.000% contain 23.529%, contain 0.000% contain 23.529%; }
.tracks[style*="--count:5"] .card:nth-child(2){ animation-range:contain 20.000% contain 43.529%, contain 20.000% contain 43.529%; }
.tracks[style*="--count:5"] .card:nth-child(3){ animation-range:contain 40.000% contain 63.529%, contain 40.000% contain 63.529%; }
.tracks[style*="--count:5"] .card:nth-child(4){ animation-range:contain 60.000% contain 83.529%, contain 60.000% contain 83.529%; }
.tracks[style*="--count:5"] .card:nth-child(5){ animation-range:contain 80.000% contain 100.000%, contain 80.000% contain 100.000%; }
.tracks[style*="--count:9"] .card:nth-child(1){ animation-range:contain 0.000% contain 13.072%, contain 0.000% contain 13.072%; }
.tracks[style*="--count:9"] .card:nth-child(2){ animation-range:contain 11.111% contain 24.183%, contain 11.111% contain 24.183%; }
.tracks[style*="--count:9"] .card:nth-child(3){ animation-range:contain 22.222% contain 35.294%, contain 22.222% contain 35.294%; }
.tracks[style*="--count:9"] .card:nth-child(4){ animation-range:contain 33.333% contain 46.405%, contain 33.333% contain 46.405%; }
.tracks[style*="--count:9"] .card:nth-child(5){ animation-range:contain 44.444% contain 57.516%, contain 44.444% contain 57.516%; }
.tracks[style*="--count:9"] .card:nth-child(6){ animation-range:contain 55.556% contain 68.627%, contain 55.556% contain 68.627%; }
.tracks[style*="--count:9"] .card:nth-child(7){ animation-range:contain 66.667% contain 79.739%, contain 66.667% contain 79.739%; }
.tracks[style*="--count:9"] .card:nth-child(8){ animation-range:contain 77.778% contain 90.850%, contain 77.778% contain 90.850%; }
.tracks[style*="--count:9"] .card:nth-child(9){ animation-range:contain 88.889% contain 100.000%, contain 88.889% contain 100.000%; }

/* The photo rides the same slice of the track as the card it belongs to -
   a child cannot inherit an animation-range, so the slices are restated
   for it. */
/* The parallax runs on the picture inside the frame, not on the frame.
   Moving the frame meant the photo drifted 112px further than the heading
   above it, and by the end of a course it had climbed 51px over the course
   name while the text was still fully legible. The frame is overflow:hidden,
   so shifting the image within it gives the same depth with nothing able to
   leave its box.

   The scale is what buys the room to move. It was 1.3, which cropped a
   good part of every photo; it is 1.1 now, so more of the photo shows,
   and the travel is cut to match: at 1.1 the picture stands 5% of the
   frame's height proud at the top and at the bottom, and it moves 4% each
   way, so no edge of it ever shows (1-2px to spare at every size). The
   travel is a percentage of the picture's own height, so it holds for the
   phone's shorter photo as well as the desktop's. */
.tracks .stage > .card .card-media{
  overflow:hidden;
}
.tracks .stage > .card .card-media img{
  animation:stage-swap-media linear both;
  animation-timeline:--track;
  will-change:transform;
}
.tracks[style*="--count:8"] .card:nth-child(1) .card-media img{ animation-range:contain 0.000% contain 14.706%; }
.tracks[style*="--count:8"] .card:nth-child(2) .card-media img{ animation-range:contain 12.500% contain 27.206%; }
.tracks[style*="--count:8"] .card:nth-child(3) .card-media img{ animation-range:contain 25.000% contain 39.706%; }
.tracks[style*="--count:8"] .card:nth-child(4) .card-media img{ animation-range:contain 37.500% contain 52.206%; }
.tracks[style*="--count:8"] .card:nth-child(5) .card-media img{ animation-range:contain 50.000% contain 64.706%; }
.tracks[style*="--count:8"] .card:nth-child(6) .card-media img{ animation-range:contain 62.500% contain 77.206%; }
.tracks[style*="--count:8"] .card:nth-child(7) .card-media img{ animation-range:contain 75.000% contain 89.706%; }
.tracks[style*="--count:8"] .card:nth-child(8) .card-media img{ animation-range:contain 87.500% contain 100.000%; }
.tracks[style*="--count:5"] .card:nth-child(1) .card-media img{ animation-range:contain 0.000% contain 23.529%; }
.tracks[style*="--count:5"] .card:nth-child(2) .card-media img{ animation-range:contain 20.000% contain 43.529%; }
.tracks[style*="--count:5"] .card:nth-child(3) .card-media img{ animation-range:contain 40.000% contain 63.529%; }
.tracks[style*="--count:5"] .card:nth-child(4) .card-media img{ animation-range:contain 60.000% contain 83.529%; }
.tracks[style*="--count:5"] .card:nth-child(5) .card-media img{ animation-range:contain 80.000% contain 100.000%; }
.tracks[style*="--count:9"] .card:nth-child(1) .card-media img{ animation-range:contain 0.000% contain 13.072%; }
.tracks[style*="--count:9"] .card:nth-child(2) .card-media img{ animation-range:contain 11.111% contain 24.183%; }
.tracks[style*="--count:9"] .card:nth-child(3) .card-media img{ animation-range:contain 22.222% contain 35.294%; }
.tracks[style*="--count:9"] .card:nth-child(4) .card-media img{ animation-range:contain 33.333% contain 46.405%; }
.tracks[style*="--count:9"] .card:nth-child(5) .card-media img{ animation-range:contain 44.444% contain 57.516%; }
.tracks[style*="--count:9"] .card:nth-child(6) .card-media img{ animation-range:contain 55.556% contain 68.627%; }
.tracks[style*="--count:9"] .card:nth-child(7) .card-media img{ animation-range:contain 66.667% contain 79.739%; }
.tracks[style*="--count:9"] .card:nth-child(8) .card-media img{ animation-range:contain 77.778% contain 90.850%; }
.tracks[style*="--count:9"] .card:nth-child(9) .card-media img{ animation-range:contain 88.889% contain 100.000%; }

/* The cards cross-fade as a whole now, so their inner parts must not
   animate as well - and a view() timeline on them would be meaningless
   anyway, since an absolutely positioned card inside a stuck stage never
   moves through the viewport. */
.tracks .card .scroll-fx{
  animation:none;
  opacity:1;
  transform:none;
}

/* ---------- Curved bank on the combined slides ---------------
   The multi-brand programmes show the lab as the same curved
   cylinder as the welcome screen rather than one flat banner -
   these are the courses that actually cover every platform, so a
   bank of screens says it better than a single photograph.

   Only the geometry is shared. The camera does not track the
   cursor here and the screens are not click targets: that belongs
   to the welcome wall, which is the one asking to be explored. */
.tracks .card-multi .monitor-wall{
  /* Sized so the bank spans the same measure as the course title above it.
     Derived from the wrap rather than a flat vw: the wrap is capped at
     1360px, so above that width the title stops growing while a vw value
     would carry on - at 1200px a fixed 10vw left the wall 28px narrow on
     each side. The rendered span is about 8.84x --w once perspective has
     pulled the far facets back - measured at 8.96, not the 8.84 the flat
     geometry predicts. */
  --w:calc((min(100vw - 2 * var(--scr-x), 1360px) - 68px) / 8.96);
  grid-column:1; grid-row:1;
  justify-self:center;
  margin:0 0 clamp(10px, 2vh, 22px);
  perspective-origin:50% 63%;
}

.wall-static li{ cursor:default; }
.wall-static .bezel b{ font-size:calc(var(--w) * 0.072); }

/* The phone size for this one. It has to sit here rather than with the
   welcome wall's mobile block near the top of the sheet: both selectors
   are three classes, a media query adds no specificity, and the desktop
   rule directly above would have won on document order.

   Narrower than the welcome wall's phone size because this cylinder is
   inside a card - the page gutter and the card's own padding come out
   first, leaving about 302px of a 390px screen rather than the lot.

   Not gated on motion: the bank is over-wide either way, clipped by the
   stage when the slides are pinned and 28px past the edge of the page
   when reduced motion stacks them. */
@media (max-width:860px){
  .tracks .card-multi .monitor-wall{ --w:min(8.6vw, 46px); }
  .wall-static .bezel b{
    font-size:max(8px, calc(var(--w) * 0.082));
    letter-spacing:0;
  }
  .wall-static .bezel .sys{ font-size:max(5.5px, calc(var(--w) * 0.042)); letter-spacing:.08em; }
}

/* ---------- Alternating course layout -----------------------
   Eight single-brand courses in a row all read the same when the
   photo is always on the left. Even-numbered ones put the photo on
   the right instead, and because the courses cross-fade into one
   another on a fixed stage, the side the picture lands on becomes
   part of the change rather than a layout inconsistency.

   Only the single-brand set: the combined programmes are a single
   column with the bank of screens on top, so there is nothing to
   mirror. */
.tracks-flat .stage > .card:nth-of-type(even){
  grid-template-columns:minmax(0,1fr) minmax(0,1.15fr);
}
.tracks-flat .stage > .card:nth-of-type(even) .card-media{
  grid-column:2;
  justify-self:start;
}
.tracks-flat .stage > .card:nth-of-type(even) .card-body{
  grid-column:1;
}

/* ---------- Ladder rung -------------------------------------
   The one drawing every PLC engineer reads all day: power rail,
   a normally-open contact, a normally-closed one, a timer and an
   output coil, tagged the way a real program is. It sits behind
   the course content at the foot of the stage.

   The spark is a translate animation rather than an animated
   stroke-dashoffset - transforms are composited, so it costs the
   scroll nothing. */
.ladder{
  position:absolute;
  left:0; right:0;

  /* Along the top band, not the bottom: below the content the photo covers
     two thirds of the rung, so only the coil end of it ever showed. Up here
     the strip between the header and the course kicker is empty on every
     slide, whichever side the photo is on. */
  /* Clear of the sticky header: the stage pins at top:0, so the header
     overlays its first ~79px and a rung any higher would run behind it. */
  top:clamp(92px, 11vh, 108px);
  height:clamp(30px, 4.2vh, 48px);

  z-index:0;
  pointer-events:none;
  overflow:hidden;      /* clips the spark at the end of the rung */
  opacity:.85;
}
.ladder svg{
  display:block;
  width:100%; height:100%;
  overflow:visible;
}
.ladder-line path,
.ladder-line rect{
  fill:none;
  stroke:rgba(245,166,35,.26);
  stroke-width:1.5;
  vector-effect:non-scaling-stroke;
}
.ladder-tag{
  fill:rgba(245,166,35,.34);
  font-family:var(--mono);
  font-size:9px;
  letter-spacing:.08em;
}

/* current running the rung */
.ladder .spark{
  position:absolute;
  top:50%; left:0;
  width:44px; height:2px;
  margin-top:-1px;
  border-radius:2px;
  background:linear-gradient(90deg,
    transparent, rgba(245,166,35,.75), transparent);
  animation:rung-current 4.6s linear infinite;
  will-change:transform;
}

@keyframes rung-current{
  0%   { transform:translateX(-44px); opacity:0; }
  8%   { opacity:1; }
  92%  { opacity:1; }
  100% { transform:translateX(calc(100vw - 40px)); opacity:0; }
}

/* The rung stays on a phone. It runs shorter and sits tighter under the
   header, but the current still crosses it - it is the one piece of the
   page that says "control system" without a word of copy. */
@media (max-width:860px){
  .ladder{
    top:clamp(78px, 10.4vh, 96px);
    height:clamp(20px, 3.2vh, 30px);
    opacity:.7;
  }
  .ladder-tag{ font-size:7px; letter-spacing:.04em; }
  .ladder .spark{ width:30px; }
}
@media (prefers-reduced-motion:reduce),
       (max-height:619px){
  .ladder{ display:none; }
  .ladder .spark{ animation:none; opacity:0; }
}

/* ---------- Terminal strip ----------------------------------
   A row of terminal markers along the foot of the stage, one per
   course in the set, with the one on screen lit. The pinned stage
   otherwise gives no sense of position in the sequence - you see
   a course but not that it is the fourth of eight.

   Each marker rides the same slice of the track's `contain` range
   as the card it belongs to, so it lights exactly in step with the
   crossfade. */
.rail{
  position:absolute;
  left:0; right:0;
  bottom:clamp(26px, 5vh, 54px);
  z-index:4;

  display:flex;
  align-items:flex-end;
  gap:clamp(14px, 2vw, 30px);
  margin:0; padding:0;
  list-style:none;
}

/* the rail itself: a hairline the markers stand on */
.rail::before{
  content:"";
  position:absolute;
  left:0; right:0; bottom:5px;
  height:1px;
  background:linear-gradient(90deg,
    var(--line) 0%, var(--line) 82%, transparent 100%);
}

.rail li{
  position:relative;
  animation:rail-live linear both;
  animation-timeline:--track;
}

/* The marker is the button. `all:unset` strips the UA button styling so the
   tick and number lay out exactly as they did as bare elements. */
.rail button{
  all:unset;
  box-sizing:border-box;
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:7px;
  padding:6px 10px;
  cursor:pointer;
}
.rail button:focus-visible{
  outline:2px solid var(--accent);
  outline-offset:2px;
  border-radius:3px;
}
.rail button:hover i{ background:var(--accent); }
.rail button:hover b{ color:var(--accent); }

/* the terminal block */
.rail i{
  display:block;
  width:2px;
  height:var(--tick, 9px);
  border-radius:1px;
  background:var(--tick-color, var(--line));
}
.rail b{
  font-size:9.5px; font-weight:700;
  letter-spacing:.12em;
  font-variant-numeric:tabular-nums;
  color:var(--num-color, var(--text-dim));
}

@keyframes rail-live{
  0%,   100% { --tick:9px;  --tick-color:var(--line);   --num-color:var(--text-dim); }
  32%,  68%  { --tick:22px; --tick-color:var(--accent); --num-color:var(--accent); }
}

/* Custom properties only interpolate when the browser knows their type;
   without this the marker would jump between states instead of easing. */
@property --tick{ syntax:"<length>"; inherits:true; initial-value:9px; }
@property --tick-color{ syntax:"<color>"; inherits:true; initial-value:#2c3036; }
@property --num-color{ syntax:"<color>"; inherits:true; initial-value:#6d747c; }

.tracks[style*="--count:8"] .rail li:nth-child(1){ animation-range:contain 0.000% contain 15.125%; }
.tracks[style*="--count:8"] .rail li:nth-child(2){ animation-range:contain 9.875% contain 27.625%; }
.tracks[style*="--count:8"] .rail li:nth-child(3){ animation-range:contain 22.375% contain 40.125%; }
.tracks[style*="--count:8"] .rail li:nth-child(4){ animation-range:contain 34.875% contain 52.625%; }
.tracks[style*="--count:8"] .rail li:nth-child(5){ animation-range:contain 47.375% contain 65.125%; }
.tracks[style*="--count:8"] .rail li:nth-child(6){ animation-range:contain 59.875% contain 77.625%; }
.tracks[style*="--count:8"] .rail li:nth-child(7){ animation-range:contain 72.375% contain 90.125%; }
.tracks[style*="--count:8"] .rail li:nth-child(8){ animation-range:contain 84.875% contain 100.000%; }
.tracks[style*="--count:5"] .rail li:nth-child(1){ animation-range:contain 0.000% contain 24.200%; }
.tracks[style*="--count:5"] .rail li:nth-child(2){ animation-range:contain 15.800% contain 44.200%; }
.tracks[style*="--count:5"] .rail li:nth-child(3){ animation-range:contain 35.800% contain 64.200%; }
.tracks[style*="--count:5"] .rail li:nth-child(4){ animation-range:contain 55.800% contain 84.200%; }
.tracks[style*="--count:5"] .rail li:nth-child(5){ animation-range:contain 75.800% contain 100.000%; }
.tracks[style*="--count:9"] .rail li:nth-child(1){ animation-range:contain 0.000% contain 13.444%; }
.tracks[style*="--count:9"] .rail li:nth-child(2){ animation-range:contain 8.778% contain 24.556%; }
.tracks[style*="--count:9"] .rail li:nth-child(3){ animation-range:contain 19.889% contain 35.667%; }
.tracks[style*="--count:9"] .rail li:nth-child(4){ animation-range:contain 31.000% contain 46.778%; }
.tracks[style*="--count:9"] .rail li:nth-child(5){ animation-range:contain 42.111% contain 57.889%; }
.tracks[style*="--count:9"] .rail li:nth-child(6){ animation-range:contain 53.222% contain 69.000%; }
.tracks[style*="--count:9"] .rail li:nth-child(7){ animation-range:contain 64.333% contain 80.111%; }
.tracks[style*="--count:9"] .rail li:nth-child(8){ animation-range:contain 75.444% contain 91.222%; }
.tracks[style*="--count:9"] .rail li:nth-child(9){ animation-range:contain 86.556% contain 100.000%; }

/* While script.js runs the stage (.js-swap - the timed change of course),
   the lit marker is the course on screen, not a slice of the scroll. The
   ranges above light a marker only through the middle half of its course,
   so around every change none was lit, or the old one was still fading,
   while the card itself changes on a timer. Here the marker changes with
   the card: the old one goes out as its course fades, the new one comes
   on as its course fades in, after the same .45s wait. The ranges above
   stay as the fallback wherever the stage is not run by script. */
.tracks.js-swap .rail li{
  animation:none;
  --tick:9px; --tick-color:var(--line); --num-color:var(--text-dim);
  transition:--tick .5s cubic-bezier(.4,0,.6,1),
             --tick-color .5s cubic-bezier(.4,0,.6,1),
             --num-color .5s cubic-bezier(.4,0,.6,1);
}
.tracks.js-swap .rail li.is-current{
  --tick:22px; --tick-color:var(--accent); --num-color:var(--accent);
  transition:--tick .65s cubic-bezier(.2,.6,.3,1) .45s,
             --tick-color .65s cubic-bezier(.2,.6,.3,1) .45s,
             --num-color .65s cubic-bezier(.2,.6,.3,1) .45s;
}

/* the stacked list keeps its strip - see "The stacked list keeps its
   terminal strip" at the end of the sheet */

/* Eight markers still fit a phone once the gap and the numerals come in.
   Where you are in a set of eight matters more on a small screen than on
   a large one, not less - there is no peripheral view of the rest. */
@media (max-width:860px){
  .rail{
    bottom:clamp(10px, 2.2vh, 22px);
    gap:0;
  }
  /* Each marker takes an equal share of the rail, up to a thumb-wide 40px -
     so every marker fits whatever the course count and however far the
     curved screen sits in: nine are 32px each at 360px, five are 40px. A
     fixed width per marker ran the ninth off the end of the rail. */
  .rail li{ gap:5px; flex:1 1 0; min-width:0; max-width:40px; }
  .rail button{ min-width:0; width:100%; padding:6px 0; }
  .rail b{ font-size:10px; letter-spacing:.04em; }
}

/* ---------- Every section is a held screen -------------------
   The same idea as the course stage: the screen holds still and
   the content changes. Each section pins to the viewport and the
   next one comes up over it, and since they all share one ground
   and carry no edge, what reads is the content swapping rather
   than a card sliding past.

   A panel taller than the window pins by its bottom edge: script.js
   (fitPanels) sets --pin-top to (window height - its height), so it
   scrolls until its foot reaches the bottom of the window and then
   holds while the next one comes up over it. That takes in the course
   panels too, each thousands of pixels tall with its own stage stuck
   inside it - a sticky stage inside a sticky panel works, since the
   stage sticks to the window within its own track either way.

   CSS scroll-snap was tried here and taken back out: with sections
   this tall, mandatory snapping would not advance - repeated wheel
   spins stayed on the same section - and it also made the footer
   unreachable, since the last panel became the final snap point
   and the scroller kept pulling back to it. */
.panel:not(.panel-scroller),
.panel.panel-scroller{
  position:sticky;
  top:var(--pin-top, 0px);
}
.panel{
  position:relative;
  min-height:100vh;
  /* A phone's URL bar makes 100vh taller than the screen it can actually
     show, so every pinned panel started a chrome-height below the fold.
     svh is the small viewport - the height with the bars out. */
  min-height:100svh;
  background:var(--bg);
  display:flex;
  flex-direction:column;
  justify-content:center;
  justify-content:safe center;   /* falls back to the top when a
                                    panel outgrows the viewport */
}
/* All one ground. A lighter tint here made the panel visible as a block
   travelling up the screen as it covered the one behind - the stacking
   itself should be invisible, only the content should read as arriving. */
.panel[data-tone="tint"]{ background:var(--bg); }
.panel[data-tone="hero"]{ background:var(--navy); }

/* No visible edge on any panel: no hairline, no rounded corners, no cast
   shadow. Every panel shares the same ground, so one section runs into the
   next as a continuous surface. */
.panel + .panel,
.site-footer{
  border-top:0;
  border-radius:0;
  box-shadow:none;
}

/* The last panel has nothing after it to cover it, so the footer
   does that job and needs the same opaque, pinned treatment. */
.site-footer{
  position:relative;
  z-index:1;
  margin-top:0;
}

/* Sticky panels rely on the document scroller; a scroll container or
   a transformed ancestor anywhere above them would break it. */
@media (prefers-reduced-motion:reduce){
  .panel,
  .panel:not(.panel-scroller),
  .panel.panel-scroller{
    position:static;
    min-height:0;
    display:block;
    border-radius:0;
    box-shadow:none;
  }
  .panel + .panel{ border-top:0; }
  .site-footer{ border-radius:0; box-shadow:none; }
}

/* ---------- Sections --------------------------------------- */
.section{ padding-block:64px 24px; }

.section-head{
  display:flex; flex-wrap:wrap; align-items:flex-end;
  justify-content:space-between; gap:20px;
  margin-bottom:30px;
}
.section-head h2,
.testimonials-title{
  position:relative;
  font-size:clamp(24px, 2.7vw, 31px); font-weight:800;
  padding-bottom:13px;
}
.section-head h2::after,
.testimonials-title::after{
  content:""; position:absolute; left:0; bottom:0;
  width:44px; height:3px; border-radius:2px;
  background:var(--accent);
}
.section-head p{
  max-width:46ch; text-align:right;
  font-size:14px; color:var(--text-muted); line-height:1.7;
}
.testimonials-title{ display:block; margin-bottom:26px; }

/* ---------- Course cards ----------------------------------- */
/* ---------- Course groups ----------------------------------
   Two headings matching the categories the main site filters by:
   combined-plc (multi-brand programmes) and individual-plc. */
.group-title{
  display:flex; align-items:center; gap:12px;
  margin:2px 0 18px;
  font-size:15px; font-weight:700; letter-spacing:.01em;
}
.group-title::before{
  content:""; flex:none;
  width:3px; height:16px; border-radius:2px;
  background:var(--accent);
}
.group-count{
  font-size:10.5px; font-weight:600;
  letter-spacing:.14em; text-transform:uppercase;
  color:var(--text-dim);
}
.course-group + .course-group{ margin-top:40px; }

/* ---------- Coverage chips -------------------------------
   What each combined programme bundles, shown in the card itself. Colour
   groups the kind of platform; every entry comes from that course's own
   page on inferconautomation.com. */
.covers-cap{
  margin-top:14px; margin-bottom:8px;
  font-size:10px; font-weight:700;
  letter-spacing:.14em; text-transform:uppercase;
  color:var(--text-dim);
}
/* the level labels match the lines on the individual course cards:
   Basic-Tool Training in light amber, Real time application in blue */
.covers-cap.lvl-basic{ color:#ffce7a; }
.covers-cap.lvl-advanced{ color:#8cc6fa; }
.covers + .covers-cap{ margin-top:12px; }
.covers{
  display:flex; flex-wrap:wrap; gap:7px;
}
.covers li{
  padding:5px 11px;
  border:1px solid transparent;
  border-radius:99px;
  font-size:11.5px; font-weight:600;
  line-height:1.35;
}
.covers .cat-plc{
  background:rgba(245,166,35,.12);
  border-color:rgba(245,166,35,.30);
  color:#ffce7a;
}
.covers .cat-scada{
  background:rgba(95,176,247,.12);
  border-color:rgba(95,176,247,.30);
  color:#8cc6fa;
}
.covers .cat-dcs{
  background:rgba(62,207,142,.12);
  border-color:rgba(62,207,142,.30);
  color:#74dfae;
}
.covers .cat-iiot{
  background:rgba(177,145,245,.12);
  border-color:rgba(177,145,245,.30);
  color:#c4acf8;
}

/* ---------- Combined programmes: image above course content */
.group-combined .tracks{
  grid-template-columns:repeat(2,minmax(0,1fr));
  grid-template-rows:none;
}
.group-combined .card{ display:block; padding:22px 24px; }
.group-combined .card-flagship{ display:flex; flex-direction:column; padding:26px 28px 24px; }
.group-combined .card-media,
.group-combined .card-flagship .card-media{
  width:auto; height:auto; min-height:0;
  margin:0 -24px 18px;
}
.group-combined .card-media img{ width:100%; height:auto; aspect-ratio:13/5; object-fit:cover; }
.group-combined .card-flagship .card-media{ margin:0 -28px 20px; }
.group-combined .card-flagship .card-media img{ aspect-ratio:4/3; }

/* Basics / Advanced inside a card: told apart by the text colour alone -
   the basics line reads light, the advanced line a shade darker. */
.card-proj.proj-basic{ color:#c9ced5; }
.card-proj.proj-advanced{ color:#c9ced5; }
/* Basic-Tool Training in light amber, Real time application in blue */
.card-proj.proj-basic b{ color:#ffce7a; }
.card-proj.proj-advanced b{ color:#8cc6fa; }


/* the individual group has no flagship, so it is a plain grid */
.tracks-flat{ grid-template-rows:none; }

.tracks{
  display:grid;
  grid-template-columns:1fr 1fr 1fr;
  grid-template-rows:auto auto;
  gap:20px;
}
.card{
  border:1px solid var(--line);
  border-radius:var(--r);
  background:var(--panel);
  overflow:hidden;
  padding:22px 24px;
  box-shadow:var(--shadow);
  transition:border-color .18s ease, transform .18s ease, box-shadow .18s ease;
}
.card{ position:relative; }
.card::after{
  content:""; position:absolute; left:0; right:0; top:0;
  height:3px;
  background:var(--accent);
  transform:scaleX(0); transform-origin:left;
  transition:transform .28s ease;
}
.card:hover{
  border-color:#3a4048;
  transform:translateY(-3px);
  box-shadow:var(--shadow-hi);
}
.card:hover::after{ transform:scaleX(1); }
.card-flagship::after,
.quote::after{ display:none; }

.card-media{
  margin:0 -24px 18px;
  line-height:0;
  background:var(--bg-2);
}
.card-media img{
  /* the photos are authored at this exact ratio (1300x500), so nothing of
     the panel name board or the PLC is ever cropped away at any card width */
  width:100%; height:auto; aspect-ratio:13/5;
  object-fit:cover; display:block;
  transition:transform .4s ease;
}
.card:hover .card-media img{ transform:scale(1.05); }

.label{
  display:block; margin-bottom:9px;
  color:var(--text-dim);
}
.card h3{ font-size:17.5px; font-weight:700; line-height:1.3; }

/* The course name leads the card, with the track label above it and
   the rating under it; the photo follows. */
.card-head{ margin-bottom:16px; }
.card-head .rating{ margin-top:8px; }

/* Individual course cards sit three to a row, so let them stretch and
   push "View curriculum" to a common baseline at the bottom. Scoped to
   .tracks-flat: .quote testimonials are also .card, and the combined
   programmes deliberately stay display:block. */
.tracks-flat .card{ display:flex; flex-direction:column; }

.meta{
  display:flex; flex-wrap:wrap; gap:8px;
  margin-top:12px;
}
.meta li{
  font-size:11.5px; font-weight:500;
  color:var(--text-muted);
  background:var(--bg-2);
  border:1px solid var(--line-soft);
  border-radius:99px;
  padding:4px 11px;
}

.card-link{
  display:inline-block;
  margin-top:auto; padding-top:16px;
  align-self:flex-start;
  font-size:13.5px; font-weight:600; color:var(--accent);
  transition:color .18s ease;
}
.card-link:hover{ color:var(--accent-hi); }
.card-link span{
  display:inline-block; margin-left:4px;
  transition:transform .2s ease;
}
.card-link:hover span{ transform:translateX(3px); }

/* flagship — the dark card */
.card-flagship{
  grid-row:1 / span 2;
  background:linear-gradient(180deg, rgba(245,166,35,.05), rgba(245,166,35,0) 55%), var(--panel);
  border-color:var(--accent);
  display:flex; flex-direction:column;
  padding:26px 28px 24px;
}
.card-flagship:hover{ border-color:var(--accent-hi); }
.card-flagship .card-media{ margin:-26px -28px 20px; background:var(--navy-2); }
.card-flagship .card-media img{ height:auto; aspect-ratio:4/3; }
.card-flagship h3{ font-size:23px; font-weight:800; color:var(--on-navy); }
.card-flagship .label{ color:var(--accent); }
.card-flagship .meta li{
  background:var(--navy-2);
  border-color:var(--navy-line);
  color:var(--on-navy-mute);
}
.card-flagship > p{
  margin-top:15px;
  font-size:13.5px; color:var(--on-navy-mute); line-height:1.75;
}
.card-flagship .card-link{ margin-top:auto; padding-top:26px; }

/* ---------- One course per screen ---------------------------
   Each course card is its own full-height slide that pins to the
   viewport, so the next course scrolls up and covers it: you read
   Siemens on its own, then Delta on its own, and so on. Same
   mechanism as the .panel stack, one level further in - these pin
   inside their .tracks group rather than inside <main>.

   Layout is a two-column spread: the course name runs across the
   top, the photo takes the left, the curriculum and its call to
   action sit on the right.

        +--------------------------------------------+
        |  01 - TRACK - SIEMENS                      |
        |  Siemens PLC Programming       ****. 4.8   |
        |  +--------------------+  +--------------+  |
        |  |                    |  | 15-20 Days   |  |
        |  |       photo        |  | BASICS ...   |  |
        |  |                    |  | ADVANCED ... |  |
        |  +--------------------+  | [ View ... ] |  |
        +--------------------------------------------+

   These rules come after the grid and .group-combined blocks above
   on purpose: they need to win over grid-template-columns and
   .group-combined .card{display:block}. */
.tracks,
.group-combined .tracks{
  display:block;
  gap:0;
  counter-reset:course;          /* drives the 01/02/03 kicker */
}

.tracks .card{
  counter-increment:course;
  position:relative;
  min-height:100vh;
  min-height:100svh;
  display:grid;
  grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);
  grid-template-rows:auto auto;
  align-content:center;
  justify-content:center;
  column-gap:clamp(32px, 4.4vw, 64px);
  row-gap:clamp(20px, 3vh, 34px);
  padding:100px 0 56px;   /* the wrap supplies the gutter */
  border:0;
  border-radius:0;
  overflow:visible;

  /* The slide lives inside section.wrap, so its own box is only ~1176px
     of a 1440px window; as a plain background that leaves a strip of bare
     page down each edge. The spread shadow paints the ground edge to edge
     and the clip-path stops it bleeding onto the next slide - the same
     trick .hero and .section-tint already use here. */
  background:var(--bg);
  box-shadow:0 0 0 100vmax var(--bg);   /* ground only - no lift shadow */
  clip-path:inset(0 -100vmax);
}

/* A wash of brand amber from the top-left corner keeps a full screen
   of dark from reading as flat. */
/* Stays inside the card box: stretching this to -100vmax to match the
   full-bleed ground made it a real 2880px-wide layout box and gave the
   whole page a horizontal scrollbar. Instead the wash is centred and
   fades out well before the edges, so no seam shows where the card box
   ends and the spread shadow takes over. */
.tracks .card::before{
  content:"";
  position:absolute; inset:0;
  pointer-events:none;
  background:
    radial-gradient(58% 48% at 50% 0%, rgba(245,166,35,.09), transparent 70%),
    radial-gradient(46% 44% at 84% 100%, rgba(245,166,35,.045), transparent 66%);
}
.tracks .card > *{ position:relative; }   /* sit above that wash */

/* ---- the heading runs across both columns ---- */
.tracks .card-multi .card-head{
  grid-row:2;
  max-width:none;
  padding-bottom:0;
  border-bottom:0;
  border-top:1px solid var(--line);
  padding-top:clamp(12px, 1.8vh, 20px);
}

.tracks .card .card-head{
  grid-column:1 / -1;
  display:grid;
  grid-template-columns:1fr auto;
  align-items:end;
  column-gap:24px;
  max-width:none; width:100%;
  margin:0 auto;
  padding-bottom:clamp(14px, 2vh, 24px);
  border-bottom:1px solid var(--line);
}

/* "01 - TRACK - SIEMENS": the number is generated, so adding or
   reordering a course renumbers the set on its own. */
.tracks .card .label{
  display:flex; align-items:center; gap:12px;
  margin-bottom:12px;
  color:var(--text-dim);
}
.tracks .card .label::before{
  content:counter(course, decimal-leading-zero);
  font-size:12px; font-weight:700; letter-spacing:.08em;
  color:var(--accent);
}
.tracks .card .label::after{
  content:"";
  width:26px; height:1px;
  background:var(--line);
}

.tracks .card h3{
  grid-column:1;
  font-size:clamp(26px, 3.4vw, 44px);
  font-weight:800; line-height:1.08;
  letter-spacing:-.015em;
}
.tracks .card .rating{
  grid-column:2; grid-row:1 / span 2;
  align-self:end;
  margin:0 0 4px;
  white-space:nowrap;
}

/* ---- left column: the photo ---- */
.tracks .card .card-media{
  grid-column:1; grid-row:2;
  margin:0;
  border:1px solid var(--line);
  border-radius:10px;
  overflow:hidden;
  box-shadow:var(--shadow-hi);
  align-self:start;
  justify-self:end;
  max-width:none; width:100%;
}
/* A fixed vh height cropped the wide panel shots to about half their
   width. 3/2 is the ratio the combined-programme photos are authored
   at, so those show uncropped, and it trims the 13/5 panel shots far
   less. Sizing by ratio rather than vh also means the slide height
   follows the column width, so it cannot outgrow the screen on its
   own. */
.tracks .card .card-media img{
  aspect-ratio:auto;
  height:clamp(300px, 52vh, 540px);
  width:100%;
  object-fit:cover;
}

/* ---- right column: curriculum + call to action ---- */
.tracks .card .card-body{
  grid-column:2; grid-row:2;
  align-self:start;
  display:flex; flex-direction:column;
  max-width:none; width:100%;
}
.tracks .card .meta{ margin:0 0 20px; }
.tracks .card .meta li{ font-size:12px; padding:5px 13px; }

/* Each curriculum stage becomes its own block with an amber rule down
   the side, so Basics and Advanced read as two steps rather than two
   paragraphs that happen to start with a bold word. */
.tracks .card :is(.card-body, .card-main) > .card-proj{
  margin:0 0 16px;
  padding:16px 20px;
  border:1px solid var(--line);
  border-left:3px solid var(--accent);
  border-radius:0 var(--r) var(--r) 0;
  background:var(--panel);
  font-size:14.5px; line-height:1.75;
}
.tracks .card :is(.card-body, .card-main) > .card-proj b{
  display:block;
  margin-bottom:6px;
  font-size:10.5px; font-weight:700;
  letter-spacing:.14em; text-transform:uppercase;
}
.tracks .card :is(.card-body, .card-main) > .card-proj.proj-advanced{ border-left-color:#6f9fca; }
/* a course with a single block (DCS, ABB, Omron, Schneider) shows it as
   plain text - no label, no amber rule, no box. It stays a .card-proj so
   the phone text sizes still apply; the doubled class outranks the phone
   padding rules. */
.tracks .card :is(.card-body, .card-main) > .card-proj.proj-plain.proj-basic{ padding:0; border:0; border-radius:0; background:none; }
/* Siemens DCS Training: its three stages as a numbered flow down an amber
   wire - 01, 02, 03 in rings, the stage name, then what is done there.
   Sized in em, so it follows the course text size on every screen. */
.tracks .card :is(.card-body, .card-main) > .card-proj.proj-steps{ list-style:none; counter-reset:step; }
.card-proj.proj-steps > li{
  position:relative; counter-increment:step;
  min-height:2em; padding:0 0 1.8em 2.8em;
}
.card-proj.proj-steps > li:last-child{ padding-bottom:0; }
.card-proj.proj-steps > li::before{
  content:counter(step, decimal-leading-zero);
  position:absolute; left:0; top:0;
  width:2.8em; height:2.8em; box-sizing:border-box;
  display:grid; place-items:center;
  border:1.5px solid var(--accent); border-radius:50%;
  background:var(--bg);
  font-size:.7em; font-weight:700; letter-spacing:.04em; line-height:1;
  color:var(--accent);
  box-shadow:0 0 0 3px rgba(245,166,35,.08);
}
.card-proj.proj-steps > li:not(:last-child)::after{
  content:""; position:absolute; left:calc(.98em - .75px); top:2.05em; bottom:.1em; width:1.5px;
  background:linear-gradient(var(--accent), rgba(245,166,35,.22));
}
.card-proj.proj-steps strong{ display:block; padding-top:.3em; color:var(--text); font-size:1.08em; font-weight:600; line-height:1.3; }
.card-proj.proj-steps span{ display:block; margin-top:.12em; color:var(--text-muted); font-size:.9em; line-height:1.4; }
.tracks .card :is(.card-body, .card-main) > .card-desc{ margin:0 0 14px; font-size:13.5px; line-height:1.7; }
.tracks .card .card-desc:empty{ display:none; }

/* The link becomes a real button - it is the one action on the screen. */
.tracks .card .card-link{
  align-self:flex-start;
  margin-top:auto; padding:13px 26px;
  border-radius:99px;
  background:var(--accent);
  color:var(--navy);
  font-size:14px; font-weight:700;
  box-shadow:0 8px 24px rgba(245,166,35,.22);
  transition:background .18s ease, transform .18s ease, box-shadow .18s ease;
}
.tracks .card .card-link:hover{
  background:var(--accent-hi);
  color:var(--navy);
  transform:translateY(-2px);
  box-shadow:0 12px 30px rgba(245,166,35,.32);
}

/* A full-screen slide should not lift on hover the way a grid tile did,
   and the photo should not creep either. */
.tracks .card:hover{ transform:none; box-shadow:0 0 0 100vmax var(--bg); }
.tracks .card::after{ display:none; }
.tracks .card:hover .card-media img{ transform:none; }

/* Twelve platform rows stacked single file ran the diploma slides past
   2200px. Two columns of them roughly halves that, and the list is a
   reference table rather than prose, so it reads fine side by side. */
.tracks .card .plc-details{
  grid-template-columns:1fr 1fr;
  gap:8px;
  margin-top:14px;
}
.tracks .card .plc-details .card-proj{ margin:0; }

/* ---- multi-brand programmes: one row of platform stills --------
   These cover eight platforms, so a single composite hero photo said
   little. They carry a still from each platform's own course instead,
   in one row under the course name - the same place the single-brand
   courses put their photo. With no photo column to sit beside, the
   whole slide goes single-column and the details spread wide. */
.tracks .card-multi{
  grid-template-columns:minmax(0,1fr);
  justify-items:center;
}
.tracks .card-multi .card-body{
  grid-column:1; grid-row:3;
  max-width:none;

  /* Two columns: the copy on the left, what it covers and the call to
     action on the right. In one column everything bunched into the left
     half and left the right side of the slide empty. */
  display:grid;
  grid-template-columns:minmax(0,1.2fr) minmax(0,1fr);
  column-gap:clamp(32px, 4vw, 60px);
  align-content:start;
}
/* Two cells, one per column. Letting the individual items place
   themselves made the aside span three auto rows, and grid then shared
   its height out across them - which stretched the duration pills into
   tall ovals and opened a 155px gap above the description. */
.tracks .card-multi .card-main{ grid-column:1; }
.tracks .card-multi .card-body > *{ align-self:start; }
/* One cell for the whole right-hand column. Placing the three items
   individually pinned each to a row sized by the left column, which
   opened a gap between the caption and the pills. */
.tracks .card-multi .card-aside{
  grid-column:2;
  align-self:start;
  display:flex; flex-direction:column; align-items:flex-start;
}
.tracks .card-multi .card-aside .covers-cap{ margin-top:0; }
.tracks .card-multi .card-aside .card-link{ margin-top:26px; }
.tracks .card-multi .plc-details{
  grid-template-columns:repeat(4, minmax(0,1fr));
}

/* The lab shot leads the slide, arched along the bottom to echo the
   curve of the console in the photograph, with the course name and the
   rest of the copy sitting underneath it.

   The curve is an elliptical border-radius rather than a clip-path so
   the corners round off without the middle of the picture being cut. */
.tracks .card-multi .card-media{
  grid-column:1; grid-row:1;
  margin:0 0 8px;
  justify-self:stretch;
  width:100%;
  max-width:none;
  overflow:hidden;
  border:0;
  border-radius:14px 14px 50% 50% / 14px 14px 42% 42%;
  box-shadow:0 24px 60px rgba(0,0,0,.5);
}
.tracks .card-multi .card-media img{
  display:block;
  aspect-ratio:auto;
  width:100%;
  height:clamp(190px, 30vh, 340px);
  object-fit:cover;
  object-position:center 42%;
}

/* The multi-brand slides drop the panel treatment the single-brand ones
   carry - no top rule, no lift shadow, no amber wash - so they read as
   plain page rather than as a card.

   The flat background stays: these are sticky slides, and an incoming
   slide has to be opaque to hide the one pinned behind it. Drop the
   background and you see straight through to the previous course. */
.tracks .card-multi,
.tracks .card-multi:hover{
  border-top:0;
  box-shadow:0 0 0 100vmax var(--bg);   /* ground only, no lift shadow */
}
.tracks .card-multi::before{ display:none; }

/* The flagship keeps its warmer ground, but as a full slide. */
.tracks .card-flagship{
  background:linear-gradient(180deg, rgba(245,166,35,.06), rgba(245,166,35,0) 45%), var(--bg);
  border-top-color:var(--accent);
}
.tracks .card-flagship.card-multi{
  background:var(--bg);
  border-top:0;
}



/* ---------- Feature band ----------------------------------- */
/* ---------- Feature blocks --------------------------------- */
.section-tint{
  position:relative;
  background:none;
  border-block:0;
  margin-block:16px;
}

.features{
  display:grid;
  grid-template-columns:repeat(3,1fr);
  gap:30px 20px;
}
.feature{ border-top:2px solid var(--accent); padding-top:16px; }
.feature h4{ font-size:14.5px; font-weight:600; }
.feature p{
  margin-top:10px;
  font-size:12.5px; color:var(--text-muted); line-height:1.7;
}

@media (max-width:900px){ .features{ grid-template-columns:1fr 1fr; gap:26px 20px; } }
@media (max-width:560px){ .features{ grid-template-columns:1fr; gap:24px; } }

/* ---------- Proof band ------------------------------------- */
.proof{
  display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
  gap:28px;
  margin-top:56px;
  background:var(--accent);
  border:1px solid var(--accent);
  border-radius:var(--r);
  padding:30px 34px;
}
.proof-stats{ display:flex; flex-wrap:wrap; gap:56px; }
.proof-stats b{
  display:block;
  font-size:33px; font-weight:800; letter-spacing:-.03em; line-height:1.1;
  font-variant-numeric:tabular-nums;
  color:#17191c;
}
.proof-stats span{
  display:block; margin-top:4px;
  font-size:11px; font-weight:700;
  letter-spacing:.09em; text-transform:uppercase;
  color:rgba(23,25,28,.78);
}

/* ---------- Graduates slider ------------------------------- */
.slider{ position:relative; }

.testimonials{
  display:flex;
  align-items:stretch;
  gap:20px;
  overflow-x:auto;
  scroll-snap-type:x mandatory;
  scroll-padding-left:2px;
  padding:4px 2px 12px;
  -webkit-overflow-scrolling:touch;
  scrollbar-width:none;
}
.testimonials::-webkit-scrollbar{ display:none; }

.testimonials .quote{
  flex:0 0 268px;
  scroll-snap-align:start;
}

.quote{
  margin:0;
  display:flex; flex-direction:column;
  padding:0; overflow:hidden;
  background:var(--panel);
  border:1px solid var(--line);
  border-top:3px solid var(--brand);
  border-radius:var(--r);
  box-shadow:var(--shadow);
  transition:border-top-color .2s ease, transform .2s ease, box-shadow .2s ease;
}
.quote:hover{
  border-top-color:var(--accent-hi);
  transform:translateY(-3px);
  box-shadow:var(--shadow-hi);
}

.q-head{
  display:grid;
  grid-template-columns:auto minmax(0,1fr);
  gap:13px;
  align-items:center;
  padding:18px 20px 16px;
}
.quote-avatar{
  width:52px; height:52px; flex:none;
  border-radius:50%;
  object-fit:cover; object-position:center top;
  border:2px solid var(--bg);
  box-shadow:0 0 0 2px var(--line);
  transition:box-shadow .2s ease;
}
.quote:hover .quote-avatar{ box-shadow:0 0 0 2px var(--brand); }

.q-head figcaption{ min-width:0; }
.q-head b{
  display:block;
  font-size:11.5px; font-weight:800;
  letter-spacing:.06em; text-transform:uppercase;
  color:var(--text);
}
.quote .course{
  display:-webkit-box; margin-top:4px;
  -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
  min-height:33px;
  font-size:11.5px; line-height:1.45; color:var(--text-dim);
}

.q-badge{
  float:right;
  margin-left:10px;
  padding:4px 10px;
  border-radius:99px;
  background:rgba(245,166,35,.16);
  color:var(--accent);
  font-size:9.5px; font-weight:800;
  letter-spacing:.11em; text-transform:uppercase;
}

/* the outcome — the reason this section exists */
.q-role{
  margin-top:auto;
  overflow:hidden;
  padding:16px 20px 20px;
  border-top:1px solid var(--line-soft);
  background:var(--bg-2);
}
.quote .role{
  display:block;
  font-size:16px; font-weight:800; line-height:1.3;
  letter-spacing:-.01em;
  color:var(--text);
}
.quote .city{
  display:inline-flex; align-items:center; gap:5px;
  margin-top:6px;
  font-size:12.5px; font-weight:600; color:var(--text-muted);
}
.quote .city svg{ color:var(--brand); flex:none; }

.slider-btn{
  position:absolute; top:50%;
  transform:translateY(-50%);
  z-index:2;
  width:38px; height:38px;
  display:grid; place-items:center;
  border:1px solid var(--line);
  border-radius:50%;
  background:var(--panel);
  color:var(--text-muted);
  cursor:pointer;
  box-shadow:var(--shadow);
  transition:opacity .18s ease, color .18s ease, border-color .18s ease;
}
.slider-btn:hover{ color:var(--text); border-color:#3a4048; }
.slider-btn:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
.slider-prev{ left:-18px; }
.slider-next{ right:-18px; }
@media (max-width:860px){
  .slider-btn{ width:44px; height:44px; }
}
.slider-btn[disabled]{ opacity:0; pointer-events:none; }

/* ---------- Lead form -------------------------------------- */
.cta{
  display:grid;
  grid-template-columns:1fr 420px;
  gap:52px;
  margin-block:56px 40px;
  padding:44px 46px;
  border:1px solid var(--accent);
  border-radius:var(--r);
  background:linear-gradient(180deg, rgba(245,166,35,.04), rgba(245,166,35,0) 60%), var(--bg-2);
  color:var(--on-navy);
}
.cta-copy h2{ font-size:clamp(25px, 2.7vw, 31px); font-weight:800; color:var(--on-navy); }
.cta-copy > p{
  margin-top:15px; max-width:44ch;
  color:var(--on-navy-mute); font-size:14.5px; line-height:1.75;
}
.batch-badge{
  display:inline-flex; align-items:center; gap:9px; flex-wrap:wrap;
  margin-top:28px;
  padding:11px 18px;
  border:1px solid var(--navy-line);
  border-radius:var(--r-sm);
  background:var(--navy-2);
  color:var(--on-navy);
  font-size:10.5px;
}
.batch-sep{ color:var(--on-navy-dim); margin-inline:2px; }

.cta-form{
  background:var(--panel);
  border:1px solid var(--line-soft);
  border-radius:var(--r);
  padding:26px 26px 28px;
}
.cta-form label{ display:block; margin-bottom:8px; color:var(--text-muted); }
.cta-form label + input{ margin-bottom:18px; }
.cta-form input{
  width:100%;
  padding:12px 14px;
  background:#15171a;
  border:1px solid var(--line);
  border-radius:var(--r-sm);
  color:var(--text);
  font-family:var(--sans); font-size:14px;
  transition:border-color .18s ease, background .18s ease;
}
.cta-form input:focus{ outline:none; border-color:var(--accent); background:#15171a; }
.cta-form input.invalid{ border-color:#e05252; }
.cta-form .btn{ margin-top:6px; }
.form-note{
  min-height:16px; margin-top:12px;
  font-size:12.5px; color:var(--green); text-align:center;
}
.form-note.error{ color:#ff6b6b; }

/* ---------- Footer ----------------------------------------- */
.site-footer{
  margin-top:20px;
  background:var(--bg);
  /* No hairline: the footer slides up over the last panel like any other,
     so a top border here reads as a line travelling up the screen. */
  border-top:0;
  padding-block:28px 34px;
}
.footer-inner{
  display:flex; flex-wrap:wrap; justify-content:space-between; gap:14px;
  font-size:12.5px; color:var(--on-navy-dim);
}
.footer-links{ display:flex; flex-wrap:wrap; gap:22px; }
.footer-links a{ color:var(--on-navy-mute); }
.footer-links a:hover{ color:var(--on-navy); }

/* ---------- Sticky WhatsApp -------------------------------- */
.whatsapp{
  position:fixed; right:24px; bottom:24px; z-index:50;
  width:52px; height:52px;
  display:grid; place-items:center;
  border-radius:50%;
  background:#25d366; color:#0d1b12;
  box-shadow:0 8px 26px rgba(37,211,102,.4);
  transition:transform .18s ease;
}
.whatsapp:hover{ transform:translateY(-2px) scale(1.05); }

/* ---------- Reveal on scroll ------------------------------- */
.reveal{ opacity:0; transform:translateY(14px); }
.reveal.in{ opacity:1; transform:none; transition:opacity .5s ease, transform .5s ease; }

/* Course cards travel further and settle on a soft overshoot curve, so
   each one reads as arriving rather than just fading up. */
.tracks .card.reveal{ opacity:0; transform:translateY(34px) scale(.97); }
.tracks .card.reveal.in{
  opacity:1; transform:none;
  transition:opacity .5s ease, transform .62s cubic-bezier(.22,.68,.32,1.02);
}

/* ---------- Responsive ------------------------------------- */
@media (max-width:1080px){
  .hero{ grid-template-columns:1fr; gap:40px; padding-top:54px; }
  .tracks{ grid-template-columns:1fr 1fr; }
  .card-flagship{ grid-row:auto; grid-column:1 / -1; }
  .cta{ grid-template-columns:1fr; gap:34px; padding:34px 30px; }
  .proof-stats{ gap:36px; }
}

@media (max-width:860px){
  .nav{
    display:none;
    position:absolute; left:0; right:0; top:100%;
    flex-direction:column; gap:0;
    background:var(--bg-2);
    border-bottom:1px solid var(--line);
    padding:8px 34px 18px;
  }
  .nav.open{ display:flex; }
  .nav a{ padding:12px 0; border-bottom:1px solid var(--navy-line); }
  .header-inner{ position:relative; gap:14px; }
  .nav-toggle{ display:block; order:3; width:44px; height:44px; padding:14px 12px; flex:none; }
  /* "Talk to Us" was breaking after "to" once the logo and the toggle
     had taken their share, which grew the sticky header to 102px - and
     every pinned screen below it is budgeted against that height. */
  .header-cta{
    margin-left:auto;
    flex:none;
    white-space:nowrap;
    padding:10px 16px;
  }
}

@media (max-width:560px){
  .wrap{ padding-inline:20px; }
  .section{ padding-block:46px 20px; }
  .section-head{ flex-direction:column; align-items:flex-start; gap:12px; }
  .section-head p{ text-align:left; }
  .section-head p br{ display:none; }
  .tracks{ grid-template-columns:1fr; }
  .hero-actions .btn{ width:100%; }
  .proof{ padding:24px; }
  .proof-stats{ gap:24px 40px; }
  .footer-inner{ flex-direction:column; }
  .footer-links{ gap:16px; }
  .logo-mark{ width:32px; height:32px; }
  .logo-text{ font-size:12.5px; }
  .brand-row{ gap:10px 20px; padding:16px 18px; }
  .brand-row li{ font-size:11.5px; letter-spacing:.09em; }
  .slider-prev{ left:-8px; }
  .slider-next{ right:-8px; }
}

@media (prefers-reduced-motion:reduce){
  html{ scroll-behavior:auto; }
  *,*::before,*::after{ transition:none !important; animation:none !important; }
  .reveal{ opacity:1; transform:none; }
}

/* course card description, sourced from each course page on the live site */
.card-desc{
  margin-top:12px;
  font-size:12.5px; line-height:1.65;
  color:var(--text-muted);
}
.card-flagship .card-desc{ color:var(--on-navy-mute); font-size:13px; }

/* course rating, from the figures published on training-list.html */
.rating{
  display:flex; align-items:center; gap:7px;
  margin-bottom:9px;
  font-size:12px; font-weight:700; color:var(--text-muted);
}
.stars{ display:inline-flex; gap:1px; font-size:13px; line-height:1; color:var(--line); }
.stars .on{ color:var(--accent); }
.stars .half{
  background:linear-gradient(90deg, var(--accent) 50%, var(--line) 50%);
  -webkit-background-clip:text; background-clip:text; color:transparent;
}
.card-flagship .rating{ color:var(--on-navy-mute); }
.card-flagship .stars{ color:var(--navy-line); }



/* module / project detail, from plctraining.inferconautomation.com */
.card-proj{
  margin-top:11px; padding-top:11px;
  border-top:1px dashed var(--line);
  font-size:12px; line-height:1.6;
  color:var(--text-muted);
}
.card-proj b{
  display:block; margin-bottom:3px;
  font-size:10px; font-weight:700;
  letter-spacing:.1em; text-transform:uppercase;
  color:var(--accent);
}
.plc-details{
  display:grid;
  gap:8px;
  margin-top:17px;
  padding:12px;
  border:1px solid var(--line-soft);
  border-radius:var(--r-sm);
  background:rgba(0,0,0,.12);
}
.plc-details .card-proj{
  margin:0;
  padding:10px 11px;
  border:1px solid var(--line);
  border-left:3px solid var(--line);
  border-radius:var(--r-sm);
  background:var(--bg-2);
  font-size:11.5px;
  line-height:1.55;
}
.plc-details .proj-basic{ border-left-color:#d89b36; }
.plc-details .proj-advanced{ border-left-color:#6f9fca; }
.plc-details .card-proj b{
  display:inline-block;
  margin:0 8px 0 0;
  padding:2px 6px;
  border-radius:2px;
  background:rgba(245,166,35,.13);
  font-size:9px;
  line-height:1.3;
  vertical-align:middle;
}
.plc-details .proj-advanced b{
  background:rgba(95,176,247,.13);
  color:#8cc6fa;
}
.plc-name{
  display:inline-block;
  margin:0 8px 0 0;
  padding-right:8px;
  border-right:1px solid var(--line);
  color:var(--accent);
  font-weight:700;
}
.plc-details .proj-advanced .plc-name{ color:#8cc6fa; }
.card-flagship .card-proj{
  border-top-color:var(--navy-line);
  color:var(--on-navy-mute);
}

/* ---------- FAQ (layout after landing-page-2) --------------- */
.faq-wrap{
  display:grid;
  grid-template-columns:minmax(0,5fr) minmax(0,7fr);
  gap:56px;
  align-items:start;
}

.faq-eyebrow{
  display:inline-flex; align-items:center; gap:10px;
  margin-bottom:14px;
  color:var(--accent);
}
.faq-eyebrow::before,
.faq-eyebrow::after{
  content:""; display:block;
  width:24px; height:2px; background:var(--accent);
}
.faq-intro h2{
  font-size:clamp(26px, 3vw, 38px); font-weight:800;
  line-height:1.1; color:var(--text);
}
.faq-sub{
  margin-top:12px; margin-bottom:30px; max-width:44ch;
  font-size:14px; line-height:1.7; color:var(--text-muted);
}












/* contact block replaces the image collage */
.faq-contact{
  margin-top:30px;
  border:1px solid var(--line);
  border-radius:var(--r);
  overflow:hidden;
  background:var(--bg-2);
}
.fc-row{
  display:flex; align-items:center; gap:14px;
  padding:16px 18px;
  border-bottom:1px solid var(--line);
  transition:background .16s ease;
}
.fc-row:last-child{ border-bottom:0; }
a.fc-row:hover{ background:#262a30; }
.fc-ico{
  flex:none;
  width:38px; height:38px;
  display:grid; place-items:center;
  border-radius:50%;
  background:rgba(245,166,35,.16);
  color:var(--accent);
}
.fc-row .label{ display:block; color:var(--text-dim); margin-bottom:3px; }
.fc-row b{
  display:block;
  font-size:14.5px; font-weight:700; color:var(--text);
  word-break:break-word;
}
a.fc-row:hover b{ color:var(--accent); }
.fc-static{ cursor:default; }

/* right column */
.faq-top-bar{
  display:flex; align-items:center; justify-content:space-between;
  flex-wrap:wrap; gap:12px;
  margin-bottom:20px;
}
.faq-top-bar-label{ font-size:13.5px; color:var(--text-muted); }
.faq-top-bar-label strong{ color:var(--text); font-weight:700; }
.faq-contact-btn{
  display:inline-flex; align-items:center; gap:8px;
  padding:11px 20px; border-radius:99px;
  background:var(--accent); color:#17191c;
  font-size:13.5px; font-weight:700;
  box-shadow:0 4px 16px rgba(245,166,35,.26);
  transition:transform .18s ease, box-shadow .18s ease, background .18s ease;
}
.faq-contact-btn:hover{
  background:var(--accent-hi);
  transform:translateY(-2px);
  box-shadow:0 8px 24px rgba(245,166,35,.34);
}

.faq{ display:flex; flex-direction:column; gap:10px; }
.faq-item{
  border:1px solid var(--line);
  border-radius:var(--r);
  background:var(--panel);
  overflow:hidden;
  transition:border-color .18s ease, box-shadow .18s ease;
}
.faq-item:hover{ border-color:#3a4048; }
.faq-item[open]{ border-color:var(--brand); box-shadow:var(--shadow); }
.faq-item summary{
  display:flex; align-items:center; gap:16px;
  padding:18px 22px;
  font-size:15px; font-weight:700; color:var(--text);
  cursor:pointer; list-style:none;
}
.faq-item summary::-webkit-details-marker{ display:none; }
.faq-item summary::after{
  content:"+";
  margin-left:auto; flex:none;
  width:27px; height:27px;
  display:grid; place-items:center;
  border-radius:50%;
  background:var(--bg-2); color:var(--accent);
  font-size:17px; font-weight:400; line-height:1;
  transition:transform .25s ease, background .2s ease, color .2s ease;
}
.faq-item[open] summary::after{
  transform:rotate(135deg); background:var(--accent); color:#17191c;
}
.faq-item p{
  padding:0 22px 20px;
  font-size:13.5px; line-height:1.8; color:var(--text-muted);
}

@media (max-width:980px){
  .faq-wrap{ grid-template-columns:1fr; gap:34px; }
}
@media (max-width:560px){
  
  
  
  
  
  
}

/* phone callout under the questions */
.faq-call{
  display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
  gap:18px;
  margin-top:22px;
  padding:20px 24px;
  border:1px solid var(--line);
  border-radius:var(--r);
  background:var(--bg-2);
}
.faq-call .label{ display:block; color:var(--text-dim); margin-bottom:5px; }
.faq-phone{
  font-size:21px; font-weight:800; letter-spacing:-.02em;
  color:var(--text);
  font-variant-numeric:tabular-nums;
  transition:color .18s ease;
}
.faq-phone:hover{ color:var(--accent); }

@media (max-width:980px){
  .faq-wrap{ grid-template-columns:1fr; gap:30px; }
  .faq-media{ position:static; flex-direction:row; }
  .faq-media figure{ flex:1; }
  .faq-media img{ height:180px; }
}
@media (max-width:560px){
  .faq-media{ flex-direction:column; }
  .faq-call{ flex-direction:column; align-items:flex-start; }
}

/* ---------- About ------------------------------------------ */
.about-top{
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,460px);
  gap:56px;
  align-items:center;
  padding-bottom:38px;
  margin-bottom:38px;
  border-bottom:1px solid var(--line);
}
.about-lead p{
  font-size:16px; line-height:1.85; color:var(--text-muted);
}
.about-lead p + p{ margin-top:16px; }

.about-media{
  margin:0; line-height:0;
  border-radius:var(--r);
  overflow:hidden;
  box-shadow:var(--shadow-hi);
}
.about-media img{ width:100%; height:300px; object-fit:cover; }





.about-grid{
  display:grid;
  grid-template-columns:repeat(3,1fr);
  gap:0;
}
.about-item{
  padding:0 34px;
  border-right:1px solid var(--line);
}
.about-item:first-child{ padding-left:0; }
.about-item:last-child{ border-right:0; padding-right:0; }
.about-item .label{
  display:block; margin-bottom:12px;
  color:var(--accent);
}
.about-item p{
  font-size:13.5px; line-height:1.85; color:var(--text-muted);
}

@media (max-width:900px){
  .about-top{ grid-template-columns:1fr; gap:30px; }
  .about-media{ order:-1; }
  .about-media img{ height:220px; }
  .about-grid{ grid-template-columns:1fr; gap:26px; }
  .about-item{ padding:0 0 22px; border-right:0; border-bottom:1px solid var(--line); }
  .about-item:last-child{ border-bottom:0; padding-bottom:0; }
}

/* slider arrows need to read on the dark ground */
.section-navy .slider-btn{
  background:var(--navy-2);
  border-color:var(--navy-line);
  color:var(--on-navy-mute);
  box-shadow:0 6px 18px rgba(4,10,20,.4);
}
.section-navy .slider-btn:hover{ color:var(--on-navy); border-color:#3a4048; }

/* white cards keep their own surface; only the ring tone changes */
.section-navy .quote-avatar{ border-color:var(--panel); }

/* what the engineering side actually does — from service.html */
.about-tags{
  display:flex; flex-wrap:wrap; gap:9px;
  margin-top:26px;
  padding-top:24px;
  border-top:1px solid var(--line);
}
.about-tags li{
  padding:7px 14px;
  border:1px solid var(--line);
  border-radius:99px;
  background:var(--bg-2);
  font-size:12.5px; font-weight:600;
  color:var(--text-muted);
  transition:border-color .18s ease, color .18s ease;
}
.about-tags li:hover{ border-color:var(--accent); color:var(--text); }

/* ---------- Curriculum gate modal --------------------------- */
.modal[hidden]{ display:none; }
.modal{
  position:fixed; inset:0; z-index:120;
  display:grid; place-items:center;
  padding:24px;
}
.modal-backdrop{
  position:absolute; inset:0;
  background:rgba(9,10,12,.80);
  backdrop-filter:blur(3px);
}
.modal-card{
  position:relative;
  width:100%; max-width:432px;
  max-height:calc(100vh - 48px);
  overflow-y:auto;
  padding:32px 32px 28px;
  border:1px solid var(--accent);
  border-radius:var(--r);
  background:linear-gradient(180deg, rgba(245,166,35,.06), rgba(245,166,35,0) 55%), var(--panel);
  box-shadow:var(--shadow-hi);
  animation:modal-in .22s ease both;
}
@keyframes modal-in{ from{ opacity:0; transform:translateY(12px); } }

.modal-x{
  position:absolute; top:12px; right:12px;
  width:32px; height:32px;
  display:grid; place-items:center;
  border:1px solid var(--line);
  border-radius:var(--r-sm);
  background:transparent; color:var(--text-muted);
  font-size:19px; line-height:1; cursor:pointer;
  transition:color .18s ease, border-color .18s ease;
}
.modal-x:hover{ color:var(--text); border-color:#3a4048; }

#cmCourse{ display:block; margin-bottom:10px; padding-right:34px; }
.modal-card h2{ font-size:22px; font-weight:800; line-height:1.2; }
.modal-sub{
  margin-top:11px;
  font-size:13px; line-height:1.7; color:var(--text-muted);
}

#cmForm{ margin-top:24px; }
#cmForm label{ display:block; margin-bottom:8px; color:var(--text-muted); }
#cmForm label .opt{ letter-spacing:.04em; color:var(--text-dim); }
#cmForm label + input{ margin-bottom:16px; }
#cmForm input{
  width:100%;
  padding:12px 14px;
  background:#15171a;
  border:1px solid var(--line);
  border-radius:var(--r-sm);
  color:var(--text);
  font-family:var(--sans); font-size:14px;
  transition:border-color .18s ease;
}
#cmForm input:focus{ outline:none; border-color:var(--accent); }
#cmForm input.invalid{ border-color:#e05252; }
#cmForm .btn{ margin-top:6px; }
#cmSubmit[disabled]{ opacity:.6; cursor:default; transform:none; }

.modal-done{ margin-top:24px; }
.modal-done-msg{
  margin-bottom:16px; padding:14px 16px;
  border:1px solid rgba(62,207,142,.35);
  border-radius:var(--r-sm);
  background:rgba(62,207,142,.09);
  font-size:13px; line-height:1.65; color:var(--text);
}
.modal-micro{
  margin-top:12px;
  font-size:11.5px; color:var(--text-dim); text-align:center;
}

body.modal-open{ overflow:hidden; }

@media (max-width:560px){
  .modal{ padding:16px; }
  .modal-card{ padding:26px 22px 24px; }
}

/* ---------- Scroll-linked motion ----------------------------
   Anime.js drives this with onScroll({ sync: true }): the animation's
   progress is tied to scroll position rather than fired once when the
   element appears, so scrubbing back up rewinds it. The browser does
   the same thing natively with a view() timeline, which keeps it off
   the main thread and needs no library.

   Only the entry is animated - content settles and then stays put.
   Fading it back out on the way past would hide copy that is still on
   screen and readable.

   The IntersectionObserver reveal in script.js is skipped when this is
   supported, so nothing animates twice. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {

    /* Ranges are given as scroll distances, not percentages. A percentage
       is measured against the animated element's own height, so a 130px
       heading finished inside ~50px of scroll while a tall block took
       hundreds - the same rule read as a snap on one element and a drift
       on another. Pixels give every element the same travel. */
    .scroll-fx{
      animation:scroll-rise cubic-bezier(.37,0,.24,1) both;
      animation-timeline:view();
      animation-range:entry 0 entry 460px;
    }

    /* A course slide arrives in three beats rather than all at once:
       name, then photo, then curriculum. Native scroll timelines have no
       stagger, so the offset is built into each element's range. */
    .tracks .card .card-head{ animation-range:entry 0 entry 400px; }
    .tracks .card .card-media,
    .tracks .card .card-arc{ animation-range:entry 100px entry 520px; }
    .tracks .card .card-body{ animation-range:entry 190px entry 620px; }

    /* Feature blocks ripple across the row the same way. */
    .features .feature:nth-child(3n+2){ animation-range:entry 70px entry 530px; }
    .features .feature:nth-child(3n+3){ animation-range:entry 140px entry 600px; }
  }
}

@keyframes scroll-rise{
  from{ opacity:0; transform:translateY(42px); }
  to  { opacity:1; transform:none; }
}

/* ---------- Course slides: stacked fallback -----------------
   Below the breakpoint the full-screen slides drop back to a plain
   stacked list, matching what .panel does at the same width.

   This block sits at the end of the sheet deliberately: the 1080px
   and 560px blocks above set .tracks{grid-template-columns:1fr 1fr},
   and at equal specificity the later rule wins. Anywhere earlier and
   a phone gets two cramped columns. */
/* Two ways to end up here. Reduced motion is the asked-for one. The
   other is a viewport too short to hold a slide: a phone turned
   sideways is about 390px tall, and a course needs roughly 620px for
   its name, photo, curriculum and button once the header and the
   terminal rail have taken their share. Measured, not guessed - below
   620px the stage clips every one of the thirteen, and at 620 it clips
   none. There is nothing to scale away at that point, so the stage is
   let go and the courses become a list. */
/* Keyed on height at any width: a big phone on its side (926x428) is
   wider than 860px, and the pinned stage cannot fit its 428px of height -
   so it gets the list too, as does a very short desktop window. */
@media (prefers-reduced-motion:reduce),
       (max-height:619px){
  /* The track collapses and the cards flow. */
  .tracks,
  .group-combined .tracks{ height:auto; view-timeline-name:none; }
  .tracks .stage{ position:static; height:auto; overflow:visible; }
  .tracks .stage > .card{
    position:relative;
    inset:auto;
    opacity:1;
    animation:none;
  }
  .tracks .card .card-media img{ height:auto; }

  .tracks .card{
    position:static;
    min-height:0;
    display:block;
    padding:22px 24px;
    border:1px solid var(--line);
    border-radius:var(--r);
    box-shadow:var(--shadow);
  }
  .tracks .card::before{ display:none; }
  .tracks .card .card-head{
    display:block;
    max-width:none;
    padding-bottom:0;
    border-bottom:0;
    margin-bottom:16px;
  }
  .tracks .card h3{ font-size:17.5px; }
  .tracks .card .rating{ margin-top:8px; }
  /* width:100% here would hold the photo at the padding box and stop
     its negative margins bleeding to the card edges. */
  .tracks .card .card-media{
    width:auto; max-width:none;
    margin:0 -24px 18px;
    border:0; border-radius:0; box-shadow:none;
  }
  .tracks .card .card-media img{ aspect-ratio:13/5; height:auto; }
  /* the desktop strip breaks out of .wrap with 100vw; inside a stacked
     card that pushes the page 6px wider than the screen */
  .tracks .card-multi .card-media{
    width:auto; max-width:none;
    margin:0 -24px 16px;
    border-radius:0;
    box-shadow:none;
  }
  .tracks .card-multi .card-media img{ height:auto; aspect-ratio:16/9; }
  .tracks .card-multi .card-head{ border-top:0; padding-top:0; }
  .tracks .card .card-body{ max-width:none; display:block; }
  .tracks .card-multi .card-aside{ display:block; }
  .tracks .card-multi .card-aside .covers-cap{ margin-top:14px; }
  .tracks .card-multi .card-aside .card-link{ margin-top:16px; }
  .tracks .card{ box-shadow:var(--shadow); clip-path:none; }
  .tracks .card :is(.card-body, .card-main) > .card-proj{ font-size:12.5px; padding:12px 15px; }
  .tracks .card .plc-details{ grid-template-columns:1fr; }
  .tracks .card .card-link{ margin-top:16px; }
  .tracks,
  .group-combined .tracks{
    display:grid;
    grid-template-columns:1fr;
    gap:20px;
  }
  .card-flagship{ grid-row:auto; grid-column:auto; }
}

/* ---------- Course slides on a phone -------------------------
   Same mechanic as the desktop - the stage pins, the cards cross-fade
   through it, one course per screen. Only the spread changes: the
   two-column layout goes single file and every measure comes down, so
   a whole course still lands inside one screen height.

   The stage clips what overflows it, so nothing here is decorative
   trimming - each of these values is what keeps the call to action
   above the fold on a 640px-tall phone.

   Placed after the reduced-motion fallback above so it wins at equal
   specificity - which is exactly why it has to say no-preference. These
   rules describe the pinned stage; the fallback describes a stacked
   list. Matching on width alone, they overrode it. */
@media (max-width:860px) and (min-height:620px)
       and (prefers-reduced-motion:no-preference){
  .tracks .card{
    grid-template-columns:minmax(0,1fr);
    grid-template-rows:auto auto auto;
    column-gap:0;
    row-gap:clamp(8px, 1.4vh, 14px);
    /* top clears the sticky header and the ladder rung under it;
       bottom clears the terminal rail. */
    padding:clamp(70px, 9.4vh, 92px) 0 clamp(44px, 6.4vh, 66px);
  }

  .tracks .card .card-head{
    grid-column:1; grid-row:1;
    padding-bottom:clamp(8px, 1.2vh, 14px);
  }
  .tracks .card .label{ margin-bottom:8px; }
  .tracks .card .label::after{ width:16px; }
  .tracks .card h3{ font-size:clamp(18px, 5.2vw, 27px); }
  .tracks .card .rating{ font-size:12px; margin-bottom:2px; }

  .tracks .card .card-media{
    grid-column:1; grid-row:2;
    justify-self:stretch;
    border-radius:8px;
  }
  .tracks .card .card-media img{ height:clamp(96px, 18vh, 190px); }

  .tracks .card .card-body{ grid-column:1; grid-row:3; }

  /* The desktop set alternates which side the photo takes, and it does
     that at a specificity the plain .tracks .card rules above cannot
     reach - so without this the even-numbered slides kept a second
     column on the phone, halved their text width and ran 100-200px past
     the bottom of the stage. On one column there is no side to swap. */
  .tracks-flat .stage > .card:nth-of-type(even){
    grid-template-columns:minmax(0,1fr);
  }
  .tracks-flat .stage > .card:nth-of-type(even) .card-media{
    grid-column:1; grid-row:2;
    justify-self:stretch;
  }
  .tracks-flat .stage > .card:nth-of-type(even) .card-body{
    grid-column:1; grid-row:3;
  }
  .tracks .card .meta{ margin:0 0 9px; }
  .tracks .card .meta li{ font-size:11px; padding:4px 10px; }

  .tracks .card :is(.card-body, .card-main) > .card-proj{
    margin:0 0 8px;
    padding:9px 12px;
    border-radius:0 var(--r-sm) var(--r-sm) 0;
    font-size:11.5px; line-height:1.55;
  }
  .tracks .card :is(.card-body, .card-main) > .card-proj b{
    margin-bottom:2px;
    font-size:9px; letter-spacing:.12em;
  }
  .tracks .card :is(.card-body, .card-main) > .card-desc{
    margin:0 0 8px; font-size:11.5px; line-height:1.55;
  }
  .tracks .card .card-link{
    margin-top:4px;
    padding:10px 20px;
    font-size:12.5px;
  }

  /* ---- the multi-brand slides ---- */
  /* Already single-column; it is the body's own two columns and the
     four-across platform table that have to fold. */
  .tracks .card-multi .card-body{
    grid-template-columns:minmax(0,1fr);
    row-gap:10px;
  }
  .tracks .card-multi .card-aside{ grid-column:1; }
  .tracks .card-multi .card-aside .card-link{ margin-top:12px; }
  /* The bank of screens (or the lab photo) leads a combined slide, as it
     does on desktop, with the heading under it. The single-brand rules
     above put every heading in row 1 and every photo in row 2, and on
     these slides that laid the heading over the bank - the course number
     and name sat behind the monitors. */
  .tracks .stage > .card-multi .monitor-wall,
  .tracks .stage > .card-multi .card-media{ grid-row:1; }
  .tracks .stage > .card-multi .card-head{ grid-row:2; }
  .tracks .stage > .card-multi .card-body{ grid-row:3; }
  /* The diploma slides carry a twelve-platform table and two rows of
     coverage pills on top of everything a single-brand slide has - on a
     640px-tall phone that is about 90px more than the stage can show, and
     the stage clips rather than scrolls. Every measure below comes down
     by roughly a fifth, which is what buys those 90px back. */
  .tracks .card .plc-details,
  .tracks .card-multi .plc-details{
    grid-template-columns:1fr 1fr;
    gap:6px;
    margin-top:8px;
    padding:8px;
  }
  .tracks .card .plc-details .card-proj{
    padding:7px 9px;
    font-size:10.5px; line-height:1.45;
  }
  .tracks .card .plc-details .card-proj b{
    margin-right:5px; padding:1px 5px;
    font-size:8px;
  }
  .tracks .card .plc-name{ margin-right:5px; padding-right:5px; }

  .tracks .card .covers-cap{
    margin-top:9px; margin-bottom:5px;
    font-size:9px;
  }
  .tracks .card .covers + .covers-cap{ margin-top:8px; }
  .tracks .card .covers{ gap:5px; }
  .tracks .card .covers li{ padding:3px 8px; font-size:10.5px; line-height:1.3; }
  .tracks .card-multi .card-media{
    border-radius:10px 10px 50% 50% / 10px 10px 30% 30%;
    box-shadow:0 14px 34px rgba(0,0,0,.5);
  }
  .tracks .card-multi .card-media img{ height:clamp(120px, 20vh, 220px); }
}

/* Short phones. A 640-667px screen is about 75px short of what the
   diploma slides need - they carry a twelve-platform table and two rows
   of coverage pills that no single-brand slide has. The stage clips
   rather than scrolls, so the shortfall has to come out of the measures
   rather than be left to overflow.

   Keyed on height as well as width: a 390x844 phone is the same width
   and needs none of this. */
@media (max-width:860px) and (min-height:620px) and (max-height:700px)
       and (prefers-reduced-motion:no-preference){
  .tracks .card{ row-gap:6px; }
  .tracks .card .card-media img{ height:clamp(76px, 12.4vh, 190px); }
  .tracks .card :is(.card-body, .card-main) > .card-desc{
    font-size:10.5px; line-height:1.45;
  }
  .tracks .card .plc-details .card-proj{
    padding:6px 8px;
    font-size:9.8px; line-height:1.38;
  }
  .tracks .card .covers li{ padding:2px 7px; font-size:10px; }
  .tracks .card .covers-cap{ margin-top:7px; margin-bottom:4px; }
  .tracks .card .covers + .covers-cap{ margin-top:6px; }
}

/* The shortest screens still in use - a 360x640 phone leaves the
   diploma slide 15px over even after the block above. */
@media (max-width:860px) and (min-height:620px) and (max-height:660px)
       and (prefers-reduced-motion:no-preference){
  .tracks .card .card-media img{ height:clamp(66px, 10.6vh, 190px); }
  .tracks .card .meta li{ font-size:10px; padding:3px 8px; }
  .tracks .card .plc-details{ padding:6px; gap:5px; }
  .tracks .card .plc-details .card-proj{ font-size:9.4px; line-height:1.34; }
}

/* ---------- Scroll-driven highlight -------------------------
   The line starts dim and lights up left to right as the section
   comes in, the brightness tied to scroll position rather than
   played on a timer. Two stops of one gradient are clipped to the
   glyphs, and scrolling slides the gradient across them.

   background-clip:text needs a transparent fill to show through,
   so the colour has to come from the gradient - which is why the
   dim end is a real colour rather than an opacity. */
.sweep{
  background-image:linear-gradient(
    90deg,
    var(--text) 0%, var(--text) 50%,
    var(--text-dim) 50%, var(--text-dim) 100%);
  background-size:220% 100%;
  background-position:100% 0;
  background-repeat:no-repeat;
  -webkit-background-clip:text;
  background-clip:text;
  color:transparent;
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .sweep{
      animation:sweep-in linear both;
      animation-timeline:view();
      animation-range:entry 8px entry 520px;
    }
    /* Section intros sweep too, a beat behind their heading. */
    .section-head p{
      background-image:linear-gradient(
        90deg,
        var(--text-muted) 0%, var(--text-muted) 50%,
        rgba(154,161,169,.28) 50%, rgba(154,161,169,.28) 100%);
      background-size:220% 100%;
      background-position:100% 0;
      background-repeat:no-repeat;
      -webkit-background-clip:text;
      background-clip:text;
      color:transparent;
      animation:sweep-in linear both;
      animation-timeline:view();
      animation-range:entry 60px entry 600px;
    }
  }
}

@keyframes sweep-in{
  to{ background-position:0 0; }
}

/* ---------- Scroll progress ---------------------------------
   With every section pinned, nothing on screen moves as you scroll
   and there is no sign the page is going anywhere. This is the one
   element that always reports it. Driven off scroll(root) so it is
   exact and costs no script. */
body::before{
  content:"";
  position:fixed;
  top:0; left:0;
  width:100%; height:3px;
  z-index:50;
  transform-origin:0 50%;
  transform:scaleX(0);
  background:linear-gradient(90deg, var(--accent), var(--accent-hi));
  box-shadow:0 0 12px rgba(245,166,35,.45);
  animation:scroll-progress linear both;
  animation-timeline:scroll(root block);
}

@keyframes scroll-progress{
  from{ transform:scaleX(0); }
  to  { transform:scaleX(1); }
}

@media (prefers-reduced-motion:reduce){
  body::before{ box-shadow:none; }
}

/* ---------- Curved screen -------------------------------------
   Everything below the navbar reads as a curved monitor - the same curve
   as the wall of monitors on the welcome screen, where the ends stand tall
   and the middle sits back. On a concave screen seen from its centre the
   ends are nearer and so look taller: the top edge sags in the middle and
   the bottom edge rises. Those two bowed edges carry most of the effect; a
   reflection down each side, where the screen turns toward you, and a band
   of light that bows with the curve do the rest. The navbar is left flat
   and untouched - the curve starts at its bottom edge.

   One fixed overlay, laid over the page and under the navbar (z-index 35:
   above the blueprint grid at 30, below the header at 40), with
   pointer-events:none, so the page scrolls and clicks through it exactly
   as before. The bowed edges are SVG: a quadratic curve is a parabola,
   which keeps the corners of the screen almost clear and puts the depth in
   the middle. An ellipse (border-radius) covers far more near the sides,
   which is where the course kickers and the terminal rail sit.

   The bottom edge bows less than the top (0.6 of it): the terminal rail
   runs along the bottom-left of every course slide and has to clear it. */
:root{
  --hdr-h:79px;                         /* script.js sets it from the real navbar */
  --curve:clamp(6px, 2vw, 30px);        /* how far the top edge sags at its middle */
  --curve-b:calc(var(--curve) * 0.6);
  --curve-rim:#6f757e;                  /* the grey of the monitor rims */
}
@media (max-width:860px){ :root{ --hdr-h:75px; } }

.curve-screen{
  position:fixed;
  top:calc(var(--hdr-h) + var(--scr-top)); right:var(--scr-x); bottom:var(--scr-bottom); left:var(--scr-x);
  z-index:35;
  pointer-events:none;
  /* the straight sides of the screen */
  box-shadow:
    inset 2px 0 0 var(--curve-rim),
    inset -2px 0 0 var(--curve-rim),
    0 0 0 100vmax var(--scr-casing);          /* the frame round the smaller screen */
  /* the glass: light down each side where the screen turns toward you, and
     a band of light whose rings sag in the middle like the top edge */
  background:
    linear-gradient(90deg,
      rgba(255,255,255,.06) 0, rgba(255,255,255,0) 7%,
      rgba(255,255,255,0) 93%, rgba(255,255,255,.06) 100%),
    radial-gradient(150% 120% at 50% -55%,
      rgba(255,255,255,0) 58%, rgba(255,255,255,.035) 64%, rgba(255,255,255,0) 70%);
}
.curve-edge{
  position:absolute;
  left:0; width:100%;
  display:block;
  overflow:visible;
}
/* the shadow the bowed bezel casts onto the screen, which is what makes
   the screen read as set back behind it */
.curve-top{ top:0; height:var(--curve); filter:drop-shadow(0 5px 7px rgba(0,0,0,.55)); }
.curve-bottom{ bottom:0; height:var(--curve-b); filter:drop-shadow(0 -5px 7px rgba(0,0,0,.55)); }
.curve-fill{ fill:#0e0f11; }
.curve-rim{
  fill:none;
  stroke:var(--curve-rim);
  stroke-width:2;
  vector-effect:non-scaling-stroke;
}

/* ---------- Welcome wall: the maker's name above each screen -----
   The name hangs over each screen the way the signs hang over the
   benches in the lab, rather than sitting on a bezel under it.
   column-reverse, not a markup change: the screen is the content and
   stays first in the document. The sign and the screen still add up to
   the facet's height, so the curve's geometry is untouched. */
.welcome .monitor-wall li{ display:flex; flex-direction:column-reverse; }

/* A smaller wall: about three-quarters of the edge-to-edge width it had
   (1085px of a 1440px window instead of 1418px). Everything in the wall
   is sized off --w, so the curve keeps its shape and the monitors come
   down in height with it. Above the phone breakpoint only - on a phone
   the wall is already as small as the names on it allow. */
@media (min-width:861px){
  .welcome .monitor-wall{ --w:clamp(60px, 8.4vw, 120px); }
}

/* ---------- Monitor walls: the maker's name above each monitor -----
   The name hangs above each monitor, like the signs over the benches in
   the lab, instead of on a bezel under the picture. The sign is lifted
   out of the facet (position:absolute, bottom:100%), so it rides the
   facet's own 3D turn and hangs square over its own screen, and the
   screen fills the whole facet. The rounded corners move from the facet
   to the screen: the facet can no longer clip, since its sign now sits
   outside it.

   li in each selector lifts these over the welcome-only height rules
   (.welcome .monitor-wall .screen / .bezel) whatever the source order.
   Applies to every wall - the course slides' banks now, and the welcome
   wall too if that section is brought back. */
.monitor-wall li{ overflow:visible; }
.monitor-wall li .screen{
  height:100%;
  border-radius:inherit;
}
.monitor-wall li .bezel{
  position:absolute;
  left:0; right:0; bottom:100%;
  height:auto;
  padding:0 0 calc(var(--w) * 0.05);
}

/* ---------- First screen: the PLC names clear of the curve ---------
   With the welcome screen commented out, the hero panel is the first
   screen. It is built to pin at top:0 and clear the navbar with its own
   top padding, but as the first thing in the page it starts below the
   navbar instead - 79px lower than designed - so on the first screen the
   row of PLC names under it ("Train on live panels from") sat on the
   curve's bottom edge: 20px under it at 1536x730, which is a 1920x1080
   screen at 125% zoom. Pulled up under the navbar, it is pinned from the
   first frame exactly where it sits when it pins later.

   :not(.welcome-panel): the welcome screen is built to sit below the
   navbar, so if it comes back this steps aside. Wide screens only -
   below 1081px the hero's top padding (54px) is less than the navbar,
   so pulling it up would put the heading underneath it. */
@media (min-width:1081px){
  main > .panel:first-child:not(.welcome-panel){ margin-top:calc(-1 * var(--hdr-h)); }
  /* Once pulled up, the heading has to clear the navbar AND the curve's
     top edge, which sags up to --curve below it - at 78px of padding the
     headline's top half sat under the curve at 1536x730. */
  main > .panel:first-child:not(.welcome-panel) .hero{
    padding-top:calc(var(--hdr-h) + var(--scr-top) + var(--curve) + 12px);
  }
}
/* Windows up to 780px tall: that extra top padding would make the panel
   taller than the screen, and it would stop pinning. The space below the
   hero and round the names row comes down to hold it to one screen. */
@media (min-width:1081px) and (max-height:780px){
  main > .panel:first-child:not(.welcome-panel) .hero{ padding-bottom:28px; }
  main > .panel:first-child:not(.welcome-panel) .brands{ padding-top:10px; padding-bottom:30px; }
}
/* Windows up to 700px tall: tighter again, so the names row clears the
   curve's bottom edge and the panel still fits the screen. */
@media (min-width:1081px) and (max-height:700px){
  main > .panel:first-child:not(.welcome-panel) .hero{ padding-bottom:20px; }
  main > .panel:first-child:not(.welcome-panel) .brands{ padding-top:6px; padding-bottom:24px; }
}

/* ---------- PLC names on the curve ---------------------------
   The platform names sit on the curved edge under the navbar, spread
   along the arc and each turned to follow the curve at its own point -
   printed on the monitor's bezel, the way a maker's name is. They moved
   here from the strip at the foot of the first screen ("Train on live
   panels from"), which the curve's bottom edge was cutting off.

   Each label's place comes from the same parabola the SVG edge is drawn
   with. At a point x of the way across, the edge sits
   S * (1 - (2x - 1)^2) below the navbar, so the label is centred in the
   band at half that depth, and turned by the curve's slope there:
   atan2(-4S(2x - 1), 100vw).

   On every screen, as on desktop. From 1024px up the band is deep enough
   to hold a line of type (about 20px deep at 1024, 30px on a wide
   screen); below that it is only a few pixels deep, so there the names
   sit a little higher, in the dark strip between the navbar and the curve
   line - see "PLC names on a phone" below. The overlay is fixed, so the
   names stay on the bezel as the page scrolls. */
@media all{
  .curve-brands{
    display:block;
    position:absolute;
    top:0; left:0; right:0;
    height:var(--curve);
    margin:0; padding:0;
    list-style:none;
  }
  .curve-brands li{
    --depth:calc(var(--curve) * (1 - (2 * var(--x) - 1) * (2 * var(--x) - 1)));
    position:absolute;
    left:calc(var(--x) * 100%);
    top:calc(var(--depth) / 2);
    translate:-50% -50%;
    rotate:atan2(calc(-4 * var(--curve) * (2 * var(--x) - 1)), calc(100vw - 2 * var(--scr-x)));
    /* a little larger (the user: "keep the font size little bit bigger"):
       10.7px at 1440 from 8.9; 8.5px at 1024 keeps 12px between names */
    font-size:clamp(8.5px, 0.74vw, 13px);
    font-weight:700;
    letter-spacing:.2em;
    text-transform:uppercase;
    line-height:1;
    white-space:nowrap;
    color:var(--on-navy-mute, #9aa1a9);
  }
  /* eight names - SCADA sits where its course does, between Rockwell and
     ABB - spread a little wider than seven were, so the longest names still
     clear each other at 1024px */
  .curve-brands li:nth-child(1){ --x:0.1600; }
  .curve-brands li:nth-child(2){ --x:0.2571; }
  .curve-brands li:nth-child(3){ --x:0.3543; }
  .curve-brands li:nth-child(4){ --x:0.4514; }
  .curve-brands li:nth-child(5){ --x:0.5486; }
  .curve-brands li:nth-child(6){ --x:0.6457; }
  .curve-brands li:nth-child(7){ --x:0.7429; }
  .curve-brands li:nth-child(8){ --x:0.8400; }

  /* the strip they replace */
  body .brands{ display:none; }
}

/* ---------- Circuit on the curve ------------------------------
   The maker names on the bezel are wired into one circuit, drawn the
   way the ladder rung on the course slides is: a thin amber wire, and a
   contact either side of each name. While a course is on the stage,
   current runs in along the wire from the left and stops at that
   maker's name, which lights; move to the next course and the light
   runs on to the next name - and back again if you scroll back. SCADA
   has its own name, where its course sits in the set, and a combined
   programme lights every maker on its monitor bank. Off the course
   stage the line goes dark.

   script.js only decides where the light should be: it sets --wire-p,
   the fraction of the way across the screen the current has reached,
   and marks the names. --wire-p is registered so a change animates, and
   the lit length of wire and the bright head at its front both follow
   it along the curve. The wire is the line through the names' centres -
   half the edge's depth, so a quadratic from corner to corner with its
   control point at the edge's full depth - and the head's height comes
   from the same parabola the names are placed with.

   A short spark also runs the lit length over and over, the current the
   ladder rung shows. It passes under the names - each is painted on the
   bezel's own colour - so it reads as going through the contacts. */
@property --wire-p{ syntax:"<number>"; inherits:true; initial-value:0; }
@property --wire-s{ syntax:"<number>"; inherits:false; initial-value:0; }

@media all{   /* every width - the phone's sizes are in "PLC names on a phone" */
  .curve-screen{ transition:--wire-p .9s cubic-bezier(.45,0,.2,1); }

  .curve-wire{
    display:block;
    top:0; height:var(--curve);
  }
  .curve-wire path{
    fill:none;
    stroke:rgba(245,166,35,.24);
    stroke-width:1;
    vector-effect:non-scaling-stroke;
  }
  /* the lit length: the same wire, cut off where the current has reached */
  .curve-wire-live{
    clip-path:inset(-8px calc((1 - var(--wire-p)) * 100%) -8px 0);
    filter:drop-shadow(0 0 3px rgba(245,166,35,.85));
  }
  .curve-wire-live path{ stroke:#f5a623; stroke-width:1.5; }

  /* each name sits on the wire as a contact: the bezel's colour behind it
     breaks the wire, and a bar either side is the contact */
  .curve-brands li{
    padding:3px 9px;
    background:#0e0f11;
    transition:color .3s ease, text-shadow .3s ease;
  }
  .curve-brands li::before,
  .curve-brands li::after{
    content:"";
    position:absolute;
    top:50%;
    width:1.5px; height:9px;
    margin-top:-4.5px;
    background:rgba(245,166,35,.34);
    transition:background .3s ease, box-shadow .3s ease;
  }
  .curve-brands li::before{ left:0; }
  .curve-brands li::after{ right:0; }

  /* contacts the current has already passed through on its way */
  .curve-brands li.is-passed::before,
  .curve-brands li.is-passed::after,
  .curve-brands li.is-on::before,
  .curve-brands li.is-on::after{
    background:#f5a623;
    box-shadow:0 0 6px rgba(245,166,35,.8);
  }
  /* The maker of the course on screen - it lights as the current arrives.
     A delayed animation rather than a delayed transition: the current's
     run changes an inherited custom property on every frame, and Chrome
     let the name's colour through on those updates at once, before the
     light had got there. The contacts either side light with it. */
  .curve-brands li.is-on{
    color:#ffc25c;
    text-shadow:0 0 10px rgba(245,166,35,.7);
    animation:name-light .35s ease .55s both;
  }
  .curve-brands li.is-on::before,
  .curve-brands li.is-on::after{ animation:contact-light .3s ease .55s both; }

  /* the bright head at the front of the current */
  .curve-head{
    display:block;
    position:absolute;
    left:calc(var(--wire-p) * 100%);
    top:calc(var(--curve) * (1 - (2 * var(--wire-p) - 1) * (2 * var(--wire-p) - 1)) / 2);
    width:7px; height:7px;
    translate:-50% -50%;
    border-radius:50%;
    background:#ffd27a;
    box-shadow:0 0 0 2px rgba(245,166,35,.3), 0 0 12px 3px rgba(245,166,35,.7);
    opacity:0;
    transition:opacity .35s ease;
  }

  /* the spark running the lit length, again and again */
  .curve-spark{
    --sx:calc(var(--wire-s) * var(--wire-p));
    display:block;
    position:absolute;
    left:calc(var(--sx) * 100%);
    top:calc(var(--curve) * (1 - (2 * var(--sx) - 1) * (2 * var(--sx) - 1)) / 2);
    width:34px; height:2px;
    translate:-100% -50%;
    transform-origin:100% 50%;
    rotate:atan2(calc(-4 * var(--curve) * (2 * var(--sx) - 1)), 100vw);
    border-radius:2px;
    background:linear-gradient(90deg, transparent, rgba(255,214,130,.95));
    opacity:0;
  }
  .curve-screen.is-live .curve-head{ opacity:1; }
  .curve-screen.is-live .curve-spark{ animation:wire-run 2.4s linear infinite; }
}

@keyframes wire-run{
  0%   { --wire-s:0; opacity:0; }
  12%  { opacity:1; }
  88%  { opacity:1; }
  100% { --wire-s:1; opacity:0; }
}
@keyframes name-light{
  from{ color:var(--on-navy-mute, #9aa1a9); text-shadow:0 0 0 rgba(245,166,35,0); }
  to  { color:#ffc25c; text-shadow:0 0 10px rgba(245,166,35,.7); }
}
@keyframes contact-light{
  from{ background:rgba(245,166,35,.34); box-shadow:0 0 0 rgba(245,166,35,0); }
  to  { background:#f5a623; box-shadow:0 0 6px rgba(245,166,35,.8); }
}

/* No travel and no spark: the light is simply where it should be. */
@media (prefers-reduced-motion:reduce){
  .curve-screen{ transition:none; }
  .curve-screen.is-live .curve-spark{ animation:none; opacity:0; }
  .curve-brands li.is-on,
  .curve-brands li.is-on::before,
  .curve-brands li.is-on::after{ animation:none; }
}

/* ---------- PLC names on a phone ----------------------------------
   Below 1024px the curve sags only a few pixels, too shallow to hold a
   line of type - so the names, and the wire that runs through them, move
   up by half the gap above the screen: each is centred in the dark strip
   between the navbar and the curve line rather than in the curve's own
   band. The wire, its head and the spark move with them, so the current
   still runs through the contacts.

   Eight names across a phone: the type is smaller and scales with the
   width, the tracking is almost gone, and each name's place was worked out
   from its own width (a long name gets more room than a short one), which
   leaves 5-7px between neighbours from 360px up and clears the stack
   light at the left end. The places are fractions of the screen's width,
   and the type is a fraction of it too, so they hold at every phone width. */
@media (max-width:1023px){
  .curve-brands,
  .curve-wire{ top:calc(var(--scr-top) / -2 + 1px); }
  .curve-head,
  .curve-spark{ margin-top:calc(var(--scr-top) / -2 + 1px); }
  .curve-brands li{
    font-size:clamp(6px, 1.75vw, 9px);
    letter-spacing:.02em;
    padding:2px 2.5px;
  }
  .curve-brands li::before,
  .curve-brands li::after{ width:1px; height:7px; margin-top:-3.5px; }
  .curve-head{ width:5px; height:5px; }
  .curve-spark{ width:22px; }

  .curve-brands li:nth-child(1){ --x:0.133; }
  .curve-brands li:nth-child(2){ --x:0.234; }
  .curve-brands li:nth-child(3){ --x:0.348; }
  .curve-brands li:nth-child(4){ --x:0.485; }
  .curve-brands li:nth-child(5){ --x:0.602; }
  .curve-brands li:nth-child(6){ --x:0.686; }
  .curve-brands li:nth-child(7){ --x:0.774; }
  .curve-brands li:nth-child(8){ --x:0.895; }
}
/* 861-1023px: the stack light stands at 11% here, not in the phone's
   margin, so the same spacing starts further in */
@media (min-width:861px) and (max-width:1023px){
  /* a little larger, as on desktop: 10px at 900 from 9, 30px between names */
  .curve-brands li{ font-size:clamp(6.5px, 1.95vw, 10px); }
  .curve-brands li:nth-child(1){ --x:0.204; }
  .curve-brands li:nth-child(2){ --x:0.296; }
  .curve-brands li:nth-child(3){ --x:0.400; }
  .curve-brands li:nth-child(4){ --x:0.526; }
  .curve-brands li:nth-child(5){ --x:0.633; }
  .curve-brands li:nth-child(6){ --x:0.709; }
  .curve-brands li:nth-child(7){ --x:0.790; }
  .curve-brands li:nth-child(8){ --x:0.900; }
}

/* ---------- Course slides inside the curve ------------------------
   The course stage pins at top:0, so each slide is laid out against the
   whole window - and the curve's top edge sags up to --curve below the
   navbar. It was covering the ladder rung on every slide (17px of it at
   1536x730) and, in a short window, the course kicker as well. Everything
   on a slide now starts below the curve: the rung sits just under it and
   the slide's top padding clears it. The bottom curve already clears the
   terminal rail everywhere.

   Only while the stage is pinned; the stacked list (reduced motion, short
   phones) keeps its own padding. */
@media (prefers-reduced-motion:no-preference) and (min-height:620px){
  .tracks .ladder{ top:calc(var(--hdr-h) + var(--scr-top) + var(--curve) + 4px); }
}
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px){
  .tracks .stage > .card{ padding-top:max(100px, calc(var(--hdr-h) + var(--scr-top) + var(--curve) + 12px)); }
}
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px){
  .tracks .stage > .card{ padding-top:max(clamp(70px, 9.4vh, 92px), calc(var(--hdr-h) + var(--scr-top) + var(--curve) + 8px)); }
}
/* Short phones: the padding above costs up to 21px, which the photo gives back. */
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px) and (max-height:700px){
  .tracks .stage > .card .card-media img{ height:clamp(66px, 10.6vh, 190px); }
}
/* Short desktop windows: the diploma slide ran 32px past a 657px stage
   before any of this, and the new top padding adds to that. Its platform
   table, coverage pills and description come down to fit. */
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px) and (max-height:700px){
  .tracks .stage > .card{ padding-bottom:44px; }
  /* the photo ran 4px under the bottom curve at 1366x657 once the slide
     moved down to clear the top one */
  .tracks .stage > .card .card-media img{ height:clamp(260px, 48vh, 540px); }
  .tracks .stage > .card :is(.card-body, .card-main) > .card-proj{ margin-bottom:12px; padding:12px 16px; font-size:13px; line-height:1.6; }
  .tracks .stage > .card :is(.card-body, .card-main) > .card-desc{ margin-bottom:10px; font-size:12.5px; line-height:1.55; }
  .tracks .stage > .card .plc-details{ gap:6px; margin-top:10px; padding:9px; }
  .tracks .stage > .card .plc-details .card-proj{ padding:7px 9px; font-size:10.5px; line-height:1.42; }
  .tracks .stage > .card .covers-cap{ margin-top:9px; margin-bottom:5px; }
  .tracks .stage > .card .covers + .covers-cap{ margin-top:8px; }
  .tracks .stage > .card .covers li{ padding:4px 9px; font-size:11px; }
}

/* ---------- A smaller curved screen ------------------------------
   The curved screen no longer runs edge to edge: it sits in from both
   sides, a little below the navbar and a little above the bottom of the
   window, with a dark frame round it - the same curved borders, on a
   smaller screen. The page is narrowed to match so nothing sits under the
   frame, and every held screen (the course slides, the pinned panels) is
   laid out inside the shorter height. */
:root{
  --scr-x:clamp(10px, 3vw, 56px);       /* how far the screen sits in from each side */
  --scr-top:clamp(12px, 4.2vh - 4px, 36px);   /* gap between the navbar and the screen - the stack light stands in it */
  --scr-bottom:clamp(10px, 3vh, 30px);  /* gap between the screen and the window's bottom */
  --scr-casing:#0e0f11;                 /* the frame: the same dark as the curved band */
}
@media (max-width:860px){
  /* A phone gets the whole monitor: the screen sits further in, and its
     edges bow with the desktop's shape - the curve is 3.6% of the width,
     the share it is on a desktop, so about 13px at 360px wide, where the
     desktop's 2vw left 7px and the screen read as a flat frame. The gap
     above is 22px: all eight PLC names run through it, above the screen,
     and the stack light stands on the screen's top edge in it (see "The
     names on a phone" at the end of the sheet). The gap below stays as it
     was - a small phone's course slides have little height to spare. */
  :root{ --scr-x:14px; --scr-top:22px; --scr-bottom:8px; --curve:clamp(12px, 3.6vw, 30px); }
}

/* the page is as wide as the screen */
main, .site-footer{ padding-inline:var(--scr-x); }

/* Course slides: the rail and the slide's foot clear the screen's new
   bottom, and the photo gives back the height the two gaps take, so every
   slide still fits its stage. */
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px){
  .tracks .stage > .card{ padding-bottom:calc(56px + var(--scr-bottom)); }
  .tracks .rail{ bottom:calc(clamp(26px, 5vh, 54px) + var(--scr-bottom)); }
  .tracks .stage > .card .card-media img{
    height:calc(clamp(300px, 52vh, 540px) - var(--scr-top) - var(--scr-bottom));
  }
}
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px) and (max-height:700px){
  .tracks .stage > .card{ padding-bottom:calc(44px + var(--scr-bottom)); }
  .tracks .stage > .card .card-media img{
    height:calc(clamp(260px, 48vh, 540px) - var(--scr-top) - var(--scr-bottom));
  }
  /* the combined programmes' bank of screens is what makes those slides
     tall - 16px too tall at 1366x657 - so it comes down a fifth */
  .tracks .stage > .card-multi .monitor-wall{
    --w:calc((min(100vw - 2 * var(--scr-x), 1360px) - 68px) / 8.96 * 0.8);
  }
}
/* Up to 800px tall the diploma slide's platform table and pills come down
   too - it is the tallest slide, and its height is the text, not the photo. */
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px) and (max-height:800px){
  .tracks .stage > .card :is(.card-body, .card-main) > .card-proj{ margin-bottom:12px; padding:12px 16px; font-size:13px; line-height:1.6; }
  .tracks .stage > .card :is(.card-body, .card-main) > .card-desc{ margin-bottom:10px; font-size:12.5px; line-height:1.55; }
  .tracks .stage > .card .plc-details{ gap:6px; margin-top:10px; padding:9px; }
  .tracks .stage > .card .plc-details .card-proj{ padding:7px 9px; font-size:10.5px; line-height:1.42; }
  .tracks .stage > .card .covers-cap{ margin-top:9px; margin-bottom:5px; }
  .tracks .stage > .card .covers + .covers-cap{ margin-top:8px; }
  .tracks .stage > .card .covers li{ padding:4px 9px; font-size:11px; }
}
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px){
  .tracks .stage > .card{ padding-bottom:calc(clamp(44px, 6.4vh, 66px) + var(--scr-bottom)); }
  .tracks .rail{ bottom:calc(clamp(10px, 2.2vh, 22px) + var(--scr-bottom)); }
  .tracks .stage > .card .card-media img{
    height:calc(clamp(104px, 19vh, 190px) - var(--scr-top) - var(--scr-bottom));
  }
}
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px) and (max-height:700px){
  .tracks .stage > .card .card-media img{
    height:calc(clamp(56px, 9.4vh, 190px) - var(--scr-top) - var(--scr-bottom));
  }
}

/* Held panels centre their content in the whole window, but the screen
   now starts below the navbar, a gap and the curve, and ends above a gap
   and the bottom curve. Padding each held panel by those amounts moves its
   content to the middle of the screen - at 1536x730 the top line of Why
   Infercon sat 6px under the screen's top edge. A panel that no longer
   fits then pins by its bottom edge (fitPanels in script.js), so all of
   it scrolls into view before the next one comes up over it. The first panel has
   its own rule, and the course track has its own stage. */
main > .panel:not(:first-child):not(.panel-scroller){
  padding-top:calc(var(--scr-top) + var(--curve));
  /* the bottom curve rises into the screen too - Why Infercon's stats
     strip sat under it at 1536x730 with the gap alone */
  padding-bottom:calc(var(--scr-bottom) + var(--curve-b));
}

/* The combined programmes' bank hangs its names above the monitors, and
   the end monitors stand highest - so the sign over the first one rose
   5-7px above the screen's top curve at 1536x730 and 1366x657. The bank
   drops by a sign's height; up to 800px tall it is also drawn a fifth
   smaller, which gives that height back so the slide still fits. */
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px){
  .tracks .stage > .card-multi .monitor-wall{ margin-top:calc(var(--w) * 0.24); }
}
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px) and (max-height:800px){
  .tracks .stage > .card-multi .monitor-wall{
    --w:calc((min(100vw - 2 * var(--scr-x), 1360px) - 68px) / 8.96 * 0.8);
  }
}

/* ---------- Stack light on the curve ---------------------------
   A small signal tower - the red / amber / green stack light that sits on
   top of machine panels - standing on the left end of the curve. script.js
   sets data-state from the course stage, like a signal on every change of
   course: red as the course on screen starts to move away, amber as the
   next one's content comes in, green once it is fully in place - and all
   three dark anywhere else. It stands in the frame and the curved band, above the
   screen, so it never covers content.

   It stands on top of the curve: at 11% of the way across, the curve is
   0.39 of its depth below the screen's top edge, and the tower's foot is
   there, on the line. It rises from there to 1px under the navbar - all
   the height there is, since the navbar paints over anything taller -
   and it is half as wide as it is tall, so the lamps are bigger than
   they were without the foot leaving the curve. The gap above the screen
   (--scr-top) is sized for it: the screen sits a little lower than it
   needs to, so the tower can stand taller. */
.stack-light{
  --sl-h:max(14px, calc(var(--scr-top) - 1px + var(--curve) * 0.39));
  position:absolute;
  left:11%;
  top:calc(1px - var(--scr-top));
  z-index:1;
  width:calc(var(--sl-h) * 0.5);
  height:var(--sl-h);
  translate:-50% 0;
  display:flex;
  flex-direction:column;
  gap:1px;
  padding:2px 1px 3px;                          /* the cap and the foot */
  border-radius:3px 3px 2px 2px;
  background:linear-gradient(90deg, #23262b, #4a4f57 45%, #1d2024);   /* the metal body, lit from the left */
  box-shadow:0 1px 0 #6f757e, 0 2px 5px rgba(0,0,0,.65);
}
.stack-light{ isolation:isolate; }
.stack-light i{
  position:relative;
  flex:1;
  border-radius:2px;
  /* a curved glass lens: bright down the middle, darker at its edges */
  background:radial-gradient(70% 90% at 42% 35%, rgba(255,255,255,.16), transparent 70%), var(--lamp-off);
}
/* The glow is its own layer, behind all three lenses. Drawn on the lens
   itself it spilled over its neighbours: with the bottom lamp lit, the
   amber lens above it looked lit as well. Behind them, it lights the
   housing round the lamp but never shows through another lens. */
.stack-light i::before{
  content:"";
  position:absolute;
  inset:-2px -3px;
  z-index:-1;
  border-radius:50%;
  background:var(--lamp-on);
  box-shadow:
    0 0 6px 2px var(--lamp-on),
    0 0 16px 4px color-mix(in srgb, var(--lamp-on) 45%, transparent);
  opacity:0;
  transition:opacity .18s ease;
}
.stack-light .lamp-red  { --lamp-off:#3b1517; --lamp-on:#ff4d4f; }
.stack-light .lamp-amber{ --lamp-off:#3a2a0e; --lamp-on:#ffb020; }
.stack-light .lamp-green{ --lamp-off:#0f2e21; --lamp-on:#3ddb96; }
.stack-light[data-state="red"] .lamp-red,
.stack-light[data-state="amber"] .lamp-amber,
.stack-light[data-state="green"] .lamp-green{
  background:radial-gradient(70% 90% at 42% 35%, rgba(255,255,255,.6), transparent 70%), var(--lamp-on);
}
.stack-light[data-state="red"] .lamp-red::before,
.stack-light[data-state="amber"] .lamp-amber::before,
.stack-light[data-state="green"] .lamp-green::before{ opacity:1; }
/* On a phone the band is only a few pixels deep, so the tower stands in
   the left margin, clear of the text, and runs a little into the screen. */
@media (max-width:860px){
  .stack-light{ left:3.5%; --sl-h:max(20px, calc(var(--scr-top) - 1px + var(--curve) * 0.39)); }
}

/* ---------- Course slides in three beats -------------------------
   Each course arrives in three beats as it is scrolled through, and the
   stack light on the curve follows them:
     red    - the course name and its photo, which come in with the
              course itself
     amber  - the curriculum comes in
     green  - everything in place.
   script.js works out each course's progress through its own slice of
   the stage, 0 to 1, from the scroll position - over the span the course
   is on screen for, so it is whole by the middle of it - and marks the
   beats as they are reached: .is-in on the combined programmes' bank
   (.monitor-wall) from 0.18, and on the curriculum (.card-body) from 0.34
   once the course has faded up. Each then comes in on its own timed
   transition. So the scroll says where a course should be, but not how
   fast it gets there: scroll fast and the beats still play out instead of
   being skipped. The light reads the same marks, so they cannot drift
   apart.

   The beats used to be one number, --q, eased by a 1.2s transition on the
   card and read by everything inside it. It was registered as inherited,
   so every frame of every ease restyled the whole card - about half of
   the work a phone did while scrolling through the courses. Classes and
   opacity/translate transitions do the same with nothing recalculated
   per frame.

   The photo used to fly in as scattered pieces that joined up into the
   picture. The course showed an empty frame while they gathered and then
   the picture all at once, so they were taken out: the photo arrives
   with its course, in the card's own fade, and keeps its drift.

   Only while script.js runs the pinned stage (.js-swap); otherwise - the
   stacked list, or no script - everything shows as it finishes. */

@media (prefers-reduced-motion:no-preference) and (min-height:620px){
  /* the curriculum comes in after the name and the photo */
  .tracks.js-swap .stage > .card .card-body{
    opacity:0;
    translate:0 18px;
    transition:opacity .45s cubic-bezier(.3,.1,.3,1),
               translate .45s cubic-bezier(.3,.1,.3,1);
  }
  .tracks.js-swap .stage > .card .card-body.is-in{ opacity:1; translate:0 0; }

  /* The combined programmes show a bank of monitors, not a photo: the
     monitors drop into the wall one after another. */
  .tracks.js-swap .stage > .card-multi .monitor-wall li{
    opacity:0;
    translate:0 -46px;
    transition:opacity .5s cubic-bezier(.3,.1,.3,1) calc(var(--mi, 0) * 70ms),
               translate .5s cubic-bezier(.3,.1,.3,1) calc(var(--mi, 0) * 70ms);
  }
  .tracks.js-swap .stage > .card-multi .monitor-wall.is-in li{ opacity:1; translate:0 0; }
  .tracks .stage > .card-multi .monitor-wall li:nth-child(2){ --mi:1; }
  .tracks .stage > .card-multi .monitor-wall li:nth-child(3){ --mi:2; }
  .tracks .stage > .card-multi .monitor-wall li:nth-child(4){ --mi:3; }
  .tracks .stage > .card-multi .monitor-wall li:nth-child(5){ --mi:4; }
  .tracks .stage > .card-multi .monitor-wall li:nth-child(6){ --mi:5; }
  .tracks .stage > .card-multi .monitor-wall li:nth-child(7){ --mi:6; }
  .tracks .stage > .card-multi .monitor-wall li:nth-child(8){ --mi:7; }
}

/* ---------- A slow change of course -----------------------------
   The change from one course to the next was tied straight to the scroll,
   so it happened within a wheel notch or two - and a fast scroll made it a
   hard cut. script.js now decides which course is on screen, from the
   scroll position, and the change itself is timed: the old course fades
   out over half a second, the new one fades in after it, and then its
   three beats play from the name. However fast the scroll, the change
   takes about a second.

   Only while script.js has marked the track (.js-swap - it does so while
   the stage is pinned): otherwise the stage's own scroll-linked fade stays
   in charge and the course section never goes blank.

   Only the course on screen takes the pointer. Every course sits in the
   same stuck stage, so before this the last one, invisible on top of the
   rest, took every click - "View curriculum" could not be pressed.

   The drift - the card's slow rise and the photo's inside its frame - is
   set by script.js too (--d, 0 to 1 over the same slice the scroll
   animation used). Only the course on screen follows the scroll; the one
   leaving keeps its place while it fades. Tied to the scroll, a fast
   scroll threw the leaving course to the end of its rise, up to 60px
   over the top of the screen, while it was still fading. */
@property --vis{ syntax:"<number>"; inherits:false; initial-value:0; }

@media (prefers-reduced-motion:no-preference) and (min-height:620px){
  .tracks.js-swap .stage > .card{
    animation:none;
    transform:translateY(calc(64px - var(--d, 0) * 128px));   /* stage-drift */
    opacity:var(--vis);
    pointer-events:none;
    transition:--vis .5s cubic-bezier(.4,0,.6,1),
               border-color .18s ease, transform .18s ease, box-shadow .18s ease;
  }
  /* arriving: waits for the last course to be nearly gone, then fades in */
  .tracks.js-swap .stage > .card.is-current{
    pointer-events:auto;
    transition:--vis .65s cubic-bezier(.2,.6,.3,1) .45s,
               border-color .18s ease, transform .18s ease, box-shadow .18s ease;
  }
  .tracks.js-swap .stage > .card .card-media img{
    animation:none;
    transform:translateY(calc((0.5 - var(--d, 0)) * 8%)) scale(1.1);   /* stage-swap-media */
  }
}

/* ---------- A curved screen on a phone ---------------------------
   The deeper phone curve (see "A smaller curved screen") takes about 6px
   of height from every held course slide - and the slides on a small phone
   had only a few pixels to spare. The photo gives it back: its height
   loses the curve's extra depth over the old 8px, the same way it already
   loses the gaps above and below the screen. Later
   in the sheet than the photo rules it replaces, which match at equal
   specificity. */
/* The photo on a phone is sized first, and tall - the course is its
   picture - and the rest of the slide fits round it. It used to be what
   gave way (the gaps and the curve were taken out of it), which left it
   137px tall on a 360x740 phone and 35px on a 375x667 one. Now: about a
   quarter of the window's height, 178px at 360x740 and 203px at 390x844,
   and a little less on a short phone, 140px at 375x667. */
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px){
  .tracks .stage > .card .card-media img{ height:clamp(150px, 24vh, 220px); }
}
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px) and (max-height:700px){
  .tracks .stage > .card .card-media img{ height:clamp(120px, 21vh, 220px); }
}

/* The names on a phone: all eight, in one row, above the screen. The
   screen sits lower on a phone (--scr-top 22px), which opens a strip
   between the navbar and its top edge; the names and the wire run through
   it ("PLC names on a phone" centres them in that strip). Eight names in
   one row across a 330-390px screen only fit small, so the type scales with
   the width - 7.5px at 360, 8.6px at 414 - with almost no padding, and the
   contact bars either side of each name are left off: they crowded the
   3.5-5px between neighbours, and the lit wire already shows how far the
   current has come. Each name's place was worked out from its own width at
   390px, packed left to right with equal gaps from just past the stack
   light to the screen's right edge; the gaps hold from 360px (3.6px) to
   414px (5px).

   The stack light stands on the screen's top edge: its foot is on the
   curve line at its place (3.5% of the way across, where the curve is
   0.135 of its depth below the screen's top), and it rises from there to
   just under the navbar. */
@media (max-width:860px){
  .curve-brands li{
    font-size:clamp(7px, 2.08vw, 9px);  /* 7px only below 337px wide - at 320 the row needs it for gaps */
    letter-spacing:.02em;
    padding:1px;
  }
  .curve-brands li::before,
  .curve-brands li::after{ display:none; }
  .curve-brands li:nth-child(1){ --x:0.120; }
  .curve-brands li:nth-child(2){ --x:0.225; }
  .curve-brands li:nth-child(3){ --x:0.347; }
  .curve-brands li:nth-child(4){ --x:0.496; }
  .curve-brands li:nth-child(5){ --x:0.620; }
  .curve-brands li:nth-child(6){ --x:0.705; }
  .curve-brands li:nth-child(7){ --x:0.794; }
  .curve-brands li:nth-child(8){ --x:0.924; }
  .stack-light{
    --sl-h:calc(var(--scr-top) - 1px + var(--curve) * 0.135);
    top:calc(1px - var(--scr-top));
  }
}

/* ---------- Course slides fit a phone -----------------------------
   On a phone the course photo is tall (the user's choice - "A curved
   screen on a phone" sets it) and it is the fixed point: everything else
   on a slide fits round it. Measured slide by slide from 360x640 to 414x896
   (and a 768 tablet), every slide's content now clears the curve at the top
   and the course markers at the foot. Placed after the phone photo rules so
   these win at equal specificity.

   - The markers along the foot were 58px tall, and the slide's bottom
     padding cleared only about 30px of them, so every slide was centred in
     a box whose last 27px lay under the markers - "View curriculum" was
     half hidden on most phones. The markers are shorter now (36px: less
     padding, a 16px lit tick) and sit a little lower, and the padding clears
     them exactly.
   - An over-tall slide squeezed the photo's grid row instead of
     overflowing (the figure clips, so its automatic minimum is 0; the
     combined set also sets min-height:0 on it) and laid the text over the
     picture. The photo's row is now sized to the photo.
   - The gaps between the head, the photo and the body, the details' type
     (10.5px) and padding come down a little. Single-brand slides start 6px
     nearer the curve - their label sits at the left end, where the curve is
     shallow; the combined slides keep their room for the bank's signs. The
     label stays on one line (at 360x640 "05 TRACK - ROCKWELL" broke in two).
   - Siemens Automation and Control, the one plain slide with the two-column
     details table, is the tallest: its two narrow columns stack into one
     full-width column, the box round them goes, and - the user's choice -
     its topic tags are hidden on a phone; the same topics are written in its
     Basics and Advanced text.
   - On phones 660px tall or less the photo comes down to about 100px (the
     user's choice for those phones), and the combined slides' bank of
     screens comes down on short phones so its signs stay under the curve. */
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px){
  .rail button{ gap:4px; padding:4px 0 2px; }
  .rail b{ line-height:1; }
  .tracks .rail{ bottom:calc(clamp(8px, 1.6vh, 16px) + var(--scr-bottom)); }
  .tracks.js-swap .rail li.is-current{ --tick:16px; }

  .tracks .stage > .card{
    padding-bottom:calc(clamp(8px, 1.6vh, 16px) + var(--scr-bottom) + 42px);
    grid-template-rows:auto max-content auto;
    row-gap:6px;
  }
  .tracks .stage > .card:not(.card-multi){
    padding-top:max(clamp(70px, 9.4vh, 92px), calc(var(--hdr-h) + var(--scr-top) + var(--curve) + 2px));
  }
  .tracks .stage > .card .card-head{ padding-bottom:6px; }
  .tracks .stage > .card .card-head{ column-gap:12px; }
  .tracks .stage > .card .card-head > .label{ letter-spacing:.12em; }

  .tracks .stage > .card :is(.card-body, .card-main) > .card-proj{
    margin-bottom:6px; padding:7px 10px;
    font-size:10.5px; line-height:1.4;
  }
  .tracks .stage > .card :is(.card-body, .card-main) > .card-desc{
    margin-bottom:6px; font-size:10.5px; line-height:1.4;
  }
  .tracks .stage > .card .meta{ margin-bottom:6px; }
  .tracks .stage > .card .card-link{ margin-top:2px; padding:9px 18px; }

  .tracks .stage > .card-multi .card-head{ padding-top:6px; }
  .tracks .stage > .card-multi .card-body{ row-gap:6px; }
  .tracks .stage > .card-multi .card-aside .card-link{ margin-top:6px; }
  .tracks .stage > .card .covers-cap{ margin-top:5px; }

  /* Siemens Automation and Control */
  .tracks .stage > .card:has(> .card-body > .plc-details) .card-body > .plc-details{
    grid-template-columns:minmax(0,1fr);
    gap:5px; margin-top:2px; padding:0;
    background:none; border:0;
  }
  .tracks .stage > .card:has(> .card-body > .plc-details) .plc-details .card-proj{
    padding:6px 8px; line-height:1.35;
  }
  .tracks .stage > .card:has(> .card-body > .plc-details) :is(.meta, .card-desc){ margin-bottom:4px; }
  .tracks .stage > .card:has(> .card-body > .plc-details) .card-body > :is(.covers-cap, .covers){ display:none; }
  .tracks .stage > .card:has(> .card-body > .plc-details) .card-link{ margin-top:0; padding:8px 16px; }
}
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px) and (max-height:700px){
  .tracks .stage > .card-multi .monitor-wall{ --w:min(7.4vw, 40px); }
}
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px) and (max-height:660px){
  .tracks .stage > .card .card-media img{ height:clamp(96px, 15.5vh, 220px); }
  .tracks .stage > .card-multi .monitor-wall{ --w:min(6vw, 32px); }
}

/* ---------- The photo switches on ---------------------------------
   Each course photo comes on like a monitor powering up as its course
   arrives: the frame starts dark, a bright scan line sweeps down it, and
   the picture is uncovered behind the line - settling from a slightly
   closer zoom as it goes. It plays once, each time a course becomes the
   one on screen (.is-current, set by script.js), and starts with the
   card's own fade-in (the .45s wait in "A slow change of course").

   Transform and opacity only, so it runs on the compositor: the dark
   cover shrinks away towards the bottom (scaleY), the line moves on
   translateY, and the photo's settle is the individual `scale` property,
   which multiplies with the drift's transform instead of replacing it.
   Measured cost-neutral on a phone at 4x CPU.

   Only while script.js runs the stage (.js-swap) and motion is welcome;
   the stacked list and reduced motion show the photo as it is. */
@media (prefers-reduced-motion:no-preference){
  .tracks.js-swap .stage > .card .card-media{ isolation:isolate; }
  .tracks.js-swap .stage > .card .card-media::before,
  .tracks.js-swap .stage > .card .card-media::after{
    content:"";
    position:absolute;
    inset:0;
    z-index:2;
    pointer-events:none;
    opacity:0;
  }
  /* the dark screen, before it comes on */
  .tracks.js-swap .stage > .card .card-media::before{
    background:#07080a;
    transform-origin:50% 100%;
  }
  /* the scan line: a bright edge with a soft glow above it, drawn at the
     top of a full-height layer that travels down the frame */
  .tracks.js-swap .stage > .card .card-media::after{
    background:
      linear-gradient(180deg,
        rgba(255,214,130,0) 0, rgba(255,214,130,.18) 10px,
        rgba(255,244,214,.95) 13px, rgba(245,166,35,.7) 15px,
        rgba(245,166,35,0) 22px) no-repeat;
    background-size:100% 22px;
    transform:translateY(-22px);
  }
  .tracks.js-swap .stage > .card.is-current .card-media::before{
    animation:screen-dark .9s cubic-bezier(.55,.05,.35,1) .45s both;
  }
  .tracks.js-swap .stage > .card.is-current .card-media::after{
    animation:screen-line .9s cubic-bezier(.55,.05,.35,1) .45s both;
  }
  .tracks.js-swap .stage > .card.is-current .card-media img{
    animation:screen-settle 1.3s cubic-bezier(.2,.7,.2,1) .45s both;
  }
}
@keyframes screen-dark{
  0%   { opacity:1; transform:scaleY(1); }
  100% { opacity:1; transform:scaleY(0); }
}
@keyframes screen-line{
  0%   { opacity:0; transform:translateY(-22px); }
  6%   { opacity:1; }
  90%  { opacity:1; }
  100% { opacity:0; transform:translateY(calc(100% - 8px)); }
}
@keyframes screen-settle{
  0%   { scale:1.09; opacity:.55; }
  60%  { opacity:1; }
  100% { scale:1; opacity:1; }
}

/* ---------- The stacked list keeps its terminal strip -------------- */
@media (prefers-reduced-motion:reduce),
       (max-height:619px){
  /* The stacked list keeps its terminal strip. With no stage to sit in, the
     strip rides along the foot of the window while the list is on screen -
     sticky at the end of the list, so it stops there once the list has
     passed - on a dark pill so it reads over the cards scrolling under it.
     script.js ("The terminal strip in the stacked list") lights the course
     nearest the middle of the window, and a marker still takes you to its
     course. */
  .rail{
    position:sticky;
    left:auto; right:auto;
    bottom:calc(var(--scr-bottom, 8px) + var(--curve-b, 6px) + 4px);
    z-index:5;
    width:min(100%, calc(var(--count, 8) * 40px + 24px));
    margin:14px auto 0;
    padding:6px 12px 5px;
    border-radius:999px;
    background:rgba(14,15,17,.94);
    box-shadow:0 0 0 1px var(--line), 0 6px 18px rgba(0,0,0,.45);
  }
  .rail::before{ left:12px; right:12px; bottom:10px; }
  .rail li{ animation:none; }
  .tracks:not(.js-swap) .rail li{
    --tick:9px; --tick-color:var(--line); --num-color:var(--text-dim);
    transition:--tick .3s ease, --tick-color .3s ease, --num-color .3s ease;
  }
  .tracks:not(.js-swap) .rail li.is-current{
    --tick:16px; --tick-color:var(--accent); --num-color:var(--accent);
  }
}

/* The names on a 320px phone: the screen there is 292px wide, and eight
   names in one row came out under 1px apart at 7px. A touch smaller, and
   no tracking, gives them room. Only the narrowest phones. */
@media (max-width:340px){
  .curve-brands li{ font-size:6.5px; letter-spacing:0; }
}

/* ---------- The navbar on the smallest phones ----------------
   At 320px the logo, "Talk to Us" and the menu button do not fit one row:
   the menu button was pushed 25px past the right edge and the whole page
   scrolled sideways. Below 360px the button keeps only its WhatsApp icon,
   as a round 42px button - the words stay in the link for screen readers
   (font-size 0 hides them from sight only, and the icon has its own size).
   The gap goes too, or the empty text would still hold the icon off
   centre. */
@media (max-width:359px){
  .site-header .header-cta{
    width:42px; height:42px;
    padding:0;
    gap:0;
    font-size:0;
    border-radius:50%;
  }
  .site-header .header-cta .btn-ico{ width:20px; height:20px; margin:0; }
}

/* ---------- Course slides fit a desktop screen --------------------
   The desktop stage had the fault the phone one had: the markers along
   the foot were 57px tall and the slide's bottom padding cleared only part
   of them, so on a 1366x768 laptop "View curriculum" sat 24-67px under the
   markers, and at 1536x730 the combined programmes' text ran into them.
   The markers are shorter (38px: less padding, an 18px lit tick), sit a
   little lower, and the padding clears them. On short windows (620-700px
   tall) the gaps between a slide's blocks, the button and the space round
   the details and tags come down too; nothing is removed. Measured slide by
   slide at 1024x768, 1280x680-800, 1366x657, 1440x650-900, 1536x730-864,
   1600x700 and 1920x969: every slide clears the curve and the markers.
   Placed after the desktop stage rules so these win at equal specificity. */
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px){
  .rail button{ gap:4px; padding:4px 0 2px; }
  .rail b{ line-height:1; }
  .tracks .rail{ bottom:calc(clamp(16px, 3vh, 36px) + var(--scr-bottom)); }
  .tracks.js-swap .rail li.is-current{ --tick:18px; }
  .tracks .stage > .card{
    padding-bottom:calc(clamp(16px, 3vh, 36px) + var(--scr-bottom) + 48px);
  }
}
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px) and (max-height:700px){
  .tracks .stage > .card{ row-gap:10px; }
  .tracks .stage > .card-multi .card-head{ padding-top:8px; }
  .tracks .stage > .card .card-link{ padding-block:10px; }
  .tracks .stage > .card .meta{ margin-bottom:10px; }
  .tracks .stage > .card :is(.card-body, .card-main) > .card-desc{ margin-bottom:6px; }
  .tracks .stage > .card .plc-details{ padding:6px; margin-top:6px; }
  .tracks .stage > .card .plc-details .card-proj{ padding:5px 8px; }
  .tracks .stage > .card .covers-cap{ margin-top:6px; margin-bottom:4px; }
  .tracks .stage > .card .covers + .covers-cap{ margin-top:6px; }
  .tracks .stage > .card .covers li{ padding:3px 8px; }
}

/* ---------- Wider pictures on the combined programmes' banks -------
   The multi-brand programmes (CAE, the diplomas, the master diploma) show
   their eight platforms as a bank of monitors, and each picture was close
   to square - 134x116 at 1440px wide - and on a phone 31x27 with the
   names over them running into each other. Now:

   - On a laptop or desktop each monitor is a wider, landscape picture
     (0.56 of the facet pitch high instead of 0.80, about 1.66:1) and the
     bank always spans the full width of the card. Up to 800px tall it used
     to be drawn a fifth smaller to fit; the lower monitors give that height
     back, so the full-width bank is no taller than the smaller one was,
     and on taller screens it is about 35px shorter.
   - On a phone the curve is laid flat as two rows of four, each picture
     16:9 and about 74px wide on a 390px screen instead of 31px, with the
     maker's name above it. The "PLC SYSTEM" line under each name is left
     off there - the names alone now have room.

   Later in the sheet than the bank rules it overrides, which match at
   equal specificity. */
@media (min-width:861px){
  .tracks .card-multi .monitor-wall,
  .tracks .stage > .card-multi .monitor-wall{
    --w:calc((min(100vw - 2 * var(--scr-x), 1360px) - 68px) / 8.96);
    height:calc(var(--w) * 0.74);
  }
  .tracks .card-multi .monitor-wall li{ height:calc(var(--w) * 0.56); }
}
@media (max-width:860px){
  .tracks .card-multi .monitor-wall{
    display:grid;
    grid-template-columns:repeat(4, minmax(0, 1fr));
    column-gap:8px;
    row-gap:20px;
    width:100%;
    height:auto;
    padding-top:16px;
    perspective:none;
    transform-style:flat;
  }
  .tracks .card-multi .monitor-wall li{
    position:relative;
    top:auto; left:auto;
    width:auto; height:auto;
    margin:0;
    aspect-ratio:16 / 9;
    transform:none;
    filter:none;
  }
  .tracks .card-multi .monitor-wall li .bezel{ padding-bottom:3px; }
  .tracks .card-multi .monitor-wall .bezel .sys{ display:none; }
}

/* ---------- The combined programmes fit a short phone ----------------
   The two-row bank of monitors (above) is 60-70px taller than the curved
   one it replaced, and on phones 700px tall or less the combined slides -
   CAE and the diplomas - ran their "View curriculum" button 6-24px under
   the course markers. On those phones the bank is laid a little tighter
   (smaller gaps, pictures 2:1, and 5:2 on the smallest), and the slide's
   description, "Also covers" box and tags are set a touch tighter. Nothing
   is removed. Every slide then clears the markers at 360x640 and 375x667;
   taller phones are untouched. After the bank rules it adjusts, which
   match at equal specificity. */
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px) and (max-height:700px){
  .tracks .card-multi .monitor-wall{ row-gap:15px; column-gap:6px; padding-top:13px; }
  .tracks .card-multi .monitor-wall li{ aspect-ratio:2 / 1; }
  .tracks .stage > .card-multi :is(.card-body, .card-main) > .card-proj{ padding:6px 9px; }
  .tracks .stage > .card-multi .card-body{ row-gap:4px; }
  .tracks .stage > .card-multi :is(.card-body, .card-main) > :is(.card-desc, .card-proj){ line-height:1.38; }
}
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px) and (max-height:660px){
  .tracks .card-multi .monitor-wall{ row-gap:12px; padding-top:10px; }
  .tracks .card-multi .monitor-wall li{ aspect-ratio:5 / 2; }
  .tracks .stage > .card-multi .covers{ gap:4px; }
  .tracks .stage > .card-multi .covers li{ padding:1px 6px; }
  .tracks .stage > .card-multi :is(.card-body, .card-main) > :is(.card-desc, .card-proj){ line-height:1.32; }
  .tracks .stage > .card-multi .card-link{ padding-block:8px; }
}

/* ---------- A course photo shown whole (.card-photo-whole) ----------
   Every course photo fills a fixed frame and is cropped to its shape, then
   zoomed a little for the drift. On Siemens PLC Programming and Allen
   Bradley (Rockwell) the user wants the original picture as it is,
   nothing cut off, no zoom: the frame takes the photo's own 3:2 shape, so
   the whole picture shows, with no zoom, no drift and no zoom-in as it
   arrives. On a desktop the photo is as wide as the slide's height allows
   (the user: "keep the image wider") - its width and height follow the
   photo, capped by the column and by the height left once the slide's top
   and bottom padding, its heading and the gap are taken out - so it fills
   the column on a big screen and is never cropped on a small one. On a
   phone it keeps the phone photo's height and is centred. */
.tracks .stage > .card.card-photo-whole .card-media{
  width:fit-content;
  max-width:100%;
  justify-self:start;
}
.tracks .stage > .card.card-photo-whole .card-media img{
  width:auto;
  max-width:100%;
  aspect-ratio:3 / 2;
  object-fit:contain;
}
/* no zoom and no drift in any mode - the pinned stage (script.js drift) or
   the stacked list (the scroll-linked parallax) - so no edge is ever lost */
.tracks .stage > .card.card-photo-whole .card-media img,
.tracks.js-swap .stage > .card.card-photo-whole .card-media img{
  transform:none;
  animation:none;
}
@media (max-width:860px){
  .tracks .stage > .card.card-photo-whole .card-media{ justify-self:center; }
}
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px){
  .tracks .stage > .card.card-photo-whole .card-media img{
    width:auto;
    height:auto;
    max-width:100%;
    max-height:calc(100svh
      - max(100px, calc(var(--hdr-h) + var(--scr-top) + var(--curve) + 12px))
      - (clamp(16px, 3vh, 36px) + var(--scr-bottom) + 48px)
      - 135px);
  }
}

/* ---------- The combined programmes fit their new text ---------------
   The combined programmes took their course details from the old site - a
   list of live projects on Diploma and Advanced Diploma (four and six
   lines), longer platform tags, and two tag rows on Master - and ran
   their "View curriculum" button up to 42px under the course markers.
   On a desktop the projects list runs in two columns under its label, and
   the space round the tags and the button comes down; up to 800px tall the
   rows of the slide sit closer, and up to 700px tall the bank of monitors
   is drawn a tenth smaller. On a phone the projects list is two columns
   and a touch smaller, the tags are smaller, and the tighter bank reaches
   phones up to 760px tall. Nothing is removed. */
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px){
  .tracks .stage > .card-multi .card-main > .card-proj.proj-advanced{ columns:2; column-gap:24px; }
  .tracks .stage > .card-multi .card-main > .card-proj.proj-advanced > b{ display:block; column-span:all; }
  .tracks .stage > .card-multi .card-aside .card-link{ margin-top:12px; }
  .tracks .stage > .card-multi .card-aside .covers + .covers-cap{ margin-top:8px; }
}
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px) and (max-height:800px){
  .tracks .stage > .card-multi{ row-gap:10px; }
}
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px) and (max-height:700px){
  .tracks .card-multi .monitor-wall,
  .tracks .stage > .card-multi .monitor-wall{
    --w:calc((min(100vw - 2 * var(--scr-x), 1360px) - 68px) / 8.96 * 0.9);
  }
}
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px){
  .tracks .stage > .card-multi .card-main > .card-proj.proj-advanced{ columns:2; column-gap:10px; font-size:9.8px; line-height:1.3; }
  .tracks .stage > .card-multi .card-main > .card-proj.proj-advanced > b{ display:block; column-span:all; }
  .tracks .stage > .card-multi .covers{ gap:4px; }
  .tracks .stage > .card-multi .covers li{ font-size:9.5px; padding:2px 6px; }
}
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px) and (max-height:760px){
  .tracks .card-multi .monitor-wall{ row-gap:13px; padding-top:11px; }
  .tracks .card-multi .monitor-wall li{ aspect-ratio:5 / 2; }
}

/* ---------- The individual courses' details read larger ------------
   The course content details (the Basic / Advanced lines, the DCS bullets)
   take an h4's 16px on desktop, up from 14.5, and the same step up on
   short screens and phones. Only the individual group: the combined
   slides are fitted to their room. Every slide was measured clear of the
   rail at 1024x768-1920x1080 and on phones 360x640-430x932. */
.group-individual .tracks .card :is(.card-body, .card-main) > .card-proj{ font-size:16px; }
.group-individual .tracks .card :is(.card-body, .card-main) > .card-desc{ font-size:15px; }
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px) and (max-height:800px){
  .group-individual .tracks .stage > .card :is(.card-body, .card-main) > .card-proj{ font-size:14.5px; }
}
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px) and (max-height:700px){
  .group-individual .tracks .stage > .card :is(.card-body, .card-main) > .card-proj{ font-size:14px; }
}
@media (max-width:860px){
  .group-individual .tracks .card :is(.card-body, .card-main) > .card-proj{ font-size:14px; }
}
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px){
  .group-individual .tracks .stage > .card :is(.card-body, .card-main) > .card-proj{ font-size:12px; }
}
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px) and (max-height:700px){
  .group-individual .tracks .stage > .card :is(.card-body, .card-main) > .card-proj{ font-size:11.5px; }
}

/* ---------- Master Diploma: Basic, Advanced and IoT in one row ------
   The flagship's coverage is three blocks side by side - Basic-Tool
   Training, Advanced (Rockwell ControlLogix) and IoT - instead of two tag
   rows stacked in the right-hand column. On the pinned desktop stage the
   description sits on the left of the top row with "View curriculum" on
   the right, and the three blocks span the full width under them, so the
   slide is no taller than it was. On a phone the blocks keep one row as
   three narrow columns. Tags that wrap to two lines get rounded corners
   rather than a pill, which would balloon into an oval. */
.cover-blocks{
  display:grid;
  grid-template-columns:minmax(0,1fr) auto minmax(0,1.25fr);
  column-gap:clamp(14px, 2vw, 28px);
}
.cover-block{
  min-width:0;
  padding-left:clamp(10px, 1.2vw, 16px);
  border-left:1px solid var(--line);
}
.tracks .card .cover-block .covers-cap{ margin-top:0; }
/* IoT's own Advanced step: the label and its tag share one line */
.cover-sub{
  display:flex; flex-wrap:wrap; align-items:center;
  column-gap:10px; row-gap:4px;
  margin-top:7px;
}
.tracks .card .cover-sub .covers-cap{ margin:0; }
.cover-block .covers li{ border-radius:12px; }
.covers-cap.lvl-iot{ color:#c4acf8; }
/* CAE, Diploma and Advanced Diploma use the same row: Basic and Advanced
   side by side (CAE has a Basic block only), the real-time projects as a
   list with a blue dot before each, two columns on a desktop. */
/* the projects take the wider share, so each one fits on a single line */
.cover-blocks.pair{ grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr); }
.cover-blocks.single{ grid-template-columns:minmax(0,1fr); }
.cover-block .proj-list{
  margin:0; padding:0; list-style:none;
  columns:2; column-gap:24px;
  font-size:13px; line-height:1.55;
  color:#c9ced5;
}
.cover-block .proj-list li{ position:relative; padding-left:12px; break-inside:avoid; }
.cover-block .proj-list li::before{
  content:"";
  position:absolute; left:0; top:.62em;
  width:5px; height:5px;
  border-radius:50%;
  background:#8cc6fa;
}
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px){
  .tracks .card-multi .card-body.has-blocks{
    grid-template-columns:minmax(0,1fr) auto;
    row-gap:16px;
  }
  .tracks .card-multi .card-body.has-blocks > .card-main{ grid-column:1; grid-row:1; }
  .tracks .card-multi .card-body.has-blocks > .cover-blocks{ grid-column:1 / -1; grid-row:2; }
  .tracks .card-multi .card-body.has-blocks > .card-link{
    grid-column:2; grid-row:1;
    align-self:end; justify-self:end;
    margin:0;
  }
}
/* On a phone each column is about 90-140px wide, where every tag wrapped
   inside its own pill and the IoT column ran the slide 59px past the
   course markers on a 360x640 screen. There the entries are a plain list,
   a dot in the tag's colour before each, and the Advanced label sits over
   its entry. */
@media (max-width:860px){
  .cover-blocks{
    grid-template-columns:minmax(0,1fr) minmax(min-content,.6fr) minmax(0,1.7fr);
    column-gap:10px;
  }
  /* the dividers cost the narrow columns 21px of text width */
  .cover-block{ padding-left:0; border-left:0; }
  .tracks .card .cover-block .covers-cap{ letter-spacing:.08em; margin-bottom:3px; }
  .cover-block .proj-list{ columns:1; font-size:10px; line-height:1.3; }
  .cover-block .proj-list li{ margin-top:2px; padding-left:8px; }
  .cover-block .proj-list li::before{ top:.5em; width:4px; height:4px; }
  /* CAE's one block has the full width, so its tags stay tags */
  .tracks .card .cover-blocks:not(.single) .covers{ display:block; }
  .tracks .card .cover-blocks:not(.single) .covers li{
    position:relative;
    margin-top:2px; padding:0 0 0 8px;
    line-height:1.25;
    border:0; border-radius:0; background:none;
  }
  .tracks .card .cover-blocks:not(.single) .covers li::before{
    content:"";
    position:absolute; left:0; top:.5em;
    width:4px; height:4px;
    border-radius:50%;
    background:currentColor;
  }
  .cover-sub{ display:block; margin-top:8px; }
}
/* Phones 700px tall or less: the lists set a touch smaller and closer,
   which brings Master and Advanced Diploma clear of the course markers
   on a 360x640 screen. */
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px) and (max-height:700px){
  .tracks .card .cover-block .covers-cap{ font-size:8.5px; }
  .tracks .card .cover-blocks:not(.single) .covers li{ margin-top:1px; font-size:9px; line-height:1.2; }
  .cover-block .proj-list{ font-size:9.5px; line-height:1.25; }
  .cover-block .proj-list li{ margin-top:1px; }
}

/* ---------- Every individual course photo the same size -------------
   Siemens PLC and Allen Bradley show their photos whole (.card-photo-whole,
   above), and next to them the other courses' photos looked a different
   size - wider, shorter, cropped and zoomed. The user: "keep same size".
   So every individual course now uses the same frame: 3:2, as tall as the
   slide allows on a desktop (the phone photo's height on a phone), with
   no zoom and no drift. A 3:2 photo - Siemens, Delta, Mitsubishi, Allen
   Bradley, SCADA - shows whole; a wider one (DCS, ABB, Omron, Schneider,
   older 2.6:1 photos) fills the frame with its sides trimmed. The combined
   programmes are not affected. */
.tracks-flat .stage > .card:nth-of-type(n) .card-media{
  width:fit-content;
  max-width:100%;
  justify-self:start;
}
.tracks-flat .stage > .card .card-media img{
  width:auto;
  max-width:100%;
  aspect-ratio:3 / 2;
  object-fit:cover;
}
.tracks-flat .stage > .card .card-media img,
.tracks.js-swap.tracks-flat .stage > .card .card-media img{
  transform:none;
  animation:none;
}
@media (max-width:860px){
  .tracks-flat .stage > .card:nth-of-type(n) .card-media{ justify-self:center; }
}
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px){
  .tracks-flat .stage > .card .card-media img{
    width:auto;
    height:auto;
    max-width:100%;
    max-height:calc(100svh
      - max(100px, calc(var(--hdr-h) + var(--scr-top) + var(--curve) + 12px))
      - (clamp(16px, 3vh, 36px) + var(--scr-bottom) + 48px)
      - 135px);
  }
}

/* ---------- The photo column fits the photo ---------------------------
   With every photo the same 3:2 size (above), a photo is often narrower
   than its grid column, and the spare width opened an empty strip between
   the photo and the details - 187px on Delta at 1536x730. The photo's
   column is now as wide as the photo, capped at the share it had before
   (50.6%), so the details sit one gap from it and take the freed width. */
@media (min-width:861px){
  .tracks-flat .stage > .card:nth-of-type(odd){ grid-template-columns:fit-content(50.6%) minmax(0,1fr); }
  .tracks-flat .stage > .card:nth-of-type(even){ grid-template-columns:minmax(0,1fr) fit-content(50.6%); }
  /* ABB, Omron and Schneider have one short block: it starts a little
     below the photo's top edge rather than level with it */
  .tracks-flat .stage > .card:has(> .card-body > .card-proj.proj-plain:not(.proj-steps)) > .card-body{ padding-top:clamp(18px, 4.5vh, 44px); }
}

/* ---------- Siemens DCS: two photos side by side (.card-photo-pair) --
   The DCS course shows two photos - the engineering station and the
   PCS 7 operator station - side by side and the same size (the user:
   "keep the two siemens dcs and dcs same", "make side by side"). They
   share one frame, split by a thin line of its ground, and each keeps
   the 3:2 shape of the other course photos. Two 3:2 photos need twice
   the width of one, so on a desktop the photo column may take up to 64%
   of the slide, which the short list of steps beside it can spare; where
   even that is too narrow for the full height, both photos come down
   together. The engineering station is aligned to its right edge so its
   "DCS Engineering Station" sign stays in view. */
.tracks-flat .stage > .card.card-photo-pair .card-media{
  display:grid;
  grid-template-columns:repeat(2, minmax(0, 1fr));
  gap:8px;
}
.tracks-flat .stage > .card.card-photo-pair .card-media img{ width:100%; }
.tracks-flat .stage > .card.card-photo-pair .card-media img:first-child{ object-position:100% 50%; }
@media (min-width:861px){
  .tracks-flat .stage > .card.card-photo-pair:nth-of-type(odd){ grid-template-columns:fit-content(64%) minmax(0,1fr); }
  .tracks-flat .stage > .card.card-photo-pair:nth-of-type(even){ grid-template-columns:minmax(0,1fr) fit-content(64%); }
}

/* ---------- Master Diploma: a bank of seven -------------------------
   The Master bank shows seven screens (the user took Rockwell out), and
   the curve at the top of the sheet is solved for eight: seven in those
   slots left an empty facet at the right-hand end. .wall-7 lays seven on
   the same cylinder - the same 13.5deg step and the same radius, 4.2539
   facet pitches - centred on the middle screen. The middle screen sits
   deepest in the curve and is darkest, as on the eight. The pitch is
   wider - the width over 7.60, not 8.96 - so the seven span the card
   edge to edge like the eight on the other diplomas (the user: "course
   width clear"). That makes the bank about 19px taller, which the Master
   slide gives back up to 800px tall with a tighter gap under its
   description and between its tags.
   Only on the curve (861px and wider): on a phone the bank is a grid,
   and the phone's transform:none matches these at equal specificity.
   There the seven sit as four over three, the three centred. */
@media (min-width:861px){
  .monitor-wall.wall-7 li:nth-child(1){ transform:translate3d(calc(var(--w) * -2.76269), 0, calc(var(--w) * 1.01920)) rotateY(40.5deg); }
  .monitor-wall.wall-7 li:nth-child(2){ transform:translate3d(calc(var(--w) * -1.93124), 0, calc(var(--w) * 0.46363)) rotateY(27deg); }
  .monitor-wall.wall-7 li:nth-child(3){ transform:translate3d(calc(var(--w) * -0.99311), 0, calc(var(--w) * 0.11754)) rotateY(13.5deg); }
  .monitor-wall.wall-7 li:nth-child(4){ transform:none; }
  .monitor-wall.wall-7 li:nth-child(5){ transform:translate3d(calc(var(--w) * 0.99311), 0, calc(var(--w) * 0.11754)) rotateY(-13.5deg); }
  .monitor-wall.wall-7 li:nth-child(6){ transform:translate3d(calc(var(--w) * 1.93124), 0, calc(var(--w) * 0.46363)) rotateY(-27deg); }
  .monitor-wall.wall-7 li:nth-child(7){ transform:translate3d(calc(var(--w) * 2.76269), 0, calc(var(--w) * 1.01920)) rotateY(-40.5deg); }

  .monitor-wall.wall-7 li:nth-child(4){ filter:brightness(.74); }
  .monitor-wall.wall-7 li:nth-child(3),
  .monitor-wall.wall-7 li:nth-child(5){ filter:brightness(.80); }
  .monitor-wall.wall-7 li:nth-child(2),
  .monitor-wall.wall-7 li:nth-child(6){ filter:brightness(.90); }
  .monitor-wall.wall-7 li:nth-child(1),
  .monitor-wall.wall-7 li:nth-child(7){ filter:none; }
  .monitor-wall.wall-7 li:hover{ filter:brightness(1.12); }

  /* +1 class over the bank pitch in "Wider pictures on the combined
     programmes' banks", which sizes it for eight */
  .tracks .card-multi .monitor-wall.wall-7,
  .tracks .stage > .card-multi .monitor-wall.wall-7{
    --w:calc((min(100vw - 2 * var(--scr-x), 1360px) - 68px) / 7.60);
  }
}
/* up to 700px tall the banks are drawn a tenth smaller; so is this one */
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px) and (max-height:700px){
  .tracks .card-multi .monitor-wall.wall-7,
  .tracks .stage > .card-multi .monitor-wall.wall-7{
    --w:calc((min(100vw - 2 * var(--scr-x), 1360px) - 68px) / 7.60 * 0.9);
  }
}
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px) and (max-height:800px){
  .tracks .card-flagship.card-multi .card-body.has-blocks{ row-gap:8px; }
  .tracks .card-flagship .cover-sub{ margin-top:4px; }
  .tracks .card-flagship .cover-block .covers{ gap:5px; }
}
@media (max-width:860px){
  /* eight half-columns, each screen spanning two, so the second row of
     three can start half a screen in */
  .tracks .card-multi .monitor-wall.wall-7{ grid-template-columns:repeat(8, minmax(0, 1fr)); }
  .tracks .card-multi .monitor-wall.wall-7 li{ grid-column:span 2; }
  .tracks .card-multi .monitor-wall.wall-7 li:nth-child(5){ grid-column:2 / span 2; }
}

/* ---------- Master Diploma: a DCS block after Advanced ---------------
   Master's row is four blocks - Basic-Tool Training | Advanced | DCS |
   IoT (the user: "next advance keep the dcs"). The DCS block carries the
   DCS course's three stages as green pills like the other blocks (the
   user: "keep the dcs also pills shape"); up to 800px tall Master's pills
   are 10px so "Engineering station - logic development" stays on one
   line at 1280 wide. On a desktop the four sit in one row; Advanced's
   single tag may wrap so IoT keeps two rows of tags, and IoT's own
   Advanced label stays beside its tag. On a phone four columns would be
   70px each, so DCS sits under Advanced in the middle column, which was
   mostly empty.
   The block captions on all the combined cards also read larger (the
   user: "font size basic and advance increase"): 12px on desktop (from
   10), 9.5px on phones (from 9), 9px up to 700px tall (from 8.5).
   Fitting it: up to 800px tall Master's tags are a touch smaller and its
   bank is drawn smaller (0.86, and 0.78 up to 700px tall); on phones up
   to 700px tall its bank pictures are 3:1 and its lists sit closer, and
   up to 660px tall its title is 15px so it takes two lines, not three.
   Measured with every combined slide clear of the rail at 1024x768 to
   1920x1080 and on phones 360x640 to 390x844 (least room: 2px, Advanced
   Diploma at 360x640). After "a bank of seven", whose rules these
   override at equal specificity. */
.covers-cap.lvl-dcs{ color:#74dfae; }
.cover-block .dcs-list{ columns:1; }
.cover-block .dcs-list li::before{ background:#3ecf8e; }
.cover-blocks.four > :nth-child(1){ grid-area:basic; }
.cover-blocks.four > :nth-child(2){ grid-area:adv; }
.cover-blocks.four > :nth-child(3){ grid-area:dcs; }
.cover-blocks.four > :nth-child(4){ grid-area:iot; }
.cover-blocks.four{
  grid-template-columns:minmax(0,.95fr) auto minmax(0,.9fr) minmax(0,1.6fr);
  grid-template-areas:"basic adv dcs iot";
  column-gap:clamp(12px, 1.4vw, 20px);
}
@media (min-width:861px){
  .tracks .card .cover-block .covers-cap{ font-size:12px; }
  .cover-blocks.four .cover-sub{ flex-wrap:nowrap; align-items:flex-start; }
  .cover-blocks.four .cover-sub .covers-cap{ padding-top:5px; flex:none; }
  .cover-blocks.four .cover-sub .covers{ min-width:0; }
  .tracks .card-flagship .cover-block .covers{ gap:5px; }
  .tracks .card-flagship .cover-block .covers li{ font-size:11px; padding:4px 9px; }
  .tracks .card-flagship .cover-block .dcs-list{ font-size:12.5px; line-height:1.5; }
  /* Advanced's tags one under the other, each on one line (the user:
     "rockwell controllogix in one row", "advance keep one by one");
     its column is as wide as "Rockwell ControlLogix" */
  .cover-blocks.four > :nth-child(2) .covers{ flex-direction:column; align-items:flex-start; }
  .cover-blocks.four > :nth-child(2) .covers li{ white-space:nowrap; }
}
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px) and (max-height:800px){
  /* the bank full width, from the title's left edge to the content's
     right (the user, on a 1536x730 screen: "start from the course title") */
  .tracks .stage > .card-multi .monitor-wall.wall-7{ --w:calc((min(100vw - 2 * var(--scr-x), 1360px) - 68px) / 7.60); }
  .tracks .card-flagship .cover-block .covers li{ font-size:10px; padding:3px 8px; }
  .tracks .card-flagship .cover-block .dcs-list{ font-size:12px; line-height:1.45; }
  /* the top row was 81px for 59px of pills and description */
  .tracks .stage > .card-flagship .card-main > .meta{ margin-bottom:8px; }
  .tracks .stage > .card-flagship .card-main > .card-desc{ margin:0; }
}
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px) and (max-height:700px){
  .tracks .stage > .card-multi .monitor-wall.wall-7{ --w:calc((min(100vw - 2 * var(--scr-x), 1360px) - 68px) / 7.60 * 0.9); }
}
@media (max-width:860px){
  .cover-blocks.four{
    grid-template-columns:minmax(0,.85fr) minmax(0,1fr) minmax(0,1.35fr);
    grid-template-rows:auto 1fr;
    grid-template-areas:"basic adv iot" "basic dcs iot";
    column-gap:10px;
  }
  .cover-blocks.four > :nth-child(3){ margin-top:8px; }
  .tracks .card .cover-block .covers-cap{ letter-spacing:.04em; }
  /* the longer "Advanced-Real time application" caption fits one line */
  .cover-blocks.pair{ grid-template-columns:minmax(0,.7fr) minmax(0,1.3fr); }
}
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px){
  .tracks .card .cover-block .covers-cap{ font-size:9.5px; }
}
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px) and (max-height:700px){
  .tracks .card .cover-block .covers-cap{ font-size:9px; }
  .tracks .card-multi .monitor-wall.wall-7 li{ aspect-ratio:3 / 1; }
  .tracks .stage > .card-flagship h3{ font-size:17px; }
  .tracks .card-flagship .cover-blocks:not(.single) .covers li{ line-height:1.15; }
  .tracks .card-flagship .cover-block .proj-list{ line-height:1.2; }
}
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px) and (max-height:660px){
  .tracks .stage > .card-flagship h3{ font-size:15px; }
  .tracks .card .cover-block .proj-list{ line-height:1.2; }
  .tracks .card .cover-block .proj-list li{ margin-top:0; }
  .tracks .card .cover-blocks:not(.single) .covers li{ line-height:1.15; }
}

/* ---------- Siemens Automation and Control: PLC and DCS side by side --
   The first combined course named its two parts only - "Siemens PLC",
   "DCS" in two small boxes. It now carries each course's own details
   (the user: "keep the plc course details and dcs course details"), in
   the same row the other combined courses use: Siemens PLC (amber) and
   Siemens DCS (green) side by side, each a list with a dot in its colour,
   two equal columns. It sits in the text column beside the photo; the
   slide stays clear of the rail with 48px or more on a desktop and 15px
   on a 360x640 phone. */
.cover-block .plc-list{ columns:1; }
.cover-block .plc-list li::before{ background:#f5a623; }
.cover-blocks.pair.even{ grid-template-columns:repeat(2, minmax(0,1fr)); }
.tracks .card:not(.card-multi) .card-body > .cover-blocks{ margin:14px 0 4px; }
/* On a phone the PLC list (three lines since "SIMATIC Manager, WinCC and
   TIA Portal." joined it) takes the wider share: with equal columns it
   wrapped to 120px and left 4px of room at 360x640; at 1.1:0.9 it is
   98px, the DCS list does not grow, and the room is back to 15px (12px
   at 375x667). */
@media (max-width:860px){
  .cover-blocks.pair.even{ grid-template-columns:minmax(0,1.1fr) minmax(0,.9fr); }
}

/* ---------- The Basic / Advanced labels read larger -----------------
   "Basic-Tool Training" and "Advanced-Real time application" over each
   individual course's details: 13px on desktop (from 10.5), 12px up to
   800px tall, 10-11px on phones (from 9). Measured: no label wraps, and
   every slide stays clear of the rail (least room 12px, 375x667). */
.group-individual .tracks .card :is(.card-body, .card-main) > .card-proj b{ font-size:13px; margin-bottom:7px; }
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px) and (max-height:800px){
  .group-individual .tracks .stage > .card :is(.card-body, .card-main) > .card-proj b{ font-size:12px; margin-bottom:5px; }
}
@media (max-width:860px){
  .group-individual .tracks .card :is(.card-body, .card-main) > .card-proj b{ font-size:11px; letter-spacing:.1em; }
}
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px){
  .group-individual .tracks .stage > .card :is(.card-body, .card-main) > .card-proj b{ font-size:10.5px; letter-spacing:.1em; margin-bottom:2px; }
}
@media (prefers-reduced-motion:no-preference) and (max-width:860px) and (min-height:620px) and (max-height:700px){
  .group-individual .tracks .stage > .card :is(.card-body, .card-main) > .card-proj b{ font-size:10px; }
}

/* ---------- The combined banks reach the title on short windows -----
   On a window 700px tall or less the banks were drawn a tenth smaller so
   the slide would fit, which left them 64px short of the course title on
   each side. They now span title to edge there too, and the height that
   costs is given back from the copy above them. The flagship's seven
   screens are the widest set: below 668px tall they still cannot reach
   the edge, so they keep a small margin rather than overflow. */
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px) and (max-height:700px){
  .tracks .card-multi .monitor-wall,
  .tracks .stage > .card-multi .monitor-wall{ --w:calc((min(100vw - 2 * var(--scr-x), 1360px) - 68px) / 8.96); }
  .tracks .card-multi .monitor-wall.wall-7,
  .tracks .stage > .card-multi .monitor-wall.wall-7{ --w:calc((min(100vw - 2 * var(--scr-x), 1360px) - 68px) / 7.60); }
  .tracks .stage > .card-multi .meta{ margin-bottom:6px; }
  .tracks .stage > .card-multi :is(.card-body, .card-main) > .card-desc{ margin-bottom:6px; line-height:1.45; }
  .tracks .stage > .card-multi .cover-block .covers li{ padding:2px 8px; font-size:10px; }
  .tracks .stage > .card-multi .cover-blocks{ row-gap:8px; }
  .tracks .stage > .card-multi .card-body{ row-gap:6px; }
}
@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px) and (max-height:685px){
  .tracks .stage > .card-multi .cover-blocks.four .covers li{ padding:2px 7px; font-size:9.5px; }
  .tracks .stage > .card-multi .cover-blocks.four .covers{ gap:5px; }
  .tracks .stage > .card-multi .cover-blocks.four .covers-cap{ margin-bottom:2px; font-size:10px; }
  .tracks .card-multi .monitor-wall.wall-7,
  .tracks .stage > .card-multi .monitor-wall.wall-7{ --w:calc((min(100vw - 2 * var(--scr-x), 1360px) - 68px) / 7.60 * 0.93); }
}

@media (prefers-reduced-motion:no-preference) and (min-width:861px) and (min-height:620px) and (max-height:650px){
  .tracks .stage > .card-multi .cover-block .covers li{ padding:2px 7px; font-size:9.5px; }
  .tracks .stage > .card-multi .cover-block .covers{ gap:5px; }
  .tracks .card-multi .monitor-wall,
  .tracks .stage > .card-multi .monitor-wall{ --w:calc((min(100vw - 2 * var(--scr-x), 1360px) - 68px) / 8.96 * 0.88); }
  .tracks .card-multi .monitor-wall.wall-7,
  .tracks .stage > .card-multi .monitor-wall.wall-7{ --w:calc((min(100vw - 2 * var(--scr-x), 1360px) - 68px) / 7.60 * 0.84); }
  .tracks .stage > .card-multi .cover-blocks{ row-gap:6px; }
  .tracks .stage > .card-multi .card-body{ row-gap:4px; }
}

/* ---------- Blueprint ground (end of sheet) ------------------
   This sits at the very bottom on purpose. `.panel{background:var(--bg)}`
   further up is the shorthand, which resets background-image to none, so
   anywhere earlier these grid layers were simply wiped. */
/* One fixed layer for the whole grid, rather than the same background on
   every panel with background-attachment:fixed.

   Painting it per element was the correct-looking way to do it and the
   wrong-performing one: attachment:fixed detaches the background from the
   element's own box, so the browser has to repaint it on every scroll
   frame. Measured over a 70-notch scroll it cost p50 29.2ms / p95 80.7ms
   with 70 of 131 frames past 20ms - about 34fps. As a single fixed,
   composited overlay the same grid runs p50 16.6ms with no long frames.

   It sits above the panels rather than behind them - the panels are opaque
   and would hide it otherwise - but at 3% it reads as surface either way,
   and pointer-events:none keeps it out of the way of the monitors. */
body::after{
  content:"";
  position:fixed;
  inset:0;
  z-index:30;
  pointer-events:none;
  background-image:
    linear-gradient(rgba(245,166,35,.030) 1px, transparent 1px),
    linear-gradient(90deg, rgba(245,166,35,.030) 1px, transparent 1px);
  background-size:72px 72px;
}

/* The two power rails a ladder diagram is drawn between, ghosted in
   down the outer edges of the page. */
.panel::after{
  content:"";
  position:absolute; inset:0;
  pointer-events:none;
  z-index:0;
  background:
    linear-gradient(90deg,
      transparent 0, transparent 38px,
      rgba(245,166,35,.10) 38px, rgba(245,166,35,.10) 39px,
      transparent 39px),
    linear-gradient(270deg,
      transparent 0, transparent 38px,
      rgba(245,166,35,.10) 38px, rgba(245,166,35,.10) 39px,
      transparent 39px);
}
.panel > *{ position:relative; z-index:1; }

@media (max-width:860px){
  /* The rails stay - 38px of inset is a tenth of a phone screen, so they
     come in to sit just off the gutter instead. */
  .panel::after{
    background:
      linear-gradient(90deg,
        transparent 0, transparent 13px,
        rgba(245,166,35,.10) 13px, rgba(245,166,35,.10) 14px,
        transparent 14px),
      linear-gradient(270deg,
        transparent 0, transparent 13px,
        rgba(245,166,35,.10) 13px, rgba(245,166,35,.10) 14px,
        transparent 14px);
  }
  body::after{ background-size:48px 48px; }
}

