/* ===========================================================================
   LNMD v2 — assets/style.css
   The only stylesheet. No framework, no build step, no web fonts, no CDN.
   ===========================================================================

   HOW THIS FILE IS ORGANISED
     1  Tokens            the palette, the grounds, the scale
     2  Reset and base    the few element defaults worth setting
     3  Grounds           the section-colour system — read this before editing
     4  Layout            wrap, section, section-head
     5  Chrome            utility bar, header, nav, footer
     6  The mechanism     the first screen. The most important block here
     7  Components        devices, figures, billboard, facts, funders, contact
     8  The band          the one surreal moment
     9  Motion and print

   THE IDEA THIS SITE IS BUILT ON
   The laboratory builds a machine 120 nanometres across that threads its way
   through a cell membrane by screw action under a rotating magnetic field.
   Three concentric layers do it: a magnetostrictive core, a piezoelectric
   shell, a porous coating carrying the drug.

   So the page is built out of the same two ideas. CONCENTRIC RINGS are the
   recurring form — in the mechanism figure, in the device diagrams, in the
   focus rings. ROTATION is the motion language: nothing on this site slides
   or fades, things turn. If you add a component and it does neither, ask
   whether it belongs.
   ========================================================================= */


/* ===========================================================================
   1. TOKENS  —  FALLBACK VALUES ONLY. THE LIVE ONES ARE IN theme.css.
   ===========================================================================

   DO NOT EDIT COLOURS, FONTS OR SIZES HERE. Every page loads
   assets/theme.css after this file and it overrides everything below. That
   is the one file to open, and THEME-GUIDE.md in the project root explains
   it. The values here are what the site falls back to if theme.css is ever
   missing — the original earth palette this build started from.

   EVERY COLOUR HERE WAS MEASURED, NOT CHOSEN. The ratios in the comments are
   real and reproducible:

       python ~/.claude/kits/ux-ui-agent-skills/scripts/contrast.py <fg> <bg>

   Two floors apply. 4.5:1 for body text, 3:1 for large text and for the edges
   of controls. Nothing on this site is allowed to sit below its floor.

   WHAT CHANGED FROM v1, AND WHY IT MATTERS HERE.
   In v1 the animation was a fixed canvas behind the WHOLE page, so every
   paragraph sat on ground-plus-animation and the composited worst case was
   #A89486. That trap caught this project twice: colours that measured fine
   on the bare ground failed by a full point once the canvas was under them.

   In v2 the canvas is the hero FIGURE. It occupies its own grid cell and no
   body text is ever drawn over it. So each ground can be measured directly,
   and the numbers below are exact rather than worst-case estimates.

   Two consequences worth keeping in mind:
     - The labels mechanism.js draws INSIDE the canvas are still text, and
       they are measured against --paper. If you change --paper, re-check
       them.
     - If anyone ever puts the canvas back behind the page, every figure in
       this block becomes optimistic and must be re-measured composited.
   ------------------------------------------------------------------------ */
:root {

  /* --- Text. These NEVER change, on any ground, on any page. ------------- *
   * They are fixed precisely because they are the values that can fail. The
   * grounds move; the ink does not. That is the whole colour system in one
   * sentence.
   *                   on paper   on bone   on clay   on ash   OVER CANVAS   */
  --ink:    #221F1A; /*  11.39     12.40      9.46     8.29        5.67      */
  --ink-2:  #2E2922; /*  10.00     10.89      8.31     7.28        4.97      */
                     /* secondary: captions, author lists, metadata. It looks
                        darker than a secondary colour needs to be. That is
                        the last column, not timidity. Do not lighten it.    */

  /* --- The grounds. These are the only colours allowed to vary. ---------- *
   * HUE CARRIES MEANING, OPACITY CARRIES DENSITY.
   * A section's colour says what KIND of section it is, and the same colour
   * means the same thing on every page. Whether it is solid or translucent
   * depends only on whether the section is a list you scan. See section 3.  */
  --paper:  #DED6C9;  /* default — hero, prose, anything unclassified      */
  --bone:   #E6DFD3;  /* PEOPLE — the roster, the PI, alumni                */
  --clay:   #CFC3B2;  /* THE WORK — devices, projects, research directions  */
  --ash:    #C0B7AB;  /* ARCHIVE — publications, patents, press, billboard  */
  --oxide:  #DFCFC2;  /* ACT NOW — contact, join us, open positions        */
  --indigo: #222146;  /* THE MOMENT — the band, and nothing else            */

  /* --- Dark grounds ------------------------------------------------------ */
  --sumi:      #1A1712;  /* utility bar, footer. Warm black, never neutral. */
  --on-dark:   #E6DED0;  /* 13.38 on sumi                                   */
  --on-dark-2: #D6CCBA;  /* 11.24 on sumi                                   */
  /* --on-dark-2 is not set by how a secondary "should" look. The binding
     constraint is the band, where the blended photograph lifts the indigo to
     rgb(77,76,111) in its brightest patches. Measured there it is 5.12:1.
     It was #C4B8A3 and measured 4.16:1 — a real failure the gate caught.
     If the band photograph is ever swapped, re-run G3 before shipping. */

  /* --- The signal: oxide rust ------------------------------------------- *
   * Rust IS the wabi-sabi accent — patina is literally what it is — so the
   * aesthetic and the subject agree for once. The lab oxidises ferrites;
   * the page oxidises too.                                                  */
  --rust:      #8C4A2F;  /* BORDERS AND FILLS ONLY. Never a letter of text. */
                         /* 2.31:1 over the canvas, which is why.           */
  --rust-ink:  #40200F;  /* every link, and small labels. 10.18 / 5.06      */
  --rust-lit:  #E8A87C;  /* links and accents on any dark ground. 8.78/5.12 */
  --rust-deep: #4A2314;  /* the colour the mechanism canvas paints with     */
  --on-rust:   #FFFFFF;  /* white on a rust fill. 6.70:1                    */

  /* --- The surreal accent ----------------------------------------------- *
   * ONE impossible hue. Deep indigo against earth is a colour that should
   * not be there, which is the job. TWICE PER PAGE AT MOST — the band, and
   * the wrong-way shadow under the mechanism. A third use turns it into a
   * brand colour and the strangeness dies.
   *   on paper 10.60   over canvas 5.27   white on it 15.28                */
  --dream:    #222146;
  --on-dream: #FFFFFF;

  /* --- Lines ------------------------------------------------------------- *
   * Two weights and one hairline. --hair exists because full-strength rules
   * on every row of a 38-row list turn the page into a cage.               */
  --bd:       1px solid var(--ink);
  --bd-heavy: 3px solid var(--ink);
  --hair:     #B9AB98;

  /* --- Type -------------------------------------------------------------- *
   * NO WEB FONTS. Zero external requests, nothing for the IITD server to
   * host, nothing for the policy to allow, no flash of unstyled text.
   *
   * The irregularity wabi-sabi wants cannot come from a font file here, so
   * it comes from rhythm instead: uneven spacing, rules that fray, headings
   * that sit off their column. See sections 3 and 4.                       */
  --font:
    "Helvetica Neue", Helvetica,      /* macOS, iOS                          */
    Arial,                            /* Windows — metric clone             */
    "Liberation Sans", "Nimbus Sans", /* Linux — metric clones              */
    sans-serif;
  --font-num:
    ui-monospace, "SF Mono", "Cascadia Mono", Consolas, "Liberation Mono",
    monospace;

  /* --- Scale ------------------------------------------------------------- */
  --s-1: 0.5rem;
  --s-2: 0.875rem;
  --s-3: 1.375rem;
  --s-4: 2.25rem;
  --s-5: 3.5rem;
  --s-6: clamp(5.5rem, 11vw, 9rem);   /* between sections. Ma — the empty   */
                                      /* space is the design, not leftover. */

  --wrap: 80rem;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* --- The cut edge ------------------------------------------------------ *
   * One lopsided radius, reused wherever an edge should look cut rather than
   * machined. Keep the numbers small: past about 20px this stops reading as
   * a torn edge and starts reading as a rounded card, which is a different
   * and much worse aesthetic.                                               */
  --edge: 14px 3px 12px 4px / 4px 13px 3px 15px;

  --z-canvas: -1;
  --z-sticky: 100;

  color-scheme: light;
}


