/* ==========================================================================
   Swati Gupté Bhisé / Cayenne Pepper Productions

   Values are taken from the PowerPoint export of the Canva design ("UPDATED
   Swati Gupté Bhisé WEBSITE.pptx") — colours, typefaces, sizes and positions
   are the source file's, not approximations from screenshots.

   CONTAINER
   The source canvas is 1920x1080 with 108px gutters, giving a 1704px content
   area. The live site (swatibhise.com, WordPress + Elementor, Hello Elementor
   theme) caps content at 1300px with 15px inline padding — Elementor puts the
   padding on .e-con and the cap on .e-con-inner, so content is a true 1300px
   above ~1330px viewport.

   To sit consistently alongside that site, this build matches it: content is
   capped at 1300px and every size derived from the source canvas has been
   rebased by 1300/1704 = 0.763. Proportions of the original design are
   preserved exactly; only the absolute scale changes.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Design tokens
   -------------------------------------------------------------------------- */

:root {
  /* Layout. Regions cap at content + both gutters so the content box itself
     lands on exactly --container, matching Elementor's .e-con-inner. */
  --container: 1300px;
  --gutter: 15px;
  --region-max: calc(var(--container) + (var(--gutter) * 2));

  /* The header bar's height. It IS the logo's height — the bar has no padding
     above or below it (see the note over .site-logo) — so the two have to move
     together: swati_assets() writes both from the Logo height option whenever
     it differs from the 75px spec figure. Read by scroll-padding-top below, so
     an anchor never lands underneath the sticky bar. */
  --header-h: 75px;

  /* Surface — sampled from the deck's own background texture */
  --c-bg: #ecebe8;

  /* Menu panels, from slide 18. The source paints them as white at 30% over
     an empty slide. On a real page there is body copy behind them, and 30%
     white is far too sheer to read against — so the panel is flattened to the
     colour that translucency actually produces (0.3 white over the #ECEBE8
     page texture = #F2F1EF). Identical on screen, legible over anything.

     Do not put backdrop-filter back on these: a parent with backdrop-filter
     becomes a backdrop root, which silently disables it on the nested
     .submenu — that was the original bug. */
  --c-bg-panel:      #f2f1ef;                     /* = #FFFFFF @ 30% on page */
  --c-row-active:    rgba(199, 173, 157, 0.30);   /* #C7AD9D @ 30%, over panel */
  --c-rule-dropdown: #e8d8cf;                     /* level-1 dividers        */
  --c-rule-submenu:  #ffffff;                     /* level-2 dividers, white */

  /* Ink — hex values are the deck's; contrast ratios measured against --c-bg */
  --c-ink:        #6b5e54;  /* body + headings          5.3:1  AA pass       */
  --c-nav:        #8e7762;  /* nav, bold                3.6:1  AA large pass */
  --c-nav-active: #a8836a;  /* active nav item          4.6:1  AA pass       */
  --c-rule:       #d9d2ca;

  /* The design's own active-nav colour is #C7AD9D, but it measures 1.8:1
     against the page and fails WCAG AA at any size. Since the highlight now
     fires on every page, it has been changed to #A8836A — same hue family,
     keeps the "lighter than the rest" cue, and reaches 4.6:1. Revert the token
     above to #c7ad9d if exact fidelity to the deck matters more. */

  /* Type — Cinzel Decorative and Cinzel are both on Google Fonts. Century
     Gothic ships with Office/Windows; Questrial is the geometric web fallback. */
  --font-display: 'Cinzel Decorative', Georgia, serif;   /* 111 runs in source */
  --font-engraved: 'Cinzel', Georgia, serif;             /*  74 runs in source */
  --font-body: 'Century Gothic', Questrial, 'URW Gothic', 'Avant Garde', sans-serif;

  /* Scale. Each vw value is expressed against the 1300px container, and each
     maximum is the source size x 0.763. Source sizes at the 1920 canvas were:
     display 128, title 56, body 32, nav 22.7, dropdown 18. */
  --step-display:  clamp(2.25rem, 7.52vw, 6.10rem);   /* 128 -> 97.7px */
  --step-title:    clamp(1.5rem,  4.06vw, 2.67rem);   /*  56 -> 42.7px */
  --step-body:     clamp(0.9rem,  1.88vw, 1.53rem);   /*  32 -> 24.4px */
  /* The minimum was 0.68rem, which resolves to 10.9px — the nav rendered below
     the 12px readability floor on every page at 767px and under. The maximum is
     the rebased figure and is untouched; only the floor moved. */
  --step-nav:      clamp(0.75rem, 1.33vw, 1.08rem);   /* 22.7 -> 17.3px, floor 12px */

  /* Deliberate deviation from the 0.763 rebase: a strict rebase puts the menu
     text at 13.7px, which is uncomfortably small to read. Bumped to 15.5px —
     still subordinate to the 17.3px top-level nav. The dropdown panel widths
     and row padding are set in em, so they scale with this automatically. */
  --step-dropdown: clamp(0.85rem, 1.19vw, 0.97rem);   /*  18 -> 15.5px */

  --shadow-panel: 0 6px 22px rgba(107, 94, 84, 0.18);
}

/* --------------------------------------------------------------------------
   Base
   -------------------------------------------------------------------------- */

*,
*::before,
*::after { box-sizing: border-box; }

/* Clips the bleed of the corner ornament. This has to live on <html>:
   `overflow` on <body> propagates to the viewport rather than clipping, and
   `overflow-x: clip` avoids creating a scroll container, so vertical
   scrolling is untouched. */
html { overflow-x: clip; }

/* Anchor landings.

   The header is sticky, so without this every in-page link — the Key Highlights
   tiles, the menu's Contact and Key Highlights items, the Learn with Swati deep
   links — drops the target under the bar and hides its first 75px. Set as
   scroll-padding on the scroll container rather than scroll-margin on each
   target: one declaration covers every anchor on the site, including ones added
   later, and nothing has to remember to opt in.

   WordPress puts --wp-admin--admin-bar--height on <html> for logged-in users
   and the bar pushes the header down by exactly that, so it belongs in the sum.
   The 1rem is breathing room, so the heading is not flush against the bar.

   scroll-behavior is inside a no-preference query rather than being switched
   off in the reduce query: that way "smooth" is only ever asked for when the
   visitor has expressed no objection, which is the safer default of the two. */
html {
  scroll-padding-top: calc(
    var(--header-h, 75px) + var(--wp-admin--admin-bar--height, 0px) + 1rem
  );
}

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body {
  margin: 0;
  min-height: 100vh;
  color: var(--c-ink);
  font-family: var(--font-body);
  font-size: var(--step-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;

  /* The deck's own paper texture. First declaration is the fallback for
     engines without image-set().

     --page-texture is written into the page by the theme when one has been
     chosen on the settings screen; the image-set below is the default when it
     has not. Deliberately a different custom property from --section-bg:
     custom properties inherit, and a name set here would reach every section
     on the page. */
  background: var(--c-bg) url('../images/page-texture.png') center / cover no-repeat fixed;
  background-image: var(--page-texture, image-set(
    url('../images/page-texture.webp') type('image/webp'),
    url('../images/page-texture.png')  type('image/png')
  ));
}

/* `height: auto` is required because the <img> tags carry intrinsic width and
   height attributes (which reserve layout space and prevent CLS). Without it
   the explicit height wins and images render squashed. */
img, picture { max-width: 100%; height: auto; display: block; }
a { color: inherit; }

/* --------------------------------------------------------------------------
   Header + navigation
   Slides 1 and 3 are the same menu with a different row highlighted, so both
   collapse into this one component. Submenu order is normalised to
   About / Team / Current / Past / Future — the source shuffles it per slide.
   -------------------------------------------------------------------------- */

/* The source slide puts the logo at x=65.7%, on its own line above the menu.
   Here it is moved to the left and shares the row with the menu, which is the
   conventional header arrangement and reclaims the empty space beside it. */
/* Sticky. Two things this depends on:

   1. An opaque background. The header used to let the body texture show
      through, which is fine when it scrolls away but not when content passes
      underneath it. --c-bg is the page's own base colour, so it reads as the
      same paper rather than as an added bar.
   2. `html { overflow-x: clip }` above, NOT `overflow-x: hidden`. `hidden`
      would make <html> a scroll container and silently break `position:
      sticky`; `clip` does not. Do not change it.

   The bar itself runs the full width of the viewport while its contents stay
   on the container, so this is split in two exactly as the footer is: an outer
   element carrying the background, shadow and sticky behaviour, and an inner
   box that caps and centres the row. The outer supplies the gutters via
   padding-inline, so the inner caps at --container rather than --region-max
   and the content box still lands on exactly --container. */
.site-header {
  position: sticky;
  top: 0;
  /* Explicit, so the open menus always paint above page content regardless of
     what stacking contexts get added to <main> later. */
  z-index: 100;

  /* Inline only. The deck sits the logo hard against the top of the canvas
     with no band above or below it, so there is no vertical padding to match —
     the row's height is the logo's height and nothing else. The gutters stay:
     they are what lines the header row up with the page content and the
     footer, and they do not affect the height.

     No box-shadow either. The deck draws no bar, rule or shadow under the
     menu; the shadow that used to be here was the one thing making this read
     as an applied bar rather than as part of the same sheet of paper. The
     opaque background above is what keeps scrolling content from showing
     through, and it does that on its own. */
  padding-inline: var(--gutter);
  background: var(--c-bg);
}

.header-inner {
  max-width: var(--container);
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem clamp(0.75rem, 2.2vw, 2.5rem);
}

/* Left of the menu, sharing its row — the conventional header arrangement,
   kept in preference to the deck's own placement (logo on its own line at
   67.8% of the content box, menu beneath).

   The asset is square (558x558), so this width is also the header's height:
   nothing else in the row comes close, the menu being ~41px. A strict rebase
   of the source's 208px gives 159px, which made the sticky header 224px tall,
   31% of a 720px laptop viewport.

   It was also 35px too wide. At 159px the menu had 1043px to lay out 1078px of
   items, so it wrapped onto a second row *above* the 767px breakpoint, where
   the wide layout was never meant to stack. Narrowing the logo pays twice: it
   lowers the row and hands the menu the width it was short of.

   With the vertical padding gone the header is the logo's height exactly, so
   this is a flat 75px to a 75px bar rather than a clamp: the height is a spec
   figure and should not drift with the viewport. Narrower than the sizes that
   caused the wrap, so the menu keeps its row with room to spare. */
.site-logo {
  flex: 0 0 auto;
  display: block;
  width: 75px;   /* 208px source -> 159px rebase, held to the 75px bar */
  margin: 0;
}

/* `flex-basis: 0` rather than `auto` matters: with `auto` the menu's full
   natural width becomes its basis, which overflows the row and forces the
   whole nav to wrap beneath the logo. At 0 it takes the space that is left
   and wraps its own items only if it genuinely runs out. */
.nav {
  flex: 1 1 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  /* Six gaps between seven items, so this is the second-largest claim on the
     row after the logo — around 80px of the row at tablet widths. Trimmed from
     1.7vw to buy the headroom that keeps the menu on the logo's line and the
     bar at its 75px. The font size is left alone, being a documented
     legibility floor, so spacing is the only give there is. */
  gap: clamp(0.4rem, 1.2vw, 1.35rem);
  list-style: none;
  margin: 0;
  padding: 0;
}

.nav > li { position: relative; }

.nav > li > a {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.4rem 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--step-nav);
  letter-spacing: 0.02em;
  text-decoration: none;
  color: var(--c-nav);
  white-space: nowrap;
}

