:root{
  /* One navy. --navy-deep is kept as a name because fifty-odd rules and two gradients read it,
     but it now resolves to the same value: the brand asked for a single blue, not a pair. Measured
     on the new value — white 15.21:1, gold #fab900 8.69:1, accent-soft 12.76:1 — so everything
     that sits on it reads better than it did on the lighter of the two it replaces. */
  --navy:#0A2450; --navy-deep:var(--navy); --gold:#fab900; --gold-deep:#DCA300;
  --paper:#F7F8FC; --white:#fff; --ink:#16203A; --muted:#5A6480; --line:#DEE3F0; --line-input:#767F9C;
  --ok:#1E7F5C; --warn:#B4690E; --display:'Sora',system-ui,sans-serif; --body:'Inter',system-ui,sans-serif;
  --r:14px; --max:70rem; --shadow:0 14px 34px rgba(10,36,80,.09);
  /* settings.theme hooks: layout.mjs injects overrides for these three after this stylesheet.
     Every consumer below reads the *-safe derived variable, never the raw one, so a value typed
     straight into Supabase — bypassing the build's own clamp — can never break layout or push a
     touch target or a contrast pair below its floor. */
  --text-scale:1; --btn-scale:1; --section-gap:1;
  --text-scale-safe:clamp(0.9, var(--text-scale,1), 1.25);
  --btn-scale-safe:clamp(0.9, var(--btn-scale,1), 1.3);
  --gap-scale-safe:clamp(0.75, var(--section-gap,1), 1.5);
  /* Per-element overrides (document.layout -> src/lib/overrides.mjs -> a <style> block emitted
     after this sheet). Same discipline as the three above and for the same reason: the injected
     value lands on --el-size / --el-space, every consumer below reads the derived *-safe variable,
     and the bounds are therefore enforced by the stylesheet as well as by sanitiseLayout(). These
     defaults make every element the owner has not touched a no-op multiply by 1. */
  --el-size:1; --el-space:1; --el-size-safe:1; --el-space-safe:1;
}
/* Re-derived on every marked element, so it resolves against THAT element's own --el-size and is
   inherited from there by its text. The bounds are the schema's LAYOUT_LIMITS. */
[data-cms-field]{--el-size-safe:clamp(0.6, var(--el-size,1), 1.8);--el-space-safe:clamp(0.5, var(--el-space,1), 3)}
*{box-sizing:border-box}
/* textScale lives on the root as a percentage of the *inherited* value (the visitor's own browser
   font-size setting), never an absolute px — so raising it never overrides a visitor who has
   already set a larger default. scroll-padding and the sticky header's height scale with it so a
   jump-link target never lands underneath the header at a larger text size. */
html{scroll-behavior:smooth;font-size:calc(100% * var(--text-scale-safe));scroll-padding:calc(88px * var(--text-scale-safe)) 0 calc(90px * var(--text-scale-safe))}
body{margin:0;background:var(--paper);color:var(--ink);font-family:var(--body);font-size:1.0625rem;line-height:1.65;-webkit-font-smoothing:antialiased;overflow-wrap:break-word}
/* No hyphens:auto here. overflow-wrap already stops a long word overflowing its column; automatic
   hyphenation went further and broke display headings wherever it improved the ragged edge, which
   on a 54px h1 with room to spare read as "In-house training built on the consulta-tive method".
   Hyphenation is reinstated below 600px, where a single long word really can be wider than the
   column and a hyphen is kinder than a hard mid-word break. */
/* `anywhere`, not `break-word`. Both break a word that cannot fit; only `anywhere` also reduces
   the element's min-content width, and min-content is what a flex item (.prog h2 holds its
   programme name as an anonymous flex item, which no selector can reach) and an `auto` grid track
   are sized from. With break-word a 54px heading at size 180% grew its own container to 436px
   inside a 276px column. Identical rendering at every size the design actually uses. */
h1,h2,h3,h4{font-family:var(--display);color:var(--navy);line-height:1.15;letter-spacing:-.02em;margin:0 0 calc(.5em * var(--el-space-safe,1));overflow-wrap:anywhere}
@media(max-width:600px){body{hyphens:auto}}
h1{font-size:calc(clamp(2.1rem,5vw,3.4rem) * var(--el-size-safe,1));font-weight:700}
h2{font-size:calc(clamp(1.5rem,3vw,2.1rem) * var(--el-size-safe,1));font-weight:600}
h3{font-size:calc(1.08rem * var(--el-size-safe,1));font-weight:600;letter-spacing:0}
h4{font-size:calc(.95rem * var(--el-size-safe,1));font-weight:600;letter-spacing:0}
p{margin:0 0 calc(1em * var(--el-space-safe,1))}
/* Text with no size rule of its own still has to answer to a `size` override — its own, or one on
   a marked ancestor. 1em is the inherited size, so at the default factor of 1 this computes to
   exactly what these elements already computed. Element specificity, so every class rule in this
   sheet still wins. */
p,li,dd,dt,td,th,figcaption,blockquote,summary,caption{font-size:calc(1em * var(--el-size-safe,1))}
img,svg{max-width:100%}
/* text-decoration-color is currentColor, not the accent: navy text on navy-ish surrounding copy
   has ~1.2:1 contrast against its own paragraph, so the underline is the only visible cue a body
   link exists — it must never depend on how light the accent yellow happens to be. */
a{color:var(--navy);text-decoration-color:currentColor;text-underline-offset:3px}
.wrap{max-width:var(--max);margin-inline:auto;padding:0 22px}
/* sectionGap scales the vw term and the ceiling only; the 46px floor is fixed so a very low
   setting (or a tall-zoomed, short viewport) never crushes a section down to nothing. */
section.wrap{padding-block:clamp(calc(46px * var(--el-space-safe,1)),calc(7vw * var(--gap-scale-safe) * var(--el-space-safe,1)),calc(84px * var(--gap-scale-safe) * var(--el-space-safe,1)))}
.band>.wrap,.cta-band>.wrap,.creed-band>.wrap{padding-block:clamp(calc(46px * var(--el-space-safe,1)),calc(7vw * var(--gap-scale-safe) * var(--el-space-safe,1)),calc(84px * var(--gap-scale-safe) * var(--el-space-safe,1)))}
.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.skip{position:absolute;left:-999px}.skip:focus{left:12px;top:12px;background:var(--navy);color:#fff;padding:10px 14px;border-radius:8px;z-index:99}
:where(a,button,input,select,textarea,summary,[tabindex]):focus-visible{outline:3px solid var(--gold);outline-offset:2px;box-shadow:0 0 0 2px var(--navy),0 0 0 8px var(--navy)}
/* header */
.site{position:sticky;top:0;z-index:40;background:rgba(247,248,252,.92);backdrop-filter:blur(10px);border-bottom:1px solid var(--line);transition:box-shadow .3s ease,background .3s ease}
.site.stuck{box-shadow:0 6px 22px rgba(17,45,94,.08);background:rgba(255,255,255,.95)}
.bar{display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:calc(70px * var(--text-scale-safe))}
.logo{text-decoration:none;display:flex;flex-direction:column;line-height:1.1}
.mark{font-family:var(--display);font-weight:700;color:var(--navy);letter-spacing:.06em;font-size:calc(1.25rem * var(--el-size-safe,1))}
.sub{font-size:.66rem;color:var(--muted);letter-spacing:.04em;max-width:240px}
.nav{display:flex;align-items:center}
.nav a{margin-left:20px;text-decoration:none;color:var(--ink);font-weight:500;font-size:calc(.95rem * var(--el-size-safe,1));position:relative}
.nav a:not(.btn)::after{content:"";position:absolute;left:0;right:100%;bottom:-4px;height:2px;background:var(--amber);transition:right .25s ease}
.nav a:not(.btn):hover::after,.nav a:not(.btn)[aria-current]::after{right:0}
.burger{display:none;background:none;border:1px solid var(--line);border-radius:10px;padding:9px 11px;cursor:pointer;min-height:max(24px,calc(38px * var(--btn-scale-safe)));min-width:max(24px,calc(38px * var(--btn-scale-safe)))}
.burger span{display:block;width:20px;height:2px;background:var(--navy);margin:4px 0;transition:transform .25s ease,opacity .25s ease}
.burger[aria-expanded="true"] span:nth-child(1){transform:translateY(6px) rotate(45deg)}
.burger[aria-expanded="true"] span:nth-child(2){opacity:0}
.burger[aria-expanded="true"] span:nth-child(3){transform:translateY(-6px) rotate(-45deg)}
@media(max-width:820px){
  .burger{display:block}
  /* The panel hangs off a position:sticky header, so its containing block is pinned to the
     viewport: page scrolling can never bring an item below the fold into view. At 400% zoom the
     panel is taller than the viewport, so it must scroll itself or the last links become
     keyboard-unreachable (WCAG 1.4.10 Reflow, 2.1.1 Keyboard). The subtracted height is the
     header's own, which scales with textScale, plus its 1px border. */
  .nav{position:absolute;top:100%;left:0;right:0;background:var(--white);border-bottom:1px solid var(--line);flex-direction:column;align-items:stretch;padding:8px 22px 18px;gap:2px;display:none;box-shadow:var(--shadow);max-height:calc(100vh - calc(70px * var(--text-scale-safe)) - 1px);max-height:calc(100dvh - calc(70px * var(--text-scale-safe)) - 1px);overflow-y:auto;overscroll-behavior:contain}
  .nav.open{display:flex;animation:drop .25s ease}
  .nav a{margin:0;padding:12px 0;border-bottom:1px solid var(--line);display:flex;align-items:center;min-height:24px}
  .nav a.btn{margin-top:12px;text-align:center;border-bottom:2px solid var(--navy)}
}
@keyframes drop{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:none}}
/* buttons */
/* Smaller by default (Simon: "the button takes up too much space") — composed, not a slab.
   buttonScale then multiplies these smaller bases, and the touch-target floors further down keep
   every control at or above the 24px minimum however low it is set. */
/* line-height is stated, not inherited. A <button> takes line-height:normal from the UA sheet
   while an <a> inherits the body's 1.65, so the identical .btn class rendered 6.8px shorter on a
   button than on a link — visible on every page, where the header's "Download brochure" button
   (34px) sat beside the "Contact" link pill (39.7px), and again in the programme plate where a
   button and a link stack directly on top of each other. 1.65 is the value the links already
   computed, so this only ever raises the buttons to meet them. */