/* ===========================================================================
   2. RESET AND BASE
   ========================================================================= */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  color: var(--ink);
  font-family: var(--font);
  font-size: 1.0625rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;

  /* THE PAPER. Three patina stains and two crossed weaves, drawn with
     gradients — no image file, about a kilobyte, scales to any screen and
     reads the palette. Every alpha is at or below 0.055; they darken the
     ground slightly, which RAISES the contrast of dark text on it. */
  background-color: var(--paper);
  background-image:
    radial-gradient(60rem 40rem at 12%  8%, rgba(140, 74, 47, 0.055), transparent 70%),
    radial-gradient(52rem 52rem at 88% 34%, rgba( 34, 33, 70, 0.045), transparent 72%),
    radial-gradient(70rem 45rem at 38% 94%, rgba( 34, 31, 26, 0.050), transparent 70%),
    repeating-linear-gradient( 58deg, rgba(34, 31, 26, 0.028) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(-32deg, rgba(34, 31, 26, 0.022) 0 1px, transparent 1px 4px);
  background-attachment: fixed;
}

h1, h2, h3, h4 { font-weight: 700; line-height: 1.05; letter-spacing: -0.02em; margin: 0; }
h1 { font-size: clamp(2.6rem, 6.5vw, 5rem); text-transform: uppercase; }
h2 { font-size: clamp(1.9rem, 3.6vw, 2.9rem); text-transform: uppercase; }
h3 { font-size: 1.2rem; letter-spacing: -0.01em; }
h4 { font-size: 1rem; }

p { margin: 0 0 var(--s-3); }
p:last-child { margin-bottom: 0; }
p, li, dd { text-wrap: pretty; }

b, strong { font-weight: 700; }
i, cite, em { font-style: italic; }

a {
  color: var(--rust-ink);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  transition: text-decoration-thickness 0.2s var(--ease);
}
a:hover { text-decoration-thickness: 3px; }

/* The focus ring is a RING — concentric, like the device. It is also the one
   piece of chrome that must never be subtle. */
:focus-visible {
  outline: 3px solid var(--rust);
  outline-offset: 3px;
  border-radius: 2px;
}

img { max-width: 100%; height: auto; display: block; }

hr { border: 0; border-top: var(--bd-heavy); margin: var(--s-5) 0; }

/* The keyboard escape hatch. First thing in the tab order, invisible until
   it is focused. */
.skip {
  position: absolute; left: -9999px;
  background: var(--sumi); color: var(--on-dark);
  padding: var(--s-2) var(--s-3); z-index: 999;
}
.skip:focus { left: var(--s-3); top: var(--s-3); }

.label {
  font-size: 0.72rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.16em;
  color: var(--rust-ink);
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}