.nav > li > a:hover,
.nav > li > a:focus-visible { color: var(--c-ink); }

.nav > li > a[aria-current='page'] { color: var(--c-nav-active); }

/* Chevron lifted from the source deck (ppt/media/image4.svg). The source draws
   it pointing right and rotates it 90deg for the top-level items, which is
   exactly what .caret--down does here. `currentColor` makes it follow the link
   colour through hover and the current-page state. */
.caret {
  flex: 0 0 auto;
  width: 0.55em;
  height: 0.55em;
  fill: currentColor;
  overflow: visible;
}

.caret--down { transform: rotate(90deg); }

/* Level-1 dropdown.
   Source panel is 272px wide against 18px text; both rebase together, so the
   em widths below are unchanged and still resolve to the right proportion. */
.dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  width: 15.1em;
  margin: 0;
  padding: 0;
  list-style: none;
  font-family: var(--font-body);
  font-size: var(--step-dropdown);
  background: var(--c-bg-panel);
  box-shadow: var(--shadow-panel);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.15s ease;
  z-index: 20;
}

.nav > li:hover > .dropdown,
.nav > li:focus-within > .dropdown { opacity: 1; visibility: visible; }

.dropdown li { position: relative; }

/* A flat 10px on all four sides, at both levels — the .submenu panel sits
   inside a .dropdown, so this rule matches its anchors too.

   Deliberately not the source's own metric: rows are ~75px tall there against
   18px text, which rebased to 1.3em/1.55em and made the panels considerably
   taller than this. 10px is a spec figure and is in pixels rather than em so
   it does not scale with --step-dropdown.

   The right side used to reserve 2.9em for the flyout chevron. That
   reservation is gone with the flat padding, so the chevron now sits 10px in
   like everything else. The three rows that carry one — Current, Past and
   Future Projects — are short labels in a 15.1em panel and clear it with room
   to spare, but a much longer label on a row that opens a flyout would run
   underneath it. Give that row its own padding-right if it ever happens. */
.dropdown a {
  display: block;
  position: relative;
  padding: 10px;
  text-decoration: none;
  color: var(--c-ink);
  border-bottom: 1px solid var(--c-rule-dropdown);
}

.dropdown > li:last-child > a { border-bottom: 0; }

.dropdown a:hover,
.dropdown a:focus-visible,
.dropdown a[aria-current='page'] { background: var(--c-row-active); }

/* Flyout chevrons sit at the far right of their row, and are proportionally
   larger than the top-level ones — 12.5x18 against 18px text in the source.
   Positioned absolutely so a long label can wrap without pushing them out of
   line. No transform conflict with .caret--down: that class only ever appears
   on the top-level items, never inside a dropdown. */
.dropdown .caret {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  width: 1.09em;
  height: 1.09em;
}

/* Source flyout is 350px wide and its top aligns with the parent row, which
   is what `top: 0` gives. Its dividers are white rather than blush. */
.submenu {
  position: absolute;
  top: 0;
  left: 100%;
  width: 19.4em;
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--c-bg-panel);
  box-shadow: var(--shadow-panel);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.15s ease;
}

.dropdown li:hover > .submenu,
.dropdown li:focus-within > .submenu { opacity: 1; visibility: visible; }

/* Nav items near the right edge would push their flyout off screen, so those
   open leftward instead. "Cayenne Pepper" — the only item with level-2
   flyouts — is third from last. */
.nav > li:nth-last-child(-n+3) .submenu { left: auto; right: 100%; }

/* Padding comes from `.dropdown a` above and is the same 10px at this level, so
   the only thing left here is the divider colour — blush at level 1, white in
   the flyouts. */
.submenu a {
  border-bottom-color: var(--c-rule-submenu);
}

.submenu > li:last-child > a { border-bottom: 0; }

/* The year/format label is bold but stays inline — in the source it simply
   wraps onto a second line when the title is long ("The Warrior Queen of
   Jhansi / Movie 2019") and sits on one line when it is short ("Aai's
   Recipes 2012"). Forcing display:block would break that. */
.submenu .meta { font-weight: 700; }

/* --------------------------------------------------------------------------
   Menu disclosure

   The seven top-level items need ~815px of row. Measured, the menu stops
   fitting beside the logo at about 1000px and drops underneath it (header
   115px), then wraps to two rows below 800 (154px), three below 500 (191px)
   and five at 320 (269px) — on a bar that is `position: sticky`, so a phone
   loses most of a screen to it permanently.

   Below the breakpoint the list collapses behind a button. Everything here is
   scoped to `.js`, set on <html> by an inline script in the head: with no
   scripting the menu stays exactly as it was, tall but complete, rather than
   collapsing behind a control that cannot open it.

   The panel is absolute against the sticky header, so opening it does not grow
   the bar — the header holds the logo's 75px at every width.
   -------------------------------------------------------------------------- */

/* Read by js/nav.js, so the breakpoint lives in one place. */
.site-header { --nav-collapse-at: 1024px; }

/* --------------------------------------------------------------------------
   Admin bar

   WordPress's toolbar is `position: fixed` at `z-index: 99999` for logged-in
   users. A `position: sticky` header pinned to `top: 0` slides underneath it as
   soon as the page scrolls, and everything in the header's top 32-46px stops
   being clickable — taps land on the toolbar instead. The menu button sits in
   exactly that band, which is why it stopped opening once scrolled.

   Invisible to logged-out visitors, so it never showed up in testing until
   someone tried it while signed in.

   Below 600px WordPress lets the toolbar scroll away with the page rather than
   keeping it fixed, so the offset goes back to nothing.
   -------------------------------------------------------------------------- */
body.admin-bar .site-header { top: var(--wp-admin--admin-bar--height, 32px); }

@media screen and (max-width: 600px) {
  body.admin-bar .site-header { top: 0; }
}

/* Both controls are off by default and switched on inside the breakpoint below.
   The base rule matters for .nav-sub-toggle in particular: js/nav.js builds
   those buttons once, and they stay in the DOM if the window is then widened
   past the breakpoint — without this they would reappear on the desktop row as
   raw browser buttons. It is also what stops them ever rendering as UA chrome
   if the styled rule is missed for any reason. */
.nav-toggle,
.nav-sub-toggle {
  display: none;
  appearance: none;
  -webkit-appearance: none;
  padding: 0;
  border: 0;
  background: transparent;
}

@media (max-width: 1024px) {
  /* Icon only — square, and sized to a finger rather than to the glyph. */
  .js .nav-toggle {
    display: grid;
    place-items: center;
    margin-left: auto;
    width: 44px;
    height: 44px;
    border-radius: 4px;
    color: var(--c-nav);
    cursor: pointer;
  }

  .js .nav-toggle:hover,
  .js .nav-toggle:focus-visible { color: var(--c-ink); }

  /* Three bars, drawn rather than exported — the deck has no hamburger. */
  .js .nav-toggle__bars {
    position: relative;
    display: block;
    width: 1.3em;
    height: 2px;
    background: currentColor;
  }

  .js .nav-toggle__bars::before,
  .js .nav-toggle__bars::after {
    content: "";
    position: absolute;
    left: 0;
    display: block;
    width: 100%;
    height: 2px;
    background: currentColor;
  }

  .js .nav-toggle__bars::before { top: -6px; }
  .js .nav-toggle__bars::after  { top: 6px; }

  .js .site-header nav {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    display: none;
    max-height: calc(100vh - 100%);
    max-height: calc(100svh - 100%);
    overflow-y: auto;
    padding: 0.25rem var(--gutter) 1rem;
    background: var(--c-bg-panel);
    box-shadow: var(--shadow-panel);
  }

  .js .site-header nav.is-open { display: block; }

  .js .nav {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }

  .js .nav > li > a {
    justify-content: space-between;
    padding: 0.75rem 0;
    border-bottom: 1px solid var(--c-rule-dropdown);
  }

  .js .nav > li:last-child > a { border-bottom: 0; }

  /* Panels sit in the flow as disclosure lists rather than hovering layers. */
  .js .dropdown,
  .js .submenu {
    position: static;
    display: none;
    width: auto;
    padding-left: 1rem;
    opacity: 1;
    visibility: visible;
    background: transparent;
    box-shadow: none;
  }

  /* A row per item: link and its expander side by side, the panel beneath. */
  .js .nav > li,
  .js .dropdown li {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
  }

  .js .nav > li > a,
  .js .dropdown li > a { flex: 1 1 auto; }

  .js .dropdown,
  .js .submenu { flex-basis: 100%; }

  /* Opened by the expander, not by hover — a finger has no hover, and these
     parents are real links, so tapping one has to keep navigating. */
  .js .dropdown[data-open],
  .js .submenu[data-open] { display: block; }

  .js .dropdown a { padding: 0.6rem 10px; }

  /* The expander js/nav.js inserts after each parent link. Sized to a finger
     and given its own hit area so it never competes with the link beside it. */
  .js .nav-sub-toggle {
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    display: grid;
    place-items: center;
    border: 0;
    background: transparent;
    color: inherit;
    cursor: pointer;
  }

  .js .nav-sub-toggle::before {
    content: "";
    width: 0.5em;
    height: 0.5em;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: translateY(-0.15em) rotate(45deg);
    transition: transform 0.15s ease;
  }

  .js .nav-sub-toggle[aria-expanded="true"]::before {
    transform: translateY(0.1em) rotate(-135deg);
  }

  /* The chevron inside the link would be a second, non-interactive copy of the
     one on the button. */
  .js .nav .caret { display: none; }
}

@media (max-width: 1024px) and (prefers-reduced-motion: reduce) {
  .js .nav-sub-toggle::before { transition: none; }
}