/* Reduced again on Simon's second pass ("this is very big button. हम इसको छोटा कर सकते हैं"):
   10/18 padding at .9rem/1.65 measured 49.2px tall at the default scale, now 7/15 at .85rem/1.5
   for 39.7px — a fifth off the resting height with the shape and weight unchanged.
   min-height is the floor every other control in this file already has and .btn did not. It is the
   only interactive control sized purely by font-size and padding, and schema.mjs promises the
   owner in the admin UI that "small controls such as the gallery dots never shrink below the
   minimum touch size, however low you set this" — that promise now covers the buttons too. At the
   0.9 slider minimum the natural height is 34.2px and the floor is 34.2px, so nothing changes
   until something else shrinks; at 24px it stops dead (WCAG 2.5.8). inline-flex is what makes
   min-height centre its text rather than pad the bottom. */
/* --el-size-safe inherits from the .cta-slot wrapper, so selecting a button on the canvas and
   pressing Bigger scales it. The site-wide --btn-scale-safe still applies on top; the
   max(24px, ...) floors are computed from both, so neither can breach the target minimum. */
.btn{display:inline-flex;align-items:center;justify-content:center;background:var(--navy);color:#fff;padding:calc(7px * var(--btn-scale-safe) * var(--el-size-safe,1)) calc(15px * var(--btn-scale-safe) * var(--el-size-safe,1));min-height:max(24px,calc(38px * var(--btn-scale-safe) * var(--el-size-safe,1)));min-width:max(24px,calc(38px * var(--btn-scale-safe) * var(--el-size-safe,1)));border-radius:999px;text-decoration:none;font-weight:600;font-size:calc(.85rem * var(--btn-scale-safe) * var(--el-size-safe,1));line-height:1.5;border:2px solid var(--navy);transition:transform .2s ease,background .2s ease,box-shadow .2s ease;cursor:pointer;font-family:var(--body);text-align:center}
.btn:hover{background:var(--navy-deep);transform:translateY(-2px);box-shadow:0 10px 22px rgba(10,36,80,.18)}
.btn.ghost{background:transparent;color:var(--navy)}
.btn.ghost:hover{background:var(--gold);border-color:var(--gold);color:var(--navy)}
.btn.gold{background:var(--gold);border-color:var(--gold);color:var(--navy-deep)}
.btn.gold:hover{background:var(--gold-deep);border-color:var(--gold-deep);color:var(--navy-deep)}
.btn.small{padding:calc(5px * var(--btn-scale-safe)) calc(12px * var(--btn-scale-safe));min-height:max(24px,calc(30px * var(--btn-scale-safe)));min-width:max(24px,calc(30px * var(--btn-scale-safe)));font-size:calc(.79rem * var(--btn-scale-safe))}
.btn[disabled],.btn[aria-disabled="true"]{opacity:.6;pointer-events:none}
.link{font-weight:600;text-decoration:none;border-bottom:2px solid currentColor;padding-bottom:1px}
/* hero */
.hero{position:relative;overflow:hidden;padding-top:clamp(40px,6vw,72px);padding-bottom:clamp(30px,5vw,56px)}
@keyframes float{from{transform:translate3d(0,0,0) scale(1)}to{transform:translate3d(-24px,18px,0) scale(1.06)}}
/* align-items:center is safe only because the art column has a fixed reserved height. If the
   gallery is ever switched to a collapsing frame this must become `start`: centring against a
   column whose height changes on a 5s timer moves the h1, the lede and both call-to-action
   buttons, and a pointer resting on a CTA is not over .gallery-rot, so the gallery's hover-pause
   never fires and the button slides out from under the cursor mid-click. */
.hero-grid{position:relative;z-index:1;display:grid;grid-template-columns:1.05fr .95fr;gap:38px;align-items:center}
/* No CSS `order` here. The gallery follows the <h1> in the DOM and it follows it on screen at
   every width: it holds four real buttons (previous, next, the dots, pause), and painting it above
   a heading it comes after in source put those controls out of tab order below 860px — SC 2.4.3.
   Moving it in the template rather than in the stylesheet is the same rule this whole feature is
   built on: DOM order is reading order is tab order. */
@media(max-width:860px){.hero-grid{grid-template-columns:1fr}}
.hero-art svg{width:100%;height:auto;display:block}
.hero-art .pulse{animation:pulse 2.6s ease-in-out infinite}
@keyframes pulse{0%,100%{r:9}50%{r:12}}
.eyebrow{font-size:.78rem;letter-spacing:.16em;text-transform:uppercase;color:var(--muted);font-weight:600}
.hero h1{max-width:17ch}
.lede{font-size:calc(1.14rem * var(--el-size-safe,1));color:var(--muted);max-width:60ch}
.hero .eyebrow,.hero .lede,.hero .hero-photo figcaption{color:#3F4862}
/* A button is an <a>, which may not carry a hotspot and may not contain the injected edit
   button, so each editable one is wrapped in a .cta-slot span. Inline everywhere else, so it is
   invisible to layout; a flex item here, where it has to reproduce the stretch the <a> had. */
.cta-row{display:flex;gap:12px;flex-wrap:wrap;margin:calc(26px * var(--el-space-safe,1)) 0 0}
.cta-row>.cta-slot{display:flex;min-width:0}
.cta-row>.cta-slot>.btn{flex:1 1 auto}
.sec-head>.head-link{display:inline-flex;min-width:0}
/* Flex, not grid, and deliberately so. The hairlines are the --line container showing through a
   1px gap, which only works if every track is filled: an auto-fit grid whose item count is not a
   multiple of the column count leaves bare tracks, and those tracks render as a slab of raw grey
   right across the strip. Wrapping flex items grow to fill whatever row they land in, so the last
   row is always complete and the container is never visible as anything but a 1px rule. */
.facts{display:flex;flex-wrap:wrap;gap:1px;background:var(--line);border:1px solid var(--line);border-radius:var(--r);overflow:hidden;margin:40px 0 0;position:relative;z-index:1}
.facts>div{flex:1 1 150px;min-width:0;background:var(--white);padding:calc(18px * var(--el-space-safe,1)) calc(20px * var(--el-space-safe,1))}
.facts dt{font-size:calc(.76rem * var(--el-size-safe,1));letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.facts dd{margin:4px 0 0;font-family:var(--display);font-weight:600;font-size:calc(1.3rem * var(--el-size-safe,1));color:var(--navy)}
/* creed */
.creed-band{background:var(--navy);color:#fff}
.creed{font-family:var(--display);font-size:calc(clamp(1.15rem,2.4vw,1.7rem) * var(--el-size-safe,1));line-height:1.4;max-width:34ch;margin:0;color:#fff}
/* bands & cards */
.band{background:var(--white);border-block:1px solid var(--line)}
.sec-head{display:flex;justify-content:space-between;align-items:baseline;gap:16px;flex-wrap:wrap;margin-bottom:calc(28px * var(--el-space-safe,1))}
.two{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(280px,100%),1fr));gap:20px}
.card-plain{background:var(--white);border:1px solid var(--line);border-left:4px solid var(--gold);border-radius:var(--r);padding:calc(24px * var(--el-space-safe,1))}
.card-plain p{color:var(--muted);margin:0}
.grid{display:grid;gap:calc(18px * var(--el-space-safe,1))}
.grid.two{grid-template-columns:repeat(auto-fit,minmax(min(300px,100%),1fr))}
/* Equal rows, not just equal columns: a six-card grid whose second row was 26px shorter than its
   first read as two different card sizes stacked, because a card's height is set by whichever
   summary in its row happens to be longest. */
.grid.three{grid-template-columns:repeat(3,1fr);grid-auto-rows:1fr}
@media(max-width:900px){.grid.three{grid-template-columns:repeat(2,1fr)}}
@media(max-width:600px){.grid.three{grid-template-columns:1fr}}
.grid.four{grid-template-columns:repeat(auto-fit,minmax(min(220px,100%),1fr))}
.grid article{background:var(--white);border:1px solid var(--line);border-radius:var(--r);padding:calc(22px * var(--el-space-safe,1));transition:box-shadow .25s ease,border-color .25s ease,transform .25s ease}
.grid article:hover{box-shadow:var(--shadow);border-color:#C9D2E8;transform:translateY(-3px)}
.grid article p{color:var(--muted);margin:0;font-size:calc(.97rem * var(--el-size-safe,1))}
.band .grid article{background:var(--paper)}
.pcard{display:flex;flex-direction:column;gap:10px}
.pcard .link{align-self:flex-start;margin-top:6px}
/* ladder */
.ladder{list-style:none;margin:0;padding:0;border-top:1px solid var(--line)}
.ladder li{display:flex;gap:24px;padding:calc(26px * var(--el-space-safe,1)) 0;border-bottom:1px solid var(--line);transition:padding-left .25s ease}
.ladder li:hover{padding-left:8px}
.num{font-family:var(--display);font-weight:700;color:var(--gold);font-size:calc(1.15rem * var(--el-size-safe,1));min-width:34px}
.ladder p{color:var(--muted);max-width:62ch;margin-bottom:.6em}
/* table */
.curriculum{width:100%;border-collapse:collapse;background:var(--white);border:1px solid var(--line);border-radius:var(--r);overflow:hidden}
.curriculum caption{text-align:left;color:var(--muted);font-size:calc(.9rem * var(--el-size-safe,1));padding-bottom:10px}
.curriculum th,.curriculum td{text-align:left;padding:calc(14px * var(--el-space-safe,1)) calc(16px * var(--el-space-safe,1));border-bottom:1px solid var(--line);vertical-align:top;font-size:calc(.96rem * var(--el-size-safe,1))}
.curriculum thead th{background:var(--navy);color:#fff;font-family:var(--display);font-weight:600;font-size:.85rem;letter-spacing:.04em}
.curriculum tbody th{font-weight:600;color:var(--navy);width:34%}
.curriculum tbody tr:hover td,.curriculum tbody tr:hover th{background:#FBFCFF}
@media(max-width:640px){
  .curriculum thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
  .curriculum tbody th,.curriculum td{display:block;width:auto;border-bottom:0}
  .curriculum.compare tbody th{width:auto}
  .curriculum tbody tr{display:block;border-bottom:1px solid var(--line);padding:6px 0}
  /* The cells are block-level here, but the TABLE still sizes its columns from their content, so a
     `size` override on a cell dragged the whole table past the viewport. Fixed layout ignores
     content width; there is only one column left to lay out at this width anyway. */
  .curriculum{table-layout:fixed;width:100%}
  .curriculum tbody [data-label]::before{content:attr(data-label) / "";display:block;font-size:calc(.72rem * var(--el-size-safe,1));font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);margin-bottom:2px}
}
/* program page */
/* The heading text is an anonymous flex item, so its default min-width:auto stops it
   shrinking below its longest word and pushes the page wider at a raised root font-size. */
.prog h2{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap;min-width:0}
.prog h2>*{min-width:0}
.prog ul{color:var(--muted);padding-left:20px}
.prog li{margin-bottom:.4em}
/* The page header owns no bottom padding — the section under it supplies the whole gap, so every
   inner page opens on the same rhythm. That only holds if the last child's bottom margin cannot
   escape: with padding-bottom 0 and no border, a trailing <p class="lede"> collapsed its 1em out
   through the section and added a stray 18px on about, corporate and contact but not on programmes
   or a programme page, where the last child happens to be a div or a figure. */
section.page-head{padding-bottom:0}
.page-head>:last-child{margin-bottom:0}
.crumbs{font-size:.85rem;color:var(--muted);margin-bottom:10px}
.crumbs a{text-decoration:none}
/* Same construction as .facts, and for the same reason: a programme has seven spec rows and six
   fit across at 1280, so an auto-fit grid left five empty tracks of bare --line grey beside the
   Coach cell. Wrapping flex items fill their row. */
.spec{display:flex;flex-wrap:wrap;gap:1px;background:var(--line);border:1px solid var(--line);border-radius:var(--r);overflow:hidden;margin:0 0 30px}
.spec>div{flex:1 1 160px;min-width:0;background:var(--white);padding:calc(16px * var(--el-space-safe,1)) calc(18px * var(--el-space-safe,1))}
/* These cells sit inside .facts/.spec, which carry overflow:hidden to keep their rounded
   corners. Without a break rule any word wider than the ~136px cell is sliced off — which
   is what the client saw. It needs no long word and no CMS change: at a raised browser
   font-size the shipped certificate line already clips. */
.facts dt,.facts dd,.spec dt,.spec dd{overflow-wrap:break-word;min-width:0}
.spec dt{font-size:calc(.74rem * var(--el-size-safe,1));letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.spec dd{margin:4px 0 0;font-family:var(--display);font-weight:600;color:var(--navy);font-size:calc(1.02rem * var(--el-size-safe,1))}
.split{display:grid;grid-template-columns:1.4fr .6fr;gap:32px;align-items:start}
@media(max-width:860px){.split{grid-template-columns:1fr}}
.checks{list-style:none;padding:0;margin:0}
.checks li{position:relative;padding-left:30px;margin-bottom:calc(.7em * var(--el-space-safe,1));color:var(--muted)}
.checks li::before{content:"";position:absolute;left:0;top:.45em;width:16px;height:16px;border-radius:50%;background:var(--gold)}
.checks li::after{content:"";position:absolute;left:5px;top:.72em;width:5px;height:9px;border:2px solid var(--navy-deep);border-top:0;border-left:0;transform:rotate(42deg)}
/* coach */
/* Top-aligned, not centred. The portrait plate is as tall as its content, and its content is a
   photograph the owner may not have uploaded yet — centring then parked a 150px stub in the middle
   of a 580px column with 215px of nothing above and below it. Aligned to the start, the plate's
   top edge lines up with the section heading whether or not there is a photograph in it. */
/* "ये दोनों blend हो रहे हैं आपस में... There is no partition." The seam between "Who the programmes
   are for" (on the #F7F8FC page) and "Meet your coach" (a white .band) was a 1.06:1 background step
   with a 1px --line divider at 1.21:1 on paper — invisible, and it does not survive greyscale or
   prefers-contrast either. Thickening --line does not help: 2px of #DEE3F0 is still 1.21:1 and 3px
   of #C9D2E8 only 1.43:1. --line-input #767F9C is 3.74:1 on paper and 3.97:1 on white and is
   already in the palette, so the rule itself now carries the partition.
   The band stays white rather than taking a tint: --ink is CMS-editable up to L=0.17, which is
   4.77:1 on white and 4.57:1 on paper but drops below 4.5:1 on any tint light enough to read as a
   tint, so a coloured surface here would put a whole section of body copy at risk at the extremes
   of the theme sliders.
   Padding is ~1.3x the shared section rhythm on both edges, which is the space Simon asked for
   above "Meet your coach" and below the "More about ISPD and the coaches" button. */
.band.coach-sec{border-block:1px solid var(--line-input)}
.coach{display:grid;grid-template-columns:.8fr 1.2fr;gap:34px;align-items:start}
@media(max-width:760px){.coach{grid-template-columns:1fr}}
.portrait{background:var(--navy);border-radius:var(--r);padding:26px;color:#fff}
.portrait svg{display:block;margin-inline:auto;max-width:220px}
.portrait p{color:rgba(255,255,255,.8);margin:14px 0 0;font-size:calc(.92rem * var(--el-size-safe,1))}
/* testimonials */
.quotes{position:relative}
.quote{background:var(--white);border:1px solid var(--line);border-radius:var(--r);padding:calc(26px * var(--el-space-safe,1));display:none}
.quote.active{display:block;animation:fade .45s ease}
@keyframes fade{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
.quote blockquote{margin:0;max-width:70ch;font-family:var(--display);font-size:calc(1.1rem * var(--el-size-safe,1));color:var(--navy);line-height:1.5}
.quote figcaption{margin-top:14px;color:var(--muted);font-size:calc(.9rem * var(--el-size-safe,1))}
.dots{display:flex;gap:8px;margin-top:16px;justify-content:center}
/* max(24px, …) is a hard floor: buttonScale can only ever grow this control, never shrink it
   below the 24px minimum target size (WCAG 2.5.8), however low the slider is set. */
.dots button{width:max(24px,calc(24px * var(--btn-scale-safe)));height:max(24px,calc(24px * var(--btn-scale-safe)));border-radius:50%;border:0;background:transparent;cursor:pointer;padding:0;position:relative}
.dots button::before{content:"";position:absolute;inset:7px;border-radius:50%;background:var(--line-input)}
/* #D94A12, not the accent. The selected dot is a UI component boundary at 3:1: the retired orange #F66123
   is 3.16:1 on white and would drop to 2.97:1 the moment this section moved off a .band to the
   page background. #D94A12 is 3.72:1 on white and 3.50:1 on paper, so it holds on either. */
.dots button[aria-selected="true"]::before{background:#D94A12;inset:5px}
/* faq */
.faq{border-top:1px solid var(--line)}
.faq details{border-bottom:1px solid var(--line)}
.faq summary{cursor:pointer;min-height:24px;padding:18px 0;font-family:var(--display);font-weight:600;color:var(--navy);list-style:none;display:flex;justify-content:space-between;gap:16px}
.faq summary>*{min-width:0}
.faq summary::-webkit-details-marker{display:none}
/* The alternative-text token keeps generated glyphs out of the accessible name: Chromium folds
   ::before/::after content in, so this "+" was landing in every FAQ toggle's name ("... ?  +") on
   eight pages, and <details> already exposes expanded state natively. Same technique as the
   responsive-table data-labels above. */
.faq summary::after{content:"";flex:none;align-self:center;width:.95rem;height:.95rem;background:linear-gradient(currentColor,currentColor) center/100% 2px no-repeat,linear-gradient(currentColor,currentColor) center/2px 100% no-repeat;color:var(--navy);transition:transform .25s ease}
.faq details[open] summary::after{transform:rotate(45deg)}
.faq p{color:var(--muted);padding-bottom:16px;margin:0;max-width:70ch}
/* cta band */
.cta-band{background:var(--navy);color:#fff}
.cta-band h2,.cta-band p{color:#fff}
.cta-band p{color:rgba(255,255,255,.82);max-width:56ch}
.cta-band .btn{background:var(--gold);border-color:var(--gold);color:var(--navy-deep)}
.cta-band .btn:hover{background:#fff;border-color:#fff}
.cta-band .btn.ghost{background:transparent;border-color:rgba(255,255,255,.5);color:#fff}
.cta-band .btn.ghost:hover{background:#fff;color:var(--navy)}
/* forms */
.contact-grid{display:grid;grid-template-columns:1.25fr .75fr;gap:30px;align-items:start}
@media(max-width:820px){.contact-grid{grid-template-columns:1fr}}
form{background:var(--white);border:1px solid var(--line);border-radius:var(--r);padding:26px}
.field{margin-bottom:16px;display:flex;flex-direction:column}
.field label{font-size:.86rem;font-weight:600;color:var(--navy);margin-bottom:6px}
input,select,textarea{font-family:var(--body);font-size:1rem;min-height:max(24px,calc(44px * var(--btn-scale-safe)));padding:12px 14px;border:1px solid var(--line-input);border-radius:10px;background:var(--paper);color:var(--ink);transition:border-color .2s ease,box-shadow .2s ease}
input:focus,select:focus,textarea:focus{border-color:var(--navy)}
.field.invalid input,.field.invalid select{border:2px solid #C2410C;background:#FFF7F3}
.err{color:#C2410C;font-size:.82rem;margin-top:5px}
.form-status{margin:12px 0 0;font-weight:600;color:var(--ok);min-height:1.2em}
.form-status.bad{color:#C2410C}
.note{font-size:calc(.84rem * var(--el-size-safe,1));color:var(--muted);margin:calc(8px * var(--el-space-safe,1)) 0 0}
.plate{background:var(--navy);color:#fff;border-radius:var(--r);padding:calc(26px * var(--el-space-safe,1))}
.plate h2{color:#fff;font-size:calc(1.2rem * var(--el-size-safe,1))}
.contact-list{margin:0 0 20px}
.contact-list>div{padding:calc(12px * var(--el-space-safe,1)) 0;border-bottom:1px solid rgba(255,255,255,.16)}
.contact-list dt{font-size:calc(.74rem * var(--el-size-safe,1));letter-spacing:.09em;text-transform:uppercase;color:var(--gold)}
.contact-list dd{margin:4px 0 0;color:rgba(255,255,255,.9);font-size:calc(.95rem * var(--el-size-safe,1))}
/* Same tap enlargement as the footer contacts: a 45px target without moving the text; the ring stays inside the padded box so it does not cut the label above. */
.contact-list dd a{display:inline-block;padding-block:13px;margin-block:-13px}
.contact-list dd a:focus-visible{outline-offset:-3px}
.contact-list a{color:#fff}
.plate .btn.ghost{border-color:rgba(255,255,255,.6);color:#fff}
.plate .btn.ghost:hover{background:var(--gold);border-color:var(--gold);color:var(--navy)}
/* misc */
/* The strip clips its own scrolling track on .marquee-viewport, never on .marquee itself: an
   overflow:hidden here would crop the Pause button's focus halo (it needs 8px of clearance and the
   control gutter drops to 6px padding under 480px). Layout, background and the control's position
   are all set in the keyword-strip block further down; nothing but the animation lives here. */
.marquee-track{display:flex;gap:46px;white-space:nowrap;width:max-content;animation:slide 26s linear infinite;color:var(--muted);font-size:calc(.85rem * var(--el-size-safe,1));letter-spacing:.12em;text-transform:uppercase;font-weight:600}
@keyframes slide{from{transform:translateX(0)}to{transform:translateX(-50%)}}
.marquee-toggle{min-width:72px;min-height:32px;padding:4px 14px;border:1px solid var(--line-input);border-radius:999px;background:var(--white);color:var(--navy);font:600 .78rem var(--body);cursor:pointer}
.wa-float{position:fixed;right:18px;bottom:18px;z-index:39;background:#25D366;color:#04310f;border-radius:999px;padding:12px 18px;font-weight:700;text-decoration:none;box-shadow:0 10px 26px rgba(0,0,0,.18);font-size:.9rem;display:inline-flex;gap:8px;align-items:center;transition:transform .2s ease}
.wa-float:hover{transform:translateY(-3px)}
.wa-float:focus-visible{box-shadow:0 0 0 2px var(--navy),0 0 0 8px var(--navy),0 10px 26px rgba(0,0,0,.18)}
.btn:hover:focus-visible{box-shadow:0 0 0 2px var(--navy),0 0 0 8px var(--navy),0 10px 22px rgba(10,36,80,.18)}
/* breathing room before the footer, whatever section happens to end the page */
/* Breathing room above the footer must be padding INSIDE it, not an outside margin:
   the band above is dark too, and a margin exposed a pale stripe of --paper between them. */
.site-foot{background:var(--navy-deep);color:rgba(255,255,255,.75)}
.site-foot .wrap{padding-block:clamp(52px,calc(6vw * var(--gap-scale-safe)),calc(84px * var(--gap-scale-safe))) 38px}
.foot-grid{display:grid;grid-template-columns:1.2fr 1fr 1fr;gap:24px;align-items:start}
/* The phone, WhatsApp and email links sit inline in a sentence, so SC 2.5.8's inline exception
   covers them at their natural 17px. That is conformant and still a poor tap: they are the whole
   point of the footer for a business that sells by phone. inline-block plus vertical padding takes
   the target to ~44px without breaking the "·"-separated run, and the negative margin keeps the
   paragraph the same height it was, so nothing below it moves. */
.foot-grid p a{display:inline-block;padding-block:13px;margin-block:-13px}
@media(max-width:760px){.foot-grid{grid-template-columns:1fr}}
.site-foot .mark{color:#fff}
.site-foot p{margin:0 0 calc(6px * var(--el-space-safe,1));font-size:calc(.9rem * var(--el-size-safe,1))}
.site-foot nav{display:flex;flex-direction:column;gap:8px}
.site-foot a{color:rgba(255,255,255,.8);text-decoration:none;font-size:calc(.9rem * var(--el-size-safe,1))}
.site-foot a:hover{color:var(--gold)}
.site-foot p a,.site-foot .social a{text-decoration:underline;text-decoration-color:rgba(255,255,255,.5);text-underline-offset:3px}
.fine{grid-column:1/-1;border-top:1px solid rgba(255,255,255,.14);padding-top:16px;font-size:calc(.8rem * var(--el-size-safe,1));color:rgba(255,255,255,.55)}
/* reveal */
/* An animation, not a transition, for the same reason .pop and .paper are animations: transition
   is a single shorthand and .reveal.in (0,2,0) beat .grid article (0,1,1), so the whole list was
   replaced and a card's hover ran "opacity, transform" at .6s instead of "box-shadow,
   border-color, transform" at .25s — the lift crawled and the shadow and border snapped with no
   transition at all. app.js drops both classes on animationend, so nothing is left holding the
   transition property once the entrance has played. */
.reveal{opacity:0;transform:translateY(18px)}
.reveal.in{animation:rise-in .6s cubic-bezier(.2,.7,.3,1) both}
@keyframes rise-in{from{opacity:0;transform:translateY(18px)}to{opacity:1;transform:none}}
.reveal:focus-within{opacity:1;transform:none;transition:none}
.count{font-variant-numeric:tabular-nums}
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important;scroll-behavior:auto!important}
  .reveal{opacity:1;transform:none}
}

/* ===== additions for the data-driven templates (Sep 2026) ===== */
/* contrast fixes: the accent is never used for small text on white */
.num{color:#8A5A00}
.nav a[aria-current]{color:var(--navy)}
.link:hover{color:var(--amber);border-color:var(--amber)}
/* header logo image */
.logo img{height:44px;width:auto;display:block}
/* hero photo: landscape (3:2), not square — a square frame on a wide group photo crops people out
   at the edges. object-position (set inline per image from the CMS focal point, default 50% 50%)
   keeps whoever matters in frame instead of just the centre. */
.hero-photo{margin:0;position:relative}
.hero-photo img{display:block;width:100%;height:auto;aspect-ratio:3/2;object-fit:cover;border-radius:var(--r);box-shadow:var(--shadow)}
.hero-photo figcaption{margin-top:10px;font-size:.85rem;color:var(--muted)}
.hero .trust{margin-top:30px;position:relative;z-index:1}
.hero .trust+.facts{margin-top:22px}
.page-head .hero-photo{margin-top:28px}
.page-head .hero-photo img{aspect-ratio:2/1}
/* trust pills */
.trust{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:8px}
.trust li{background:var(--white);border:1px solid var(--line);border-radius:999px;padding:calc(6px * var(--el-space-safe,1)) calc(14px * var(--el-space-safe,1));font-size:calc(.84rem * var(--el-size-safe,1));font-weight:600;color:var(--navy)}
.site-foot .trust{grid-column:1/-1}
.site-foot .trust li{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.2);color:rgba(255,255,255,.9)}
/* coaches */
.coach-card{background:var(--white);border:1px solid var(--line);border-radius:var(--r);padding:calc(22px * var(--el-space-safe,1))}
.coach-card img{display:block;width:100%;max-width:320px;aspect-ratio:4/5;object-fit:cover;border-radius:10px;margin-bottom:16px}
.coach-card .role{color:var(--muted);font-size:calc(.95rem * var(--el-size-safe,1));margin:-.3em 0 .7em}
.coach-card .creds,.coach .creds{padding-left:20px;margin:calc(10px * var(--el-space-safe,1)) 0 calc(18px * var(--el-space-safe,1));color:var(--muted);font-size:calc(.95rem * var(--el-size-safe,1))}
.coach-card .creds li,.coach .creds li{margin-bottom:.3em}
.band .coaches article{background:var(--white)}
/* The coach list is CMS-driven, so it is one, two, three or more people. A fixed three-column
   track left an empty third of the row whenever there were two of them; auto-fit collapses the
   tracks it cannot fill, so however many there are they always span the full width. */
.grid.coaches{grid-template-columns:repeat(auto-fit,minmax(min(17rem,100%),1fr))}
/* Natural height, no 4/5 crop, for the same reason the hero gallery no longer crops: his portrait
   is 1067x1600 (0.667) and a 4/5 box cropped 17% of it away — off the top and bottom, which on a
   head-and-shoulders shot is exactly the complaint. object-fit stays so the CMS focal point still
   applies if a container ever does constrain this. */
.portrait img{display:block;width:100%;max-width:320px;height:auto;object-fit:cover;border-radius:10px;margin-inline:auto}
.portrait .name{color:#fff;font-family:var(--display);font-weight:600;font-size:calc(1.1rem * var(--el-size-safe,1));margin:16px 0 0;text-align:center}
/* The 16px is the gap under the portrait photograph. A coach whose photo the owner has not
   uploaded yet has no photograph, and the margin then sat as dead space at the top of the plate,
   pushing the name off-centre against the 26px padding below it. */
.portrait .name:first-child{margin-top:0}
.portrait .name+p{margin-top:4px;text-align:center}
/* story */
.story-photo{margin:0 0 22px}
.story-photo img{display:block;width:100%;height:auto;aspect-ratio:3/2;object-fit:cover;border-radius:var(--r);box-shadow:var(--shadow)}
/* The measure belongs to the paragraphs, not to the section. .founder shares its element with
   .wrap, so a max-width here shrank the whole section box and — because .wrap is margin-auto
   centred — pulled the founder letter 174px inboard of every other section's left edge. */
.founder>*{max-width:72ch}
.signature{font-family:var(--display);font-weight:600;color:var(--navy);margin-top:1.4em}
/* testimonials */
.quote.single{display:block}
.quote figure{margin:0}
.quote blockquote p{margin:0}
.quote .avatar{width:56px;height:56px;border-radius:50%;object-fit:cover;display:block;margin-bottom:12px}
.quote-program{color:var(--navy);font-weight:500}
/* fee block (white inset so the struck price keeps contrast inside the navy plate) */
.fee{display:inline-flex;flex-wrap:wrap;align-items:baseline;gap:4px 14px;background:#fff;border-radius:10px;padding:10px 14px;margin-top:4px;color:var(--navy)}
.fee-original{color:#5F6370;font-size:calc(.95rem * var(--el-size-safe,1))}
.fee-original s{text-decoration-thickness:2px}
.fee-offer{font-family:var(--display);font-weight:700;font-size:calc(1.4rem * var(--el-size-safe,1));color:var(--navy);line-height:1.2}
/* breadcrumbs */
.crumbs ol{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:4px 8px}
.crumbs li+li::before{content:"›" / "";margin-right:8px;color:var(--muted)}
.crumbs a{color:var(--navy);text-decoration:underline;text-decoration-color:currentColor;display:inline-flex;align-items:center;min-height:24px}
.crumbs a:hover{text-decoration-color:var(--gold-deep)}
/* tables */
/* The table clips to its rounded corners and the caption shares that box: a hair of side padding keeps a
   glyph with a negative bearing (the "I" of "ISPD") from losing its left edge. */
.curriculum caption{caption-side:top;text-align:left;font-family:var(--display);font-weight:600;font-size:calc(.95rem * var(--el-size-safe,1));color:var(--navy);padding:0 3px calc(10px * var(--el-space-safe,1))}
/* Scoped to the width where the table is still a table. Unscoped, this rule sits later in the
   file than the responsive `width:auto` inside the max-width:640px block and beats it at equal
   specificity, so on a phone — where every cell is display:block and stacked — the programme
   name was pinned to a 103px column while the cells under it ran the full 344px. */
@media(min-width:641px){.curriculum.compare tbody th{width:30%}}
.curriculum.compare tbody th a{text-decoration-color:currentColor}
.two.lists{margin-top:8px}
.two.lists h2{font-size:calc(1.2rem * var(--el-size-safe,1))}
/* faq with headings inside summary */
.faq summary h3{margin:0;font:inherit;color:inherit;letter-spacing:0;line-height:inherit}
.faq .answer{color:var(--muted);padding-bottom:calc(16px * var(--el-space-safe,1));max-width:70ch}
.faq .answer p{padding-bottom:0;margin:0 0 .8em}
.faq .answer p:last-child{margin-bottom:0}
.faq .answer ul{padding-left:20px;margin:0 0 .8em}
/* form hints */
.hint{font-size:.84rem;color:var(--muted);margin:-2px 0 6px}
.field .opt{font-weight:400;color:var(--muted)}
.field.invalid textarea{border:2px solid #C2410C}
/* footer extras */
.foot-title{color:#fff;font-size:calc(.95rem * var(--el-size-safe,1));margin:0 0 6px;letter-spacing:.02em}
.site-foot .social{list-style:none;padding:0;margin:12px 0 0;display:flex;gap:14px;flex-wrap:wrap}
.site-foot .social li{margin:0}
/* WhatsApp float icon */
.wa-float svg{flex:none}
/* programmes page sections */
.prog .checks{margin:0 0 6px}
.prog .cta-row{margin-top:18px}
/* header button: .nav a would otherwise override the button's white text */
.nav a.btn{color:#fff}
.nav a.btn:hover{color:#fff}
/* The last nav item renders as a button, so it is excluded from the ::after underline rule that
   marks the current page on the plain links — on contact.html it carried aria-current with no
   visible cue at all. It gets the same underline metaphor, drawn on the label itself: a shape, not
   a colour swap, so the state survives greyscale and forced-colours (white on navy, 13.45:1). */
.nav a.btn[aria-current]{text-decoration:underline;text-decoration-color:currentColor;text-decoration-thickness:2px;text-underline-offset:4px}
/* keep the enquiry form inside narrow viewports: long <select> options must not set the min-content width */
/* Every other flex and grid container on the site gets the same treatment, for the same reason and
   for one more: an `auto` track (1fr is minmax(auto,1fr)) is at least as wide as its item's
   min-content, and a per-element `size` override of 180% on top of textScale 125% makes a single
   long word wider than a 276px column at 320px. Without this the column grows instead of the word
   wrapping, and the whole page scrolls sideways — measured at 533px on the home page.
   overflow-wrap:break-word does NOT prevent that: it breaks a line when one is already too narrow,
   but it does not reduce the intrinsic min-content width the track is sized from. */
.contact-grid>*,.split>*,.grid>*,.two>*,.coach>*,.foot-grid>*,.hero-grid>*,.ladder li>div,.icon-checks li,.icon-checks li>span{min-width:0}
.field input,.field select,.field textarea{width:100%;min-width:0;max-width:100%}
/* accessibility audit additions (Sep 2026) */
.site-foot nav a{padding:2px 0;display:inline-flex;align-items:center;min-height:24px}
@media (forced-colors: active){.burger span{background:ButtonText}.nav a:not(.btn)::after{background:LinkText}}
main:focus{outline:none}

/* =====================================================================================
   Warmth pass (Sep 2026), rebased onto the true logo yellow (Sep 2026 refinement):
   hero gallery, programme cards, brochure dialog, and the tinted "accent-soft" band/pill
   background driven by settings.theme.accentSoft. Every text pairing below was recomputed for
   the current values:
     navy #0A2450 on accent-soft #FDF3D0 13.69:1 · amber #8A5300 on accent-soft 5.70:1 · amber on white 6.33:1
     navy #0A2450 on accent #fab900 8.69:1 · white on navy 15.21:1 · one navy since 7 Sep 2026
     amber #705300 on accent-soft 6.02:1 · accent #fab900 on white 1.51:1 (never used for text or a required boundary)
   accentSoft has a luminance floor (checked in layout.mjs before it is ever emitted) so amber,
   muted and amber all keep working on top of whatever an admin sets it to.
   ===================================================================================== */
:root{--accent-soft:var(--gold);--accent-soft-line:var(--gold-deep);--amber:#705300;--ease-out:cubic-bezier(.2,.7,.3,1);--ease-pop:cubic-bezier(.34,1.56,.64,1)}

/* ---- eyebrows, headings, small highlights ---- */
.eyebrow{display:inline-block;max-width:100%;overflow-wrap:break-word;background:var(--accent-soft);color:var(--navy);padding:calc(6px * var(--el-space-safe,1)) calc(12px * var(--el-space-safe,1));border-radius:999px;font-size:calc(.72rem * var(--el-size-safe,1));letter-spacing:.14em;margin:0 0 calc(14px * var(--el-space-safe,1));line-height:1.3}
.hero .eyebrow{color:var(--navy)}
/* Section-heading underline rules: Simon signed off on these already and asked explicitly that
   they not move when the accent swaps to the new brand yellow, so they're pinned to the old
   value rather than reading --gold. */
.sec-head h2::after,.founder h2::after,.journey-sec h2:not(.visually-hidden)::after{content:"";display:block;width:56px;height:5px;border-radius:3px;background:var(--gold);margin-top:12px}
.sec-head{align-items:end}
.facts>div{border-top:3px solid var(--gold)}
.trust li{background:var(--accent-soft);border-color:var(--accent-soft-line)}
.trust li::before{content:"";display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--navy);margin-right:8px;vertical-align:1px}
.site-foot .trust li::before{background:var(--gold)}
.card-plain{border-left:0;border-top:5px solid var(--gold);box-shadow:0 4px 14px rgba(17,45,94,.05)}
.creed::before{content:"\201C" / "";display:block;color:var(--gold);font-size:calc(3.4rem * var(--el-size-safe,1));line-height:.55;margin-bottom:14px;font-family:Georgia,serif}
.plate{border-top:5px solid var(--gold)}
/* No ring around the portrait. Simon: "ये frame कैसा आ रहा है? ... I don't need a frame here. No
   frame at all." Nothing replaces it — the photograph is not a control, carries no state and needs
   no boundary, and his portrait is shot on plain white, which is 13.45:1 against the navy plate on
   its own. A compensating hairline would just be the frame again in a thinner weight. */
.quote{border-left:4px solid var(--gold)}
.foot-title{color:var(--gold)}
.spec>div{border-top:3px solid var(--gold)}
.link{transition:color .2s ease,border-color .2s ease}
.grid article:not(.pcard){position:relative}
.grid article:not(.pcard)::after{content:"";position:absolute;left:0;top:18px;bottom:18px;width:4px;border-radius:0 4px 4px 0;background:var(--gold);transform:scaleY(0);transform-origin:top;transition:transform .3s var(--ease-out)}
.grid article:not(.pcard):hover::after,.grid article:not(.pcard):focus-within::after{transform:scaleY(1)}

/* ---- "Why choose ISPD": navy band, yellow headings (7.2:1), popped cards ---- */
.why-sec{background:var(--navy);border:0;color:#fff}
.why-sec h2,.why-sec .lede{color:#fff}
.why-sec .lede{color:rgba(255,255,255,.86)}
.band.why-sec .grid article{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.16);box-shadow:none}
.band.why-sec .grid article:hover{background:rgba(255,255,255,.1);border-color:rgba(255,255,255,.3)}
.why-sec .grid article h3{color:var(--gold);padding-left:0}
/* No rule above the card headings. Simon kept the gold rule under a section <h2> ("Why choose
   ISPD") and cut the small one that sat above every <h3> card heading: "यहाँ पे नहीं। That doesn't
   make any sense." It differentiated nothing — it was emitted identically on every card, had no
   state variant, and the heading is 7.45:1 on the band without it, so removing it costs nothing.
   The badge margin drops back to the shared value now that there is no rule to space away from. */
.why-sec .icon-cards .fi-badge{margin-bottom:2px}
.why-sec .grid article p{color:rgba(255,255,255,.86)}
.why-sec .grid article::after{background:var(--gold)}

/* ---- programme cards: the thing being sold ---- */
/* A rem-based minimum (not px) so a long word or a high textScale drops a column instead of
   squeezing four into a row that no longer fits — no separate breakpoints needed. */
.grid.four.pcards{grid-template-columns:repeat(auto-fit,minmax(min(15.5rem,100%),1fr));gap:20px;align-items:stretch}
.grid article.pcard,.band .grid article.pcard{background:#fff;border:1px solid var(--line);border-top:6px solid var(--gold);border-radius:var(--r);padding:calc(22px * var(--el-space-safe,1)) calc(24px * var(--el-space-safe,1)) calc(24px * var(--el-space-safe,1));display:flex;flex-direction:column;gap:12px;position:relative;box-shadow:0 6px 18px rgba(17,45,94,.06);min-width:0}
.pnum{font-family:var(--display);font-weight:700;font-size:calc(1.7rem * var(--el-size-safe,1));line-height:1;color:var(--amber);letter-spacing:-.02em;flex:none}
.pcard h3{font-size:calc(1.17rem * var(--el-size-safe,1));line-height:1.25;margin:0;color:var(--navy);text-wrap:balance}
.pcard p{margin:0}
.pcard .link{margin-top:auto;padding-top:14px;border-bottom:0;display:inline-flex;align-items:center;gap:8px;color:var(--navy);text-decoration:none;font-size:calc(.95rem * var(--el-size-safe,1))}
.pcard .link::before{content:"";position:absolute;inset:0;border-radius:var(--r)}
.pcard .link::after{content:"\2192" / "";font-family:var(--display);transition:transform .25s var(--ease-out);color:var(--amber)}
.pcard:hover .link::after,.pcard:focus-within .link::after{transform:translateX(5px)}
.grid article.pcard:hover,.band .grid article.pcard:hover{border-top-color:var(--gold);box-shadow:var(--shadow);transform:translateY(-4px)}
.grid article.pcard:hover .link{color:var(--amber)}
.pcard .link:focus-visible{outline-offset:4px}
.pcard .fi-badge{margin-bottom:2px}

/* ---- shared icon language ----
   One small, flat, two-colour badge (navy line work + a small amber accent — the CMS accent
   yellow itself is 1.5:1 on white, so it is never the informative stroke, only ever a decorative
   fill elsewhere) used everywhere Simon asked for "the same minimal icon language as the
   programme cards": key features, the audience row, and the checklist blocks on a programme page.
   The badge is always a plain white circle so the icon reads the same regardless of what section
   background it sits on. The badge box and its font-size are in rem and the glyph inside is 1.6em
   of that font-size, so the whole family tracks the root font-size — which is itself textScale
   times the visitor's own browser default. These were px, which pinned every icon while every
   label beside it grew across the 90-125% textScale range; the em on .fi alone could not save it,
   because the em resolved against a px font-size. */
.fi-badge{display:inline-grid;place-items:center;width:3.25rem;height:3.25rem;font-size:1.625rem;border-radius:50%;background:#fff;border:1px solid var(--line);box-shadow:0 4px 10px rgba(17,45,94,.08);flex:none;color:var(--fi-c,var(--navy))}
.fi-badge.small{width:2.25rem;height:2.25rem;font-size:1.1875rem;box-shadow:none}
.fi{width:1.6em;height:1.6em;display:block}
.fi .fi-dot{fill:var(--amber)}
.fi .fi-dot-path{stroke:var(--amber);fill:none}
/* Varied line colour, one disciplined family. Simon: "हर जगह white-blue, white-blue, white-blue
   ज्यादा हो रहा है" — everything was the same navy on white and the page needed a break. He had
   already rejected his old site's funky coloured icons, so nothing else changes: same geometry,
   same 1.7 stroke, same optical box, same white badge, same single amber accent inside every
   glyph. Only the line colour cycles, through five dark, desaturated tones drawn from the brand
   set. Measured against the badge's white fill, which is the background they actually sit on in
   every section including the navy "Why choose ISPD" band:
     --fi-1 navy #0A2450         15.21:1
     --fi-2 #0F6E56 deep teal     6.20:1
     --fi-3 #8A3A12 burnt sienna  7.79:1
     --fi-4 #4A3A8C indigo        9.23:1
     --fi-5 #0C5C7A petrol        7.43:1
   The 3:1 graphics minimum is the floor; the lowest here is more than double it. Nothing is
   conveyed by the colour: every one of these icons is aria-hidden and sits beside a visible label,
   and the glyph itself is chosen *from* that label by ICON_RULES, so it is redundant with the text
   by construction. The cycle is positional, so re-ordering cards in the CMS re-colours them and
   never leaves two neighbours the same. */
/* One ink for every section icon: the site is navy and gold, and five tints read as five brands. */
:root{--fi-1:var(--navy);--fi-2:var(--navy);--fi-3:var(--navy);--fi-4:var(--navy);--fi-5:var(--navy)}
:is(.icon-grid,.icon-cards,.grid.pcards,.icon-checks)>:nth-child(5n+1){--fi-c:var(--fi-1)}
:is(.icon-grid,.icon-cards,.grid.pcards,.icon-checks)>:nth-child(5n+2){--fi-c:var(--fi-2)}
:is(.icon-grid,.icon-cards,.grid.pcards,.icon-checks)>:nth-child(5n+3){--fi-c:var(--fi-3)}
:is(.icon-grid,.icon-cards,.grid.pcards,.icon-checks)>:nth-child(5n+4){--fi-c:var(--fi-4)}
:is(.icon-grid,.icon-cards,.grid.pcards,.icon-checks)>:nth-child(5n+5){--fi-c:var(--fi-5)}

/* key-features / "what every programme includes" infographic */
.icon-cards article{gap:10px}
.icon-cards .fi-badge{margin-bottom:2px}
.why-sec .icon-cards .fi-badge{box-shadow:0 4px 14px rgba(0,0,0,.25)}

/* "who the programmes are for": an icon and a heading per audience, brand palette only, gently
   staggered in. The one-line description is back at Simon's request — "अगर मैं mouse इसपे लेकर आया...
   तब ये थोड़ा सा expand होगा, और एक one-liner में we can describe this" — but a line that only exists
   while a pointer is over the tile does not exist at all for a screen reader, or for anyone
   driving the page from the keyboard. So it is always in the markup and always in the
   accessibility tree; only its *presentation* is conditional:

   - Default (this block): shown permanently, in flow, under the label. That is what a touch
     device, a no-hover pointer and a no-CSS reader all get, and it is what the 390px screenshots
     show. Nothing is hidden from anyone who cannot hover.
   - (hover:hover) and (pointer:fine) below: the same element becomes a floating caption that fades
     in on :hover and on :focus-within. It is absolutely positioned, so revealing it cannot push a
     single pixel of the rest of the page around, and opacity:0 keeps it in the accessibility tree
     where display:none or visibility:hidden would not.

   The tiles stay non-interactive <li>s. :focus-within is there so the caption still appears if a
   focusable element is ever added inside one, but nothing is being put into the tab order for the
   sake of a hover effect — a tile that focuses but does nothing is worse than no keyboard
   affordance at all, and the text is already available to AT without it. */
.icon-grid{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(auto-fit,minmax(min(9.5rem,100%),1fr));gap:22px 16px;text-align:center}
/* Two columns on a phone: an odd last tile spans the row and sits centred instead of orphaned bottom-left. */
@media (max-width:530px){.icon-grid li:last-child:nth-child(odd){grid-column:1/-1}}
.icon-grid li{display:flex;flex-direction:column;align-items:center;gap:calc(12px * var(--el-space-safe,1));min-width:0;position:relative}
.icon-grid .fi-badge{width:4rem;height:4rem;font-size:1.875rem}
.icon-grid-label{font-family:var(--display);font-weight:600;color:var(--navy);font-size:calc(.98rem * var(--el-size-safe,1));line-height:1.3;min-width:0;overflow-wrap:break-word;hyphens:auto}
.icon-grid-note{display:block;margin-top:-4px;font-size:calc(.86rem * var(--el-size-safe,1));line-height:1.45;color:var(--muted);max-width:22ch;margin-inline:auto;text-wrap:pretty}
/* The five audience descriptions used to be a hover panel. Measured on the rendered page: the
   panel sits 10px below its tile with pointer-events:none, so moving the pointer onto it drops
   :hover and it disappears — a magnifier user could never park on the text to read it. And
   :focus-within was dead by construction: these tiles contain nothing focusable, so a sighted
   keyboard user had no way to see any of it. The text is two short lines. Hiding it bought a
   tidier grid and cost a whole group of users the content, so it now just sits under the label.
   The lift on the badge is kept: it is decoration, and nothing depends on it. */
@media (hover:hover) and (pointer:fine){
  .icon-grid li .fi-badge{transition:transform .22s var(--ease-out),box-shadow .22s var(--ease-out)}
  .icon-grid li:hover .fi-badge{transform:translateY(-3px);box-shadow:0 10px 20px rgba(10,36,80,.16)}
}
@media (prefers-reduced-motion: no-preference){
  .icon-grid li{opacity:0;animation:rise .6s var(--ease-out) both;animation-delay:calc(var(--i,0)*80ms)}
}
@media (prefers-reduced-motion: reduce){
  .icon-grid-note,.icon-grid li .fi-badge{transition:none}
  .icon-grid li:hover .fi-badge,.icon-grid li:focus-within .fi-badge{transform:none}
  @media (hover:hover) and (pointer:fine){.icon-grid-note{transform:none}}
}

/* programme-page checklists ("What you will be able to do", "What is included", "What you will
   achieve"): the same badge instead of the plain tick, at the smaller size */
.icon-checks{list-style:none;margin:0;padding:0;display:grid;gap:14px}
.icon-checks li{display:flex;align-items:flex-start;gap:12px;padding-left:0;margin-bottom:0}
.icon-checks li::before,.icon-checks li::after{content:none}
.icon-checks li>span:last-child{padding-top:6px;color:var(--muted)}

/* ---- outcome lists: arrow chips instead of circle-and-tick ---- */
.checks li{padding-left:36px;margin-bottom:.75em}
.checks li::before{top:.15em;width:24px;height:24px;border-radius:8px;background:var(--accent-soft) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23112D5E' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h13M12 5l7 7-7 7'/%3E%3C/svg%3E") center/13px no-repeat;border:1px solid var(--accent-soft-line)}
.checks li::after{content:none}

/* ---- how it works: numbered stepper (horizontal on wide screens, timeline on phones) ---- */
.ladder.steps,.ladder.timeline{border-top:0;position:relative}
.ladder.timeline li,.ladder.steps li{padding:0;border-bottom:0;position:relative;gap:18px;min-width:0}
.ladder.timeline li:hover,.ladder.steps li:hover{padding-left:0}
.ladder.timeline .num,.ladder.steps .num{width:48px;height:48px;min-width:48px;border-radius:50%;background:var(--gold);color:var(--navy-deep);display:grid;place-items:center;font-size:1rem;box-shadow:0 0 0 4px var(--paper);position:relative;z-index:1;flex:none}
.band .ladder .num{box-shadow:0 0 0 4px #fff}
.ladder.timeline li+li,.ladder.steps li+li{margin-top:28px}
.ladder.timeline li::before,.ladder.steps li::before{content:"";position:absolute;left:22px;top:52px;bottom:-28px;width:3px;background:repeating-linear-gradient(180deg,#C9D2E8 0 8px,transparent 8px 14px)}
.ladder.timeline li:last-child::before,.ladder.steps li:last-child::before{display:none}
.ladder h3{margin-bottom:.35em}
.ladder p{margin-bottom:0}
@media(min-width:861px){
  .ladder.steps{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(11rem,100%),1fr));gap:24px;overflow-x:clip}
  .ladder.steps li{flex-direction:column;gap:16px}
  .ladder.steps li+li{margin-top:0}
  .ladder.steps li::before{left:52px;right:-24px;top:22px;bottom:auto;height:3px;width:auto;background:repeating-linear-gradient(90deg,#C9D2E8 0 8px,transparent 8px 14px)}
  .ladder.steps h3{font-size:calc(1.02rem * var(--el-size-safe,1))}
  .ladder.steps p{font-size:calc(.95rem * var(--el-size-safe,1))}
}

/* ---- journey infographic: nodes on a wave ----
   Signature element, made bigger and cleaner: the old muddy cream backdrop is replaced by the
   clean accent-soft tint (derived from the real logo yellow), and the nodes below are ~30%
   larger than the first pass. grid-auto-rows uses a rem minimum, not a fixed pixel height — a
   fixed height with `overflow` left to its default clips real content the moment a step grows
   past the box (this happened at 4-6 steps with the old 320-340px fixed value); minmax(…,auto)
   grows the row instead of clipping it. */
/* The artwork carries its own pale backdrop, which lands as a hard-edged rectangle on the yellow
   band. Rounding it and lifting it turns that edge into a card, so the picture reads as placed
   rather than pasted, and the band stays yellow. overflow:hidden on the wrapper because the radius
   has to clip the image itself, not just the box around it. */
.journey-sec{background:var(--white);border-block:1px solid var(--line)}
.journey-sec>.wrap{padding-block:clamp(calc(40px * var(--el-space-safe,1)),calc(6vw * var(--gap-scale-safe) * var(--el-space-safe,1)),calc(64px * var(--gap-scale-safe) * var(--el-space-safe,1)))}
/* ---- the four-step journey, rebuilt natively from ISPD's own artwork ----
   Only the four illustrations come from the supplied picture; ribbon, circles, joints and labels
   are the site's own. The nodes sit level. The ribbon is a thick S that swings --j-amp BELOW an
   even node and ABOVE an odd one, so each circle sits inside a loop with the ribbon passing behind
   its edge — which is how the original reads. Its box is inset to span first-centre→last-centre
   and centre−amp→centre+amp, so the path just walks the corners (journey() in partials.mjs), and
   each label goes on the side the loop is not. A gold halo round each node is the gap where the
   ribbon passes under. */
.journey{position:relative;overflow-x:clip;--j-node:clamp(112px,15vw,210px);--j-ring:5px;--j-amp:calc(var(--j-node) * .5);--j-rib:calc(var(--j-node) * .36);--j-joint:calc(var(--j-node) * .4);--j-gap:24px;--j-col-gap:16px;--j-lbl:3rem;--jw-1:#f9a700;--jw-2:#0ac9e8;--jw-3:#5747e5;--jw-4:#f32265;--jj-1:#149a3a;--jj-2:#2a2fc4;--jj-3:#5a2e8c;--j-cy:calc(var(--j-lbl) + var(--j-gap) + var(--j-node) / 2);--j-inset:calc((100% - (var(--n) - 1) * var(--j-col-gap)) / var(--n) / 2)}
.journey ol{list-style:none;margin:0;padding:0;position:relative;z-index:1;display:grid;grid-template-columns:repeat(var(--n),minmax(0,1fr));column-gap:var(--j-col-gap)}
.jn{display:grid;grid-template-rows:var(--j-lbl) var(--j-node) var(--j-lbl);row-gap:var(--j-gap);justify-items:center;align-items:center;min-width:0}
/* A white disc with a hairline ring, a white halo that cuts the ribbon around it, and a soft drop. */
.jn-node{grid-row:2;position:relative;width:var(--j-node);height:var(--j-node);border-radius:50%;background:#fff;border:0;overflow:hidden;box-shadow:0 0 0 1px rgba(10,36,80,.14),0 0 0 calc(var(--j-node) * .06) var(--white),0 16px 30px rgba(10,36,80,.18)}
.jn-node img{display:block;width:100%;height:100%;object-fit:cover}
.jn-icon{display:grid;place-items:center;width:100%;height:100%;padding:19%;color:var(--navy);box-sizing:border-box}
.jn-icon .g{fill:var(--gold)}.jn-icon .w{fill:#fff}
.jn-icon svg{width:100%;height:100%;display:block}
.jn-text{display:block;width:100%;min-width:0;justify-self:stretch;text-align:center;container-type:inline-size}
.jn.high .jn-text{grid-row:1;align-self:end}
.jn.low .jn-text{grid-row:3;align-self:start}
.jn-label{display:block;font-family:var(--display);font-weight:700;line-height:1.15;color:var(--navy);font-size:.9rem;font-size:clamp(.85rem,12cqw,calc(1.45rem * var(--el-size-safe,1)));overflow-wrap:normal;hyphens:none}
.jn-body{display:block;color:var(--ink);font-size:calc(.92rem * var(--el-size-safe,1));margin-top:4px}
/* 15% wider each side than centre-to-centre: room for the ribbon tails (viewBox -150..1150). */
.jw-box{position:absolute;z-index:0;left:calc(var(--j-inset) - .15 * (100% - 2 * var(--j-inset)));right:calc(var(--j-inset) - .15 * (100% - 2 * var(--j-inset)));top:calc(var(--j-cy) - var(--j-amp));height:calc(var(--j-amp) * 2);pointer-events:none}
.jw-ribbon{position:absolute;inset:0;width:100%;height:100%;overflow:visible;display:block}
.jw-ribbon path{fill:none;stroke:var(--navy);stroke-width:var(--j-rib);stroke-linecap:round;stroke-linejoin:round;vector-effect:non-scaling-stroke}
/* One colour per step, changing under the joint dots, as in the approved artwork. */
.jw-ribbon .jw-s1{stroke:var(--jw-1)}.jw-ribbon .jw-s2{stroke:var(--jw-2)}.jw-ribbon .jw-s3{stroke:var(--jw-3)}.jw-ribbon .jw-s4{stroke:var(--jw-4)}
.jw-v{display:none}
.jn-joint{position:absolute;left:calc((.15 + (var(--k) - .5) / (var(--n) - 1)) / 1.3 * 100%);top:50%;width:var(--j-joint);height:var(--j-joint);border-radius:50%;background:var(--navy);transform:translate(-50%,-50%)}
.jj1{background:var(--jj-1)}.jj2{background:var(--jj-2)}.jj3{background:var(--jj-3)}
@media (max-width:700px){
  .journey{--j-node:clamp(96px,26vw,128px);--j-gap:18px;--j-rib:calc(var(--j-node) * .4)}
  .journey ol{grid-template-columns:1fr;row-gap:var(--j-gap)}
  .jn{grid-template-rows:none;grid-template-columns:minmax(0,1fr) var(--j-node) minmax(0,1fr);column-gap:14px;row-gap:0;align-items:center}
  .jn-node{grid-row:1;grid-column:2}
  /* A label wider than its column (user text-spacing at 320px) must spill into the wrap gutter,
     never under the circle: the left labels overflow leftwards via flex-end, the right ones
     rightwards, and the section stops clipping so neither loses its last letters. */
  .journey{overflow-x:visible}
  .jn.high .jn-text{grid-row:1;grid-column:1;align-self:center;display:flex;justify-content:flex-end;text-align:right}
  .jn.low .jn-text{grid-row:1;grid-column:3;align-self:center;text-align:left}
  /* The side columns are ~100px on a 375 screen. At the desktop size a single word like
     "Placements" is wider than that and was being broken mid-word; a smaller face fits, and a
     word must never be split — a label that overflows is legible, a split one is not. */
  .jn-label{font-size:.78rem;font-size:clamp(.78rem,14cqw,calc(1.05rem * var(--el-size-safe,1)))}
  /* At a raised browser font the .8rem floor outgrows a 100px column ("Mentorship" is 145px at a
     30px root). A dictionary hyphen ("Mentor-ship") is the one break that stays readable; it only
     fires when the word does not fit, so at default sizes nothing changes. */
  .jn-label{hyphens:auto;overflow-wrap:break-word;min-width:0}
  .jw-box{left:calc(50% - var(--j-amp));right:auto;width:calc(var(--j-amp) * 2);top:calc(var(--j-node) / 2 - .15 * (var(--n) - 1) * (var(--j-node) + var(--j-gap)));height:calc(1.3 * (var(--n) - 1) * (var(--j-node) + var(--j-gap)))}
  .jw-h{display:none}
  .jw-v{display:block}
  .jn-joint{left:50%;top:calc((.15 + (var(--k) - .5) / (var(--n) - 1)) / 1.3 * 100%)}
}
@media (forced-colors:active){.jw-ribbon .jw-s{stroke:CanvasText}.jn-node{box-shadow:none;border:1px solid CanvasText;outline:calc(var(--j-node) * .06) solid Canvas}.jn-joint{background:CanvasText;border:1px solid Canvas}.jn-icon .g,.jn-icon .w{fill:Canvas}}

/* ---- scrolling keyword strip: cream band, masked edges, control in its own gutter ---- */
.marquee{display:flex;align-items:stretch;padding:0;background:var(--accent-soft);border-block:1px solid var(--accent-soft-line);position:relative}
/* the fade ramp is a share of the strip's own width, not a fixed 64px, so a short word is never
   stuck entirely inside the fade on a narrow phone */
.marquee-viewport{flex:1 1 auto;min-width:0;overflow:hidden;display:flex;align-items:center;padding:14px 0;-webkit-mask-image:linear-gradient(90deg,transparent,#000 clamp(10px,7%,64px),#000 calc(100% - clamp(10px,7%,64px)),transparent);mask-image:linear-gradient(90deg,transparent,#000 clamp(10px,7%,64px),#000 calc(100% - clamp(10px,7%,64px)),transparent)}
.marquee-track{color:var(--navy);animation:slide 32s linear infinite;animation-delay:-3s;animation-play-state:running}
.marquee:hover .marquee-track{animation-play-state:running}
html:not(.js) .marquee-track{animation:none}
html:not(.js) .marquee-ctl{display:none}
.marquee[data-state="paused"] .marquee-track,.marquee.paused .marquee-track{animation-play-state:paused}
.marquee-ctl{flex:none;display:flex;align-items:center;padding:8px 16px;border-left:1px solid var(--accent-soft-line)}
.marquee-toggle{background:#fff;border-color:var(--navy);color:var(--navy);min-height:max(24px,calc(36px * var(--btn-scale-safe)))}
.marquee-toggle:hover{background:var(--navy);color:#fff}
@media(max-width:480px){.marquee-ctl{padding:6px 10px}.marquee-toggle{min-width:60px;padding:4px 10px}}

/* ---- hero gallery ----
   Nothing here crops. Simon said twice that heads were being cut off ("दो लोगों के सर कट रहे हैं...
   ये थोड़ा सा unprofessional दिखता है") and asked for a vertical photo to arrive vertical and a
   horizontal one horizontal. The seven photographs run from 1.05 to 2.10 wide.

   Two ways to honour that: collapse the frame to each slide's own ratio (true variable height), or
   keep one reserved box and letterbox inside it. This ships the second — object-fit:contain in the
   existing 16/10 box, with the box painted in the page colour so the reserved space around a
   narrow photograph is invisible. Every photograph is shown whole; what differs from the literal
   request is that the space is reserved rather than collapsed.

   The variable-height version was measured, not skipped. At the 493px art column the ratio range
   is a 235px height swing; .hero-grid centres the text column against the art column, so the h1,
   the lede and both call-to-action buttons would travel +/-117px on the 5s rotation — and a
   pointer resting on a CTA is not over .gallery-rot, so the gallery's hover-pause never fires and
   the button moves out from under the cursor mid-click. At 320px the art column is ~276px while
   the control bar needs ~366px, so it wraps to ~110px tall against a 2.10-ratio slide only 131px
   tall. If Simon prefers the collapsing frame after seeing this, the pieces are already here:
   every slide carries data-ar and real width/height, so app.js only has to set an explicit height
   on .gr-track and transition it (and .hero-grid must become align-items:start).

   object-fit stays — it is contain, not none — because object-position, emitted by html.mjs img()
   from the CMS focal point and set through the picker in the admin, only does anything under
   object-fit. Removing the property would silently turn that whole control into a no-op.

   width/height on every <img> and --gr-ar in the markup are the real pixel dimensions, read off
   disk at build time, so the box is reserved before a single byte of image data arrives. */
/* Transparent, not the page colour. It reserves space before the image loads, and an opaque fill
   would paint a pale rectangle over whatever the hero puts behind it — which was a blurred
   gradient until 7 Sep 2026 and is the flat page now. Transparent is right either way, and stays
   right if anything is ever put back there. */
.gallery-rot{position:relative;background:transparent;isolation:isolate}
.gr-track{position:relative;aspect-ratio:16/10;border-radius:var(--r);overflow:hidden}
/* visibility, not opacity alone. Every slide stays in the DOM, so with opacity as the only cue the
   live region's text content never actually changed on a slide change — only aria-hidden flipped,
   which is the least reliable signal there is. visibility transitions discretely, so it is delayed
   to the end of the cross-fade on the way out and applied immediately on the way in: the fade is
   untouched and the change is now a real render-tree mutation a live region can announce. */
.gr-slide{position:absolute;inset:0;opacity:0;visibility:hidden;transition:opacity 1s ease,visibility 0s linear 1s;z-index:0}
.gr-slide.is-active{opacity:1;visibility:visible;transition:opacity 1s ease,visibility 0s linear 0s;z-index:1}
.gr-slide img{width:100%;height:100%;object-fit:contain;display:block}
/* Single-photo fallback (a gallery of one): same treatment, no rotation. */
.hero-art .hero-photo-natural img{aspect-ratio:16/10;object-fit:contain;box-shadow:none}
/* The controls sit under the photograph, not on top of it. Overlaid they covered the bottom of
   every slide — and once the box is page-coloured rather than navy, a navy scrim floating in the
   reserved band above a wide photo reads as a smudge. Below the image the bar is correctly placed
   whatever shape the current slide is, and it never hides part of a photograph Simon has just
   asked to see in full. */
.gr-controls{--btn-scale-safe:clamp(0.9,var(--btn-scale,1),1);display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:6px;padding:12px 0 13px}
/* 1px #4C5878 is 6.65:1 on the page background, so the button's own boundary carries the required
   3:1 now that it no longer sits on a dark scrim. */
.gr-btn{width:max(24px,calc(40px * var(--btn-scale-safe)));height:max(24px,calc(40px * var(--btn-scale-safe)));border-radius:50%;border:1px solid #4C5878;background:#fff;color:var(--navy);display:grid;place-items:center;cursor:pointer;padding:0;transition:background .2s ease,transform .2s ease;flex:none}
.gr-btn:hover{background:var(--gold);transform:scale(1.06)}
.gr-toggle{margin-left:6px}
.gr-dots{display:flex;gap:6px;padding:0 4px;flex-wrap:wrap;justify-content:center}
.gr-dot{width:max(24px,calc(24px * var(--btn-scale-safe)));height:max(24px,calc(24px * var(--btn-scale-safe)));border:0;background:transparent;padding:0;cursor:pointer;position:relative;border-radius:50%;flex:none}
.gr-dot::before{content:"";position:absolute;inset:8px;border-radius:50%;background:#fff;border:1px solid var(--navy-deep);transition:inset .2s ease,background .2s ease}
.gr-dot:hover::before{background:var(--accent-soft)}
/* The gold fill is 1.06-1.12:1 against the inactive fill across real slides, so it contributes
   nothing to the state; the diameter change is what carries it. A 2px ring against the inactive
   1px adds a second non-colour cue rather than leaning on a difference nobody can see. */
.gr-dot[aria-current="true"]::before{inset:5px;background:var(--gold);border-width:2px}
.gr-toggle .i-play{display:none}
.gallery-rot.paused .gr-toggle .i-play{display:block}
.gallery-rot.paused .gr-toggle .i-pause{display:none}
/* The dots keep their 24px target box (WCAG 2.5.8); only the gap between them tightens, which is
   what keeps the whole bar on one row at 390px. */
@media(max-width:430px){.gr-dot::before{inset:8px}.gr-dot[aria-current="true"]::before{inset:6px}.gr-dots{gap:2px;padding:0}.gr-toggle{margin-left:2px}.gr-controls{gap:4px}}

/* ---- brochure dialog ---- */
.brochure-open{display:inline-flex;align-items:center;gap:8px}
.brochure-open svg{flex:none}
.nav .brochure-open{margin-left:20px}
/* Both buttons in a plate run the full width of the panel. The brochure button already did; the
   enquiry button above it was shrink-to-fit, so the pair stepped in and out by 11px at 1280 and by
   420px once the split collapsed to one column. */
.plate .btn{display:flex;align-items:center;justify-content:center;width:100%;text-align:center}
.plate .brochure-open{margin-top:12px;border-color:rgba(255,255,255,.6);color:#fff;width:100%;justify-content:center}
.plate .brochure-open:hover{background:var(--gold);border-color:var(--gold);color:var(--navy)}
.page-head .cta-row{margin-top:18px}
@media(max-width:820px){
  .nav .brochure-open{margin:12px 0 0;width:100%;justify-content:center}
  .nav .brochure-open+a.btn{margin-top:10px}
}
.modal{border:0;padding:0;border-radius:var(--r);width:min(540px,calc(100vw - 32px));max-height:calc(100vh - 32px);box-shadow:0 30px 80px rgba(11,31,66,.35);background:#fff;color:var(--ink);overflow:auto}
.modal::backdrop{background:rgba(11,31,66,.6)}
.modal-body{padding:28px 28px 24px;position:relative}
@media(max-width:480px){.modal-body{padding:22px 18px 18px}}
.modal h2{font-size:1.4rem;padding-right:44px;margin-bottom:.4em}
.modal h2::before{content:"";display:block;width:44px;height:5px;background:var(--gold);border-radius:3px;margin-bottom:12px}
.modal-intro{color:var(--muted);margin-bottom:18px;font-size:.98rem}
.modal form{border:0;padding:0;background:transparent}
.modal-close{position:absolute;top:14px;right:14px;width:max(24px,calc(40px * var(--btn-scale-safe)));height:max(24px,calc(40px * var(--btn-scale-safe)));border-radius:50%;border:1px solid #4C5878;background:#fff;color:var(--navy);display:grid;place-items:center;cursor:pointer;transition:background .2s ease}
.modal-close:hover{background:var(--accent-soft)}
.modal-done{background:var(--accent-soft);border-radius:10px;padding:18px 20px;color:var(--navy)}
.modal-done-title:focus{outline:none;box-shadow:none}
.modal-done-title{font-family:var(--display);font-weight:600;color:var(--navy);font-size:1.1rem;margin-top:0;margin-bottom:.4em}
.modal-done p{margin-bottom:.8em}
.modal-done a{color:var(--navy);text-decoration-color:currentColor}
body.modal-open{overflow:hidden}

/* ---- footer social logos ---- */
.site-foot .social{gap:10px}
/* The full-colour brand marks (LinkedIn blue, Instagram's gradient) are designed for a white
   background — composited against the dark footer chip they used to sit on, LinkedIn drops to
   ~2.3:1 and Instagram's purple to ~2:1, both well under the 3:1 non-text contrast minimum. A
   plain white chip restores their intended contrast (LinkedIn ~5:1, Instagram purple ~6.7:1)
   without losing the "always the brand's own colour" requirement. */
.site-foot .social a.social-logo{display:grid;place-items:center;width:40px;height:40px;border-radius:10px;background:#fff;border:1px solid rgba(255,255,255,.5);text-decoration:none;transition:transform .2s ease,box-shadow .2s ease}
.site-foot .social a.social-logo:hover{transform:translateY(-2px);box-shadow:0 6px 14px rgba(0,0,0,.25)}
.site-foot .social a.social-logo svg{display:block}
@media (forced-colors: active){.site-foot .social a.social-logo{forced-color-adjust:none;background:Canvas;border-color:ButtonText}}

/* ---- motion system ----
   .reveal (fade-up), .pop (scale with overshoot), .paper (sheet sliding in from the left) are
   added by app.js only when motion is allowed, so nothing is ever hidden without JavaScript or
   under prefers-reduced-motion. The classes are removed on animationend so hover transforms work. */
.pop,.paper{opacity:0}
.pop.in{animation:pop .7s var(--ease-pop) both;animation-delay:calc(var(--i,0)*90ms)}
.paper.in{animation:paper .75s var(--ease-out) both;animation-delay:calc(var(--i,0)*110ms);transform-origin:0 100%}
@keyframes pop{0%{opacity:0;transform:scale(.82) translateY(12px)}65%{opacity:1;transform:scale(1.035) translateY(-3px)}100%{opacity:1;transform:none}}
@keyframes paper{0%{opacity:0;transform:translateX(-48px) rotate(-3.5deg)}60%{opacity:1}100%{opacity:1;transform:none}}
@keyframes rise{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion: no-preference){
  .page-home .hero .eyebrow,.page-home .hero h1,.page-home .hero .lede,.page-home .hero .cta-row{animation:rise .7s var(--ease-out) both}
  .page-home .hero h1{animation-delay:.08s}
  .page-home .hero .lede{animation-delay:.16s}
  .page-home .hero .cta-row{animation-delay:.24s}
  .page-home .hero .hero-art{animation:rise .8s var(--ease-out) .2s both}
  .page-home .hero .trust,.page-home .hero .facts{animation:rise .7s var(--ease-out) .4s both}
}
@media (prefers-reduced-motion: reduce){
  .reveal,.pop,.paper,.hero .eyebrow,.hero h1,.hero .lede,.hero .cta-row,.hero .hero-art,.hero .trust,.hero .facts{opacity:1!important;transform:none!important;animation:none!important}
  .gr-slide{transition:none}
  .icon-grid-note{transition:none!important}
  .grid article:hover,.btn:hover,.wa-float:hover,.gr-btn:hover,.site-foot .social a.social-logo:hover{transform:none}
  /* The keyword strip starts still and the Play button lets the visitor start it (deterministic on every machine). */
  .marquee-track,.marquee:hover .marquee-track{animation-duration:32s!important;animation-iteration-count:infinite!important;animation-play-state:paused}
  .marquee[data-state="playing"] .marquee-track{animation-play-state:running}
}

/* ---- fixes after the first visual pass ---- */
[hidden]{display:none!important}
/* programme cards: the number badge floats on the corner (absolute, against the card), so the row
   that holds it and the icon badge is plain flow, not a flex row. */
.pcard-top{margin-bottom:2px}
.pnum{position:absolute;top:-6px;right:18px;width:40px;height:40px;border-radius:50%;background:var(--navy-deep);color:#fff;font-size:calc(.95rem * var(--el-size-safe,1));display:grid;place-items:center;border:3px solid #fff;box-shadow:0 4px 10px rgba(10,36,80,.18)}
.grid article.pcard,.band .grid article.pcard{padding-top:26px;overflow:visible}
@media(max-width:720px){
}