/* ===========================================================================
   3. GROUNDS — the section-colour system
   ===========================================================================

   READ THIS BEFORE ADDING A SECTION.

   Put ONE ground class on every <section>. The class says what kind of
   section it is, and the same class means the same thing on all nine pages:

       .g-paper   default. Hero, prose, anything unclassified.
       .g-bone    PEOPLE. The roster, the principal investigator, alumni.
       .g-clay    THE WORK. Devices, projects, research directions.
       .g-ash     ARCHIVE. Publications, patents, press, the billboard.
       .g-oxide   ACT NOW. Contact, join us, open positions.

   OPACITY IS NOT A STYLE CHOICE, IT IS A FUNCTION OF DENSITY.
   The body carries a drawn paper — stains and a weave, section 2. Sections
   you READ are translucent, so that paper shows through and the page feels
   like one continuous sheet rather than a stack of coloured boxes. Sections
   you SCAN are solid, because texture behind a 38-row list is exactly where
   this aesthetic collapses: the eye stops being able to find the next row.

   So: add .solid to any section that is a list. .g-ash is almost always
   .solid, .g-bone often is; .g-paper, .g-clay and .g-oxide almost never are.

   A translucent ground is painted at 0.82 alpha. That is the largest value
   that still lets the paper read through.

   Why the figures in section 1 are still safe when a ground is translucent:
   the composite of a ground over --paper always lands BETWEEN the two, so
   its contrast is bounded by the two measured numbers. The lowest figure in
   the whole system is --ink-2 on solid ash at 7.28:1, and ash is always
   solid. Nothing here comes near the 4.5 floor. Add a ground darker than ash
   and that stops being true — measure it before you use it.
   ------------------------------------------------------------------------ */
.g-paper  { --ground: var(--paper);  }
.g-bone   { --ground: var(--bone);   }
.g-clay   { --ground: var(--clay);   }
.g-ash    { --ground: var(--ash);    }
.g-oxide  { --ground: var(--oxide);  }

.g-bone, .g-clay, .g-ash, .g-oxide {
  background-color: color-mix(in srgb, var(--ground) 82%, transparent);
}
/* color-mix is widely supported, but a browser without it must not lose the
   ground entirely — this is the fallback and it is deliberately first-loss:
   slightly more opaque, never transparent. */
@supports not (background-color: color-mix(in srgb, red 50%, transparent)) {
  .g-bone, .g-clay, .g-ash, .g-oxide { background-color: var(--ground); }
}

/* Lists cover the canvas completely. */
.solid { background-color: var(--ground) !important; }

/* Where two grounds meet, the seam is torn rather than ruled. 1.2% — a
   bigger tear stops reading as a decision and starts reading as a bug. */
.g-bone, .g-clay, .g-ash, .g-oxide {
  clip-path: polygon(
    0% 1.2%, 19% 0.1%, 43% 1.4%, 72% 0.3%, 100% 1.1%,
    100% 98.8%, 74% 99.9%, 46% 98.6%, 22% 99.7%, 0% 98.9%
  );
  padding-block: var(--s-6);
  margin-block: calc(var(--s-6) * -0.25);
}


/* ===========================================================================
   4. LAYOUT
   ========================================================================= */
.wrap {
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 5vw, 3rem);
}

/* Asymmetry is the resting state — but only where there is room for it. On a
   phone an uneven page margin is indistinguishable from a mistake. */
@media (min-width: 60rem) {
  .wrap { padding-inline: clamp(2rem, 6vw, 5.5rem) clamp(1.25rem, 3vw, 2.5rem); }
}

.section { padding-block: var(--s-6); }

.section-head { margin-bottom: var(--s-5); max-width: 46rem; }
@media (min-width: 60rem) {
  .section-head { margin-left: clamp(0rem, 3vw, 3rem); }
  .section:nth-of-type(even) .section-head {
    margin-left: 0; margin-right: clamp(0rem, 3vw, 3rem);
  }
}
.section-head p { margin-top: var(--s-2); color: var(--ink-2); max-width: 40rem; }

/* The rule under a heading frays out to the right instead of stopping dead.
   It is the cheapest possible hand-cut edge: one border-image, no image. */
.section-head h1, .section-head h2 {
  border-bottom: 3px solid transparent;
  border-image: linear-gradient(90deg,
                  var(--ink) 0%, var(--ink) 42%,
                  var(--rust) 58%, rgba(140, 74, 47, 0) 100%) 1;
  padding-bottom: var(--s-1);
}


/* ===========================================================================
   5. CHROME — utility bar, header, nav, footer
   ========================================================================= */
.utility {
  background: var(--sumi);
  color: var(--on-dark);
  font-size: 0.74rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.utility-inner {
  display: flex; flex-wrap: wrap; gap: var(--s-2);
  justify-content: space-between; padding-block: 0.6rem;
}
.utility a { color: var(--rust-lit); text-decoration: none; }
.utility a:hover { text-decoration: underline; text-decoration-thickness: 2px; }

.site-head {
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  border-bottom: var(--bd-heavy);
  backdrop-filter: blur(6px);
}
@media (min-width: 48rem) {
  .site-head { position: sticky; top: 0; z-index: var(--z-sticky); }
}

.head-inner {
  display: flex; flex-wrap: wrap; gap: var(--s-3);
  align-items: center; justify-content: space-between;
  padding-block: var(--s-2);
}

.brand { display: flex; align-items: center; gap: var(--s-2); text-decoration: none; color: inherit; }

/* ---------------------------------------------------------------------------
   IMAGE SLOTS — replace the file, not the markup
   ---------------------------------------------------------------------------
   Every picture on this site is a SLOT with a fixed shape. The height or the
   aspect ratio is pinned in CSS and the image is cropped to fill it, so a
   replacement file of any size drops straight in without shifting the layout.
   The full list of slots, with their names and the shape each one wants, is
   in assets/ASSETS.md. Keep the filenames; change the files.
   ------------------------------------------------------------------------ */
.brand-logo { height: 2.6rem; width: auto; }

/* The general photograph slot, used by the pages that carry photographs.
   4:3 by default; add .portrait for people, .wide for a device or a scene. */
.photo {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--edge);
}
.photo.portrait { aspect-ratio: 3 / 4; }
.photo.wide     { aspect-ratio: 16 / 9; }