/* --------------------------------------------------------------------------
   One flat picture in place of a composed cluster

   Several bands build their photo group out of separate images plus the CSS
   that frames, rotates, masks and shadows them. Where a band offers a "whole
   thing as one image" field and it has been filled in, that arrangement is not
   drawn at all — the file already has it — and what is left to do is get out of
   the way: no frame, no shadow, no aspect lock, nothing that would fight
   proportions the picture already carries.

   The cap below the breakpoint is the one thing kept, and it is the same
   34rem .bio-cluster and .pe-cluster hold themselves to: in one column a
   full-width photo group dwarfs the copy above it.
   -------------------------------------------------------------------------- */

.media-single {
  margin: 0;
}

.media-single img { width: 100%; }

/* Visually hidden but still read. The theme had no such utility before the
   video dialog needed one — the play link's own text is the only name a
   screen reader gets when the picture's alt is empty, so it cannot simply be
   display:none. The 1px-clip form is used rather than a bare text-indent
   because the latter is announced inconsistently once the element is inside a
   link. */
.screen-reader-text {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------------------
   A picture that opens a video
   A band carrying a video address renders its single picture inside a link.
   Nothing here changes the picture's own size or place: .media-single__play is
   a plain block wrapper, so the band lays out exactly as it did before the
   link existed and a band with no video is untouched.
   -------------------------------------------------------------------------- */

.media-single__play {
  position: relative;
  display: block;
  cursor: pointer;
}

.media-single__play:focus-visible {
  outline: 2px solid var(--c-ink, #6b5e54);
  outline-offset: 3px;
}

/* The play glyph that used to sit in the middle of this picture was removed on
   request. Nothing replaces it: the picture is unmarked and the whole of it is
   the control, so the pointer cursor above and the focus ring are the only
   things saying so. Kept deliberately rather than tidied away — with no glyph
   they are all that is left. */

/* --------------------------------------------------------------------------
   A mark that shares its row's link

   Several bands set a play mark or an icon BESIDE a link rather than inside it
   — the news cards, the hero link rows, the film link rows, the social row.
   Every one of those rows is a flex container with the mark and the <a> as
   sibling items, and the mark was not clickable: the thing that most looks like
   a button was the one part of the row that did nothing.

   swati_icon_link() wraps the mark in an anchor to the row's own address. The
   anchor is inline-flex so it shrink-wraps the mark and lays out as the mark
   did — the row's gap and align-items still see one item of the same size, so
   nothing moves.

   It carries aria-hidden and tabindex="-1" on purpose. The address is already
   named by the real link beside it, so announcing it twice and stopping at it
   twice on the way through would make the page worse for exactly the people
   this kind of change is usually meant to help. Hidden and unfocusable, it is a
   second hit area for the same destination and nothing more — which is what a
   decorative mark should be. aria-hidden on a focusable element would be a
   violation; tabindex="-1" is what makes the pair legitimate.
   -------------------------------------------------------------------------- */
.icon-link {
  display: inline-flex;
  color: inherit;
  text-decoration: none;
}

/* --------------------------------------------------------------------------
   The video dialog
   Built once by js/video-modal.js and reused. Hidden with the [hidden]
   attribute, so it is inert to assistive technology as well as invisible.
   -------------------------------------------------------------------------- */

.video-modal[hidden] { display: none; }

.video-modal {
  position: fixed;
  inset: 0;
  z-index: 999;
  display: grid;
  place-items: center;
  padding: clamp(1rem, 4vw, 3rem);
}

.video-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(20, 17, 15, 0.82);
}

/* 16:9 is the shape of both services this rewrites addresses for. width/height
   are capped together so a tall viewport cannot stretch the box past the
   screen. */
.video-modal__box {
  position: relative;
  width: min(100%, 1100px);
  aspect-ratio: 16 / 9;
  max-height: 100%;
  background: #000;
  box-shadow: 0 18px 60px rgba(0, 0, 0, 0.5);
}

/* iframe for a hosted player, video for a file served from this site — see
   isFile() in js/video-modal.js. object-fit keeps a file whose shape is not
   16:9 inside the box instead of stretching it. */
.video-modal__frame,
.video-modal__frame iframe,
.video-modal__frame video {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

.video-modal__frame video { object-fit: contain; background: #000; }

/* Outside the box on a roomy screen, inside it when there is no room above. */
.video-modal__close {
  position: absolute;
  top: -2.6rem;
  right: 0;
  width: 2.1rem;
  height: 2.1rem;
  display: grid;
  place-items: center;
  font-size: 1.7rem;
  line-height: 1;
  color: #fff;
  background: none;
  border: 0;
  cursor: pointer;
}

.video-modal__close:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

@media (max-height: 520px), (max-width: 600px) {
  .video-modal__close {
    top: 0.25rem;
    right: 0.25rem;
    background: rgba(0, 0, 0, 0.55);
    border-radius: 50%;
  }
}

/* The page behind must not scroll under the dialog. */
.has-video-modal,
.has-video-modal body { overflow: hidden; }

@media (max-width: 1024px) {
  .media-single {
    max-width: 34rem;
    margin-inline: auto;
  }
}

/* --------------------------------------------------------------------------
   A chosen section background paints the whole band

   Each band's own rule reads `var(--section-bg, <the deck's artwork>)`, so an
   editor's image replaces whatever that band was painting. The trouble is that
   it also inherits how that band painted it, and the deck treats them very
   differently: home's hero is a masked watermark at 45% opacity, Woven Heritage
   Textiles is a 53%-wide panel down the left at 20% opacity, the biography band
   paints two layers at two different sizes. Dropped into those, a chosen
   photograph came out faint, cropped to a sliver, or drawn twice — which is why
   the images looked like they had not been applied at all.

   So when a band carries a chosen background, it is painted here instead: one
   layer, full width, centred, covering. The deck's own treatment is untouched
   when nothing has been chosen, which is still the usual case.

   The attribute selector is doing real work rather than being clever.
   --section-bg is only ever written inline, by swati_style_attr(), and only
   when swati_background_value() returned something — an empty value is skipped,
   never written as empty. So "has this attribute" is exactly "has an editor
   chosen an image here", which is the thing that needs styling and which no
   class currently records.

   `body ` is on the front for specificity, not for scope: the per-page sheets
   are enqueued after css/site.css, so at (0,1,0) their band rules would win on
   source order. At (0,2,0) this lands wherever it is loaded from.
   -------------------------------------------------------------------------- */

body [style*="--section-bg"] {
  background-image: var(--section-bg);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

/* Two bands paint their deck artwork through a pseudo-element rather than on
   themselves — home's hero watermark and the textiles panel. Both read
   --section-bg too, so without this the chosen image would paint twice: once
   full width by the rule above, and once more through the decorative layer,
   masked or faded. Only ever matches the pseudo of the element carrying the
   property, so nothing else drawn with ::before is touched. */
body [style*="--section-bg"]::before {
  background-image: none;
}

/* --------------------------------------------------------------------------
   Page shell
   -------------------------------------------------------------------------- */

/* `:where(body)` is not decoration — it is what stops this rule matching <body>
   itself. WordPress's body_class() puts `page` on the body of every Page, and a
   bare `.page` selector therefore capped the whole document at --region-max and
   centred it: every full-bleed band boxed to 1300px on any viewport wider than
   that, while the body's own texture still propagated to the full canvas. It is
   invisible below 1330px, which is why it survived until someone opened the
   site on a 2560px monitor.

   :where() contributes no specificity, so this matches exactly what `.page` did
   — minus the body — and nothing that overrides it needs revisiting. Do not
   simplify it back, and do not use `:not(body)`, which would add specificity
   and start beating the per-page rules that sit alongside this class. */
:where(body) .page {
  position: relative;
  max-width: var(--region-max);
  margin-inline: auto;
  padding: clamp(1.25rem, 4.51vw, 2.67rem) var(--gutter) clamp(2.5rem, 6.76vw, 4.58rem);
}

/* Decorative corner ornament, 21.1% of the content width as in the source.
   Purely decorative, so it is hidden from AT. */
.ornament-tr {
  position: absolute;
  top: 0;
  right: 0;
  width: 21.1%;
  max-width: 275px;
  pointer-events: none;
  z-index: 0;
}

.page > .split { position: relative; z-index: 1; }

.split {
  display: grid;
  grid-template-columns: var(--split, 1fr 1fr);
  gap: clamp(1.25rem, 4.73vw, 3.81rem);
  align-items: var(--split-align, center);
}

/* --------------------------------------------------------------------------
   "View more"
   Not in the deck at all. Each of the six hero bands is the top of its own page
   there, so none of them has anywhere to send anybody; used as summaries on the
   home page they all do. Six bands share it, so it lives here rather than being
   written out six times in six section sheets.

   A link wearing a button, never a <button>: it goes somewhere, so it has to be
   middle-clickable, copyable and openable in a new tab.

   #E8D8CF is the menu's divider wash. The reference render uses the browser
   chrome's #DDCCC2, which ties more prettily to the mosaics these sit beside,
   but it measures 4.02:1 against the ink and the label is well under the 24px
   that would make it large text — so it fails AA. This is 4.52:1: same family,
   one step lighter. Bands that need the button indented to line up with
   something say so themselves; see .pd-hero .section-more.
   -------------------------------------------------------------------------- */

.section-more {
  display: inline-block;
  margin-top: clamp(0.9rem, 1.85vw, 24px);
  padding: 0.52em 1.5em;
  border-radius: 999px;
  background: #e8d8cf;
  color: var(--c-ink);
  font-size: clamp(0.85rem, 1.35vw, 1.1rem);
  line-height: 1.4;
  text-decoration: none;
  transition: background-color 0.15s ease;
}

.section-more:hover,
.section-more:focus-visible { background: #dcc7bb; }

/* --------------------------------------------------------------------------
   Typography
   -------------------------------------------------------------------------- */

.display-title {
  margin: 0 0 clamp(0.85rem, 2.14vw, 1.72rem);
  font-family: var(--font-display);
  font-size: var(--step-display);
  font-weight: 700;
  line-height: 1.05;
  color: var(--c-ink);
}

.engraved-title {
  margin: 0 0 1.25rem;
  font-family: var(--font-engraved);
  font-size: var(--step-title);
  font-weight: 400;
  line-height: 1.15;
  color: var(--c-ink);
}

.body-copy p { margin: 0 0 1.3em; }
.body-copy p:last-child { margin-bottom: 0; }

.more-link {
  display: inline-block;
  margin-top: 1.5rem;
  font-weight: 700;
  text-decoration: none;
  border-bottom: 1px solid var(--c-rule);
}

.more-link:hover,
.more-link:focus-visible { border-bottom-color: var(--c-ink); }

/* The same affordance as a <button>, for a More that opens a dialog rather
   than going anywhere. Shared by the About introduction, each person on the
   team and the Blue Boy band — one control and one dialog shape, wherever a
   piece is longer than the page gives it.

   Only the browser's own button chrome is undone; every other value is
   inherited so this and .more-link read as one control. */
.more-dialog__open {
  padding: 0;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--c-rule);
  font: inherit;
  font-weight: 700;
  color: inherit;
  cursor: pointer;
}

/* Without scripting the dialog cannot open, so the control is not offered —
   the same bargain .nav-toggle and the header search strike, see the note in
   header.php.

   Nothing is lost by hiding it. The paragraphs the page holds back are still in
   the document, inside the <dialog>, so a reader without scripting gets the
   whole piece rather than a truncated one. That is why the dialog carries the
   text as markup instead of being filled by script when it opens. */
html:not(.js) .more-dialog__open { display: none; }

html:not(.js) .more-dialog,
html.no-dialog .more-dialog {
  display: block;
  position: static;
  width: auto;
  max-height: none;
  background: none;
  color: inherit;
}

html:not(.js) .more-dialog__box,
html.no-dialog .more-dialog__box { padding: 0; overflow: visible; }
html:not(.js) .more-dialog__close,
html.no-dialog .more-dialog__close { display: none; }

/* And the opening paragraphs stand down, because the dialog beneath them is
   the same text from its first word. Showing both would print the first two
   twice. With scripting this is the copy and the dialog is closed; without it
   they swap. */
html:not(.js) .more-preview,
html.no-dialog .more-preview { display: none; }

/* --------------------------------------------------------------------------
   The More dialog
   -------------------------------------------------------------------------- */

.more-dialog {
  width: min(46rem, calc(100vw - 2 * var(--gutter)));
  max-height: min(80vh, 46rem);
  padding: 0;
  border: 0;
  border-radius: 2px;
  background: var(--c-page, #ecebe8);
  color: var(--c-ink);
  overflow: hidden;
}

.more-dialog::backdrop { background: rgba(60, 51, 51, 0.55); }

.more-dialog__box {
  position: relative;
  max-height: inherit;
  padding: clamp(1.5rem, 3.4vw, 2.75rem);
  padding-right: clamp(2.75rem, 5vw, 4rem);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Sticky rather than absolute: the box scrolls, and a close control that
   scrolls out of reach on a long piece is a trap. */
.more-dialog__close {
  position: sticky;
  top: 0;
  float: right;
  margin: -0.35em -1.4em 0 0.6em;
  padding: 0;
  width: 1.6em;
  height: 1.6em;
  background: none;
  border: 0;
  font-size: 1.6rem;
  line-height: 1;
  color: var(--c-ink);
  cursor: pointer;
}

.more-dialog__close:hover,
.more-dialog__close:focus-visible { color: var(--c-nav-active); }

/* --------------------------------------------------------------------------
   Home — slide 2 of old.pptx
   Source sizes at the 1920 canvas: headline 59pt (78.7px), title 60pt (80px),
   byline and blurb 29pt (38.7px). All rebased by 0.763 like the rest of the
   site and expressed against the 1300px container.
   -------------------------------------------------------------------------- */

/* Faint Chola-empire map with a Nataraja figure, full-bleed as on the slide.
   It cannot sit on .home itself — that element is capped at --region-max — so
   it goes on a full-width wrapper around it.

   Deliberately NOT `width: 100vw`: on a page with a vertical scrollbar, 100vw
   is the viewport *including* the scrollbar, which pushed the layer ~8px past
   the content box and made the page horizontally scrollable. A plain
   block-level wrapper is the viewport's client width by definition, so this
   is scrollbar-safe with no measuring.

   The asset is pre-cropped to exactly the window Canva shows. The design's
   background rect carries an imageBox of (-360.64,-329.14 2344.28x2041.48)
   inside a 1920x1080 frame — i.e. the image is placed larger than the frame
   and offset, so only x 15.4%..97.3% and y 16.1%..69.0% of it is visible.
   That window is 16:9, so nothing is distorted; the Nataraja figure sits
   below it and is deliberately out of frame.

   The PowerPoint export loses that crop — it writes the whole 2963x2581 image
   with `stretch` and no `srcRect`, which is why the export looks squashed and
   why fitting the full asset never matched. Canva's element data is the
   authority here, not the PPTX.

   Element opacity in the design is 32%, already baked into the exported
   asset's alpha (~27%). It composites to a light warm grey that holds body
   copy above the AA floor.

   SIZING. This used to be `center center / cover`, on the reasoning that an
   already-cropped asset means `cover` re-crops nothing meaningful. That only
   holds while the section is itself 16:9, and it never is: the section's
   height is driven by its content, not by the slide. Measured against the
   asset's own 1400x788, `cover` was showing 77% of the artwork at 1280px,
   49% at 1024, 29% at 768 and 9% at 375 — at phone widths the map was a
   narrow vertical slice with no readable geography left. It also cut the
   right edge (Indonesia, and the eastern end of the trade route) while still
   leaving part of the asset's blank left margin on screen, so the frame read
   as off-centre at every width.

   Sizing to the section's WIDTH instead keeps the slide's horizontal
   composition exact at every viewport — the same left-to-right framing the
   design has, blank left margin included — and lets the height fall where it
   falls. The layer is a ::before pinned to the top and given the asset's own
   aspect ratio, so `100% 100%` fills it without distortion.

   The section is taller than that layer, so the artwork would otherwise stop
   in a hard horizontal line partway down. The mask dissolves the last third
   into the page texture instead. */
.home-bleed {
  position: relative;
  isolation: isolate;
}

/* Slide 2 draws this as "Freeform 2", and every number below is read off that
   shape rather than estimated:

     <a:off x="0" y="0"/> <a:ext cx="18288000" cy="10287000"/>
       — 1920x1080, the whole slide canvas. Full-bleed and full-height, under
         the hero AND the photograph strip, which is where the source puts it.

     <a:alphaModFix amt="31999"/>
       — 32% opacity, applied to the FILL. It is not baked into image11.png,
         which is why it has to be applied here. Painting the artwork at its
         own strength, as this layer did, is roughly three times the design and
         puts a legible map of the Bay of Bengal behind the book title.

     <a:stretch><a:fillRect l="-17142" t="-33392" r="-4955" b="-55632"/>
       — every inset negative, so the picture sits OUTSIDE the shape on all
         four edges. That is PowerPoint's way of writing what CSS calls
         `cover`: 2963x2581 scaled to 2344x2042 (its own ratio, undistorted)
         with the slide showing the window that lands inside it. Worked back
         through the 0.7912 scale, that window is (416, 456, 2427x1365) of the
         source — which is exactly the crop images/home-watermark.png already
         holds. So `cover` here reproduces the slide at the design ratio and
         crops the same way the slide itself would at any other.

   The old `100% 100%` stretched the artwork to whatever box it was given and
   the box was capped at 1465px and centred, which is what left the hard
   vertical seam down the left of the hero. */
.home-bleed::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;

  /* --section-bg is set on .home-bleed rather than here — a pseudo-element
     takes no inline style, but custom properties inherit into it. */
  /* The 2x source is the PowerPoint original (ppt/media/image11.png, 2963x2581)
     cropped to the window worked out above. Offered by resolution rather than
     served to everyone: a 1x screen wants the small file and only a high-DPI
     one benefits from the 480KB. No 2x PNG — a browser with no webp support on
     a 2x screen is not a real population, and it would have cost 2.9MB. */
  background: url('../images/home-watermark.png') center / cover no-repeat;
  background-image: var(--section-bg, image-set(
    url('../images/home-watermark@2x.webp') type('image/webp') 2x,
    url('../images/home-watermark.webp')    type('image/webp') 1x,
    url('../images/home-watermark.png')     type('image/png')  1x
  ));

  /* Kept as opacity on the layer rather than burnt into the asset because that
     is where the source keeps it, because it stays tunable, and because a
     background chosen on the options screen then lands at the same strength as
     the deck's own artwork instead of overpowering it.

     0.45 rather than the 0.32 the slide XML gives. The client's own reference
     render of slide 2 shows the map appreciably more present than a literal
     32% composite does — the .pptx is an export of a Canva original, so it is
     the export that is most likely to have drifted, not the reference. Matching
     the reference was the instruction.

     Measured before settling on it. Ink #6B5E54 is 5.25:1 on the bare page; at
     0.45 the worst pixel behind the copy is 3.41:1 and the 1st percentile is
     3.93:1. Every piece of hero text at this width is large by WCAG — the blurb
     and byline are 29.4px, the title 50-61px — so the bar is 3.0:1 and there is
     room to spare. 0.60 was rejected on the same measurement: it puts the worst
     case at 2.90:1, under the bar. The narrow breakpoint has its own value; see
     the note there. */
  opacity: var(--watermark-alpha, 0.45);

  /* The slide ends at its own edge; the page does not. A short fade over the
     last eighth stops the artwork meeting the band below on a hard line.
     Deliberately much later than the 68% it used to be — the watermark was
     gone well before the photograph strip it is supposed to sit behind. */
  -webkit-mask-image: linear-gradient(to bottom, #000 88%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 88%, transparent 100%);
}

/* Source indents this 11.9% into the content column rather than aligning it
   to the gutter. Reproduced, and dropped at the tablet breakpoint. */
.home-headline {
  margin: 0 0 clamp(1rem, 2.9vw, 2.36rem);
  margin-left: 11.9%;
  font-family: var(--font-engraved);
  font-size: clamp(1.4rem, 4.62vw, 3.75rem);
  line-height: 1.2;
  text-transform: uppercase;
  color: var(--c-ink);
}

/* Bharatanatyam centres it instead, on request. Scoped rather than changed in
   place: the home page carries this band too, with its own headline, and the
   11.9% indent above is measured off slide 2 and stays that page's.

   The body class comes from the map in functions.php — the mechanism that was
   already there for womens-advocacy, which is exactly this problem. */
body.bharatanatyam .home-headline {
  margin-left: 0;
  text-align: center;
}

/* The title is set at TWO sizes in the source, not one. TextBox 16 on slide 2
   holds two paragraphs: "Bharatanatyam:" at 60.42pt and "A Dance of Patrons,
   Gods & Brides" at 48.96pt — the book's name leads and its subtitle sits under
   it. Rebased by the usual 0.763 that is 61.5px over 49.8px, and the build had
   both lines at the first figure, which is why the second ran wider and heavier
   than the design draws it.

   ::first-line rather than a span around the first line, because the field
   allows <br> and nothing else — wrapping it would mean widening what an editor
   may put in a title, to buy markup that only exists to be styled. The <br> in
   the content is what makes the first formatted line exactly the book's name,
   and if a very narrow viewport ever broke it differently the emphasis would
   still land on the line that leads, which is the point of it. */
.home-title {
  margin: 0 0 clamp(0.75rem, 2.2vw, 1.8rem);
  font-family: var(--font-display);
  font-size: clamp(1.3rem, 3.8vw, 3.11rem);      /* 48.96pt -> 49.8px */
  font-weight: 700;
  line-height: 1.15;
  color: var(--c-ink);
}

.home-title::first-line {
  font-size: clamp(1.6rem, 4.69vw, 3.81rem);     /* 60.42pt -> 61.5px */
}

.home-byline,
.home-blurb {
  font-size: clamp(0.95rem, 2.27vw, 1.84rem);
  line-height: 1.5;
  color: var(--c-ink);
}

.home-byline { margin: 0 0 clamp(1rem, 2.6vw, 2.1rem); }
.home-blurb  { margin: 0; }

.home-cover { margin: 0; }

/* --------------------------------------------------------------------------
   Intro — slide 3 of old.pptx
   Source geometry (1920x1080), rebased by 0.763 like the rest of the site:
     background  full bleed, the same floral damask used elsewhere
     "Swati"     x=126 y=347  106pt   |  "gupté"  x=610 y=397  64pt
     "Bhisé"     x=297 y=496  106pt   |  roles    x=338 y=701  24pt centred
     phone frame x=1157 y=50  485x980 |  screen   x=1187 y=79  425x922

   Coordinates come from the PowerPoint, cross-checked against the Canva
   element data. Note Canva reports `pos` as y,x — transposed — so its numbers
   have to be swapped before use.
   -------------------------------------------------------------------------- */

/* The damask asset is pre-flattened onto #ebebeb and carries no alpha — that
   took it from 747KB to 59KB, since WebP alpha was costing more than the
   artwork itself. `cover` guarantees it always fills the section, so nothing
   shows through and the flattening is invisible.

   Consequence worth knowing: changing the colour below will NOT change the
   damask, because that colour is baked into the image. Re-flatten the asset
   if this ever changes. */
/* --section-bg is written onto the band by its template part when a background
   has been chosen for that section; the image-set is the deck's own asset and
   the default. Same pattern on every bleed band. */
.intro-bleed {
  background: #ebebeb url('../images/damask-full.png') center / cover no-repeat;
  background-image: var(--section-bg, image-set(
    url('../images/damask-full@2x.webp') type('image/webp') 2x,
    url('../images/damask-full.webp')    type('image/webp') 1x,
    url('../images/damask-full.png')     type('image/png')  1x
  ));
}

.intro { padding-block: clamp(2rem, 6vw, 4.9rem); }

/* The three name words are staggered in the source rather than set as plain
   lines: "gupté" sits to the right of "Swati" at ~60% its size, and "Bhisé"
   drops to a second line indented 1.21em. */
.name-display {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1.06;
  text-transform: uppercase;
  color: var(--c-ink);
}

.name-display__row { display: block; }

/* The indent must be em of the large word (108px), not of .name-display,
   which inherits the much smaller body size — 1.2em there is only a quarter
   of the intended offset. */
.name-display__row--indent .name-display__lg { margin-left: 1.2em; }

.name-display__lg { font-size: clamp(2rem, 8.31vw, 6.75rem); }   /* 141 -> 108px */
.name-display__sm {
  font-size: clamp(1.2rem, 5vw, 4.06rem);                        /*  85 ->  65px */
  margin-left: 0.3em;
}

/* Centred block sitting under the name, indented as in the source. */
.name-roles {
  margin: clamp(1rem, 3.2vw, 2.6rem) 0 0 1.6em;
  max-width: 15em;
  font-size: var(--step-body);
  line-height: 1.4;
  text-align: center;
  color: var(--c-ink);
}

/* Phone mockup, drawn in CSS rather than exported as an image so it stays
   sharp. Proportions are the source's: 485x980 frame, screen inset 30px left
   and 29px top, corner radius ~13.6% of the frame width. */
.phone {
  position: relative;
  width: 100%;
  max-width: 370px;                 /* 485 x 0.763 */
  aspect-ratio: 485 / 980;
  /* Left-aligned in its column, as in the source — centring pushes it ~5%
     too far right. Re-centred below the tablet breakpoint. */
  margin-inline: 0 auto;
  padding: 2.96% 6.2%;              /* 29/980 and 30/485 */
  border-radius: 13.6% / 6.7%;
  background: #2e2e2e;
  box-shadow: 0 10px 30px rgba(107, 94, 84, 0.28);
}

.phone__screen {
  width: 100%;
  height: 100%;
  border-radius: 9% / 4.4%;
  overflow: hidden;
  background: #000;
}

.phone__screen img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Speaker slot and camera dot, positioned as in the source. */
.phone::before,
.phone::after {
  content: '';
  position: absolute;
  background: #555;
  border-radius: 999px;
}

.phone::before {                    /* speaker: 66x8 at 207,37 within the frame */
  left: 42.7%;
  top: 3.8%;
  width: 13.6%;
  height: 0.8%;
}

.phone::after {                     /* camera: 12x12 at 294,35 */
  left: 60.6%;
  top: 3.6%;
  width: 2.5%;
  aspect-ratio: 1;
  height: auto;
}

/* --------------------------------------------------------------------------
   Biography — slide 4 of old.pptx
   Source geometry (1920x1080), rebased by 0.763:
     floral overlay  x=0 y=0 890x1080, scaled to full height
     "Biography"     x=108 y=197  128pt, line-height 0.77
     bio copy        x=108 y=359  w=911  32pt, line-height 1.5
     ellipse portrait x=1055 y=231 370x551, rotated 12.54deg, 17px white stroke
     frame (top)     x=1326 y=42  472x560   photo inset 6.8% / 4.6% / 12.2%
     frame (bottom)  x=1282 y=592 377x447
     CPP logo        x=1659 y=909 208x132
   -------------------------------------------------------------------------- */

/* The floral appears TWICE in the source, and both are needed:

     page background  imageBox=(-625.56, 0  1920x2331.13)  -> spans the full
                      width, scaled well past the frame and offset upwards, so
                      only a soft middle band of the artwork shows
     RECT at 0,0      890x1080                             -> a second, much
                      closer copy anchored to the left edge

   The first layer was missing, which is why the right-hand side looked bare
   against the design. Layer order below is front-to-back, so the left copy
   sits over the wide one.

   The wide layer's offset works out to exactly `center`: with
   `background-size: 100% auto` the image is 2330px tall in a 1080px box, and
   background-position-y 50% resolves to 0.5 x (1080 - 2330) = -625px. */
.bio-bleed {
  background:
    url('../images/bio-floral.png') left   top    / auto 100% no-repeat,
    url('../images/bio-floral.png') center center / 100% auto no-repeat,
    #e7d7ce;
  /* Two layers of the same asset — left-anchored full height, and centred full
     width. A chosen background replaces both, so it reads the same way. */
  background-image:
    var(--section-bg,
        image-set(url('../images/bio-floral.webp') type('image/webp'),
                  url('../images/bio-floral.png')  type('image/png'))),
    var(--section-bg,
        image-set(url('../images/bio-floral.webp') type('image/webp'),
                  url('../images/bio-floral.png')  type('image/png')));
}

.bio { padding-block: clamp(2rem, 6vw, 4.9rem); }

.bio-title {
  margin: 0 0 clamp(0.75rem, 2.2vw, 1.8rem);
  font-family: var(--font-display);
  font-size: clamp(2.25rem, 7.52vw, 6.10rem);   /* 128 -> 97.7px */
  font-weight: 700;
  line-height: 0.9;
  color: var(--c-ink);
}

.bio-copy {
  margin: 0;
  font-size: var(--step-body);
  line-height: 1.5;
  color: var(--c-ink);
}

/* The photo cluster. Items are placed absolutely as percentages of this box,
   which carries the source cluster's own 757x999 proportions so everything
   scales together. */
.bio-cluster {
  position: relative;
  aspect-ratio: 757 / 999;
}

.bio-cluster > * { position: absolute; }

/* White card with a polaroid-style deeper bottom edge, as in the source
   frame asset — which also carries its shadow down and to the left. */
.bio-frame {
  /* <figure> carries a UA default of `margin: 1em 40px`, which offsets an
     absolutely positioned frame by 40px horizontally and ~24px vertically. */
  margin: 0;
  padding: 4.6% 6.8% 12.2%;
  background: #fff;
  box-shadow: -6px 8px 18px rgba(107, 94, 84, 0.30);
}

.bio-frame img { width: 100%; display: block; }

.bio-frame--top    { left: 35.8%; top: 0;     width: 62.4%; }
.bio-frame--bottom { left: 30.0%; top: 55.1%; width: 49.8%; }

/* Ellipse mask tilted 12.54deg with the photo counter-rotated inside it, so
   the frame tilts but the face stays upright. The scale compensates for the
   corners the counter-rotation would otherwise expose — the source does the
   same thing by drawing the image 1.29x the shape. */
.bio-portrait {
  left: 0;
  top: 18.9%;
  width: 48.9%;
  aspect-ratio: 370 / 551;
  overflow: hidden;
  border: clamp(5px, 1vw, 13px) solid #fff;   /* source stroke 17 -> 13px */
  border-radius: 50%;
  transform: rotate(12.54deg);
  box-shadow: 0 14px 26px rgba(107, 94, 84, 0.29);
}

.bio-portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: rotate(-12.54deg) scale(1.29);
}



/* --------------------------------------------------------------------------
   Carousel
   The source lays these eight images out as a static strip, eight across: on
   slide 2 they run x=108..1812 at y=882, each ~204x135 with a ~10px gap, which
   is the full content width divided by eight. Eight per view is therefore the
   design's own count and is what the widest breakpoint uses; below 1200px they
   would fall under ~140px each, so the ladder steps down from there.

   The track is a scroll-snap container and works with no JavaScript at all —
   touch, trackpad and keyboard all scroll it. js/carousel.js adds the
   previous/next buttons, the automatic slide, and the control that stops it.
   -------------------------------------------------------------------------- */

.carousel {
  --slides: 8;
  --slide-gap: clamp(6px, 0.92vw, 12px);

  position: relative;
  margin: clamp(1.5rem, 4.6vw, 3.75rem) 0 0;
}

/* Between here and the 1024px stack, eight slides are too narrow to read as
   photographs. Placed with the carousel rather than in the responsive section
   so the ladder is legible in one place; the 1024 and 767 steps come later in
   the file and still win where they apply. */
@media (max-width: 1200px) {
  .carousel { --slides: 5; }
}

.carousel__track {
  display: flex;
  gap: var(--slide-gap);
  margin: 0;
  padding: 0;
  list-style: none;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;            /* Firefox */
}

.carousel__track::-webkit-scrollbar { display: none; }

.carousel__track > li {
  flex: 0 0 calc((100% - (var(--slides) - 1) * var(--slide-gap)) / var(--slides));
  scroll-snap-align: start;
}

/* Sources have mixed aspect ratios, so they are cropped to fill rather than
   letterboxed — the source slots are 3:2. */
.carousel__track img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

.carousel__btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  display: grid;
  place-items: center;
  width: clamp(32px, 2.9vw, 38px);
  height: clamp(32px, 2.9vw, 38px);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--c-bg-panel);
  box-shadow: var(--shadow-panel);
  color: var(--c-ink);
  cursor: pointer;
  transition: opacity 0.15s ease, background-color 0.15s ease,
              color 0.15s ease, box-shadow 0.15s ease;
}

/* Hover and keyboard focus invert the disc: ink ground, panel-coloured chevron.

   It used to take --c-row-active, which is the dropdown's row highlight — and
   that token is deliberately TRANSLUCENT (#C7AD9D at 30%), because it is drawn
   over the opaque menu panel where it belongs. On a button floating over a strip
   of photographs there is nothing solid behind it, so the photographs showed
   through and the hover read as a smudge rather than a state. Inverting to the
   ink keeps the disc opaque at every position of the strip, which is the only
   way this control can look the same wherever it happens to sit.

   The chevron needs no rule of its own: it is filled with currentColor, so it
   follows the colour set here.

   Focus-visible shares the inversion and adds the ring the rest of the site
   uses — 2px of ink at 3px offset, the same as .media-single__play. The button
   had no focus style at all before, which on a control that pages a carousel is
   the difference between usable and not. */
.carousel__btn:hover:not(:disabled),
.carousel__btn:focus-visible:not(:disabled) {
  background: var(--c-ink);
  color: var(--c-bg-panel);
  box-shadow: 0 8px 26px rgba(107, 94, 84, 0.28);
}

.carousel__btn:focus-visible {
  outline: 2px solid var(--c-ink);
  outline-offset: 3px;
}

/* Pressed: the lift comes back down. Nothing moves — the button is centred with
   translateY(-50%) and a transform here would fight it. */
.carousel__btn:active:not(:disabled) {
  box-shadow: 0 2px 10px rgba(107, 94, 84, 0.22);
}

.carousel__btn:disabled { opacity: 0.3; cursor: default; }

/* Buttons sit just inside the track edges so they never widen the container. */
.carousel__btn--prev { left: calc(var(--slide-gap) * -0.5); }
.carousel__btn--next { right: calc(var(--slide-gap) * -0.5); }

/* Same chevron as the navigation, pointing left for the previous button. */
.carousel__btn svg {
  width: 0.75em;
  height: 0.75em;
  fill: currentColor;
}

.carousel__btn--prev svg { transform: rotate(180deg); }

/* When every slide already fits, js/carousel.js flags the track and the
   buttons drop out rather than sitting there permanently disabled. */
.carousel.is-static .carousel__btn { display: none; }

/* Marquee mode. js/carousel.js adds this once it has filled the track with
   copies of itself and so has something to scroll.

   Snap has to come off. `mandatory` fights a position that moves half a pixel
   per frame — it drags the track back to the nearest slide and the strip judders
   instead of drifting.

   The paging buttons used to go with it, on the reasoning that previous/next on
   top of continuous motion is two answers to the same question. They are kept
   now, on request: the drift is slow, and waiting for a particular photograph to
   come round again is not navigation — a visitor who wants to look through the
   strip had nothing to reach for.

   Nothing in js/carousel.js needed changing for it, which is worth recording
   because it looks like it should have. step() has been marquee-aware all
   along: it winds the scroll position back by exactly one cycle when a page
   would run off either end, so paging an endless strip is seamless, and every
   press calls hold(), which stops the drift for 2.5s so the two never fight
   each other for the same scrollLeft. update() likewise returns early while
   marqueeing, so neither button is ever disabled — on a strip that wraps there
   is no end to be at. */
.carousel.is-marquee .carousel__track { scroll-snap-type: none; }

/* --------------------------------------------------------------------------
   Site footer — slide 102
   Source is a 1920x1080 slide with everything centred; sizes below are the
   slide's own, rebased by 0.763 and expressed against the 1300px container.
   The background bleeds full width while the content sits in the container.

   The source sets the contact block and the "Social Media" label in TT Drugs,
   a licensed font that appears nowhere else in the 102-slide deck (6 runs
   total, all on this slide). The site body face is used instead. If TT Drugs
   is licensed for web, add it to --font-footer and it will apply to both.
   -------------------------------------------------------------------------- */

.site-footer {
  --font-footer: var(--font-body);

  padding-block: clamp(2rem, 6.65vw, 5.44rem) clamp(2.5rem, 8.90vw, 7.17rem);
  padding-inline: var(--gutter);
  text-align: center;
  color: var(--c-ink);

  /* Distinct from the page texture — warmer, averaging #EADCD2. */
  background: #eadcd2 url('../images/footer-texture.png') center / cover no-repeat;
  background-image: var(--footer-texture, image-set(
    url('../images/footer-texture@2x.webp') type('image/webp') 2x,
    url('../images/footer-texture.webp')    type('image/webp') 1x,
    url('../images/footer-texture.png')     type('image/png')  1x
  ));
}

.footer-inner {
  max-width: var(--container);
  margin-inline: auto;
}

/* 90pt at the source canvas, set very tight — two lines at a 119px pitch
   against a 120px face. */
.footer-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 7.04vw, 5.72rem);
  font-weight: 700;
  line-height: 1;
  text-transform: uppercase;
  color: var(--c-ink);
}

/* The only place Cinzel rather than Cinzel Decorative is used here. */
.footer-lede {
  margin: clamp(0.4rem, 1.13vw, 0.92rem) auto 0;
  font-family: var(--font-engraved);
  font-size: clamp(0.9rem, 2.50vw, 2.04rem);
  line-height: 1.3;
}

/* 25pt with a 50.6px line pitch in the source, i.e. line-height 1.52. */
.footer-contact {
  margin-top: clamp(1rem, 2.65vw, 2.14rem);
  font-family: var(--font-footer);
  font-size: clamp(0.85rem, 1.96vw, 1.59rem);
  line-height: 1.52;
}

.footer-contact p { margin: 0; }

.footer-contact a {
  text-decoration: none;
  border-bottom: 1px solid transparent;
}

.footer-contact a:hover,
.footer-contact a:focus-visible { border-bottom-color: currentColor; }

/* Pipes are decorative separators between two fields, not content. */
.footer-contact .sep {
  padding: 0 0.15em;
  opacity: 0.55;
}

.footer-social-label {
  margin: clamp(1.25rem, 5.75vw, 4.65rem) 0 0;
  font-family: var(--font-footer);
  font-size: clamp(0.85rem, 1.96vw, 1.59rem);
  font-weight: 700;
}

/* Source centres four icons 101px apart. Their rendered sizes there vary
   (87/59/62/59px) because each source image carries different transparent
   padding; they are normalised to one size here. */
.footer-social {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: clamp(0.75rem, 2.41vw, 1.95rem);
  margin: clamp(0.5rem, 1.58vw, 1.30rem) 0 0;
  padding: 0;
  list-style: none;
}

.footer-social a {
  display: block;
  width: clamp(28px, 3.53vw, 46px);
  border-radius: 50%;
  transition: transform 0.15s ease;
}

.footer-social a:hover,
.footer-social a:focus-visible { transform: translateY(-2px); }

/* --------------------------------------------------------------------------
   Responsive
   Breakpoints match Elementor's on the live site: tablet 1024, mobile 767.
   -------------------------------------------------------------------------- */

@media (max-width: 1024px) {
  .split { grid-template-columns: 1fr; }
  .split > .media-col { order: 2; }

  .ornament-tr { width: 38%; opacity: 0.7; }

  body { background-attachment: scroll; }

  /* The book cover leads on the home page, so it keeps source order rather
     than dropping below the copy like the other two-column layouts. */
  .home-split > .media-col { order: 0; }
  .home-cover { max-width: 26rem; }

  .home-headline { margin-left: 0; }

  /* Stacked, the hero band runs far taller than the artwork's 1.78 ratio, and
     `cover` would blow a narrow vertical sliver of the map up to fill it. The
     artwork spans the width at its own ratio instead and the mask carries it
     out, which is what this layer did at every width before. */
  .home-bleed::before {
    background-size: 100% auto;
    background-position: top center;
  }

  /* And it goes lighter here, because the type does not stay large. The
     headline floors at 22.4px and the blurb at 15.2px, both under the 24px
     that makes text "large" for WCAG, so the bar rises from 3.0:1 to 4.5:1 —
     and 0.45 measures 3.99:1 behind the headline, which would fail. At 0.20
     the worst pixel is 4.62:1 behind the headline and 4.37:1 behind the copy,
     so both pass outright.

     It suits the layout as well as the contrast: the artwork is compressed
     into a band across the top here rather than filling the section, and a
     texture is the right weight for it at that size. Set on .home-bleed
     because a pseudo-element takes no inline style but inherits properties. */
  .home-bleed { --watermark-alpha: 0.20; }

  .carousel { --slides: 3; }

  /* Single column: the portrait leads, and the staggered name flattens so it
     does not run off the edge on narrow screens. */
  .intro-split > .media-col { order: 0; }
  .phone { margin-inline: auto; }
  .name-display__row--indent .name-display__lg { margin-left: 0; }
  .name-roles { margin-left: 0; }

  /* Biography: copy leads, cluster follows and is capped so the tilted
     portrait does not dominate a narrow column. */
  .bio-split { gap: clamp(1.5rem, 5vw, 3rem) !important; }
  .bio-split > .media-col { order: 2; }
  .bio-cluster { max-width: 34rem; margin-inline: auto; }

  /* The section grows tall in one column, so hold the left copy back rather
     than letting it scale to the full height. Both layers must be listed —
     a single value would apply to the wide layer too and stretch it. */
  .bio-bleed { background-size: auto 55%, 100% auto; }
}

@media (max-width: 767px) {
  .carousel { --slides: 2; }

  /* Header stacks: logo on its own line, menu left-aligned beneath it. The
     flex row lives on .header-inner, so the override belongs there too. */
  .header-inner { justify-content: flex-start; }
  .nav { flex-basis: 100%; justify-content: flex-start; gap: 0.5rem 1.1rem; }

  /* Dropdowns become keyboard/tap disclosure lists rather than hovers. */
  .dropdown,
  .submenu {
    position: static;
    display: none;
    width: auto;
    opacity: 1;
    visibility: visible;
    box-shadow: none;
    background: transparent;
  }

  /* Scoped to the no-JS case on purpose. With js/nav.js running, each parent
     gets a .nav-sub-toggle button *inside* its <li>, so pressing that button
     leaves the <li> matching :focus-within — this rule then pinned the panel
     open at a higher specificity than `.js .dropdown[data-open]`, and a second
     tap on the arrow flipped aria-expanded and dropped data-open without
     closing anything. `html:not(.js)` rather than a bare `:not(.js)`: <body>
     has no js class either, so the bare form would still match and the panel
     would stay stuck. */
  html:not(.js) .nav > li:focus-within > .dropdown,
  html:not(.js) .dropdown li:focus-within > .submenu { display: block; }

  /* ------------------------------------------------------------------------
     Touch targets.

     The deck sets these as plain text links, so their hit area is the text box
     and nothing more — measured at 17px for the footer addresses and 20-23px
     for the "More…" links, against a 24px minimum for a finger. The type is
     deliberately left alone; only the padding grows, so the design reads the
     same and the target does not.

     inline-block rather than padding alone: padding on an inline box expands
     the hit area without moving the line, so neighbouring rows end up
     overlapping it. This grows the row instead. */
  .footer-contact a,
  .more-link {
    display: inline-block;
    padding-block: 0.3rem;
  }

  .more-link { margin-top: 1.2rem; }   /* was 1.5rem — the padding makes it up */

  /* ------------------------------------------------------------------------
     Long words at narrow widths.

     The deck's headings are set on a 1920px canvas where nothing has to wrap,
     so several are single words longer than a 320px screen — "MultifAceTeD
     PeRfoRmer & EducAtoR" overflowed its box by 60px. Breaking inside a word is
     the lesser evil against text running under the next element. */
  h1,
  h2,
  h3,
  .display-title,
  .engraved-title { overflow-wrap: break-word; }
}

@media (prefers-reduced-motion: reduce) {
  /* Was `transition` alone, which left keyframe animations running for a
     reader who had asked for less motion. Both are named now, so anything
     added later is covered by default rather than by remembering to. */
  *,
  *::before,
  *::after {
    transition: none !important;
    animation: none !important;
    scroll-behavior: auto !important;
  }
}

/* --------------------------------------------------------------------------
   Search in the header

   The bar was logo + nav under justify-content: space-between, so the menu sat
   hard right by consequence rather than by instruction. A third child would
   have re-spread the row and floated the menu into the middle — the one thing
   this must not do. margin-left:auto on the nav collapses the free space ahead
   of it instead, so the menu and the search sit together at the right exactly
   where the menu alone used to be. Scoped above 1024px because below it the
   nav wraps to its own row and must not be pushed.
   -------------------------------------------------------------------------- */

@media (min-width: 1025px) {
  .header-inner > nav { margin-left: auto; }
}

.site-search {
  position: relative;
  display: flex;
  align-items: center;
}

/* The button is the enhancement. Hidden until js/search.js says it works, so
   without scripting the field below is simply present in the bar. */
.site-search__toggle { display: none; }

.site-search[data-search="ready"] .site-search__toggle {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  color: var(--c-nav);
  background: none;
  border: 0;
  cursor: pointer;
}

.site-search__toggle:hover,
.site-search__toggle:focus-visible { color: var(--c-ink); }

/* Drawn, not exported: a ring and a handle, so it stays crisp at any size and
   costs no request. currentColor means it inherits the nav's own ink. */
.site-search__icon {
  position: relative;
  display: block;
  width: 0.95em;
  height: 0.95em;
  font-size: 1.05rem;
  border: 2px solid currentColor;
  border-radius: 50%;
}

.site-search__icon::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 100%;
  width: 2px;
  height: 0.42em;
  background: currentColor;
  transform: translate(-45%, -30%) rotate(-45deg);
  transform-origin: top center;
}

