/* ── Typefaces ───────────────────────────────────────────────────────────────
   Self-hosted, latin subset only (site/fonts/, 326 KB total). The cyrillic,
   greek and vietnamese subsets would have tripled that for glyphs this site
   never renders. font-src 'self' in the CSP already permits these, so no policy
   change was needed.
   Newsreader carries display type, Inter everything else. Arial was the previous
   answer everywhere, which is the typeface a browser falls back to when no
   decision has been made. */
@font-face{font-family:'Newsreader';src:url('../fonts/newsreader-400.woff2') format('woff2');font-weight:400;font-display:swap;}
@font-face{font-family:'Newsreader';src:url('../fonts/newsreader-600.woff2') format('woff2');font-weight:600;font-display:swap;}
@font-face{font-family:'Inter';src:url('../fonts/inter-400.woff2') format('woff2');font-weight:400;font-display:swap;}
@font-face{font-family:'Inter';src:url('../fonts/inter-500.woff2') format('woff2');font-weight:500;font-display:swap;}
@font-face{font-family:'Inter';src:url('../fonts/inter-600.woff2') format('woff2');font-weight:600;font-display:swap;}
@font-face{font-family:'Inter';src:url('../fonts/inter-700.woff2') format('woff2');font-weight:700;font-display:swap;}

/* ══════════════════════════════════════════════════════════════════════════
   EPFL MDH DASHBOARD — MAIN STYLESHEET
   Whole-site styles. The MDH Context tab (#panel-mdh) has its own scoped
   stylesheet in mdh-map.css; everything else lives here.

   ── NEW HERE? READ THIS FIRST ─────────────────────────────────────────────
   This file styles the whole site. It is organised into 13 numbered sections
   (see the table of contents below). To find the styles for a page, jump to
   its section: e.g. the Actors page = Section 6, the landing page = Section 12.

   Two kinds of section:
     • SHARED sections style things used by MANY pages (design tokens, the
       header/nav, cards, filters, modals). Change these and it affects the
       whole site. Sections 1, 2, 3, 5, 9, 10 are shared.
     • PAGE sections style ONE page only. Sections 4, 6, 7, 8, 11, 12.

   How CSS "wins" (important): when two rules style the same thing, the one that
   comes LATER in this file wins. So order matters — do not reorder sections. To
   tweak a page without affecting others, edit that page's own section.

   Every colour comes from a named EPFL brand token defined in Section 1 (e.g.
   var(--groseille) for red). Never paste a raw hex like #B51F1F into a rule —
   use the token, or add a new token in Section 1. See feedback_epfl_color_palette.

   TABLE OF CONTENTS
     1.  Design tokens (:root) + a11y helpers        [SHARED] colours, sizes
     2.  Base + header + top navigation              [SHARED] the top bar
     3.  Panels + shared layout primitives           [SHARED] page frames, section heads
     4.  Beyond EPFL panel                           [PAGE]   the "Beyond EPFL" tab
     5.  Intro banners + Takeaways                    [SHARED+PAGE] tab intros; Takeaways tab
     6.  Actors status panel (cards, funnel, views)  [PAGE]   the "EPFL Actors" tab
     7.  Visual mapping (map + spider charts)         [PAGE]   the "Visual Mapping" tab
     8.  Projects / publications cards + badges       [PAGE]   the "Publications" tab
     9.  Filters (search, bars, colours, collapsibles)[SHARED] used by Actors + Publications
     10. Charts + modals (bib, enlarge, researcher)   [SHARED] pop-up dialogs
     11. Funding panel                                [PAGE]   the "Funding" tab
     12. Landing page (hero, tiles, explore, footer)  [PAGE]   the home page
     13. Responsive overrides                         [SHARED] phone/tablet tweaks
   ══════════════════════════════════════════════════════════════════════════ */

/* ── 1. DESIGN TOKENS + A11Y ─────────────────────────────────────────────── */
   /* The site colour palette + sizes, defined once as var(--name). Everything else references these. */

/* Visually hidden but exposed to screen readers (a11y labels). */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

:root {
  /* ── EPFL official brand palette ──────────────────────────────────────────
     Names + hexes are verbatim from the EPFL brand chart ("La marque EPFL |
     Couleurs"). These are the ONLY colours used on the site; every rule
     references one of these tokens. See feedback_epfl_color_palette. */
  --rouge-suisse:#FF0000; /* Rouge suisse (Pantone 485, RAL 3020)       */
  --groseille:#B51F1F;    /* Groseille (Pantone 3546, RAL 3000)         */
  --leman:#00A79F;        /* Léman (Pantone 3272, RAL 5018)             */
  --canard:#007480;       /* Canard (Pantone 7714, RAL 5025)            */
  --mont-rose:#F39869;    /* Mont Rose                                  */
  --perle:#CAC7C7;        /* Perle                                      */
  --vert-deau:#C2DDB0;    /* Vert d'eau                                 */
  --rose:#ED6E9C;         /* Rose                                       */
  --acier:#4F8FCC;        /* Acier                                      */
  --soufre:#FBEE66;       /* Soufre                                     */
  --carotte:#EC6608;      /* Carotte                                    */
  --zinzolin:#5C2483;     /* Zinzolin                                   */
  --chartreuse:#C8D300;   /* Chartreuse                                 */
  --marron:#5B3428;       /* Marron (Pantone Black 4 C)                 */
  --ardoise:#453A4C;      /* Ardoise (Pantone Black 5 C) — primary ink  */
  --taupe:#413D3A;        /* Taupe (Pantone Black 7 C)                  */

  /* Everything above is a real EPFL chart colour, used by name — there are NO
     colour aliases (no --navy/--ink/--red/--muted/--bg). Rules reference the
     chart token directly (Ardoise for dark ink/text, Rouge suisse for red).
     Plain white is written as #fff. --border is a translucent Ardoise hairline. */
  --border:rgba(69,58,76,0.2);

  /* ── Secondary ink, two steps, both WCAG AA ───────────────────────────────
     Muted label colours were written inline as rgba(69,58,76,a) at eight
     different alphas between .30 and .72. Measured over the site's own grounds
     (tools/dev/contrast.mjs), NONE of those clear 4.5:1 for normal text: .62 on
     white is 3.58:1 and .50 is 2.67:1, so the glossary sources, the table's
     sort headers, the closeness chips and the figure captions were all failing
     AA. .75 is the smallest alpha that passes on white, --ground, --ground-tint
     and the three washes at once (4.69 to 5.09:1).

     Two steps rather than eight is also the better typography: full Ardoise for
     primary text, --ink-body where a block should recede slightly, --ink-muted
     for labels and captions. Anything fainter than --ink-muted is decoration,
     not text, and must not carry information.

     --perle (#CAC7C7) is the exception: as a TEXT ground it needs .84. It is a
     rule and divider colour, so nothing sets type on it; if that changes, check
     the table in tools/dev/contrast.mjs first. */
  --ink-body:rgba(69,58,76,.86);
  --ink-muted:rgba(69,58,76,.75);

  /* ── Text-safe equivalents of the light chart hues ────────────────────────
     The EPFL chart is a FILL palette. Measured on white (tools/dev/contrast.mjs)
     Mont Rose is 2.21:1, Rose 2.87:1, Carotte 3.25:1, Acier 3.42:1 and Perle
     1.68:1, all under the 4.5:1 AA floor for normal text. mdh-map.css already
     solved this for the three pillars with --mdh-m-txt / --mdh-d-txt /
     --mdh-hs-txt; these are the same idea for the rest, and the first three
     resolve to exactly those on-chart inks so the two sets cannot drift.

     Use for `color:`. Keep the base hues for fills, borders and dots, where the
     threshold is 3:1 and the identity matters more. */
  --rose-txt:#D81B5F;       /* Rose darkened, true hue  4.55 on --ground */
  --acier-txt:#3373B1;      /* Acier darkened, true hue  4.56 on --ground */
  --mont-rose-txt:#5B3428;  /* = Marron     10.68:1 */
  --mont-rose-dark:#C04B0F; /* Mont Rose darkened, true hue, 4.53:1: text on a Mont Rose ground (owner, 2026-09-16) */
  --carotte-txt:#B95006;    /* Carotte darkened      4.58:1 on --ground */
  --leman-txt:#007D77;      /* Leman darkened        4.59:1 on --ground */
  --perle-txt:#969494;      /* Perle darkened        3.02:1, large text only */

  /* ── Chrome grey, OFF the EPFL chart, by agreement ────────────────────────
     The header, nav and footer were Ardoise (#453A4C) and #372E3C, both of which
     are violet: on a large flat area that cast reads as brownish rather than as
     ink, which is what it looks like at 12px in a table cell. This is a dark
     NEUTRAL grey, so the only chroma in the header band is the book spines.
     Declared once here rather than pasted, because every rule below references a
     token and an off-chart literal scattered through the file is how a palette
     stops being one. */
  --chrome:#2A2A2D;

  /* ── FACET COLOUR KEY ─────────────────────────────────────────────────────
     One EPFL hue per facet type, used EVERYWHERE that facet appears: the chip
     in the shared filter, the pill on an actor card, the pill on a publication
     card, the tag in the actor modal. A reader who learns a colour once knows
     it on all three Mapping EPFL subtabs.

     Rose, Acier and Mont Rose are RESERVED for the three MDH pillars and are
     never spent on anything else - focus is the one facet whose VALUES carry
     the colour, because M, D and H are the site's primary axis and already wear
     these hues in the wordmark and the charts.

     16 facets, 16 hues, from a 16-token palette. One pair shares, and it is a
     genuine parent/child pair: theme with subtopic. Everything else is its own
     colour.
     Affiliation took Rouge suisse on 2026-09-10, which frees Marron for Lab.
     Rouge suisse was the only palette token not on a facet, but it was not idle:
     it is also the site's link and accent red (project links, the details
     summary). It now does both jobs, so a red label and a red link sit on the
     same page. Chosen by the user over leaving Lab without a hue of its own. */
  --f-focus-m:var(--rose);       /* Misinformation   */
  --f-focus-d:var(--acier);      /* Disinformation   */
  --f-focus-h:var(--mont-rose);  /* Targeted harm    */
  --f-close:var(--groseille);
  --f-stage:var(--chartreuse);
  --f-theme:var(--leman);        /* + subtopic, its child */
  --f-subject:var(--zinzolin);
  --f-method:var(--canard);
  --f-data:var(--vert-deau);
  --f-platform:var(--carotte);
  --f-type:var(--ardoise);
  --f-year:var(--soufre);         /* swapped with Author: Soufre reads better on a
                                     chart than Perle, and a year needs no hue of its own */
  --f-affil:var(--rouge-suisse); /* also the site's link red, see above */
  --f-lab:var(--marron);         /* freed by Affiliation moving to Rouge suisse */
  --f-actor:var(--perle);         /* swapped with Year */
  --f-score:var(--taupe);

  /* Shared page-banner backdrop. Near-black, but built FROM the chart: Ardoise
     under a black scrim, so the base keeps its violet cast without introducing
     an off-chart hex. Animated art only reads against a near-black; on plain
     Ardoise a glow has nowhere to fall off to and the field goes muddy. Used by
     the MDH Context, Research Directions, Funding and Takeaways banners, which
     all have to match. */
  --banner-bg:linear-gradient(rgba(0,0,0,0.82),rgba(0,0,0,0.82)),var(--ardoise);
  /* The title/description plate that sits on those banners. Plain black
     rather than a lifted grey: against a near-black field a grey plate reads
     as a slab floating on top of the glow. At 0.82 the art stops showing
     through, which matters most on MDH Context, where the backdrop is a
     field of 28px white glyphs with a halo and the 14px description sat on
     a mottled, pulsing surface. One value for all four banners. */
  --banner-plate:rgba(0,0,0,0.82);

  /* Layout metrics. The three bar heights track the type inside them: the nav
     tab, sub-tab and header rules set min-height from these, so raising the type
     scale without raising these crops the text against the bar edge. */
  --header-h:72px; --nav-h:48px; --subnav-h:40px;
  --gutter:48px;
  /* Soft body ink. Was used in six places before it existed; an undefined
     var() makes an inherited property inherit instead of falling back, so
     those six quietly took their parent colour. */
  --ink-soft:rgba(69,58,76,.78);
  /* ── The spine band ────────────────────────────────────────────────────────
     Book spines reduced to a repeating 250px tile: bars of varied width with
     thin dark gaps, in the landing-page bookcase's own colours. Thirteen soft
     tones carry it (Mont Rose, Vert d'eau, Rose, Soufre, Acier, Perle) with four
     bolder spines spaced across (Groseille, Canard, Zinzolin, Carotte), exactly
     the proportion the big shelf uses.
     A tile, not a stretch: it repeats as the window widens and clips mid-tile as
     it narrows, so it never distorts and needs no media query. */
  --spine-band:linear-gradient(90deg,
    #F39869 0 13px, transparent 13px 16px,
    #C2DDB0 16px 25px, transparent 25px 28px,
    #ED6E9C 28px 39px, transparent 39px 42px,
    #FBEE66 42px 49px, transparent 49px 52px,
    #B51F1F 52px 63px, transparent 63px 66px,
    #4F8FCC 66px 76px, transparent 76px 79px,
    #CAC7C7 79px 87px, transparent 87px 90px,
    #F39869 90px 102px, transparent 102px 105px,
    #C2DDB0 105px 113px, transparent 113px 116px,
    #007480 116px 127px, transparent 127px 130px,
    #ED6E9C 130px 140px, transparent 140px 143px,
    #FBEE66 143px 151px, transparent 151px 154px,
    #4F8FCC 154px 166px, transparent 166px 169px,
    #5C2483 169px 176px, transparent 176px 179px,
    #CAC7C7 179px 190px, transparent 190px 193px,
    #F39869 193px 203px, transparent 203px 206px,
    #EC6608 206px 217px, transparent 217px 220px,
    #4F8FCC 220px 231px, transparent 231px 234px,
    #C2DDB0 234px 246px, transparent 246px 250px);

  /* Content measure. Without it, body text ran the full window: on a wide screen
     that is 200+ characters a line, where comfortable reading is 60-80, and it is
     the main reason the pages felt like a wall. The panel is capped and centred;
     the banner breaks back out to full width on its own (see .map-hero). */
  --measure:1240px;

  /* ── GRID SCALE ───────────────────────────────────────────────────────────
     Charts, Actors and Publications share one filter, and used to run at four
     different densities: 260, 280, 360 and 420px minimums with two different
     gaps. Three subtabs of one tab read as three different pages.
     One scale now, sized so each lands on a whole number of columns inside the
     1240px measure with an 18px gutter:
       --grid-card   4 across   a name, a unit and a few label rows
       --grid-panel  3 across   the distribution charts and the work cards
       --grid-wide   2 across   the radials, which carry three encodings each
     Chosen from the measure backwards, not by eye: at 390px a chart is wide
     enough for a horizontal bar's category label, which is the entire reason
     four of them are horizontal, and at 260px it was not. */
  --grid-card:290px;
  --grid-panel:390px;
  --grid-wide:560px;
  --grid-gap:18px;
  /* Display serif and UI sans. Every Arial declaration on the site now points
     at one of these two, so the pairing is changed in one place. */
  --sans:'Inter',Arial,Helvetica,sans-serif;
  --display:'Newsreader',Georgia,'Times New Roman',serif;
  /* Three section grounds, all derived from the chart: white, Perle at 18%,
     and Ardoise at 5% for the cooler third register. */
  --ground:#F6F5F5;
  --ground-tint:#F0EEF1;
  /* The panel's own top padding. A token because the full-bleed banner cancels
     it with a negative margin to sit flush under the nav. */
  --panel-pad-top:28px;
  /* Static fallback for .main's top padding (header + topnav, incl. any wrapped
     rows/subnav). JS measures the real rendered height on load/resize and
     overrides this inline on <html>, so it self-corrects instead of relying on
     this guess staying right for every width and every tab's subnav state. */
  --main-top:160px;
}
/* Realistic-desktop tier (roughly a 14" laptop's logical CSS px, after typical
   OS scaling): the values above are now this tier's baseline. Genuinely large
   monitors get a bit more breathing room back via this band. */
@media (min-width:1441px){
  :root{--header-h:96px; --nav-h:54px; --subnav-h:42px; --gutter:80px; --main-top:150px;}
}

/* ── Internal-section passcode gate (panel-gate.js) ───────────────────────── */
.gate-hidden{display:none !important;}
.tab-gate{display:flex;justify-content:center;align-items:flex-start;padding:64px 20px;min-height:50vh;}
/* A gated tab keeps its banner (see applyGate in panel-gate.js), so the card no
   longer has to carry the whole page on its own: it sits under the banner
   instead of being pushed down and stretched to fill the viewport. */
.map-hero ~ .tab-gate{padding-top:36px;min-height:0;}
.tab-gate-card{background:#fff;border-radius:12px;
  box-shadow:0 6px 26px rgba(0,0,0,.12);padding:30px 30px 26px;max-width:400px;width:100%;text-align:center;}
.tab-gate-lock{font-size:34px;line-height:1;margin-bottom:8px;}
/* The card is just a lock, a title and the field. The explanatory lead that used
   to sit between the title and the field is gone, so the title carries the gap. */
.tab-gate-title{font-family:var(--sans);font-size:20px;font-weight:700;color:var(--ardoise);margin-bottom:18px;}
.tab-gate-row{display:flex;gap:8px;}
.tab-gate-input{flex:1;font-family:var(--sans);font-size:16px;color:var(--ardoise);
  border:1.5px solid var(--perle);border-radius:12px;padding:10px 13px;}
.tab-gate-input:focus{outline:none;border-color:var(--canard);}
/* Why these two min-widths exist. A gated tab is a column flex container
   (.panel.active:has(> .page-head .scope-note) below), so .tab-gate is a
   stretched flex item and cannot render narrower than its own min-content
   width. That width was being set by this input: a text input carries a default
   `size` of 20 characters, and as a flex item its automatic minimum size is that
   intrinsic width, so it refused to shrink. At 375px the gate measured 420px
   against a 375px viewport and Beyond, Takeaways and Funding were the only three
   tabs on the site that scrolled sideways on a phone. */
.tab-gate{min-width:0;}
/* min-width:0 alone is not enough here: the panel's `margin-inline:auto` turns
   .tab-gate into a shrink-to-fit box, and a shrink-to-fit box is floored at its
   min-content width, to which an <input> still contributes its intrinsic
   `size` width. `width:0` drops that contribution to nothing and flex-grow then
   gives the field the whole row. */
.tab-gate-input{min-width:0;width:0;}
/* ...and with the field contributing nothing, the shrink-to-fit gate shrank to the title's width and
   left a 57px field. The gate takes the panel's width; the card inside keeps its 400px cap. */
.tab-gate{width:100%;box-sizing:border-box;}
.tab-gate-btn{font-family:var(--sans);font-size:15px;font-weight:700;color:#fff;
  background:var(--ardoise);border:none;border-radius:12px;padding:10px 20px;cursor:pointer;}
.tab-gate-btn:hover{background:var(--taupe);}
.tab-gate-err{font-size:13.5px;color:var(--groseille);margin:12px 0 0;}
@media (max-width:480px){
  .tab-gate{padding:36px 14px;}
  .tab-gate-card{padding:24px 20px 22px;}
}

/* ── 2. BASE + HEADER + TOP NAVIGATION ───────────────────────────────────── */
   /* The dark bar at the very top: logo, title, and the row of section tabs. Same on every page. */
html{scroll-behavior:smooth;}
/* Note: the site does NOT honour the OS "reduce motion" setting as a global
   rule (removed per an explicit request); the big banner animations run for
   everyone. Two small, deliberate exceptions remain and are fine to keep: the
   chat typing dots (mdh-map.css) and the funding particles (fund-particles.js)
   still respect reduce-motion. If a global guard is ever wanted again, add a
   `@media (prefers-reduced-motion: reduce)` block here that zeroes animation and
   transition durations. */
*{box-sizing:border-box;margin:0;padding:0;}
/* ── B. Page ground ─────────────────────────────────────────────────────────
   #F6F5F5 is Perle (#CAC7C7) at 18% over white, so it is the chart palette, not
   a new colour. White panels and cards now sit ON something instead of being
   white-on-white, which is what separates them since the static drop shadows
   were removed. Figure and ground rather than a stack of invisible boxes. */
/* ── PAPER ────────────────────────────────────────────────────────────────
   TWO layers, both Ardoise, both background images so neither costs a request
   and both vanish in print.

     1. A DOT GRID on a 22px pitch, 1.3px dots. This is the layer you actually
        see: it reads as dot-grid paper, the kind a notebook is ruled with, and
        it gives the page a measure to sit on.
     2. A MICRO GRAIN on a 5px pitch, sub-pixel dots, at roughly half the
        strength. On its own it is invisible; under the grid it stops the dots
        floating on a flat fill and reads as the tooth of the stock.

   The first pass used only layer 2, at 3.8 to 6 percent. That is below the
   threshold where a texture registers at all on a normal screen, which is why
   it looked like nothing had changed.

   background-attachment:fixed on the page so the grain belongs to the sheet
   rather than scrolling with the content, which is the difference between paper
   and wallpaper. Cards deliberately stay clean: they sit ON the paper, and the
   figure-and-ground separation described just above is what replaced the static
   drop shadows. Texturing them too would put that back to square one. */
/* An explicit base size. Nothing anchored the cascade before this: html and
   body set no font-size at all, so every text block carried a local override and
   anything that did not was silently on the browser's 16px default. 16.5px is
   the site's own body step, already set by .panel; putting it on body means a
   new element inherits the right size instead of the accidental one. */
body{font-size:16.5px;font-family:var(--sans);background:#F6F5F5;
  color:var(--ardoise);min-height:100vh;display:flex;flex-direction:column;
  /* Crisper text rendering (no layout change): smoother glyph edges on the dark
     header + hero, and legibility-optimised kerning/ligatures site-wide. */
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility;}
/* Header + topnav share one fixed wrapper: the nav sits in normal flow right
   after the header inside it, so their relative position is the browser's own
   layout (correct under any zoom/OS scaling) instead of a JS-measured top:Npx
   offset between two independently fixed siblings. */
.site-chrome{position:fixed;top:0;left:0;right:0;z-index:110;}
/* Skip-to-content link: off-screen until a keyboard user tabs to it, then it
   slides into the top-left so they can jump past the fixed header/nav. */
.skip-link{position:absolute;left:8px;top:-48px;z-index:200;background:var(--groseille);color:#fff;padding:8px 14px;border-radius:0 0 12px 12px;font-size:15px;font-weight:700;text-decoration:none;transition:top .15s;}
.skip-link:focus{top:0;outline:2px solid #fff;outline-offset:2px;}
/* The skip link's target takes focus from script (A1, 2026-09-14); the page itself needs no ring. */
#main-content:focus{outline:none}
/* Scoped to .site-chrome: a bare header{} selector also matched every panel's
   <header class="page-head">, painting the page title Ardoise-on-Ardoise. */
.site-chrome header{background:var(--ardoise);padding:12px var(--gutter);display:flex;justify-content:space-between;align-items:center;gap:24px;flex-wrap:wrap;position:relative;overflow:hidden;}

/* ── The shelf in the header ────────────────────────────────────────────────
   The bookcase fills the gap BETWEEN the wordmark and the buttons, as a real
   flex item rather than an overlay pinned to a percentage of the band.

   That choice is what makes it survive every width. An overlay sized to 46% is
   fine on a laptop but collapses on a phone, where the wordmark wraps to two
   lines and grows into the art, leaving books squashed behind text. As a flex
   item the shelf simply takes whatever space is left over: it stretches on a
   wide monitor, and on a narrow one it shrinks to nothing and disappears
   cleanly. No breakpoint, no overlap, and no text ever sits on a spine.

   Because nothing has to stay legible over it, the scrim can be light (35%) and
   the books actually show, instead of being dimmed to a suggestion. It fades to
   solid Ardoise at both ends so it reads as shelving glimpsed through the band
   rather than as a panel dropped into it. */
/* Ground is a DARKER ARDOISE, not near-black: same family as the header band,
   so the shelf reads as a recess in it rather than a hole cut through it. */
/* #2A2A2D, a dark neutral grey. This was #372E3C, which is a plum, and that is
   where the band's brownish cast came from. Neutral lets the spine colours be
   the only chroma in the header. */
.h-shelf{position:absolute;inset:0;z-index:0;overflow:hidden;background-color:var(--chrome);
  /* Declared here, not on .map-hero-art: .h-shelf-rail is that element's
     sibling, so a --rail defined inside the art could never reach it. */
  --rail:#9a979c;}
/* The wordmark and the buttons ride ON the shelf, so they need to sit above it. */
.h-left,.h-right{position:relative;z-index:1;}

/* ── The header bookshelf: 159 individually placed books ─────────────────
   NOT a tile. A repeating background cannot avoid repeating: widening the tile
   only makes the books bigger, and shifting it only changes the phase. These
   are 159 separate background layers across 2600px, each with its own width,
   height, colour and position, so no run of spines ever recurs.
   Each book is a two-stop gradient, a lit face and a shaded side, which is what
   makes a rectangle read as a book. Heights vary between 60 and 100 percent of
   the band, the way shelved books do. Generated deterministically
   (tools note: scratchpad/gen-header-shelf.mjs, seed 20260806) so it is stable
   across builds and reviewable in a diff. Soft tones carry it; Groseille,
   Canard, Zinzolin and Carotte appear about one book in eight. */
/* The header shows the SAME bookcase as the landing page, one shelf row deep.
   The art draws its own Perle rail as the shelf board, so no separate board
   element is needed. 196x88 keeps the tile's proportions (250:112 = 2.23,
   196:88 = 2.23) rather than stretching the books to fill the band. */
.h-shelf .map-hero-art{--glow:.86;--color-1:var(--chrome);opacity:.5;}
/* ── Colour zones ──────────────────────────────────────────────────────────
   The artwork is untouched: same 17 spines, same 250px tile, same widths and
   heights. The lit layer is laid down as 10 slices, each exactly one tile wide
   and each with its own palette, so a book shape that recurs every 250px is a
   different colour every time.
   Slice edges sit on tile boundaries, where the artwork has a 1% gap between the
   last spine (ending 99%) and the first (starting 0%), so a colour change never
   cuts a book in half.
   Since 2026-09-16 there is ONE grey, #45454A, with the two neighbours six
   points either side of it, flat: each spine's lit face and shaded side are the
   same value, so the shelf reads as a grain in the band rather than as an
   illustration on it. At the 50% opacity the art carries, the whole range lands
   inside about four points of lightness, which separates one spine from the next
   without reading as a second colour. The earlier seventeen-tone palette (pinks,
   olives, oranges, none of them EPFL values) was the loudest thing on the page.
   Generated by scratchpad/apply-shelf2.js. */
.hz{position:absolute;inset:0;}
.hz1{clip-path:inset(0 calc(100% - 250px) 0 0px);--s1:#45454A;--s1d:#45454A;--s2:#3F3F44;--s2d:#3F3F44;--s3:#4B4B50;--s3d:#4B4B50;--s4:#45454A;--s4d:#45454A;--s5:#3F3F44;--s5d:#3F3F44;--s6:#4B4B50;--s6d:#4B4B50;--s7:#45454A;--s7d:#45454A;--s8:#3F3F44;--s8d:#3F3F44;--s9:#4B4B50;--s9d:#4B4B50;--s10:#45454A;--s10d:#45454A;--s11:#3F3F44;--s11d:#3F3F44;--s12:#4B4B50;--s12d:#4B4B50;--s13:#45454A;--s13d:#45454A;--s14:#3F3F44;--s14d:#3F3F44;--s15:#4B4B50;--s15d:#4B4B50;--s16:#45454A;--s16d:#45454A;--s17:#3F3F44;--s17d:#3F3F44;}
.hz2{clip-path:inset(0 calc(100% - 500px) 0 250px);--s1:#3F3F44;--s1d:#3F3F44;--s2:#4B4B50;--s2d:#4B4B50;--s3:#45454A;--s3d:#45454A;--s4:#3F3F44;--s4d:#3F3F44;--s5:#4B4B50;--s5d:#4B4B50;--s6:#45454A;--s6d:#45454A;--s7:#3F3F44;--s7d:#3F3F44;--s8:#4B4B50;--s8d:#4B4B50;--s9:#45454A;--s9d:#45454A;--s10:#3F3F44;--s10d:#3F3F44;--s11:#4B4B50;--s11d:#4B4B50;--s12:#45454A;--s12d:#45454A;--s13:#3F3F44;--s13d:#3F3F44;--s14:#4B4B50;--s14d:#4B4B50;--s15:#45454A;--s15d:#45454A;--s16:#3F3F44;--s16d:#3F3F44;--s17:#4B4B50;--s17d:#4B4B50;}
.hz3{clip-path:inset(0 calc(100% - 750px) 0 500px);--s1:#4B4B50;--s1d:#4B4B50;--s2:#45454A;--s2d:#45454A;--s3:#3F3F44;--s3d:#3F3F44;--s4:#4B4B50;--s4d:#4B4B50;--s5:#45454A;--s5d:#45454A;--s6:#3F3F44;--s6d:#3F3F44;--s7:#4B4B50;--s7d:#4B4B50;--s8:#45454A;--s8d:#45454A;--s9:#3F3F44;--s9d:#3F3F44;--s10:#4B4B50;--s10d:#4B4B50;--s11:#45454A;--s11d:#45454A;--s12:#3F3F44;--s12d:#3F3F44;--s13:#4B4B50;--s13d:#4B4B50;--s14:#45454A;--s14d:#45454A;--s15:#3F3F44;--s15d:#3F3F44;--s16:#4B4B50;--s16d:#4B4B50;--s17:#45454A;--s17d:#45454A;}
.hz4{clip-path:inset(0 calc(100% - 1000px) 0 750px);--s1:#45454A;--s1d:#45454A;--s2:#3F3F44;--s2d:#3F3F44;--s3:#4B4B50;--s3d:#4B4B50;--s4:#45454A;--s4d:#45454A;--s5:#3F3F44;--s5d:#3F3F44;--s6:#4B4B50;--s6d:#4B4B50;--s7:#45454A;--s7d:#45454A;--s8:#3F3F44;--s8d:#3F3F44;--s9:#4B4B50;--s9d:#4B4B50;--s10:#45454A;--s10d:#45454A;--s11:#3F3F44;--s11d:#3F3F44;--s12:#4B4B50;--s12d:#4B4B50;--s13:#45454A;--s13d:#45454A;--s14:#3F3F44;--s14d:#3F3F44;--s15:#4B4B50;--s15d:#4B4B50;--s16:#45454A;--s16d:#45454A;--s17:#3F3F44;--s17d:#3F3F44;}
.hz5{clip-path:inset(0 calc(100% - 1250px) 0 1000px);--s1:#3F3F44;--s1d:#3F3F44;--s2:#4B4B50;--s2d:#4B4B50;--s3:#45454A;--s3d:#45454A;--s4:#3F3F44;--s4d:#3F3F44;--s5:#4B4B50;--s5d:#4B4B50;--s6:#45454A;--s6d:#45454A;--s7:#3F3F44;--s7d:#3F3F44;--s8:#4B4B50;--s8d:#4B4B50;--s9:#45454A;--s9d:#45454A;--s10:#3F3F44;--s10d:#3F3F44;--s11:#4B4B50;--s11d:#4B4B50;--s12:#45454A;--s12d:#45454A;--s13:#3F3F44;--s13d:#3F3F44;--s14:#4B4B50;--s14d:#4B4B50;--s15:#45454A;--s15d:#45454A;--s16:#3F3F44;--s16d:#3F3F44;--s17:#4B4B50;--s17d:#4B4B50;}
.hz6{clip-path:inset(0 calc(100% - 1500px) 0 1250px);--s1:#4B4B50;--s1d:#4B4B50;--s2:#45454A;--s2d:#45454A;--s3:#3F3F44;--s3d:#3F3F44;--s4:#4B4B50;--s4d:#4B4B50;--s5:#45454A;--s5d:#45454A;--s6:#3F3F44;--s6d:#3F3F44;--s7:#4B4B50;--s7d:#4B4B50;--s8:#45454A;--s8d:#45454A;--s9:#3F3F44;--s9d:#3F3F44;--s10:#4B4B50;--s10d:#4B4B50;--s11:#45454A;--s11d:#45454A;--s12:#3F3F44;--s12d:#3F3F44;--s13:#4B4B50;--s13d:#4B4B50;--s14:#45454A;--s14d:#45454A;--s15:#3F3F44;--s15d:#3F3F44;--s16:#4B4B50;--s16d:#4B4B50;--s17:#45454A;--s17d:#45454A;}
.hz7{clip-path:inset(0 calc(100% - 1750px) 0 1500px);--s1:#45454A;--s1d:#45454A;--s2:#3F3F44;--s2d:#3F3F44;--s3:#4B4B50;--s3d:#4B4B50;--s4:#45454A;--s4d:#45454A;--s5:#3F3F44;--s5d:#3F3F44;--s6:#4B4B50;--s6d:#4B4B50;--s7:#45454A;--s7d:#45454A;--s8:#3F3F44;--s8d:#3F3F44;--s9:#4B4B50;--s9d:#4B4B50;--s10:#45454A;--s10d:#45454A;--s11:#3F3F44;--s11d:#3F3F44;--s12:#4B4B50;--s12d:#4B4B50;--s13:#45454A;--s13d:#45454A;--s14:#3F3F44;--s14d:#3F3F44;--s15:#4B4B50;--s15d:#4B4B50;--s16:#45454A;--s16d:#45454A;--s17:#3F3F44;--s17d:#3F3F44;}
.hz8{clip-path:inset(0 calc(100% - 2000px) 0 1750px);--s1:#3F3F44;--s1d:#3F3F44;--s2:#4B4B50;--s2d:#4B4B50;--s3:#45454A;--s3d:#45454A;--s4:#3F3F44;--s4d:#3F3F44;--s5:#4B4B50;--s5d:#4B4B50;--s6:#45454A;--s6d:#45454A;--s7:#3F3F44;--s7d:#3F3F44;--s8:#4B4B50;--s8d:#4B4B50;--s9:#45454A;--s9d:#45454A;--s10:#3F3F44;--s10d:#3F3F44;--s11:#4B4B50;--s11d:#4B4B50;--s12:#45454A;--s12d:#45454A;--s13:#3F3F44;--s13d:#3F3F44;--s14:#4B4B50;--s14d:#4B4B50;--s15:#45454A;--s15d:#45454A;--s16:#3F3F44;--s16d:#3F3F44;--s17:#4B4B50;--s17d:#4B4B50;}
.hz9{clip-path:inset(0 calc(100% - 2250px) 0 2000px);--s1:#4B4B50;--s1d:#4B4B50;--s2:#45454A;--s2d:#45454A;--s3:#3F3F44;--s3d:#3F3F44;--s4:#4B4B50;--s4d:#4B4B50;--s5:#45454A;--s5d:#45454A;--s6:#3F3F44;--s6d:#3F3F44;--s7:#4B4B50;--s7d:#4B4B50;--s8:#45454A;--s8d:#45454A;--s9:#3F3F44;--s9d:#3F3F44;--s10:#4B4B50;--s10d:#4B4B50;--s11:#45454A;--s11d:#45454A;--s12:#3F3F44;--s12d:#3F3F44;--s13:#4B4B50;--s13d:#4B4B50;--s14:#45454A;--s14d:#45454A;--s15:#3F3F44;--s15d:#3F3F44;--s16:#4B4B50;--s16d:#4B4B50;--s17:#45454A;--s17d:#45454A;}
.hz10{clip-path:inset(0 0 0 2250px);--s1:#45454A;--s1d:#45454A;--s2:#3F3F44;--s2d:#3F3F44;--s3:#4B4B50;--s3d:#4B4B50;--s4:#45454A;--s4d:#45454A;--s5:#3F3F44;--s5d:#3F3F44;--s6:#4B4B50;--s6d:#4B4B50;--s7:#45454A;--s7d:#45454A;--s8:#3F3F44;--s8d:#3F3F44;--s9:#4B4B50;--s9d:#4B4B50;--s10:#45454A;--s10d:#45454A;--s11:#3F3F44;--s11d:#3F3F44;--s12:#4B4B50;--s12d:#4B4B50;--s13:#45454A;--s13d:#45454A;--s14:#3F3F44;--s14d:#3F3F44;--s15:#4B4B50;--s15d:#4B4B50;--s16:#45454A;--s16d:#45454A;--s17:#3F3F44;--s17d:#3F3F44;}

.h-shelf .mh-lit{display:none;}
.h-shelf .mhs-up{display:none;}
/* The fade: solid behind the wordmark, fully clear by 640px, so the books reach
   full colour across the rest of the band. Nothing dims the right-hand side, which
   is why Search and Log in carry solid backgrounds - white text on a lit Soufre or
   Vert d'eau spine is unreadable, so the controls bring their own ground. */
/* SHORTENED from 500px solid / clear by 920px. This scrim, not the ground
   colour, was the reason the band read as a flat slab: it covered most of the
   artwork on any normal screen, so recolouring the shelf changed almost nothing
   that was actually visible. The wordmark still gets its backing; the books now
   show across the rest of the band. */
.h-shelf::after{content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  background:linear-gradient(90deg,var(--chrome) 0,var(--chrome) var(--shelf-start),
    transparent calc(var(--shelf-start) + 24px),
    transparent calc(100% - var(--shelf-end) - 24px),
    var(--chrome) calc(100% - var(--shelf-end)));}
/* Both ends are measured, not guessed, and both are distances the layout actually produces:
   --shelf-start is where the wordmark ends, --shelf-end is how far the Search and Log in buttons
   reach in from the right. The buttons are right-aligned, so the second is near enough constant per
   breakpoint: 262px above 1600, 230px from 1024, 202px below that
   (scratchpad/shelf-options/measure-header.mjs). Ascending min-width only, so each one overrides
   the last. The books therefore live in the gap between the two, and touch neither. */
.h-shelf{--shelf-start:525px;--shelf-end:202px;}
@media (min-width:1024px){.h-shelf{--shelf-start:553px;--shelf-end:230px;}}
@media (min-width:1600px){.h-shelf{--shelf-start:585px;--shelf-end:262px;}}
/* Below 768px the wordmark and the buttons nearly touch: about 10px of band is left, which is not a
   shelf, it is a smear. Hide the art and the board rather than draw a sliver. */
@media (max-width:767px){.h-shelf .map-hero-art,.h-shelf-rail{display:none;}}
/* The shelf line runs the FULL width, uniformly, including behind the wordmark.
   The artwork draws its own rail, but the fade covers it with solid Ardoise on
   the left, so the board appeared to start only where the books did. Drawing it
   as its own element ABOVE the fade (z-index 3, the fade is 2) makes it one
   continuous line edge to edge.
   Proportions match the art exactly: the tile puts Perle in its bottom 5% and a
   dark sliver from 5 to 7%, which at an 88px band is 4.4px and 1.8px. */
.h-shelf-rail{position:absolute;left:0;right:0;bottom:0;height:6.2px;z-index:3;pointer-events:none;
  background:linear-gradient(to top,var(--rail) 0 4.4px,var(--chrome) 4.4px 6.2px);}
.h-shelf .mhs-lo{top:auto;bottom:0;height:88px;
  background-size:196px 88px;background-position:var(--shelf-x) bottom;}

/* Search and Log in are opaque, so they read as solid controls resting on the
   shelf rather than as outlines with books showing through them. */
header .h-search-btn,header .h-login-btn{background:var(--ardoise);border-color:rgba(255,255,255,.5);
  box-shadow:0 2px 12px rgba(0,0,0,.55);}
header .h-search-btn:hover,header .h-login-btn:hover{background:#3a3140;border-color:rgba(255,255,255,.75);color:#fff;}
.h-right{display:flex;flex-direction:row;align-items:center;gap:18px;}
.h-title-block{cursor:pointer;display:inline-block;}
.h-title-block:hover .h-brand{opacity:0.85;}
.h-title-block:focus-visible{outline:2px solid rgba(255,255,255,0.5);outline-offset:4px;border-radius:4px;}
.h-logo-link{display:inline-flex;align-items:center;text-decoration:none;}
.h-logo-link:focus-visible{outline:2px solid rgba(255,255,255,0.5);outline-offset:4px;border-radius:4px;}
.h-logo{height:44px;width:auto;}
/* The header brand is the MDH laptop illustration (decorative), inlined so its
   SMIL animation runs (SMIL is frozen inside <img>). Sized to roughly match the
   MDH title block height. The header has no fixed height and a load/resize script
   re-measures --main-top, so a taller brand mark just grows the header band. */
.h-logo-laptop-wrap{display:inline-flex;align-items:center;line-height:0;}
/* 42:28 now that the viewBox is cropped to the laptop itself (2026-09-16): the same 64px of header
   height, with no empty margin inside it. */
.h-logo-laptop{height:64px;width:96px;display:block;}
.h-logo-sub{font-size:12px;letter-spacing:2px;text-transform:uppercase;color:#fff;}
/* EssentialTech logo, moved to the footer "Built by" column and the homepage
   About section. White logo on the dark footer; boxed on the light About card. */
.ph-footer-logo-link{display:inline-block;margin:2px 0 0;}
.ph-footer-logo{height:56px;width:auto;display:block;}
.ph-about-logo-link{display:inline-block;margin:18px 0 0;}
.ph-about-logo{height:40px;width:auto;display:block;background:var(--ardoise);padding:8px 12px;border-radius:12px;}
/* Laptop brand mark sits to the LEFT of the MDH title block. */
.h-left{display:flex;align-items:center;gap:16px;}
/* The site name was the page's h1 on every tab, so no page had its own title as its main heading.
   It is a div now, and these rules carry the h1 look it had (bold, no margin, normal line height). */
.h-left .h-brand{font-family:var(--sans);font-size:22px;font-weight:700;line-height:normal;margin:0;color:#fff;letter-spacing:-0.5px;}
.h-left .h-brand em{font-style:normal;}
.h-left p{font-size:13px;color:#fff;margin-top:4px;letter-spacing:1.2px;text-transform:uppercase;}
.h-left p em{font-style:normal;font-weight:700;font-size:20px;line-height:0;letter-spacing:0;}
.hm{color:var(--rose);}
.hd{color:var(--acier);}
.hh{color:var(--mont-rose);}

.tab-bar[hidden]{display:none !important;}
sup.ref{font-size:12px;font-weight:700;opacity:.65;vertical-align:super;margin-left:1px;cursor:pointer;color:var(--zinzolin);text-decoration:underline;text-underline-offset:2px}
sup.ref:hover{opacity:1}
.main{padding-top:var(--main-top);flex:1 0 auto;}

/* ── Top navigation bar: section tabs (left) + audience selector (right), fixed under the header ── */
/* ── The header ground ─────────────────────────────────────────────────────
   Flat Ardoise read as a cheap slab: one unmodulated fill has no depth, and at
   nav height it is the first thing anyone sees. Three layers now, in the order
   they stack:
     1. a micro dot lattice, 1px at 5.5% white on a 7px grid, which reads as fine
        texture rather than as dots and gives the surface something to catch;
     2. a downward scrim, the SAME construction as --banner-bg (Ardoise under
        black rather than a new hex), so the header and the page banners read as
        one family and the bar sits above the page instead of floating on it;
     3. Ardoise itself, still the only colour involved.
   A dot lattice, not the filter bar's diagonal hatch: the two surfaces do
   different jobs, one is identity and one is a control, and repeating the hatch
   would make them look like the same component. The subnav between them stays
   flat Perle on purpose - three textured rows in a stack is noise. */
/* NO PATTERN HERE, deliberately. A dot lattice was tried and removed: the page
   below now carries a grain and the filter row carries a hatch, and a third
   textured surface in the same vertical stack is noise rather than depth. The
   nav keeps only the downward scrim, which gives it weight without a texture.
   Its grey matches the header exactly so the two read as one object. */
.topnav{
  background:
    linear-gradient(rgba(0,0,0,.14), rgba(0,0,0,.30)),
    var(--chrome);
  border-top:1px solid rgba(255,255,255,.22);
  /* A 1px light edge along the bottom. This is what makes an edge read as milled
     rather than cut, and it is most of the difference between "flat" and
     "finished" on a dark bar. */
  box-shadow:inset 0 -1px 0 rgba(255,255,255,.10);
  position:relative;z-index:1}
.topnav-inner{display:flex;align-items:stretch;justify-content:space-between;gap:10px;padding:0 var(--gutter);min-height:var(--nav-h);flex-wrap:wrap}
.topnav-tabs{display:flex;align-items:stretch;gap:0;flex-wrap:wrap}
.topnav-tab{appearance:none;background:none;border:none;color:rgba(255,255,255,.72);font-family:var(--sans);font-size:13.5px;font-weight:500;letter-spacing:.1px;padding:0 9px;cursor:pointer;position:relative;display:inline-flex;align-items:center;min-height:var(--nav-h);transition:color .15s;white-space:nowrap}
.topnav-tab:hover{color:#fff}
.topnav-tab.active{color:#fff;font-weight:600}
.topnav-tab.active::after{content:"";position:absolute;left:14px;right:14px;bottom:0;height:3px;background:var(--groseille);border-radius:4px 4px 0 0}
.topnav-tab:focus-visible{outline:2px solid var(--canard);outline-offset:-2px;border-radius:4px}
/* Each nav tab that has sub-tabs reveals them as a hover menu for direct
   selection (this replaces the old "explain the tab" tooltip). Tabs without
   sub-tabs show nothing on hover. Guarded to fine-pointer devices so touch
   users are not left with a stuck-open menu. */
.topnav-tab-wrap{position:relative;display:flex;align-items:stretch}
.topnav-tab-menu{position:absolute;top:100%;left:6px;z-index:130;min-width:190px;background:#fff;border-radius:0 0 12px 12px;box-shadow:0 12px 30px rgba(0,0,0,.28);padding:6px;display:flex;flex-direction:column;gap:1px;opacity:0;visibility:hidden;transform:translateY(-4px);transition:opacity .14s,transform .14s,visibility .14s;pointer-events:none}
.topnav-tab-menu-item{appearance:none;background:none;border:none;text-align:left;font-family:var(--sans);font-size:13.5px;font-weight:700;letter-spacing:.1px;color:var(--ardoise);padding:9px 12px;border-radius:4px;cursor:pointer;white-space:nowrap;transition:background .12s,color .12s}
.topnav-tab-menu-item:hover{background:var(--perle);color:var(--groseille)}
.topnav-tab-menu-item:focus-visible{outline:2px solid var(--canard);outline-offset:-2px}
@media (hover:hover) and (pointer:fine){
  .topnav-tab-wrap:hover .topnav-tab-menu,.topnav-tab-wrap:focus-within .topnav-tab-menu{opacity:1;visibility:visible;transform:translateY(0);pointer-events:auto}
  /* The active section already shows its sub-tabs in the row below, and its hover menu opened exactly on top
     of that row, so a pointer moving down to a sub-tab could click the menu instead (site scan, 2026-09-15). */
  .topnav-tab-wrap:has(.topnav-tab.active) .topnav-tab-menu{display:none}
}
.topnav-aud{display:flex;align-items:center;gap:6px;flex-shrink:0}
.topnav-aud-label{font-family:var(--sans);font-size:12px;font-weight:600;letter-spacing:1.2px;text-transform:uppercase;color:rgba(255,255,255,.55)}
/* View select box: a single button that opens a small menu. */
.aud-select{position:relative}
.aud-select-btn{display:inline-flex;align-items:center;gap:6px;background:transparent;border:1px solid rgba(255,255,255,.35);color:#fff;font-family:var(--sans);font-size:13px;font-weight:500;letter-spacing:.1px;padding:5px 10px;border-radius:12px;cursor:pointer;transition:border-color .15s,background .15s;white-space:nowrap}
.aud-select-btn:hover{border-color:rgba(255,255,255,.65)}
.aud-select-btn[aria-expanded="true"]{background:rgba(255,255,255,.10);border-color:rgba(255,255,255,.65)}
.aud-select-btn:focus-visible{outline:2px solid var(--canard);outline-offset:2px}
.aud-select-caret{font-size:12px;opacity:.85}
.aud-select-menu{position:absolute;top:calc(100% + 6px);right:0;z-index:120;min-width:210px;background:#fff;border-radius:12px;box-shadow:0 14px 36px rgba(0,0,0,.32);padding:6px;display:flex;flex-direction:column;gap:2px}
.aud-select-menu[hidden]{display:none}
.aud-select-menu .topnav-aud-btn{appearance:none;display:block;width:100%;text-align:left;background:transparent;border:none;color:var(--ardoise);border-radius:4px;padding:10px 12px;font-family:var(--sans);font-size:13.5px;font-weight:600;letter-spacing:0;cursor:pointer}
.aud-select-menu .topnav-aud-btn:hover{background:rgba(69,58,76,.08)}
.aud-select-menu .topnav-aud-btn.active{background:var(--groseille);color:#fff}
.aud-select-menu .topnav-aud-btn:focus-visible{outline:2px solid var(--canard);outline-offset:-2px}
/* Second row: the active section's subtabs, shown only when a section has them. */
.topnav-sub{background:var(--perle);border-top:1px solid rgba(0,0,0,.10);border-bottom:1px solid rgba(0,0,0,.10)}
.topnav-sub[hidden]{display:none}
.topnav-sub-inner{display:flex;align-items:stretch;gap:2px;padding:0 var(--gutter);min-height:var(--subnav-h);flex-wrap:wrap}
.topnav-subtab{position:relative;appearance:none;background:none;border:none;color:var(--ink-body);font-family:var(--sans);font-size:13px;font-weight:500;letter-spacing:.2px;padding:0 11px;min-height:var(--subnav-h);display:inline-flex;align-items:center;cursor:pointer;transition:color .15s;white-space:nowrap}
.topnav-subtab:hover{color:var(--ardoise)}
.topnav-subtab.active{color:var(--ardoise);font-weight:600}
.topnav-subtab.active::after{content:"";position:absolute;left:14px;right:14px;bottom:0;height:3px;background:var(--groseille);border-radius:4px 4px 0 0}
.topnav-subtab:focus-visible{outline:2px solid var(--canard);outline-offset:-2px;border-radius:4px}
/* On hover/focus a subtab explains what it is. */
.topnav-subtab-desc{position:absolute;top:calc(100% + 6px);left:14px;z-index:120;width:max-content;max-width:300px;padding:8px 11px;background:#fff;color:var(--ardoise);border-radius:12px;box-shadow:0 10px 28px rgba(0,0,0,.28);font-size:13.5px;font-weight:400;line-height:1.45;letter-spacing:0;text-transform:none;white-space:normal;opacity:0;visibility:hidden;transform:translateY(-3px);transition:opacity .14s,transform .14s,visibility .14s;pointer-events:none}
.topnav-subtab:hover .topnav-subtab-desc,.topnav-subtab:focus-visible .topnav-subtab-desc{opacity:1;visibility:visible;transform:translateY(0)}
/* ── Interior page header ───────────────────────────────────────────────────
   What replaced the 265px bookshelf band on every interior page. A hero is a
   greeting and you greet someone once: Home keeps the full shelf, and the pages
   people actually work in start with their content instead.

   Four lines, always in the same order, so a reader learns the shape once:
     eyebrow   which section this belongs to (only where there is a parent)
     title     the page
     sub       one line on what it is for
     meta      a figure where the count is the point, a keyword where it is not

   Groseille on the eyebrow is the site's single accent, not a per-page colour:
   one accent is what keeps a page from reading as multicoloured. */
/* No rule under the header: the note beside it and the band change below it
   already separate it from the page, and a third divider read as a stray line
   with empty space under it. */
.page-head{padding:0;margin:0;}
.page-eyebrow{font-family:var(--sans);font-size:12.5px;font-weight:700;letter-spacing:.15em;
  text-transform:uppercase;color:var(--groseille);margin:0 0 9px;}
.page-head .page-headline-title{font-family:var(--display);font-size:30px;font-weight:600;
  letter-spacing:-.02em;line-height:1.04;color:var(--ardoise);margin:0;}
.page-head .page-sub{font-size:19px;line-height:1.55;color:rgba(69,58,76,.78);margin:14px 0 0;max-width:none;}
.page-head .page-meta{font-family:var(--sans);font-size:12.5px;font-weight:700;letter-spacing:.15em;
  text-transform:uppercase;color:var(--ink-muted);margin:26px 0 0;}


/* ── 3b. THE PAGE FORMAT: bands, eyebrows, display headings ─────────────────
   The blog/poster register. A page is a stack of BANDS, each a full-width strip
   of one ground colour holding one centred content column. Alternating the
   ground is what gives a long page rhythm instead of one continuous scroll.

   The shape inside a band is always the same, so a reader learns it once:
       eyebrow      small, uppercase, Groseille - what kind of thing this is
       display h    Newsreader serif, large - the point
       body         Inter, 17px, capped at 62 characters
       content      whatever the section actually holds

   Bands are full-bleed: they cancel the panel's gutter with a negative margin
   and re-establish the measure inside, so a ground colour reaches both edges of
   the window while the text stays in the same column as everything else. */
/* 56px, not 64. What is MDH? sets 56 (.mdh-page-section) and it is now the one number
   for every banded section on the site, homepage included. The old 3b-i note called the
   64/56 split "the one number the four tabs still disagree on"; they no longer do. */
.band{margin:0 calc(var(--gutter) * -1);padding:56px var(--gutter);}
.band > *{max-width:var(--measure);margin-inline:auto;}
/* GROUNDS ALTERNATE AUTOMATICALLY. Hand-assigned .band-white / .band-ground drifted the
   moment a band was added or removed: map.html ended up white, ground, white, WHITE,
   breaking its own stated rule that no two bands of the same ground may touch. This is the
   mechanism .mdh-page-section already uses, and it cannot get out of order.
   The explicit classes are kept as an override for the few panels that still set them. */
/* Bands paint their own ground, and the `background` SHORTHAND resets
   background-image, so each has to restate the grain in longhand or the page
   texture simply vanishes behind them.
   THREE strengths, not one: white bands take the faintest dose so they still
   read as the light half of the alternation, tinted and ground bands take more.
   A single uniform texture flattened the band rhythm the whole site is built
   on. */
.band:nth-of-type(odd),.band-white{background-color:#fff;}
.band:nth-of-type(even),.band-ground{background-color:var(--ground);}
/* .band.band-tint, not .band-tint: the alternation above is a class PLUS a
   pseudo-class, so a bare class loses to it and every tinted band on the site
   was rendering as plain ground instead. Measured on Beyond EPFL: the band
   marked band-tint came back #F6F5F5, the ground, where the token says #F0EEF1.
   Four bands were affected, on Beyond EPFL, Contribute, Funding and Takeaways.
   Tint has to be hand-assigned because the alternation only knows two grounds,
   so this is the one ground class that must be able to win. */
.band.band-tint{background-color:var(--ground-tint);}

/* ── GRAPH PAPER, under the cards themselves ──────────────────────────────
   Earlier this was on the whole band, keyed by :has(). That was still too broad:
   a band usually opens with an eyebrow, a headline and a lede before its cards,
   and ruling ran under all of it. Ruling under running text makes a page look
   like a form to fill in.
   It is on the GRID now, so the paper starts where the cards start and stops
   where they stop, and the prose above sits on a clean ground. Padding so the
   sheet extends a little past the cards rather than being cut flush to them.

   Pitches divide by construction: a 16px minor rule and an 80px major, exactly
   five minors, the 5:1 real graph paper uses. An earlier pass used a 22px dot
   grid over a 5px grain, and those do not divide, so the small dots never landed
   on the big ones and it read as two unrelated speckles.

   Anchored to the element, not the viewport: background-attachment:fixed made
   one continuous sheet under the whole page, but the ruling then slid under the
   content as you scrolled and belonged to the window rather than to the cards. */
/* FULL BLEED, not a panel. Painted on the grid box itself with padding and a
   radius it read as one more rounded card, a box around the boxes, which is the
   opposite of what a ground should do. It is a layer behind the cards now: an
   absolutely positioned ::before, 100vw wide and centred, so the ruling runs the
   full width of the section and simply stops where the cards stop vertically.
   The grid keeps its own layout untouched, so no column maths changes.
   overflow-x:clip on .band contains the 100vw so it cannot raise a horizontal
   scrollbar; clip rather than hidden, because hidden would make every band a
   scroll container and break sticky positioning inside it. */
.chart-grid,.actor-grid,.proj-grid,.spider-grid,
.rd-grid,.ins-grid,.beyond-grid,.pgrid{position:relative;padding:22px 0;}
.chart-grid::before,.actor-grid::before,.proj-grid::before,.spider-grid::before,
.rd-grid::before,.ins-grid::before,.beyond-grid::before,.pgrid::before{
  content:"";position:absolute;inset:0 auto;top:0;bottom:0;
  left:50%;width:100vw;transform:translateX(-50%);
  z-index:0;pointer-events:none;
  background-image:
    linear-gradient(rgba(69,58,76,.042) 1px,transparent 1px),
    linear-gradient(90deg,rgba(69,58,76,.042) 1px,transparent 1px),
    linear-gradient(rgba(69,58,76,.022) 1px,transparent 1px),
    linear-gradient(90deg,rgba(69,58,76,.022) 1px,transparent 1px);
  background-size:80px 80px,80px 80px,16px 16px,16px 16px;
}
/* The cards ride above the sheet. Without this the ::before would paint over
   them, because a positioned pseudo-element beats static siblings. */
.chart-grid > *,.actor-grid > *,.proj-grid > *,.spider-grid > *,
.rd-grid > *,.ins-grid > *,.beyond-grid > *,.pgrid > *{position:relative;z-index:1}
/* Beside the filter rail the paper starts at the content column: centred on 100vw it ran under
   the rail and ended there in a hard edge (2026-09-14). It still bleeds to the right edge. */
.epfl-main :is(.chart-grid,.actor-grid,.proj-grid,.spider-grid,.pgrid)::before{
  left:0;right:calc(var(--gutter) * -1);width:auto;transform:none}
.band{overflow-x:clip}

/* Consecutive bands of the same ground would read as one, so collapse the seam. */
.band + .band{padding-top:56px;}
/* Nothing between the last band and the footer. A panel ending in a band kept its own 48px bottom
   padding, painted in the body grey under the band's white: an empty two-tone strip above the
   footer on every interior page. The What is MDH? and Glossary bodies are built of full-bleed
   sections that carry their own bottom padding, so the band around them adds none either. */
.panel.active:has(.band){padding-bottom:0;}
.band:has(> #panel-mdh-body),.band:has(> #panel-glossary-body){padding-bottom:0;}

/* ── Card interiors, in the draft's voice ────────────────────────────────────
   A card's own heading is display type, like every other heading on the site,
   and its supporting text is Inter at the same measure as body copy. Before
   this, card titles were bold sans at a size close to their body text, so a card
   had no internal hierarchy at all and read as one undifferentiated block.
   Scoped to the card classes so nothing else inherits it. */
.beyond-card-q,
/* .rd-card h3 removed from this list: the card's h3 carries .rd-card-title,
   and this selector (0,1,1) was beating it (0,1,0), so the 21px title size
   declared below never rendered. */
.ins-card h3, .ins-card .ins-title,
/* .proj-card is gone: nothing in site/js or site/partials emitted it. Work cards
   are .rcard.rcard-compact, the same component the actor cards use, so the two
   grids already share an anatomy. Its rules included a border colour-coded by
   MDH closeness in three different hues, which is the same "one facet, three
   colours" defect removed from the label pills, so it is deleted rather than
   left to be revived by accident. .proj-grid is still live and still used. */
/* .chart-card h3 was declared here at 19px and again below at 16px; the later
   rule wins, so this one only made the real size hard to find. */
.def-block h4{
  font-family:var(--display);font-weight:600;font-size:19px;line-height:1.2;
  color:var(--ardoise);margin:0 0 6px;
}
/* The audience note under a Beyond card says who the card is for. It was set like
   the figure labels, but it runs two to five lines, and letter-spaced capitals at
   that length shout; .beyond-card p also beat the 12px, so it rendered at 16px.
   Sentence case and normal weight, a step under body (2026-09-14). Two classes so
   it outranks .beyond-card p. */
.beyond-card .beyond-card-aud{
  font-family:var(--sans);font-size:15px;font-weight:400;letter-spacing:normal;
  line-height:1.55;text-transform:none;color:var(--ink-muted);
}

/* ── Actions: the draft's buttons ───────────────────────────────────────────
   A band can end by sending the reader somewhere. These are the same two shapes
   the reference uses: one filled, one outlined, side by side.
   They are <button> elements wired to the site's own dispatcher
   (data-action="navigateFromDrawer"), not links, so tab switching stays in one
   place rather than being duplicated as hrefs that would reload the page.
   Groseille only. A second button colour would make the accent mean nothing. */
.actions{display:flex;gap:14px;flex-wrap:wrap;margin:28px 0 0;align-items:center;}
.btn{appearance:none;font-family:var(--sans);font-size:15px;font-weight:600;
  border-radius:10px;padding:13px 22px;cursor:pointer;text-decoration:none;
  display:inline-flex;align-items:center;gap:8px;line-height:1;
  transition:background .15s,border-color .15s,color .15s;}
.btn-solid{background:var(--groseille);color:#fff;border:1px solid var(--groseille);}
.btn-solid:hover{background:#8f1919;border-color:#8f1919;}
.btn-ghost{background:#fff;color:var(--ardoise);border:1px solid var(--ardoise);}
.btn-ghost:hover{background:var(--ardoise);color:#fff;}
.btn:focus-visible{outline:2px solid var(--groseille);outline-offset:3px;}

/* ── Chips ──────────────────────────────────────────────────────────────────
   A list of short labels that is not a filter and not a link: who a page is for,
   what a section covers. Deliberately quiet, because a row of chips next to a
   row of filter buttons would otherwise look like a second set of controls. */
.chips{display:flex;flex-wrap:wrap;gap:10px;margin:0 0 22px;}
.chip{font-family:var(--sans);font-size:14.5px;font-weight:500;color:var(--ardoise);
  background:#fff;border:1px solid var(--perle);border-radius:999px;padding:9px 16px;}


/* ── Card accents ───────────────────────────────────────────────────────────
   One colour per card family, on the title and on a single-side rule. Chosen
   from the only chart colours that are legible as text on white: Canard 5.5:1,
   Zinzolin 10.3:1, Marron 10.7:1. Groseille is reserved for eyebrows and
   interaction, and Rose, Acier and Mont Rose are reserved for the MDH pillars,
   so they never appear here.
   The rule is on ONE side only, deliberately: a full border plus a coloured bar
   is the generated-component look we are moving away from. */
/* The border-left card treatment that used to live here is dead: .beyond-card and
   .beyond-card-q are both redeclared later in this file and win on order. Removed
   rather than left to be read as the live rule. */
/* The border-left .rd-card treatment that was here is dead: .rd-card is
   redeclared later in this file and wins on order. */
.rd-card h3,.rd-card .rd-title{color:var(--zinzolin);}
.ins-card{border:none;border-left:3px solid var(--marron);border-top:none;border-radius:0 12px 12px 0;}
.ins-card h3,.ins-card .ins-title{color:var(--marron);}
.fc-card{border:none;border-left:3px solid var(--canard);border-radius:0 12px 12px 0;}
/* Actor and work cards stay neutral. They are the bulk of the site, and giving
   the two densest grids a colour as well would turn the page back into a
   rainbow, which is the thing this palette exists to prevent. */
/* ── NEW COPY, FLAGGED FOR REVIEW ────────────────────────────────────────────
   Marron marks a sentence that was WRITTEN during the redesign rather than
   carried over from the original page. It is a review aid, not a design
   decision: the point is that you can see at a glance what is new and approve
   or reject it, instead of having to diff two versions of every page.
   Marron is the right colour for it because it is a real chart colour that is
   used nowhere else in the UI, so nothing it marks can be confused for a normal
   accent, and it clears 10.68:1 on white so the flagged text is still readable.
   TO REMOVE THE FLAGGING once the copy is approved: delete this rule, then run
   node tools/build/unflag-new-copy.js to unwrap the spans. */
.new-copy{color:var(--marron);}
.eyebrow{font-family:var(--sans);font-size:12.5px;font-weight:700;letter-spacing:.15em;
  text-transform:uppercase;color:var(--groseille);margin:0 0 14px;}

/* Display headings. Newsreader at a real size: the point of a serif here is the
   jump between it and the body, so these are deliberately much larger than the
   17px text under them rather than a step away from it. */
.display-xl{font-family:var(--display);font-weight:600;font-size:30px;line-height:1.08;
  letter-spacing:-.015em;color:var(--ardoise);margin:0 0 22px;max-width:16ch;}
.display-l{font-family:var(--display);font-weight:600;font-size:23px;line-height:1.15;
  letter-spacing:-.01em;color:var(--ardoise);margin:0 0 14px;}
.display-m{font-family:var(--display);font-weight:600;font-size:20px;line-height:1.2;
  color:var(--ardoise);margin:0 0 10px;}
/* 80ch, up from 62ch. 62 is the classic reading measure, but at 17px it stopped
   body prose around 530px inside a 1240px band, so every paragraph sat in a
   narrow column with a page of empty space beside it. Widened on request. */
.band p{font-size:17px;line-height:1.65;color:rgba(69,58,76,.78);max-width:none;}
/* Every eyebrow on the site is a <p class="eyebrow"> and every one of them sits
   inside a band, so .band p (0,1,1) outranked .eyebrow (0,1,0) and the label the
   format is built on rendered as 17px faded body prose instead of 12.5px
   Groseille. Same failure the Status hints hit (see the #panel-status
   .sf-multi-hint / .aca-ack-intro rules): .band p is for
   UNCLASSED prose, and a component that sets its own size keeps it. Re-asserted
   at matching specificity rather than by editing .eyebrow, which is already
   correct for the un-banded case. */
.band p.eyebrow{font-size:12.5px;line-height:inherit;color:var(--groseille);}
/* Same collision, same remedy, for the other classed paragraphs that banding
   swept into a .band. Each of these sets its own type scale, so .band p (0,1,1)
   beating their single-class rules (0,1,0) is a regression, not a cascade the
   format wants:
     .figure-source    a 13.5px italic credit line under a chart, blown up to 17px
                       upright-weight body prose (Publications, Visual Mapping)
     .dataset-download a 15px row holding the download pill, blown up to 17px and
                       clamped to 62ch, which wrapped the note onto a second line
     .sf-multi-hint    protected on size, colour and measure by its id-scoped
                       rules already, but not on line-height, so 13px text was
                       being led at 1.65 instead of 1.5
   Values restored are the components' own, not new ones. */
.band p.figure-source{font-size:13.5px;line-height:1.5;color:var(--ink-muted);max-width:none;}
.band p.dataset-download{font-size:15px;line-height:1.6;color:var(--ardoise);max-width:var(--measure);}
.band p.sf-multi-hint{line-height:1.5;}

/* ── 3b-i. THE TABS ALIGNED TO What is MDH? ─────────────────────────────────
   What is MDH? and the banded interior tabs build a section out of the same
   four parts - eyebrow, serif head, lede, content - and were not drawing them
   at the same size. What is MDH? sets its own furniture in mdh-map.css
   (.mdh-page-section*): a 30px serif head at a 840px measure, a 16.5px lede at
   760px, and a 28px gap from that head block to the content. The generic band
   scale is a 23px .display-l, 17px prose at 62ch, and a .section-head-title at
   46px - larger than the page title above it, and half again the biggest head
   on What is MDH?. Two tabs one click apart in the nav read as two sites.

   Research Directions was aligned first, and its rules lived in that tab's own
   section further down this file. They are here now, with the three Mapping
   EPFL subtabs added to the same selector lists, because the moment a second
   tab wanted them they stopped being one tab's business. Every value below is
   the one Research Directions already shipped, so that tab renders unchanged;
   what is new is only which panels the list names. To align the next tab, add
   its id here rather than writing a fourth set of numbers.

   NOT included, deliberately: .band's 64px padding. What is MDH? bands are
   56px and the Mapping EPFL tabs take that below, but Research Directions was
   aligned without it and changing a tab nobody asked about is not this pass's
   business. That is the one number the four tabs still disagree on.

   The judgement call is .section-head. On What is MDH? a collapse toggle
   (.mdh-tac-hd) is a control and the serif head above it is the heading. On
   these tabs one element was doing both jobs at 46px, which is why an eyebrow,
   a display head and a section head stacked as three competing titles - on
   Publications & Projects the eyebrow repeated the section head word for word
   in every band. It drops to 21px, a clear step under the 30px band head, and
   keeps its triangle, its hover underline and its focus ring: still obviously
   a control, no longer the loudest thing on the page. */
#panel-research-direction .band-hd .display-l,
#panel-map .band-hd .display-l,
#panel-status .band-hd .display-l,
#panel-features .band-hd .display-l,
#panel-contribute .band-hd .display-l{font-size:30px;line-height:1.08;
  letter-spacing:-.015em;max-width:none}
/* The lede. Research Directions marks it with .band-lede; the Mapping EPFL
   partials leave it an unclassed <p>, so the selector takes any paragraph in
   the head block that is not the eyebrow. The :not(.eyebrow) is load bearing:
   .band p.eyebrow (0,1,2) is the only thing keeping an eyebrow at 12.5px
   inside a band, and this rule at (1,2,1) would otherwise blow it up to lede
   size. */
#panel-research-direction .band-hd > p:not(.eyebrow),
#panel-map .band-hd > p:not(.eyebrow),
#panel-status .band-hd > p:not(.eyebrow),
#panel-features .band-hd > p:not(.eyebrow),
#panel-contribute .band-hd > p:not(.eyebrow){font-size:16.5px;line-height:1.6;
  color:rgba(69,58,76,.78);max-width:var(--measure)}
/* Full measure, like the head above it and the content below. The 80ch reading
   cap that used to be here left every band lede visibly narrower than its own
   section, which reads as a mistake rather than as a measure. Section intros are
   two or three lines, not continuous prose, so the measure argument does not
   really apply to them. */
/* .band-hd sets the 28px gap to the content, so the last line inside it must
   not add its own on top. */
#panel-research-direction .band-hd > :last-child,
#panel-map .band-hd > :last-child,
#panel-status .band-hd > :last-child,
#panel-features .band-hd > :last-child,
#panel-contribute .band-hd > :last-child{margin-bottom:0}
/* The collapse toggle, demoted (see the note above). */
#panel-research-direction .section-head,
#panel-map .section-head,
#panel-status .section-head,
#panel-features .section-head{margin-top:36px}
#panel-research-direction .section-head-title,
#panel-map .section-head-title,
#panel-status .section-head-title,
#panel-features .section-head-title{font-size:21px;max-width:none}
#panel-research-direction .section-head-sub,
#panel-map .section-head-sub,
#panel-status .section-head-sub,
#panel-features .section-head-sub{font-size:15.5px;color:rgba(69,58,76,.78)}

/* ── ONE LEFT EDGE ──────────────────────────────────────────────────────────
   Measured in chromium at 1600: the three Mapping EPFL tabs each had three. The
   page head started at 260, the bands at 180, the section heads and pull quotes
   at 80, and loose prose floated centred at 505, so the eye found a new margin
   four times on the way down a page. What is MDH? holds one edge at 180
   throughout, and that is most of what made these tabs read as a different site.
   Beyond EPFL, Takeaways and Funding were later found to have the same three
   edges from the same two causes, and are fixed by the same two remedies below.

   The header stray is FIXED IN THE MARKUP, not here, the way Research
   Directions fixed it. .page-head-main's padding-left is
   max(gutter, 50vw - measure/2), the formula that centres a full-bleed header
   at the measure, and it only resolves to a left edge if its parent spans the
   window. .page-head:has(.scope-note) is what makes it span. These tabs used to
   carry the one-line .scope-line variant, so that rule never matched: the
   header stayed inside the panel's gutter and the centring padding stacked on
   top of it, at 260. They now carry the full .scope-note panel, verbatim from
   mdh.html, so the rule matches and the header lands at 180 exactly as What is
   MDH? does, with no override at all. Do not add panel-scoped .page-head rules
   to "fix" it: with .scope-note present they fight the grid rule instead of
   helping it.

   Beyond, Funding and Takeaways were the last three still carrying .scope-line
   and the +80px offset it caused. They have since had the same markup swap, so
   every tab on the site now holds one edge and .scope-line has no users left.

   INSIDE a band there were two more edges, both from one cause. A band is
   full-bleed: it cancels the panel gutter to reach the window, then
   `.band > *` re-establishes the column with max-width:var(--measure) AND
   margin-inline:auto. Centring a child is only the same as aligning it when the
   child fills the measure, so:

     - .section-head sets margin:44px 0 16px. A shorthand writes margin-inline:0,
       it matches `.band > *` at equal specificity (0,1,0) and it is declared
       later in this file, so it cancels the auto and the head drops to the
       band's padding edge: 80. Its rule and its border-top were 100px left of
       the headline above it. Same fault as .pull, which Research Directions
       hit and patched one element at a time.
     - A bare <p> is clamped to 62ch by `.band p`, and 62ch centred in a 1240px
       measure starts at 505. Loose prose was floating in the middle of the page
       under a headline flush left.

   Patching each stray element one at a time is what produced four edges in the
   first place, so this fixes the container: pad the band to the centring
   formula, so
   its content box IS the measure column, then turn the centring off. Every
   direct child - wide or narrow, with a margin shorthand or without one - now
   starts at the same x as the page title. Below about 1400px the max() picks
   the gutter and the geometry is what it always was, so nothing moves at narrow
   widths. What is MDH? reaches the same place structurally, by keeping every
   paragraph inside a wrapper that fills the measure (.mdh-page-section-hd,
   .mdh-page-section-body); these tabs have loose children the collapse walk
   will not let us wrap, so they get it from the container.

   Beyond EPFL, Takeaways and Funding are on this list too, added after the
   mapping pass deferred them. They had the identical fault from the identical
   cause: eyebrows and section heads at the band's padding edge, loose prose
   floating centred a third of the way across, content blocks at the measure.
   The vertical padding comes along with the horizontal, so their bands go from
   64px to 56px top and bottom. That is deliberate rather than incidental: 56 is
   already what every band after the first one uses (.band + .band sets
   padding-top:56px), so this settles the three pages on one value instead of
   leaving the first band taller than the rest.

   Research Directions is NOT on this list and must not be added: it solves the
   same problem structurally, the What is MDH? way, by wrapping each band head in
   .band-hd. Adding it here would pad its bands twice. */
#panel-map .band,
#panel-status .band,
#panel-features .band,
#panel-beyond .band,
#panel-takeaways .band,
#panel-funding .band{padding:56px max(var(--gutter),calc(50vw - var(--measure) / 2));}
#panel-map .band > *,
#panel-status .band > *,
#panel-features .band > *,
#panel-beyond .band > *,
#panel-takeaways .band > *,
#panel-funding .band > *{margin-inline:0}
/* The step under the band head. 23px is what .display-l used to be on these
   pages, so a group head keeps the size the reader already knew and only the
   head above it grows. */
#panel-map .display-m,
#panel-status .display-m,
#panel-features .display-m{font-size:23px;line-height:1.15;letter-spacing:-.01em;
  margin:0 0 12px;max-width:none;}

/* Two-column band, the draft's pairing. Used only where a page genuinely has two
   related blocks; forcing it elsewhere would be decoration. */
.band-two{display:grid;grid-template-columns:1fr 1fr;gap:64px;align-items:start;}
@media(max-width:860px){.band-two{grid-template-columns:1fr;gap:44px;}}

/* Pull quote. NOT a boxed card with a coloured left bar: that combination of
   white panel, hairline border, rounded corners and accent stripe is the most
   over-produced component on the web and reads as generated rather than set.
   A pull quote in print is not a box at all. It is the same words, larger, given
   room, and separated by rules rather than enclosed by them. So: no background,
   no border box, no radius, no colour. Hairlines above and below, generous air,
   and the serif doing the work. */
.pull{background:none;border:none;border-radius:0;
  border-top:1px solid var(--border);border-bottom:1px solid var(--border);
  padding:26px 0;margin:34px 0;}
.pull p{font-family:var(--display);font-weight:400;font-style:italic;font-size:25px;
  line-height:1.38;color:var(--ardoise);max-width:34ch;margin:0;}

/* Figure row: the page's live numbers, set as data rather than as a sentence. */
.figs{display:flex;gap:44px;flex-wrap:wrap;margin:26px 0 0;}
.fig b{display:block;font-family:var(--display);font-weight:600;font-size:27px;line-height:1;color:var(--ardoise);}
.fig span{display:block;font-family:var(--sans);font-size:12.5px;font-weight:700;letter-spacing:.15em;
  text-transform:uppercase;color:var(--ink-muted);margin-top:6px;}

/* Research Directions only: the count sits on one line with its label, both at
   the eyebrow step, rather than a 27px numeral above a caption. One number on a
   page that is otherwise a list of questions did not need the display size, and
   stacked it read as a statistic rather than as a count of what follows. The
   other seven tabs that use .figs carry three or more numbers and keep the
   display treatment, where the size is doing real work. */
#panel-research-direction .fig{display:flex;align-items:baseline;gap:8px;}
#panel-research-direction .fig b{font-family:var(--sans);font-size:12.5px;font-weight:700;
  letter-spacing:.15em;color:var(--ink-muted);}
#panel-research-direction .fig span{margin-top:0;}

/* A media query does not change specificity, so a narrow-width rule still has to
   out-specify the base rule it means to override. The three below were written
   one class short and never applied on the six banded tabs: the id-scoped
   `#panel-X .band` padding (1,1,0) and `#panel-X .band-hd .display-l` size
   (1,2,0) both beat a bare `.band` / `.display-l` (0,1,0) at every width, so on
   a phone the section heads stayed at 30px and every band kept its 56px of
   vertical padding. The id-scoped repeats below restore the intent at matching
   specificity. See tools/dev/find-dead-media-rules.mjs, which fails on any new
   instance of this. */
@media (max-width:860px){
  .display-xl{font-size:34px;}
  .display-l{font-size:26px;}
  #panel-research-direction .band-hd .display-l,
  #panel-map .band-hd .display-l,
  #panel-status .band-hd .display-l,
  #panel-features .band-hd .display-l,
  #panel-contribute .band-hd .display-l{font-size:26px;}
  .band{padding:40px var(--gutter);}
  /* The horizontal half is unchanged: below 860px the max() already resolves to
     var(--gutter). Keeping it means this rule still centres correctly if
     --measure moves. Only the 56px vertical becomes 40px. */
  #panel-map .band,
  #panel-status .band,
  #panel-features .band,
  #panel-beyond .band,
  #panel-takeaways .band,
  #panel-funding .band{padding:40px max(var(--gutter),calc(50vw - var(--measure) / 2));}
}

/* ── 3. PANELS + SHARED LAYOUT PRIMITIVES ────────────────────────────────── */
   /* The frame each page sits in: the white panel box, page titles, and section header rows. */
.panel{display:none;padding:var(--panel-pad-top) var(--gutter) 48px;font-family:var(--sans);font-size:16.5px;line-height:1.6;color:var(--ardoise);}
/* The measure is applied to the panel's CHILDREN, not to the panel itself, so the
   banner can stay full width without needing to break out of anything.
   Deliberately NOT done with 50vw: a vw-based breakout is wider than the content
   box by the scrollbar's width, which can add a scrollbar, which narrows the
   viewport, which removes the scrollbar - an oscillation that reads as the page
   flickering or reloading on its own. This approach cannot do that: nothing here
   is measured against the viewport. */
/* The measure applies to a panel's ordinary children, but NOT to bands, and not
   to any wrapper that CONTAINS a band. A band is a full-bleed strip of colour: it
   escapes the panel's gutter with a negative margin and re-establishes the
   measure on its own children (.band > *). Capping the band itself would fight
   that, stop the colour reaching the window edge, and leave a visible seam down
   both sides of every section - which is exactly what it did.
   The :has() clause matters because several pages keep the band inside an
   existing wrapper (.spider-section on Visual Mapping, a bare div on Actors); a
   capped wrapper would trap its bands just as effectively. */
.panel:not(.panel-landing) > *:not(.map-hero):not(.band):not(:has(.band)):not(.page-head){max-width:var(--measure);margin-inline:auto;}
.panel:not(.panel-landing) > p{max-width:min(80ch,var(--measure));}
.panel.active{display:block;}
.panel-inner{background:#ffffff;border-radius:12px;border:1px solid var(--border);padding:26px 28px;margin-bottom:16px}
/* Bare variant: cards sit directly on the dark page background, no white panel.
   Section descriptions inside it switch to light text so they stay legible. */
.panel-inner-bare{background:transparent;border:none;padding:0}
.panel-inner-bare .section-desc{color:rgba(69,58,76,0.82)}
/* EPFL Actors & Projects: inner subtab nav */
/* Legacy underline subnav (kept for backwards compatibility). */
.epfl-subnav{display:flex;gap:2px;margin:-36px -52px 22px -52px;padding:0 52px;border-bottom:1px solid var(--perle);background:#fff;position:sticky;top:var(--main-top);z-index:90;}
.epfl-subnav-btn{flex:none;padding:12px 18px;font-family:var(--sans);font-size:15px;font-weight:600;color:var(--ardoise);background:none;border:none;border-bottom:3px solid transparent;cursor:pointer;transition:all .15s;}
.epfl-subnav-btn:hover{opacity:.8;}
.epfl-subnav-btn.active{color:var(--ardoise);border-bottom-color:var(--ardoise);font-weight:700;}

/* ── 4. BEYOND EPFL PANEL ────────────────────────────────────────────────── */
   /* Styles used ONLY by the "Beyond EPFL" tab (the question cards). */

/* ── Beyond EPFL panel: skeleton + question cards ── */
.beyond-status{margin:18px 0 28px;padding:14px 18px;background:var(--perle);border-radius:12px}
.beyond-status-tag{display:inline-block;font-size:13px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:var(--groseille);margin-bottom:6px}
.beyond-status p{font-size:16px;line-height:1.6;color:var(--ardoise);margin:0}
.beyond-grid{display:grid;grid-template-columns:repeat(2, 1fr);gap:14px;margin-bottom:32px}
@media (max-width: 900px){.beyond-grid{grid-template-columns:1fr}}
.beyond-card{background:#fff;border:1px solid var(--border);border-radius:12px;padding:26px 28px;transition:border-color 0.15s, transform 0.15s}
.beyond-card:hover{border-color:var(--canard);transform:translateY(-1px)}
/* Display face, like every other card title on the site. This was the one the
   harmonisation pass missed: sans 17.5px/700, which made Beyond's cards the only
   ones set in a different typeface. 16px is the dense-card step. */
.beyond-card-q{font-family:var(--display);font-size:16px;font-weight:600;color:var(--canard);margin-bottom:10px;line-height:1.25;letter-spacing:-.01em}
/* A title that links keeps the title's colour with a soft underline, like the site's other text
   links. Unstyled, the Reference models titles came out in the browser's default blue (2026-09-14). */
.beyond-card-q a{color:inherit;text-decoration:underline;text-decoration-color:rgba(0,116,128,.4);text-underline-offset:3px}
.beyond-card-q a:hover{text-decoration-color:currentColor}
.beyond-card p{font-size:16px;line-height:1.6;color:var(--ardoise);margin:0 0 10px}
.beyond-card p:last-child{margin-bottom:0}
.beyond-card-aud{font-size:13.5px;color:var(--ink-muted);font-style:normal;padding-top:8px;border-top:1px solid var(--border);margin-top:10px}
/* Banding this tab put the question cards inside a .band, and .band p (0,1,1)
   carries max-width:62ch. .beyond-card p never declares a max-width of its own,
   so it inherited the cap: in the three single-card bands the card is the full
   1240px measure while its text stopped at roughly 545px, leaving the right
   half of every card empty. .band p is for UNCLASSED band prose; a card that
   sets its own type and sizes its own box keeps its own width. Same failure the
   #panel-status hint and credit lines hit when that tab was banded. */
#panel-beyond .band .beyond-card p{max-width:none;}

/* New tile-style subnav (matches the MDH Context tile look). */
.epfl-subtab-tiles{
  display:grid;
  grid-template-columns:repeat(2, 1fr);
  gap:12px;
  background:transparent;
  margin:0 0 24px;
  padding:0 0 24px;
  width:100%;
  border-bottom:4px solid var(--ardoise);
}
@media (max-width: 700px){.epfl-subtab-tiles{grid-template-columns:1fr}}
.panel-title{font-family:var(--sans);font-size:20px;font-weight:700;margin-bottom:4px;color:var(--ardoise);}
.panel-title-light{color:var(--ardoise);}
.panel-desc{font-size:15px;color:var(--ardoise);margin-bottom:28px;line-height:1.6;}
.panel-desc-light{color:var(--ardoise);}
.takeaways-section{margin-bottom:40px;scroll-margin-top:168px;}
.takeaways-h{font-family:var(--sans);font-size:19.5px;font-weight:600;color:var(--ardoise);margin:0 0 8px;padding-bottom:6px;border-bottom:1px solid var(--perle);}
.takeaways-sub{font-family:var(--sans);font-size:16px;font-weight:700;color:var(--ardoise);margin:18px 0 6px;text-transform:uppercase;letter-spacing:.04em;}
/* Peer-institution colour cues. Subtle, readable, distinct. */
.peer-eth{color:var(--acier);border-bottom:1.5px dotted var(--acier);padding-bottom:1px;}
.peer-uzh{color:var(--zinzolin);border-bottom:1.5px dotted var(--zinzolin);padding-bottom:1px;}
.peer-idiap{color:var(--canard);border-bottom:1.5px dotted var(--canard);padding-bottom:1px;}
.peer-other{color:var(--ardoise);border-bottom:1.5px dotted var(--ardoise);padding-bottom:1px;}

/* ── 5. INTRO BANNERS + TAKEAWAYS ────────────────────────────────────────── */
   /* The "What you will find" intro box (shared by every tab) + the Takeaways tab layout. */

/* ── Shared "What you will find on this page" intro banner (every tab) ──
   Mirrors the MDH Context .mdh-tab-intro: Perle grey box, small groseille
   caps title, short body. Grey-on-dark-text stays legible on both the white
   .panel-inner and the dark page background. */
.tab-intro{background:#ffffff;padding:16px 20px;margin:0 0 24px;border-radius:12px;}
.tab-intro-title{font-family:var(--sans);font-size:15px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:var(--groseille);margin:0 0 8px;}
.tab-intro-body{font-family:var(--sans);font-size:16px;line-height:1.55;color:var(--ardoise);margin:0;}
.tab-intro-body strong{color:var(--ardoise);font-weight:700;}
.tab-intro-body em{font-style:normal;font-weight:700;color:var(--ardoise);}

/* ── Takeaways: jump-nav, source-tagged cards, provenance chips ── */
/* Not sticky since 2026-09-14: pinned under the header it covered the figures and card text
   scrolling beneath it. It stays at the head of the page as a plain jump list. */
.tk-nav{display:flex;flex-wrap:wrap;align-items:center;gap:8px;background:#fff;margin:0 0 24px;padding:11px 0;border-bottom:1px solid var(--perle);}
.tk-nav-lbl{font-size:13px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--ardoise);opacity:.5;margin-right:2px;}
.tk-nav a{font-family:var(--sans);font-size:13.5px;font-weight:700;color:var(--ardoise);text-decoration:none;padding:6px 13px;border-radius:999px;border:1.5px solid var(--perle);transition:background .15s,border-color .15s,color .15s;}
.tk-nav a:hover,.tk-nav a:focus-visible{border-color:var(--canard);color:var(--canard);outline:none;}

/* Numbered list items in Trends / Gaps / Opportunities become cards. */
.tk-cards{list-style:none;counter-reset:tkcard;margin:14px 0 0;padding:0;display:flex;flex-direction:column;gap:12px;}
.tk-cards > li{counter-increment:tkcard;position:relative;background:#fff;border:1.5px solid var(--perle);border-radius:12px;padding:16px 18px 14px 48px;transition:border-color .15s;}
.tk-cards > li:hover{border-color:var(--canard);}
.tk-cards > li::before{content:counter(tkcard);position:absolute;left:14px;top:15px;display:flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;background:var(--ardoise);color:#fff;font-size:13px;font-weight:700;}
.tk-cards > li > strong:first-child{display:block;margin-bottom:7px;font-size:16px;font-weight:700;color:var(--ardoise);line-height:1.35;}
.tk-cards > li > ul{margin:8px 0 0;padding-left:18px;}
.tk-cards > li > ul > li{margin-bottom:6px;}

/* Provenance footer + chips (chip tints are alpha variants of EPFL palette). */
.tk-src{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:12px;padding-top:10px;border-top:1px dashed var(--border);}
.tk-src-lbl{font-size:12px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--ardoise);opacity:.45;margin-right:2px;}
/* The chip has to be able to give. .tk-src already wraps its chips onto new
   lines, but that cannot help a chip that is on its own wider than the row:
   white-space:nowrap plus an unbounded width made "Insights Trust Indicators >
   Single Detectors" 402px, and at a 390px viewport it pushed the whole page
   sideways by 81px. Three parts, all needed:
     max-width  - the chip can never be wider than the row it sits in.
     white-space:normal - its text is allowed to break at all.
     flex-wrap  - the label and the text are separate flex items, and items do
                  not move to a second line without it, so the chip would still
                  overflow even with the text breakable.
   The label keeps nowrap of its own, so "INTERVIEWS" never breaks mid-word.
   Nothing moves on a wide screen: text only wraps when it does not fit, and at
   the measure these all fit on one line as before. */
.tk-chip{display:inline-flex;flex-wrap:wrap;align-items:center;gap:5px;font-family:var(--sans);font-size:13px;font-weight:600;line-height:1.3;padding:3px 9px;border-radius:999px;border:1px solid;white-space:normal;max-width:100%;}
.tk-chip .k{font-size:12px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;opacity:.7;white-space:nowrap;}
.tk-chip.src-interview{color:var(--canard);border-color:rgba(0,116,128,.40);background:rgba(0,116,128,.07);}
.tk-chip.src-insight{color:var(--zinzolin);border-color:rgba(92,36,131,.40);background:rgba(92,36,131,.07);}
.tk-chip.src-ref{color:var(--groseille);border-color:rgba(181,31,31,.38);background:rgba(181,31,31,.06);}
.tk-chip.src-tab{color:var(--leman);border-color:rgba(0,167,159,.45);background:rgba(0,167,159,.09);}
.tk-chip.src-site{color:var(--ardoise);border-color:var(--perle);background:#fff;}

/* Headline recommendation: featured card at the top of the guideline section. */
.tk-headline{border:2px solid var(--zinzolin);border-radius:12px;background:#fff;padding:18px 20px;margin:14px 0 20px;}
/* Banding Takeaways put this card inside a .band, where .band p (0,1,1) is the
   only rule setting size, colour and measure on its paragraphs, so it won: the
   page's single most emphasised text went to 78%-opacity ardoise and stopped at
   62ch inside a 1240px bordered card, leaving ~575px of empty white in the box
   (measured in chromium at 1600px). .band p is for UNCLASSED band prose; a card
   that draws its own border sets its own text. Same fix and same specificity as
   .band p.eyebrow above - this rule already matches .band p at (0,1,1) and comes
   later, so it only needs the missing declarations. */
.tk-headline > p{margin:0 0 10px;font-size:16.5px;line-height:1.6;color:var(--ardoise);max-width:none;}
.tk-headline > p:last-child{margin-bottom:0;}

@media (max-width:700px){
  .takeaways-section{scroll-margin-top:120px;}
}
/* ── 6. ACTORS STATUS PANEL ──────────────────────────────────────────────── */
   /* The "EPFL Actors" tab: the person cards, the count funnel, the status sections, the view toggle. */

/* Anchor the flock to the Actors panel (not the page): the panel needs a
   positioning context. Birds sit above the panel background but below the
   interactive cards, and never intercept clicks (pointer-events:none). */
/* Panels that host a bird flock need a positioning context for it. (The
   #bird-glyph <symbol> is defined once in status.html; <use> resolves it
   document-wide, so other panels can reference it even while status is hidden.) */
#panel-status,#panel-takeaways{position:relative;}
/* A strip of open sky for the flock to cross. Takeaways' content sits in an
   opaque white .panel-inner card, so without its own band the birds would fly
   behind it and be clipped. */
/* Dove-flock banner (Takeaways). Same shape as the other tab banners: dark
   ground, decorative field behind, title + description on a plate on top.
   The clip is birds-on-true-black (its green screen was keyed out in-repo),
   so mix-blend:screen drops the background cleanly. */
.flock-banner{position:relative;height:230px;margin:20px 0 18px;border-radius:12px;
  overflow:hidden;background:var(--banner-bg);
  display:flex;align-items:center;justify-content:center;}
/* Fill the banner: the clip is 16:9 and the banner a wide strip, so cover
   scales it up and crops top/bottom - the birds then span the full width and
   use the full height. object-position right keeps the right-hand side of the
   frame (where the flock enters) rather than centring it.
   mix-blend:screen still drops the clip's true-black background cleanly:
   screen(any colour, black) returns that colour unchanged, so this works on
   the Ardoise ground, not just on black. */
.flock-video{position:absolute;inset:0;z-index:0;width:100%;height:100%;
  object-fit:cover;object-position:right center;
  mix-blend-mode:screen;pointer-events:none;}
.flock-banner-plate{position:absolute;left:22px;top:22px;z-index:1;
  max-width:min(56%,540px);background:var(--banner-plate);
  border:1px solid rgba(255,255,255,.12);border-radius:12px;padding:16px 20px;
  box-shadow:0 6px 22px rgba(0,0,0,.4);}
.flock-banner-plate .flock-banner-title{color:#fff;}
.flock-banner-plate .flock-banner-lead{color:rgba(255,255,255,.9);font-size:16px;
  line-height:1.5;margin:6px 0 0;}
@media (max-width:700px){
  .flock-banner{height:auto;min-height:210px}
  .flock-banner-plate{position:static;max-width:none;margin:12px;left:auto;top:auto}
}
/* Lottie banner (Research Directions, Funding). Same shell as the other tab
   banners: Ardoise ground, animation behind, title + description on a plate.
   The stage holds the lottie-web SVG, sized to cover the banner. */
.lottie-banner{position:relative;height:230px;margin:20px 0 18px;border-radius:12px;
  overflow:hidden;background:var(--banner-bg);}
/* Lottie/animation stages sit LEFT and RIGHT; the plate is centred between
   them. Each side takes ~26% of the width, contained so the whole art shows. */
.lottie-stage{position:absolute;top:0;bottom:0;z-index:0;pointer-events:none;width:27%;}
.lottie-stage svg{width:100%!important;height:100%!important;object-fit:contain;display:block;}
.lottie-stage.lottie-left{left:1%;}
.lottie-stage.lottie-right{right:1%;}
.lottie-stage.lottie-flip svg{transform:scaleX(-1);}
/* Funding particle stream: spans the whole banner, sits above the side SVGs but
   below the centred plate (z-index 1) so particles flow behind the title. */
.fund-particles{position:absolute;inset:0;width:100%;height:100%;z-index:1;pointer-events:none;}
/* The data-center SVG art nearly fills its viewBox; scale it down a touch so it
   has breathing room in the banner instead of touching the edges. */
/* (the .rd-svg data-centre stage was removed with the circuit-network rework) */
@media (max-width:700px){.lottie-stage{opacity:.5;width:34%}}
/* Centred plate shared by the animated banners. */
.banner-plate-center{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);z-index:1;
  text-align:center;max-width:min(52%,540px);
  background:var(--banner-plate);border:1px solid rgba(255,255,255,.12);
  border-radius:12px;padding:16px 22px;box-shadow:0 6px 22px rgba(0,0,0,.4);}
.banner-plate-center .page-headline-title{color:#fff;}
.banner-plate-center p{color:rgba(255,255,255,.9);font-size:16px;line-height:1.5;margin:6px 0 0;}
@media (max-width:700px){.banner-plate-center{position:static;transform:none;max-width:none;margin:12px}}

/* ── Research Directions circuit network ─────────────────────────────────────
   Isometric PCB traces spanning the banner BETWEEN the two illustrations, with
   research vocabulary set along the diagonals and a signal pulse running each
   trace. Sits behind both stages and the centre plate (first child, z-index 0),
   so the traces read as wiring the left figure to the right data centre.
   Geometry is generated (exact 30deg isometric axes, and each word's glow delay
   is the moment its trace's pulse reaches it); see the comment in the partial.
   Decorative and aria-hidden. */
.rdnet{position:absolute;inset:0;width:100%;height:100%;z-index:0;
  pointer-events:none;display:block;overflow:visible;}
/* Two gradients (userSpaceOnUse, so they span the banner rather than each path's
   own box): the top band sweeps the EPFL chart palette left to right, the bottom
   band sweeps it right to left. Running them in opposite directions is what
   stops the two bands landing on the same colour at the same x. Palette: Rose,
   Mont-rose, Carotte, Soufre, Chartreuse, Vert d'eau, Leman, Acier. Canard is
   out (darkest of the set, its words read dim), as are Zinzolin, Groseille,
   Rouge suisse, Marron and Taupe. Each trace picks its gradient inline.
   Generated by tools/build/gen-rd-network.mjs - `npm run gen:rd-network`. */
.rdnet-trace{fill:none;stroke-width:2.2;opacity:.85;
  stroke-linecap:round;stroke-linejoin:round;}
/* Companion traces shadowing each routed trace as a bus. They are exact
   translations of it, so they stay parallel and never cross. Progressively
   fainter, which gives the field depth without adding clutter. */
.rdnet-bus{stroke-width:1.5;opacity:.45;}
.rdnet-bus + .rdnet-bus{stroke-width:1.2;opacity:.28;}
/* Dead-end branches. The reference artwork is full of these: short taps off a
   trace that simply stop at a pad rather than joining anything. */
.rdnet-stub{stroke-width:1.3;opacity:.45;}
/* Rungs and braces between the bands, crossing to form X junctions. Thinner and
   dimmer than the bands so they read as links rather than more wire of the same
   weight. The middle ones disappear behind the title plate by design - they come
   back out the other side, which reads as wiring routed under the panel. */
.rdnet-cross{stroke-width:1.4;opacity:.42;}
/* Donut nodes: the banner ground shows through, so they read as solder pads
   rather than dots painted on top of the trace. Stroke colour is set per node
   inline, from the same sweep, so a pad matches the wire it sits on. */
.rdnet-node{fill:var(--ardoise);stroke-width:2;opacity:.9;}
/* --rdw is the word's resting colour, set inline from the sweep. */
.rdnet-word{fill:var(--rdw,var(--rose));opacity:.8;font-family:var(--sans);
  font-size:12px;font-weight:700;letter-spacing:1.1px;text-anchor:middle;
  dominant-baseline:middle;
  /* Halo in the banner's own ground colour, painted UNDER the glyph (paint-order
     puts the stroke first). The field is dense enough that a trace will cross a
     word somewhere; without this the two merge and the word becomes unreadable. */
  paint-order:stroke fill;stroke:var(--ardoise);stroke-width:2.8px;
  stroke-linejoin:round;
  /* duration + delay are set per word inline: each word loops on ITS trace's
     pulse duration, so word and pulse stay locked together indefinitely. */
  animation-name:rdnet-word-glow;animation-timing-function:linear;
  animation-iteration-count:infinite;}
/* The flare sits at 0% so it lands exactly on the inline animation-delay, which
   is when the pulse arrives; the rest of the cycle is the resting state. */
@keyframes rdnet-word-glow{
  0%{fill:#fff;opacity:1}
  4%{fill:#fff;opacity:.92}
  12%{fill:var(--rdw,var(--rose));opacity:.8}
  100%{fill:var(--rdw,var(--rose));opacity:.8}
}
.rdnet-pulse{fill:#fff;filter:drop-shadow(0 0 5px #fff) drop-shadow(0 0 11px var(--soufre));}

/* The Research Directions question mark and its Lottie figure were retired when
   the network grew to fill the full banner. Both variants, and the CSS they
   need, are preserved in source/docs/banner-designs.md. */
@media (max-width:700px){
  /* Narrow: the plate goes static and full-width, so the network would only show
     as clipped stubs behind it. Hide it rather than ship a fragment. */
  .rdnet{display:none;}
}

/* Network-field banner (Research Directions). Sized and structured like the
   Mapping EPFL flashlight hero and the MDH Context symbol banner: dark ground,
   decorative field behind, title + description on a solid plate on top. */
.net-banner{position:relative;height:230px;margin:20px 0 18px;border-radius:12px;
  overflow:hidden;background:var(--ardoise);}
.net-canvas{position:absolute;inset:0;width:100%;height:100%;display:block;
  z-index:0;pointer-events:none;}
.net-banner-plate{position:absolute;left:22px;top:22px;z-index:1;
  max-width:min(56%,540px);background:var(--banner-plate);
  border:1px solid rgba(255,255,255,.12);border-radius:12px;padding:16px 20px;
  box-shadow:0 6px 22px rgba(0,0,0,.4);}
.net-banner-plate .net-banner-title{color:#fff;}
.net-banner-plate .net-banner-lead{color:rgba(255,255,255,.9);font-size:16px;
  line-height:1.5;margin:6px 0 0;}
@media (max-width:700px){
  .net-banner{height:auto;min-height:210px}
  .net-banner-plate{position:static;max-width:none;margin:12px;left:auto;top:auto}
}

/* STATUS */
.funnel-row{display:flex;gap:10px;margin-bottom:28px;flex-wrap:wrap;}
.funnel-item{background:#ffffff;border-radius:12px;padding:16px 22px;display:flex;align-items:center;gap:12px;flex:1;min-width:140px;}
.fi-num{font-size:34px;font-weight:700;line-height:1;}
.fi-label{font-size:13.5px;color:var(--ardoise);}
.leg-row{display:flex;gap:16px;flex-wrap:wrap;margin-bottom:22px;}
.leg{display:flex;align-items:center;gap:7px;font-size:13.5px;color:var(--ardoise);}
.leg-dot{width:11px;height:11px;border-radius:50%;flex-shrink:0;}
/* 280px/16px, up from 210px/8px. The actor grid was the smallest card on the site
   and the only one on an 8px gutter, so the three Mapping EPFL subtabs read as three
   different densities: .proj-grid next door is 360px/18px and .chart-grid 260px/18px.
   At 210px a two-line name plus a unit plus a tag row had no room and the cards
   fought their own padding. */
.actor-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(var(--grid-card),100%),1fr));gap:var(--grid-gap);}
/* The actor/person card. This is the single definition (it used to be split in
   two, ~600 lines apart — now merged). Expandable cards override cursor below. */
.rcard{background:#ffffff;border-radius:12px;padding:18px 20px;border:1px solid var(--border);transition:box-shadow 0.15s;cursor:pointer;overflow:hidden;min-height:132px;display:flex;flex-direction:column;}
.rcard:hover{box-shadow:0 4px 14px rgba(0,0,0,0.1);}
.rcard.no_response{opacity:0.75;}
/* ── ONE CARD TITLE FACE ───────────────────────────────────────────────────
   The site had three: 21px display on the homepage tiles and the research
   direction cards, 15px sans on actor and work cards, 16px sans on chart and
   spider cards. Two sizes is defensible, because a 290px card four across
   cannot carry a 21px serif; three FACES was not, because it made cards on
   different tabs look like they came from different sites.
   One face now, the display serif the homepage uses, at two sizes: 21px for the
   large editorial tiles, 16px for the dense data cards. Same weight, same
   tracking, same ink. */
.rcard-name{font-family:var(--display);font-weight:600;font-size:16px;
  line-height:1.25;letter-spacing:-.01em;color:var(--ardoise);margin-bottom:3px;}
.rcard-unit{font-size:13px;color:var(--ardoise);margin-bottom:6px;}
/* Actor cards: lab and school as the eyebrow, grey because the card is white (decision 7). */
.rcard-eyebrow{font-family:var(--sans);font-size:12.5px;font-weight:700;letter-spacing:.15em;
  text-transform:uppercase;line-height:1.4;color:var(--ink-muted);margin:0 0 6px;}
/* The whole eyebrow, however many lines, with the names still in line (B26, 2026-09-14). The
   two-line clamp cut 11 of 36 at 1280 and left the rest to a tooltip. Each card is a subgrid of
   four rows (eyebrow, name, tags, note), so the tallest eyebrow in a grid row sets that row for
   every card in it. The grid's row gap moves to a card margin, or it would also open between a
   card's own rows. */
.actor-grid{row-gap:0;padding-bottom:calc(22px - var(--grid-gap))}
.actor-grid > .rcard{display:grid;grid-row:span 4;grid-template-rows:subgrid;row-gap:0;margin-bottom:var(--grid-gap)}
.actor-grid > .rcard > .rcard-header{grid-row:1 / span 3;display:grid;grid-template-rows:subgrid;
  grid-template-columns:minmax(0,1fr) auto;row-gap:0}
.actor-grid > .rcard > .rcard-header > div:first-child{display:contents}
.actor-grid > .rcard > .rcard-header > div > *{grid-column:1}
.actor-grid .rcard-toggle{grid-column:2;grid-row:1}
.actor-grid .rcard-note-body{grid-row:4}
.rcard-eyebrow + .rcard-name{margin-bottom:10px;}
.rcard-status{font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:0.7px;}
/* NOTE: the .rcard.done/.agreed/... border-left-color rules were removed — they
   referenced undefined vars (--done etc.) and rendered nothing (dead code). */
.tag-row{display:flex;flex-wrap:wrap;gap:3px;margin-top:7px;}
/* An actor card stacks its label types, one .tag-line each, and each line wraps
   within itself. A .tag-row that holds .tag-line children therefore becomes a
   column; a .tag-row of bare pills (the modal) is untouched. */
.tag-row:has(.tag-line){flex-direction:column;align-items:flex-start;gap:5px;}
.tag-line{display:flex;flex-wrap:wrap;gap:4px;}
.tag{font-size:12px;padding:2px 6px;border-radius:4px;background:var(--perle);color:var(--ardoise);}
.tag-flag{background:#fff;border:1.5px dashed var(--carotte);color:var(--ardoise);font-weight:700;}
#panel-status.view-internal .tag-flag{background:var(--carotte);color:#fff;border-style:solid;}
/* Vert d'eau, the Data facet's colour in the filter. This was Mont Rose at 18%,
   which is the HATE SPEECH pillar's hue - so a data-type pill on an actor card
   read as a pillar badge, and disagreed with the green Data chip that selects it. */
/* ── The same pill, in the actor modal ─────────────────────────────────────
   The modal drew facet values as bare .tag: 4px corners, normal weight, and in
   two cases a hue that disagreed with the card two clicks away (.tag-data was
   Mont Rose, the HATE SPEECH hue, against a Vert d'eau Data chip). These now
   take the .ax-pill form and read --px from the same key in :root, so a value
   looks identical in the filter, on a card and in the modal.
   .tag-flag and plain .tag keep the square form on purpose: those are
   annotations about an entry, not values of a facet. */
/* Same metrics as .ax-pill and .bib-topic: 12px/600, 3px 8px, 1px, fully round.
   These had kept 700 and 2.5px 7px from before the three were unified, so a value
   in the actor modal was a slightly different pill from the same value on the
   card behind it. */
.tag-data,.tag-tech,
.tag.mdh-e,.tag.mdh-r,.tag.mdh-a{
  border-radius:999px;font-size:12px;font-weight:600;padding:3px 8px;line-height:1.3;
  background:#fff;background:color-mix(in srgb, var(--px, var(--perle)) 20%, #fff);
  border:1px solid var(--border);
  border-color:color-mix(in srgb, var(--px, var(--perle)) 55%, #fff);
  color:var(--ardoise);
}
.tag-data{--px:var(--f-data);}
.tag-tech{--px:var(--f-method);}   /* technology IS the method axis */
/* Closeness is ONE facet, so one hue; the word carries which of the three. */
.tag.mdh-e,.tag.mdh-r,.tag.mdh-a{--px:var(--f-close);}
/* .tag-pillar is gone with the word-pills it styled: the actor modal draws the
   MDH label as the .bt-pil triple now, like the cards and the Works table. */
/* Unified card axis pills (actor + project tiles), four colour-coded axes:
   focus=Zinzolin purple, data=Vert d'eau green, method=Acier blue, theme=Leman teal. */
/* ── The facet pill, one form for every facet on every subtab ───────────────
   Hue comes in through --px from the key in :root, so a pill is never given a
   literal colour and can never drift from the filter chip that selects it.
   A 20% tint plus a 55% ring: the ring is what lets the four pale hues in the
   key (Vert d'eau, Soufre, Chartreuse, Perle) hold their own next to Zinzolin
   and Marron, which a flat tint alone could not do. Ardoise text throughout,
   because it clears 4.5:1 on all fifteen tints where the hues themselves,
   several of them pale, would not. The plain background/border lines are the
   fallback for engines without color-mix. */
/* ONE value pill, three places. .bib-topic in the works table, .ax-pill on the
   actor and work cards and .sf-chip in the filter are the same object: a facet
   value. They were three sizes - 13px/600/5px 11px/1.5px in the table,
   12px/700/2.5px 7px/1px on the cards, and the filter matched whichever it was
   last told to. A comment above .bib-topic claimed the first two were "the SAME
   component" while the rules said otherwise. The table's metrics win: it is the
   works table's RENDERED metrics win, measured in the browser rather than read
   off the base rule: 12px, 600, 3px 8px, a 1px border, fully round. The
   .bib-topic declaration says 13px/5px 11px/1.5px, but the table overrides it,
   so matching the declaration would have matched nothing on screen. */
/* The facet colour is now a 3px EDGE rather than a wash across the whole pill.
   Fifteen tinted pills in one table read as a paint chart and pushed the label
   text down to whatever contrast the tint left it; on white, every label sits at
   full Ardoise and the hue still says which dimension the value belongs to. The
   left corners square off to 3px so the edge reads as a key, not as a clipped
   border. */
.ax-pill{display:inline-block;font-size:12px;font-weight:600;padding:3px 8px;
  border-radius:3px 999px 999px 3px;line-height:1.3;
  background:#fff;
  border:1px solid var(--border);
  border-color:color-mix(in srgb, var(--px, var(--perle)) 42%, #fff);
  border-left:3px solid var(--px, var(--perle));
  color:var(--ardoise);}
/* No .ax-focus rule any more: the MDH label is the .bt-pil triple everywhere,
   so no card draws focus as a word-pill. */
.ax-data{--px:var(--f-data);}
.ax-method{--px:var(--f-method);}
.ax-subject{--px:var(--f-subject);}
.ax-theme{--px:var(--f-theme);}
/* The overflow marker. Neutral on purpose: it stands for several facets at once,
   so giving it any facet's hue would claim it belongs to that one. */
.ax-more{--px:var(--perle);color:var(--ink-muted);cursor:help;}
/* ── 7. VISUAL MAPPING (map + spider) ────────────────────────────────────── */
   /* The "Visual Mapping" tab: the radar/spider charts and their legend. */

/* ── Bookshelf-reveal hero ──────────────────────────────────────────────────
   A flashlight sweeps a dark two-shelf bookcase and lights up the spines beneath
   it (metaphor: illuminating the body of MDH research). Every tab carries this
   banner; the .mh-* modifier further down picks that tab's palette, shelf
   layout, sweep and motif.
   The spines are drawn from linear-gradients (EPFL tokens only, no image). The
   title sits in a solid plate on top so it always meets contrast. The sweep runs
   for everyone: the site does not honour reduce-motion for this scene (see the
   note in section 2). Adapted from a Uiverse "flashlight" pattern, recoloured to
   the EPFL palette. */
/* min-height, NOT height: the plate sits in normal flow and the banner grows to
   whatever the title and lead need. With a fixed height plus an absolutely
   positioned plate, a long lead simply got cut off by overflow:hidden - which is
   what happened to Publications & Projects when the type scale went up. The art
   is inset:0 so it fills whatever height this ends up being. */
/* Full-bleed band, like the landing hero. Negative horizontal margins cancel the
   panel's gutter so it runs edge to edge; a first-child banner also cancels the
   panel's top padding so it sits flush under the nav. No radius and no shadow: a
   band that touches both edges has no corners to round and nothing to float
   above - the rounded, inset card version read as a widget on the page rather
   than as the page's header. */
.map-hero{position:relative;min-height:240px;overflow:hidden;
  margin:0 calc(var(--gutter) * -1) 26px;border-radius:0;}
.panel > .map-hero:first-child{margin-top:calc(var(--panel-pad-top) * -1);}

/* ── The title plate is a book standing on the shelf ────────────────────────
   Everything that says "book" here is geometry, not ornament: nothing is drawn
   ON the cover at all. Three cues do the whole job.

     1. Proportion. Narrow enough (360px) to stand upright rather than lie flat.
     2. The cut. Square corners on the binding edge, rounded on the fore-edge,
        which is how a book is actually trimmed. This asymmetry is the strongest
        single cue and costs one border-radius value.
     3. The two edges. One hairline where the cover folds (::before) and a 3px
        sliver of paper block on the opening side (::after).

   Ardoise is the cover colour, the same ink as the header, nav and every heading,
   so the book belongs to the site's own family rather than introducing a colour
   of its own. It is lighter than the near-black shelf behind it (#2b2530), which
   is what separates the object from its background without a border.

   The plate sits in normal flow (position:relative + margin, not absolute +
   inset) so its height pushes the banner taller instead of being clipped by it,
   and its left margin lines the text up with the centred content column below,
   computed from the banner's own width so no viewport unit is involved. */
.map-hero-plate{position:relative;z-index:2;
  margin:30px;
  margin-left:max(12px,calc((100% - var(--measure)) / 2 - 20px));
  max-width:min(34%,360px);
  background:var(--ardoise);border:none;
  border-radius:2px 9px 9px 2px;
  padding:30px 30px 36px 34px;
  box-shadow:0 16px 40px rgba(0,0,0,.5);}
/* The binding edge. */
.map-hero-plate::before{content:"";position:absolute;left:9px;top:0;bottom:0;width:1px;
  background:rgba(255,255,255,.16);}
/* The fore-edge: the paper block you see when a book faces you. */
.map-hero-plate::after{content:"";position:absolute;right:0;top:7px;bottom:7px;width:3px;
  border-radius:0 7px 7px 0;
  background:linear-gradient(90deg,rgba(255,255,255,.04),rgba(255,255,255,.14));}
.map-hero-title.page-headline-title{color:#fff;font-size:26px;font-weight:700;letter-spacing:-.2px;line-height:1.14;}
.map-hero-plate .map-hero-lead{color:rgba(255,255,255,.64);margin:12px 0 0;font-size:14.5px;}
/* Meta line: a figure where the count is the point, a keyword where it is not,
   and omitted entirely where neither adds anything. See the partials. */
.map-hero-meta{margin-top:22px;padding-top:14px;border-top:1px solid rgba(255,255,255,.17);
  font-size:12px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;
  color:rgba(255,255,255,.56);}
@media (max-width:700px){
  .map-hero-plate{max-width:none;margin:12px;}
}

/* ── Flashlight artwork (EPFL-recoloured) ─────────────────────────────────────
   Adapted from the Uiverse.io "Flashlight" pattern.

   Copyright (c) 2026 Cobp (Fabio Cobb)

   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to deal
   in the Software without restriction, including without limitation the rights
   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   copies of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in all
   copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.

   The notice is reproduced IN FULL here, not merely referenced, because this
   stylesheet is what gets distributed: CREDITS.md is not part of the deploy
   (see tools/build/assemble-site.js), so a pointer to it would dangle for
   anyone receiving the shipped copy. This is also why the site needs no
   on-page credit: MIT asks the notice to travel with the code, not to be
   rendered to the reader.
   Every gradient layer is kept exactly as the original; the colours are remapped
   to the EPFL palette, and the original's blinking eyes are replaced by a
   per-tab motif. React/Vue/Svelte/Lit exports of this component are just
   wrappers around this same CSS, so plain CSS is the correct vanilla form.
   Colour map (original -> EPFL): near-blacks -> Ardoise/Taupe/Marron; tan ->
   Perle; reds -> Groseille/Rouge suisse; orange-reds -> Carotte/Mont Rose;
   olives/yellows -> Chartreuse/Soufre; teals -> Canard/Léman; browns -> Marron;
   slate/mauve -> Acier/Zinzolin/Rose.
   Structure (markup lives in each tab's partial):
     .map-hero-art          the frame; holds every shared variable
       .mh-shelves.mh-dark  the bookcase with the light off
       .mh-shelves.mh-glow  the lit bookcase at --glow opacity, everywhere
       .mh-shelves.mh-lit   the lit bookcase at full strength, clipped to the beam
         .mhs-up / .mhs-lo  the upper and lower shelf, offset independently
     .map-hero-art::after   the tab's motif, lit as the beam passes
   The animations run for everyone; there is no reduce-motion guard here. */
.map-hero-art{
  /* dark scene / shadow tones -> EPFL darks (Ardoise, Taupe, Marron) */
  --color-0:#fff;--color-1:#2b2530;--color-2:#453A4C;--color-3:#5B3428;--color-4:#413D3A;

  /* ── Lit spine palette (::before) ──────────────────────────────────────────
     Every lit colour is a token so a per-tab modifier can restyle the shelf by
     overriding variables alone, instead of duplicating the 24-gradient stack.
     --s1..--s17 are the spines left-to-right-ish in paint order; each --sNd is
     that spine's shaded side. --rail is the Perle backboard along the top.
     Defaults below are the original Mapping EPFL shelf, so that tab is
     unchanged. All values are EPFL chart colours or a darkened twin of one. */
  --rail:#CAC7C7;                                     /* Perle backboard */
  --s1:#F39869;  --s1d:#a86848;                       /* Mont Rose */
  --s2:#4F8FCC;  --s2d:#37648e;                       /* Acier */
  --s3:#FBEE66;  --s3d:#afa63f;                       /* Soufre */
  --s4:#CAC7C7;  --s4d:#8c8a8a;                       /* Perle */
  --s5:#ED6E9C;  --s5d:#a44c6c;                       /* Rose */
  --s6:#C2DDB0;  --s6d:#87977a;                       /* Vert d'eau */
  --s7:#F39869;  --s7d:#a86848;                       /* Mont Rose */
  --s8:#5C2483;  --s8d:#40195c;                       /* Zinzolin */
  --s9:#C2DDB0;  --s9d:#87977a;                       /* Vert d'eau */
  --s10:#B51F1F; --s10d:#7d1515;                      /* Groseille */
  --s11:#4F8FCC; --s11d:#37648e;                      /* Acier */
  --s12:#007480; --s12d:#005158;                      /* Canard */
  --s13:#ED6E9C; --s13d:#a44c6c;                      /* Rose */
  --s14:#FBEE66; --s14d:#afa63f;                      /* Soufre */
  --s15:#CAC7C7; --s15d:#8c8a8a;                      /* Perle */
  --s16:#EC6608;                                      /* Carotte */
  --s17:#4F8FCC; --s17d:#37648e;                      /* Acier */

  /* Shelf tiling and sweep timing, overridable per tab. The banner holds two
     shelf rows. They are drawn as two elements rather than as one vertically
     repeating background, because a repeating background gets ONE horizontal
     offset for every row: the upper shelf came out as an exact copy of the
     lower one, just cropped. Separate rows let each carry its own x, so the two
     shelves are different stretches of the same bookcase.
       --shelf-w / --shelf-h  the tile, i.e. one shelf
       --shelf-x              lower row's horizontal offset
       --shelf-x-top          upper row's, deliberately about half a tile away
     The tile size is deliberately NOT per-tab: it sets how big the books are,
     and therefore how much shelf the fixed-radius beam covers. Every tab uses
     What is MDH?'s scale so the flashlight reads identically on all six; only
     the two offsets vary, which is what still makes each shelf its own stretch
     of bookcase.
     The tiling MUST stay identical on the dark rows and the lit ones, or the
     flashlight reveals a scene that does not line up with the shelf underneath
     it - hence shared variables rather than hand-kept copies. */
  /* Smaller books read softer, and two shelves at this height (2 x 112 = 224)
     fit inside the banner's 240px minimum with room to spare, so neither row is
     ever sliced. Raising --shelf-h above half the banner height crops the upper
     shelf; keep the pair under it. */
  --shelf-w:250px; --shelf-h:112px;
  --shelf-x:40px;
  --shelf-x-top:-118px;
  /* Where the beam comes to rest. --rest-x is set per tab (see the shelves
     block); the height is shared, sitting on the lower shelf. --arrive is how
     long the single sweep takes to get there. */
  --rest-y:76%;
  --arrive:2.2s;

  /* One constant value for all six shelves: the light keeps its size for the
     whole sweep rather than breathing in and out. Change it here to resize every
     tab's flashlight at once.
     Width of the lit stretch, not a radius: the light spans the full height of
     the bookcase and is soft-edged, so there is no circle outline to notice. */
  --beam-w:430px;

  /* How much of the spine colour survives outside the beam (see .mh-glow).
     Low enough that the unlit shelf still reads as dark and the flashlight is
     still the event; high enough that the colours are there to be found. */
  --glow:.15;

  position:absolute;inset:0;z-index:1;
  background-color:var(--color-1);
}

/* ── The two shelf rows ─────────────────────────────────────────────────────
   Each group covers the whole banner: .mh-dark is the shelf with the light off,
   .mh-lit the same shelf in full colour, clipped to the moving flashlight. The
   clip lives on the GROUP, not on the individual rows, so both rows are cut by
   one circle in one coordinate space - clipping them separately would resolve
   the circle against each row's own box and split the beam in two.
   Each row is bottom-anchored, so its books stand on the shelf below them: the
   lower row on the banner's floor, the upper row on the lower row's top edge. */
.mh-shelves{position:absolute;inset:0;background-color:var(--color-1);}
/* Outside the beam the shelf is not pitch dark: the coloured bookcase also sits
   over the dark one at low opacity, so a spine's colour stays just readable
   everywhere and the flashlight reveals what is there rather than appearing to
   invent it. Raise --glow to show more colour in the unlit shelf, drop it to 0
   for the original hard-cut look. */
.mh-glow{opacity:var(--glow);}
/* The beam runs ONCE and holds where it lands (see @keyframes map-arrive). It is
   paused until its panel is active, so the arrival plays the first time a reader
   opens each tab rather than having quietly finished on a hidden panel. */
.mh-lit{
  --beam-mask:radial-gradient(ellipse 52% 118% at 50% 52%,#000 26%,rgba(0,0,0,.55) 60%,transparent 100%);
  -webkit-mask-image:var(--beam-mask);mask-image:var(--beam-mask);
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-size:var(--beam-w) 100%;mask-size:var(--beam-w) 100%;
  -webkit-mask-position:var(--rest-x) 0;mask-position:var(--rest-x) 0;
  animation:map-arrive var(--arrive) cubic-bezier(.25,.46,.45,.94) both;
  animation-play-state:paused;
}
.panel.active .mh-lit,
.panel.active .map-hero-art::after{animation-play-state:running;}
.mhs-up,.mhs-lo{
  position:absolute;left:0;right:0;
  background-repeat:repeat-x;background-size:var(--shelf-w) var(--shelf-h);
}
/* Both rows are exactly ONE tile tall and stack from the banner's floor, so the
   upper shelf shows a whole shelf of books instead of whatever height happened to
   be left over above the lower one - which is what cropped it. Any banner height
   beyond the two shelves becomes air above the bookcase, never a sliced row. */
.mhs-lo{bottom:0;height:var(--shelf-h);background-position:var(--shelf-x) bottom;}
.mhs-up{bottom:var(--shelf-h);height:var(--shelf-h);background-position:var(--shelf-x-top) bottom;}

.mh-dark .mhs-up,.mh-dark .mhs-lo{
  background-image:
    linear-gradient(to top,var(--color-2) 5%,var(--color-1) 6%,var(--color-1) 7%,transparent 7%),
    linear-gradient(to bottom,var(--color-1) 30%,transparent 80%),
    linear-gradient(to right,var(--color-2),var(--color-4) 5%,transparent 5%),
    linear-gradient(to right,transparent 6%,var(--color-2) 6%,var(--color-4) 9%,transparent 9%),
    linear-gradient(to right,transparent 27%,var(--color-2) 27%,var(--color-4) 34%,transparent 34%),
    linear-gradient(to right,transparent 51%,var(--color-2) 51%,var(--color-4) 57%,transparent 57%),
    linear-gradient(to bottom,var(--color-1) 35%,transparent 35%),
    linear-gradient(to right,transparent 42%,var(--color-2) 42%,var(--color-4) 44%,transparent 44%),
    linear-gradient(to right,transparent 45%,var(--color-2) 45%,var(--color-4) 47%,transparent 47%),
    linear-gradient(to right,transparent 48%,var(--color-2) 48%,var(--color-4) 50%,transparent 50%),
    linear-gradient(to right,transparent 87%,var(--color-2) 87%,var(--color-4) 91%,transparent 91%),
    linear-gradient(to bottom,var(--color-1) 37.5%,transparent 37.5%),
    linear-gradient(to right,transparent 14%,var(--color-2) 14%,var(--color-4) 20%,transparent 20%),
    linear-gradient(to bottom,var(--color-1) 40%,transparent 40%),
    linear-gradient(to right,transparent 10%,var(--color-2) 10%,var(--color-4) 13%,transparent 13%),
    linear-gradient(to right,transparent 21%,var(--color-2) 21%,#2b2530 25%,transparent 25%),
    linear-gradient(to right,transparent 58%,var(--color-2) 58%,var(--color-4) 64%,transparent 64%),
    linear-gradient(to right,transparent 92%,var(--color-2) 92%,var(--color-4) 95%,transparent 95%),
    linear-gradient(to bottom,var(--color-1) 48%,transparent 48%),
    linear-gradient(to right,transparent 96%,var(--color-2) 96%,#2b2530 99%,transparent 99%),
    linear-gradient(to bottom,transparent 68.5%,transparent 76%,var(--color-1) 76%,var(--color-1) 77.5%,transparent 77.5%,transparent 86%,var(--color-1) 86%,var(--color-1) 87.5%,transparent 87.5%),
    linear-gradient(to right,transparent 35%,var(--color-2) 35%,var(--color-4) 41%,transparent 41%),
    linear-gradient(to bottom,var(--color-1) 68%,transparent 68%),
    linear-gradient(to right,transparent 78%,var(--color-3) 78%,var(--color-3) 80%,transparent 80%,transparent 82%,var(--color-3) 82%,var(--color-3) 83%,transparent 83%),
    linear-gradient(to right,transparent 66%,var(--color-2) 66%,var(--color-4) 85%,transparent 85%);
}
/* The lit bookcase is painted twice: once faintly across the whole banner
   (.mh-glow) and once at full strength inside the beam (.mh-lit). Same stack,
   one selector, so the two can never fall out of step. */
.mh-lit .mhs-up,.mh-lit .mhs-lo,
.mh-glow .mhs-up,.mh-glow .mhs-lo{
  background-image:
    linear-gradient(to top,var(--rail) 5%,var(--color-1) 6%,var(--color-1) 7%,transparent 7%),
    linear-gradient(to bottom,var(--color-1) 30%,transparent 30%),
    linear-gradient(to right,var(--s1),var(--s1d) 5%,transparent 5%),
    linear-gradient(to right,transparent 6%,var(--s2) 6%,var(--s2d) 9%,transparent 9%),
    linear-gradient(to right,transparent 27%,var(--s3) 27%,var(--s3d) 34%,transparent 34%),
    linear-gradient(to right,transparent 51%,var(--s4) 51%,var(--s4d) 57%,transparent 57%),
    linear-gradient(to bottom,var(--color-1) 35%,transparent 35%),
    linear-gradient(to right,transparent 42%,var(--s5) 42%,var(--s5d) 44%,transparent 44%),
    linear-gradient(to right,transparent 45%,var(--s6) 45%,var(--s6d) 47%,transparent 47%),
    linear-gradient(to right,transparent 48%,var(--s7) 48%,var(--s7d) 50%,transparent 50%),
    linear-gradient(to right,transparent 87%,var(--s8) 87%,var(--s8d) 91%,transparent 91%),
    linear-gradient(to bottom,var(--color-1) 37.5%,transparent 37.5%),
    linear-gradient(to right,transparent 14%,var(--s9) 14%,var(--s9d) 20%,transparent 20%),
    linear-gradient(to bottom,var(--color-1) 40%,transparent 40%),
    linear-gradient(to right,transparent 10%,var(--s10) 10%,var(--s10d) 13%,transparent 13%),
    linear-gradient(to right,transparent 21%,var(--s11) 21%,var(--s11d) 25%,transparent 25%),
    linear-gradient(to right,transparent 58%,var(--s12) 58%,var(--s12d) 64%,transparent 64%),
    linear-gradient(to right,transparent 92%,var(--s13) 92%,var(--s13d) 95%,transparent 95%),
    linear-gradient(to bottom,var(--color-1) 48%,transparent 48%),
    linear-gradient(to right,transparent 96%,var(--s14) 96%,var(--s14d) 99%,transparent 99%),
    linear-gradient(to bottom,transparent 68.5%,transparent 76%,var(--color-1) 76%,var(--color-1) 77.5%,transparent 77.5%,transparent 86%,var(--color-1) 86%,var(--color-1) 87.5%,transparent 87.5%),
    linear-gradient(to right,transparent 35%,var(--s15) 35%,var(--s15d) 41%,transparent 41%),
    linear-gradient(to bottom,var(--color-1) 68%,transparent 68%),
    linear-gradient(to right,transparent 78%,var(--s16) 78%,var(--s16) 80%,transparent 80%,transparent 82%,var(--s16) 82%,var(--s16) 83%,transparent 83%),
    linear-gradient(to right,transparent 66%,var(--s17) 66%,var(--s17d) 85%,transparent 85%);
}
/* Bigger radius (210px), and the light enters + reaches the scene sooner: it is
   already partly on-screen at 0% and lands on the shelf by ~16% (was 38%), so
   the reveal starts quickly. Overall duration is longer (see ::before below). */
/* ── The light arrives, then stays ──────────────────────────────────────────
   One sweep, not a loop. The beam enters from off-frame, travels across the
   bookcase and settles on this tab's spot, where it stops. The old 28s infinite
   cycle meant something was always moving in the corner of the reader's eye on
   every page; a single arrival is calmer and reads as deliberate rather than
   decorative.
   'both' holds the final frame, so the beam stays put once it lands. The
   animation is PAUSED until its panel is active, so the arrival plays the first
   time a reader opens each tab instead of having already finished off-screen. */
@keyframes map-arrive{
  0%{-webkit-mask-position:-26% 0;mask-position:-26% 0;}
  100%{-webkit-mask-position:var(--rest-x) 0;mask-position:var(--rest-x) 0;}
}
/* The motif lights as the beam settles on it, and stays lit. */
@keyframes map-motif{
  0%,50%{opacity:0;}
  72%,100%{opacity:1;}
}
/* Only the caret keeps moving after the arrival: a caret that does not blink
   reads as a stray mark rather than as a cursor. */
@keyframes map-caret{
  0%,58%{opacity:0;}
  70%,82%{opacity:1;}
  86%,92%{opacity:0;}
  96%,100%{opacity:1;}
}

/* ── Per-tab shelves ────────────────────────────────────────────────────────
   ONE bookcase, six windows onto it. Every tab draws the same shelf in the same
   colours from the defaults above: this is one body of research, and a tab is a
   different stretch of the same library rather than a differently-painted room.
   What varies per tab is only:

     --shelf-x / --shelf-x-top   which stretch of the bookcase you are looking at
     --rest-x                    where the light comes to rest, walking left to
                                 right as you read the tabs in order
     the motif                   the one object the beam settles on

   The six separate palettes this replaced are in the git history; they made the
   tabs look like six different sites. Add a tab by picking an unused --rest-x
   and a shelf offset; nothing else needs touching. */

/* The motif itself: one small object per tab, revealed as the beam settles and
   left lit. Paused until the panel is active, on the same clock as the arrival. */
.map-hero-art::after{
  content:"";position:absolute;z-index:1;pointer-events:none;
  animation:map-motif var(--arrive) ease-out both;
  animation-play-state:paused;
}
/* Motifs share a frame: centred on the tab's resting beam so the light lands on
   the object rather than near it. */
.mh-epfl .map-hero-art::after,
.mh-mdh .map-hero-art::after,
.mh-beyond .map-hero-art::after,
.mh-directions .map-hero-art::after,
.mh-funding .map-hero-art::after,
.mh-takeaways .map-hero-art::after{
  left:var(--rest-x);bottom:52px;
}

/* What is MDH? - the library opens here. Motif: the letters themselves. */
.mh-mdh .map-hero-art{--shelf-x:-60px; --shelf-x-top:96px; --rest-x:17%;}
.mh-mdh .map-hero-art::after{
  width:26px;height:34px;transform:translateX(-50%);
  background-image:linear-gradient(90deg,#ED6E9C 0 30%,transparent 30% 35%,#4F8FCC 35% 65%,transparent 65% 70%,#F39869 70% 100%);
  filter:drop-shadow(0 0 8px rgba(237,110,156,.45));
}

/* Mapping EPFL. Motif: a map pin. */
.mh-epfl .map-hero-art{--shelf-x:40px; --shelf-x-top:-118px; --rest-x:33%;}
.mh-epfl .map-hero-art::after{
  width:14px;height:14px;
  border-radius:50% 50% 50% 0;transform:translateX(-50%) rotate(-45deg);
  background:var(--rouge-suisse);
  box-shadow:0 0 12px rgba(255,0,0,.55),inset 0 0 0 3.5px rgba(0,0,0,.6);
}

/* Beyond EPFL. Motif: a glow past the last spine, the field continuing off the
   shelf. */
.mh-beyond .map-hero-art{--shelf-x:120px; --shelf-x-top:-22px; --rest-x:49%;}
.mh-beyond .map-hero-art::after{
  width:92px;height:34px;transform:translateX(-50%);
  background:radial-gradient(ellipse at 50% 100%,rgba(0,167,159,.5) 0%,rgba(79,143,204,.26) 46%,transparent 72%);
}

/* Research Directions. Motif: a caret, still blinking. */
.mh-directions .map-hero-art{--shelf-x:-140px; --shelf-x-top:24px; --rest-x:64%;}
.mh-directions .map-hero-art::after{
  width:3px;height:21px;transform:translateX(-50%);
  background:var(--chartreuse);box-shadow:0 0 9px rgba(200,211,0,.75);
  animation-name:map-caret;
}

/* Funding. Motif: a coin caught in the beam. */
.mh-funding .map-hero-art{--shelf-x:200px; --shelf-x-top:66px; --rest-x:79%;}
.mh-funding .map-hero-art::after{
  width:16px;height:16px;border-radius:50%;transform:translateX(-50%);
  background:radial-gradient(circle at 34% 30%,#fff 0 11%,var(--soufre) 12% 60%,#c9bd2e 61% 100%);
  box-shadow:0 0 13px rgba(251,238,102,.6),inset 0 0 0 2px rgba(0,0,0,.35);
}

/* Takeaways - the closing tab, so the far end of the shelf. Motif: a bookmark
   left in a spine. */
.mh-takeaways .map-hero-art{--shelf-x:-20px; --shelf-x-top:154px; --rest-x:92%;}
.mh-takeaways .map-hero-art::after{
  width:11px;height:31px;transform:translateX(-50%);
  background:linear-gradient(180deg,var(--groseille),#7c1616);
  clip-path:polygon(0 0,100% 0,100% 100%,50% 72%,0 100%);
  box-shadow:0 0 10px rgba(181,31,31,.5);
}

/* MAP */
.map-layout{display:flex;gap:32px;align-items:flex-start;flex-wrap:wrap;}
#map-svg{border-radius:12px;background:#ffffff;}
.map-wrap{display:flex;gap:40px;align-items:center;flex-wrap:wrap;}
.map-main{flex:0 0 auto;}
.map-wip{flex:1;min-width:260px;font-family:var(--sans);font-size:30px;line-height:1.25;font-weight:700;color:#ffffff;margin:0;}
.map-wip a{color:var(--chartreuse);font-weight:700;text-decoration:underline;text-underline-offset:3px;}
/* WIP placeholder shown inside a white panel-inner (e.g. Takeaways content). */
.sec-wip{padding:48px 24px;text-align:center;font-family:var(--sans);font-size:22px;font-weight:800;color:var(--ardoise);}
.map-sidebar{flex:1;min-width:220px;max-width:300px;}
.sb-section{margin-bottom:20px;}
.sb-title{font-size:13px;font-weight:700;text-transform:uppercase;letter-spacing:1px;color:var(--ardoise);margin-bottom:9px;}
.sb-item{display:flex;align-items:flex-start;gap:9px;font-size:13.5px;margin-bottom:7px;line-height:1.5;}
.si-dot{width:13px;height:13px;border-radius:50%;flex-shrink:0;margin-top:1px;}
.sectors-box{background:white;border-radius:4px;padding:12px 14px;font-size:13.5px;line-height:2;}
/* ── 8. PROJECTS / PUBLICATIONS CARDS + BADGES ───────────────────────────── */
   /* The "Publications & Projects" tab: the work cards and their coloured badges. */

/* PROJECTS */
.search-bar{display:flex;gap:10px;margin-bottom:20px;flex-wrap:wrap;}
.search-bar input{flex:1;min-width:200px;padding:10px 14px;border:1px solid var(--border);border-radius:12px;font-family:var(--sans);font-size:15px;}
.filter-btns{display:flex;gap:6px;flex-wrap:wrap;}
.fbtn{padding:8px 14px;border:1px solid var(--border);border-radius:4px;background:white;font-size:13.5px;cursor:pointer;font-family:var(--sans);transition:all 0.15s;}
.fbtn:hover,.fbtn.active{background:var(--ardoise);color:white;border-color:var(--ardoise);}
.proj-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(var(--grid-panel),100%),1fr));gap:var(--grid-gap);}
.proj-desc-preview{font-size:16px;color:var(--ardoise);line-height:1.65;margin-bottom:14px;}
.proj-details{margin-top:14px;}
.proj-details summary{cursor:pointer;font-size:15px;color:var(--rouge-suisse);font-weight:700;padding:9px 0;-webkit-user-select:none;user-select:none;transition:color 0.15s;}
.proj-details summary:hover{color:var(--ardoise);opacity:0.88;}
.proj-details[open] .proj-desc-preview{display:none;}
.proj-desc-full{font-size:16px;color:var(--ardoise);line-height:1.7;margin-top:14px;padding-top:14px;border-top:1px solid rgba(15,23,45,0.08);}
.proj-researcher{font-size:13px;color:var(--ardoise);font-weight:700;text-transform:uppercase;letter-spacing:0.9px;margin-bottom:8px;}
.proj-name{font-size:17.5px;font-weight:700;margin-bottom:14px;color:var(--ardoise);line-height:1.35;}
.proj-desc{font-size:16px;color:var(--ardoise);line-height:1.7;margin-bottom:14px;}
.proj-meta{display:flex;flex-wrap:wrap;gap:10px;margin-top:18px;align-items:center;}
.proj-badge{font-size:13px;padding:7px 12px;border-radius:999px;font-weight:700;letter-spacing:0.25px;}
.pb-data{background:var(--vert-deau);color:var(--canard);}
.pb-tech{background:var(--vert-deau);color:var(--canard);}
/* MDH-closeness badge: one dimension colour (Groseille red) for all levels; the word says the level. */
.pb-e,.pb-r,.pb-a{background:#fff;border:1.5px solid var(--groseille);color:var(--groseille);}
.proj-link{margin-top:16px;display:inline-flex;align-items:center;gap:6px;font-size:13.5px;color:var(--rouge-suisse);text-decoration:none;font-weight:700;padding:8px 14px;border:1px solid rgba(181,31,31,0.18);border-radius:999px;}
.proj-link:hover{color:var(--ardoise);border-color:var(--ink-muted);}
.proj-footer{margin-top:18px;display:flex;flex-wrap:wrap;gap:12px;align-items:center;}
/* FEATURES */
.features-two{display:grid;grid-template-columns:1fr 1fr;gap:22px;margin-bottom:22px;}
/* White section headers on the dark panel: Filters / Charts / Work */
/* Subsections are separated by a thick ardoise rule and collapse on click. */
/* NOT A CONTROL any more. The triangle, the pointer cursor, the hover underline and the
   top rule all promised a collapse that no longer exists, so they are gone with it. What
   survives is a plain heading, which is all this element ever needed to be on the pages
   that still use it (Funding, Research Directions, Draft). The three Mapping EPFL subtabs
   no longer carry one at all: their titles moved into the band head, in the reference's
   four-part order. */
.section-head{margin:44px 0 16px;padding:22px 0 0;position:relative}
.section-head-title{font-family:var(--display);font-size:46px;font-weight:600;color:var(--ardoise);letter-spacing:-.015em;line-height:1.1;max-width:22ch}
/* no hover affordance: nothing happens on click */
/* Down-triangle to the LEFT of the title: points down when open, right when collapsed. */
/* the disclosure triangle is gone with the disclosure */

/* not focusable: it is not a control */
.section-head-sub{font-size:16px;color:rgba(69,58,76,.8);margin-top:4px;line-height:1.5}

/* ── C. Lead with the point, park the detail ────────────────────────────────
   The how-to-read-it detail goes behind a disclosure next to the thing it
   explains. The .def-row / .def-block rules that used to sit here drew the
   Charts page's three stage definitions; those blocks were removed because the
   Stage chips in the shared filter define the same three terms, in more detail,
   at the point where they are selected. Nothing else used the classes. */

.how-to{border:1px solid var(--border);border-radius:12px;background:#fff;margin:0 0 20px;padding:0}
.how-to > summary{cursor:pointer;list-style:none;padding:12px 16px;font-size:14.5px;font-weight:700;
  color:var(--groseille);display:flex;align-items:center;gap:8px}
.how-to > summary::-webkit-details-marker{display:none}
.how-to > summary::before{content:"+";font-size:16px;font-weight:700;line-height:1;width:14px;text-align:center}
.how-to[open] > summary::before{content:"\2212"}
.how-to > summary:focus-visible{outline:2px solid var(--groseille);outline-offset:-2px;border-radius:12px}
.how-to-body{padding:0 16px 14px;font-size:15px;line-height:1.6;color:rgba(69,58,76,.85)}
.how-to-body p{margin:0 0 8px}
.how-to-body p:last-child{margin:0}
/* Reusable thick ardoise divider, full width of the panel content. */
.section-sep{border:none;border-top:4px solid var(--ardoise);width:100%;margin:4px 0 24px}
/* Page title in EPFL Groseille red; single lead line below. */
.page-headline{margin:24px 0 12px}
.page-headline-title{font-family:var(--sans);font-size:28px;font-weight:800;letter-spacing:-.5px;line-height:1.15;color:var(--groseille);margin:0}
.page-lead{font-family:var(--sans);font-size:16px;line-height:1.55;color:rgba(69,58,76,.85);margin:0 0 22px}
.chart-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(var(--grid-panel),100%),1fr));gap:var(--grid-gap);margin-bottom:22px;}
.chart-card{background:#fff;border:1px solid var(--border);border-radius:12px;padding:24px;display:flex;flex-direction:column;}
.chart-card h3{font-family:var(--display);font-size:16px;font-weight:600;letter-spacing:-.01em;color:var(--ardoise);margin-bottom:16px;}
.chart-card .chart-eyebrow{font-size:13px;font-weight:700;color:var(--ardoise);text-transform:uppercase;letter-spacing:1px;margin-bottom:10px;}
/* min-height, not height+flex:1. flex:1 expands to flex-basis:0, which defeats a
   declared height in a column flex item: the 240px here never rendered and the
   canvas came out at 157px, which is also why the tick labels truncate. */
.chart-canvas-wrap{position:relative;min-height:240px;flex:1 0 240px;}
.matrix-wrap{background:white;border-radius:12px;padding:22px;overflow-x:auto;}
.matrix-wrap h3{font-size:16px;font-weight:600;margin-bottom:14px;}
table.mx{border-collapse:collapse;width:100%;font-size:13px;}
table.mx th{padding:8px 9px;background:var(--ardoise);font-weight:500;border:1px solid var(--border);text-align:left;white-space:nowrap;}
table.mx td{padding:7px 9px;border:1px solid var(--border);text-align:center;}
.mdot{width:14px;height:14px;border-radius:50%;display:inline-block;background:var(--rouge-suisse);opacity:0.7;}
/* NETWORK */
.net-wrap{background:white;border-radius:12px;padding:20px;}
#net-svg{width:100%;border-radius:4px;}
.net-legend{display:flex;gap:18px;flex-wrap:wrap;margin-top:14px;font-size:13.5px;}
.nl-item{display:flex;align-items:center;gap:6px;}
.nl-dot{width:12px;height:12px;border-radius:50%;}
/* INSIGHTS */
.ins-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(300px,100%),1fr));gap:18px;}
.ins-card{background:white;border-radius:12px;padding:24px;border:1px solid var(--border);border-top:3px solid var(--groseille);transition:all 0.2s;}
.ins-card:hover{box-shadow:0 6px 16px rgba(0,0,0,0.12);}
.ins-title{font-size:16.5px;font-weight:700;margin-bottom:10px;color:var(--ardoise);}
.ins-body{font-size:16px;color:var(--ardoise);line-height:1.65;margin-bottom:12px;}
.ins-quote{position:relative;padding:10px 14px 10px 17px;background:#ffffff;font-size:13.5px;font-style:italic;line-height:1.6;border:1px solid var(--border);border-radius:4px;color:var(--ardoise);}
.ins-quote::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--rouge-suisse);border-radius:4px 0 0 4px;}
/* REPORT */
.report-body{max-width:860px;}
.rb{background:white;border-radius:12px;padding:26px 30px;margin-bottom:18px;}
.rb h2{font-family:var(--sans);font-size:19.5px;margin-bottom:11px;}
.rb h3{font-size:16px;font-weight:600;margin:14px 0 7px;}
.rb p{font-size:16px;color:var(--ardoise);line-height:1.7;margin-bottom:9px;}
.rb ul{padding-left:16px;margin-bottom:9px;}
.rb li{font-size:16px;color:var(--ardoise);line-height:1.7;margin-bottom:4px;}
.rtag{display:inline-block;background:#ffffff;color:var(--rouge-suisse);font-size:13px;padding:2px 9px;border-radius:4px;font-weight:600;margin:2px;}
.pgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:11px;margin-top:12px;}
.pcard{border-radius:12px;padding:13px 15px;}
.pcard.high{background:#ffffff;border:1px solid var(--rouge-suisse);}
.pcard.med{background:#ffffff;border:1px solid var(--carotte);}
.pcard.low{background:var(--vert-deau);border:1px solid var(--canard);}
.pcard .plabel{font-size:12px;text-transform:uppercase;letter-spacing:1px;font-weight:700;opacity:0.6;margin-bottom:4px;}
.pcard .ptitle{font-size:15px;font-weight:700;margin-bottom:4px;}
.pcard .pdesc{font-size:13px;color:var(--ardoise);line-height:1.5;}
/* TOOLTIP */
#tooltip{position:fixed;background:var(--ardoise);color:white;padding:12px 14px;border-radius:12px;font-size:13.5px;pointer-events:none;z-index:9999;max-width:300px;display:none;line-height:1.5;box-shadow:0 8px 28px rgba(0,0,0,0.3);}
#tt-name{font-weight:700;font-size:15px;margin-bottom:2px;}
#tt-unit{color:rgba(255,255,255,0.5);font-size:13px;margin-bottom:6px;}
#tt-body{color:rgba(255,255,255,0.8);}
/* Narrow screens tighten spacing but NOT type: the header title and the nav tabs
   keep their desktop size all the way down. They used to step down to 15px and
   11.5px, which is where the site was least readable and where a visitor is most
   likely to be on a phone. Tabs give up padding and wrap or scroll instead. */
@media(max-width:960px){
  :root{--gutter:20px;--header-h:72px;--nav-h:46px;}
  .site-chrome header{padding:10px var(--gutter);gap:12px;}
  .h-logo{height:36px;}
  .topnav-inner{gap:10px;}
  .topnav-tab{padding:0 8px;}
  .topnav-aud-label{display:none;}
  .aud-select-btn{padding:5px 9px;}
  /* Sides read --gutter, not a literal. A band cancels exactly --gutter with a
     negative margin to reach the window edge, so the panel's side padding has to
     BE --gutter or the difference is left over as an inset. This said 20px while
     --gutter drops to 14px further down, and every band on the site stopped 6px
     short of both edges at 375. Top and bottom keep their 20px. */
  .panel{padding:20px var(--gutter);}.tab-bar{padding:0 12px;}.features-two{grid-template-columns:1fr;}.pgrid{grid-template-columns:1fr;}
}
@media(max-width:700px){
  /* Below this width the section tabs and the audience selector no longer fit
     on one row: stack them as two rows instead of letting flex-wrap add a row
     unpredictably. .main's actual top padding is corrected by JS
     (syncHeaderNavHeight in ui.js) to match whatever this ends up rendering
     at; --nav-h here only sets each tab button's own height, not page spacing. */
  /* In :root since 2026-09-13. As a bare declaration directly inside @media it was invalid, and the
     parser dropped it together with the .topnav-inner rule after it, so the rows never stacked. */
  :root{--nav-h:42px;}
  .topnav-inner{flex-direction:column;align-items:stretch;gap:0;padding-top:8px;padding-bottom:8px;}
  .topnav-aud{justify-content:flex-start;margin-top:8px;}
}
@media(max-width:480px){
  :root{--gutter:14px;--header-h:66px;}
  /* The header subtitle is the one thing still dropped on phones: it is an
     all-caps strapline, not content, and cutting it buys the title its size. */
  .h-left p{display:none;}
  .h-logo{height:30px;}
  .site-chrome header{gap:8px;}
  .topnav-tab{padding:0 9px;}
}

/* ── Phone header (decision 18 C, 2026-09-13) ──
   One slim bar: the laptop mark, the name, a search icon and Menu. The tabs, the sub-tabs, the view
   selector and Log in open under it as a menu laid over the page, so opening it moves nothing, and
   the bar tucks away while scrolling down (ui.js). */
.h-menu-btn,.topnav-login{display:none}
@media(max-width:700px){
  .site-chrome{transition:transform .25s ease}
  .site-chrome.is-tucked{transform:translateY(-100%)}
  .site-chrome header{padding:8px var(--gutter);flex-wrap:nowrap;gap:10px}
  .h-left{gap:10px;min-width:0;flex:1 1 auto;overflow:hidden}
  .h-title-block{min-width:0;overflow:hidden}
  /* Same footprint as the old 38px square on a phone: wider, the mark pushed the title into an ellipsis. */
  .h-logo-laptop{height:28px;width:42px}
  .h-left p{display:none}
  .h-left .h-brand{font-size:15px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .h-right{gap:8px;flex:none}
  header .h-login-btn{display:none}
  /* At the specificity of the header's own button rule, or its font-size wins and the word stays. */
  .site-chrome header .h-search-btn{font-size:0;gap:0;padding:6px 10px}
  .site-chrome header .h-search-btn span{font-size:15px}
  .h-menu-btn{display:inline-flex;align-items:center;gap:6px;font-family:var(--sans);font-size:13.5px;font-weight:700;
    color:#fff;background:transparent;border:1px solid rgba(255,255,255,.5);border-radius:999px;padding:5px 12px;cursor:pointer}
  .h-menu-ico{font-size:15px;line-height:1}
  .site-chrome .topnav{display:none;position:absolute;left:0;right:0;top:100%;max-height:calc(100vh - 56px);
    overflow-y:auto;box-shadow:0 14px 30px rgba(0,0,0,.35)}
  body.menu-open .site-chrome .topnav{display:block}
  .topnav-tabs{flex-direction:column}
  .topnav-tab{padding:12px 4px;text-align:left;font-size:15px}
  .topnav-tab.active::after{left:-10px;right:auto;top:10px;bottom:10px;width:3px;height:auto;border-radius:0 3px 3px 0}
  /* The sub-tabs sit under their section in the menu, and the row under the header scrolls
     instead of wrapping a tab onto a line of its own (2026-09-14). */
  .topnav-tab-wrap{flex-direction:column}
  .topnav-tab-menu{position:static;opacity:1;visibility:visible;transform:none;pointer-events:auto;
    background:none;box-shadow:none;border-radius:0;min-width:0;padding:0 0 6px 14px}
  .topnav-tab-menu-item{color:rgba(255,255,255,.72);font-size:14px;font-weight:500;padding:8px 4px}
  .topnav-tab-menu-item:hover{background:none;color:#fff}
  .topnav-sub-inner{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none}
  .topnav-subtab{white-space:nowrap}
  .topnav-inner{flex-direction:column;align-items:stretch;gap:0;padding:6px var(--gutter) 12px}
  .topnav-aud{justify-content:flex-start;margin-top:10px}
  .topnav-login{display:inline-flex;font-family:var(--sans);font-size:13.5px;font-weight:700;color:#fff;background:transparent;
    border:1px solid rgba(255,255,255,.5);border-radius:999px;padding:5px 12px;margin-right:8px;cursor:pointer}
  /* The View list opens in place inside the menu (A2, 2026-09-14). Dropped below its button it was
     clipped by the menu's own scroll area, so only its first option showed, 4px past the left edge. */
  .topnav-aud{flex-wrap:wrap;align-items:flex-start}
  .aud-select-menu{position:static;margin-top:8px;min-width:0;width:max-content;max-width:100%;box-shadow:none}
  /* A backdrop under the open menu, so nothing sticky on the page shows along its edge (A9, 2026-09-14).
     Below the header's 110, above the page's own sticky bars and the back-to-top button. */
  body.menu-open::after{content:'';position:fixed;inset:0;z-index:105;background:rgba(33,27,38,.55)}
}
/* Below 380px the laptop mark gives its 48px to the site name, which otherwise ends in an ellipsis. */
@media(max-width:380px){.h-logo-laptop-wrap{display:none}}
@media (prefers-reduced-motion: reduce){.site-chrome{transition:none}}

/* ── Interview dashboard styles ── */

  .d1w * {box-sizing:border-box;margin:0;padding:0}
  .d1w body {font-family:var(--font-sans)}
  .d1w .tabs {display:flex;gap:4px;border-bottom:0.5px solid var(--color-border-tertiary);margin-bottom:1.5rem;flex-wrap:wrap}
  .d1w .tab {padding:8px 14px;font-size:15px;cursor:pointer;border:none;background:transparent;color:var(--color-text-secondary);border-bottom:2px solid transparent;transition:all 0.15s}
  .d1w .tab.active {color:var(--color-text-primary);border-bottom:2px solid var(--color-text-primary);font-weight:500}
  .d1w .tab:hover {color:var(--color-text-primary)}
  .d1w .panel {display:none}.panel.active{display:block}
  .d1w .person-grid {display:grid;grid-template-columns:repeat(auto-fill,minmax(min(280px,100%),1fr));gap:12px}
  .d1w .card {background:var(--color-background-primary);border:0.5px solid var(--color-border-tertiary);border-radius:var(--border-radius-lg);padding:1rem 1.25rem}
  .d1w .card h3 {font-size:16px;font-weight:500;margin-bottom:4px;color:var(--color-text-primary)}
  .d1w .card .role {font-size:13.5px;color:var(--color-text-secondary);margin-bottom:10px}
  .d1w .tag {display:inline-block;font-size:13px;padding:2px 8px;border-radius:4px;margin:2px 2px 2px 0}
  .d1w .tag-tech {background:var(--vert-deau);color:var(--canard)}
  .d1w .tag-mdh {background:var(--vert-deau);color:var(--canard)}
  .d1w .tag-rel {background:#ffffff;color:var(--marron)}
  .d1w .tag-low {background:#ffffff;color:var(--ardoise)}
  .d1w .card p {font-size:15px;color:var(--color-text-secondary);line-height:1.6;margin-top:8px}
  .d1w .card .key {font-size:13.5px;font-weight:500;color:var(--color-text-primary);margin-top:10px;margin-bottom:4px}
  .d1w .expandbtn {font-size:13.5px;color:var(--color-text-secondary);cursor:pointer;background:none;border:none;padding:4px 0;text-decoration:underline}
  .d1w .detail {display:none;margin-top:8px;border-top:0.5px solid var(--color-border-tertiary);padding-top:8px}
  .d1w .detail.open {display:block}
  .d1w .section-head {font-size:17.5px;font-weight:500;margin-bottom:1rem;color:var(--color-text-primary)}
  .d1w .research-item {border:0.5px solid var(--color-border-tertiary);border-radius:var(--border-radius-md);padding:10px 14px;margin-bottom:8px;background:var(--color-background-primary)}
  .d1w .research-item h4 {font-size:15px;font-weight:500;color:var(--color-text-primary);margin-bottom:4px}
  .d1w .research-item p {font-size:13.5px;color:var(--color-text-secondary);line-height:1.5}
  .d1w .research-item .why {font-size:13.5px;color:var(--canard);background:var(--vert-deau);padding:3px 8px;border-radius:4px;display:inline-block;margin-top:6px}
  .d1w .people-grid {display:grid;grid-template-columns:repeat(auto-fill,minmax(min(220px,100%),1fr));gap:10px}
  .d1w .person-item {border:0.5px solid var(--color-border-tertiary);border-radius:var(--border-radius-md);padding:10px 12px;background:var(--color-background-primary)}
  .d1w .person-item .name {font-size:15px;font-weight:500;color:var(--color-text-primary)}
  .d1w .person-item .affil {font-size:13.5px;color:var(--color-text-secondary);margin:2px 0 6px}
  .d1w .person-item .reason {font-size:13.5px;color:var(--color-text-secondary);line-height:1.5}
  .d1w .badge {font-size:13px;padding:2px 8px;border-radius:4px;margin-right:4px}
  .d1w .badge-epfl {background:#ffffff;color:var(--zinzolin)}
  .d1w .badge-ext {background:#ffffff;color:var(--marron)}
  .d1w .badge-left {background:#ffffff;color:var(--ardoise)}
  .d1w .viz-card {border:0.5px solid var(--color-border-tertiary);border-radius:var(--border-radius-lg);padding:1rem 1.25rem;margin-bottom:12px;background:var(--color-background-primary)}
  .d1w .viz-card h4 {font-size:16px;font-weight:500;margin-bottom:6px;color:var(--color-text-primary)}
  .d1w .viz-card p {font-size:15px;color:var(--color-text-secondary);line-height:1.6}
  .d1w .viz-card .data {font-size:13.5px;color:var(--color-text-secondary);margin-top:8px;padding:8px;background:var(--color-background-secondary);border-radius:4px}
  .d1w .miss-grid {display:grid;grid-template-columns:repeat(auto-fill,minmax(min(280px,100%),1fr));gap:10px}
  .d1w .miss-item {border:0.5px solid var(--color-border-tertiary);border-radius:var(--border-radius-md);padding:10px 14px;background:var(--color-background-primary)}
  .d1w .miss-item .person-name {font-size:13.5px;font-weight:500;color:var(--acier);margin-bottom:6px}
  .d1w .miss-item ul {list-style:none;padding:0}
  .d1w .miss-item ul li {font-size:13.5px;color:var(--color-text-secondary);padding:3px 0;border-bottom:0.5px solid var(--color-border-tertiary);line-height:1.5}
  .d1w .miss-item ul li:last-child {border-bottom:none}
  .d1w .miss-item ul li::before {content:"? ";color:var(--carotte);font-weight:500}
  .d1w .filter-row {display:flex;gap:8px;margin-bottom:1rem;flex-wrap:wrap}
  .d1w .filter-btn {font-size:13.5px;padding:4px 12px;border:0.5px solid var(--color-border-secondary);border-radius:12px;cursor:pointer;background:transparent;color:var(--color-text-secondary)}
  .d1w .filter-btn.active {background:var(--color-text-primary);color:var(--color-background-primary);border-color:transparent}
  .d1w .step-list {counter-reset:steps;list-style:none;padding:0}
  .d1w .step-list li {counter-increment:steps;padding:10px 14px 10px 44px;position:relative;border-bottom:0.5px solid var(--color-border-tertiary);font-size:15px;color:var(--color-text-secondary);line-height:1.6}
  .d1w .step-list li::before {content:counter(steps);position:absolute;left:12px;top:10px;width:22px;height:22px;background:var(--color-background-secondary);border:0.5px solid var(--color-border-secondary);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:500;color:var(--color-text-primary)}
  .d1w .urgency-high {border:1px solid var(--carotte)}
  .d1w .urgency-med {border:1px solid var(--carotte)}
  .d1w .urgency-low {border:1px solid rgba(69,58,76,.2)}

.d1-content{display:block!important}

/* ── Network legend bar ── */
.net-legend-bar {
  display:flex;flex-wrap:wrap;gap:8px 16px;align-items:center;
  padding:10px 14px;background:#ffffff;border-radius:12px;margin-bottom:12px;
  font-size:13.5px;color:var(--ardoise);border:1px solid var(--border);
}
.net-legend-bar .nl-item { display:flex;align-items:center;gap:5px; }
.net-legend-bar .nl-dot { width:12px;height:12px;border-radius:50%;display:inline-block;flex-shrink:0; }
.net-size-note { font-size:13px;color:var(--ardoise);margin-left:auto; }

/* ── 11. FUNDING PANEL ───────────────────────────────────────────────────── */
   /* The "Funding" tab: the funding-opportunity cards. */

/* ── Funding Landscape panel ── */
.funding-grid { display:grid;grid-template-columns:repeat(auto-fill,minmax(min(340px,100%),1fr));gap:12px; }
.funding-card { background:white;border-radius:12px;border:1px solid var(--perle);padding:16px;position:relative; }
.funding-card.fit-high { border-color:var(--leman); }
.funding-card.fit-medium { border-color:var(--carotte); }
.funding-card.fit-low-medium,.funding-card.fit-variable { border-color:var(--perle); }
.fc-header { display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px;gap:8px; }
.fc-name { font-size:16px;font-weight:700;color:var(--ardoise);line-height:1.3; }
.fc-badges { display:flex;gap:4px;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end; }
.fc-badge { font-size:12px;padding:2px 7px;border-radius:4px;font-weight:700;white-space:nowrap; }
.fc-badge.fit-high { background:var(--vert-deau);color:var(--leman); }
.fc-badge.fit-medium { background:rgba(236,102,8,.14);color:#8a3c04; }
.fc-badge.fit-low-medium,.fc-badge.fit-variable { background:rgba(69,58,76,.10);color:var(--ardoise); }
.fc-badge.status-active { background:var(--vert-deau);color:var(--canard); }
.fc-badge.status-open { background:var(--vert-deau);color:var(--canard); }
.fc-badge.status-competitive,.fc-badge.status-to-lobby { background:rgba(181,31,31,.12);color:var(--groseille); }
.fc-badge.status-case-by-case { background:rgba(92,36,131,.12);color:var(--zinzolin); }
.fc-amount { font-size:13.5px;font-weight:600;color:var(--rouge-suisse);margin-bottom:3px; }
.fc-timeline { font-size:13px;color:var(--ardoise);margin-bottom:8px; }
.fc-note { font-size:13.5px;color:var(--ardoise);line-height:1.55;margin-bottom:8px; }
.fc-partners { font-size:13px;color:var(--ardoise);margin-bottom:4px; }
.fc-contact { font-size:13px;color:var(--ardoise);font-weight:600; }


/* ── Researcher modal overlay ── */
.rm-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.55);z-index:1000;align-items:flex-start;justify-content:center;padding:24px 16px;overflow-y:auto}
.rm-overlay.open{display:flex}
.rm-dialog{background:white;border-radius:12px;width:100%;max-width:820px;margin:auto;box-shadow:0 20px 60px rgba(0,0,0,0.35);overflow:hidden}
.rm-header{display:flex;align-items:flex-start;justify-content:space-between;padding:24px 28px 16px;background:var(--ardoise);color:white;gap:12px}
.rm-name{font-size:22px;font-weight:800;margin-bottom:4px}
.rm-unit{font-size:15px;opacity:0.75;margin-bottom:8px}
.rm-status{font-size:13.5px;padding:3px 10px;border-radius:4px;display:inline-block;font-weight:600;background:rgba(255,255,255,0.15)}
.rm-status.s-done{background:var(--leman)}
.rm-status.s-agreed{background:var(--zinzolin)}
.rm-status.s-answered{background:var(--carotte)}
.rm-close{background:rgba(255,255,255,0.15);border:none;color:white;width:36px;height:36px;border-radius:50%;font-size:22px;cursor:pointer;flex-shrink:0;line-height:1;display:flex;align-items:center;justify-content:center;transition:background 0.15s}
.rm-close:hover{background:rgba(255,255,255,0.3)}
.rm-section{padding:20px 28px;border-bottom:1px solid var(--border)}
.rm-section:last-child{border-bottom:none}
/* (.rm-section-title is defined once, further down — the duplicate here was removed.) */
.rm-interview-body p{font-size:15px;line-height:1.65;color:var(--ardoise);margin-bottom:8px}
.rm-interview-body .key{font-size:13px;font-weight:800;color:var(--rouge-suisse);text-transform:uppercase;letter-spacing:0.8px;margin:14px 0 4px}
.rm-interview-body .rm-detail{display:block!important}
.rm-interview-body strong{color:var(--ardoise)}
.rm-interview-body .tag{font-size:13px;padding:2px 8px;border-radius:4px;margin-right:4px;margin-bottom:4px;display:inline-block}
.rm-interview-body .tag-mdh{background:#ffffff;color:var(--groseille)}
.rm-interview-body .tag-rel{background:var(--vert-deau);color:var(--canard)}
.rm-interview-body .tag-tech{background:#ffffff;color:var(--ardoise)}
.rm-interview-body .tag-low{background:#ffffff;color:var(--ardoise)}
.rm-proj{padding:10px 12px;background:#ffffff;border-radius:4px;border:1px solid var(--border);margin-bottom:8px}
.rm-proj-header{margin-bottom:4px}
.rm-paper{font-size:15px;padding:6px 0;border-bottom:1px solid rgba(69,58,76,.2);line-height:1.5}
.rm-paper:last-child{border-bottom:none}
.rm-paper a{color:var(--rouge-suisse);text-decoration:none;font-weight:600}
.rm-paper a:hover{text-decoration:underline}
/* ── 9. FILTERS (search · bars · per-dimension colours · collapsibles) ───── */
   /* The filter bars + search box shared by the Actors and Publications tabs. Each filter has its own colour. */

/* ── Status panel: search + collapsible filters ── */
.sf-search-row{display:flex;flex-direction:column;align-items:flex-start;gap:8px;margin:18px 0 10px}
.sf-search{width:100%;padding:9px 14px;border:1.5px solid var(--perle);border-radius:12px;font-family:var(--sans);font-size:15px;color:var(--ardoise);background:#fff;outline:none;transition:border-color .15s}
.sf-search:focus{border-color:var(--leman)}
/* Quiet by default. As a filled Zinzolin pill this was the most prominent thing
   in the panel, and it is the one control you want LEAST often: it throws away
   the work you just did. It reads as an outline until hovered. */
.sf-toggle-btn{padding:6px 13px;border-radius:12px;border:1px solid rgba(31,42,55,.16);background:#fff;color:var(--ink-muted);font-size:13px;font-weight:700;cursor:pointer;transition:all .15s;font-family:var(--sans);align-self:flex-start}
.sf-toggle-btn:hover{border-color:var(--groseille);color:var(--groseille);background:#fff}
.sf-toggle-btn:hover{background:var(--ardoise);border-color:var(--ardoise);color:#fff}
/* Filters toggle moved to the top of the section, in EPFL Groseille red. */
.sf-toggle-top{margin-bottom:14px}
.sf-toggle-red{border-color:var(--groseille);background:var(--groseille);color:#fff}
.sf-toggle-red:hover{border-color:var(--ardoise);background:var(--ardoise);color:#fff}
#status-filters-wrap.collapsed{display:none}
.sf-count{display:inline-block;margin-left:5px;padding:0 6px;font-size:12px;font-weight:700;border-radius:12px;background:rgba(0,0,0,0.08);min-width:16px;text-align:center}
.sf-btn.active .sf-count{background:rgba(255,255,255,0.28)}
/* ── Status filter bar ── */
.sf-bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:16px 0 8px}
.sf-label{font-size:13.5px;font-weight:700;text-transform:uppercase;letter-spacing:0.6px;margin-right:4px;padding:5px 12px;border-radius:12px;border:1.5px solid var(--perle)}
/* Styled hover tooltip for filter labels (data-tip is set from the title attribute by JS on load). */
.sf-label[data-tip],.sf-collapse-btn[data-tip],.sf-btn[data-tip]{cursor:help;position:relative}
.sf-label[data-tip]:hover::after,.sf-collapse-btn[data-tip]:hover::after,.sf-btn[data-tip]:hover::after,.sf-label[data-tip]:focus-visible::after,.sf-collapse-btn[data-tip]:focus-visible::after,.sf-btn[data-tip]:focus-visible::after{content:attr(data-tip);position:absolute;left:0;top:calc(100% + 6px);z-index:300;width:max-content;max-width:320px;background:var(--ardoise);color:#fff;font-size:13px;font-weight:400;text-transform:none;letter-spacing:normal;line-height:1.45;padding:9px 11px;border-radius:12px;box-shadow:0 6px 20px rgba(0,0,0,.22);white-space:normal;pointer-events:none}
.sf-btn{padding:5px 14px;border-radius:12px;border:1.5px solid var(--perle);background:#ffffff;font-size:13.5px;font-weight:600;cursor:pointer;transition:all .15s;color:var(--ardoise)}
.sf-btn.active,.sf-btn:hover{border-color:var(--canard);background:var(--canard);color:white}
.sf-select{padding:5px 12px;border-radius:12px;border:1.5px solid var(--perle);background:#fff;font-family:var(--sans);font-size:13.5px;font-weight:600;color:var(--ardoise);cursor:pointer;max-width:300px;outline:none}
.sf-select[multiple]{border-radius:12px;padding:4px;min-width:220px;vertical-align:top}
.sf-select[multiple] option{padding:3px 8px;border-radius:12px}
.sf-select[multiple] option:checked{background:var(--ardoise);color:#fff}
.sf-multi-hint{font-size:13px;color:var(--ardoise);margin:0 0 10px;line-height:1.5}
.sf-stage-legend{font-size:13px;color:var(--ardoise);margin:2px 0 10px;line-height:1.5;max-width:1000px}
.sf-stage-legend strong{color:var(--ardoise)}
/* Collapsible tickable filter panels (Theme / Lab / Actor) */
.sf-collapse{display:inline-block;vertical-align:top;position:relative;margin:0 8px 8px 0}
.sf-collapse-btn{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border-radius:12px;border:1.5px solid var(--perle);background:#fff;font-family:var(--sans);font-size:13.5px;font-weight:700;color:var(--ardoise);cursor:pointer;outline:none}
/* Sort buttons: full background in their navy colour (match the filled Reset/Hide). */
#status-sort-collapse .sf-collapse-btn,#features-sort-collapse .sf-collapse-btn{background:var(--ardoise);border-color:var(--ardoise);color:#fff}
.sf-collapse-btn:hover{border-color:var(--canard)}
.sf-collapse-count{background:var(--ardoise);color:#fff;border-radius:999px;font-size:12px;font-weight:700;padding:1px 7px;min-width:8px;text-align:center}
.sf-collapse-count:empty{display:none}
.sf-collapse-caret{font-weight:700;color:var(--ardoise)}
.sf-collapse.open .sf-collapse-btn{border-color:var(--canard);background:#fff}
.sf-collapse-body{display:none;position:absolute;z-index:30;margin-top:4px;min-width:240px;max-width:340px;max-height:280px;overflow-y:auto;background:#fff;border:1.5px solid var(--perle);border-radius:12px;padding:6px;box-shadow:0 6px 22px rgba(0,0,0,0.12)}
.sf-collapse.open .sf-collapse-body{display:block}
.sf-check-search{width:100%;box-sizing:border-box;margin:0 0 6px;padding:5px 8px;border:1.5px solid var(--perle);border-radius:4px;font-size:13.5px;font-family:var(--sans);color:var(--ardoise);outline:none}
.sf-check-search:focus{border-color:var(--canard)}
.sf-check{display:block;font-size:13.5px;color:var(--ardoise);padding:5px 8px;border-radius:12px;cursor:pointer;font-weight:400;line-height:1.3}
.sf-check:hover{background:#fff}
.sf-check input{margin-right:7px;vertical-align:middle;accent-color:var(--ardoise)}
.sf-check-n{color:var(--ardoise);font-size:13px}
.sf-typekey{font-size:13px;color:var(--ardoise);margin:2px 0 8px;line-height:1.5}
.sf-select:focus{border-color:var(--canard)}
/* ── Filter-bar base: neutral resting state (perle), shared by every bar ──────
   The per-dimension colours below override label/active/contour at equal
   specificity (same #id .class weight, later in the file), exactly as before.
   Kept as explicit ID lists so the cascade order and specificity are unchanged;
   only the hard-coded hexes became tokens. */
#status-filter-bar .sf-label,
#mdh-focus-filter-bar .sf-label,
#features-mdh-focus-bar .sf-label,
#affil-filter-bar .sf-label,
#data-filter-bar .sf-label,
#focus-filter-bar .sf-label,
#stage-filter-bar .sf-label,
#features-relevance-bar .sf-label,
#features-stage-bar .sf-label,
#features-type-bar .sf-label{color:var(--ardoise);border:1.5px solid var(--perle);}
#status-filter-bar .sf-btn,
#mdh-focus-filter-bar .sf-btn,
#features-mdh-focus-bar .sf-btn,
#affil-filter-bar .sf-btn,
#data-filter-bar .sf-btn,
#focus-filter-bar .sf-btn,
#stage-filter-bar .sf-btn,
#features-relevance-bar .sf-btn,
#features-stage-bar .sf-btn,
#features-type-bar .sf-btn{border-color:var(--perle);color:var(--ardoise);background:#fff;}
#status-filter-bar .sf-btn:hover,
#mdh-focus-filter-bar .sf-btn:hover,
#features-mdh-focus-bar .sf-btn:hover,
#affil-filter-bar .sf-btn:hover,
#data-filter-bar .sf-btn:hover,
#focus-filter-bar .sf-btn:hover,
#stage-filter-bar .sf-btn:hover,
#features-relevance-bar .sf-btn:hover,
#features-stage-bar .sf-btn:hover,
#features-type-bar .sf-btn:hover{background:var(--perle);border-color:var(--perle);color:var(--ardoise)}

/* ── Per-dimension colours ── each filter bar owns one EPFL hue; the same hue
   appears on the matching card labels so the filter doubles as a colour key:
     closeness = Groseille · focus = Zinzolin · stage = Carotte · type = Navy
     data = Vert d'eau · year = Leman · subject/topic = Acier · method = Rose
     lab = Marron · actor = Mont Rose · affiliation = Rouge suisse
   Label (text+contour), resting btn contour, and active fill each carry the
   hue. Pale hues (data) darken their label/active text for contrast. */
#features-relevance-bar .sf-label,#status-filter-bar .sf-label{color:var(--groseille);border-color:var(--groseille)}
#features-mdh-focus-bar .sf-label,#mdh-focus-filter-bar .sf-label{color:var(--zinzolin);border-color:var(--zinzolin)}
#features-stage-bar .sf-label,#stage-filter-bar .sf-label{color:var(--carotte);border-color:var(--carotte)}
#features-type-bar .sf-label{color:var(--canard);border-color:var(--canard)}
#features-data-bar .sf-label,#data-filter-bar .sf-label{background:#fff;color:var(--ardoise);border-color:var(--vert-deau)}
#features-year-bar .sf-label{color:var(--leman);border-color:var(--leman)}
#focus-filter-bar .sf-label{color:var(--acier);border-color:var(--acier)}
#affil-filter-bar .sf-label{color:var(--ardoise);border-color:var(--perle)}
#features-affil-bar .sf-label{color:var(--rouge-suisse);border-color:var(--rouge-suisse)}
#features-relevance-bar .sf-btn.active,#status-filter-bar .sf-btn.active{background:var(--groseille);border-color:var(--groseille);color:#fff}
#features-mdh-focus-bar .sf-btn.active,#mdh-focus-filter-bar .sf-btn.active{background:var(--zinzolin);border-color:var(--zinzolin);color:#fff}
#features-stage-bar .sf-btn.active,#stage-filter-bar .sf-btn.active{background:var(--carotte);border-color:var(--carotte);color:#fff}
#focus-filter-bar .sf-btn.active{background:var(--acier);border-color:var(--acier);color:#fff}
#data-filter-bar .sf-btn.active{background:var(--vert-deau);border-color:var(--vert-deau);color:var(--ardoise)}
#affil-filter-bar .sf-btn.active{background:var(--perle);border-color:var(--perle);color:var(--ardoise)}
/* Long facets (Themes, Tech & method) collapse to their top values behind "+N more". */
/* Every "More filters" group header carries its dimension colour. */
#features-year-collapse .sf-collapse-btn{color:var(--leman);border-color:var(--leman)}
#features-score-collapse .sf-collapse-btn{color:var(--ardoise);border-color:var(--ardoise)}
/* A group with an active selection: filled count badge + tinted header. */
.sf-collapse.has-sel .sf-collapse-btn{background:rgba(31,42,55,.06)}
#features-year-collapse .sf-collapse-count{background:var(--leman)}
#features-lab-collapse .sf-collapse-count{background:var(--marron)}
#features-actor-collapse .sf-collapse-count{background:var(--mont-rose)}

/* ── Active-filters summary row: one coloured chip per selected value ── */
.features-active-filters{display:flex;flex-wrap:wrap;align-items:center;gap:7px;margin:2px 0 10px 176px;min-height:28px}
.features-active-filters::before{content:"Active:";font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--ardoise);opacity:.55;margin-right:3px}
/* Empty state keeps its height so the first click does not shift the layout. */
.features-active-filters.is-empty::before{content:"Active: none";opacity:.3}
.af-chip{background:#fff;border:1.5px solid rgba(31,42,55,.3);border-radius:999px;padding:4px 11px;font-size:13px;font-weight:700;color:var(--ardoise);cursor:pointer}
.af-chip .af-x{font-weight:800;margin-left:4px;opacity:.65}
.af-chip:hover{background:rgba(31,42,55,.05)}
.af-chip:hover .af-x{opacity:1}
.af-close{color:var(--groseille);border-color:var(--groseille)}
.af-focus{color:var(--zinzolin);border-color:var(--zinzolin)}
.af-stage{color:var(--carotte);border-color:var(--carotte)}
.af-data{color:var(--ardoise);border-color:var(--vert-deau)}
.af-method{color:#b02a63;border-color:var(--rose)}
.af-type{color:var(--canard);border-color:var(--canard)}
.af-affil{color:var(--rouge-suisse);border-color:var(--rouge-suisse)}
.af-theme{color:var(--acier);border-color:var(--acier)}
.af-year{color:var(--leman);border-color:var(--leman)}
.af-score{color:var(--ardoise);border-color:var(--ardoise)}
.af-subject{color:var(--acier);border-color:var(--acier)}
.af-lab{color:var(--marron);border-color:var(--marron)}  /* Lab keeps Marron */
.af-actor{color:var(--mont-rose);border-color:var(--mont-rose)}
@media (max-width:640px){.features-active-filters{margin-left:0}}
#features-subject-collapse .sf-collapse-btn{color:var(--ardoise);border-color:var(--acier)}
#features-lab-collapse .sf-collapse-btn{color:var(--marron);border-color:var(--marron)}
#features-actor-collapse .sf-collapse-btn{color:var(--mont-rose);border-color:var(--mont-rose)}
#method-filter-bar .sf-label{color:var(--ardoise);border-color:var(--rose)}
#method-filter-bar .sf-btn.active{background:var(--rose);border-color:var(--rose);color:#fff}
#subject-filter-bar .sf-label{color:var(--ardoise);border-color:var(--acier)}
#subject-filter-bar .sf-btn.active{background:var(--acier);border-color:var(--acier);color:#fff}

/* Resting value-button contour matches its dimension's label colour. */
#status-filter-bar .sf-btn,#features-relevance-bar .sf-btn{border-color:var(--groseille)}
#mdh-focus-filter-bar .sf-btn,#features-mdh-focus-bar .sf-btn{border-color:var(--zinzolin)}
#stage-filter-bar .sf-btn,#features-stage-bar .sf-btn{border-color:var(--carotte)}
#features-type-bar .sf-btn{border-color:var(--canard)}
#features-affil-bar .sf-btn{border-color:var(--rouge-suisse)}
#data-filter-bar .sf-btn,#features-data-bar .sf-btn{border-color:var(--vert-deau)}
#method-filter-bar .sf-btn,#features-method-bar .sf-btn{border-color:var(--rose)}
#subject-filter-bar .sf-btn{border-color:var(--acier)}
#features-year-bar .sf-btn{border-color:var(--leman)}

/* Tag-style buttons used inside data-filter-bar / focus-filter-bar dynamic lists */
.sf-btn.sf-tag-data{border-color:var(--perle);color:var(--ardoise);background:#fff;}
.sf-btn.sf-tag-data:hover{background:var(--perle);border-color:var(--perle);color:var(--ardoise);}
.sf-btn.sf-tag-data.active{background:var(--mont-rose);border-color:var(--mont-rose);color:white;}
.sf-btn.sf-tag-tech{border-color:var(--perle);color:var(--ardoise);background:#fff;}
.sf-btn.sf-tag-tech:hover{background:var(--perle);border-color:var(--perle);color:var(--ardoise);}
.sf-btn.sf-tag-tech.active{background:var(--rose);border-color:var(--rose);color:white;}

/* Features-filters wrap: collapsed by default, white panel when open */
#features-filters-wrap.collapsed{display:none}
/* Bare like the actor tab's #status-filters-wrap: no inner box/contour, filters
   sit directly inside the surrounding white panel-inner. */
#features-filters-wrap{margin-top:4px}

/* ═══ Features filter — calm, aligned "table" layout ══════════════════════════
   Each row: a right-aligned UPPERCASE category header (with a small dimension
   colour-dot) in a fixed left column, then quiet value pills. Rows are transparent
   with a hairline divider. The category name now clearly outranks the values.
   Scoped to #features-filters-wrap so the Actors/Status tab is untouched. */
#features-search-row,
.sf-search-row{margin-bottom:14px}
/* Outside the panel now: full width under the summary line, and it keeps its own
   spacing rather than the panel's. */
/* Small, and on the summary line rather than a row of its own. Outside the panel
   it is reachable without opening fifteen facets, which is the point; full width
   on its own row it was also spending 54px of vertical space on a control that
   needs about 300px of horizontal. It sits beside the Filters button now and
   grows to fill whatever the summary text leaves. */
.sf-search-row-top{margin:0;flex:1 1 260px;max-width:420px;min-width:200px}
.sf-search-row-top .sf-search{width:100%;box-sizing:border-box;padding:5px 12px;
  border:1px solid rgba(31,42,55,.16);border-radius:999px;font-size:13px;
  color:var(--ardoise);background:#fff;outline:none}
.sf-search-row-top .sf-search:focus{border-color:var(--canard);box-shadow:0 0 0 3px rgba(0,167,159,.14)}
#features-bib-search{
  width:100%;box-sizing:border-box;padding:11px 16px;
  border:1px solid rgba(31,42,55,.16);border-radius:12px;
  font-size:16px;color:var(--ardoise);background:#fff;outline:none;
  transition:border-color .15s,box-shadow .15s;
}
#features-bib-search:focus{border-color:var(--canard);box-shadow:0 0 0 3px rgba(0,167,159,.14)}
#features-bib-search::placeholder{color:rgba(31,42,55,.45)}

/* Three rows that cost height and said nothing were making this panel 400px, 44%
   of a 900px viewport, before a single filter was touched.
   1. The empty state read "Active: none", a row whose only content was the news
      that there was no news. It appears once something is active.
   2. The hint sits with the search row rather than on a line of its own.
   3. The facet rows pair up on wide screens: the label gutter is a fixed 176px,
      so a single facet leaves most of a 1240px row empty. */
#features-filters-wrap .features-active-filters.is-empty{display:none}
/* On the Reset row now, not a line of its own. */
#features-filters-wrap .sf-actions-row .sf-multi-hint{margin:0;color:var(--ardoise);opacity:.6;font-size:13px;flex:1 1 320px;align-self:center}
#features-filters-wrap .sf-multi-hint{margin:-8px 0 10px 176px;color:var(--ardoise);opacity:.6;font-size:13px}
#features-filters-wrap .sf-bar,
#features-filters-wrap .sf-bar:first-child,
#features-filters-wrap .sf-bar:last-child{
  position:relative;display:flex;flex-wrap:wrap;align-items:center;gap:7px;
  margin:0;padding:7px 0 7px 176px;border-bottom:1px solid rgba(31,42,55,.07);
}
#features-filters-wrap .sf-bar:last-of-type{border-bottom:none}
/* The facet names are column headers for the same data the works table shows, so
   they take that table's header format exactly: 11px, 700, .09em, uppercase,
   --ink-muted (see .bt-sort). They were 13px/800/.055em in full Ardoise, heavier
   than the values beside them and heavier than the same words in the table. */
#features-filters-wrap .sf-label{
  position:absolute;left:0;top:11px;width:160px;text-align:right;
  margin:0;padding:0;border:none;background:none;border-radius:0;
  font-size:11px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:var(--ink-muted);line-height:1.3;white-space:normal;
}
/* No colour dot before the label. The values carry the facet's colour themselves
   now that they are the site's own components, so the dot was a second key for
   something already keyed, and it put a coloured mark in a column of quiet grey
   type. The per-facet dot colours below are kept as declarations only because
   they cost nothing and document the key; they render nothing. */
#features-filters-wrap .sf-btn{
  padding:4px 12px;border-radius:12px;border:1px solid rgba(31,42,55,.16);
  background:#fff;font-size:13.5px;font-weight:600;color:var(--ardoise);
}
#features-filters-wrap .sf-btn:hover{border-color:var(--canard);background:rgba(0,167,159,.07);color:var(--ardoise)}
#features-filters-wrap .sf-count{background:rgba(31,42,55,.06);font-weight:700}
#features-filters-wrap .sf-btn.active .sf-count{background:rgba(255,255,255,.32)}
/* dimension colour-key dots (core visible bars) */
#features-relevance-bar .sf-label::before{background:var(--f-close)}
/* Focus is the one facet with three colours, so its dot carries all three rather
   than a fourth hue that means something else. It was Zinzolin, which now means
   Subject, so the dot was naming the wrong facet. */
#features-mdh-focus-bar .sf-label::before{
  background:linear-gradient(90deg,
    var(--f-focus-m) 0 33.33%,
    var(--f-focus-d) 33.33% 66.66%,
    var(--f-focus-h) 66.66% 100%);
}
#features-stage-bar .sf-label::before{background:var(--f-stage)}
/* Themes dropdown (now under "More filters"): grouped by macro-category, wider so it fits. */
/* "More filters" heading + quiet expander chips */
#features-filters-wrap .sf-collapse{margin:0 8px 8px 0}
#features-filters-wrap .sf-collapse-btn{border:1px solid rgba(31,42,55,.16);color:var(--ardoise);font-weight:700;background:#fff}
#features-filters-wrap .sf-collapse-btn:hover{border-color:var(--canard)}
/* Mobile: stack the category header above its values */
@media (max-width:640px){
  #features-filters-wrap .sf-bar{padding-left:0}
  /* No width and no flex here: the rail label is width:100% at every size now that it is a button, and
     since 2026-09-14 it shares a row with its i (.sf-head), which a full-row flex basis would break. */
  #features-filters-wrap .sf-label{position:static;text-align:left;margin-bottom:6px}
  #features-filters-wrap .sf-multi-hint{margin-left:0}
}
#features-filters-wrap .sf-bar:first-child{margin-top:6px}
#features-filters-wrap .sf-bar:last-child{margin-bottom:6px}

/* ═══ Features filter v2 — three-state chips, grouped themes ═════════════════
   Every filter value is one .sf-chip with three states: resting (white, coloured
   contour), included (.is-inc, filled with the facet colour) and excluded
   (.is-exc, dashed red contour + struck-through label). Each facet carries its
   own colour through the --fc custom property, matching the label chips on the
   cards, so a filter and the pill it selects read as the same dimension.
   Chips and dropdowns are built in JS (renderFeaturesFilterUI); this block is
   the whole visual contract. */

#features-filters-wrap .sf-search-row{display:block;margin:0 0 12px;align-items:stretch}
#features-filters-wrap .sf-multi-hint{margin:0 0 12px;max-width:none}
#features-filters-wrap .features-active-filters{margin-left:0;margin-bottom:14px}

/* Per-facet colour key: every value comes from --f-* in :root, the SAME token
   the card pill for that facet reads. Neither side carries a literal colour, so
   a chip and the pill it selects cannot drift apart. */
#features-filters-wrap .sf-chip,#features-filters-wrap .sf-collapse-btn{--fc:rgba(31,42,55,.22)}
/* Focus is per VALUE, not per facet: the three pillar hues, keyed off the value
   the chip carries in data-arg2. */
#features-filters-wrap .sf-f-focus[data-arg2="M"]{--fc:var(--f-focus-m)}
#features-filters-wrap .sf-f-focus[data-arg2="D"]{--fc:var(--f-focus-d)}
#features-filters-wrap .sf-f-focus[data-arg2="H"]{--fc:var(--f-focus-h)}
#features-filters-wrap .sf-f-close{--fc:var(--f-close)}
#features-filters-wrap .sf-f-stage{--fc:var(--f-stage)}
#features-filters-wrap .sf-f-theme,
#features-filters-wrap .sf-f-subtopic{--fc:var(--f-theme)}
#features-filters-wrap .sf-f-subject{--fc:var(--f-subject)}
#features-filters-wrap .sf-f-method{--fc:var(--f-method)}
#features-filters-wrap .sf-f-data{--fc:var(--f-data)}
#features-filters-wrap .sf-f-platform{--fc:var(--f-platform)}
#features-filters-wrap .sf-f-type{--fc:var(--f-type)}
#features-filters-wrap .sf-f-year{--fc:var(--f-year)}
#features-filters-wrap .sf-f-score{--fc:var(--f-score)}
#features-filters-wrap .sf-f-affil{--fc:var(--f-affil)}
#features-filters-wrap .sf-f-lab{--fc:var(--f-lab)}
#features-filters-wrap .sf-f-actor{--fc:var(--f-actor)}

/* ── The chip ── */
/* Same format as the value pills on the cards (.ax-pill / .bib-topic): a 20%
   tint of the facet colour, a 55% border of it, a full round, 12px at 700. The
   chips were white with a full-strength contour at 13.5px/600, so the same value
   looked like one thing in the filter and another on the card it filters to.
   Padding stays larger than a card pill's: this one is a target, not a label.
   The .is-inc filled state below is untouched, so the tri-state still reads and
   the contrast work on the filled hues still holds. */
#features-filters-wrap .sf-chip{
  display:inline-flex;align-items:center;gap:6px;
  /* The works table's rendered metrics, same as .ax-pill above. min-height
     carries the 24px WCAG 2.2 target size, which the pill does not reach on its
     own at this size. */
  padding:3px 8px;min-height:24px;
  /* Colour as an edge, not a wash: see the note on .ax-pill. The filter and the
     pill it selects have to be one object, so the two rules move together. */
  border-radius:3px 999px 999px 3px;
  border:1px solid color-mix(in srgb, var(--fc, var(--perle)) 42%, #fff);
  border-left:3px solid var(--fc, var(--perle));
  background:#fff;color:var(--ardoise);
  font-family:var(--sans);font-size:12px;font-weight:600;line-height:1.3;
  cursor:pointer;position:relative;overflow:hidden;
  transition:background .15s,border-color .15s,color .15s,opacity .15s;
}
#features-filters-wrap .sf-chip:hover{background:color-mix(in srgb, var(--fc, var(--perle)) 14%, #fff)}
#features-filters-wrap .sf-chip.bt-close::after,
#features-filters-wrap .sf-chip.sf-f-focus:has(.bt-pil)::after{display:none}
#features-filters-wrap .sf-chip .sf-count{
  margin-left:0;padding:0 6px;border-radius:12px;min-width:16px;text-align:center;
  font-size:12px;font-weight:700;background:rgba(31,42,55,.07);color:var(--ardoise);
  /* Tabular figures: the counts change on every click, and proportional digits
     make the whole row of chips shift width as they do. */
  font-variant-numeric:tabular-nums;
}
/* Size says rank. The three rows above the fold are the facets a reader is meant
   to start from, so they keep the table's 12px; everything behind the More
   filters button is a narrowing cut and sits one step down at 11.5px. Nothing
   here goes below the 24px WCAG 2.2 target, which min-height carries. */
#features-filters-wrap .sf-more-wrap .sf-chip{font-size:11.5px}
/* ONE selection signal for every facet: a teal ring, plus a light wash of the
   facet's own colour. It used to be a full fill here and a ring on closeness and
   the pillar badges, because those two carry their own fill and could not take
   another - so "chosen" looked like two different things depending on which row
   you were in.
   The ring also retires 22 lines of per-hue contrast special-casing: a filled
   chip had to switch its label between white and Ardoise depending on whether
   the hue could carry white at 4.5:1, and two hues (Carotte, Leman) could carry
   neither. On a wash at 16% every label stays Ardoise and clears AA on all
   fifteen. */
#features-filters-wrap .sf-chip.is-inc{
  background:color-mix(in srgb, var(--fc, var(--perle)) 16%, #fff);
  border-color:var(--fc, var(--perle));
  box-shadow:0 0 0 3px rgba(0,167,159,.4);
  color:var(--ardoise);
}
#features-filters-wrap .sf-chip.is-inc .sf-count{background:rgba(31,42,55,.16);color:var(--ardoise)}
#features-filters-wrap .sf-chip.is-exc{
  background:#fff;border-color:var(--groseille);border-style:dashed;color:var(--groseille);
}
#features-filters-wrap .sf-chip.is-exc .sf-chip-lbl{text-decoration:line-through}
#features-filters-wrap .sf-chip.is-exc .sf-count{background:rgba(181,31,31,.1);color:var(--groseille)}
/* Nothing left to show under the current selection: greyed, still clickable. */
/* Closeness chips ARE the table's badge, not a pill that resembles it: 11px,
   700, .05em, uppercase, 5px corners, filled for Direct, solid outline for
   Adjacent, dashed for Transferable. Values are the same three the table draws.
   The include and exclude states are restated below at the same specificity,
   because these rules would otherwise beat them and the chip would stop showing
   whether it is selected. Include is a ring rather than a fill: Direct is
   already filled, so a fill cannot carry the state. */
#features-filters-wrap .sf-chip.bt-close{
  font-size:11.5px;font-weight:700;letter-spacing:.03em;text-transform:none;
  padding:3px 8px;border-radius:5px;white-space:nowrap;min-height:24px;
}
#features-filters-wrap .sf-chip.bt-close.is-direct{background:var(--ardoise);color:#fff;border:1.5px solid var(--ardoise)}
#features-filters-wrap .sf-chip.bt-close.is-adjacent{background:#fff;color:var(--ardoise);border:1.5px solid var(--ardoise)}
#features-filters-wrap .sf-chip.bt-close.is-infrastructure{background:#fff;color:var(--ink-muted);border:1.5px dashed rgba(69,58,76,.42)}
/* NA is a fourth closeness value and had no variant of its own, so it fell back
   to the generic chip and looked like a different kind of thing sitting under
   three badges. Same badge, faintest step: it is the end of the same ladder. */
#features-filters-wrap .sf-chip.bt-close.is-NA,
#features-filters-wrap .sf-chip.bt-close.is-na{
  background:#fff;color:var(--ink-muted);border:1.5px dotted rgba(69,58,76,.34)}
.bt-close.is-NA,.bt-close.is-na{background:#fff;color:var(--ink-muted);border:1.5px dotted rgba(69,58,76,.34)}
#features-filters-wrap .sf-chip.bt-close.is-direct .sf-count{background:rgba(255,255,255,.28);color:#fff}
#features-filters-wrap .sf-chip.bt-close.is-inc{box-shadow:0 0 0 3px rgba(0,167,159,.4)}
#features-filters-wrap .sf-chip.bt-close.is-exc{
  background:#fff;color:var(--groseille);border:1.5px dashed var(--groseille);box-shadow:none;
}
#features-filters-wrap .sf-chip.bt-close.is-exc .sf-count{background:rgba(181,31,31,.1);color:var(--groseille)}
/* The pillar badge inside a focus chip. Same 21px square, same corner and the
   same pillar fills as .bt-pil in the works table; the chip's own padding comes
   down so it does not grow taller than the other facets' chips. */
/* A pillar chip is the badge and its count, with no pill behind them: the badge
   already carries the colour and the shape, so a tinted rounded box around it was
   a second container for one object. Only the M/D/H chips lose the pill; "Out of
   scope" has no badge and keeps one. */
#features-filters-wrap .sf-chip.sf-f-focus:has(.bt-pil){
  background:none;border-color:transparent;padding:1px 6px 1px 1px;
}
#features-filters-wrap .sf-chip.sf-f-focus:has(.bt-pil):hover{background:rgba(31,42,55,.05)}
#features-filters-wrap .sf-chip.sf-f-focus.is-inc:has(.bt-pil){
  background:none;box-shadow:0 0 0 3px rgba(0,167,159,.4);border-color:transparent;
}
#features-filters-wrap .sf-chip.sf-f-focus.is-exc:has(.bt-pil){
  background:none;border-color:transparent;box-shadow:none;
}
#features-filters-wrap .sf-chip.sf-f-focus.is-exc:has(.bt-pil) .bt-pil{opacity:.45}
#features-filters-wrap .sf-chip.sf-f-focus{padding-top:1px;padding-bottom:1px;padding-left:4px}
#features-filters-wrap .sf-chip-pil{flex:none}
/* A value no work carries is not a filter, it is a disappointment waiting to be
   clicked. Hidden rather than dimmed. The counts are live, so a chip returns as
   soon as another selection gives it works again. */
#features-filters-wrap .sf-chip.sf-zero{display:none}
#features-filters-wrap .sf-chip.sf-zero.is-inc,#features-filters-wrap .sf-chip.sf-zero.is-exc{opacity:.8}
#features-filters-wrap .sf-chip:focus-visible{outline:2px solid var(--canard);outline-offset:2px}
/* Same hover tooltip the old .sf-btn values had. */
#features-filters-wrap .sf-chip[data-tip]{cursor:pointer}
#features-filters-wrap .sf-chip[data-tip]:hover::after,
#features-filters-wrap .sf-chip[data-tip]:focus-visible::after{
  content:attr(data-tip);position:absolute;left:0;top:calc(100% + 6px);z-index:300;
  width:max-content;max-width:320px;background:var(--ardoise);color:#fff;
  font-size:13px;font-weight:400;text-transform:none;letter-spacing:normal;line-height:1.45;
  padding:9px 11px;border-radius:12px;box-shadow:0 6px 20px rgba(0,0,0,.22);
  white-space:normal;pointer-events:none;text-decoration:none;
}

/* ── Themes: ONE collapsible section holding all 17 themes. The taxonomy's three
      groups are non-interactive sub-headers inside the single open panel. ── */
/* Themes as one more dropdown, not a section.
   It used to sit under the ten secondary facets on a row of its own, opening in
   place, which cost a full line before it was even used. Inside .sf-more-wrap it
   flows beside them and opens as an overlay like they do. The rules below only
   apply there, so the same markup elsewhere is untouched. */
/* The wrap carries the divider and the padding that .sf-more-row used to, and the
   row becomes display:contents so its ten dropdowns and the works count become
   items of the wrap directly. Without that the row is a full-width flex item and
   Themes is pushed onto a line of its own, which is the thing being fixed. */
/* The disclosure that holds the ten secondary facets and Themes. Its summary is
   a chip like the ones inside it, so the row reads as one more control rather
   than as a section heading. */
.sf-more-fold{margin-top:6px;padding-top:12px;border-top:1px solid rgba(31,42,55,.1)}
.sf-more-summary{display:inline-flex;align-items:center;gap:6px;cursor:pointer;list-style:none;
  padding:6px 13px;border-radius:12px;border:1px solid rgba(31,42,55,.16);background:#fff;
  font-family:var(--sans);font-size:13.5px;font-weight:700;color:var(--ardoise);width:max-content}
.sf-more-summary::-webkit-details-marker{display:none}
.sf-more-summary::before{content:"▸";display:inline-block;font-size:11px;opacity:.6;transition:transform .15s}
.sf-more-fold[open] > .sf-more-summary::before{transform:rotate(90deg)}
.sf-more-summary:hover{background:rgba(31,42,55,.05)}
.sf-more-count{background:var(--canard);color:#fff;border-radius:999px;font-size:12px;font-weight:700;
  padding:1px 7px;min-width:8px;text-align:center}
.sf-more-count:empty{display:none}
/* Inside the fold the divider and the top padding belong to the fold, not the row. */
.sf-more-fold .sf-more-wrap{margin-top:10px;padding-top:0;border-top:none}
.sf-more-wrap{display:flex;flex-wrap:wrap;align-items:center;gap:8px;
  margin-top:6px;padding:15px 0 4px;border-top:1px solid rgba(31,42,55,.1)}
.sf-more-wrap .sf-more-row{display:contents}
.sf-more-wrap .sf-groups{margin:0;border-top:none;position:relative}
.sf-more-wrap .sf-group{border-bottom:none}
/* Same metrics as .sf-collapse-btn beside it, or Themes sits 4px shorter and
   fails to line up with the row it has just joined. */
.sf-more-wrap .sf-group-btn{width:auto;display:inline-flex;align-items:center;gap:6px;
  padding:6px 13px;border-radius:12px;border:1px solid rgba(31,42,55,.16);background:#fff}
.sf-more-wrap .sf-group-btn:hover{background:rgba(31,42,55,.05)}
.sf-more-wrap .sf-group-title{font-size:13.5px;font-weight:700;letter-spacing:0;text-transform:none}
.sf-more-wrap .sf-group-body{position:absolute;z-index:40;left:0;margin-top:6px;
  min-width:320px;max-width:min(680px,90vw);max-height:52vh;overflow-y:auto;
  background:#fff;border:1px solid rgba(31,42,55,.16);border-radius:12px;
  box-shadow:0 8px 26px rgba(31,42,55,.14);padding:12px 14px}
.sf-groups{margin:2px 0 4px;border-top:1px solid rgba(31,42,55,.09)}
.sf-group{border-bottom:1px solid rgba(31,42,55,.09)}
.sf-group-btn{
  display:flex;align-items:center;gap:10px;width:100%;
  padding:13px 2px;background:none;border:none;cursor:pointer;text-align:left;
  font-family:var(--sans);
}
.sf-group-caret{display:inline-block;width:13px;text-align:center;font-size:15px;line-height:1;color:var(--ardoise);opacity:.6}
.sf-group-title{
  font-size:13px;font-weight:800;letter-spacing:.075em;text-transform:uppercase;color:var(--ardoise);
}
.sf-group-btn:hover .sf-group-title,.sf-group-btn:hover .sf-group-caret{color:var(--canard);opacity:1}
.sf-group.has-sel .sf-group-title{color:var(--acier)}
.sf-group-count{
  background:var(--acier);color:#fff;border-radius:999px;
  font-size:12px;font-weight:700;padding:1px 7px;min-width:8px;text-align:center;
}
.sf-group-count:empty{display:none}
.sf-group-body{display:none;flex-wrap:wrap;gap:8px;padding:0 0 16px 22px}
.sf-group.open .sf-group-body{display:flex}
/* Sub-header inside the single Themes panel: reads as a divider, not a control. */
.sf-group-sub{
  flex:0 0 100%;width:100%;margin:6px 0 -1px;
  font-size:12px;font-weight:800;letter-spacing:.075em;text-transform:uppercase;
  color:var(--ardoise);opacity:.42;
}
.sf-group-sub:first-child{margin-top:0}
/* A selected theme's subtopics: full-width row right under that theme's chip. */
.sf-subrow{
  flex:0 0 100%;width:100%;display:flex;flex-wrap:wrap;gap:6px;
  margin:0 0 2px 10px;padding:2px 0 4px 12px;border-left:2px solid rgba(79,143,204,.4);
}
#features-filters-wrap .sf-subrow .sf-chip{font-size:13px;padding:3px 10px;border-style:dashed}
#features-filters-wrap .sf-subrow .sf-chip.is-inc{border-style:solid}
/* Method dropdown: the declared method hierarchy, indented under its parent.
   Same geometry as .sf-subrow, but a child method is a full filter value in its
   own right (not a narrowing subtopic), so it keeps the solid chip contour and
   the dashed border stays reserved for the excluded state. */
.sf-treerow{
  width:100%;display:flex;flex-wrap:wrap;gap:6px;
  margin:0 0 2px 4px;padding:2px 0 4px 10px;border-left:2px solid rgba(79,143,204,.4);
}
#features-filters-wrap .sf-treerow .sf-chip{font-size:13px;padding:3px 10px}

/* ── "More filters" row: one dropdown per remaining facet ── */
.sf-more-row{
  display:flex;flex-wrap:wrap;align-items:center;gap:8px;
  margin-top:6px;padding:15px 0 4px;border-top:1px solid rgba(31,42,55,.1);
}
/* One control line: Tech & method, Data type, then the More-filters button.
   The row owns the divider and the padding; the two rows inside it are
   display:contents so their dropdowns are items of this flex row directly, and
   all three controls sit on one line. */
.sf-controls-row{position:relative;display:flex;flex-wrap:wrap;align-items:center;gap:8px;
  margin-top:6px;padding:15px 0 4px;border-top:1px solid rgba(31,42,55,.1)}
.sf-controls-row .sf-primary-row{display:contents}
.sf-controls-row > .sf-more-fold{margin:0;padding:0;border-top:none;display:flex;align-items:center}
/* One height for all three controls on the row. The details wrapper and the
   .sf-collapse wrappers box their contents differently, which left the
   More-filters pill 5px below the two beside it; pinning the height is what
   makes the three read as one set. */
.sf-controls-row .sf-collapse-btn,
.sf-controls-row .sf-more-summary{height:31px;box-sizing:border-box}
/* The fold opens as an OVERLAY, the way every facet dropdown beside it already
   does, rather than expanding in flow. In flow it pushed the panel from 166px
   to 219px and moved the table under it on every open; as an overlay the filter
   bar is one line high whatever is open, which is the whole point of the fold. */
.sf-controls-row > .sf-more-fold[open] > .sf-more-wrap{
  position:absolute;left:0;top:100%;width:100%;z-index:30;box-sizing:border-box;
  margin:0;padding:12px;border:1.5px solid var(--perle);border-radius:12px;background:#fff;
  box-shadow:0 6px 22px rgba(0,0,0,.12);max-height:min(58vh,420px);overflow-y:auto}
/* .sf-collapse carries margin-bottom:8px from the stacked layout it was
   written for. On a centred flex row that 8px is part of its margin box, so
   the two dropdowns sat 4px ABOVE the More-filters pill beside them.
   position:relative is load-bearing and was dropped once by an earlier version
   of this rule: without it the absolutely-positioned .sf-collapse-body anchors
   to .sf-controls-row instead of its own button, and opens ON TOP of the button
   that opened it, so there is nothing left to click to close it. The explicit
   top/left below pin it under the button rather than at whatever static
   position the flex line gives it. */
#features-filters-wrap .sf-controls-row .sf-collapse{margin:0;position:relative;display:flex;align-items:center}
#features-filters-wrap .sf-controls-row .sf-collapse > .sf-collapse-body{top:100%;left:0;margin-top:6px}
.sf-more-row .sf-collapse{margin:0;position:relative;display:inline-block;vertical-align:top}
#features-filters-wrap .sf-more-row .sf-collapse-btn{
  display:inline-flex;align-items:center;gap:6px;
  padding:6px 13px;border-radius:12px;border:1px solid var(--fc);
  background:#fff;color:var(--ardoise);
  font-family:var(--sans);font-size:13.5px;font-weight:700;cursor:pointer;
}
#features-filters-wrap .sf-more-row .sf-collapse-btn:hover{background:rgba(31,42,55,.05)}
/* The active heading used to fill with the facet's colour at full strength, with
   white type, plus two per-facet exceptions where white failed on a pale hue.
   That is what turned DATA TYPE into a Vert d'eau bar on one click. It is from
   the horizontal-bar layout, which is gone; the heading now keeps its own colour
   and changes nothing. */
#features-filters-wrap .sf-more-row .sf-collapse-count{background:rgba(31,42,55,.2)}
#features-filters-wrap .sf-more-row .sf-collapse.has-sel .sf-collapse-count{background:rgba(255,255,255,.32)}
#features-filters-wrap .sf-more-row .sf-collapse-caret{font-size:13px;opacity:.6}
#features-filters-wrap .sf-more-row .sf-collapse-body{
  display:none;position:absolute;z-index:40;left:0;margin-top:6px;
  min-width:240px;max-width:420px;max-height:320px;overflow-y:auto;
  background:#fff;border:1px solid rgba(31,42,55,.16);border-radius:12px;
  padding:10px;box-shadow:0 8px 26px rgba(0,0,0,.14);
}
#features-filters-wrap .sf-more-row .sf-collapse.open .sf-collapse-body{display:flex;flex-wrap:wrap;gap:6px}
#features-filters-wrap .sf-more-row .sf-check-search{flex:0 0 100%;width:100%;margin:0 0 4px}
/* Beside Reset filters now, so it reads as a result rather than as one more
   facet dropdown, which is where it landed between Author and Themes. */
/* The count now sits in the summary line beside the Filters button. */
.topnav-filter-inner > .sf-shown-count{margin:0;align-self:center;font-size:13px;
  font-weight:700;color:var(--ink-muted);flex:none;white-space:nowrap}
.topnav-filter-inner > .sf-shown-count:empty{display:none}
.sf-shown-count{
  margin-left:auto;font-size:13.5px;font-weight:700;color:var(--ardoise);opacity:.7;white-space:nowrap;
}

/* ── Active-filter chips: new facets, exclusions, Clear all ── */
.af-subtopic{color:var(--acier);border-color:var(--acier)}
.af-platform{color:var(--canard);border-color:var(--canard)}
.af-chip{display:inline-flex;align-items:center;gap:5px}
.af-chip.is-exc{color:var(--groseille);border-color:var(--groseille);border-style:dashed}
.af-chip.is-exc .af-lbl{text-decoration:line-through}
.af-no{font-weight:800}
.af-clear{
  background:none;border:none;padding:4px 6px;cursor:pointer;
  font-family:var(--sans);font-size:13px;font-weight:700;
  color:var(--groseille);text-decoration:underline;
}
.af-clear:hover{color:var(--ardoise)}

@media (max-width:640px){
  .sf-group-body{padding-left:8px}
  .sf-subrow{margin-left:0;padding-left:8px}
  .sf-shown-count{margin-left:0;flex:0 0 100%}
  /* Repeated at matching specificity: the count now lives in .sf-actions-row,
     whose rule is (0,2,0) and would otherwise pin flex:none at every width and
     keep the count on the same line as Reset on a phone. */
  .sf-actions-row .sf-shown-count{margin-left:0;flex:0 0 100%}
}

/* Publication / Project visual badge on cards */
/* Type badge: one dimension colour (Ardoise navy) for every type; the word says which. */
.proj-type-badge{display:inline-block;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:0.4px;padding:3px 9px;border-radius:999px;border:1.5px solid var(--ardoise);color:var(--ardoise);background:#fff;margin-right:8px}
.proj-type-badge.is-project{border-color:var(--ardoise);color:var(--ardoise)}
.proj-type-badge.is-reference{border-color:var(--ardoise);color:var(--ardoise)}

/* Tighter card layout: title + tags only, click to open modal */
.rcard-compact{cursor:pointer;transition:box-shadow 0.15s, transform 0.15s;}
.rcard-compact:hover{box-shadow:0 8px 20px rgba(0,0,0,.12);transform:translateY(-2px);}
/* Clamped to two lines. Titles run 13-151 characters, so an unclamped title was
   1-4 lines and the third-largest contributor to the card-height spread. The
   modal carries the full title. */
/* Type metrics match .rcard-name and .rcard-unit exactly, so a Publications card
   and an Actors card are the same card. They had drifted: 6px under the title
   against the base's 3px, and a 13.5px unit line with 10px under it against
   13px and 6px. Only the two structural parts are kept, the two-line clamp above
   and the flex row below, which hold the title and the type on one baseline. */
.rcard-compact .rcard-name{font-size:16px;line-height:1.25;margin-bottom:3px;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
.rcard-compact .rcard-unit{font-size:13px;color:var(--ardoise);margin-bottom:6px;display:flex;justify-content:space-between;align-items:baseline;gap:10px;}
.rcard-type{flex:none;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:0.4px;color:var(--ardoise);white-space:nowrap;}
.rcard-authors{font-size:13px;color:var(--ardoise);margin:-4px 0 10px;line-height:1.4;}
.rcard-authors strong{color:var(--ardoise);font-weight:700;}
.auth-more{background:none;border:none;color:var(--canard);font-size:13px;font-weight:700;cursor:pointer;padding:0 2px;text-decoration:underline}
.rcard-compact .rcard-tags{display:flex;flex-wrap:wrap;gap:6px;align-items:center;}

/* ── 10. CHARTS + MODALS (bib · chart-enlarge · researcher) ──────────────── */
   /* Pop-up dialogs: the publication detail modal, the click-to-enlarge chart, the researcher profile. */

/* Modal overlay for the full publication / project info */
.bib-modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.45);display:none;align-items:flex-start;justify-content:center;z-index:9998;padding:48px 16px;overflow-y:auto;}
.bib-modal-overlay.open{display:flex;}
.bib-modal{background:#fff;border-radius:12px;max-width:820px;width:100%;padding:34px 40px 38px;box-shadow:0 16px 40px rgba(0,0,0,0.25);position:relative;color:var(--ardoise);}
/* The same round close button as the actor profile, on a light ground (2026-09-14). */
.bib-modal-close{position:absolute;top:12px;right:12px;width:36px;height:36px;border-radius:50%;background:rgba(69,58,76,.08);border:none;font-size:22px;color:var(--ardoise);cursor:pointer;line-height:1;display:flex;align-items:center;justify-content:center;}
.bib-modal-close:hover{background:rgba(69,58,76,.16);}
.bib-modal-close:focus-visible{outline:2px solid var(--canard);outline-offset:2px;}
/* On phones the title and the type badge stack, so the title keeps the width (2026-09-14). */
@media (max-width:600px){
  .bib-modal-overlay{padding:16px 10px}
  .bib-modal{padding:26px 18px 28px}
  .bib-modal-head{flex-direction:column;gap:8px}
}
.bib-modal-title{font-size:21px;font-weight:700;color:var(--ardoise);margin-bottom:10px;line-height:1.35;padding-right:34px;}
.bib-modal-meta{font-size:13.5px;color:var(--ardoise);margin-bottom:20px;}
.bib-modal-author-link{font-family:var(--sans);font-size:13.5px;font-weight:700;color:var(--canard);background:none;border:none;cursor:pointer;padding:0;margin-left:6px}
.bib-modal-author-link:hover{text-decoration:underline}
.bib-modal-tags{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:8px;}
/* No size override. The modal used to draw facet pills at 13px/4px 11px, one
   step larger than the same values on the card that opened it. */
.bib-modal-body{font-size:16px;line-height:1.8;color:var(--ardoise);}
/* A long follow-up pill ran out of the window (2026-09-14). */
.bib-modal .ax-pill,.bib-modal .bib-topic{white-space:normal;max-width:100%}
.bib-modal-topics{display:flex;flex-wrap:wrap;gap:8px;margin:4px 0 6px;}
/* ── ONE facet label, everywhere ──────────────────────────────────────────────
   .bib-topic and .ax-pill are the SAME component. They were not: cards drew a
   tinted pill keyed to the facet, the Works table drew a neutral grey pill with
   Stage, Data, Method and Subject all passing an empty class (so four different
   facets looked identical), and the modals drew a third form (.lbl-*) on the
   pre-key colour assignments. Four naming schemes for the same values.
   Hue arrives through --px from the --f-* key in :root. Anything without an
   f-* class falls back to Perle, which is how an unkeyed value announces itself.
   Size is set per surface further down: 13px here, 12px in a table row and on a
   publication card, where density matters more than presence. */
/* Colour as a 3px left EDGE, not a wash. Same change, same reasons, as .ax-pill
   above; the two move together or the table and the filter that drives it stop
   looking like one system, which is the whole point of the shared --px key. */
.bib-topic{font-size:13px;font-weight:600;padding:5px 11px;
  border-radius:3px 999px 999px 3px;
  background:#fff;
  border:1px solid var(--border);
  border-color:color-mix(in srgb, var(--px, var(--perle)) 42%, #fff);
  border-left:3px solid var(--px, var(--perle));
  color:var(--ardoise);}

/* The facet key, shared by .bib-topic and .ax-pill. Same tokens the filter chips
   and the distribution charts read, so a value cannot look like one facet here
   and another facet there. */
.f-close{--px:var(--f-close)}
.f-stage{--px:var(--f-stage)}
.f-theme,.f-subtopic{--px:var(--f-theme)}
.f-subject{--px:var(--f-subject)}
.f-method{--px:var(--f-method)}
.f-data{--px:var(--f-data)}
.f-platform{--px:var(--f-platform)}
.f-type{--px:var(--f-type)}
.f-year{--px:var(--f-year)}
.f-affil{--px:var(--f-affil)}
.f-lab{--px:var(--f-lab)}
.f-actor{--px:var(--f-actor)}
.f-score{--px:var(--f-score)}

/* FORM modifiers, not colour ones. A subtopic is a child of its theme and a
   key term is free text, so both are dashed; they keep whatever hue their f-*
   class gives them. */
.bib-kw{border-style:dashed;opacity:.92;}

/* ── Modal header: clickable title + "View" after it, Type badge on the right ── */
.bib-modal-head{display:flex;justify-content:space-between;align-items:flex-start;gap:14px;margin-bottom:14px;padding-right:26px;}
.bib-modal-head-main{flex:1 1 auto;min-width:0;}
.bib-modal-title{font-size:20px;font-weight:700;line-height:1.35;color:var(--ardoise);text-decoration:none;}
a.bib-modal-title:hover{text-decoration:underline;color:var(--canard);}
.bib-modal-view{display:inline-block;margin-left:8px;font-size:13.5px;font-weight:700;color:var(--rouge-suisse,#b51f1f);text-decoration:none;white-space:nowrap;border:1px solid rgba(181,31,31,.22);border-radius:999px;padding:2px 11px;vertical-align:middle;}
.bib-modal-view:hover{background:rgba(181,31,31,.07);}
.bib-modal .bib-modal-type{flex:0 0 auto;background:#fff;border:1.5px solid var(--perle);color:var(--ardoise);font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;padding:5px 11px;border-radius:999px;white-space:nowrap;}

/* ── Labels table: one readable row per facet ── */
.bib-label-table{width:100%;border-collapse:collapse;margin:10px 0 4px;}
.bib-label-table th{text-align:right;vertical-align:top;width:152px;padding:10px 16px 10px 0;font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--ardoise);opacity:.6;white-space:nowrap;border-top:1px solid rgba(31,42,55,.08);}
.bib-label-table td{padding:9px 0;border-top:1px solid rgba(31,42,55,.08);vertical-align:top;}
.bib-label-table tr:first-child th,.bib-label-table tr:first-child td{border-top:none;}
.bib-label-table .sec-note{display:block;font-weight:600;text-transform:none;letter-spacing:normal;opacity:.85;font-size:12px;}
.bib-label-table .bib-topic{margin:0 6px 7px 0;}
/* A theme with its subtopics nested beside it, kept together as one cluster. */
.bib-label-table .th-block{display:inline-flex;flex-wrap:wrap;align-items:center;gap:5px;margin:0 12px 7px 0;}
.bib-label-table .th-block .bib-topic{margin:0;}
.th-sub{border-style:dashed;font-weight:500;opacity:.85;}
/* The .lbl-* names the modals use are kept as aliases onto the same key rather
   than re-plumbed through every call site. They used to set their own colours,
   on the PRE-key assignments: lbl-focus was Zinzolin (which now means Subject)
   and lbl-method was Rose (now reserved for the M pillar), so the modal was
   actively contradicting the filter beside it. */
.lbl-close{--px:var(--f-close)}
.lbl-focus{--px:var(--f-focus-d)}
.lbl-stage{--px:var(--f-stage)}
.lbl-data{--px:var(--f-data)}
.lbl-method{--px:var(--f-method)}
.lbl-rel{--px:var(--f-score);font-weight:800;}
/* "Built at EPFL" is the ONE filled chip in the table, in EPFL Canard: an artifact the
   work CONTRIBUTED must not read like the models and datasets it merely consumed. */
.lbl-built{background:var(--canard);color:#fff;border-color:var(--canard);}
/* Built at EPFL is now a FLAG rather than its own row: the artifact stays in Models &
   tools / Datasets & sources and is marked in place, so a reader sees what was used and
   what was produced in one list. Marron, so "contributed" reads as its own category rather
   than borrowing the rouge that already means EPFL institutional across the site. */
.bib-topic.is-built{border-color:var(--marron);border-left-color:var(--marron);color:var(--marron);background:rgba(91,52,40,.07)}
.built-mark{font-size:12px;font-weight:800;letter-spacing:.06em;background:var(--marron);color:#fff;border-radius:4px;padding:1px 4px;margin-left:4px;vertical-align:1px}
/* "Data used" is a sentence, not a label, so it is set as prose rather than a chip: the
   row a reader falls back on when the work analysed material that has no name. */
.bib-data-desc{display:block;font-size:13.5px;line-height:1.5;color:var(--ardoise);opacity:.9;}
/* The same labels table on the compact grid card, scaled down to fit. */
.rcard-labels{margin-top:8px;}
.rcard-labels .bib-label-table th{width:112px;padding:6px 10px 6px 0;font-size:12px;}
.rcard-labels .bib-label-table td{padding:5px 0;}
.rcard-labels .bib-topic{font-size:12px;padding:3px 8px;margin:0 4px 4px 0;border-width:1px;}
/* A long label wraps inside the card instead of running past its edge ("Blind waterma", B14, 2026-09-14). */
.rcard-labels .bib-topic{white-space:normal;max-width:100%;overflow-wrap:anywhere}
.rcard-labels .th-block{gap:3px;margin:0 8px 4px 0;}
.rcard-labels .bib-data-desc{font-size:13px;line-height:1.45;}
.rcard-labels .sec-note{display:none;}
.bib-modal-section-lbl .sec-note{font-weight:600;text-transform:none;letter-spacing:normal;opacity:.8;font-size:12px;}
.bib-modal-body p{margin:0 0 14px;}
.bib-modal-section-lbl{font-size:13px;font-weight:800;text-transform:uppercase;letter-spacing:0.6px;color:var(--ardoise);opacity:.62;margin-top:26px;margin-bottom:12px;padding-top:20px;border-top:1px solid rgba(31,42,55,.09);}

/* Publications & Projects headline stats */
.features-headline{margin:16px 0 24px}
.features-headline-blurb{margin:0;padding:14px 18px;background:#fff;border:1px solid var(--border);border-left:4px solid var(--zinzolin);border-radius:12px;font-size:16.5px;line-height:1.6;color:var(--ardoise)}
.features-headline-blurb strong{color:var(--zinzolin);font-weight:700}
.features-headline-stat{flex:1;min-width:120px;text-align:center;padding:6px 10px;border-right:1px solid var(--border)}
.features-headline-stat:last-child{border-right:none}
.features-headline-num{font-size:28px;font-weight:700;color:var(--ardoise);line-height:1.1}
.features-headline-lbl{font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px;color:var(--ardoise);margin-top:4px}
/* The "N works" count above the publications grid (was inline in features.html). */
.features-bib-count{font-size:16px;color:var(--ardoise);font-weight:600;margin:6px 0 14px}
.features-bib-count strong{font-size:18.5px;font-weight:800;color:var(--canard)}
.features-bib-count .fbc-total{opacity:.6;font-weight:600}

/* Floating popover for "see all" on chart click */
.chart-list-popover{position:fixed;z-index:9999;background:#fff;border:1px solid var(--border);border-radius:12px;box-shadow:0 8px 28px rgba(0,0,0,0.18);padding:14px 16px;max-width:380px;max-height:60vh;overflow-y:auto;font-size:15px;color:var(--ardoise)}
.chart-list-popover-title{font-weight:700;color:var(--ardoise);font-size:15px;margin-bottom:8px;padding-bottom:6px;border-bottom:1px solid var(--border)}
.chart-list-popover-close{position:absolute;top:8px;right:10px;cursor:pointer;border:none;background:transparent;font-size:17.5px;color:var(--ardoise);line-height:1}
.chart-list-popover ul{list-style:none;padding:0;margin:0}
.chart-list-popover li{padding:4px 0;border-bottom:1px dashed var(--border);line-height:1.45}
.chart-list-popover li:last-child{border-bottom:none}
/* The card is no longer one big button (C4 A, 2026-09-14): a bar opens its works and the Expand button
   in the title opens the chart full size, instead of "Click to enlarge" under all nine charts. */
.chart-card h3{display:flex;align-items:center;gap:8px}
.chart-card h3 .vm-expand{float:none;flex:none;margin-left:auto;text-transform:none;letter-spacing:normal}
.chart-note{margin:8px 0 0;font-size:13px;line-height:1.45;color:var(--ink-muted)}
.chart-note[hidden]{display:none}
/* MDH closeness as one strip across the row (C7 A). */
.chart-grid > .chart-card-strip{grid-column:1/-1}
.close-strip .cs-bar{display:flex;gap:3px;height:36px}
.close-strip .cs-seg{min-width:6px;border:none;border-radius:4px;padding:0 10px;text-align:left;
  font-family:var(--sans);font-size:13.5px;font-weight:700;cursor:pointer}
.close-strip .cs-seg:hover{filter:brightness(.93)}
.close-strip .cs-seg:focus-visible{outline:2px solid var(--canard);outline-offset:2px}
.close-strip .cs-key{list-style:none;margin:12px 0 0;padding:0;display:flex;flex-wrap:wrap;gap:6px 24px;
  font-size:13.5px;color:var(--ink-body)}
.close-strip .cs-key li{display:flex;align-items:center;gap:8px}
.close-strip .cs-key b{font-weight:700;color:var(--ardoise)}
.close-strip .cs-sw{width:12px;height:12px;border-radius:3px;flex:none}
/* MDH at a glance (C14 A, 2026-09-14): closeness across the top, focus and stage side by side under it,
   each a row per value out of every work in view (C12 B, C13 B). */
.chart-card-glance .glance{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:28px 48px}
.chart-card-glance .glance-close{grid-column:1/-1}
.chart-card-glance .glance-sec h4{display:flex;align-items:center;gap:8px;margin:0 0 12px;font-family:var(--sans);
  font-size:12px;font-weight:700;letter-spacing:.12em;line-height:1.3;text-transform:uppercase}
.share-rows{display:grid;gap:4px}
.sr-row{display:grid;grid-template-columns:minmax(92px,128px) minmax(0,1fr) auto;gap:12px;align-items:center;
  width:calc(100% + 12px);margin:0 -6px;padding:5px 6px;border:none;border-radius:6px;background:none;text-align:left;
  font-family:var(--sans);font-size:13.5px;line-height:1.3;color:var(--ardoise);cursor:pointer}
.sr-row:hover{background:rgba(69,58,76,.06)}
.sr-row:focus-visible{outline:2px solid var(--canard);outline-offset:1px}
.sr-track{height:20px;border-radius:4px;background:#F2F0F1;overflow:hidden}
.sr-fill{display:block;height:100%;border-radius:4px}
.sr-num{min-width:64px;font-size:13px;color:var(--ink-muted);text-align:right;white-space:nowrap}
.sr-num b{font-weight:700;color:var(--ardoise)}
@media (max-width:760px){.chart-card-glance .glance{grid-template-columns:minmax(0,1fr)}}

/* ── The i: what a filter means (F1 A, F2 B, 2026-09-14) ──────────────────────
   Beside a filter name, a works table column name or a chart title. A click opens .fh-card, which
   links to .fh-panel with every filter. It replaced hover tooltips that phones never showed. */
.sf-head{display:flex;align-items:center;gap:6px}
.filter-rail #features-filters-wrap .sf-bar > .sf-head{margin:0 0 9px}
.filter-rail #features-filters-wrap .sf-head .sf-label{margin:0;flex:0 1 auto}
.filter-rail #features-filters-wrap .sf-head .sf-collapse-btn,.filter-rail .sf-head .sf-group-btn{flex:1 1 auto;min-width:0}
.fh-i{flex:none;display:inline-flex;align-items:center;justify-content:center;width:17px;height:17px;padding:0;
  border-radius:50%;border:1.5px solid rgba(69,58,76,.45);background:#fff;color:var(--ardoise);
  font:italic 700 11px/1 Georgia,'Times New Roman',serif;letter-spacing:0;text-transform:none;cursor:pointer}
.fh-i:hover,.fh-i[aria-expanded="true"]{background:var(--ardoise);border-color:var(--ardoise);color:#fff}
.fh-i:focus-visible{outline:2px solid var(--canard);outline-offset:2px}
.bt-th .fh-i{width:15px;height:15px;font-size:10px}
.fh-card{position:absolute;z-index:600;width:min(360px,calc(100vw - 24px));max-height:min(72vh,580px);overflow:auto;
  background:#fff;border:1px solid rgba(69,58,76,.14);border-radius:12px;box-shadow:0 14px 36px rgba(69,58,76,.22);
  padding:18px 20px 16px;color:var(--ardoise);font-family:var(--sans);text-align:left}
.fh-card.is-sheet{position:fixed;left:0;right:0;bottom:0;top:auto;width:auto;max-height:78vh;border-radius:16px 16px 0 0;
  box-shadow:0 -10px 30px rgba(69,58,76,.25)}
.fh-x{position:absolute;top:10px;right:10px;width:30px;height:30px;border:none;border-radius:50%;
  background:rgba(69,58,76,.07);font-size:19px;line-height:1;color:var(--ardoise);cursor:pointer}
.fh-x:hover{background:rgba(69,58,76,.14)}
.fh-x:focus-visible{outline:2px solid var(--canard);outline-offset:2px}
.fh-eb{margin:0;font-size:11.5px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-muted)}
.fh-title{margin:2px 36px 8px 0;font-family:var(--display);font-size:21px;font-weight:600;line-height:1.2;color:var(--ardoise)}
.fh-lead{margin:0 0 12px;font-size:14.5px;line-height:1.55;color:var(--ardoise)}
/* Each value is its rail chip, its meaning under it (F4 A). */
.fh-vals{margin:0;display:grid;gap:14px}
.fh-vals dt{margin:0 0 5px;line-height:1}
.fh-vals dt .bib-topic{margin:0}
.fh-vals dd{margin:0;font-size:13.5px;line-height:1.5;color:var(--ink-body)}
.fh-pil{display:inline-flex;align-items:center;gap:8px;font-size:13.5px;font-weight:700;color:var(--ardoise)}
.fh-foot{margin:14px 0 0;padding-top:10px;border-top:1px solid rgba(69,58,76,.12);display:flex;flex-wrap:wrap;
  gap:8px 16px;align-items:center;justify-content:space-between}
.fh-hint{margin:0;font-size:12.5px;line-height:1.45;color:var(--ink-muted)}
.fh-all{border:none;background:none;padding:0;font-family:var(--sans);font-size:13.5px;font-weight:700;color:var(--groseille);
  cursor:pointer;text-decoration:underline;text-underline-offset:3px}
/* A red i opens an explanation that is not a filter's (2026-09-14), such as the method behind the Overview
   charts: the i and its words are one button, and the card is wider for a few paragraphs. */
.fh-i.is-red{border-color:var(--groseille);color:var(--groseille)}
.rs-how{display:inline-flex;align-items:center;gap:7px;margin:0 0 0 6px;padding:0;border:none;background:none;
  font-family:var(--sans);font-size:13px;font-weight:700;color:var(--groseille);cursor:pointer;vertical-align:baseline}
.rs-how:hover .fh-i.is-red,.rs-how[aria-expanded="true"] .fh-i.is-red{background:var(--groseille);color:#fff}
.rs-how:focus-visible{outline:2px solid var(--canard);outline-offset:2px;border-radius:4px}
.fh-card.fh-info{width:min(480px,calc(100vw - 24px))}
.fh-card.fh-info.is-sheet{width:auto}
.fh-info .rs-methods{font-size:13.5px;line-height:1.55;color:var(--ink-body)}
.fh-info .rs-methods ul{margin:0;padding-left:18px}
.fh-info .rs-methods li{margin:0 0 8px}
.fh-info .rs-methods strong{color:var(--ardoise)}
/* What a label means, on hover, on work and actor cards, in the works table and in the work pop-up (H1 B,
   2026-09-14): the label as its chip, then its definition, in the card the filter explanations use. It follows
   no pointer and takes no clicks; phones, which have no hover, keep the i. */
.lbl-tip{position:fixed;z-index:700;width:min(320px,calc(100vw - 24px));background:#fff;border:1px solid rgba(69,58,76,.14);
  border-radius:12px;box-shadow:0 14px 36px rgba(69,58,76,.22);padding:12px 14px 13px;pointer-events:none;
  font-family:var(--sans);color:var(--ardoise);opacity:0;transition:opacity .12s ease}
.lbl-tip.is-on{opacity:1}
.lbl-tip .fh-eb{margin:0 0 7px}
.lbl-tip .lbl-tip-v{margin:0 0 8px;display:flex;flex-wrap:wrap;gap:6px;align-items:center}
.lbl-tip .lbl-tip-v .bib-topic{margin:0}
.lbl-tip .lbl-tip-n{margin:0 0 6px;font-size:13px;font-weight:700;color:var(--ardoise)}
.lbl-tip .lbl-tip-d{margin:0;font-size:13.5px;line-height:1.5;color:var(--ink-body)}
.lbl-tip .lbl-tip-d + .lbl-tip-v{margin-top:10px}
@media (prefers-reduced-motion:reduce){.lbl-tip{transition:none}}
.fh-overlay{position:fixed;inset:0;z-index:610;background:rgba(69,58,76,.45);display:none}
.fh-overlay.open{display:block}
/* All filters explained, one filter at a time (F3 A): the list on the left, the filter being read on the
   right with Previous and Next under it. The filter being read is red in the list and in its title (F5 C).
   On a phone the list is a row of chips above the filter. */
.fh-panel{position:absolute;top:0;right:0;bottom:0;width:min(860px,100vw);background:#fff;display:flex;flex-direction:column;
  padding:22px 0 0;box-shadow:-12px 0 36px rgba(69,58,76,.2);color:var(--ardoise);font-family:var(--sans)}
.fh-panel > .fh-eb,.fh-panel > .fh-title,.fh-panel > .fh-lead{padding:0 26px}
.fh-panel > .fh-title{font-size:24px;margin-right:50px}
.fh-body{flex:1 1 auto;min-height:0;display:grid;grid-template-columns:210px minmax(0,1fr);border-top:1px solid rgba(69,58,76,.12)}
.fh-nav{overflow:auto;background:#F7F5F6;padding:10px 0}
.fh-nav button{display:block;width:100%;border:none;background:none;padding:9px 22px;text-align:left;font-family:var(--sans);
  font-size:14px;line-height:1.3;color:var(--ardoise);cursor:pointer}
.fh-nav button:hover{background:rgba(69,58,76,.06)}
.fh-nav button.on{background:#fff;color:var(--groseille);font-weight:700;box-shadow:inset 3px 0 0 var(--groseille)}
.fh-nav button:focus-visible,.fh-step button:focus-visible{outline:2px solid var(--canard);outline-offset:-2px}
.fh-main{overflow:auto;padding:20px 28px 32px}
.fh-sec .fh-title{margin:0 0 8px;font-size:22px;color:var(--groseille)}
.fh-sec .fh-title:focus{outline:none}
.fh-step{display:flex;flex-wrap:wrap;justify-content:space-between;gap:10px 16px;margin:24px 0 0;padding-top:14px;
  border-top:1px solid rgba(69,58,76,.12)}
.fh-step button{border:none;background:none;padding:4px 0;font-family:var(--sans);font-size:14px;font-weight:700;
  color:var(--canard);cursor:pointer;text-align:left}
.fh-step .fh-next{margin-left:auto;text-align:right}
@media (max-width:700px){
  .fh-panel > .fh-eb,.fh-panel > .fh-title,.fh-panel > .fh-lead{padding:0 18px}
  .fh-body{grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr)}
  .fh-nav{display:flex;gap:6px;overflow-x:auto;padding:10px 18px;background:none;border-bottom:1px solid rgba(69,58,76,.12)}
  .fh-nav button{flex:none;width:auto;border-radius:999px;background:#F4F2F2;padding:5px 12px;font-size:13px}
  .fh-nav button.on{background:var(--groseille);color:#fff;box-shadow:none}
  .fh-main{padding:16px 18px 28px}
}
/* Visible keyboard focus for card-like buttons (cards/headers/chart tiles). */
.chart-card:focus-visible,.rcard[role="button"]:focus-visible,.rcard-header[role="button"]:focus-visible{outline:2px solid var(--canard);outline-offset:2px;border-radius:12px}

/* Filter actions row (under the filters): Reset - Hide - Sort, side by side */
.sf-actions-row{display:flex;flex-wrap:wrap;align-items:flex-start;gap:10px;margin:12px 0 16px}
.sf-wip{font-size:13.5px;color:var(--ardoise);padding:6px 8px;font-weight:600;white-space:nowrap}

/* Click-to-enlarge chart modal */
.chart-modal-overlay{position:fixed;inset:0;z-index:500;display:none;align-items:center;justify-content:center;background:rgba(69,58,76,.55);padding:24px}
.chart-modal-overlay.open{display:flex}
.chart-modal{background:#fff;border-radius:12px;padding:20px 24px 24px;width:min(960px,94vw);max-height:92vh;box-shadow:0 14px 44px rgba(0,0,0,.3);position:relative;display:flex;flex-direction:column}
.chart-modal-title{font-size:19.5px;font-weight:700;color:var(--ardoise);margin:0 30px 14px 0}
.chart-modal-canvas-wrap{position:relative;flex:1;min-height:62vh}
/* A long chart is given its full height in the window, which then scrolls (B11, 2026-09-14). */
.chart-modal{overflow-y:auto}
.chart-modal-close{position:absolute;top:8px;right:14px;background:none;border:none;font-size:28px;line-height:1;color:var(--ardoise);cursor:pointer}

/* ── Visual Mapping: MDH spider charts ── */
.spider-section{margin-top:30px}
.spider-legend{display:flex;flex-wrap:wrap;gap:8px 22px;align-items:center;margin:2px 0 14px;font-size:13.5px;color:var(--ardoise)}
.spider-legend .sl-item{display:inline-flex;align-items:center;gap:7px}
.spider-legend .sl-swatch{width:14px;height:14px;border-radius:4px;display:inline-block;flex-shrink:0}
/* Pillar swatch colours (were inline hex in map.html; now tokenised). */
.sl-swatch-m{background:var(--rose)}
.sl-swatch-d{background:var(--acier)}
.sl-swatch-hs{background:var(--mont-rose)}
.spider-legend .sl-hatch{background:var(--acier);background-image:repeating-linear-gradient(45deg,var(--acier) 0 1.4px,transparent 1.4px 5px)}
.spider-legend .sl-rings{color:var(--perle);font-style:italic}
/* 1fr rather than a fixed cap, so the two circles use the full page width. */
.spider-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(var(--grid-wide),100%),1fr));gap:var(--grid-gap)}
/* No outline: these sit on a grey band, and a line is only drawn where a white card sits on white
   (decision 11). */
.spider-card{background:#fff;border-radius:12px;padding:18px 18px 10px;}
/* The small facet label of the bar chart cards, not a serif title: one title style for every chart
   card on the page (C10 A, 2026-09-14), in the colour of the filter it matches. */
.spider-card h3{font-family:var(--sans);font-size:12.5px;font-weight:700;letter-spacing:.15em;text-transform:uppercase;
  line-height:1.6;margin:0 0 10px;color:var(--ardoise)}
.spider-card h3{display:flex;align-items:center;gap:8px}
.spider-card h3 .vm-expand{float:none;margin-left:auto;text-transform:none;letter-spacing:normal}
.spider-card:has(#vm-data) h3{color:var(--f-data-name)}
.spider-card:has(#vm-tech) h3{color:var(--f-method-name)}
.spider-card:has(#vm-strategy) h3{color:var(--f-subject-name)}
.spider-card h3 .spider-rings-note{font-size:13px;font-weight:400;color:var(--ardoise);margin-left:8px}
.spider-count{text-align:center;font-size:13.5px;font-weight:700;color:var(--ardoise);margin-top:6px}
.spider-stage-list{margin:7px 0;padding-left:20px;list-style:disc}
.spider-stage-list li{margin:3px 0}

/* ── Visual Mapping: the three chart forms (heatmap / radial / bars) ──
   One <div class="vm-plot"> per card; panel-visual.js fills it with a table,
   an SVG or a bar stack depending on the selected form. Everything clickable
   carries data-k (a cell -> the works behind it); everything explainable
   carries data-tip (label -> its definition, shown in #tooltip). */
.vm-closeness{margin:0 0 14px}
.vm-plot{margin:2px 0 4px}
.spider-count .vm-held{display:block;font-weight:400;font-size:13px;color:var(--taupe);opacity:.75;margin-top:2px}
/* PROSE, so it must not be a flex container. .sl-item is inline-flex with a 7px
   gap, which is right for a swatch beside a label and wrong for a sentence: the
   glossary script wraps defined terms like "Prevention" in a .gloss-term button,
   and inside an inline-flex box every text node and that button become flex
   ITEMS, so the sentence broke into gapped chunks with the term stranded in its
   own column. display:block puts it back to normal text flow. */
.spider-legend .vm-legend-note{display:block;font-weight:400;font-size:13px;
  opacity:.8;max-width:42em;line-height:1.5}
/* One ramp per pillar, stacked so a column compares the same count across the
   three hues and a row is the "more is darker" scale. */
.vm-key{display:inline-grid;grid-template-columns:auto auto;gap:3px 12px;align-items:center;font-size:13px;color:var(--ardoise)}
.vm-key-name{display:inline-flex;align-items:center;gap:6px;white-space:nowrap;font-weight:600;justify-self:end}
.vm-key-title{font-weight:700;justify-self:end}
.vm-key-letter{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:4px;
  color:#fff;font-size:12px;font-weight:700;flex-shrink:0}
.vm-key-bins,.vm-key-steps{display:grid;grid-template-columns:repeat(6,34px);gap:2px}
.vm-key-bins{font-size:12px;opacity:.65;text-align:center;font-weight:700}
.vm-key-steps i{display:block;height:14px;border-radius:4px}
.vm-key-steps i.vm-key-zero{background:#fff;}

/* The table form: a dot matrix (C1 c, 2026-09-14). A real <table>, so it is also the accessible reading;
   a dot's area is the count and its colour the pillar. It scrolls sideways on a phone instead of
   shrinking its words. */
.vm-dots-wrap{overflow-x:auto}
/* The category column stays in view while the table scrolls sideways on a phone. */
.vm-dots .vm-dots-row,.vm-dots .vm-dots-corner{position:sticky;left:0;z-index:1;background:#fff}
.vm-dots tbody tr:nth-child(odd) .vm-dots-row{background:#FAF9F9}
.vm-dots{border-collapse:collapse;width:100%;font-size:13px;color:var(--ardoise)}
.vm-dots th{font-weight:700;text-align:center;padding:4px 2px}
.vm-dots .vm-dots-corner{text-align:left;font-size:12px;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-muted)}
.vm-dots .vm-dots-stage{font-size:12.5px;border-bottom:2px solid var(--border);padding-bottom:5px;white-space:nowrap}
.vm-dots .vm-dots-stage.is-nr{color:var(--ink-muted);font-weight:600}
.vm-dots .vm-dots-stage[data-tip]{cursor:help}
.vm-dots .vm-dots-sub th{font-size:12px;padding-top:6px}
.vm-dots .vm-dots-row{text-align:left;font-weight:600;font-size:13.5px;line-height:1.25;padding:6px 12px 6px 6px;min-width:130px;max-width:210px}
.vm-dots .vm-dots-row.has-tip{cursor:help}
.vm-dots tbody tr:nth-child(odd){background:#FAF9F9}
.vm-dots tr.is-pinned .vm-dots-row{font-weight:400;font-style:italic;color:var(--ink-muted)}
.vm-dots .vm-dots-cell{width:36px;height:38px;text-align:center;vertical-align:middle;padding:0}
.vm-dots .vm-dots-cell.vm-grp{padding-left:10px}
.vm-dot-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;background:none;
  padding:0;cursor:pointer;border-radius:50%}
.vm-dot-btn:focus-visible{outline:2px solid var(--canard);outline-offset:1px}
.vm-dot{display:inline-flex;align-items:center;justify-content:center;border-radius:50%;font-size:11px;font-weight:700;line-height:1}
.vm-dot-btn:hover .vm-dot{box-shadow:0 0 0 2px var(--ardoise)}
.vm-dot-empty{display:inline-block;width:4px;height:4px;border-radius:50%;background:#D9D5D5}
.vm-dots .vm-dots-tot{text-align:left;white-space:nowrap;padding-left:10px}
.vm-dots th.vm-dots-tot{font-size:12px;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-muted)}
.vm-dots-totbar{display:inline-block;height:7px;border-radius:0 4px 4px 0;background:var(--perle);vertical-align:middle;margin-right:6px}
.vm-dots td.vm-dots-tot b{font-size:13px}
/* Keys for the two forms. */
.vm-key-dots{display:flex;flex-wrap:wrap;gap:6px 18px}
.vm-key-dot{display:inline-block;width:12px;height:12px;border-radius:50%;margin-right:6px;vertical-align:middle}
.vm-key-steps-3,.vm-key-bins-3{grid-template-columns:repeat(3,84px)}
.vm-key-bins-3 span{font-size:11.5px}
/* One circle per row until there is room for two at a readable size; tables a little narrower. */
.spider-grid.vm-form-radial{grid-template-columns:repeat(auto-fit,minmax(min(640px,100%),1fr))}
.spider-grid.vm-form-matrix{grid-template-columns:repeat(auto-fit,minmax(min(560px,100%),1fr))}

/* Radial: the polar heatmap. Same ramp as the table; hovering a wedge dims the
   rest of the chart so one (axis x stage x pillar) cell can be isolated. */
/* 620 = the viewBox side, so the SVG renders 1:1 and every font-size in it is
   the size it appears on screen. Do not raise this past the viewBox or the
   labels scale up with it. */
.vm-svg{width:100%;max-width:620px;height:auto;display:block;margin:0 auto;overflow:visible}
.vm-svg .vm-wedge{transition:opacity .12s}
.vm-svg .vm-wedge.has{cursor:pointer}
.vm-svg .vm-wedge.has:hover path{stroke:var(--ardoise);stroke-width:1.8}
.vm-svg.vm-dim .vm-wedge:not(:hover){opacity:.28}
/* Radial bars: a bar takes the ardoise edge on hover and on keyboard focus. */
.vm-svg.vm-rbars{max-width:720px}
.vm-svg .vm-wedge.has:focus-visible path{stroke:var(--ardoise);stroke-width:2}
.vm-svg .vm-wedge:focus-visible{outline:none}
.vm-key-note{font-size:12.5px;color:var(--ink-muted);margin:6px 0 0}

/* Expand to a window. Clicking a chart anywhere other than on a cell opens it
   full size; the Expand button is there so the affordance is visible. The chart
   is re-rendered into the bigger host, not scaled, so labels keep their size. */
.vm-plot{cursor:zoom-in}
.vm-plot [data-k]{cursor:pointer}
.vm-expand{float:right;min-height:24px;font-family:var(--sans);font-size:13px;font-weight:700;color:var(--ardoise);
  background:#fff;border:1.5px solid var(--perle);border-radius:999px;padding:2px 10px;cursor:pointer;
  transition:background .15s,color .15s}
.vm-expand:hover{background:var(--ardoise);color:#fff;border-color:var(--ardoise)}
.vm-zoom{width:min(1180px,96vw);max-height:94vh;overflow:auto}
.vm-zoom .vm-zoom-toggle{margin:0 0 12px;align-self:flex-start}
.vm-zoom .spider-legend{margin:0 0 14px}
.vm-zoom-body{display:flex;justify-content:center}
.vm-zoom #vm-zoom-plot{width:100%;cursor:default}
.vm-zoom .vm-svg{max-width:min(760px,76vh)}
.vm-zoom .vm-heat{font-size:15px}
.vm-zoom .vm-heat .vm-heat-cell{min-width:40px;height:34px;font-size:16px}
.vm-zoom .vm-heat .vm-heat-row{font-size:15px;max-width:none}
.vm-zoom .spider-count{margin-top:12px}
.vm-svg .vm-axis.has-tip,.vm-svg .vm-ring-label,.vm-svg [data-tip]{cursor:help}
.vm-svg .vm-axis.is-pinned{font-style:italic;opacity:.75}
.vm-ramp-zero{background:#fff;color:var(--ardoise)}

/* The expanded state is repeated here on purpose. .vm-zoom sets larger cells so
   the "Expand" view is worth opening, and those rules are one class more
   specific than the three below, so on a phone the expanded heat map kept its
   40px cells and ran off the side. A phone has no room to spend on the zoom, so
   below 640px the expanded view takes the compact sizing too. */
@media (max-width:640px){
  .vm-heat{font-size:12px}
  .vm-heat .vm-heat-cell{min-width:20px;height:22px;font-size:13px}
  .vm-heat .vm-heat-row{max-width:88px;white-space:normal}
  .vm-zoom .vm-heat{font-size:12px}
  .vm-zoom .vm-heat .vm-heat-cell{min-width:20px;height:22px;font-size:13px}
  .vm-zoom .vm-heat .vm-heat-row{max-width:88px}
}

/* Spider click-through modal (reuses .chart-modal-overlay / .chart-modal) */
.sp-modal{width:min(900px,94vw)}
.chart-modal-title .sp-x{color:var(--ardoise);font-weight:400;margin:0 2px}
.chart-modal-title .sp-count{display:inline-block;margin-left:8px;background:var(--ardoise);color:#fff;border-radius:12px;font-size:13.5px;font-weight:700;padding:1px 9px;vertical-align:middle}
.sp-modal-list{overflow:auto;max-height:72vh;padding-right:4px}
.sp-modal-list.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(250px,100%),1fr));gap:12px;align-content:start}
/* Works or actors behind a chart selection (renderWorksOrActors in panel-biblio.js). */
.wa-toggle{display:inline-flex;align-self:flex-start;gap:2px;margin:0 0 14px;padding:3px;background:#F1EEF2;border-radius:999px}
.wa-toggle button{font:600 13px/1.2 var(--sans);color:var(--ardoise);background:none;border:0;border-radius:999px;padding:5px 14px;cursor:pointer}
.wa-toggle button[aria-selected="true"]{background:var(--ardoise);color:#fff}
.sp-modal-list.wa-actors{display:block}
.wa-actor{display:flex;justify-content:space-between;align-items:baseline;gap:16px;padding:10px 4px;border-top:1px solid var(--border)}
.wa-actor:first-child{border-top:0}
.wa-actor-who{min-width:0}
.wa-actor-name{font:700 15px/1.35 var(--sans);color:var(--ardoise);background:none;border:0;padding:0;margin-right:8px;cursor:pointer;text-align:left;text-decoration:underline;text-decoration-color:var(--perle);text-underline-offset:3px}
.wa-actor-name:hover{text-decoration-color:currentColor}
.wa-actor-lab{font-size:12.5px;color:var(--ink-muted)}
.wa-actor-n{flex:none;font-size:12.5px;font-weight:600;color:var(--ardoise)}
.wa-empty{color:var(--ink-muted);padding:8px 2px}
.sp-item{padding:10px 2px;border-bottom:1px solid var(--perle)}
.sp-item:last-child{border-bottom:none}
.sp-item-h{font-size:15px;line-height:1.45;color:var(--ardoise)}
.sp-item-h a{color:var(--canard);text-decoration:none}
.sp-item-h a:hover{text-decoration:underline}
.sp-item-who{font-size:13.5px;color:var(--ardoise);margin-top:2px}
.sp-item-topics{margin-top:5px;display:flex;flex-wrap:wrap;gap:4px}
.sp-yr{color:var(--ardoise);font-weight:400}
.sp-foc{display:inline-block;font-size:12px;font-weight:800;width:19px;height:19px;line-height:19px;text-align:center;border-radius:4px;color:#fff;margin-right:2px}
.sp-foc-M{background:var(--rose)}.sp-foc-D{background:var(--acier)}.sp-foc-H{background:var(--mont-rose)}
.sp-single{display:inline-block;font-size:12px;font-weight:700;color:var(--acier);background:repeating-linear-gradient(45deg,rgba(31,90,138,.18) 0 2px,transparent 2px 5px);border:1px solid rgba(31,90,138,.35);border-radius:4px;padding:0 5px;margin-right:4px}
.sp-topic{font-size:12px;color:var(--ardoise);background:#fff;border:1px solid var(--perle);border-radius:12px;padding:1px 8px}

/* ── Card click indicator ── */
.rcard-expandable .rcard-header{cursor:pointer}
/* A real hover on the one clickable region. Both declarations here used to set
   background:#ffffff on an already-white card, so the card's only affordance gave
   no feedback. */
.rcard-expandable .rcard-header:hover{color:var(--canard)}
/* (The duplicate .rcard-toggle rule that was here is removed — the fuller
   definition further down in "Expandable researcher cards" supersedes it.) */


/* Project-level data type badge */
.proj-data-type{font-size:13px;color:var(--ardoise);margin-left:8px;font-weight:400}
.proj-papers-list{margin-top:8px;padding-top:8px;border-top:1px dashed var(--border)}



/* ── Modal: Focus section ── */
.rm-focus-body p { font-size: 15px; line-height: 1.7; color: var(--ardoise); margin-bottom: 10px; }
.rm-focus-body em { color: var(--ardoise); font-style: italic; }
/* ── Modal: Interview section ── */
.rm-interview-body .key {
  font-size: 13px; font-weight: 800; color: white; background: var(--ardoise);
  text-transform: uppercase; letter-spacing: 0.8px;
  padding: 3px 10px; border-radius: 4px; display: inline-block;
  margin: 16px 0 6px;
}
.rm-interview-body p { font-size: 15px; line-height: 1.7; color: var(--ardoise); margin-bottom: 8px; }
.rm-interview-body strong { color: var(--ardoise); }
.rm-interview-body em { color: var(--ardoise); }
/* ── Modal: Works grid ── */
.rm-works-grid { display: flex; flex-direction: column; gap: 14px; }
.rm-work-card {
  border: 1px solid var(--perle); border-radius: 12px; padding: 14px 16px;
  background: #ffffff;
}
.rm-work-card:hover { border-color: var(--ardoise); background: #ffffff; }
.rm-work-header { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 5px; }
.rm-work-badge {
  font-size: 12px; font-weight: 700; padding: 2px 8px; border-radius: 4px;
  text-transform: uppercase; letter-spacing: 0.5px; white-space: nowrap; flex-shrink: 0;
}
.rm-work-title { font-size: 16px; font-weight: 700; color: var(--ardoise); text-decoration: none; }
a.rm-work-title { color: var(--rouge-suisse); }
a.rm-work-title:hover { text-decoration: underline; }
.rm-work-meta { font-size: 13px; color: var(--ardoise); margin-bottom: 6px; }
.rm-work-relevance {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 13px; color: var(--ardoise); margin-bottom: 6px;
  padding: 3px 8px; background: #fff; border-radius: 12px;
  cursor: help; -webkit-user-select: none; user-select: none;
}
.rm-work-relevance .rel-label { font-weight: 600; text-transform: uppercase; letter-spacing: 0.4px; }
.rm-work-relevance .rel-dots { display: inline-flex; gap: 2px; }
.rm-work-relevance .rel-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: transparent; border: 1.5px solid var(--ardoise);
  display: inline-block;
}
.rm-work-relevance .rel-dot-on { background: var(--rouge-suisse); border-color: var(--rouge-suisse); }
.rm-work-relevance .rel-score { font-weight: 700; color: var(--ardoise); margin-left: 2px; }
.rm-work-data { font-size: 13.5px; color: var(--ardoise); margin-bottom: 6px; }
.rm-work-data strong { color: var(--ardoise); }
.rm-work-what { font-size: 15px; line-height: 1.65; color: var(--ardoise); }
/* ── Section title styles ── */
.rm-section-title { font-size: 13px; font-weight: 800; color: var(--ardoise); text-transform: uppercase; letter-spacing: 0.9px; margin-bottom: 14px; padding-bottom: 6px; border-bottom: 2px solid var(--border); }

/* ── Expandable researcher cards (status panel) ──
   (The base .rcard rule was merged into Section 6 above; here we only add the
   expandable-card behaviour: header, body, toggle.) */
.rcard .rcard-header { flex:1; }
.rcard.rcard-expandable { cursor:default; }
.rcard-header { display:flex; align-items:flex-start; justify-content:space-between; padding:12px 14px; }
.rcard.rcard-expandable .rcard-header { cursor:pointer; }
.rcard-toggle { font-size:19.5px; font-weight:700; color:var(--zinzolin); background:#ffffff; width:26px; height:26px; border-radius:50%; display:flex; align-items:center; justify-content:center; margin-left:8px; flex-shrink:0; transition:all 0.15s; -webkit-user-select:none; user-select:none; }
.rcard-body { display:none; border-top:1px solid var(--border); padding:14px; background:#ffffff; }
.rcard-body.open { display:block; }
.rcard-projects { margin-bottom:12px; }
.rcard-proj { padding:8px 10px; background:white; border-radius:4px; border:1px solid var(--border); margin-bottom:6px; }
.rcard-proj-badge { display:inline-block; font-size:12px; padding:1px 6px; border-radius:4px; font-weight:700; margin-right:6px; }
.rcard-proj-badge.proj-mdh-e { background:#ffffff; color:var(--groseille); }
.rcard-proj-badge.proj-mdh-r { background:var(--vert-deau); color:var(--canard); }
.rcard-proj-badge.proj-mdh-a { background:#ffffff; color:var(--ardoise); }
.rcard-proj-tech { font-size:13px; color:var(--ardoise); display:block; margin:3px 0; font-style:italic; }
.rcard-proj-desc { font-size:13.5px; color:var(--ardoise); line-height:1.55; margin-top:4px; }
.rcard-papers { border-top:1px solid var(--border); padding-top:10px; }
.rcard-papers-title { font-size:13px; font-weight:700; color:var(--ardoise); text-transform:uppercase; letter-spacing:0.8px; margin-bottom:6px; }
.rcard-paper { font-size:13.5px; padding:4px 0; border-bottom:1px solid rgba(69,58,76,.2); line-height:1.5; }
.rcard-paper:last-child { border-bottom:none; }
.rcard-paper a { color:var(--rouge-suisse); text-decoration:none; font-weight:500; }
.rcard-paper a:hover { text-decoration:underline; }
.rcard-paper-meta { color:var(--ardoise); font-size:13px; margin-left:4px; }
/* No-response grid: dimmed */
/* #noresp-grid is dead: the markup id is grid-noresp (status.html). Removed
   rather than corrected, because the dimming was never in effect and nobody has
   missed it. */
/* Network: labels outside nodes */
.net-label-out { pointer-events:none; }

/* ── Status panel: section headers ─────────────────────────────────────────
   EYEBROWS, not filled bars. Every one of these was a solid Ardoise block with
   white type, and the academia tiers below added a 5px coloured stripe on the
   leading edge on top of that. Six dark bars stacked down a page of white cards
   read as the loudest thing on it, and the stripe is the same asymmetric
   furniture already removed from the signpost note, the intro card and
   Honorable mentions.
   Same type as .eyebrow everywhere else. The change of label is what separates
   one group from the next; it does not need a ground to do it. */
.section-header,
.section-done,
.section-agreed,
.section-answered,
.section-noresp,
.section-tocontact,
.section-honorary {
  background: none;
  color: var(--groseille);
  border: none;
  border-radius: 0;
  padding: 0;
  margin: 34px 0 8px;
  font-family: var(--sans);
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: .15em;
  text-transform: uppercase;
}
.section-count    { font-weight: 400; font-size: 12.5px; margin-left: 6px; opacity: 0.7;
  letter-spacing: 0; text-transform: none; }
.section-desc     { font-size: 13.5px; color: var(--ardoise); margin: 0 0 10px; padding: 0 2px; }

/* ── EPFL Actors & Projects: view toggle (Academia <-> Internal) ── */
.view-toggle{display:inline-flex;margin:0 0 18px;border:1.5px solid var(--perle);border-radius:999px;overflow:hidden;background:#fff;}
.view-toggle-btn{font-family:var(--sans);font-size:13.5px;font-weight:700;color:var(--ardoise);background:#fff;border:none;padding:8px 18px;cursor:pointer;transition:background .15s,color .15s;}
.view-toggle-btn:not(:last-child){border-right:1.5px solid var(--perle);}
.view-toggle-btn.active{background:var(--ardoise);color:#fff;}
.view-toggle-btn:hover:not(.active){background:#fff;}
/* The Actors toggle outlives its own hidden child. Its ONLY button carries
   .internal-only, which is display:none !important until login, so the flex box
   collapses to a 0x0 content area - but the wrapper is still in the layout, and
   its border and 999px radius still paint. Every logged-out visitor got a 2x2px
   dot. status.html's comment claimed "a public visitor sees no toggle at all
   here": true of the button, never true of the box around it.
   It was tucked in the left gutter and easy to miss until the .scope-note swap
   made the panel a flex column (see .panel.active:has(> .page-head .scope-note)),
   which turned `.panel > *`'s margin-inline:auto into real centring and parked
   the dot in the middle of the page.
   Scoped to #panel-status on purpose: the other .view-toggle, the Circle/Table
   switch on Visual Mapping, has buttons that are always visible and must not be
   hidden. Revealed again by the same body class that reveals the button, so the
   two cannot fall out of step. */
#panel-status .view-toggle{display:none;}
body.internal-unlocked #panel-status .view-toggle{display:inline-flex;}

/* Mode visibility: one section-set + one banner at a time. */
#panel-status .tab-intro-internal,
#panel-status .tab-intro-leadership{display:none;}
/* Banding this tab put these two classed paragraphs inside a .band, where the
   band's body-prose rule (.band p, 17px / 62ch / faded) outranks their own
   single-class rules and blew up a 13px filter hint and a 15px credit line.
   .band p is for UNCLASSED prose; a component that sets its own size keeps it.
   Scoped to this panel so the Features tab, which protects its copy of
   .sf-multi-hint with an id-scoped rule already, is not touched. */
#panel-status .sf-multi-hint{font-size:13px;line-height:1.5;color:var(--ardoise);max-width:none;}
#panel-status .aca-ack-intro{font-size:16px;line-height:1.6;color:var(--ardoise);max-width:min(80ch,var(--measure));}

/* Academia (default) and Leadership share the relevance-tier grid; Internal uses the status grid. */
#panel-status.view-academia #status-internal,
#panel-status.view-leadership #status-internal{display:none;}
#panel-status.view-academia #funnel-row,
#panel-status.view-leadership #funnel-row{display:none;}
#panel-status.view-academia #status-filter-bar,
#panel-status.view-leadership #status-filter-bar{display:none;} /* relevance is the grouping itself */
#panel-status.view-leadership .tab-intro-academia{display:none;}
#panel-status.view-leadership .tab-intro-leadership{display:block;}
#panel-status.view-internal #status-academia{display:none;}
#panel-status.view-internal .tab-intro-academia{display:none;}
#panel-status.view-internal .tab-intro-internal{display:block;}

/* Academia tier headers: navy bar with a coloured left accent per tier. */
/* The tier accent now lives in the eyebrow's own colour rather than in a stripe
   beside a dark ground. Inherits everything else from .section-header above. */
.section-aca-direct,.section-aca-transfer,.section-aca-adjacent,.section-aca-foundational{
  background:none;border:none;border-radius:0;}
.section-aca-direct{color:var(--groseille);}
.section-aca-transfer{color:var(--canard);}
.section-aca-adjacent{color:var(--ink-muted);}
/* An eyebrow that opens a section is red, like every other one (decision 7). */
.section-aca-foundational{color:var(--groseille);}
/* Honorable mentions: a quieter, smaller sub-heading, not a full tier pill. */
/* Folded into the shared .section-header eyebrow rule above; only its accent
   hue is still declared, beside the other three tiers. */

/* Acknowledgements block at the foot of the academia view. */
/* A section, not a boxed aside. This was a white rectangle with a Perle contour
   sitting on the panel's own white, so it read as a callout pinned to the actor
   list rather than as the next thing on the page. It now takes the tint ground
   the site uses to mark a new section, and drops the contour: the change of
   ground is what says "new section", and a border on top of that says it twice. */
.aca-ack-block{margin:48px 0 8px;padding:28px 30px 30px;border:none;
  border-radius:12px;background:var(--ground-tint);}
.aca-ack-title{font-family:var(--sans);font-size:12.5px;font-weight:700;letter-spacing:.15em;text-transform:uppercase;color:var(--groseille);margin-bottom:10px;}
.aca-ack-intro{font-size:16px;color:var(--ardoise);line-height:1.6;margin:0 0 12px;}
.aca-ack-names{display:flex;flex-wrap:wrap;gap:8px;}
.aca-ack-name{font-size:13.5px;font-weight:600;color:var(--ardoise);background:#fff;border:1px solid var(--perle);border-radius:999px;padding:5px 11px;}
.aca-ack-aff{font-weight:400;color:var(--ardoise);opacity:.75;}

/* ── Interview themes: per-view audience filtering + Internal colour-code ──
   Each theme block is tagged .iv-aud-academia / -leadership / -both. The modal
   body carries .iv-view-academia / -leadership / -internal. */
.iv-theme{margin:0 0 6px;}
/* Academia and Leadership views: show only their slice (+ both); no colour-code */
.iv-view-academia .iv-aud-leadership{display:none;}
.iv-view-leadership .iv-aud-academia{display:none;}
/* Internal view: show everything, colour-coded by audience */
.iv-view-internal .iv-theme{border-left:3px solid transparent;border-radius:0 4px 4px 0;padding:5px 10px;margin-bottom:8px;}
.iv-view-internal .iv-aud-academia{border-left-color:var(--canard);background:rgba(0,116,128,.06);}
.iv-view-internal .iv-aud-leadership{border-left-color:var(--carotte);background:rgba(236,102,8,.06);}
.iv-view-internal .iv-aud-both{border-left-color:var(--zinzolin);background:rgba(92,36,131,.06);}
/* Legend (Internal only) */
.iv-legend{font-size:13px;color:var(--ardoise);margin:0 0 10px;display:flex;flex-wrap:wrap;align-items:center;gap:12px;}
.iv-leg{display:inline-flex;align-items:center;gap:5px;font-weight:700;}
.iv-leg::before{content:'';width:10px;height:10px;border-radius:4px;display:inline-block;}
.iv-leg-academia{color:var(--canard);} .iv-leg-academia::before{background:var(--canard);}
.iv-leg-leadership{color:var(--carotte);} .iv-leg-leadership::before{background:var(--carotte);}
.iv-leg-both{color:var(--zinzolin);} .iv-leg-both::before{background:var(--zinzolin);}

/* ── Actor modal: Focus tag row + collapsible Discussion section ── */
.rm-focus-tags{margin-top:10px;}
.rm-discuss{margin-top:18px;}
.rm-discuss-toggle{display:flex;align-items:center;gap:8px;width:100%;text-align:left;background:#fff;border:1px solid var(--perle);border-radius:12px;padding:11px 14px;cursor:pointer;font-family:var(--sans);font-size:16px;font-weight:700;color:var(--ardoise);transition:border-color .15s;}
.rm-discuss-toggle:hover{border-color:var(--canard);}
.rm-discuss-count{font-weight:400;color:var(--ardoise);}
.rm-discuss-caret{margin-left:auto;font-size:19.5px;line-height:1;color:var(--ardoise);}
.rm-discuss-body{display:none;padding:14px 2px 0;}
.rm-discuss.open .rm-discuss-body{display:block;}
.rm-discuss-empty{font-size:15px;color:var(--ardoise);font-style:italic;padding:8px 2px 0;}
.rm-links{margin:6px 0 2px;}
/* White on the dark profile header: Canard there was hard to read (2026-09-14). */
.rm-profile-link{font-size:13.5px;font-weight:700;color:#fff;text-decoration:none;border-bottom:1px solid rgba(255,255,255,.55);}
.rm-profile-link:hover{color:#fff;border-bottom-color:#fff;}

/* Internal view: tint each actor card by which audience view it appears in (same colours as the discussion themes). */
#panel-status.view-internal .rcard.ivc-academia{border-left:4px solid var(--canard);}
#panel-status.view-internal .rcard.ivc-leadership{border-left:4px solid var(--carotte);}
#panel-status.view-internal .rcard.ivc-both{border-left:4px solid var(--zinzolin);}

/* ── Note shown inline on non-interviewed cards (toggled by + button) ── */
.rcard-note-body {
  display: none;
  font-size: 13.5px;
  color: var(--ardoise);
  padding: 10px 14px 12px;
  border-top: 1px solid var(--border);
  line-height: 1.55;
  background: #ffffff;
}
.rcard.open .rcard-note-body { display: block; }
.rcard.open .rcard-toggle { background: var(--leman); color: #fff; }

/* ── Honorable mentions ──
   Normal cards on a quiet grey tint (S6 A, 2026-09-14). Two of them used to be faded to 75% with a
   purple edge, and the third, flagged honorable_mention rather than honorary, looked like any card. */
#grid-aca-mentions .rcard { background: #F1F0F2; }
#grid-aca-mentions .rcard .rcard-header { background: transparent; }

/* ── 12. LANDING PAGE (hero · tiles · explore · featured · about · footer) ── */
   /* The home page: big hero, the "who are you" tiles, the section list, and the footer. */

/* ── PHANTOM-STYLE LANDING (and welcome panel) ──────────────────────
   Editorial / institutional pattern, not SaaS cards:
   - Full-bleed sections, alternating background colours
   - Solid colour blocks (no rounded corners, no soft shadows)
   - Numbered tiles, uppercase eyebrows, large bold titles
   - Hover: filter darken, not transform lift
   All colours strictly from the EPFL brand chart variables.
   ───────────────────────────────────────────────────────────────── */

/* The landing runs full width: its sections (hero, tiles, About) each manage
   their own internal measure, so the panel-level cap must not apply here. */
.panel-landing { padding: 0; }
/* Kept as a belt-and-braces, but the real fix is the :not(.panel-landing) on the
   capping rule above: this one is (0,1,0) and cannot outrank it. */
.panel-landing > * { max-width: none; }
/* The landing takes the same alternating grounds as every other page, so the
   site reads as one thing from the first screen. The hero keeps its own dark
   ground and its word-storm; the sections beneath it alternate. */
/* Section grounds are set on each section's own rule further down, not here:
   declaring them twice meant the later definition silently won. Order down the
   page is hero (dark) -> pick (white) -> explore (ground) -> about (tint) ->
   event (white), so no two touching sections share a ground. */
.ph-pick{background:var(--ground);}

/* Section primitives */
/* Full-bleed section, measure-constrained content: the ground colour reaches
   both window edges and the inner header/list re-establish the column. */
/* 56px, matching .mdh-page-section and .band. The homepage was the last page drawing a
   section at a different height. */
.ph-section { padding: 56px var(--gutter); position: relative; }
.ph-section > * { max-width: var(--measure); margin-inline: auto; }
.ph-section-hd { max-width: 1160px; margin: 0 auto 36px; }
.ph-section-title {
  font-family:var(--display);
  font-size: 30px;
  font-weight: 600;
  color: var(--ardoise);
  letter-spacing: -0.015em;
  line-height: 1.08;
  max-width: 34ch;
}
/* Small subtitle directly under a section title (sits inside .ph-section-hd) */
/* 16.5px, matching .mdh-page-section-lede and the band lede. At 19px this was the only
   section sub-text on the site set larger than body copy, which made the homepage read a
   size up from every page it links to. */
.ph-section-sub {
  font-family:var(--sans);
  font-size: 16.5px;
  font-weight: 400;
  line-height: 1.55;
  color: rgba(69,58,76,.78);
  margin: 14px 0 0;
  max-width: 58ch;
}
/* The eyebrow was 22px/800 in Canard, which read as a heading in its own right
   and competed with the title under it. Same as every other page now: small,
   Groseille, a label rather than a headline. */
.ph-eyebrow {
  font-family:var(--sans);
  font-size: 12.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .15em;
  color: var(--groseille);
  margin-bottom: 14px;
}
/* "Who are you?..." sits under a now-prominent eyebrow, so dial it back a little. */
/* .ph-pick used to run larger than the other section titles; it now shares
   the one size, which is the hero title's size. */

/* HERO ----------------------------------------------------------- */
.ph-hero {
  min-height: clamp(420px, 64vh, 600px);
  /* Fills the first screen below the site header at every size (owner, 2026-09-15). A browser without svh
     drops this line and keeps the clamp above. --main-top is the header height, set by syncHeaderNavHeight. */
  min-height: max(420px, calc(100svh - var(--main-top, 0px)));
  background: #000;
  color: #fff;
  display: flex;
  align-items: stretch;
  justify-content: center;
  padding: 48px 52px 44px;
  position: relative;
  overflow: hidden;
}

/* ── Hero "word storm" ────────────────────────────────────────────────────────
   MDH vocabulary drifts across the dark sky behind the title: verified terms glow
   in the cool EPFL colours, false ones burn warm and dissolve mid-flight, neutral
   ones drift muted. Painted on a canvas (see site/js/hero-wordstorm.js), which is
   far cheaper than animating ~24 DOM nodes and makes the blur/fade trivial.
   The words are confined to a top and a bottom band by the renderer, so the
   middle stays clear for the title. Decorative: never intercepts clicks. */
.ph-wordstorm {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}
/* Soft top-to-bottom scrim so the title stays readable over the hero. */
.ph-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  background:
    linear-gradient(180deg, rgba(0,0,0,0.35) 0%, rgba(0,0,0,0.15) 45%, rgba(0,0,0,0.45) 100%);
  pointer-events: none;
}
/* The title block is CENTRED in the hero (matching the concept preview): the
   whole group sits in the middle, sub-line directly beneath the headline, rather
   than the headline being pinned to the top and the sub-line to the bottom. */
.ph-hero-inner {
  max-width: 1280px;
  text-align: center;
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  flex: 1;
}
.ph-hero-foot { margin-top: 0; }
/* The hero title is pinned to Arial on purpose. It moved to Newsreader on 2026-09-13 and back to
   Arial the next day, at the owner's request, with the size and weight it had before. Do not fold it
   into the shared type tokens without asking. */
.ph-hero-title {
  font-family: Arial, sans-serif;
  font-size: 30px;
  font-weight: 800;
  line-height: 1.15;
  color: #fff;
  letter-spacing: -0.5px;
  margin-bottom: 10px;
  white-space: normal;
}
.ph-hero-title em { font-style: normal; }
/* The three terms very large, one per line, with the line naming the platform under them (HR2 a, 2026-09-15).
   Still Arial 800, as the note above asks; only the size of the terms grew. */
.ph-hero-terms {
  display: block;
  font-size: clamp(40px, 6vw, 74px);
  line-height: 1.04;
  letter-spacing: -1px;
}
.ph-hero-tagline {
  color: #fff;             /* white */
  display: block;
  margin-top: 18px;
  font-size: clamp(20px, 2.1vw, 28px);
  line-height: 1.25;
  letter-spacing: -0.3px;
  text-wrap: balance;      /* no lone "by EPFL" on the last line at phone width */
}
.ph-hero-sub {
  font-size: 20px;
  line-height: 1.55;
  color: rgba(255,255,255,0.72);
  max-width: 720px;
  margin: 0 auto;
  text-wrap: balance;      /* no lone "there." on a second line */
}
.ph-down {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border: 2px solid rgba(255,255,255,0.35);
  border-radius: 50%;
  color: #fff;
  font-size: 20px;
  text-decoration: none;
  transition: background 0.2s, border-color 0.2s;
}
.ph-down:hover { background: rgba(255,255,255,0.10); border-color: #fff; }
@media (max-width: 760px) {
  .ph-hero-title { font-size: 24px; letter-spacing: -0.4px; }
  .ph-hero-sub { font-size: 17.5px; }
  .ph-section { padding: 70px 24px; }
  .ph-section-title { font-size: 26px; }
}

/* PICK section --------------------------------------------------- */
.ph-pick { background: var(--ground); }
.ph-tiles {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px;
}
/* ── Audience tiles ─────────────────────────────────────────────────────────
   White cards with a rule on ONE SIDE ONLY. Not a bordered box with an accent
   stripe on top: a full outline plus a coloured bar is the shape every component
   library ships, and it is exactly what makes a card look generated rather than
   designed. One rule, one edge, and the card is held by the ground it sits on
   instead of by a frame around it.
   The four saturated backgrounds are gone. Colour on this site means MDH pillar,
   and an audience is not a pillar, so four audience colours were competing with
   the one thing colour is meant to say. */
.ph-tile {
  background: #fff;
  border: none;
  border-radius: 12px;
  padding: 26px 24px;
  text-align: left;
  font-family:var(--sans);
  color: var(--ardoise);
  cursor: pointer;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 0;
  transition: transform .18s ease, background .18s ease;
}
.ph-tile:hover { box-shadow: 0 6px 20px rgba(69,58,76,.09); }
.ph-tile:focus-visible { outline: 2px solid var(--groseille); outline-offset: 3px; }
.ph-tile.chosen { background: #fff; box-shadow: inset 3px 0 0 var(--groseille), 0 6px 20px rgba(69,58,76,.09); }
/* The audience name, set as meta rather than as a heading: the SENTENCE under it
   is what a reader actually chooses between. */
/* Matched to .eyebrow, which every other tab uses for the same job. They were
   12px/.14em here and 12.5px/.15em everywhere else, which is the kind of gap
   nobody names but everybody feels. */
.ph-tile-tag {
  font-family: var(--sans);
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: .15em;
  text-transform: uppercase;
}
/* Four audiences, four colours. Canard 5.5:1, Zinzolin 10.3:1 and Marron
   10.7:1 clear AA as 12.5px text on white; Carotte is 3.2:1 and does not.
   It was chosen by name; Groseille (6.6:1) is the nearest legible swap if
   the label ever needs to pass. */
.ph-tile-public   .ph-tile-tag { color: var(--carotte-txt); }
.ph-tile-research .ph-tile-tag { color: var(--canard); }
.ph-tile-strategy .ph-tile-tag { color: var(--zinzolin); }
.ph-tile-all      .ph-tile-tag { color: var(--marron); }
.ph-tile-title {
  font-family: var(--display);
  font-size: 21px;
  font-weight: 600;
  line-height: 1.18;
  letter-spacing: -.01em;
  color: var(--ardoise);
}
.ph-tile-body {
  font-size: 15.5px;
  line-height: 1.55;
  color: rgba(69,58,76,.78);
  opacity: 1;
  flex: 1;
}
/* Once a view is selected the picker and the explore list belong together,
   so close the gap between them. */
/* Split view from the start (decision 2 C, 2026-09-13): a slim row per choice, with a dot that
   fills when it is chosen. The longer description is left to the sections panel. */
.ph-tile { flex-direction: row; align-items: flex-start; gap: 14px; padding: 14px 18px; }
.ph-tile-dot { flex: none; width: 18px; height: 18px; margin-top: 3px; border-radius: 50%; border: 1.5px solid var(--perle); box-sizing: border-box; }
.ph-tile[aria-pressed="true"] .ph-tile-dot { border: 5px solid var(--groseille); }
.ph-tile-text { display: flex; flex-direction: column; gap: 4px; }
.ph-tile .ph-tile-title { font-size: 18px; }
.ph-tile-body { display: none; }

/* EXPLORE: the sections panel, right of the choices from the start --- */
.ph-explore { background: #fff; color: var(--ardoise); border-radius: 12px; padding: 22px 24px; min-height: 330px; }
.ph-explore-empty { min-height: 280px; display: grid; place-items: center; text-align: center; color: var(--ink-muted);
  border: 1.5px dashed var(--perle); border-radius: 10px; padding: 20px; font-size: 15.5px; line-height: 1.55; }
/* display:grid above outranks the browser's own [hidden] rule, so it has to be restated. */
.ph-explore-empty[hidden] { display: none; }
/* On phones the choices sit above the box, not beside it, so it needs no height of its own. */
@media (max-width: 700px) {
  .ph-explore { min-height: 0; }
  .ph-explore-empty { min-height: 0; padding: 14px 0; }
}
/* Grey, like every eyebrow inside a white card (decision 7). */
.ph-explore-eyebrow { font-family: var(--sans); font-size: 12.5px; font-weight: 700; letter-spacing: .15em;
  text-transform: uppercase; color: var(--ink-muted); margin: 0 0 12px; }
.ph-explore-list { max-width: none; margin: 0; }
.ph-explore-item {
  display: grid;
  grid-template-columns: 40px 1fr;
  gap: 12px;
  align-items: baseline;
  width: 100%;
  border: none;
  border-top: 1px solid var(--border);
  padding: 13px 4px;
  background: transparent;
  color: var(--ardoise);
  text-align: left;
  font-family: var(--sans);
  cursor: pointer;
  transition: background 0.2s ease;
}
.ph-explore-item:last-child { border-bottom: 1px solid var(--border); }
/* A tint, not a shift: nothing in the panel moves. */
.ph-explore-item:hover { background: rgba(69,58,76,0.04); }
.ph-explore-item:focus-visible { outline: 2px solid var(--groseille); outline-offset: 2px; }
.ph-explore-num { font-size: 13px; font-weight: 600; letter-spacing: .08em; color: var(--ink-muted); }
.ph-explore-content { display: flex; flex-direction: column; }
.ph-explore-title { font-size: 16px; font-weight: 600; color: var(--ardoise); line-height: 1.3; }
/* Always shown: the hover reveal said what a page holds only to a mouse. */
.ph-explore-desc { font-size: 14px; line-height: 1.5; color: var(--ink-muted); margin-top: 2px; }

/* FEATURED section ---------------------------------------------- */
.ph-featured { background: #fff; border-top: 1px solid var(--perle); }
.ph-feat-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 24px;
}
@media (max-width: 900px) { .ph-feat-grid { grid-template-columns: 1fr; } }
.ph-feat {
  display: block;
  padding: 36px 32px 32px;
  font-family:var(--sans);
  text-decoration: none;
  color: var(--ardoise);
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 12px;
  transition: background 0.18s, border-color 0.18s, transform 0.18s;
  position: relative;
  min-height: 320px;
}
.ph-feat:hover { background: #fff; border-color: rgba(69,58,76,0.35); transform: translateY(-1px); }
.ph-feat-kind {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 2.5px;
  margin: 0 0 16px;
}
.ph-feat-case .ph-feat-kind     { color: var(--carotte); }
.ph-feat-actor .ph-feat-kind    { color: var(--canard); }
.ph-feat-takeaway .ph-feat-kind { color: var(--zinzolin); }
.ph-feat-title {
  font-size: 21px;
  font-weight: 700;
  line-height: 1.28;
  letter-spacing: -0.3px;
  color: var(--ardoise);
  margin: 0 0 14px;
}
.ph-feat-body {
  font-size: 16px;
  line-height: 1.6;
  color: rgba(69,58,76,0.85);
  margin: 0 0 22px;
}
.ph-feat-cta {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  margin: 0;
  position: absolute;
  bottom: 32px;
  left: 36px;
}
.ph-feat-case .ph-feat-cta     { color: var(--carotte); }
.ph-feat-actor .ph-feat-cta    { color: var(--canard); }
.ph-feat-takeaway .ph-feat-cta { color: var(--zinzolin); }

/* ABOUT section -------------------------------------------------- */
.ph-about { background: #fff; border-top: none; }
/* Its own section, so its own ground: white against About's tint above and
   the Event band below, which keeps the alternation unbroken. */
.ph-whois { background: var(--ground); }
.ph-whois .eyebrow { margin-bottom: 22px; }
/* The SDG goals now sit beside the About title instead of under a photo. */
.ph-about .ph-section-hd { display: grid; grid-template-columns: 1fr auto;
  align-items: end; gap: 20px 40px; }
.ph-about-sdg { display: flex; gap: 10px; align-self: end; }
.ph-about-sdg img { width: 78px; height: 78px; border-radius: 4px; display: block; }
@media (max-width: 860px) {
  .ph-about .ph-section-hd { grid-template-columns: 1fr; }
  .ph-about-sdg img { width: 62px; height: 62px; }
}
/* Choices on the left, the sections panel on the right, from the first paint. */
.ph-pick-grid { display: grid; grid-template-columns: 360px minmax(0, 1fr); gap: 28px; align-items: start; max-width: 1160px; margin: 0 auto; }
@media (max-width: 860px) { .ph-pick-grid { grid-template-columns: 1fr; } }
.ph-explore-grid { display: block; }
@media (max-width: 900px) { .ph-explore-grid { grid-template-columns: 1fr; gap: 26px; } }
.ph-chosen-card { background: #fff; border-radius: 12px; padding: 26px 24px; position: sticky; top: 96px; }
@media (max-width: 900px) { .ph-chosen-card { position: static; } }
.ph-chosen-lbl { font-size: 12px; font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: rgba(69,58,76,.45); margin: 0 0 16px; }
.ph-chosen-card .ph-tile-tag { display: block; margin: 0 0 10px; }
.ph-chosen-title { font-family: var(--display); font-size: 23px; font-weight: 600;
  line-height: 1.2; letter-spacing: -.01em; margin: 0 0 10px; color: var(--ardoise); }
.ph-chosen-body { font-size: 15px; line-height: 1.55; color: var(--ink-soft); margin: 0 0 20px; }
.ph-chosen-change { font-size: 13px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--groseille); text-decoration: none;
  border-bottom: 1px solid currentColor; padding-bottom: 2px; }

.ph-about #panel-about-body { max-width: 1160px; margin: 0 auto; padding: 0; }
/* The same measure as the heading above it: the cards started 12px left of it (2026-09-14). */
.ph-whois #panel-whois-body { max-width: 1160px; margin: 0 auto; }

/* HACKATHON callout --------------------------------------------- */
/* One card with the date block on the left (decision 5). The section is white, so the card carries
   the thin outline a white card on a white page gets (decision 11). */
/* Sides as every .ph-section: var(--gutter), then 24px from 760 down (2026-09-14). */
.ph-event { background: #fff; padding: 56px var(--gutter); }
.ph-event-link {
  display: grid;
  grid-template-columns: 170px minmax(0, 1fr);
  gap: 34px;
  align-items: center;
  padding: 34px 38px;
  border: 1px solid var(--border);
  background: #fff;
  color: var(--ardoise);
  text-decoration: none;
  max-width: 1160px;
  margin: 0 auto;
  border-radius: 12px;
  position: relative;
  transition: padding-left 0.2s, background 0.2s;
}
/* The 6px Carotte bar down the left edge is gone: it was the last of the
   left-bar-on-a-box devices, and the section now carries its accent the way
   every other section does, through an eyebrow. */
.ph-event-link:hover { background: var(--ground); }
/* An eyebrow, matching every other section head, rather than a solid chip. */
.ph-event-kicker {
  display: block;
  margin-bottom: 10px;
  font-family: var(--sans);
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--groseille);
}
.ph-event-date {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding-right: 30px;
  border-right: 1px solid var(--border);
}
.ph-event-days {
  white-space: nowrap;
  font-family: var(--display);
  font-size: 40px;
  font-weight: 600;
  line-height: 1;
  color: var(--ardoise);
}
.ph-event-month {
  margin-top: 8px;
  font-family: var(--sans);
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.ph-event-where {
  margin: 0 0 12px;
  font-size: 15px;
  color: var(--ink-muted);
}
@media (max-width: 760px) {
  .ph-event { padding: 40px 24px; }
  .ph-event-link { grid-template-columns: 1fr; gap: 18px; padding: 24px 22px; }
  .ph-event-date { flex-direction: row; align-items: baseline; gap: 12px; padding: 0 0 14px; border-right: 0; border-bottom: 1px solid var(--border); }
  .ph-event-month { margin-top: 0; }
}
/* Same 30px Newsreader as every other section title. */
.ph-event-title {
  font-family: var(--display);
  font-size: 30px;
  font-weight: 600;
  letter-spacing: -0.015em;
  margin-bottom: 14px;
  line-height: 1.08;
}
.ph-event-body {
  font-size: 16.5px;
  line-height: 1.6;
  color: var(--ink-soft);
  max-width: 60ch;
  margin: 0 0 18px;
}
.ph-event-body strong { color: var(--ardoise); font-weight: 600; }
.ph-event-cta {
  display: inline-block;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--groseille);
  border-bottom: 1px solid currentColor;
  padding-bottom: 2px;
}

/* FOOTER --------------------------------------------------------- */
/* Sides as every .ph-section. A fixed 52px started the footer 28px inside the column above it on a
   phone (2026-09-14). */
.ph-footer { background: var(--chrome); padding: 64px var(--gutter) 48px; color: #fff; }
@media (max-width: 760px) { .ph-footer { padding-inline: 24px; } }
.ph-footer-inner {
  max-width: 1160px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 48px;
}
@media (max-width: 700px) { .ph-footer-inner { grid-template-columns: 1fr; } }
.ph-footer-label {
  font-family:var(--sans);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  color: #fff;
  margin-bottom: 12px;
}
.ph-footer-text { font-family:var(--sans); font-size: 16px; line-height: 1.65; color: #fff; }
.ph-footer-text a { color: #fff; text-decoration: none; border-bottom: 1px solid rgba(255,255,255,0.45); }
.ph-footer-text a:hover { border-bottom-color: #fff; }
.ph-footer-text code { font-family: 'Courier New', monospace; font-size: 15px; background: rgba(255,255,255,0.08); padding: 2px 7px; }

/* ── MDH Context: hide deep sub-tab tiles for light audience tracks ── */
body.track-public   .mdh-subtile.mdh-sub-deep,
body.track-strategy .mdh-subtile.mdh-sub-deep {
  display: none;
}
body.mdh-deep-revealed .mdh-subtile.mdh-sub-deep { display: flex !important; }
/* Legacy .mdh-reveal-* selectors removed (2026-05-27); toggle now lives in
   .mdh-subtab-actions / .mdh-guide-toggle in mdh-map.css. */


/* ── 14. GLOBAL SEARCH (Ctrl/Cmd+K overlay) ──────────────────────────────────
   The header search button + the full-screen search overlay with a result list.
   Uses EPFL brand tokens; keyboard-navigable (arrow keys + Enter). */
/* Quieter than the Log in button beside it: search is a utility, not the
   header's call to action. The Ctrl/Cmd+K chip was dropped from the markup. */
.h-search-btn{display:inline-flex;align-items:center;gap:5px;background:transparent;border:1px solid rgba(255,255,255,.22);color:rgba(255,255,255,.82);font-family:var(--sans);font-size:13px;font-weight:500;padding:4px 10px;border-radius:12px;cursor:pointer;transition:background .15s,border-color .15s,color .15s;}
.h-search-ico{display:block}
.h-search-btn:hover{background:rgba(255,255,255,.10);border-color:rgba(255,255,255,.45);color:#fff;}
.h-search-btn kbd{font-family:var(--sans);font-size:12px;font-weight:700;background:rgba(255,255,255,.18);border-radius:4px;padding:1px 5px;letter-spacing:.5px;}
@media (max-width:700px){.h-search-btn kbd{display:none;}}

.search-overlay{position:fixed;inset:0;z-index:400;background:rgba(69,58,76,.55);display:none;align-items:flex-start;justify-content:center;padding:80px 16px;}
.search-overlay.open{display:flex;}
.search-box{background:#fff;border-radius:12px;width:min(640px,100%);box-shadow:0 24px 64px rgba(0,0,0,.35);overflow:hidden;display:flex;flex-direction:column;max-height:70vh;}
.search-input{border:none;border-bottom:2px solid var(--perle);padding:18px 22px;font-family:var(--sans);font-size:18.5px;color:var(--ardoise);outline:none;}
.search-input:focus{border-bottom-color:var(--canard);}
.search-results{list-style:none;margin:0;padding:6px;overflow-y:auto;flex:1;}
.search-result{display:flex;align-items:center;gap:12px;padding:11px 14px;border-radius:12px;cursor:pointer;}
.search-result.selected,.search-result:hover{background:#F2F1EE;}
.search-kind{flex:none;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;padding:3px 8px;border-radius:999px;}
.search-kind-actor{background:#fff;border:1.5px solid var(--zinzolin);color:var(--zinzolin);}
.search-kind-work{background:var(--vert-deau);color:var(--canard);}
.search-kind-project{background:#fff;border:1.5px solid var(--carotte);color:var(--carotte);}
/* Glossary terms and Deeper dive topics are searchable too (A7, 2026-09-14). */
.search-kind-glossary{background:#fff;border:1.5px solid var(--canard);color:var(--canard);}
.search-kind-topic{background:var(--perle);color:var(--ardoise);}
/* Where a search result lands, for a moment, so the eye finds it. */
.search-hit{outline:2px solid var(--canard);outline-offset:4px;border-radius:4px}
/* Events and "other" works get their own kind, in the Ardoise the work cards use for type (A5, 2026-09-14). */
.search-kind-event,.search-kind-other{background:#fff;border:1.5px solid var(--ardoise);color:var(--ardoise);}
.search-body{display:flex;flex-direction:column;min-width:0;}
.search-title{font-size:16px;font-weight:600;color:var(--ardoise);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.search-sub{font-size:13.5px;color:var(--ink-muted);}
.search-empty{padding:24px;text-align:center;color:var(--ink-muted);font-size:16px;}
.search-hint{padding:9px 16px;border-top:1px solid var(--perle);font-size:13px;color:var(--ink-muted);background:#faf9fa;}
/* On phones the type pill took a third of the row and titles were cut after about 20 characters. */
@media (max-width:600px){
  .search-result{flex-direction:column;align-items:flex-start;gap:5px}
  .search-title{white-space:normal;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
}
@media (hover:none){.search-hint{display:none}}

/* Figure source line under a chart grid / graph: quiet, italic, cited provenance
   so a visualisation reads as informational content, not a raw dev widget. */
.figure-source{margin:14px 2px 0;font-size:13.5px;font-style:italic;color:var(--ink-muted);line-height:1.5;max-width:none;}

/* Live result summary under the actor filters: "Showing N actors" or, when the
   filters match nothing, a short empty-state with a one-click reset. */
.result-summary{margin:18px 0 8px;font-size:15px;min-height:22px;}
.result-count{font-weight:700;color:var(--ardoise);}
.result-empty{color:var(--ardoise);}
.result-empty-reset{margin-left:8px;font:inherit;font-weight:700;color:var(--canard);background:none;border:1.5px solid var(--canard);border-radius:999px;padding:3px 12px;cursor:pointer;transition:background .18s ease,color .18s ease;}
.result-empty-reset:hover{background:var(--canard);color:#fff;}

/* Dataset download link (Publications tab). */
/* Positive top margin. This was -8px, which pulled the download pill up into the
   .figs row above it in the page header and overlapped the stat labels. The
   negative value dated from a layout where this line followed a paragraph, not a
   stat row. */
.dataset-download{margin:18px 0 4px;font-size:15px;}
.dataset-download a{display:inline-flex;align-items:center;gap:6px;font-weight:700;color:var(--canard);text-decoration:none;border:1.5px solid var(--canard);border-radius:999px;padding:6px 14px;transition:background .15s,color .15s;}
.dataset-download a:hover{background:var(--canard);color:#fff;}
.dataset-download-note{color:var(--ink-muted);margin-left:10px;font-size:13.5px;}
@media (max-width:600px){.dataset-download-note{display:block;margin:8px 0 0}}

/* ── 15. PRINT / PDF ─────────────────────────────────────────────────────────
   Make the active tab printable: drop the fixed chrome, nav, filters, search
   and interactive controls; show content full-width in black-on-white; keep
   links readable. Print from any content tab (Ctrl/Cmd+P). */
@media print{
  .site-chrome,.topnav,header,.skip-link,.search-overlay,
  .sf-bar,.sf-search-row,.sf-actions-row,.sf-collapse,.view-toggle,
  .funnel-row,.chart-grid,.dataset-download,.h-search-btn,
  #status-filters-wrap,#features-filters-wrap{display:none !important;}
  body{background:#fff;color:#000;}
  .main{padding-top:0 !important;}
  .panel{display:none !important;}
  .panel.active{display:block !important;padding:0 !important;}
  .panel-inner,.panel-inner-bare{border:none !important;padding:0 !important;}
  a{color:#000;text-decoration:underline;}
  /* Expand any collapsed content so nothing is hidden on paper. */
  .rcard-body,.rm-discuss-body,details[open],.mdh-tab-panel{display:block !important;}
  /* Avoid breaking cards across pages where possible. */
  .rcard,.proj-card,.beyond-card,.mdh-def-card,.funding-card{break-inside:avoid;}
  .page-headline-title{color:#000 !important;}
}

/* ── 16. POLISH LAYER ────────────────────────────────────────────────────────
   Small, cross-cutting refinements applied last (so they win on the shared
   card/button rules above without editing each page section). Three groups:
     A. Consistent card hover "lift" so every clickable card behaves alike.
     B. A single keyboard-focus ring on interactive elements (a11y + finish).
     C. Gentle transition timing. All motion is disabled for users who set
        "reduce motion" (see the media query in Section 2). Colours stay on the
        EPFL palette; nothing here changes layout or content. */

/* A. Card lift ─ every clickable card rises a touch and deepens its shadow on
   hover. .proj-card had a transition declared but no hover state; the rest lift
   inconsistently. Unify them. Transition transform+shadow only (not `all`). */
.rcard{transition:transform .18s ease,box-shadow .18s ease;}
.rcard:hover{transform:translateY(-2px);box-shadow:0 8px 20px rgba(0,0,0,.12);}
.ins-card{transition:transform .18s ease,box-shadow .18s ease;}
.ins-card:hover{transform:translateY(-2px);box-shadow:0 8px 20px rgba(0,0,0,.13);}
.beyond-card{transition:transform .18s ease,box-shadow .18s ease,border-color .18s ease;}
.beyond-card:hover{transform:translateY(-2px);box-shadow:0 8px 20px rgba(69,58,76,.12);}

/* B. Focus-visible ring ─ keyboard users get a clear, consistent Canard outline
   on every interactive control. Mouse users are unaffected (:focus-visible only
   fires for keyboard/AT focus). */
a:focus-visible,button:focus-visible,[role="button"]:focus-visible,
.sf-btn:focus-visible,.rcard:focus-visible,.proj-card:focus-visible,
.tab-btn:focus-visible,.topnav-tab:focus-visible,.topnav-subtab:focus-visible,
input:focus-visible,select:focus-visible{
  outline:2px solid var(--canard);outline-offset:2px;border-radius:4px;
}

/* C. Gentle timing for the interactive controls that used snappier/`all`
   transitions, so hover/active feels smooth and uniform. */
.sf-btn{transition:background .18s ease,border-color .18s ease,color .18s ease,transform .18s ease;}
.sf-btn:active{transform:translateY(1px);}


/* ══ Research Directions (partials/research-direction.html, panel-research-direction.js)
   This tab is set to the visual system of the landing page and the What is MDH?
   overview. It was the one banded tab reading differently from both: loose band
   children whose ledes came out centred, and a dense three-across grid of
   bordered white cards, a card treatment neither reference page uses.
   Everything below is scoped to #panel-research-direction where it re-sizes
   shared type, so beyond / funding / takeaways keep the sizes they have.
   ══════════════════════════════════════════════════════════════════════════ */

/* ONE LEFT EDGE. Measured in chromium at 1440: the tab had three. The page
   head started at 148, the bands at 100, and the pull quote at 48, so the eye
   found a new margin three times on the way down. Both reference pages hold a
   single edge (What is MDH? at 100 throughout, the landing at 140), and that is
   most of what made this tab read as a different page.

   The header stray is FIXED IN THE MARKUP, not here. .page-head-main's
   padding-left is max(gutter, 50vw - measure/2), the formula that centres a
   full-bleed header at the measure, and it only computes to a left edge if its
   parent spans the window. .page-head:has(.scope-note) is what makes it span.
   This tab used to carry the one-line .scope-line variant, so that rule never
   matched: the header stayed inside the panel's 48px gutter and the 100px
   padding stacked on top of it, at 148. Three RD-scoped overrides used to sit
   here undoing that. The tab now carries the full .scope-note panel, verbatim
   from mdh.html, so the rule matches and the header lands at 100 the same way
   What is MDH? does, with no override at all. Do not reintroduce them: with
   .scope-note present they fight the grid rule instead of helping it.

   The pull quote is a separate cause and still needs a rule. .pull sets
   margin:34px 0, which lands after .band > *'s margin-inline:auto in the
   cascade at equal specificity and cancels it, dropping the quote to the band's
   padding edge at 48. Re-asserting the inline margins keeps the 34px block
   margins the shorthand is actually there for.
   Beyond EPFL, Takeaways and Funding were the last three still carrying
   .scope-line; they have since had the same markup swap, so every tab now holds
   one edge. */
#panel-research-direction .pull{margin-inline:auto}

/* Section head. .band > * caps every child at var(--measure) AND centres it
   with margin-inline:auto, and .band p clamps prose to 62ch on top of that, so
   a bare <p> lede was centred inside the measure and read as indented under a
   headline that filled the width. Wrapping eyebrow + headline + lede in one
   block moves the centring up to the wrapper: the block is centred at the
   measure, its children flow left together. .mdh-page-section-hd (mdh-map.css)
   and .ph-section-hd do exactly this on the two reference pages.
   Not scoped, because it is a plain layout block any banded tab can adopt. */
.band-hd{margin-bottom:28px}
.band-hd-last{margin-bottom:0}
/* The type inside .band-hd (30px headline, 16.5px lede at 760px) and the
   demoted .section-head that goes with it USED TO BE DECLARED HERE, scoped to
   this tab alone. They moved to "3b-i. THE TABS ALIGNED TO What is MDH?" in
   section 3b when the three Mapping EPFL subtabs were aligned to the same
   reference and wanted the same numbers. The values did not change; the
   selector lists gained three panel ids. Align the next tab by adding its id
   there, not by re-declaring the sizes here.
   The one-left-edge fix for this tab is still below: it is the same fault the
   Mapping EPFL tabs had, but the two are written as separate rules because
   this tab needs .pull re-centred as well and they do not. */
#panel-research-direction .band p.band-lede{margin:0}

/* The thesis / lab pair, as the landing page's .abt-card.frame-* panels: no
   border, a wash of the accent, the label and heading carrying the colour.
   .band-two's 64px gap is a gap between columns of prose; between two filled
   panels it reads as a hole, so the pair closes to the 18px .abt-cards-stack
   uses. */
.rd-pair{gap:18px}
.rd-note{padding:24px 28px 26px;border-radius:10px}
.rd-note.a-leman{background:#E6F6F5}
.rd-note.a-zinzolin{background:#EFEAF3}
.rd-note .display-m{margin-top:0}
.band .rd-note p.eyebrow{color:var(--ardoise)}
.band p.rd-note-b{font-size:15.5px;line-height:1.62;color:rgba(69,58,76,.86);max-width:none;margin:0}

/* The direction cards, the .mdh-rat-entry pattern from the What is MDH?
   rationale: a card, but barely. Each accent sits at 4% on white rather than
   the 8-12% the bordered cards elsewhere use, so the tint reads as a tone and
   the colour itself lands on the numeral and the heading, where it clears AA
   (Canard 5.5, Zinzolin 10.3, Marron 10.7, Groseille 7.6). Two across, not
   three: these carry a question, a why, and a disclosure, which is more than a
   340px column holds at a readable measure. */
.rd-grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--grid-gap);margin-top:8px;align-items:start}
@media (max-width:900px){.rd-grid{grid-template-columns:1fr;gap:22px}}
.rd-card{padding:26px 30px 28px;border:none;border-radius:12px;text-align:left;background:#F9F7F7}
.rd-card.a-canard{background:#F5F9FA}
.rd-card.a-zinzolin{background:#F9F6FA}
.rd-card.a-marron{background:#F9F7F7}
.rd-card.a-groseille{background:#FDF6F6}
/* The card opens on one eyebrow in its own colour: the number, then the title. */
/* The title is a long question, so it reads as a heading in sentence case, not as small letter-spaced
   capitals over two lines (A46, 2026-09-14). */
.rd-card-title{font-family:var(--display);font-size:19px;font-weight:600;letter-spacing:-.005em;
  text-transform:none;line-height:1.3;margin:0 0 10px}
.rd-card-n{margin-right:.3em}
.rd-card.a-canard .rd-card-title{color:var(--canard)}
.rd-card.a-zinzolin .rd-card-title{color:var(--zinzolin)}
.rd-card.a-marron .rd-card-title{color:var(--marron)}
.rd-card.a-groseille .rd-card-title{color:var(--groseille)}
.rd-card-q{font-size:16.5px;line-height:1.6;color:var(--ardoise);margin:0 0 14px}
.rd-card-lbl{font-family:var(--sans);font-size:12.5px;font-weight:700;text-transform:uppercase;
  letter-spacing:.15em;color:var(--ink-muted);margin:14px 0 4px}
.rd-card-body{font-size:16px;line-height:1.6;color:rgba(69,58,76,.86);margin:0}
/* Banding the tab moved #rd-grid inside a .band, so the card paragraphs became
   descendants of one and .band p (0,1,1) started outranking .rd-card-q and
   .rd-card-body (0,1,0): the question went 16px -> 17px and the body 15px ->
   17px, both losing their own colour to the faded band prose. Same failure as
   the eyebrows and the Status hints; same remedy, re-asserted at matching
   specificity rather than by editing the base rules, which are correct for the
   un-banded case. max-width is released too, because 62ch is a page measure and
   these sit in a half-measure card. */
.band p.rd-card-q{font-size:16.5px;line-height:1.6;color:var(--ardoise);max-width:none;}
.band p.rd-card-body{font-size:16px;line-height:1.6;color:rgba(69,58,76,.86);max-width:none;}
.rd-card-more{margin-top:14px}
.rd-card-more summary{font-size:13.5px;font-weight:700;cursor:pointer;list-style:none}
.rd-card.a-canard .rd-card-more summary{color:var(--canard)}
.rd-card.a-zinzolin .rd-card-more summary{color:var(--zinzolin)}
.rd-card.a-marron .rd-card-more summary{color:var(--marron)}
.rd-card.a-groseille .rd-card-more summary{color:var(--groseille)}
.rd-card-more summary::-webkit-details-marker{display:none}
.rd-card-more summary::before{content:"+ ";font-weight:700}
.rd-card-more[open] summary::before{content:"- "}

/* ══ Contribute ═════════════════════════════════════════════════════════════
   The submission tab: the paper/profile pair, the switch and the embedded
   Google Form. The three numbered steps this used to name are gone.

   This tab holds ONE LEFT EDGE the Research Directions way, structurally: each
   band head is wrapped in .band-hd, and #panel-contribute is on the three
   selector lists in 3b-i that set the aligned type. It is deliberately NOT on
   the ONE LEFT EDGE padding list above, for the reason given there - a tab that
   wraps its heads would have its bands padded twice. What .band-hd does not
   cover is the loose children in the form band, so those are aligned one at a
   time below; there are three of them and they are named.
   ══ */

/* The paper / person pair, taking .rd-pair's treatment verbatim: no border, a
   wash of the accent, and the 18px gap a pair of filled panels wants instead of
   .band-two's 64px prose gutter. Leman and Groseille rather than Leman and
   Zinzolin, so the two halves read as the two things the tab collects rather
   than as a copy of the Research Directions pair. Both washes are the chart
   colour at 8% on white, which is where #E6F6F5 came from. */
/* align-items:stretch rather than .band-two's start. Two filled panels of
   unequal height read as a ragged edge; two columns of prose do not, which is
   why the base rule is right and this override is scoped to the pair. */
.ctb-pair{gap:18px;align-items:stretch}
.ctb-note{padding:24px 28px 26px;border-radius:12px}
.ctb-note.a-leman{background:#E6F6F5}
.ctb-note.a-groseille{background:#F9EDED}
/* On a tinted card the eyebrow takes the card's colour (decision 7). */
.band .ctb-note.a-leman p.eyebrow{color:var(--leman-txt)}
.ctb-note .display-m{margin-top:0}
/* Eyebrow colour left alone: these were the only non-Groseille eyebrows on the
   site, which made two cards look like a different system. */
/* Same specificity fight .rd-note-b documents: .band p (0,1,1) outranks a bare
   class, so the card body would take band-prose size, colour and the 62ch page
   measure inside a half-measure card. Re-asserted at matching specificity
   rather than by weakening .band p, which is right for the un-carded case. */
.band p.ctb-note-b{font-size:16.5px;line-height:1.6;color:rgba(69,58,76,.86);max-width:none;margin:0}

/* ── The switch ────────────────────────────────────────────────────────────
   Two buttons in a rounded track, the shape the site uses for a choice between
   two views rather than a filter. It is rendered by panel-contribute.js and
   stays hidden when only one form is connected: a switch with one option is a
   label pretending to be a control. */
.ctb-switch{display:inline-flex;gap:4px;padding:4px;margin:0 0 20px;
  background:var(--ground-tint);border-radius:999px}
.ctb-switch[hidden]{display:none}
.ctb-switch-btn{appearance:none;border:none;cursor:pointer;
  font-family:var(--sans);font-size:14px;font-weight:600;letter-spacing:.1px;
  color:var(--ink-muted);background:transparent;padding:9px 20px;border-radius:999px;
  transition:background .15s,color .15s,box-shadow .15s}
.ctb-switch-btn:hover{color:var(--ardoise)}
/* The selected half is a white chip lifted off the track. Not a colour fill:
   the accent on this tab belongs to the two cards above, and repeating it here
   would make the switch compete with them. */
.ctb-switch-btn.active{background:#fff;color:var(--ardoise);
  box-shadow:0 1px 2px rgba(69,58,76,.14)}
.ctb-switch-btn:focus-visible{outline:2px solid var(--canard);outline-offset:2px}

/* ── The embed ─────────────────────────────────────────────────────────────
   A Google Form in a frame, on a white card so the form's own white ground has
   an edge rather than bleeding into the band.

   FIXED HEIGHT, ON PURPOSE. A cross-origin frame cannot report its content
   height, and Google sends no resize message, so there is no honest way to fit
   the frame to the form. It is sized to a tall-but-bounded window and the form
   scrolls inside it: clamp keeps it from overflowing a laptop viewport at the
   top end and from collapsing to a letterbox at the bottom. Changing the form's
   length does not require touching this. */
.ctb-embed-wrap{background:#fff;border-radius:12px;overflow:hidden;
  box-shadow:0 1px 3px rgba(69,58,76,.12)}
.ctb-frame{display:block;width:100%;height:clamp(560px,86vh,1000px);border:none}
.ctb-frame[hidden]{display:none}

/* The card holds a form only when one is connected; until then it stays hidden and the steps above carry
   the email (CT1 A, 2026-09-14). The grey fallback box that repeated them is gone. */
.ctb-embed-wrap[hidden]{display:none}
.ctb-steps + .ctb-switch,.ctb-steps ~ .ctb-embed-wrap{margin-top:26px}

/* The way out for a reader behind a network that blocks Google, or who will not
   fill a Google form. Stays visible under a form that loaded fine.
   .band p clamps a loose paragraph to 62ch and `.band > *` centres it, which
   together float it a third of the way across the page - the second of the two
   causes ONE LEFT EDGE describes. Released and pinned left here. */
#panel-contribute .band p.ctb-alt{font-size:14.5px;line-height:1.55;
  color:var(--ink-muted);max-width:none;margin:16px 0 0;margin-inline:0}
#panel-contribute .band p.ctb-alt[hidden]{display:none}
.ctb-alt a{color:var(--groseille);font-weight:600}

/* How to send it (CT1 A, 2026-09-14): where, what to include, what happens next, as three numbered steps
   under the heading. Not the old "checked, placed, published" steps, which told one promise three times:
   each of these says something the reader needs to do or know. */
/* margin-block, not the margin shorthand: the shorthand wrote margin-inline:0 and cancelled the centring
   `.band > *` gives every band child, so the steps started 52px left of their heading (2026-09-15). */
.ctb-steps{list-style:none;margin-block:4px 0;padding:0;display:grid;gap:14px;counter-reset:ctb}
.ctb-steps li{display:grid;grid-template-columns:30px minmax(0,1fr);gap:14px;align-items:center;
  font-size:17px;line-height:1.55;color:var(--ardoise);counter-increment:ctb}
.ctb-steps li::before{content:counter(ctb);display:grid;place-items:center;width:28px;height:28px;border-radius:50%;
  background:var(--ardoise);color:#fff;font-size:13px;font-weight:700;line-height:1}
.ctb-steps a{color:var(--groseille);font-weight:600;text-decoration:underline;text-underline-offset:3px}
.ctb-steps a:hover{color:var(--ardoise)}

/* ── Internal login: header button, internal-only UI, login modal ─────────── */
.h-login-btn{font-family:var(--sans);font-size:13.5px;font-weight:700;color:#fff;
  background:transparent;border:1.5px solid rgba(255,255,255,.35);border-radius:999px;
  padding:6px 14px;cursor:pointer;transition:background .15s,border-color .15s;margin-left:10px}
.h-login-btn:hover{background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.6)}
body.internal-unlocked .h-login-btn{border-color:var(--leman);color:var(--leman)}
/* Internal-only UI: the internal Actors-view toggle and the Draft nav tab.
   Hidden until the reader logs in (body.internal-unlocked). */
.internal-only,[data-action="setActorView"][data-arg="internal"]{display:none !important}
body.internal-unlocked .internal-only,
body.internal-unlocked [data-action="setActorView"][data-arg="internal"]{display:inline-flex !important}
/* Table rows need their own reveal. The rule above restores `inline-flex`, which on a <tr>
   collapses the th/td into a single inline box and loses the column alignment entirely. The
   Relevance and Key terms rows of the label table are internal-only, so they need `table-row`.
   Type-qualified, so it outranks the class-only rule without another !important war. */
tr.internal-only{display:none !important}
body.internal-unlocked tr.internal-only{display:table-row !important}
.login-overlay{position:fixed;inset:0;z-index:600;display:none;align-items:flex-start;justify-content:center;
  background:rgba(69,58,76,.55);padding:80px 20px}
.login-overlay.open{display:flex}
.login-modal{position:relative;width:100%;max-width:420px}
.login-modal .tab-gate-card{max-width:none}
.login-close{position:absolute;top:-38px;right:0;background:none;border:none;font-size:30px;line-height:1;color:#fff;cursor:pointer}

/* Freshly relabelled works are pinned to the top of the grid; a left accent marks them
   so a reviewer can tell at a glance which cards carry the current taxonomy. */
.rcard.is-relabelled{border-left:3px solid var(--canard)}

/* The kind of an EPFL-built artifact (dataset / tool / model), shown inside its chip so a
   reader knows what the thing IS without opening the paper. Quieter than the name it
   qualifies, and quieter still than the EPFL mark itself. */
.bib-topic.is-built .built-kind {
  opacity: 0.7;
  font-style: italic;
  margin-left: 0.15em;
}

/* Jury marker on a relabelled work. Internal signal rather than public copy: it tells whoever
   is auditing which works have been through the v4 panel and how many jurors decided them,
   since a two-juror panel needs unanimity where a three-juror one needs a majority. Sits
   beside the type badge and stays quieter than it. */
.rcard-jury {
  margin-left: 0.5em;
  padding: 0.05em 0.45em;
  border: 1px solid var(--canard);
  border-radius: 999px;
  color: var(--canard);
  font-size: 0.72em;
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

/* ── Scope note ───────────────────────────────────────────────────────────
   Beside every page title: the site follows the UN M/D/H framing but reads the
   H pillar wider than "hate speech". Collapsed to one line, expandable in
   place. No box and no accent colour: it is a qualification on the data, not a
   warning, and a coloured panel here would compete with the pillar colours it
   is explaining. ── */
/* Two columns, each with its own ground, both running the full height of the
   block. It breaks out of the band gutter so the two grounds meet the page
   edges, and each column restores that gutter as padding. */
/* The two grounds are the section: they start at its very top, so the panel's
   own top padding is pulled back rather than showing above them. */
/* Every page header is one full-bleed column since the disclaimer panel beside the title became a
   line under it (2026-09-13). Keyed on .page-head-main rather than on the panel it used to hold, at
   the same specificity, so nothing else in the cascade moves. One column since 2026-09-14 (L1 A): the
   title and its text start and end where the page content under them does, --page-inset on both sides,
   and the text runs the full width of that column. */
.page-head:has(> .page-head-main){display:grid;
  grid-template-columns:minmax(0,1fr);gap:0;
  align-items:stretch;max-width:none;
  margin:calc(var(--panel-pad-top) * -1) calc(var(--gutter) * -1) 0;
  min-height:100%}
.page-head-main{min-width:0;background:var(--ground);
  padding:34px var(--page-inset) 40px}
@media (max-width:1000px){
  .page-head-main{padding:28px var(--gutter) 24px}
}
/* The UN-framing disclaimer: one quiet line under every page title, with Why leading to the H
   definition (decision 20, 2026-09-13). It replaced a panel beside the title. */
:is(.page-head,.page-headline) .page-note{font-size:13.5px;line-height:1.5;color:var(--ink-muted);margin:16px 0 0;max-width:none}
.page-note-link{padding:0;background:none;border:none;font:inherit;font-weight:600;color:var(--ink-muted);
  cursor:pointer;text-decoration:underline;text-underline-offset:2px}
.page-note-link:hover{color:var(--ardoise)}
:is(.page-head,.page-headline) .page-note-lbl{font-weight:600;color:var(--ardoise)}

/* The header band hugs its two columns; the columns provide the padding. */
.band:has(> .page-head:has(> .page-head-main)){padding-top:0;padding-bottom:0}

/* The intro is the section split in two, so nothing sits between the two
   grounds and whatever follows. */
.page-head:has(> .page-head-main){margin-bottom:0}
.page-head:has(> .page-head-main) + *{margin-top:0}

/* The intro columns run straight into the first section: the wrapping band
   contributed a strip of page ground under them, and the sections inside it
   already pad themselves. */
.band:has(> #panel-mdh-body),.band:has(> #panel-glossary-body){padding-top:0}

/* The three letters in their pillar colours, the same device the wordmark and
   the definition entries use. Decorative marks on a word that is spelled out
   immediately after, so contrast is not load-bearing here. */
.mdh-l{font-style:normal;font-weight:700;font-size:1.15em;line-height:0}
.mdh-l.m{color:#ED6E9C}
.mdh-l.d{color:#4F8FCC}
.mdh-l.hs{color:#F39869}


/* ══ Scope note ═════════════════════════════════════════════════════════
   Every tab now carries the same full .scope-note panel in its .page-head,
   verbatim from mdh.html. The one-line .scope-line variant that used to sit
   here ("One line under the page lead, on every tab except What is MDH?") was
   retired once the last three tabs, Beyond EPFL, Takeaways and Funding, moved
   over; its rule is deleted rather than left as dead weight, since the class it
   styled has no users left in site/partials/. Reinstating the one-liner means
   reinstating this rule AND the +100px header offset it caused: .page-head:has(
   .scope-note) is what lays every page head out. ══ */

/* Whitespace between the header and the band below it was forming a line box,
   leaving a strip of page ground under the two intro grounds. A flex container
   ignores whitespace-only text nodes, which removes the strip without a magic
   negative margin. Column direction keeps the children stacking as blocks. */
.panel.active:has(> .page-head .page-head-main){display:flex;flex-direction:column}


/* ══ WORKS TABLE VIEW ════════════════════════════════════════════════════════
   One row per work, sortable, with a filter menu on each column header. A second
   view of the same filtered set, not a second dataset: it exists for the question
   the card grid cannot answer, "which works carry H and no theme".

   Colours are the ones already assigned elsewhere on the tab, so nothing here
   teaches the reader a new key: Canard for themes and the sorted column, Marron
   for Built at EPFL, Groseille for an exclude, Rose / Acier / Mont Rose for the
   three pillars and nothing else. */
.features-bib-bar{display:flex;flex-wrap:wrap;align-items:baseline;gap:10px 18px;margin:6px 0 14px}
.features-bib-bar .features-bib-count{margin:0}
.bib-view-sw{margin-left:auto;display:flex;border:1px solid var(--border);border-radius:9px;overflow:hidden;background:#fff}
.bib-view-btn{appearance:none;border:0;cursor:pointer;background:transparent;color:var(--ardoise);
  font-family:var(--sans);font-size:13.5px;font-weight:600;padding:7px 15px;line-height:1.3;
  transition:background .15s,color .15s}
.bib-view-btn + .bib-view-btn{border-left:1px solid var(--border)}
.bib-view-btn:hover{background:rgba(31,42,55,.05)}
.bib-view-btn[aria-pressed="true"]{background:var(--ardoise);color:#fff}
.bib-view-btn:focus-visible{outline:2px solid var(--groseille);outline-offset:-2px}

/* The table scrolls inside its own box so the page never scrolls sideways. */
/* Seventeen columns do not fit any screen, so the table scrolls sideways inside its own
   box and the scrollbar is made PERMANENT rather than overlay-on-hover. An auto-hiding
   scrollbar on a table this wide reads as a table that simply ends at the window edge:
   the reader never learns there are ten more columns. Height and colour are set for the
   same reason - it has to be visible before you touch it. */
.bib-table-wrap{background:none;border:0}
@media (max-width:700px){.bib-view-sw{display:none}}
/* The container is as tall as the table, which is about 9500px for 103 works. Its
   horizontal scrollbar therefore sat at the very bottom of the page: the only way
   to reach columns 9 to 15 was to scroll past every row first, and while reading
   any row the bar was thousands of pixels below the fold. Capping the height
   makes the table its own viewport, so the bar is on screen the whole time. The
   header is already position:sticky, so it stays put inside it. */
/* Since 2026-09-13 (decision 17) the page scrolls the table: no height cap, no box. The sideways
   scroll returns only with .is-wide, which fitBibTable in panel-biblio.js sets when the chosen
   columns do not fit. */
.bib-table-scroll{overflow:visible;background:#fff;
  border:1px solid var(--border);border-radius:12px;
  scrollbar-color:var(--ardoise) rgba(31,42,55,.09);scrollbar-width:auto}
.bib-table-scroll::-webkit-scrollbar{height:14px}
.bib-table-scroll::-webkit-scrollbar-track{background:rgba(31,42,55,.07);border-radius:0 0 11px 11px}
.bib-table-scroll::-webkit-scrollbar-thumb{background:var(--ardoise);border-radius:7px;border:3px solid #fff}
.bib-table-scroll::-webkit-scrollbar-thumb:hover{background:var(--canard)}
.bt-filt-inline{font-family:var(--mono,monospace);border:1px solid var(--border);border-radius:4px;padding:0 4px}
/* A hint above the table, so the sideways scroll is announced rather than discovered. */
.bib-table-hint{display:flex;align-items:center;gap:7px;margin:0 0 8px;
  font-size:12.5px;color:var(--ink-muted)}
.bib-table-hint b{font-weight:700;color:var(--ardoise)}
.bib-table-scroll.is-wide{overflow-x:auto}
table.bib-table{border-collapse:separate;border-spacing:0;width:100%;min-width:100%;font-size:13.5px}
table.bib-table th{text-align:left;padding:0;vertical-align:bottom;background:#fff;
  border-bottom:1.5px solid var(--ardoise);position:sticky;top:0;z-index:3;white-space:nowrap}
/* In the page, the column names stick under the fixed site header. Inside a window that scrolls
   (a chart's works list) they stick to that window's own top. */
.bib-table-scroll:not(.is-wide) table.bib-table th{top:var(--main-top,0px)}
.sp-modal-list .bib-table-scroll table.bib-table th{top:0}
/* A header is two controls, not one: the label sorts, the menu button opens the filter
   list. Splitting them lets a reader filter a column without disturbing the sort. */
.bt-th{display:flex;align-items:center;gap:2px;padding:9px 12px 7px;position:relative}
/* 24px minimum on the three controls that were under it: WCAG 2.2 SC 2.5.8
   Target Size (Minimum), Level AA. The sort buttons were 18px tall, the column
   filter buttons 20x21 and Expand 22px. Each sits inside a cell or bar with its
   own padding, so raising the control's own box does not move the layout. */
.bt-sort{appearance:none;min-height:24px;border:0;background:transparent;cursor:pointer;padding:2px;
  font-family:var(--sans);font-size:11px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:var(--ink-muted);display:inline-flex;align-items:center;gap:5px;line-height:1.3}
.bt-sort:hover{color:var(--ardoise)}
.bt-sort:focus-visible,.bt-filt:focus-visible{outline:2px solid var(--groseille);outline-offset:1px}
.bt-arrow{opacity:.4;font-size:11px}
th.is-sorted .bt-sort{color:var(--canard)}
th.is-sorted .bt-arrow{opacity:1}
th.is-sorted{border-bottom-color:var(--canard)}
.bt-filt{appearance:none;min-width:24px;min-height:24px;border:1px solid transparent;border-radius:5px;background:transparent;
  cursor:pointer;color:var(--ink-muted);font-size:13px;line-height:1;padding:3px 5px;
  display:inline-flex;align-items:center;gap:3px}
.bt-filt:hover{background:rgba(31,42,55,.07);color:var(--ardoise)}
.bt-filt.on{color:var(--canard);border-color:var(--canard);background:rgba(0,116,128,.08)}
.bt-badge{font-size:10px;font-weight:800;background:var(--canard);color:#fff;border-radius:8px;padding:0 4px;min-width:14px;text-align:center}
.bt-badge.is-ex{background:var(--groseille)}

table.bib-table td{padding:10px 12px;border-bottom:1px solid var(--border);vertical-align:top}
table.bib-table tbody tr:last-child td{border-bottom:0}
table.bib-table th.tight,table.bib-table td.tight{width:1%;white-space:nowrap}
.bt-row{cursor:pointer;transition:background .12s}
.bt-row:hover td{background:rgba(0,116,128,.045)}
.bt-row:focus-visible{outline:2px solid var(--groseille);outline-offset:-2px}
/* Works carrying current-taxonomy labels are marked the way the cards mark them. */
/* No accent rule on the left edge. The teal bar marked "relabelled under the current
   taxonomy", which is now true of almost every row, so it read as a border on the table
   itself rather than as information. */
.bt-title{font-weight:600;font-size:14px;line-height:1.3;max-width:34ch}
.bt-sub{font-size:12px;color:var(--ink-muted);margin-top:2px}
.bt-chips{display:flex;flex-wrap:wrap;gap:5px}
.bt-empty{color:var(--ink-muted)}
/* ── Column picker ─────────────────────────────────────────────────────────
   A <details>, so it needs no JS to open and closes with Escape for free. It
   sits in the hint line where the column count already was, because the count is
   the thing that makes a reader want the control. */
.bt-cols{display:inline-block;position:relative}
.bt-cols > summary{cursor:pointer;font-weight:700;color:var(--ardoise);list-style:none;
  border-bottom:1px dotted rgba(69,58,76,.45)}
.bt-cols > summary::-webkit-details-marker{display:none}
.bt-cols[open] > summary{color:var(--canard);border-bottom-color:var(--canard)}
.bt-cols-menu{position:absolute;z-index:60;top:calc(100% + 6px);left:0;min-width:210px;
  background:#fff;border:1px solid var(--border);border-radius:12px;padding:10px 12px;
  box-shadow:0 10px 28px rgba(69,58,76,.18);display:flex;flex-direction:column;gap:2px}
.bt-cols-item{display:flex;align-items:center;gap:8px;font-size:13.5px;font-weight:500;
  color:var(--ardoise);padding:3px 2px;cursor:pointer;white-space:nowrap}
.bt-cols-item:hover{color:var(--canard)}
.bt-cols-item.is-fixed{opacity:.55;cursor:default}
.bt-cols-reset{margin-top:8px;padding-top:8px;border:0;border-top:1px solid var(--border);
  background:none;font-family:var(--sans);font-size:12.5px;font-weight:700;color:var(--canard);
  cursor:pointer;text-align:left}

.bt-more{font-size:12px;font-weight:700;color:var(--ink-muted);align-self:center;cursor:help}
.bt-none{padding:26px 14px;text-align:center;color:var(--ink-muted);font-size:14.5px}
table.bib-table .bib-topic{font-size:12px;padding:3px 8px}
/* Long names wrap inside their cell rather than running over the next column: with all 15 columns
   on, Built at EPFL chips crossed into Type, and Lab and Author chips into their neighbours (B28,
   2026-09-14). Only these three wrap, each over a width floor. Letting every chip wrap let the table
   squeeze its columns, and 131 short Author and 39 Data chips broke onto two lines. */
table.bib-table td:has(> .bt-chips > .f-lab),table.bib-table td:has(> .bt-chips > .f-actor){min-width:210px}
table.bib-table td:has(> .bt-chips > .is-built){min-width:250px}
table.bib-table .bt-chips > .is-built,table.bib-table .bt-chips > .f-lab,table.bib-table .bt-chips > .f-actor{
  white-space:normal;max-width:100%;overflow-wrap:break-word}
/* Author chips take --f-actor-name like everywhere else: it was darkened site-wide to 5.33:1 (B8, B35,
   2026-09-14), so the table no longer needs a colour of its own. */
/* "Not recorded" in a cell, where an em dash was: a word says it, a dash had to be decoded (B29). */
.bt-empty{font-size:12px}

/* Pillar: always all three letters, with the absent ones as ghosts. "M and D, not H"
   is a different fact from "M and D", and only the full triplet says which you are
   looking at. These are the site's reserved pillar colours, used nowhere else. */
.bt-pil{display:inline-flex;gap:3px}
.bt-pil span{display:grid;place-items:center;width:21px;height:21px;border-radius:5px;
  font-size:12px;font-weight:700;color:var(--ink-muted);background:rgba(69,58,76,.05)}
/* White ink, on request. Measured, so the trade is on the record rather than
   discovered later: white on these three fills is Rose 2.6:1, Acier 3.0:1 and
   Mont Rose 2.0:1, all under the 4.5:1 AA floor, where Ardoise cleared it on all
   three. The letter is not carrying the meaning alone though - the square is
   always in its pillar's colour, always in M-D-H order, and always present even
   when unfilled, so position and hue both say which pillar it is before the
   glyph does. If this should pass AA as well, the fills have to darken; the
   letters cannot do it on their own. */
.bt-pil span.on{color:#fff}
.bt-pil span.on.p-m{background:var(--f-focus-m)}
.bt-pil span.on.p-d{background:var(--f-focus-d)}
.bt-pil span.on.p-h{background:var(--f-focus-h)}
/* Sentence case, not uppercase. Every other value label on the site reads in
   sentence case, so the closeness badge was the only one shouting, and small
   uppercase is the harder of the two to read. The shape, the weight and the
   letterspacing stay: those are what make it a badge rather than a pill. */
.bt-close{display:inline-flex;font-size:11.5px;font-weight:700;letter-spacing:.03em;
  text-transform:none;padding:3px 8px;border-radius:5px;white-space:nowrap}
.bt-close.is-direct{background:var(--ardoise);color:#fff}
.bt-close.is-adjacent{background:#fff;color:var(--ardoise);border:1.5px solid var(--ardoise)}
.bt-close.is-infrastructure{background:#fff;color:var(--ink-muted);border:1.5px dashed rgba(69,58,76,.42)}
/* Relevance as ticks: a jury score is compared across works far more often than it is
   read on one, and ticks compare down a column at a glance where "4 / 5" does not. */
.bt-rel{display:inline-flex;gap:2.5px;align-items:center}
.bt-rel i{width:6px;height:13px;border-radius:1.5px;background:rgba(69,58,76,.14)}
.bt-rel i.on{background:var(--ardoise)}

/* The column filter menu. Its chips are the filter bar's own .sf-chip in the same
   two states, restated here because the bar's rules are scoped to
   #features-filters-wrap and this menu lives inside the grid. */
.bt-menu{position:fixed;z-index:400;width:264px;max-height:330px;
  display:flex;flex-direction:column;background:#fff;border:1px solid var(--perle);
  border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,.16);padding:10px;white-space:normal}
.bt-menu-q{width:100%;margin:0 0 8px;padding:6px 9px;border:1px solid var(--border);
  border-radius:7px;font-family:var(--sans);font-size:13px;color:var(--ardoise)}
.bt-menu-q:focus-visible{outline:2px solid var(--canard);outline-offset:-1px}
.bt-menu-body{overflow-y:auto;display:flex;flex-wrap:wrap;gap:5px;min-height:0}
.bt-menu .sf-chip{display:inline-flex;align-items:center;cursor:pointer;
  padding:4px 10px;border-radius:11px;border:1px solid var(--border);background:#fff;
  color:var(--ardoise);font-family:var(--sans);font-size:12.5px;font-weight:600;line-height:1.35;
  transition:background .15s,border-color .15s,color .15s}
.bt-menu .sf-chip:hover{background:rgba(31,42,55,.05)}
.bt-menu .sf-chip.is-inc{background:var(--canard);border-color:var(--canard);color:#fff}
.bt-menu .sf-chip.is-exc{background:#fff;border-color:var(--groseille);border-style:dashed;
  color:var(--groseille);text-decoration:line-through}
.bt-menu-foot{display:flex;align-items:center;gap:8px;margin-top:9px;padding-top:8px;
  border-top:1px solid var(--border)}
.bt-menu-hint{font-size:11.5px;color:var(--ink-muted);line-height:1.3}
.bt-menu-clear{appearance:none;margin-left:auto;border:1px solid var(--border);border-radius:7px;
  background:#fff;cursor:pointer;font-family:var(--sans);font-size:12px;font-weight:600;
  color:var(--ardoise);padding:4px 10px;white-space:nowrap}
.bt-menu-clear:hover{border-color:var(--groseille);color:var(--groseille)}
/* The last columns' menus would otherwise open past the right edge of the scroll box. */
/* The old nth-last-child flip is gone: the menu is position:fixed and JS flips it
   against the VIEWPORT, which is the only edge that actually matters once the table
   scrolls sideways underneath it. */

/* The Work column is pinned left. With sixteen columns you are always scrolled away
   from the title, and a row of chips whose work you cannot see is unreadable. Needs an
   opaque background and a right edge of its own, or the scrolling cells show through.
   z-index 5 beats the sticky header's 3 so the top-left cell wins both stacking
   contexts; the header cell needs 6 for the same reason. */
table.bib-table td.bt-stick,table.bib-table th.bt-stick{
  position:sticky;left:0;z-index:5;background:#fff;
  box-shadow:1px 0 0 var(--border);min-width:250px;max-width:290px}
table.bib-table th.bt-stick{z-index:6}
.bt-row:hover td.bt-stick{background:#F7FBFB}

.bt-plain{font-size:13px}
.bt-num{font-variant-numeric:tabular-nums;font-size:13.5px}
/* Cells are capped so one work with nine platforms cannot set the row height for the
   whole table, and so a column keeps a predictable width while you scroll past it. */
table.bib-table td{max-width:210px}
table.bib-table td.tight{max-width:none}

/* Themes, Subtopics and Method hold prose-length labels ("Influence operations &
   coordinated manipulation"), not short codes. At the shared 210px cap every chip wrapped
   to three lines and set the row height for the whole table, so they get their own width
   and show three chips instead of two. */
table.bib-table td.bt-wide{max-width:340px;min-width:250px}
table.bib-table th.bt-wide{min-width:250px}

/* ══ THE SHARED FILTER BAR ════════════════════════════════════════════════════
   A third row in the fixed site chrome, under the subtab nav, shown only on Mapping
   EPFL. It is out here rather than inside a panel because Charts, Actors and
   Publications share ONE selection: sitting above the thing it filters is what makes
   that structural instead of a claim a label has to make.

   Collapsed it is a summary of its own result and costs about 40px. Expanded it drops
   the full 15-facet panel over the page. Canard throughout, because that is already
   the filter colour on this tab. */
/* CONTRAST. The chrome above this row goes Ardoise (topnav) then Perle (subnav); this
   row was white, so it read as the top of the PAGE rather than the bottom of the chrome.
   It now carries a Canard wash and a firm Canard bottom edge: Canard is already the
   filter colour on this tab, so the tint says what the row is as well as separating it
   from the white page below. */
/* CONTRAST, in the chart's own language. The radials build structure out of exactly two
   neutrals - Ardoise for the primary dividers and label ink, Perle for the secondary ones -
   and reserve Rose, Acier and Mont rose for the three pillars, which is data. A green
   filter row introduced a hue that means nothing in that system and sat next to hues that
   do. This is a wash of the SAME Ardoise the charts draw their structure in: darker than
   the white page below, lighter than the Perle subnav above, and carrying no colour that
   could be mistaken for a pillar. */
.topnav-filter {
  /* 16% Ardoise, chosen by measurement rather than eye. Perle sits 1.68:1 from the white
     page; this lands 1.32:1 from the page and 1.27:1 from Perle, so it is a real step down
     from the content AND still clearly lighter than the subnav above it. 26% was tried and
     reads at 1.06:1 against Perle, which merges the two chrome rows into one slab. */
  /* A very light vertical gradient, not a texture. The diagonal hatch that was
     here read as machined, which was the point, but it also competed with the
     graph paper the card sections now carry, and two rulings in one viewport is
     one too many. A gradient does the same job with nothing to compete: it is a
     surface, not a pattern.
     Still Ardoise rather than a hue, for the reason the note above gives. Every
     colour on this page already means something (Rose, Acier and Mont Rose are
     the pillars, the facet key spends eight more), so a bar that stood out by
     hue would be claiming to be data.
     Deepest where it meets the Perle subnav and falling away downward, so the
     row is anchored at the top and hands off cleanly to the panel below. 13% to
     4%: the flat wash it replaces was a uniform 16%, so this is lighter
     throughout, and the 3px Ardoise seam below is what keeps it distinct from
     the white page rather than the fill doing the work. */
  background:
    linear-gradient(180deg, rgba(69,58,76,.13) 0%, rgba(69,58,76,.07) 62%, rgba(69,58,76,.04) 100%),
    #fff;
  border-top: 1px solid rgba(69,58,76,.18);
  /* 3px, up from 2px: this is the seam between the chrome and the page, and it is
     the one edge that tells a reader the filter is not part of what it filters. */
  border-bottom: 3px solid var(--ardoise);
  /* Sticky: this bar is the one control the three Mapping EPFL views share, and
     scrolling a 103-row table used to carry it off the screen. It sticks under
     the subnav, whose height the same tokens describe. Collapsed it is 48px, so
     it costs little to keep. */
  position: sticky;
  top: calc(var(--header-h) + var(--nav-h) + var(--subnav-h));
  z-index: 90;
}
/* Sticky only while the panel is CLOSED. Measured open at 375x667 the bar is 595px
   tall, so a sticky one would pin 89% of the viewport and never actually stick;
   even at 1440 it takes 342px. Closed it is the 48px the note above assumes.
   Behaviourally this is also the right split: with the panel open the reader is
   working the panel, not scrolling the table it filters. */
.topnav-filter:has(#shared-filter-panel:not([hidden])) { position: static; }
/* One line: the Filters toggle and its summary, then the search box. */
.topnav-filter-inner { max-width: 1360px; margin: 0 auto; padding: 0 28px; }
.topnav-filter-inner { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
.topnav-filter-inner > #shared-filter-summary { flex: 1 1 auto; min-width: 0; }
.topnav-filter-inner > .shared-filter-panel { flex: 0 0 100%; }
@media (max-width: 700px) { .topnav-filter-inner { padding: 0 14px; } }
/* Not sticky on a phone. Measured at 375x667: the nav above already pins 181px,
   and the collapsed bar is 125px there (the search wraps to its own line), so
   sticking it would leave 239px of the 667 for the content being filtered.
   Same specificity as the base rule (0,1,0) and later in the file, so it wins;
   lint-css checks exactly this. */
@media (max-width: 700px) { .topnav-filter { position: static; } }

#shared-filter-summary {
  display: flex; align-items: center; flex-wrap: wrap; gap: 7px 11px;
  padding: 7px 0;
  min-height: 40px;
}
/* The toggle carries the count, so the row reads as one control with a state rather
   than a label beside a number. */
.sfb-toggle {
  appearance: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--sans); font-size: 13.5px; font-weight: 700;
  color: #fff; background: var(--ardoise);
  border: 1px solid var(--ardoise); border-radius: 8px; padding: 5px 12px;
  transition: border-color .15s, background .15s, color .15s;
}
.sfb-toggle:hover { background: #2E2634; border-color: #2E2634; }
.sfb-toggle:focus-visible { outline: 2px solid var(--groseille); outline-offset: 2px; }
/* Open reads as pressed IN: dark ink on white, the inverse of the closed state. */
.sfb-toggle[aria-expanded='true'] { background: #fff; border-color: var(--ardoise); color: var(--ardoise); }
.sfb-caret { font-weight: 800; font-size: 15px; line-height: 1; width: 9px; text-align: center; }
.sfb-count {
  font-size: 11.5px; font-weight: 800; background: rgba(255,255,255,.32); color: #fff;
  border-radius: 9px; padding: 0 6px; min-width: 17px; text-align: center;
}
.sfb-toggle[aria-expanded='true'] .sfb-count { background: var(--ardoise); color: #fff; }

/* The active values, named. "3 filters active" alone sends a reader hunting through
   fifteen facets to find which three, which is the failure this bar exists to prevent.
   Each chip is also its own remove button. */
.sfb-chips { display: flex; flex-wrap: wrap; gap: 5px; min-width: 0; }
/* The SAME pill the cards and the table use, keyed by facet through --px.
   Every active filter was rendered solid Canard whatever it was, so the summary
   said "you have three filters on" and nothing about which dimensions they were
   - and since Canard is the Method colour, selecting a Stage value produced a
   chip in Method's colour. Now a chip you drop from the summary looks like the
   label you clicked to set it. */
.sfb-chip {
  appearance: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--sans); font-size: 12.5px; font-weight: 700;
  color: var(--ardoise);
  background: #fff;
  background: color-mix(in srgb, var(--px, var(--perle)) 20%, #fff);
  border: 1px solid var(--border);
  border-color: color-mix(in srgb, var(--px, var(--perle)) 55%, #fff);
  border-radius: 999px; padding: 3px 9px;
  transition: background .15s, border-color .15s;
}
.sfb-chip:hover { background: color-mix(in srgb, var(--px, var(--perle)) 34%, #fff); }
/* Focus is per value; every other facet takes its own key colour. */
.sfb-chip.f-focus.p-m{--px:var(--f-focus-m)}
.sfb-chip.f-focus.p-d{--px:var(--f-focus-d)}
.sfb-chip.f-focus.p-hs{--px:var(--f-focus-h)}
.sfb-chip:focus-visible { outline: 2px solid var(--groseille); outline-offset: 2px; }
.sfb-chip.is-exc {
  background: #fff; color: var(--groseille); border-color: var(--groseille);
  border-style: dashed;
}
.sfb-chip.is-exc:hover { background: rgba(181,31,31,.07); }
.sfb-not { font-weight: 800; text-transform: uppercase; font-size: 10px; letter-spacing: .05em; }
.sfb-x { font-size: 14px; line-height: 1; opacity: .75; }
.sfb-chip .sfb-x{opacity:.55;font-weight:700}
.sfb-chip:hover .sfb-x { opacity: 1; }

.sfb-clear {
  appearance: none; cursor: pointer;
  font-family: var(--sans); font-size: 12.5px; font-weight: 600;
  color: var(--ardoise); background: #fff;
  border: 1px solid rgba(69,58,76,.34); border-radius: 7px; padding: 4px 10px;
}
.sfb-clear:hover { border-color: var(--groseille); color: var(--groseille); }
.sfb-clear:focus-visible { outline: 2px solid var(--groseille); outline-offset: 2px; }
.sfb-none { font-size: 13px; color: rgba(69,58,76,.82); }
/* The scope line is the sentence that names all three views. Pushed right, quiet, and
   the first thing to go when the row runs out of width. */
.sfb-scope {
  margin-left: auto; font-size: 11.5px; color: rgba(69,58,76,.72);
  font-style: italic; white-space: nowrap;
}
@media (max-width: 900px) { .sfb-scope { display: none; } }

/* The expanded panel. Scrolls on its own rather than pushing the page down, so opening
   the filter never moves the content you were reading. */
/* An inset card, not a full-width slab: the hatch now runs around all four sides
   so the panel reads as a thing sitting ON the chrome rather than as another band
   of page. The side padding used to be 0, which put the chips hard against the
   edge of the white. */
.shared-filter-panel {
  background: #fff;
  border: 1px solid rgba(69,58,76,.20);
  border-radius: 14px;
  padding: 18px 24px 20px;
  margin: 2px 0 16px;
  max-height: min(66vh, 560px);
  overflow-y: auto;
  box-shadow: 0 2px 10px rgba(69,58,76,.07);
}
.shared-filter-panel[hidden] { display: none; }
/* .sf-scope-note is gone with the line it styled: it announced "one filter for all
   three views" directly above a hint that said the same thing. */

/* Grid items get their own reveal. The generic .internal-only rule restores inline-flex,
   and while a grid container blockifies its children (so inline-flex would compute to
   flex anyway), stating it here means the card does not depend on that subtlety holding.
   Used by the Relevance chart, which is the jury's own 1-5 score and internal like the
   Relevance row of the label table. */
.chart-card.internal-only{display:none !important}
body.internal-unlocked .chart-card.internal-only{display:flex !important;flex-direction:column}


/* ── Ask the research ─────────────────────────────────────────────────────
   The thread scrolls inside a fixed height so the composer stays put; a chat
   whose input walks down the page as answers arrive is unusable on a laptop.
   Claim and source are one block, because a claim separated from its quote is
   exactly the thing this feature exists to avoid. */
.ask-wrap { max-width: 780px; margin: 0 auto; }
.ask-thread {
  min-height: 320px; max-height: 58vh; overflow-y: auto;
  padding: 4px 2px 8px; margin-bottom: 14px;
}
.ask-empty-lead { font-size: 1.02rem; margin: 8px 0 16px; }
.ask-examples { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
.ask-chip {
  border: 1px solid var(--line, #d8d8d8); background: #fff; cursor: pointer;
  border-radius: 999px; padding: 7px 14px; font: inherit; font-size: .88rem;
}
.ask-chip:hover:not(:disabled) { border-color: #5C2483; color: #5C2483; }
.ask-chip:disabled { opacity: .45; cursor: not-allowed; }
.ask-limits { font-size: .84rem; color: #6b6b6b; max-width: 62ch; line-height: 1.6; }

.ask-turn { margin: 0 0 18px; }
.ask-turn-user { display: flex; justify-content: flex-end; }
.ask-q {
  background: #5C2483; color: #fff; border-radius: 14px 14px 3px 14px;
  padding: 9px 15px; margin: 0; max-width: 80%; font-size: .95rem;
}
.ask-claim {
  border-left: 3px solid #00A79F; padding: 2px 0 2px 14px; margin: 0 0 14px;
}
.ask-claim-text { margin: 0 0 7px; line-height: 1.6; }
.ask-source { font-size: .86rem; }
.ask-source-sum { cursor: pointer; color: #4a4a4a; list-style: none; }
.ask-source-sum::-webkit-details-marker { display: none; }
.ask-source-sum::before { content: '▸ '; color: #00A79F; }
details[open] .ask-source-sum::before { content: '▾ '; }
.ask-source-n {
  display: inline-block; min-width: 18px; height: 18px; line-height: 18px;
  text-align: center; background: #00A79F; color: #fff; border-radius: 50%;
  font-size: .7rem; margin-right: 7px;
}
.ask-source-title { text-decoration: underline; text-underline-offset: 2px; }
.ask-source-body { padding: 8px 0 4px 25px; }
.ask-quote {
  margin: 0 0 6px; padding: 8px 12px; background: #f6f6f4;
  border-left: 2px solid #cfcfcf; font-style: italic; line-height: 1.55;
}
.ask-source-link { font-size: .84rem; }
.ask-verified { font-size: .82rem; color: #6b6b6b; margin: 2px 0 0; }
.ask-tick {
  color: #00A79F; font-weight: 600; margin-right: 2px;
}
.ask-tick::before { content: '✓ '; }
.ask-timing { font-size: .76rem; color: #9a9a9a; margin: 6px 0 0; }

/* A refusal is a correct outcome, so it is styled as information, not failure.
   Errors are the red ones. */
.ask-refusal {
  background: #f6f6f4; border-left: 3px solid #CAC7C7; padding: 11px 15px;
  margin: 0; line-height: 1.6; color: #444;
}
.ask-error {
  background: #fdf1ee; border-left: 3px solid #EC6608; padding: 11px 15px;
  margin: 0; line-height: 1.6;
}

.ask-pending { display: flex; align-items: center; gap: 10px; color: #6b6b6b; }
.ask-elapsed { font-variant-numeric: tabular-nums; font-size: .86rem; }
.ask-pending-note { font-size: .86rem; }
.ask-dots {
  width: 9px; height: 9px; border-radius: 50%; background: #00A79F;
  animation: askPulse 1.1s ease-in-out infinite;
}
@keyframes askPulse { 0%,100% { opacity: .25 } 50% { opacity: 1 } }
@media (prefers-reduced-motion: reduce) { .ask-dots { animation: none } }

.ask-form { display: flex; gap: 9px; align-items: flex-end; }
.ask-input {
  flex: 1; resize: none; font: inherit; line-height: 1.5;
  padding: 11px 14px; border: 1px solid var(--line, #d8d8d8); border-radius: 10px;
}
.ask-input:focus { outline: 2px solid #5C2483; outline-offset: 1px; }
.ask-input:disabled { background: #f4f4f4; color: #999; }
.ask-send {
  background: #5C2483; color: #fff; border: none; border-radius: 10px;
  padding: 11px 20px; font: inherit; cursor: pointer;
}
.ask-send:hover:not(:disabled) { background: #4a1d6b; }
.ask-send:disabled { opacity: .5; cursor: not-allowed; }
.ask-foot { font-size: .8rem; color: #6b6b6b; margin: 9px 0 0; }
.ask-foot-offline { color: #EC6608; }

/* ═══ FILTER RAIL ═══════════════════════════════════════════════════════════
   The fifteen facets moved from a horizontal bar into a left column beside the
   three Mapping EPFL panels. Baymard's testing puts a horizontal filter
   toolbar's ceiling at 6 to 8 filter types; past that the overflow hides behind
   a button their participants overlooked. Fifteen facets need a column.
   The rail is sticky and scrolls on its own, so a long facet list never pushes
   the works out of view, and the works keep their own scroll. */
/* The rail sits beside the CONTENT, below the page title. placeFilterRail() in
   ui.js wraps the first content band and everything after it in .epfl-body and
   drops the rail in as its first child, so each panel's full-bleed hero is left
   alone above. Wrapping the whole panel instead made the hero a 700px flex item
   and broke the page, one word of the title per line. */
/* One place for the rail's width and the gap beside it: the bands reserve
   exactly this much on their left, so the two cannot drift apart. */
/* --page-inset is where THIS SITE starts its text: the same
   max(gutter, 50vw - measure/2) the three panels already use for their bands and
   the page header uses for its title. Named once so the rail, the bands and the
   title cannot drift apart. */
:root{--rail-w:244px;--rail-gap:26px;
  --page-inset:max(var(--gutter), calc(50vw - var(--measure) / 2))}
/* stretch, not flex-start, so the content column grows to whichever is taller,
   the filter or the section. Without it the rail ran 259px past the bottom of
   the white on Publications and hung over the page ground.
   The rail itself opts out with align-self, or it would stretch too: on Charts
   the column beside it is 5400px of three sections, and the rail's divider would
   have run the whole way down past two more grounds. */
/* The rail column starts on the page's own left edge, not on the panel gutter.
   Measured before this: the title started at 160 and the filter at 80, so the
   filter hung further left than the title above it and the page carried three
   different left edges down its length where every other page carries one. */
.epfl-body{display:flex;align-items:stretch;gap:var(--rail-gap);
  padding-left:calc(var(--page-inset) - var(--gutter));
  padding-right:calc(var(--page-inset) - var(--gutter))}
.epfl-main{flex:1 1 auto;min-width:0;display:flex;flex-direction:column}
.filter-rail{align-self:flex-start}
/* The first section absorbs the slack, so its white always reaches the bottom of
   the filter beside it. */
.epfl-main > section.band:first-of-type{flex:1 0 auto}
.filter-rail{
  flex:0 0 var(--rail-w);width:var(--rail-w);
  position:sticky;top:calc(var(--header-h) + var(--nav-h) + var(--subnav-h) + 8px);
  max-height:calc(100vh - var(--header-h) - var(--nav-h) - var(--subnav-h) - 24px);
  overflow-y:auto;overscroll-behavior:contain;
  padding:16px 14px 24px 0;border-right:1px solid rgba(69,58,76,.12);
}
.filter-rail[hidden]{display:none}
/* In the rail the panel is the rail's content, not a disclosure. The Filters
   toggle that opened it is a phone control and is hidden on desktop below. */
.filter-rail .shared-filter-panel[hidden]{display:block}
.filter-rail .sf-actions-row{display:block;margin:0 0 14px}
.filter-rail .sf-multi-hint{font-size:11.5px;line-height:1.45;margin:8px 0 0;max-width:none}
.filter-rail .sf-toggle-btn{width:100%}
.filter-rail .features-active-filters{margin:0 0 14px}

/* A facet group: header always visible, values open in place. Not a dropdown
   any more, so nothing is absolutely positioned and nothing can open over its
   own button. */
.filter-rail #features-filters-wrap .sf-more-row{display:block;margin:0;padding:0;border-top:0}
.filter-rail #features-filters-wrap .sf-collapse{display:block;position:static;margin:0;
  border-top:1px solid rgba(69,58,76,.1)}
.filter-rail #features-filters-wrap .sf-collapse-btn{
  display:flex;align-items:center;gap:7px;width:100%;padding:9px 2px;
  background:none;border:0;border-radius:0;color:var(--ardoise);
  font-family:var(--sans);font-size:11px;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;cursor:pointer;text-align:left;height:auto;
}
.filter-rail #features-filters-wrap .sf-collapse-btn:hover{background:rgba(69,58,76,.035)}
.filter-rail #features-filters-wrap .sf-collapse-btn > span:nth-child(2){flex:1 1 auto}
.filter-rail #features-filters-wrap .sf-collapse-count{
  background:var(--canard);color:#fff;border-radius:999px;font-size:11px;padding:0 6px;letter-spacing:0}
.filter-rail #features-filters-wrap .sf-collapse-body{
  position:static;display:none;max-width:none;min-width:0;max-height:none;
  border:0;box-shadow:none;padding:0 0 12px;margin:0;background:none;overflow:visible}
.filter-rail #features-filters-wrap .sf-collapse.open .sf-collapse-body{display:block}
.filter-rail .sf-check-search{width:100%;margin:0 0 8px;padding:5px 9px;font-size:12px;
  border:1px solid var(--border);border-radius:7px;font-family:var(--sans)}

/* The three named rows stack, label above values. These carry the
   #features-filters-wrap prefix because the horizontal versions do: that rule
   set puts the label in an absolutely positioned gutter, and a class-only
   override loses to an id every time. The first version of this block did lose,
   and the rail rendered as a 40px column of clipped chips. */
.filter-rail #features-filters-wrap .sf-bar{display:block;padding:0 0 12px;margin:0 0 12px;border:0}
.filter-rail #features-filters-wrap .sf-label{position:static;display:block;width:auto;
  text-align:left;margin:0 0 7px;font-size:11px;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;color:var(--ink-muted)}
.filter-rail #features-filters-wrap #features-relevance-bar .sf-chip{
  display:flex;width:100%;margin:0 0 5px;justify-content:flex-start}
/* Re-assert the zero rule at this specificity. A value no work carries is not a
   filter, it is a disappointment waiting to be clicked, and the row rule above
   carries an id, so the plain .sf-zero hide lost to it and NA came back at 0. */
.filter-rail #features-filters-wrap #features-relevance-bar .sf-chip.sf-zero{display:none}
.filter-rail #features-filters-wrap #features-relevance-bar .sf-chip.sf-zero.is-inc,
.filter-rail #features-filters-wrap #features-relevance-bar .sf-chip.sf-zero.is-exc{display:flex}
/* The panel was a floating white card under the bar. In the rail it IS the
   rail, so it gives up the card. */
/* The panel keeps max-height:560px and overflow-y:auto from its dropdown days,
   when it hung under the top bar and had to stop somewhere. That cap, not the
   rail, is what put a scrollbar inside the filter: the rail itself measured as
   overflow:visible with nothing to scroll, while the panel one level in was
   820px of content in a 560px box. In a column it has the whole page to be as
   tall as it likes. */
.filter-rail .shared-filter-panel{background:none;border:0;box-shadow:none;
  border-radius:0;padding:0;margin:0;max-width:none;max-height:none;overflow:visible}

/* Stage takes the ordinary label, like every other facet. It was a joined
   segmented control, on the argument that before/during/after is a sequence and
   the shape should say so. It did say so, and it also made Stage the one facet
   that looked like a different kind of control, which is the louder signal. */

/* Themes keep their section, restyled for the column. */
.filter-rail .sf-groups{margin:0;border:0}
.filter-rail .sf-group{border:0;border-top:1px solid rgba(69,58,76,.1)}
.filter-rail .sf-group-btn{width:100%;padding:9px 2px;background:none;border:0;
  font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  display:flex;align-items:center;gap:7px;color:var(--ardoise);cursor:pointer}
/* inherit, or the span inside the button repaints the heading Ardoise and
   Themes becomes the only category not showing its own colour. */
/* inherit everything, not just the colour. The span carried its own 13px/800/
   .075em from the old Themes section and that is why Themes read bigger than
   every other category heading beside it. */
.filter-rail .sf-group-title{flex:1 1 auto;text-align:left;color:inherit;
  font-size:inherit;font-weight:inherit;letter-spacing:inherit;text-transform:inherit}
.filter-rail .sf-group-sub{font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;
  color:var(--ink-muted);margin:10px 0 4px;font-weight:700}
/* Column-only, so the phone rules for these two keep winning below 1100px.
   lint-css caught the first version of this: .filter-rail .sf-subrow (0,2,0)
   out-specified the max-width:640px .sf-subrow padding and killed it. */
@media (min-width: 1101px){
  .filter-rail .sf-subrow{margin:0 0 4px 14px;padding-left:10px;border-left:2px solid rgba(0,167,159,.35)}
  .filter-rail .sf-treerow{margin:0;padding:0 0 0 14px;border-left:0}
}
.filter-rail .sf-treerow .sf-chip,.filter-rail .sf-subrow .sf-chip{font-size:12px}

/* Phone and tablet: no room for a column beside the works, so the rail goes
   back above them and the Filters button that opens it comes back with it. */
@media (max-width: 1100px){
  .epfl-body{display:block}
  .filter-rail{position:static;width:auto;max-height:none;overflow:visible;
    border-right:0;border-bottom:1px solid rgba(69,58,76,.12);
    padding:0 28px 14px;margin:0 0 18px}
  .filter-rail .shared-filter-panel[hidden]{display:none}
}
@media (max-width: 700px){ .filter-rail{padding-left:0;padding-right:0} }

/* ── Rail column, second pass ────────────────────────────────────────────────
   Two things made the first version read as unfinished rather than designed. */

/* 1. The dead gutter. A .band is a full-bleed section: it carries
   padding-left:160px and margin-left:-80px so its ground reaches the window
   edge while its text stays on the page grid. Inside the rail column that bleed
   is 106px of empty space between the rail and the first word of the content,
   and the negative margin pulls the ground back UNDER the rail. In this column
   the rail is the left gutter, so the band gives up its own. */
/* A band beside the rail keeps its FULL BLEED, and reserves the rail's width in
   its own left padding instead of giving the bleed up.
   Cancelling the bleed was the bug: the band's ground stopped at the rail on one
   side and 80px short of the window on the other, so every section on these
   three subtabs became a floating 1130px box with visible edges, stacked under a
   hero that still bled full width. That is the "borders are weird" the user saw.
   Now the ground reaches both window edges again and runs BEHIND the rail, so the
   filter sits on the section's own white rather than painting a second white box
   beside it, and the rule down its right side is the only seam.
   The id is load-bearing: these three panels carry an id-scoped rule that pads a
   band to max(--gutter, 50vw - measure/2), and a class-only override loses to it. */
#panel-map .epfl-main section.band,
#panel-status .epfl-main section.band,
#panel-features .epfl-main section.band{
  margin-left:calc((var(--page-inset) + var(--rail-w) + var(--rail-gap)) * -1);
  padding-left:calc(var(--page-inset) + var(--rail-w) + var(--rail-gap));
  margin-right:calc(var(--page-inset) * -1);
  padding-right:var(--page-inset);
}
/* No rail column below 1100px, so the bands stop reserving one. This has to sit
   AFTER the rule above, not inside the earlier media block: the two selector
   lists are identical, so specificity is equal and source order decides. Placed
   earlier it did nothing, and the band kept a -284px left margin at 375 and slid
   off the left edge (measured left -264, right 369 in a 375 viewport). */
@media (max-width: 1100px){
  #panel-map .epfl-main section.band,
  #panel-status .epfl-main section.band,
  #panel-features .epfl-main section.band{
    margin-left:calc(var(--page-inset) * -1);padding-left:var(--page-inset)}
}
.epfl-main .band-hd,.epfl-main .band > *{max-width:none}

/* 2. The rail read as leftover space rather than as a panel: grey ground on the
   left, white band on the right, and a hairline between them. It is a card now,
   on the section's own ground, which is how every other grouped control on the
   site is drawn. */
.filter-rail{
  background:#fff;border:1px solid var(--border);border-right:1px solid var(--border);
  border-radius:12px;padding:14px 12px 16px;
  box-shadow:0 1px 2px rgba(69,58,76,.04);
}
/* Its own header line: the word, and Reset as a quiet link beside it. Reset was
   a full-width pill, which made the loudest thing in the rail the one control
   that throws work away. */
.filter-rail .sf-actions-row{
  display:flex;align-items:baseline;gap:8px;margin:0 0 10px;
  padding:0 0 10px;border-bottom:1px solid rgba(69,58,76,.1)}
.filter-rail .sf-actions-row::before{
  content:"Filters";flex:1 1 auto;font-size:11px;font-weight:700;letter-spacing:.07em;
  text-transform:uppercase;color:var(--ardoise)}
/* min-height carries the 24px WCAG 2.2 target. Turning Reset into a quiet text
   link dropped it to 19px, which the layout audit caught. The look is unchanged;
   the padding is inside the underline, not around it. */
.filter-rail .sf-toggle-btn{
  width:auto;flex:none;order:2;background:none;border:0;padding:4px 0;
  min-height:24px;display:inline-flex;align-items:center;
  font-size:11.5px;font-weight:600;color:var(--ink-muted);text-decoration:underline;
  text-underline-offset:2px;cursor:pointer}
.filter-rail .sf-toggle-btn:hover{color:var(--groseille)}
/* The how-to line is a footnote, not an introduction: it was four lines of
   instruction sitting above every value in the panel. */
.filter-rail .sf-multi-hint{
  order:3;flex:0 0 100%;font-size:11px;line-height:1.4;color:var(--ink-muted);
  margin:6px 0 0;font-weight:400}
.filter-rail .sf-multi-hint strong{font-weight:700;color:var(--ink-body)}

/* First and last group lose the rule that would double the card's own edge. */
.filter-rail #features-filters-wrap > .sf-bar:first-of-type{margin-top:2px}
.filter-rail .sf-groups .sf-group:last-child{border-bottom:0}

/* ── Rail column, third pass ─────────────────────────────────────────────────
   Chosen against the second pass: 300px instead of 244, no card, no sticky and
   no inner scrollbar, and a top edge that lines up with the content beside it. */
.filter-rail{
  /* 300px. At 244 the type had to run at 10.5 to 11px to fit, which is most of
     why the column read as grey: nothing in it could be a normal size. */
  flex:0 0 var(--rail-w);width:var(--rail-w);
  /* Scrolls with the page. Sticky meant a second scrollbar inside the card, and
     the list was cut off mid-facet wherever the viewport happened to end. */
  position:static;max-height:none;overflow:visible;
  /* No card, but not transparent either. The rail always runs alongside the
     FIRST content band, and that band is always white: the site alternates
     grounds automatically from nth-of-type, so band one is always the odd one.
     Measured on all three subtabs, the rail overlaps band one and no other. So
     it takes the same white and reads as part of that section, with a single
     rule marking where the filter ends and the content begins. */
  /* No background of its own. The band behind it is white and now bleeds the
     full width of the window, so the rail sits ON that white and a second white
     box would only put a seam back. z-index because the band is painted by a
     later sibling and would otherwise cover the rail it bleeds under. */
  background:none;border:0;border-radius:0;box-shadow:none;
  border-right:1px solid rgba(69,58,76,.16);
  /* Full height, so the divider does not stop where the filters end (2026-09-14). */
  align-self:stretch;
  /* top:auto is load-bearing. The sticky version of this rail set
     top:calc(header + nav + subnav + 8px), which went inert when it became
     position:static and came back to life the moment position:relative was
     added for the z-index: the rail dropped 200px and hung that far below the
     white it is supposed to sit on. */
  /* 5, not 1: the rail's tooltips run over the cards beside it, which sit at z-index 1 (2026-09-14). */
  position:relative;top:auto;z-index:5;
  /* 56px is the .band top padding, so the word Filters sits on the same line as
     the eyebrow beside it. The two columns started on different lines before. */
  padding:56px 26px 40px 0;
}

/* The panel title. Newsreader at 19px, the same display face the section
   headings beside it use, instead of an 11px uppercase label that looked like
   one more facet name. This is the one place in the rail with a real heading. */
.filter-rail .sf-actions-row{
  display:flex;align-items:baseline;gap:10px;margin:0 0 14px;
  padding:0 0 12px;border-bottom:1px solid rgba(69,58,76,.14)}
.filter-rail .sf-actions-row::before{
  content:"Filters";flex:1 1 auto;font-family:var(--display);
  font-size:19px;font-weight:600;letter-spacing:-.01em;text-transform:none;
  color:var(--ardoise)}
.filter-rail .sf-toggle-btn{font-size:12px}
.filter-rail .sf-multi-hint{font-size:11.5px;line-height:1.45}
/* The rail moves into the active panel, and on Actors #panel-status .sf-multi-hint (13px) outranked
   the rule above, so the same hint read 13px there and 11.5px on Charts and Publications. */
#panel-status .filter-rail .sf-multi-hint{font-size:11.5px;line-height:1.45}

/* Hierarchy, three steps instead of one flat grey.
   The facet name is the structure of the panel, so it takes the ink; it was
   --ink-muted, the same grey as the footnote above it and the counts below it,
   which is what made the whole column read as one texture. */
.filter-rail #features-filters-wrap .sf-label,
.filter-rail .sf-collapse-btn,
.filter-rail .sf-group-btn{
  font-size:11.5px;letter-spacing:.07em;color:var(--ardoise);font-weight:700}
/* Air between facets. The groups were 1px rules 9px apart, a list of hairlines. */
.filter-rail #features-filters-wrap .sf-bar{margin:0 0 22px;padding:0}
.filter-rail #features-filters-wrap .sf-label{margin:0 0 9px}
.filter-rail #features-filters-wrap .sf-collapse-btn{padding:11px 2px}
.filter-rail .sf-group-btn{padding:11px 2px}

/* A facet that is doing something says so in its own colour, on the header, so
   it can be seen without opening the group. */
.filter-rail #features-filters-wrap .sf-collapse{border-left:2px solid transparent;padding-left:0}
.filter-rail #features-filters-wrap .sf-collapse.has-sel{
  border-left-color:var(--fc);padding-left:8px;background:rgba(69,58,76,.02)}
/* The name keeps its own facet colour when a value is selected. This used to
   force it back to ink, which was part of the heading changing appearance. */
.filter-rail .sf-group.has-sel{border-left:2px solid var(--f-theme);padding-left:8px}

/* Room recovered by the extra 56px of width. */
/* Back at 244, so the row forms take the size that fits it. */
.filter-rail #features-filters-wrap .sf-chip.sf-form-measured{font-size:12.5px}
.filter-rail .filter-rail #features-filters-wrap .sf-chip.sf-form-quiet{font-size:12.5px}
.filter-rail #features-filters-wrap .sf-chip.sf-form-entity{font-size:12.5px}

/* Below 1100px there is no second column, so the rail is a full-width block
   above the works. The width and flex have to be restated here: the base rule
   above sits LATER in the file than the earlier narrow-width block, and at
   equal specificity later wins, so without this the rail stayed a 300px stub on
   a 375px screen. */
@media (max-width: 1100px){
  /* No side padding: the wrapper is already inset by the page gutter, so 28px
     more put the filter 28px right of the title it sits under. */
  .filter-rail{flex:1 1 auto;width:auto;padding:0 0 14px;border-right:0;
    border-bottom:1px solid rgba(69,58,76,.14)}
}
@media (max-width: 700px){ .filter-rail{padding-left:0;padding-right:0} }

/* ── The facet colour key, on the category names ─────────────────────────────
   Every facet already owns one EPFL hue in the --f-* key, and the charts, the
   works table and the card pills all draw values in it. The filter was the one
   surface where the category itself said nothing about which colour it maps to,
   so a reader had to learn the key from the charts and carry it back here.
   A 3px bar, not a dot: dots were tried and dropped, and a bar reads as a key
   rather than as a bullet. It sits on the CATEGORY, never on the values, so it
   marks the dimension once instead of tinting every row.
   The three per-facet backgrounds for the open rows were already written and had
   no size, so they had never rendered. */
.filter-rail #features-filters-wrap .sf-label::before,
.filter-rail #features-filters-wrap .sf-collapse-btn::before,
.filter-rail .sf-group-btn::before{
  content:"";flex:none;display:inline-block;
  width:3px;height:11px;border-radius:2px;margin-right:8px;
  background:var(--fc, var(--perle));
  vertical-align:-1px;
}
/* The three always-open rows name their own hue: their labels are markup, not
   chips, so they never receive --fc. */
.filter-rail #features-relevance-bar .sf-label::before{background:var(--f-close)}
.filter-rail #features-stage-bar .sf-label::before{background:var(--f-stage)}
/* Focus is the one facet whose VALUES carry the colour, all three pillars, so
   its key carries all three rather than inventing a fourth hue. */
.filter-rail #features-mdh-focus-bar .sf-label::before{
  background:linear-gradient(180deg,
    var(--f-focus-m) 0 33.33%,
    var(--f-focus-d) 33.33% 66.66%,
    var(--f-focus-h) 66.66% 100%);
}
.filter-rail .sf-group-btn::before{background:var(--f-theme)}
/* The label rows are blocks, so the bar needs a line box to sit on. */
.filter-rail #features-filters-wrap .sf-label{display:flex;align-items:center}

/* ── ONE LABEL: colour block and name (option 1D) ────────────────────────────
   Chosen over the contour version after measuring every facet hue against both
   grounds. A contour is a non-text boundary and needs 3:1 under WCAG 1.4.11, and
   five of the eleven coloured facets cannot reach it: Vert d'eau 1.47, Soufre
   1.20, Perle 1.68, Leman 2.99, and Carotte 3.25 on white but 2.98 on the grey
   band. Darkening them far enough stops them being the colour they are keying
   to, so the key would no longer match the charts.
   A FILL has no such problem. The block sits inside a bordered box, so the box
   draws the edge, the word carries the meaning, and the hue is free to be itself
   at any lightness. Every one of the fifteen works as itself.
   One rectangle, one corner radius, one rule, in the filter, the works table,
   the cards and the modals. --px on the pills, --fc on the filter chips, both
   from the same --f-* key in :root. */
.ax-pill,
.bib-topic,
#features-filters-wrap .sf-chip{
  display:inline-flex;align-items:center;gap:6px;
  padding:3px 9px 3px 14px;border-radius:5px;
  border:1px solid var(--border);
  background:linear-gradient(90deg, var(--lblc) 0 6px, #fff 6px);
  color:var(--ardoise);
  font-family:var(--sans);font-size:11.5px;font-weight:700;letter-spacing:.03em;
  line-height:1.35;white-space:nowrap;text-align:left;
}
/* No raw-hue fallback here. --px is a FILL colour, and using it for a contour
   and then for text put Vert d'eau on white at 1.47:1 on every actor card. A
   pill reads the same two tokens as every other label; where a facet has not set
   them the label falls back to ink, which is legible by construction. */
.ax-pill,.bib-topic{--lblc:var(--ink-muted)}
/* The card pills key off .ax-* rather than .f-*, so they need the map too. */
.ax-data{--lblc:var(--f-data);--lbltx:var(--f-data-name)}
.ax-method{--lblc:var(--f-method);--lbltx:var(--f-method-name)}
.ax-subject{--lblc:var(--f-subject);--lbltx:var(--f-subject-name)}
.ax-theme{--lblc:var(--f-theme);--lbltx:var(--f-theme-name)}
.ax-more{--lblc:var(--perle);--lbltx:var(--ink-muted)}
#features-filters-wrap .sf-chip{--lblc:var(--fc, var(--perle))}
/* The chips are targets as well as labels, so they keep the 24px WCAG 2.2
   minimum and the pointer. */
#features-filters-wrap .sf-chip{min-height:24px;cursor:pointer;position:relative;overflow:hidden}
#features-filters-wrap .sf-chip:hover{background:linear-gradient(90deg, var(--lblc) 0 6px, rgba(69,58,76,.045) 6px)}
/* Selected: the ring, unchanged, plus the block widening into a fuller stripe so
   the state is legible without relying on the ring alone. */
#features-filters-wrap .sf-chip.is-inc{
  background:linear-gradient(90deg, var(--lblc) 0 6px, color-mix(in srgb, var(--lblc) 12%, #fff) 6px);
  border-color:color-mix(in srgb, var(--lblc) 55%, #fff);
  box-shadow:0 0 0 3px rgba(0,167,159,.4);
}
#features-filters-wrap .sf-chip.is-exc{
  background:linear-gradient(90deg, var(--groseille) 0 6px, #fff 6px);
  border-color:var(--groseille);border-style:dashed;color:var(--groseille);
}
#features-filters-wrap .sf-chip .sf-count{
  background:rgba(31,42,55,.08);color:var(--ardoise);border-radius:12px;
  padding:0 6px;min-width:16px;text-align:center;font-size:11px;font-weight:700;
  font-variant-numeric:tabular-nums;
}
/* The count, drawn as well as written: a 2px rule along the bottom, as wide a
   share of the chip as the count is of the largest value in the same facet.
   Inside the chip's own box, so it costs no height. */
/* The four that keep their own form take none of this. Closeness is a ladder,
   the pillars are squares, and both say something the general label cannot. */
#features-filters-wrap .sf-chip.bt-close,
#features-filters-wrap .sf-chip.sf-f-focus:has(.bt-pil){
  background:#fff;padding:3px 8px;border-radius:5px}
/* No count bar in the filter. The charts are one click away and draw the same
   distribution properly, with an axis, so a 2px rule inside a chip was a second
   and worse version of something the site already does well. The number stays. */
#features-filters-wrap .sf-chip.bt-close::after,
#features-filters-wrap .sf-chip.sf-f-focus:has(.bt-pil)::after{display:none}
#features-filters-wrap .sf-chip.sf-f-focus:has(.bt-pil){border-color:transparent}

/* Hierarchies keep their indent and their aligned counts: the block is the
   label's form, the list is the layout, and they are different decisions. */
#features-filters-wrap .sf-chip.sf-form-measured{width:100%;justify-content:flex-start}
/* A long theme or method name wraps inside its row rather than being cut at about 16 letters ("Toxicity &
   haras...") in the 244px rail (B9, 2026-09-14). */
#features-filters-wrap .sf-chip.sf-form-measured{white-space:normal;height:auto;align-items:center}
#features-filters-wrap .sf-chip.sf-form-measured .sf-chip-lbl{
  flex:1 1 auto;min-width:0;white-space:normal;overflow-wrap:anywhere;line-height:1.3;padding:2px 0}
#features-filters-wrap .sf-chip.sf-form-measured .sf-count{
  flex:none;width:48px;text-align:right;background:none;color:var(--ink-muted)}
#features-filters-wrap .sf-chip.sf-form-measured::after{display:none}
/* Entities are named, so the name takes the room. */
#features-filters-wrap .sf-chip.sf-form-entity{width:100%;justify-content:flex-start}
#features-filters-wrap .sf-chip.sf-form-entity .sf-chip-lbl{
  flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis}

/* ── The category heading: name in ink, colour as a swatch ───────────────────
   Coloured category text was the other candidate and it fails for the same
   reason the contour does, only harder: 11px bold is small text and needs 4.5:1,
   which would push Soufre to olive #787231 and Vert d'eau to grey-green #67755d.
   A swatch keeps the name at full contrast and the hue true. */
.filter-rail #features-filters-wrap .sf-label::before,
.filter-rail #features-filters-wrap .sf-collapse-btn::before,
.filter-rail .sf-group-btn::before{
  width:11px;height:11px;border-radius:3px;margin-right:8px;
  border:1px solid rgba(69,58,76,.14);
}

/* No swatch: the name itself carries the colour now. */
.filter-rail #features-filters-wrap .sf-label::before,
.filter-rail #features-filters-wrap .sf-collapse-btn::before,
.filter-rail .sf-group-btn::before{display:none}

.filter-rail #features-relevance-bar .sf-label{color:var(--f-close-name)}
.filter-rail #features-stage-bar .sf-label{color:var(--f-stage-name)}
/* Focus is the one facet whose VALUES carry the colour, all three pillars at
   once, so its name stays in ink rather than claiming one of the three. */
.filter-rail #features-mdh-focus-bar .sf-label{color:var(--ardoise)}
.filter-rail .sf-group-btn{color:var(--f-theme-name)}
.filter-rail #features-filters-wrap .sf-f-method .sf-collapse-btn,
.filter-rail #features-method-collapse .sf-collapse-btn{color:var(--f-method-name)}
.filter-rail #features-data-collapse .sf-collapse-btn{color:var(--f-data-name)}
.filter-rail #features-strategy-collapse .sf-collapse-btn{color:var(--f-subject-name)}
.filter-rail #features-platform-collapse .sf-collapse-btn{color:var(--f-platform-name)}
.filter-rail #features-type-collapse .sf-collapse-btn{color:var(--f-type-name)}
.filter-rail #features-affil-collapse .sf-collapse-btn{color:var(--f-affil-name)}
.filter-rail #features-lab-collapse .sf-collapse-btn{color:var(--f-lab-name)}
.filter-rail #features-actor-collapse .sf-collapse-btn{color:var(--f-actor-name)}
.filter-rail #features-year-collapse .sf-collapse-btn{color:var(--f-year-name)}
.filter-rail #features-score-collapse .sf-collapse-btn{color:var(--f-score-name)}

/* ── ONE LABEL: the closeness badge in the facet's colour (option 1B) ─────────
   Switched from the colour-block form to the contour form on request. A contour
   is a non-text boundary and needs 3:1 under WCAG 1.4.11, and five of the hues
   cannot reach it as themselves: Vert d'eau 1.47, Soufre 1.20, Perle 1.68, Leman
   2.99, and Carotte 3.25 on white but 2.98 on the grey band.
   So a label's contour reads the SAME darkened token the category names use, the
   hue scaled toward black by the smallest step that clears 4.5:1, which clears
   3:1 with room to spare. Six of the fifteen were already dark enough and are
   untouched. The category name and the values beneath it are then the exact same
   colour, which is what makes the column read as one thing. */

/* The badge itself: the closeness form, in the facet's colour. */
/* Text in the SAME colour as the contour, on request. That forces one choice:
   a contour needs 3:1 and 11.5px bold text needs 4.5:1, so painting the text in
   the 3:1 line colour would have failed AA on five of the eleven coloured
   facets. Both now read the text-safe value instead, so they match exactly and
   the darker of the two thresholds is the one that governs. The contours are a
   shade deeper than they were; the text is legible, which it would not have
   been the other way round. */
.ax-pill,
.bib-topic,
#features-filters-wrap .sf-chip{
  background:#fff;
  border:1.5px solid var(--lblc, var(--ink-muted));
  border-radius:5px;
  padding:3px 9px;
  /* Contour in the facet's TRUE hue, exactly as the charts draw it, text in a
     darker shade of the same hue.
     A contour is a non-text boundary and needs 3:1, which the true hue meets on
     eight of the fifteen and comes within a hair of on the rest; 11.5px bold
     text needs 4.5:1, which several of them cannot reach as themselves. Two
     values of one hue rather than one value compromised for both jobs. */
  color:var(--lbltx, var(--lblc, var(--ardoise)));
}
#features-filters-wrap .sf-chip:hover{background:rgba(69,58,76,.05)}
#features-filters-wrap .sf-chip.is-inc{
  background:color-mix(in srgb, var(--lblc) 12%, #fff);
  border-color:var(--lblc);
  box-shadow:0 0 0 3px rgba(0,167,159,.4);
}
#features-filters-wrap .sf-chip.is-exc{
  background:#fff;border-color:var(--groseille);border-style:dashed;color:var(--groseille);
}
/* Closeness and the pillar badges keep their own contours, which say something
   the facet hue does not: filled, outlined, dashed, dotted is a ladder. */
#features-filters-wrap .sf-chip.bt-close{border-color:var(--ardoise)}
#features-filters-wrap .sf-chip.bt-close.is-infrastructure{border-color:rgba(69,58,76,.42)}
#features-filters-wrap .sf-chip.bt-close.is-NA,
#features-filters-wrap .sf-chip.bt-close.is-na{border-color:rgba(69,58,76,.34)}
#features-filters-wrap .sf-chip.sf-f-focus:has(.bt-pil){border-color:transparent}

/* The ancestors inside a method chip on a card. Muted and lighter, so the chip
   reads as "the leaf, and where it sits" rather than as three labels crammed
   into one box. */
.lbl-anc{color:var(--ink-muted);font-weight:600}
.lbl-sep{color:var(--ink-muted);opacity:.6;margin:0 3px;font-weight:400}

/* ── Two derived colours per facet, each darkened only as far as its own job
      needs ──────────────────────────────────────────────────────────────────
   The first version of this was too dark, for two reasons, both fixed here.
   1. It used the TEXT colour for the label contours as well. A contour is a
      non-text boundary and needs 3:1, not the 4.5:1 small text needs, so every
      border on the site was darker than it had to be. There are two tokens now:
      -name for the 11px category names, -line for the 1.5px contours.
   2. It darkened by scaling every channel toward black, which drops saturation
      with lightness and turns a pale hue into mud: Soufre came out #787231, an
      olive, and Vert d'eau came out #67755d, a grey-green. Darkening now holds
      the hue, drops only LIGHTNESS, and lifts saturation to keep the colour
      reading as itself. Soufre is a dark gold, Vert d'eau a real green.
      Saturation is lifted only on a hue that has some: Perle is a neutral grey,
      and forcing saturation onto it produced red.
   Measured against the page ground, #F6F5F5, not against white. White was the
   first choice and it was wrong by about 0.4 of a ratio point: four names came
   out at 4.26 to 4.35 where the audit resolves the ground behind them, not the
   band. Targets carry a small margin over the floor for the same reason.
   Six of the fifteen already clear both bars and are used untouched, and eight
   of the fifteen contours are the true chart hue or within a hair of it. */
:root{
  /* Label text and category names: the TRUE hue wherever it already reads at
     2.5:1, and only the five that cannot are lightened, each by the smallest
     step that gets there. Chosen by the user from four levels shown side by side
     in source/docs/reference/label-text-darkness.html.
     This is BELOW the 4.5:1 WCAG AA floor for small text, deliberately and with
     the cost stated: the labels are brighter and closer to the chart colours,
     and a reader on a dim screen or with low vision will find the five pale
     facets harder. The five are listed in tools/dev/audit-layout.mjs as accepted
     so the audit still reports anything NEW. */
  --f-close-name:#B51F1F;       /* true hue, 6.07 */
  --f-stage-name:#6F7500;       /* Chartreuse darkened, true hue  4.57 */
  --f-theme-name:#007E78;       /* Leman darkened, true hue  4.53 */
  --f-subject-name:#5C2483;     /* true hue, 9.48 */
  --f-method-name:#007480;      /* true hue, 5.07 */
  --f-data-name:#517C35;        /* Vert d'eau darkened, true hue  4.50 */
  --f-platform-name:#B95006;    /* Carotte darkened, true hue  4.58 */
  --f-type-name:#453A4C;        /* true hue, 9.83 */
  /* Year, Affiliation and Author names darkened to read as text, not as disabled (B8, 2026-09-14): the
     category names are small bold text and need 4.5:1, and these three sat at 2.5 to 3.7. The hue stays. */
  --f-year-name:#7D7300;        /* Soufre, darkened  4.86 */
  --f-affil-name:#D10000;       /* Rouge suisse, darkened  5.66 */
  --f-lab-name:#5B3428;         /* true hue, 9.81 */
  --f-actor-name:#6F6A6A;       /* Perle, darkened  5.33 */
  --f-score-name:#413D3A;       /* true hue, 9.88 */
  --f-focus-m-name:#D81B5F;     /* Rose darkened, true hue  4.55 */
  --f-focus-d-name:#3373B1;     /* Acier darkened, true hue  4.56 */
  --f-focus-h-name:#C04B0F;     /* Mont Rose darkened, true hue  4.53 */
  /* The -line tokens are gone. Contours read the true hue directly from the
     --f-* key, so a second value for them was a value nothing used. */
}
/* Contours take the lighter of the two, which is the whole point of having two. */
/* A work card and the works table name their pills lbl-*: the same facet, so the same colour as the rail
   chip. Those classes set only --px, so Tech & method, Data type and Stage pills fell back to grey. */
.f-close,.lbl-close,#features-filters-wrap .sf-f-close{--lblc:var(--f-close);--lbltx:var(--f-close-name)}
.f-stage,.lbl-stage,#features-filters-wrap .sf-f-stage{--lblc:var(--f-stage);--lbltx:var(--f-stage-name)}
.f-theme,.f-subtopic,
#features-filters-wrap .sf-f-theme,#features-filters-wrap .sf-f-subtopic{--lblc:var(--f-theme);--lbltx:var(--f-theme-name)}
.f-subject,#features-filters-wrap .sf-f-subject{--lblc:var(--f-subject);--lbltx:var(--f-subject-name)}
.f-method,.lbl-method,#features-filters-wrap .sf-f-method{--lblc:var(--f-method);--lbltx:var(--f-method-name)}
.f-data,.lbl-data,#features-filters-wrap .sf-f-data{--lblc:var(--f-data);--lbltx:var(--f-data-name)}
.f-platform,#features-filters-wrap .sf-f-platform{--lblc:var(--f-platform);--lbltx:var(--f-platform-name)}
.f-type,#features-filters-wrap .sf-f-type{--lblc:var(--f-type);--lbltx:var(--f-type-name)}
.f-year,#features-filters-wrap .sf-f-year{--lblc:var(--f-year);--lbltx:var(--f-year-name)}
.f-affil,#features-filters-wrap .sf-f-affil{--lblc:var(--f-affil);--lbltx:var(--f-affil-name)}
.f-lab,#features-filters-wrap .sf-f-lab{--lblc:var(--f-lab);--lbltx:var(--f-lab-name)}
.f-actor,#features-filters-wrap .sf-f-actor{--lblc:var(--f-actor);--lbltx:var(--f-actor-name)}
.lbl-rel,#features-filters-wrap .sf-f-score{--lblc:var(--f-score);--lbltx:var(--f-score-name)}
#features-filters-wrap .sf-f-strategy{--lblc:var(--f-subject);--lbltx:var(--f-subject-name)}

/* ── The category heading does not change when a filter is on ────────────────
   Selecting a value filled the whole heading with the facet's colour, at full
   strength: picking one data type turned DATA TYPE into a Vert d'eau bar. Four
   rules were doing it between them, from three different passes. The heading now
   keeps its own colour and gains nothing: what is selected is legible from the
   chips themselves and from the active-filters row at the top of the rail. */
.filter-rail #features-filters-wrap .sf-collapse.has-sel .sf-collapse-btn,
.filter-rail #features-filters-wrap .sf-more-row .sf-collapse.has-sel .sf-collapse-btn{
  background:none;
}
.filter-rail #features-filters-wrap .sf-collapse.has-sel{
  border-left:0;padding-left:0;background:none;
}
.filter-rail .sf-group.has-sel{border-left:0;padding-left:0}
.filter-rail .sf-group.has-sel .sf-group-title{color:inherit}
/* No count badge on a heading either. */
.filter-rail .sf-collapse-count,
.filter-rail .sf-group-count{display:none}
/* The name keeps its facet colour when selected, which the has-sel rules were
   overriding back to ink. */
.filter-rail #features-method-collapse.has-sel .sf-collapse-btn{color:var(--f-method-name)}
.filter-rail #features-data-collapse.has-sel .sf-collapse-btn{color:var(--f-data-name)}
.filter-rail #features-strategy-collapse.has-sel .sf-collapse-btn{color:var(--f-subject-name)}
.filter-rail #features-platform-collapse.has-sel .sf-collapse-btn{color:var(--f-platform-name)}
.filter-rail #features-type-collapse.has-sel .sf-collapse-btn{color:var(--f-type-name)}
.filter-rail #features-affil-collapse.has-sel .sf-collapse-btn{color:var(--f-affil-name)}
.filter-rail #features-lab-collapse.has-sel .sf-collapse-btn{color:var(--f-lab-name)}
.filter-rail #features-actor-collapse.has-sel .sf-collapse-btn{color:var(--f-actor-name)}
.filter-rail #features-year-collapse.has-sel .sf-collapse-btn{color:var(--f-year-name)}
.filter-rail #features-score-collapse.has-sel .sf-collapse-btn{color:var(--f-score-name)}

/* ── Themes reads like every other category ──────────────────────────────────
   It ran at 11.5px against everyone else's 11px, with its own letter-spacing,
   which is why it looked like a different kind of heading. */
.filter-rail .sf-group-btn{
  font-size:11px;letter-spacing:.06em;font-weight:700;text-transform:uppercase;
  font-family:var(--sans);
}


/* ── Category order: most used first ─────────────────────────────────────────
   MDH focus, closeness, stage, data type, tech and method, author, year, themes,
   then the rest. The markup order cannot express this on its own: closeness,
   focus and stage are fixed rows in the template, the ten dropdowns are rendered
   into one container, and Themes is a third container after both. Flattening the
   dropdown container with display:contents makes all sixteen siblings of one
   column, and then order can put them where they belong without moving any
   markup or changing a single render function. */
.filter-rail #features-filters-wrap{display:flex;flex-direction:column}
.filter-rail #features-filters-wrap .sf-more-row{display:contents}
.filter-rail #features-filters-wrap > *{order:20}
.filter-rail #features-active-filters{order:0}
.filter-rail #features-mdh-focus-bar{order:1}
.filter-rail #features-relevance-bar{order:2}
.filter-rail #features-stage-bar{order:3}
.filter-rail #features-data-collapse{order:4}
.filter-rail #features-method-collapse{order:5}
.filter-rail #features-actor-collapse{order:6}
.filter-rail #features-year-collapse{order:7}
.filter-rail #features-theme-groups{order:8}
.filter-rail #features-strategy-collapse{order:9}
.filter-rail #features-platform-collapse{order:10}
.filter-rail #features-type-collapse{order:11}
.filter-rail #features-affil-collapse{order:12}
.filter-rail #features-lab-collapse{order:13}
.filter-rail #features-score-collapse{order:14}
/* Stage used to be the one named row that borrowed the dropdowns' spacing,
   because it sat between two of them. All three named rows take it now (see the
   thirteen-sections block at the end of this file), so the special case is gone. */

/* ── The rail header, after the top bar was deleted ──────────────────────────
   The sticky bar above the page carried the Filters button, the active-filter
   summary, the result count and the search box. It duplicated the rail's own
   title 400px below it, so it went; everything it did that was not duplication
   moved in here, outside the collapsible panel so the phone layout keeps it. */
.filter-rail .sf-search-row-top{margin:0 0 10px}
.filter-rail .sf-search{width:100%;padding:7px 10px;font-family:var(--sans);font-size:12.5px;
  border:1px solid var(--border);border-radius:8px;background:#fff;color:var(--ardoise)}
.filter-rail .sf-search:focus{outline:2px solid var(--canard);outline-offset:1px;border-color:var(--canard)}
.filter-rail .sf-shown-count{display:block;margin:0 0 12px;font-size:12px;font-weight:700;
  color:var(--ink-muted);font-variant-numeric:tabular-nums}
.filter-rail .sf-shown-count:empty{display:none}
/* Phone only. On a wide screen the facets are always shown, so a show/hide
   control would be a button that does nothing, which is what the old top bar's
   Filters button had quietly become. */
.sf-rail-toggle{display:none}
@media (max-width: 1100px){
  .sf-rail-toggle{display:inline-flex;align-items:center;gap:6px;margin:0 0 12px;
    padding:7px 13px;border-radius:8px;border:1px solid var(--border);background:#fff;
    font-family:var(--sans);font-size:12.5px;font-weight:700;color:var(--ardoise);cursor:pointer}
  .sf-rail-toggle:hover{background:rgba(69,58,76,.05)}
}

/* ── Active filters take the SAME label as everything else ───────────────────
   The chips listing what is currently on were the last survivor of the old pill:
   999px corners, a neutral grey contour, 13px. Every other label on the site is
   the badge below, so the one row whose whole job is to say "these are your
   labels" was showing them in a form that no longer exists anywhere. Same
   rectangle, same radius, same weight, same per-facet contour as the value it
   came from, plus the cross that removes it. */
#features-active-filters .af-chip{
  display:inline-flex;align-items:center;gap:6px;
  padding:3px 9px;border-radius:5px;
  background:#fff;
  border:1.5px solid var(--lblc, var(--ink-muted));
  color:var(--lbltx, var(--lblc, var(--ardoise)));
  font-family:var(--sans);font-size:11.5px;font-weight:700;letter-spacing:.03em;
  line-height:1.35;white-space:nowrap;cursor:pointer;
}
#features-active-filters .af-chip:hover{background:rgba(69,58,76,.05)}
#features-active-filters .af-chip .af-x{margin-left:1px;font-weight:800;opacity:.6}
#features-active-filters .af-chip:hover .af-x{opacity:1}
#features-active-filters .af-chip.is-exc{
  border-color:var(--groseille);border-style:dashed;color:var(--groseille)}
/* The facet's own colour, from the same key the value badges read. */
.af-close{--lblc:var(--f-close);--lbltx:var(--f-close-name)}
.af-stage{--lblc:var(--f-stage);--lbltx:var(--f-stage-name)}
.af-theme,.af-subtopic{--lblc:var(--f-theme);--lbltx:var(--f-theme-name)}
.af-subject,.af-strategy{--lblc:var(--f-subject);--lbltx:var(--f-subject-name)}
.af-method{--lblc:var(--f-method);--lbltx:var(--f-method-name)}
.af-data{--lblc:var(--f-data);--lbltx:var(--f-data-name)}
.af-platform{--lblc:var(--f-platform);--lbltx:var(--f-platform-name)}
.af-type{--lblc:var(--f-type);--lbltx:var(--f-type-name)}
.af-year{--lblc:var(--f-year);--lbltx:var(--f-year-name)}
.af-affil{--lblc:var(--f-affil);--lbltx:var(--f-affil-name)}
.af-lab{--lblc:var(--f-lab);--lbltx:var(--f-lab-name)}
.af-actor{--lblc:var(--f-actor);--lbltx:var(--f-actor-name)}
.af-score{--lblc:var(--f-score);--lbltx:var(--f-score-name)}
/* Focus is coloured per VALUE: the chip carries its pillar class. */
.af-focus.p-m{--lblc:var(--f-focus-m);--lbltx:var(--f-focus-m-name)}
.af-focus.p-d{--lblc:var(--f-focus-d);--lbltx:var(--f-focus-d-name)}
.af-focus.p-hs{--lblc:var(--f-focus-h);--lbltx:var(--f-focus-h-name)}

/* The two that keep their own ink. Closeness is a ladder whose Direct step is a
   solid Ardoise fill, so its text must stay white; the pillar squares are the
   same shape. Neither takes the facet colour on its text. */
#features-filters-wrap .sf-chip.bt-close{color:var(--ardoise)}
#features-filters-wrap .sf-chip.bt-close.is-direct{color:#fff}
#features-filters-wrap .sf-chip.bt-close.is-infrastructure,
#features-filters-wrap .sf-chip.bt-close.is-NA,
#features-filters-wrap .sf-chip.bt-close.is-na{color:var(--ink-muted)}
#features-filters-wrap .sf-chip.sf-f-focus:has(.bt-pil){color:var(--ardoise)}

/* ONE text format for every label. Measured before this: closeness 11px at
   .04em, the filter and card labels 11.5px at .03em, the works table 12px at
   .03em. Three sizes for one object. */
.ax-pill,.bib-topic,.bt-close,
#features-filters-wrap .sf-chip,
#features-active-filters .af-chip{
  font-size:11.5px;font-weight:700;letter-spacing:.03em;text-transform:none;
}
table.bib-table .bib-topic,.rcard-labels .bib-topic{font-size:11.5px}

/* A chart's title and a filter category name are the same thing said twice: both
   name a facet. They did not look it. The chart used the display serif at 16px
   while the filter used 11px uppercase sans in the facet's own colour, so the
   two surfaces that a reader moves between most read as unrelated. The chart
   title now takes the filter's format, and the facet colours below give it the
   same hue as the category it matches. Since 2026-09-13 it is the site's one eyebrow size and
   spacing (12.5px, .15em) rather than the filter label's 11.5px: every eyebrow now has one format,
   and the facet colour is what still ties the chart to its filter. */
.chart-card h3{
  font-family:var(--sans);font-size:12.5px;font-weight:700;letter-spacing:.15em;
  text-transform:uppercase;color:var(--ardoise);margin-bottom:14px;
}
/* MDH at a glance keeps its title in Ardoise; each of its three parts takes its filter's colour. */
#glance-close-h{color:var(--f-close-name)}
#glance-focus-h{color:var(--ardoise)}
#glance-stage-h{color:var(--f-stage-name)}
.chart-card:has(#chart-tt) h3{color:var(--f-method-name)}
.chart-card:has(#chart-strategy) h3{color:var(--f-subject-name)}
.chart-card:has(#chart-relevance) h3{color:var(--f-score-name)}
.chart-card:has(#featuresBibYearChart) h3{color:var(--f-year-name)}
.chart-card:has(#featuresBibLabChart) h3{color:var(--f-lab-name)}
/* The one "Lab / group" chart is a LAB chart, so it takes Marron with the facet,
   not the red Affiliation moved to.
   And featuresBibFocusChart is titled "Data type" in map.html, so it takes the
   data hue rather than the pillar one its name suggests. */
.chart-card:has(#featuresBibFocusChart) h3{color:var(--f-data-name)}

/* One size for every category heading in the rail, matching the chart titles.
   Measured before: the three open rows at 11.5px, the ten dropdowns and Themes
   at 11px. */
.filter-rail #features-filters-wrap .sf-label,
.filter-rail #features-filters-wrap .sf-collapse-btn,
.filter-rail .sf-group-btn{font-size:11.5px;letter-spacing:.07em}

/* ── The three named rows are disclosures too ────────────────────────────
   Closeness, focus and stage were the only sections in the rail that could not
   be closed, so the column had ten headings that behaved one way and three that
   behaved another. Their labels are buttons now, carrying the same caret as the
   dropdowns, and the chips are what folds away.
   Behaving the same, they take the same shape: the hairline above, the 11px
   heading padding, the 12px of air under the values. That padding is also what
   gives the heading a 39px hit area, where the bare 17px line of text it used to
   be would have missed the 24px minimum the moment it became clickable.
   A button brings its own font, centring and pointer defaults; border and
   background were already cleared by the #features-filters-wrap label rule. */
.filter-rail #features-filters-wrap .sf-bar{
  margin:0;padding:0 0 12px;border-top:1px solid rgba(69,58,76,.1);
}
.filter-rail #features-filters-wrap .sf-label{
  font-family:var(--sans);width:100%;margin:0;padding:11px 2px;cursor:pointer;gap:7px;
  -webkit-appearance:none;appearance:none;
}
.filter-rail #features-filters-wrap .sf-label:hover{background:rgba(69,58,76,.035)}
.filter-rail #features-filters-wrap .sf-label .sf-collapse-caret{
  font-size:13px;opacity:.6;color:inherit;
}
/* The chips carry two id-prefixed display rules of their own (the closeness row
   forces display:flex, twice), so hiding them needs a selector with the row's id
   in it or the fold does nothing. */
.filter-rail #features-filters-wrap #features-relevance-bar.is-collapsed .sf-chip,
.filter-rail #features-filters-wrap #features-mdh-focus-bar.is-collapsed .sf-chip,
.filter-rail #features-filters-wrap #features-stage-bar.is-collapsed .sf-chip{
  display:none;
}
/* Closed, the row is its heading and nothing else. */
.filter-rail #features-filters-wrap .sf-bar.is-collapsed{padding-bottom:0}
/* The dropdowns reserve 2px on the left for the bar that marks a facet with a
   selection. The named rows and Themes did not, so their headings started 2px
   left of the other nine and the column had two left edges. */
.filter-rail #features-filters-wrap .sf-bar,
.filter-rail .sf-group{border-left:2px solid transparent}

/* Actor card: the words that stand in for the M D H letters when an actor has no pillar and a reason
   is recorded (panel-status.js, pillar_note). */
.rcard-pillar-note{font-size:12.5px;line-height:1.4;color:var(--ink-muted);}

/* Later work, after EPFL (later_work, owner's choice B, 2026-09-15): a quiet line on the actor card and a folded
   list in the profile (ui.js openActorModal). Not EPFL research, so a plain reference list with no labels. */
.rcard-later{font-size:12.5px;line-height:1.4;color:var(--ink-muted);}
.rm-later-note{font-size:14px;line-height:1.5;color:var(--ink-muted);margin:0 0 12px;}
.rm-later-list{list-style:none;margin:0;padding:0;display:grid;gap:12px;}
.rm-later-list li{font-size:15px;line-height:1.45;color:var(--ardoise);}
.rm-later-list a{color:var(--ardoise);font-weight:600;text-decoration:none;border-bottom:1px solid var(--perle);}
.rm-later-list a:hover{border-bottom-color:var(--canard);}
.rm-later-meta{display:block;font-size:13.5px;color:var(--ink-muted);margin-top:2px;}

/* ── Share and correct (2026-09-15) ───────────────────────────────────────────
   "Copy link" and "Suggest a correction" beside the profile link in an actor pop-up and under the title of
   a work pop-up (site/js/share.js). Quiet on purpose: a reader checking their own card finds them, and they
   never compete with the card's content. */
.share-actions{display:inline-flex;flex-wrap:wrap;align-items:center;gap:6px 12px;margin-left:12px;}
.rm-links .share-actions:first-child{margin-left:0;}
.share-copy{appearance:none;background:none;border:1px solid var(--perle);border-radius:999px;padding:3px 10px;font-family:var(--sans);font-size:12.5px;font-weight:600;line-height:1.4;color:var(--ardoise);cursor:pointer;}
.share-copy:hover{border-color:var(--ardoise);}
.share-correct{font-family:var(--sans);font-size:12.5px;font-weight:600;line-height:1.4;color:var(--canard);}
.share-copy:focus-visible,.share-correct:focus-visible{outline:2px solid var(--canard);outline-offset:2px;}
.bib-modal-share{display:flex;flex-wrap:wrap;margin:8px 0 2px;}
.bib-modal-share .share-actions{margin-left:0;}

/* "What came out of it": the last row of a work's table, a size down, and only the address is a link, so
   the description reads as text rather than as one long underline (owner, 2026-09-15). */
.bib-label-table tr.row-after th,.bib-label-table tr.row-after td{font-size:12.5px;}
.bib-follow{display:block;line-height:1.5;color:var(--ink-body);}
.bib-follow + .bib-follow{margin-top:4px;}
.bib-follow-link{color:var(--canard);text-decoration:underline;text-underline-offset:2px;overflow-wrap:anywhere;}
/* On the actor pop-up's dark header the two actions take the header's white, like the EPFL profile link beside
   them; the default ink and teal were near-invisible on the purple (screenshot check, 2026-09-15). */
.rm-header .share-copy{color:#fff;border-color:rgba(255,255,255,.5);}
.rm-header .share-copy:hover{border-color:#fff;}
.rm-header .share-correct{color:#fff;text-decoration:underline;text-underline-offset:3px;}
.rm-header .share-copy:focus-visible,.rm-header .share-correct:focus-visible{outline-color:#fff;}
/* A long step wraps to several lines on a phone; its number sits on the first line, not in the middle of the
   paragraph (screenshot check, 2026-09-15). */
.ctb-steps li{align-items:start;}

/* Eyebrows are Groseille everywhere (owner, 2026-09-16): three places had their own colour. */
.ph-eyebrow,.page-eyebrow,.band p.eyebrow,.band .rd-note p.eyebrow,.band .ctb-note.a-leman p.eyebrow{color:var(--groseille);}

/* The "Who are you?" tiles read in one institutional ink (owner, 2026-09-16). */
#ph-pick .ph-section-title,#ph-pick .ph-section-sub,#ph-pick .ph-tile-title,#ph-pick .ph-tile-text,
#ph-pick .ph-tile-tag{color:var(--ardoise);}
#ph-pick .ph-tile-dot{background:var(--ardoise);}

/* The hero title stays in Arial (owner, 2026-09-16), set on .ph-hero-title above. */
/* EPFL in the tagline takes the EPFL red (owner, 2026-09-16). */
.ph-hero-epfl{font-style:normal;color:var(--rouge-suisse);}
/* A phone held sideways is shorter than the 420px floor, so the title started below the screen. */
@media (max-height:520px){.ph-hero{min-height:max(260px,calc(100svh - var(--main-top, 0px)));}}

/* ── Page feedback: "Was this page useful?" ────────────────────────────────
   Built 2026-09-16 to the owner's design. It sits between the last band and the
   footer, on every page, and reads as part of the page furniture rather than as
   an advertisement for itself: a hairline rule, no card, no shadow, no tint. The
   three grades are outline buttons, not a scale, so no answer looks like the
   expected one.
   Groseille marks the chosen grade and the send button, which is the same rule
   the eyebrows and the Contribute address follow. */
.pfb{border-top:1px solid var(--rule,#e4e1de);background:#fff;}
.pfb-inner{max-width:var(--measure,1180px);margin:0 auto;padding:26px 24px 30px;
  display:grid;gap:14px;justify-items:start;}
.pfb-q{margin:0 0 12px;font-family:var(--sans);font-size:15.5px;font-weight:700;color:var(--ardoise);}
.pfb-grades{display:flex;flex-wrap:wrap;gap:10px;}
.pfb-grade{font:600 14.5px/1 var(--sans);color:var(--ardoise);background:#fff;
  border:1px solid var(--perle,#cac7c7);border-radius:999px;padding:9px 18px;cursor:pointer;
  transition:border-color .15s,color .15s;}
.pfb-grade:hover{border-color:var(--groseille);color:var(--groseille);}
.pfb-grade[aria-pressed="true"]{border-color:var(--groseille);color:#fff;background:var(--groseille);}
.pfb-note{margin:12px 0 0;font-size:13px;color:var(--muted,#6b6560);}
.pfb-why{display:grid;gap:10px;width:100%;max-width:640px;}
.pfb-why[hidden]{display:none;}
.pfb-why-label{font:600 14.5px/1.4 var(--sans);color:var(--ardoise);}
.pfb-why-text{font:400 15px/1.5 var(--sans);color:var(--ardoise);padding:10px 12px;width:100%;
  border:1px solid var(--perle,#cac7c7);border-radius:6px;background:#fff;resize:vertical;}
.pfb-why-text:focus{outline:2px solid var(--groseille);outline-offset:1px;border-color:var(--groseille);}
.pfb-why-actions{display:flex;flex-wrap:wrap;gap:12px;align-items:center;}
.pfb-send{font:700 14.5px/1 var(--sans);color:#fff;background:var(--groseille);border:1px solid var(--groseille);
  border-radius:999px;padding:10px 22px;cursor:pointer;}
.pfb-send:hover{background:#8f1818;border-color:#8f1818;}
.pfb-skip{font:600 14px/1 var(--sans);color:var(--muted,#6b6560);background:none;border:0;
  padding:0;cursor:pointer;text-decoration:underline;text-underline-offset:3px;}
.pfb-skip:hover{color:var(--groseille);}
.pfb-thanks{margin:0;font:600 15px/1.5 var(--sans);color:var(--canard);}
.pfb-thanks[hidden]{display:none;}
/* The corner button (P3, owner's choice 2026-09-16, replacing a tab rotated
   90 degrees against the right edge, which was hard to read and easy to miss).
   It only scrolls to the strip and puts the cursor on the first grade, so
   nothing is ever covered and nothing opens over the page.
   Deliberately NOT a round floating bubble with a shadow: a 4px radius, a flat
   Ardoise ground and a hairline is the same furniture the rest of the site uses,
   and the bubble is the one shape that would make this page look like a product
   rather than a university's.
   Hidden on a phone, as the rotated tab was. A fixed control in the corner of a
   360px screen sits on top of the content, and the strip itself is only a scroll
   away. */
.pfb-tab{position:fixed;right:22px;bottom:22px;z-index:40;
  font:600 13px/1 var(--sans);letter-spacing:.04em;
  color:#fff;background:var(--ardoise);border:1px solid rgba(255,255,255,.22);border-radius:4px;
  padding:11px 18px;cursor:pointer;transition:background .15s;}
.pfb-tab:hover{background:var(--groseille);border-color:var(--groseille);}
/* Always shown, on every page (owner, 2026-09-16). It used to step back while the
   strip was on screen, which on a short page like Contribute meant it never
   appeared at all. */
/* (The phone rules for the button and the card are in the feedback card block below.) */

/* ── The three counts, in the shared filter rail ───────────────────────────
   Moved out of the page heads on 2026-09-16. In the head they cost 79px above
   the fold on every page and said nothing the rail did not already say. Here
   they sit at the top of the column they describe, in the same serif the page
   headlines use, with a hairline under them so they read as a heading for the
   filters rather than as the first filter. */
.rail-figs{gap:24px;margin:0 0 16px;padding:0 0 16px;border-bottom:1px solid var(--rule);}
.rail-figs .fig b{font-size:23px;}
.rail-figs .fig span{font-size:11.5px;letter-spacing:.13em;}

/* ── Team log in, in the footer (2026-09-16) ──────────────────────────────────
   A text button, not a pill: it is the least-used control on the site and should
   look it. Same weight and underline as the footer's other links, so it reads as
   one more line of the contact block rather than as a call to action. */
.ph-footer-login{font:inherit;color:inherit;background:none;border:0;padding:0;cursor:pointer;
  text-decoration:underline;text-underline-offset:3px;text-decoration-color:rgba(255,255,255,.45);}
.ph-footer-login:hover{text-decoration-color:#fff;}
.ph-footer-login-note{color:rgba(255,255,255,.55);}
.ph-footer-login-row{margin-top:14px;}

/* ── Feedback card (2026-09-16) ──────────────────────────────────────────────
   Replaces the strip above the footer. It floats above the corner button, so
   pressing Feedback never moves the reader. This one does carry a shadow: it
   sits over the page, and the site's rule is that only things that float get one.
   Declared after the strip's rules, which it overrides. */
.pfb{position:fixed;right:22px;bottom:72px;z-index:41;width:340px;max-width:calc(100vw - 24px);
  background:#fff;border:1px solid var(--perle,#cac7c7);border-top:1px solid var(--perle,#cac7c7);
  border-radius:6px;box-shadow:0 10px 32px rgba(0,0,0,.18);}
.pfb[hidden]{display:none;}
.pfb-inner{position:relative;max-width:none;margin:0;padding:18px 20px 16px;gap:12px;}
.pfb-q{margin:0 28px 12px 0;}
.pfb-note{margin:0;}
.pfb-why{max-width:none;}
.pfb-close{position:absolute;top:8px;right:10px;width:28px;height:28px;border:0;background:none;
  font:400 22px/1 var(--sans);color:var(--muted,#6b6560);cursor:pointer;border-radius:4px;}
.pfb-close:hover{color:var(--groseille);}
/* On a phone the card spans the width of the screen above the button, and the
   button itself shows at every width now, since it is the only way in. */
@media (max-width:767px){
  .pfb{left:12px;right:12px;bottom:64px;width:auto;max-width:none;}
  .pfb-tab{display:block;right:12px;bottom:12px;}
}

/* ── Header: the computer, not the books (2026-09-16) ─────────────────────────
   The owner keeps one image in the header, the computer or the books, and chose
   the laptop showing a landscape. The shelf across the band is hidden rather than
   deleted, so bringing it back is one line; its colour zones and scrim above are
   left in place for the same reason. */
.h-shelf .map-hero-art,.h-shelf-rail{display:none;}

/* ── Lab / group runs the full width (2026-09-16) ───────────────────────────────
   It sat alone on the last row in the public view, and its bars are now upright
   along the x axis, which needs the width. It spans in every view, logged in too. */
.chart-grid > .chart-card:has(#featuresBibLabChart){grid-column:1/-1;}

/* ── Header search field (X6, 2026-09-16) ─────────────────────────────────────
   A wide field instead of the small pill. Still a button that opens the search
   overlay. Declared after the pill's rules and the header overrides, which it
   replaces. On a phone (the existing max-width:720px rule sets font-size:0 on
   the button) the label and the shortcut are hidden, leaving the icon. */
.site-chrome header .h-search-field{min-width:320px;justify-content:flex-start;gap:9px;padding:9px 12px;
  border-radius:6px;font-size:14px;font-weight:500;color:rgba(255,255,255,.72);
  background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.28);box-shadow:none;}
.site-chrome header .h-search-field:hover{background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.55);color:#fff;}
.h-search-field .h-search-kbd{margin-left:auto;font:600 11px/1 var(--sans);letter-spacing:.04em;
  color:rgba(255,255,255,.6);background:none;border:1px solid rgba(255,255,255,.3);border-radius:3px;padding:3px 5px;}
@media (max-width:900px){.site-chrome header .h-search-field{min-width:0;}.h-search-field .h-search-label,.h-search-field .h-search-kbd{display:none;}}

/* ── Title, second line quieter (X1 adapted, 2026-09-16) ──────────────────────
   "Misinformation - Disinformation - Hate speech" goes to a quiet grey, but its
   M, D and H keep the pillar colours, as the owner asked: the colour stays where
   it carries meaning and the rest of the line steps back behind the title. */
.h-left p{color:rgba(255,255,255,.6);}

/* ── Audience tiles, four soft colours (owner's option T4, 2026-09-16) ──────────
   The one-ink pass made the four tiles read as a single list. Each tile now has
   a soft coloured edge and dot, and its small label in the darker, text-safe
   version of the same colour; the titles stay dark. The colours come from the
   site's pillar palette but are used here as a palette, not as a meaning: the
   tiles are audiences, not pillars. Declared after the one-ink rule, which it
   overrides for the edge, the dot and the label only. */
#ph-pick .ph-tile-public{box-shadow:inset 3px 0 0 #F4B89A;}
#ph-pick .ph-tile-public .ph-tile-dot{background:#F4B89A;border-color:#F4B89A;}
#ph-pick .ph-tile-public .ph-tile-tag{color:#B95006;}
#ph-pick .ph-tile-research{box-shadow:inset 3px 0 0 #9CC4E6;}
#ph-pick .ph-tile-research .ph-tile-dot{background:#9CC4E6;border-color:#9CC4E6;}
#ph-pick .ph-tile-research .ph-tile-tag{color:#3373B1;}
#ph-pick .ph-tile-strategy{box-shadow:inset 3px 0 0 #F2A9C3;}
#ph-pick .ph-tile-strategy .ph-tile-dot{background:#F2A9C3;border-color:#F2A9C3;}
#ph-pick .ph-tile-strategy .ph-tile-tag{color:#D81B5F;}
#ph-pick .ph-tile-all{box-shadow:inset 3px 0 0 #CAC7C7;}
#ph-pick .ph-tile-all .ph-tile-dot{background:#CAC7C7;border-color:#CAC7C7;}
#ph-pick .ph-tile-all .ph-tile-tag{color:#453A4C;}

/* ── "Who are you?" without dots (owner, 2026-09-16) ────────────────────────────
   The coloured dot beside each audience is gone. It also marked the chosen
   audience, so the chosen tile now shows it with a thicker edge in its own colour. */
#ph-pick .ph-tile-dot{display:none;}
#ph-pick .ph-tile-public[aria-pressed="true"]{box-shadow:inset 6px 0 0 #F4B89A;}
#ph-pick .ph-tile-research[aria-pressed="true"]{box-shadow:inset 6px 0 0 #9CC4E6;}
#ph-pick .ph-tile-strategy[aria-pressed="true"]{box-shadow:inset 6px 0 0 #F2A9C3;}
#ph-pick .ph-tile-all[aria-pressed="true"]{box-shadow:inset 6px 0 0 #CAC7C7;}

/* ── The three round charts in one row (owner, 2026-09-16) ─────────────────────
   Data type, Tech & method and Research strategy side by side on a wide screen,
   instead of one per row: the section drops from about 2190px to about 400px.
   Each chart's frame is fitted to its drawing (fitRadial in panel-visual.js).
   Below 1100px there is not room for three readable circles, so the grid's own
   auto-fit takes over again. */
@media (min-width:1100px){#panel-map .spider-grid{grid-template-columns:repeat(3,minmax(0,1fr));}}

/* ── Hero title, option B (owner, 2026-09-16) ────────────────────────────────────
   Three sizes a step apart instead of a leap: the terms 40px (32 until the owner asked for them
   bigger the same day), the platform 50px,
   the by-line 26px. Declared after the three-line title's rules, which it
   replaces; the clamps keep the same proportions on a phone. */
.ph-hero-terms{font-size:clamp(20px,2.9vw,40px);font-weight:700;line-height:1.2;letter-spacing:-0.3px;color:rgba(255,255,255,.78);}
.ph-hero-terms em{font-size:1.19em;font-weight:800;}
.ph-hero-sep{opacity:.45;}
.ph-hero-head{display:block;margin-top:14px;font-size:clamp(30px,3.5vw,50px);font-weight:800;line-height:1.08;letter-spacing:-0.8px;color:#fff;text-wrap:balance;}
.ph-hero-by{display:block;margin-top:10px;font-size:clamp(17px,1.8vw,26px);font-weight:400;letter-spacing:0;color:rgba(255,255,255,.8);}
/* The phone title rule at max-width 700px set the whole h1 to 24px, which the
   spans above now size themselves; the terms may wrap to two lines there. */
/* On a phone each term takes its own line. Hiding the " - " separators alone left no space between the
   terms, so the line could not wrap and ran off both edges; as zero-height blocks they force the breaks. */
@media (max-width:700px){.ph-hero-sep{display:block;height:0;overflow:hidden;font-size:0;}}

/* The drawn key above the round charts' colour table (K2, 2026-09-16). */
.vm-catkey{margin:0 0 16px;padding:14px 16px 8px;background:#fff;border:1px solid var(--rule,#e1ddda);border-radius:8px;max-width:740px;}
.vm-catkey svg{display:block;width:100%;height:auto;}

/* Hero title all white and all bold (owner, 2026-09-16): in grey it was hard to tell apart from the
   grey words drifting behind it. The coloured M, D, H and the red EPFL keep their colours. */
.ph-hero-title,.ph-hero-terms,.ph-hero-head,.ph-hero-by{color:#fff;}
.ph-hero-terms,.ph-hero-by{font-weight:800;}
.ph-hero-sep{opacity:.6;}
/* The scrim (.ph-hero::before, z-index 2) is meant to sit between the drifting words and the title,
   but the title block was at z-index 1, under it, so the scrim dimmed the white title to grey by 15 to
   45 percent. The title now sits above the scrim, which still darkens the words behind (2026-09-16). */
.ph-hero-inner{z-index:3;}