.brand-mark {
  background: var(--sumi); color: var(--on-dark);
  font-weight: 700; letter-spacing: 0.08em;
  padding: 0.35rem 0.6rem;
  border-radius: var(--edge);
}
.brand-name {
  font-size: 0.66rem; font-weight: 700; line-height: 1.25;
  text-transform: uppercase; letter-spacing: 0.1em;
  max-width: 11rem; color: var(--ink);
}

.nav ul { list-style: none; display: flex; flex-wrap: wrap; gap: var(--s-3); margin: 0; padding: 0; }
.nav a {
  font-size: 0.78rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  text-decoration: none; color: var(--ink);
  padding-bottom: 3px; border-bottom: 2px solid transparent;
}
.nav a:hover { color: var(--rust-ink); border-bottom-color: var(--rust); }
.nav a[aria-current="page"] { border-bottom-color: var(--ink); }

.site-foot { background: var(--sumi); color: var(--on-dark); padding-block: var(--s-5); }
.site-foot a { color: var(--rust-lit); }
.foot-grid { display: grid; gap: var(--s-4); grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.foot-tagline { font-size: 1.1rem; font-weight: 700; }
.foot-legal {
  margin-top: var(--s-4); padding-top: var(--s-3);
  border-top: 1px solid rgba(220, 231, 242, 0.25);
  font-size: 0.78rem; color: var(--on-dark-2);
}


/* ===========================================================================
   6. THE MECHANISM — the first screen
   ===========================================================================

   THIS IS THE PAGE. Everything else supports it.

   A visitor should understand what this laboratory does before reading a
   word: a machine 120 nanometres across, driven from outside the body,
   threading through a cell membrane by screw action.

   HOW IT IS BUILT, AND WHY IN THAT ORDER
   The HTML contains a complete, labelled, static SVG diagram. That diagram
   is the page — it is in the markup, it is in the accessibility tree, it
   prints, and it is what a search engine indexes.

   The script does not create content. It adds a class to <html>, draws the
   same mechanism on a canvas, and only then fades the static diagram down.
   NOTHING HERE IS HIDDEN BY CSS THAT ONLY A SCRIPT CAN REVEAL. With
   JavaScript off, the first screen is complete and labelled. That rule is
   absolute; breaking it is how a static site ships a blank page.
   ------------------------------------------------------------------------ */
.mech {
  position: relative;
  padding-block: var(--s-5) var(--s-6);
  min-height: 72vh;
}

.mech-grid { display: grid; gap: var(--s-5); align-items: center; }

@media (min-width: 62rem) {
  .mech-grid {
    grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
    gap: var(--s-5);
    align-items: start;
  }
  /* THE FIGURE IS PINNED WHILE THE MECHANISM RUNS.
     The four stages are driven by scroll, so the figure has to still be on
     screen while they play. Without this the sequence finished after it had
     scrolled away and a visitor never saw steps 03 and 04.
     The extra height below is the room the sticky figure travels through. It
     is empty on the left, and that emptiness is the point — it is the only
     place on the site where the reader is asked to just watch. */
  /* THE SEQUENCE IS NO LONGER DRIVEN BY SCROLL.
     It used to be: the figure was pinned and the four stages advanced as you
     scrolled, which needed `.mech-grid { min-height: 190vh }` and sticky on
     both columns. The animation is now an autonomous loop on its own clock,
     so all of that is gone and the hero is one screen again.
     If you ever put scroll-driving back, the height must go on the GRID, not
     on the <section> — a sticky element can only travel inside its grid area,
     and putting it on the section pins nothing. That cost a build to find. */
}

.mech h1 { margin-block: var(--s-3) var(--s-4); }
.mech .lede { font-size: 1.2rem; color: var(--ink-2); max-width: 34rem; }

/* THE HERO PICTURE.
   This column held an animated diagram driven by assets/mechanism.js. It now
   holds the published micrograph instead. The script and the static diagram
   are both gone from index.html; mechanism.js is still in assets/ if the
   animation is ever wanted back.
   The shape of the picture is --shape-hero in theme.css. */
.hero-figure { margin: 0; }
/* The picture column is much shorter than the text beside it, so it is centred
   in its cell rather than pinned to the top — otherwise the front page opens
   with a large empty rectangle under the figure. */
@media (min-width: 62rem) { .hero-figure { align-self: center; } }
.hero-figure figure { margin: 0; }
.hero-figure img {
  width: 100%; object-fit: cover;
  border-radius: var(--edge);
}
.hero-figure figcaption {
  margin-top: var(--s-2); padding-top: var(--s-1);
  border-top: var(--bd);
  font-size: 0.82rem; color: var(--ink-2);
}

/* Kept for the archived animation. Unused while the hero is a photograph. */
.mech-stage { position: relative; display: grid; }
.mech-stage > * { grid-area: 1 / 1; }

.mech-figure { margin: 0; }
.mech-figure svg { width: 100%; height: auto; }
.mech-figure figcaption {
  margin-top: var(--s-2); padding-top: var(--s-1);
  border-top: var(--bd);
  font-size: 0.82rem; color: var(--ink-2);
}

/* The canvas is inert until the script says otherwise. */
.mech-canvas { width: 100%; height: 100%; display: none; pointer-events: none; }
.js .mech-canvas { display: block; }

/* Only once the script has run does the static diagram step back — and it
   steps back to 0 opacity, never to display:none, so it still prints and
   still reads. */
.js .mech-figure svg { opacity: 0; transition: opacity 0.5s var(--ease); }
.js .mech-figure { position: relative; z-index: 1; pointer-events: none; }

/* The scroll read-out: which stage of the mechanism is showing. It is real
   text, not decoration, and it exists in the static version too. */
.mech-steps { list-style: none; margin: var(--s-4) 0 0; padding: 0; display: grid; gap: 2px; }
.mech-steps li {
  display: grid; grid-template-columns: 2.6rem 1fr; gap: var(--s-2);
  align-items: baseline;
  padding: var(--s-1) 0;
  border-top: 1px solid var(--hair);
  color: var(--ink-2);
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease);
}
.mech-steps li b { font-family: var(--font-num); font-size: 0.8rem; color: var(--rust-ink); }
.mech-steps li[data-on="true"] {
  color: var(--ink);
  border-top-color: var(--rust);     /* a BORDER may be rust. Text may not. */
}
/* The current step was briefly set in --rust and the contrast gate caught it
   at 4.03:1. --rust is a border and fill colour; it is never a letter. The
   step reads as current through its rule, its weight and its ink instead. */