.site-search__form {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

.site-search__field {
  width: min(42vw, 14rem);
  padding: 0.3rem 0.5rem;
  font: inherit;
  font-size: 0.95rem;
  color: var(--c-ink);
  background: var(--c-bg-panel, #f2f1ef);
  border: 1px solid var(--c-rule);
  border-radius: 2px;
}

.site-search__field:focus-visible {
  outline: 2px solid var(--c-nav-active);
  outline-offset: 1px;
}

.site-search__submit {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  color: var(--c-nav);
  background: none;
  border: 0;
  cursor: pointer;
}

.site-search__submit:hover,
.site-search__submit:focus-visible { color: var(--c-ink); }

/* With scripting the field is closed until the button opens it. Laid over the
   bar rather than in it, so opening the field cannot widen the header row and
   push the menu about — which is what would have changed the design. */
.site-search[data-search="ready"] .site-search__form {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  z-index: 20;
  padding: 0.5rem;
  background: var(--c-bg-panel, #f2f1ef);
  border: 1px solid var(--c-rule);
  box-shadow: 0 6px 18px rgba(107, 94, 84, 0.18);
}

.site-search[data-search="ready"] .site-search__form[hidden] { display: none; }

@media (max-width: 1024px) {
  .site-search__field { width: min(60vw, 12rem); }
}

/* --------------------------------------------------------------------------
   Search results

   No new colours and no new faces: the heading takes .display-title as every
   other page's does, and everything below is the body face in --c-ink on the
   page's own ground. What is new is only the list, and it is a list rather
   than a run of cards because a card implies a picture and these results have
   none — the pages keep their images in section fields.
   -------------------------------------------------------------------------- */

/* Smaller than --step-display, on request. This heading is a sentence with the
   reader's own words in it, not a page title — at 97px it read as the loudest
   thing on the site while saying the least. --step-title is the next step down
   and already carries the same face and weight, so nothing else changes. */
.search-results__title {
  font-size: var(--step-title);
  margin-bottom: clamp(0.5rem, 1.2vw, 0.9rem);
}

/* The words searched for, set apart from the sentence around them without a
   second colour: the display face is already the page's loudest voice. */
.search-results__term::before { content: "\201C"; }
.search-results__term::after  { content: "\201D"; }

.search-results__count {
  margin: 0.5rem 0 clamp(1.5rem, 3vw, 2.5rem);
  color: var(--c-nav);
}

.search-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: 52rem;
}

/* A rule between results rather than around each: the boundary is all that is
   needed, and boxes would make eight pages look like eight products. */
.search-list__item + .search-list__item {
  margin-top: clamp(1.25rem, 2.5vw, 2rem);
  padding-top: clamp(1.25rem, 2.5vw, 2rem);
  border-top: 1px solid var(--c-rule);
}

.search-list__title {
  margin: 0;
  font-size: clamp(1.15rem, 2vw, 1.6rem);
  line-height: 1.25;
  font-weight: 400;
}

.search-list__title a {
  color: var(--c-ink);
  text-decoration: none;
}

/* Underlined on hover rather than always, matching .pt-url and the footer
   links: the list is already obviously a list of links. */
.search-list__title a:hover,
.search-list__title a:focus-visible {
  color: var(--c-nav-active);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.search-list__summary {
  margin: 0.4rem 0 0;
  line-height: 1.5;
  color: var(--c-ink);
}

/* Bold and closing the result, the same continuation every More on this site
   is — a link at the end of the words rather than a button under them. */
.search-list__more {
  margin: 0.5rem 0 0;
}

.search-list__more a {
  font-weight: 700;
  color: var(--c-ink);
  text-decoration: none;
}

.search-list__more a::after { content: "\2026"; }

.search-list__more a:hover,
.search-list__more a:focus-visible {
  color: var(--c-nav-active);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}


.search-results__empty { max-width: 40rem; }

.search-results__form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 1.25rem;
}

/* Wider than the one in the bar, because here it is the page's main control
   rather than something tucked behind an icon. */
.search-results__field { width: min(100%, 22rem); }

.search-results__submit {
  padding: 0.35rem 1.1rem;
  font: inherit;
  color: var(--c-bg);
  background: var(--c-nav-active);
  border: 0;
  border-radius: 2px;
  cursor: pointer;
}

.search-results__submit:hover { background: var(--c-ink); }

.search-results__submit:focus-visible {
  outline: 2px solid var(--c-ink);
  outline-offset: 2px;
}

/* core's markup for the_posts_pagination(). */
.search-results .nav-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: clamp(1.5rem, 3vw, 2.5rem);
}

.search-results .page-numbers {
  display: grid;
  place-items: center;
  min-width: 2.1rem;
  padding: 0.3rem 0.55rem;
  color: var(--c-ink);
  text-decoration: none;
  border: 1px solid var(--c-rule);
  border-radius: 2px;
}

.search-results .page-numbers:hover { border-color: var(--c-nav-active); }

.search-results .page-numbers.current {
  color: var(--c-bg);
  background: var(--c-nav-active);
  border-color: var(--c-nav-active);
}

/* --------------------------------------------------------------------------
   Top header

   The strip above the bar. Its ground is --c-ink, the theme's own body ink
   used as a surface, which is the taupe the design shows — so the strip
   introduces no colour the site did not already have. The type on it is
   --c-bg, the page's ground, for the same reason: the two swap roles rather
   than a new pair being invented.

   #ecebe8 on #6b5e54 is 5.25:1, the same ratio the body text gets the other
   way up, so the strip is AA at its 0.85rem floor.
   -------------------------------------------------------------------------- */

.top-header {
  background: var(--c-ink);
  margin-top: 0;
  padding-inline: var(--gutter);
}

/* Centred as one row, wrapping rather than scrolling: four ventures at this
   size fit a laptop, and on a phone they stack into a short block instead of
   a horizontal scroller nobody finds. */
.top-header__list {
  list-style: none;
  max-width: var(--container);
  /* margin: 0 auto, not margin-inline: auto. A <ul> carries a default
     margin-block, and .top-header has no padding-top for it to sit inside — so
     it collapsed through the parent and pushed the whole strip down, showing
     the page ground above it. Zeroing the block axis is what makes the strip
     meet the top edge. */
  margin: 0 auto;
  padding: 0.4rem 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.3rem clamp(1rem, 3.4vw, 3.25rem);
}

.top-header__item {
  /* Equal above and below. The link is inline-flex, so its box hugged the
     text and sat off-centre against the strip; centring the item and giving
     the link a symmetric padding-block is what evens it. */
  margin: 0;
  display: flex;
  align-items: center;
}

.top-header__link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: clamp(0.9rem, 1.25vw, 1.1rem);
  line-height: 1.3;
  color: var(--c-bg);
  text-decoration: none;
}