.mech-steps li[data-on="true"] b { color: var(--rust-ink); }

/* SURREAL MOMENT ONE — the wrong-way shadow.
   The object is 120 nanometres across and everything else on the page is at
   human scale, so it does not sit in the grid: it is rotated, oversized, and
   its shadow falls UPWARD. Light from below cannot happen. That single wrong
   shadow is what makes the page dreamlike rather than merely textured. */
@media (min-width: 62rem) {
  .mech-stage,
  .hero-figure {
    transform: rotate(-1.2deg);
    filter: drop-shadow(0 -16px 22px rgba(22, 50, 79, 0.34));
  }
}

/* The stage note carries the development stage. It is small, and it is not
   optional: an animation of a machine killing a tumour cell, on a university
   laboratory's front page, without a line saying where the work actually is,
   is the kind of thing that gets quoted back at you. */
.mech-stage-note {
  margin-top: var(--s-3);
  font-size: 0.84rem;
  color: var(--ink-2);
  max-width: 34rem;
  border-left: 3px solid var(--rust);
  padding-left: var(--s-2);
}

/* Shown by the script only when the loop has finished its four cycles.
   `hidden` in the markup keeps it out of the way when there is no script —
   there is nothing to replay then. It is a real <button>, so it is keyboard
   reachable and announced; a play triangle drawn on the canvas would be
   neither. */
.mech-replay[hidden] { display: none; }
.mech-replay { font-family: inherit; cursor: pointer; }

.btn-row { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-top: var(--s-4); }

.btn {
  display: inline-block;
  background: var(--sumi); color: var(--on-dark);
  padding: 0.85rem 1.5rem;
  font-size: 0.8rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  text-decoration: none;
  border: 2px solid var(--sumi);
  border-radius: var(--edge);
  transition: transform 0.2s var(--ease);
}
.btn:hover { transform: rotate(-0.8deg); }
.btn:active { transform: rotate(0deg) translateY(1px); }
.btn.ghost { background: transparent; color: var(--ink); border-color: var(--ink); }


/* ===========================================================================
   7. COMPONENTS
   ========================================================================= */

/* --- The three devices ---------------------------------------------------
   A triptych, not a card grid. Card grids were rejected for this site once
   already: they make three unrelated things look like three products.
   Here each device keeps its own ring diagram and its own hard number. */
.devices { display: grid; gap: var(--s-4); }
@media (min-width: 54rem) { .devices { grid-template-columns: repeat(3, 1fr); } }

.device {
  display: flex; flex-direction: column; gap: var(--s-2);
  padding: var(--s-3);
  border: var(--bd);
  border-radius: var(--edge);
  background: color-mix(in srgb, var(--bone) 55%, transparent);
  transition: transform 0.25s var(--ease);
}
.device:hover { transform: rotate(-0.6deg); }
.device svg { width: 100%; height: auto; max-width: 12rem; margin-inline: auto; }
.device h3 { margin-top: var(--s-1); }
.device p { font-size: 0.92rem; color: var(--ink-2); margin-bottom: 0; }
.device .device-stat {
  font-family: var(--font-num); font-size: 0.78rem;
  letter-spacing: 0.04em; color: var(--rust-ink);
  border-top: 1px solid var(--hair); padding-top: var(--s-1); margin-top: auto;
}

/* --- Key figures --------------------------------------------------------
   Four hard numbers in one frame. RULE: every number must be a fact a
   visitor could go and verify, and the label under it must say where it
   comes from. An unsourced number on a research group's front page does the
   group damage, not marketing. */
.figures {
  margin: 0; display: grid; gap: 1px;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  background: var(--sumi);
  border: var(--bd-heavy);
}
.figures > div { background: var(--ash); padding: var(--s-3); }
.figures dt {
  font-family: var(--font-num); font-size: clamp(2.2rem, 4vw, 3rem);
  font-weight: 700; line-height: 1; letter-spacing: -0.04em;
}
.figures .unit { font-size: 0.9rem; letter-spacing: 0; margin-left: 0.15em; }
.figures dd { margin: var(--s-2) 0 0; font-size: 0.85rem; color: var(--ink-2); }

/* --- The record callout ------------------------------------------------- */
.callout {
  margin-top: var(--s-4);
  padding: var(--s-4);
  border: var(--bd-heavy);
  border-radius: var(--edge);
  background: color-mix(in srgb, var(--oxide) 70%, transparent);
}
.callout h3 { margin-bottom: var(--s-2); }
.callout p { margin-bottom: 0; }

/* --- Billboard: recent news --------------------------------------------- */
.billboard { list-style: none; margin: 0; padding: 0; }
.billboard li {
  display: grid; gap: var(--s-2);
  padding-block: var(--s-3);
  border-top: 1px solid var(--hair);
}
@media (min-width: 46rem) { .billboard li { grid-template-columns: 8rem 1fr; } }
.billboard time { font-family: var(--font-num); font-size: 0.85rem; color: var(--ink-2); }
.billboard h3 { margin-bottom: var(--s-1); }
.billboard p { font-size: 0.92rem; color: var(--ink-2); margin-bottom: 0; }

/* --- Numbered research directions ---------------------------------------
   The numbers are drawn by CSS, not typed, so adding or removing an entry
   renumbers the set. These are numbered because five research directions
   ARE a countable set — do not copy this onto a list that is not one. */
.facts { margin: 0; counter-reset: fact; display: grid; gap: 1px; background: var(--hair); }
.facts > div {
  counter-increment: fact;
  background: var(--clay);
  padding: var(--s-3);
  display: grid; gap: var(--s-1);
}
@media (min-width: 46rem) { .facts > div { grid-template-columns: 4rem 1fr; column-gap: var(--s-3); } }
.facts > div::before {
  content: "0" counter(fact);
  font-family: var(--font-num); font-size: 1.1rem; color: var(--rust-ink);
}
.facts dt { font-weight: 700; }
.facts dd { margin: 0; color: var(--ink-2); font-size: 0.94rem; }
@media (min-width: 46rem) { .facts dd { grid-column: 2; } }

/* --- Funders ------------------------------------------------------------- */
.funders { display: grid; gap: var(--s-3); grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); }
.funder {
  border: var(--bd); border-radius: var(--edge);
  padding: var(--s-3); text-align: center;
  display: grid; gap: var(--s-1); align-content: center; min-height: 8rem;
}
.funder-mark { font-size: 1.6rem; font-weight: 700; letter-spacing: 0.05em; }
.funder-name { font-size: 0.8rem; color: var(--ink-2); }

/* The logo file, when the agency has supplied one. Height is pinned by
   --funder-logo-height in theme.css and the width follows, so artwork of any
   proportion drops in without changing the row. */
.funder-logo {
  width: auto; height: auto;
  object-fit: contain;
  margin-inline: auto;
}

/* --- Contact ------------------------------------------------------------- */
.contact-grid { display: grid; gap: var(--s-4); grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); }
address { font-style: normal; color: var(--ink-2); }

/* --- The platform, fanning out into domains ------------------------------
   THE CORRECTION THIS COMPONENT EXISTS FOR.
   An early draft of this site read as though the laboratory built one device.
   It does not. It builds one CLASS OF MATERIAL — magnetoelectric core-shell
   nanostructures — and applies it across medicine, energy, environment,
   sensing and communications. The nanorobot is the flagship, not the
   boundary. The publication list is the evidence: catalysis, oxygen
   evolution, plasmonic sensing, energy harvesting, pollutant degradation,
   terahertz control.
   So: one platform statement, then the domains it reaches. Do not let this
   collapse back into a device list. */
.platform {
  border-top: var(--bd-heavy);
  border-bottom: var(--bd-heavy);
  padding-block: var(--s-4);
  margin-bottom: var(--s-5);
}
.platform p { font-size: clamp(1.15rem, 2.2vw, 1.5rem); max-width: 46rem; margin-bottom: 0; }
.platform b { color: var(--rust-ink); }

/* Cell BORDERS, not a coloured grid gap. The gap-background trick draws the
   grid's own colour through any cell the content does not fill, so eight
   domains in a three-column grid left a phantom ninth tile. Borders belong to
   the cells that exist. */