/* No underline on hover, on request. The colour lift below is what marks the
   link as live instead -- it is the only cue left, so it stays. */
.top-header__link:hover,
.top-header__link:focus-visible { color: #fff; }

.top-header__link:focus-visible {
  outline: 2px solid var(--c-bg);
  outline-offset: 2px;
}

/* A round window, whatever shape the mark was uploaded at — the design has
   them all as discs. object-fit covers rather than stretches, and the white
   ground is what keeps a transparent PNG legible against the dark strip. */
.top-header__icon {
  flex: 0 0 auto;
  display: block;
  width: 1.55em;
  height: 1.55em;
  overflow: hidden;
  border-radius: 50%;
  background: #fff;
}

.top-header__icon img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* A row of four is too much for a narrow screen to hold on one line; two by
   two reads better than four cramped. */
@media (max-width: 700px) {
  .top-header__list {
    gap: 0.4rem 1.25rem;
  }
}

/* Equal top and bottom on the link itself, so the hover/selection box is
   centred on the strip rather than hanging below the words. */
.top-header__link { padding-block: 0.2rem; }

/* --------------------------------------------------------------------------
   Top header, tablet and below — the marks only

   The four labels cannot share a phone's width without wrapping to three or
   four rows and turning a thin strip into a block. Below the breakpoint the
   words come off and the icons carry the links on their own, spread across
   the bar.

   The label is hidden the .screen-reader-text way, not with display: none.
   These icons are decorative (alt=""), so the label is the only accessible
   name each link has — removing it from the accessibility tree would leave
   four links announced as nothing at all.
   -------------------------------------------------------------------------- */

@media (max-width: 1024px) {
  .top-header__label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  .top-header__list {
    justify-content: space-around;
    padding: 0.5rem 0;
  }

  .top-header__link { gap: 0; }

  /* A little larger with nothing beside them, and a wider tap target than the
     mark itself so the link is comfortably hittable on a touch screen. */
  .top-header__icon {
    width: 1.9em;
    height: 1.9em;
  }

  .top-header__link {
    padding: 0.25rem;
  }
}


/* ==========================================================================
   Entrance motion, site-wide

   TWO MECHANISMS, because every page has two kinds of moment:

   1. The band the reader lands on is already on screen and has nothing to
      scroll into, so it gets a plain time-based entrance that runs on load,
      in CSS alone.
   2. Everything below it is revealed as it arrives, driven by an
      IntersectionObserver in js/reveal.js. See the note over the reveal rules
      below for why an observer rather than a scroll-linked timeline.

   Both halves sit inside prefers-reduced-motion: no-preference, and reveal.js
   returns before touching anything when that query matches, so a reader who
   has asked for less motion gets the site exactly as it was.

   ONE MOVEMENT PER BAND is the restraint that keeps twenty-three pages of this
   from turning into a fairground. A band arrives once — as its two columns a
   beat apart where it is built from a .split, otherwise as a single block. A
   band of eleven rows does not arrive eleven times.

   WHY .page, .wa-page AND .pd-stage ARE THE TARGETS. All three are already
   `position: relative`, so all three are already the containing block for the
   absolutely positioned, individually rotated artwork that the photo clusters
   carry. A transform on them cannot reparent anything that was not already
   parented there, and the same holds for the .split columns, whose artwork is
   always inside an aspect-locked cluster of its own. That is the whole safety
   argument, and it is why reveal.js works from a list of three classes rather
   than sweeping every container it can find: a static ancestor would pull the
   artwork out from under its own coordinates. Check this before adding a
   fourth.

   WHY THE ANIMATIONS END ON `transform: none`. A filled animation holds its
   last keyframe for good, so ending on translateY(0) would leave a transform
   in force on some ninety elements, each one then a stacking context that its
   own band's z-indexed ornaments have to compete with. `none` settles back to
   no transform at all, so a reveal leaves no trace once it has run — which is
   also what keeps .keyhi-tile's 2px hover lift working afterwards.

   TUNING: the load entrance's travel is the translateY in @keyframes
   swati-rise and its pacing is the delays under it. Everything below the fold
   is tuned by --reveal-rise / --reveal-time and by the rootMargin in
   reveal.js.
   ========================================================================== */

@keyframes swati-rise {
  from { opacity: 0; transform: translateY(30px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: no-preference) {

  /* ---- 1. The landing band, on load ------------------------------------- */

  /* The book hero, in six steps. `.page.home` is the home page and
     Bharatanatyam, which is built from the same parts and so takes the same
     arrival; the class appears on no other page, which is what keeps this off
     the other twenty-one. Read top to bottom, the order the eye takes them in
     — the steps are small enough to read as one movement rather than as six
     separate arrivals. */
  .page.home .home-headline,
  .page.home .home-cover,
  .page.home .home-title,
  .page.home .home-byline,
  .page.home .home-blurb,
  .page.home > .carousel {
    animation: swati-rise 0.85s cubic-bezier(0.16, 1, 0.3, 1) both;
  }

  .page.home .home-headline { animation-delay: 0.06s; }
  .page.home .home-cover    { animation-delay: 0.16s; }
  .page.home .home-title    { animation-delay: 0.26s; }
  .page.home .home-byline   { animation-delay: 0.38s; }
  .page.home .home-blurb    { animation-delay: 0.48s; }
  .page.home > .carousel    { animation-delay: 0.58s; }

  /* Every other page takes the same entrance as one movement rather than six.
     `.site-header + *` is the band the reader lands on: the header is always
     body's first child and the first band always follows it directly. What
     moves is the content container inside that band, not the band, so the
     background and the ornaments behind the words stay put instead of fading
     up with them.

     Two selectors because the container is sometimes the band itself
     (`main.page`, on About and Producer/Director) and usually one level in
     (`main.bbc-hero > .page`). The third is Women's Advocacy, whose <main>
     wraps all five of its bands rather than being one of them, so the
     adjacency lands a level higher up than it does anywhere else. */
  body:not(.home):not(.bharatanatyam) > .site-header + .page,
  body:not(.home):not(.bharatanatyam) > .site-header + * > .page,
  body.wa > .site-header + main > .wa-hero-bleed > .wa-page {
    animation: swati-rise 0.7s cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: 0.08s;
  }
}

/* ---- 2. Every band below the landing band, on scroll --------------------- */

/* Driven by js/reveal.js and an IntersectionObserver rather than by
   `animation-timeline: view()`. The scroll-timeline version that was here
   first only ran in Chromium — Safari and Firefox do not implement it, so on
   those browsers every band below the fold was simply static and the page read
   as though nothing had been done to it. An observer is supported everywhere
   and, more usefully, it lets the trigger point be chosen: the rootMargin in
   reveal.js holds the reveal back until the band is properly on screen instead
   of firing while it is still a sliver at the bottom edge, which is where the
   first attempt spent its whole animation.

   THE HIDING IS GATED ON A CLASS THE SCRIPT ITSELF SETS. reveal.js adds
   `reveal-ready` to <html> before first paint and carries a failsafe timer
   that takes it off again if the observer never starts, so a blocked or broken
   script leaves the page fully visible rather than blank. Without the class
   none of the rules below match and every band is simply there, which is the
   same bargain nav.js and carousel.js strike.

   No `will-change` here on purpose. It would promise the compositor a layer
   for every one of the ninety-odd elements this now covers, most of which are
   far offscreen and will not move for another minute of reading. Both animated
   properties are ones browsers already promote on their own once a transition
   on them actually starts.

   TUNING: --reveal-rise is the travel, --reveal-time the duration, and the
   rootMargin in reveal.js decides how far up the screen a band has to come
   before it starts. */

html.reveal-ready {
  --reveal-rise: 44px;
  --reveal-time: 0.75s;
  --reveal-ease: cubic-bezier(0.16, 1, 0.3, 1);
}

html.reveal-ready .swati-reveal {
  opacity: 0;
  transform: translateY(var(--reveal-rise));
  transition: opacity var(--reveal-time) var(--reveal-ease),
              transform var(--reveal-time) var(--reveal-ease);
}

html.reveal-ready .swati-reveal.is-in {
  opacity: 1;
  transform: none;
}

/* The picture columns travel less far than the words beside them, so a tall
   photograph does not have to cross half a screen to arrive. */
html.reveal-ready .media-col.swati-reveal {
  --reveal-rise: 26px;
}

/* Inside a .split the columns arrive in sequence rather than together. Small
   steps — the point is that the eye follows something, not that it waits.
   Scoped to .split children rather than left general: a whole-band reveal is
   one movement by design, and a stray nth-child delay on one of those would
   hold a band back for no reason a reader could see. */
html.reveal-ready .split > .swati-reveal:nth-child(2) { transition-delay: 0.10s; }
html.reveal-ready .split > .swati-reveal:nth-child(3) { transition-delay: 0.20s; }
html.reveal-ready .split > .swati-reveal:nth-child(4) { transition-delay: 0.30s; }

/* The six Key Highlights tiles — the one band that arrives as its parts rather
   than as a block, because a grid of six cards is what a stagger is actually
   for. A shorter rise, and never a transform that outlives the reveal:
   .keyhi-tile lifts 2px on hover, and a lingering transform on the element
   would sit on top of that rule. Using a transition rather than a filled
   animation is what keeps the hover working — once .is-in has settled the
   element is back to its own untransformed state. */
html.reveal-ready .keyhi-tile.swati-reveal { --reveal-rise: 20px; }

html.reveal-ready .keyhi-tile.swati-reveal:nth-child(1) { transition-delay: 0.00s; }
html.reveal-ready .keyhi-tile.swati-reveal:nth-child(2) { transition-delay: 0.07s; }
html.reveal-ready .keyhi-tile.swati-reveal:nth-child(3) { transition-delay: 0.14s; }
html.reveal-ready .keyhi-tile.swati-reveal:nth-child(4) { transition-delay: 0.21s; }
html.reveal-ready .keyhi-tile.swati-reveal:nth-child(5) { transition-delay: 0.28s; }
html.reveal-ready .keyhi-tile.swati-reveal:nth-child(6) { transition-delay: 0.35s; }