.domains { display: grid; gap: var(--s-2); }
@media (min-width: 40rem) { .domains { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 64rem) { .domains { grid-template-columns: repeat(3, 1fr); } }
/* SCOPED ON PURPOSE. `.domain` is also the name of the research-domain
   line on a person card (see .person above), and an unscoped rule here put a
   bordered box round it. These four rules belong to the domain CARDS only. */
.domains .domain {
  border: var(--bd);
  border-radius: var(--edge);
  padding: var(--s-3);
  display: grid; gap: var(--s-1); align-content: start;
}
.domains .domain h3 { font-size: 1.05rem; }
.domains .domain p { font-size: 0.9rem; color: var(--ink-2); margin-bottom: 0; }
.domains .domain .where {
  font-family: var(--font-num); font-size: 0.72rem;
  color: var(--rust-ink); letter-spacing: 0.04em;
}

/* --- Alternating figure-and-text rows ------------------------------------
   Used by Projects and Innovation. NOT a card grid: a card grid makes three
   unrelated things look like three products, which was rejected for this site
   once already. Add class "flip" to swap the sides. */
.entry {
  display: grid; gap: var(--s-4);
  padding-block: var(--s-5);
  border-top: 1px solid var(--hair);
}
.entry:first-of-type { border-top: 0; }
@media (min-width: 54rem) {
  .entry { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
  .entry.flip > figure { order: 2; }
}
.entry figure { margin: 0; }
.entry figure img { border-radius: var(--edge); }
.entry figcaption {
  margin-top: var(--s-2); padding-top: var(--s-1);
  border-top: var(--bd);
  font-size: 0.82rem; color: var(--ink-2);
}
.entry h2 { margin-bottom: var(--s-2); }
.entry ul { margin: var(--s-2) 0 0; padding-left: 1.1rem; color: var(--ink-2); font-size: 0.94rem; }
.entry li { margin-bottom: var(--s-1); }

/* These are figures lifted from the papers, not snapshots of a bench, so
   they have to be big enough to read as documents. Two per row, not three. */
.gallery { display: grid; gap: var(--s-2); grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); }

/* --- The spec strip ------------------------------------------------------
   The hard facts, where the eye lands, before the prose. A device entry
   without one reads as a name and a photograph — which is exactly the
   complaint this answers. Every cell must be checkable: a size, a status, a
   filing number, a citation. Never a claim. */
.spec {
  display: grid; gap: 1px;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  background: var(--ink);
  border: var(--bd);
  margin-block: var(--s-3);
}
.spec > div { background: var(--bone); padding: var(--s-2); }
.spec dt {
  font-size: 0.66rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.14em; color: var(--ink-2);
}
.spec dd {
  margin: 0.35rem 0 0; font-family: var(--font-num);
  font-size: 0.92rem; line-height: 1.3; color: var(--ink);
}

/* --- People --------------------------------------------------------------
   A dense roster that reads as a group rather than as scattered names. */
.people {
  display: grid; gap: var(--s-3);
  grid-template-columns: repeat(auto-fill, minmax(11.5rem, 1fr));
  margin-bottom: var(--s-5);
}
/* FIVE LINES, ALWAYS IN THIS ORDER — name, position, domain, contact,
   current affiliation. Any of the last three may be absent; nothing shifts
   when one is, because they are stacked rather than positioned. */
.person { display: grid; gap: 0.2rem; align-content: start; }
.person img { width: 100%; object-fit: cover; border-radius: var(--edge); aspect-ratio: 3 / 4; }
.person h4      { font-size: 0.92rem; line-height: 1.25; margin-top: var(--s-1); }
.person p       { margin: 0; }
.person .role   { font-size: 0.76rem; color: var(--ink-2); }
.person .domain { font-size: 0.74rem; color: var(--ink-2); font-style: italic; }
.person .contact{ font-size: 0.74rem; overflow-wrap: anywhere; }
.person .affil  { font-size: 0.74rem; color: var(--ink-2); }
.person .since  { font-family: var(--font-num); font-size: 0.72rem; color: var(--rust-ink); }

/* THE MISSING-PORTRAIT STATE, AND WHY IT IS A DESIGN AND NOT A HOLE.
   Seven of the group have no photograph yet. A dashed empty box reads as
   broken and makes the whole page look unfinished; a monogram set in the
   site's own type reads as a decision. Photographs can then arrive one at a
   time without the page ever looking half-built. */
.monogram {
  width: 100%; aspect-ratio: 3 / 4;
  border-radius: var(--edge);
  background: var(--clay);
  border: var(--bd);
  display: grid; place-items: center;
  font-size: 1.9rem; font-weight: 700; letter-spacing: 0.06em;
  color: var(--rust-ink);
}

.subgroup {
  font-size: 0.74rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.16em; color: var(--ink-2);
  margin: var(--s-4) 0 var(--s-2);
  padding-bottom: var(--s-1); border-bottom: 1px solid var(--hair);
}

.group-photo { margin: 0 0 var(--s-5); }
.group-photo img { width: 100%; aspect-ratio: 3 / 1; object-fit: cover; border-radius: var(--edge); }

/* A framed note standing in for something not yet supplied. Visible, but not
   pretending to be content. */
.note {
  border: 2px dashed var(--rust); border-radius: var(--edge);
  padding: var(--s-3); color: var(--ink-2); font-size: 0.9rem;
  background: color-mix(in srgb, var(--oxide) 55%, transparent);
}

/* --- The publication index ----------------------------------------------
   38 entries. This is the densest thing on the site and the place where a
   textured ground would destroy scanning — which is why every archive
   section is .solid. Rules are hairlines, not full-strength borders. */
.pubs { list-style: none; margin: 0 0 var(--s-3); padding: 0; }

/* NO RULE BETWEEN TWO PAPERS. Each entry runs as one continuous citation —
   authors, title, journal, doi — so the eye finds the next entry by the
   bold title, not by a line. On 38 rows a line every row is a cage. The
   only rules left on this page are the year headings. */
.pubs li {
  display: grid; gap: var(--s-1);
  grid-template-columns: 2.6rem 1fr;
  padding-block: 0.3rem;
  font-size: 0.94rem; line-height: 1.45;
}
.pubs li > div > * { display: inline; }
.pubs .n { font-family: var(--font-num); font-size: 0.78rem; color: var(--rust-ink); }
.pubs .authors { color: var(--ink-2); }
.pubs .title { font-weight: 700; color: var(--ink); }
.pubs .venue { font-style: italic; }
.pubs a { font-family: var(--font-num); font-size: 0.82rem; white-space: nowrap; }
.pubs-year {
  display: flex; flex-wrap: wrap; gap: var(--s-2); align-items: baseline;
  margin: var(--s-3) 0 var(--s-1);
  padding-bottom: 0.2rem; border-bottom: var(--bd-heavy);
  font-size: 1.05rem;
}
.pubs-year .count { font-family: var(--font-num); font-size: 0.8rem; color: var(--ink-2); }

/* --- Patents -------------------------------------------------------------- */
.filings { list-style: none; margin: 0; padding: 0; }
.filings li { padding-block: var(--s-3); border-top: 1px solid var(--hair); }
.filings .number { font-family: var(--font-num); font-size: 0.85rem; color: var(--rust-ink); }
.filings h3 { margin-block: var(--s-1); }
.filings .status {
  display: inline-block; font-size: 0.68rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  padding: 0.2rem 0.5rem; border: var(--bd); border-radius: var(--edge);
}
.filings .status.granted { background: var(--rust); color: var(--on-rust); border-color: var(--rust); }

/* --- News: talks and the press archive ------------------------------------ */
.talks { list-style: none; margin: 0 0 var(--s-5); padding: 0; }
.talks li { padding-block: var(--s-3); border-top: 1px solid var(--hair); }
.talks time { font-family: var(--font-num); font-size: 0.82rem; color: var(--ink-2); }

.press { list-style: none; margin: 0; padding: 0; columns: 1; }
@media (min-width: 46rem) { .press { columns: 2; column-gap: var(--s-4); } }
.press li { break-inside: avoid; padding-block: var(--s-2); border-top: 1px solid var(--hair); }
.press .outlet { display: block; font-weight: 700; font-size: 0.92rem; }
.press .host { font-family: var(--font-num); font-size: 0.74rem; color: var(--ink-2); }

/* THE VIDEO IS A LINK, NOT A PLAYER, AND THAT IS THE WHOLE POINT.
   An embedded YouTube iframe was measured making ten third-party requests
   the moment the page opened — player, stylesheet, scripts, a Google Fonts
   file and two telemetry calls — before the visitor clicked anything. This
   card costs one click and keeps the site's strongest claim true: no page
   contacts any other host. It is also why every page keeps frame-src 'none'.
   An optional poster image can be added later; see assets/ASSETS.md. */
.videocard {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3);
  border: var(--bd-heavy); border-radius: var(--edge);
  background: color-mix(in srgb, var(--bone) 70%, transparent);
  text-decoration: none; color: var(--ink);
  transition: transform 0.25s var(--ease);
}
.videocard:hover { transform: rotate(-0.5deg); }
.videocard-play {
  flex: none; width: 3.2rem; height: 3.2rem;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--rust); color: var(--on-rust);
  font-size: 1.1rem; padding-left: 0.2rem;
}
.videocard-text b { display: block; }
.videocard-text span { font-size: 0.9rem; color: var(--ink-2); }

/* --- About: education, appointments, awards ------------------------------- */
.timeline { list-style: none; margin: 0; padding: 0; }
.timeline li {
  display: grid; gap: var(--s-1);
  padding-block: var(--s-2); border-top: 1px solid var(--hair);
}
@media (min-width: 40rem) { .timeline li { grid-template-columns: 7rem 1fr; column-gap: var(--s-3); } }
.timeline .when { font-family: var(--font-num); font-size: 0.82rem; color: var(--rust-ink); }
.timeline .what b { display: block; }
.timeline .what span { color: var(--ink-2); font-size: 0.92rem; }

.prose { max-width: 42rem; }
.prose h3 { margin-top: var(--s-4); margin-bottom: var(--s-2); }

/* --- Join us: the positions ---------------------------------------------- */
.positions { display: grid; gap: var(--s-3); grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.position {
  border: var(--bd); border-radius: var(--edge); padding: var(--s-3);
  display: grid; gap: var(--s-1); align-content: start;
}
.position h3 { margin-bottom: var(--s-1); }
.position p { font-size: 0.92rem; color: var(--ink-2); margin-bottom: 0; }
.position .state {
  font-size: 0.68rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.12em; color: var(--rust-ink);
}


/* ===========================================================================
   8. THE BAND — surreal moment two
   ===========================================================================
   The ground leaves the earth palette entirely. The photograph is blended to
   luminosity, so the picture survives as light and shade while its colour is
   replaced by one that was never in it. Edges torn, not ruled.

   White on indigo is 12.41:1, so this is the highest-contrast text on the
   page, not the lowest. The binding constraint is not the indigo itself but
   the places the photograph LIGHTENS it — measured at #3D3C61, with the text
   made invisible and the lightest remaining pixel taken.
   ------------------------------------------------------------------------ */
.band {
  position: relative; overflow: clip;
  background: var(--indigo);
  color: var(--on-dream);
  padding-block: clamp(4rem, 10vw, 8rem);
  margin-block: var(--s-6);
  clip-path: polygon(
    0% 1.6%, 18% 0.2%, 42% 1.9%, 71% 0.4%, 100% 1.5%,
    100% 98.4%, 76% 99.8%, 47% 98.1%, 21% 99.6%, 0% 98.5%
  );
}
/* The photograph's opacity is a CONTRAST control, not a taste setting. At
   0.3 the bright parts of the picture lifted the indigo to rgb(93,92,126)
   and the secondary text measured 3.25:1 there — a real failure the gate
   caught. Every step up from 0.22 must be re-measured with G3. */
.band-bg {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  mix-blend-mode: luminosity; opacity: 0.22;
}

/* THE SCRIM. This is what makes the band photograph a REPLACEABLE ASSET.
   Without it the text's background depends on how bright the picture happens
   to be — measured at 4.71:1 with the current one, which passes, and would
   fail outright the day someone drops in a lighter photograph. The scrim caps
   how bright the ground under the text can get, so any image can be swapped
   into assets/images/band-home.jpg without re-measuring.
   It fades out to the right, where there is no text, so the picture still
   reads. Strengthen the left stop, never weaken it.

   ORDER MATTERS HERE. ::before is generated as the FIRST child, so without a
   z-index it paints UNDERNEATH the <img> and does nothing at all — which is
   exactly what happened on the first attempt: the measurement did not move by
   a hundredth. The scrim needs 1, the text needs 2. */
.band::before {
  content: "";
  position: absolute; inset: 0;
  z-index: 1;
  background: linear-gradient(90deg,
              rgba(22, 50, 79, 0.80) 0%,
              rgba(22, 50, 79, 0.72) 48%,
              rgba(22, 50, 79, 0.20) 100%);
}
.band-inner { position: relative; z-index: 2; }
.band-cap { font-size: clamp(1.8rem, 4vw, 3rem); font-weight: 700; line-height: 1.05; letter-spacing: -0.02em; }
.band-sub { max-width: 42rem; color: var(--on-dark-2); margin-bottom: 0; }
.band a { color: var(--rust-lit); }


/* ===========================================================================
   9. MOTION AND PRINT
   ========================================================================= */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
  /* The mechanism stops advancing on scroll and shows its final state. The
     static diagram is never hidden in this mode. */
  .js .mech-figure svg { opacity: 1; }
  .js .mech-canvas { display: none; }
}

@media print {
  body { background-image: none; background-color: #FFF; color: #000; }
  .utility, .nav, .btn-row, .site-foot { display: none; }
  .g-bone, .g-clay, .g-ash, .g-oxide, .solid { background: #FFF !important; clip-path: none; }
  .band { clip-path: none; background: #FFF; color: #000; }
  .band-bg { display: none; }
  .mech-stage { transform: none; filter: none; }
  .mech-canvas { display: none !important; }
  .mech-figure svg { opacity: 1 !important; }
  a { text-decoration: none; color: #000; }
}
