/* ============================================================================
   THE STATUS BOARD
   One canonical board. Every row carries its own lamp: green where the evidence
   is public and checkable, amber where it is withheld and said so. Nothing is
   omitted to look tidier, which is the argument the page is making.

   Flat, ruled, square, with one stated exception: the deck card in Work, which
   carries a radius and a shadow because it is an object being slid past rather
   than a band lying on the page. Everywhere else the rule holds. Depth is the
   tonal step between ground and board face, and the rules between rows.
   ========================================================================= */

@font-face {
  font-family: "Archivo Narrow";
  src: url("archivo-narrow.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Ink blue is the default board. color-scheme is not decoration: without it
     the UA paints the canvas white before the first style resolves, and hands
     out light scrollbars and form controls on a dark page.

     The ground replaced a neutral graphite, #101315. Its hue matters more than
     its darkness: the two lamps are the argument of this page, and a ground
     sharing their hue stops them reading as signals. At 223 degrees this one
     sits 75 from the green lamp and 173 from the amber, the widest separation
     of the candidates considered. A green-cast ground measured 10 degrees from
     the green lamp, which is why it was rejected despite looking better in
     isolation. The board keeps the tonal step the design already had, 1.098:1
     against the previous 1.099, so depth did not change with colour. */
  color-scheme: dark;
  --ground:    #050b1a;
  --board:     #111726;
  --ink:       #eef0ed;
  --ink-soft:  #a7aeb1;
  --ink-faint: #838b8e;
  --rule:      #2c3337;
  --rule-hard: #6c7478;
  --lamp-go:   #5cb083;
  --lamp-hold: #d9a04a;
  --edge:      #eef0ed;
  /* The deck card is an object, not a band, so it takes a bigger lift than
     --board's 1.10:1: 1.56:1 here. That lift eats the contrast of anything
     faint printed on it, so two tokens travel with it rather than being
     overridden inline. On this ground --ink-faint would fall to 3.64:1 and
     --rule-hard to 2.65:1, both under their thresholds. */
  --card:      #2d3342;
  --card-faint: #979fa2;
  --card-rule: #788084;
  /* The third token the card needed and did not get. The 1px row rule stayed on
     --rule, which is a step above the ground and not above the card: #2c3337 on
     #2d3342 is 1.02:1, so the line the design asks for above a card's stack
     line is not drawn at all in dark. Light was never wrong, at 1.66:1, which
     is how it survived every review and every gate: the gate checks declared
     pairs and nobody declared this one. It restores the relationship rather
     than the value, 1.54:1 on the card against the 1.53:1 --rule holds on the
     ground, and it sits on the card's own hue ramp. */
  --card-hair: #494f5e;
  --radius-card: 14px;
  --card-shadow: 0 1px 2px rgb(0 0 0 / .5), 0 14px 34px rgb(0 0 0 / .45);

  --board-face: "Archivo Narrow", "Arial Narrow", system-ui, sans-serif;
  --text-face: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;

  --page: 74rem;
  --measure: 62ch;
  --s1: .35rem; --s2: .7rem; --s3: 1.1rem; --s4: 1.8rem; --s5: 3rem; --s6: 4.5rem;
  --lamp: .5rem;
  --arrow-ne: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M7 17 17 7M8 7h9v9"/></svg>');
  /* The chevron site.js draws on the deck's next button, to the path and the
     stroke weight. The same mark for the same idea, one step forward inside the
     site, and deliberately not --arrow-ne: that one is green and means the link
     leaves for public evidence. */
  --arrow-e: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 5l7 7-7 7"/></svg>');
}

/* Daylight: porcelain enamel and steel. Deliberately cool, not cream. Reached
   only by the toggle. There is no prefers-color-scheme rule on purpose: dark
   is the default for everyone, not the default for people whose OS agrees. */
:root[data-theme="light"] {
  color-scheme: light;
  --ground: #e6e7e4; --board: #f4f5f3;
  --ink: #14171a; --ink-soft: #464d52; --ink-faint: #5f676c;
  --rule: #c6cac7; --rule-hard: #7e8582;
  --lamp-go: #206b45; --lamp-hold: #885305; --edge: #14171a;
  /* Daylight needs no rescue: white clears every threshold against the
     existing tokens, so the two card tokens simply repeat them and the .card
     rule stays identical in both themes. */
  --card: #ffffff; --card-faint: #5f676c; --card-rule: #7e8582;
  --card-hair: #c6cac7;
  /* Daylight takes a tenth of the dark shadow. The same value that reads as
     lift on a near-black ground reads as dirt on porcelain. */
  --card-shadow: 0 1px 2px rgb(0 0 0 / .12), 0 14px 34px rgb(0 0 0 / .10);
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; scroll-padding-top: 4.5rem; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* A view is meant to be one screen and stop there. On the short ones the
   document ended above the fold and left a strip of bare ground under the
   footer, which reads as the page having failed to load the rest. The column
   fills the viewport instead and the footer sits at the bottom of it, while a
   long view still pushes it below the fold. */
body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--text-face);
  font-size: 17px;
  line-height: 1.6;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}

main { flex: 1 0 auto; display: flex; flex-direction: column; }
.wrap { max-width: var(--page); margin: 0 auto; padding: 0 var(--s4); }
.prose { max-width: var(--measure); }
/* Two columns above 58rem, where the measure leaves half the page empty. A
   prose block is capped at 62ch so it stays readable, which on a 1126px column
   means 568px of text and 558px of nothing beside it, for 1784px of height in
   Parcours. Columns spend that width instead of leaving it: each one lands at
   539px, still the measure, and the view drops from 1944px to 1151. Contact
   had the same shape and the same fix until it was removed.
   Parcours is the only view built from a prose block. The others lay their own
   content out across the full width already.
   break-inside keeps a block whole and break-after keeps a heading with what it
   introduces. 58rem is the breakpoint the hero already uses to go to one
   column. */
@media (min-width: 58rem) {
  #background .prose { max-width: none; column-count: 2; column-gap: var(--s5); }
  #background .prose > * { break-inside: avoid; }
  #background .prose > h3 { break-after: avoid; }
}

/* ---- board lettering -------------------------------------------------- */

h1, h2, h3, h4 {
  font-family: var(--board-face);
  font-weight: 700;
  line-height: 1.05;
  margin: 0;
  letter-spacing: -.01em;
  text-wrap: balance;
}
h1 { font-size: clamp(2.4rem, 6.5vw, 4rem); text-transform: uppercase; letter-spacing: -.015em; }
h2 { font-size: clamp(1.6rem, 3.4vw, 2.2rem); text-transform: uppercase; letter-spacing: .002em; }
/* One h3 on the site, and it is Méthode's. Salif read Méthode and Parcours side
   by side and said the type did not match: Méthode's panel headings were
   1.02rem of the text face at 650, Parcours' were 1.2rem of the board face at
   700, a step and a face nothing else here uses. That was two rules agreeing on
   nothing, so the register moves to the element and the panels keep only what
   is theirs, the room for two lines. Writing's group heads set their own and
   are untouched.
   letter-spacing goes to 0 rather than keeping the -.01em the display block
   sets, which is the one thing here that is not a straight move: Méthode's
   titles widen by 5 to 7px. Negative tracking is for the board face at display
   sizes, and h4 already sets 0 for exactly this case, a text-face heading at
   body size. Checked for rewrap at 1440, 900 and 360 in both languages: the
   line counts are identical, so the two lines of room still hold. */
h3 { font-family: var(--text-face); font-size: 1.02rem; font-weight: 650; letter-spacing: 0; }
h4 { font-size: 1.05rem; font-family: var(--text-face); font-weight: 650; letter-spacing: 0; margin-bottom: var(--s1); }

p { margin: 0 0 var(--s3); }
ul { margin: 0 0 var(--s3); padding-left: 1.1rem; }
li { margin-bottom: var(--s2); }
strong { font-weight: 650; color: var(--ink); }
/* The world declares no monospace. Left alone, these inherit the UA default. */
code { font-family: inherit; font-weight: 650; }

/* ---- the two marks, preserved ----------------------------------------- */

a { color: inherit; }

/* Public evidence: a green lamp on the line. */
a.v {
  text-decoration: none;
  border-bottom: 2px solid var(--lamp-go);
  padding-bottom: 1px;
  transition: background-color .12s linear;
}
a.v:hover, a.v:focus-visible { background: color-mix(in srgb, var(--lamp-go) 18%, transparent); }
a.v::after {
  content: ""; display: inline-block; width: .58em; height: .58em;
  margin-left: .2em; vertical-align: -.02em;
  background: var(--lamp-go);
  -webkit-mask: var(--arrow-ne) center / contain no-repeat;
  mask: var(--arrow-ne) center / contain no-repeat;
}

/* Withheld: ruled from above, like every other group on the page, rather than
   the tinted left-border alert every UI kit ships. Same 2px structural weight
   the rest of the world uses. */
.limit {
  border-top: 2px solid var(--lamp-hold);
  padding: var(--s2) 0 0;
  margin: var(--s4) 0;
  color: var(--ink-soft);
  font-size: .95rem;
}
.limit ul { margin: var(--s2) 0 0; }
.limit li:last-child { margin-bottom: 0; }
.limit .tag {
  display: flex; align-items: center; gap: .45rem;
  font-family: var(--board-face); font-weight: 600; font-size: .72rem;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--lamp-hold); margin-bottom: var(--s1);
}
.limit .tag::before {
  content: ""; width: var(--lamp); height: var(--lamp);
  background: var(--lamp-hold); flex: none;
}

a:not(.v) { color: inherit; text-decoration-color: var(--rule-hard); text-underline-offset: 3px; }
a:not(.v):hover { text-decoration-color: var(--edge); }

/* ---- header rail ------------------------------------------------------ */

nav {
  position: sticky; top: 0; z-index: 20;
  background: var(--board);
  border-bottom: 2px solid var(--edge);
}
.nav-in {
  max-width: var(--page); margin: 0 auto; padding: .5rem var(--s4);
  display: flex; align-items: center; gap: var(--s3);
}
/* The way home, named rather than implied. It read "Salif Sawadogo" and linked
   to the hero, which is the oldest convention on the web and still left Salif
   asking what it did, so it says what it does. The name is not lost: it is the
   h1 on the landing view and the first thing in the footer on every other one,
   so every screen still carries it. Heavier and larger than an entry, because
   it is the only control here that goes back rather than onward, and it takes
   the same 2px underline: transparent at rest, --edge on hover, green when the
   landing view is showing. */
.mark {
  font-family: var(--board-face); font-weight: 700; font-size: 1rem;
  text-transform: uppercase; letter-spacing: .04em;
  text-decoration: none; margin-right: auto;
  padding: .2rem 0; border-bottom: 2px solid transparent;
}
.mark:hover { border-bottom-color: var(--edge); }
.mark[aria-current] { border-bottom-color: var(--lamp-go); }
.nav-links { display: flex; gap: var(--s3); align-items: center; }
.nav-links a {
  font-family: var(--board-face); font-weight: 600; font-size: .82rem;
  text-transform: uppercase; letter-spacing: .09em;
  color: var(--ink-soft); text-decoration: none;
  padding: .2rem 0; border-bottom: 2px solid transparent;
}
.nav-links a:hover { color: var(--ink); border-bottom-color: var(--edge); }
.nav-links a[aria-current] { color: var(--ink); border-bottom-color: var(--lamp-go); }

#menu, #theme {
  display: none; font: inherit; background: none; cursor: pointer;
  border: 1px solid var(--rule-hard); color: var(--ink-soft);
  padding: .3rem .5rem; line-height: 0; position: relative;
}
/* The pattern the deck arrows already use, for the same reason: the box draws
   35x28 and a touch target has to reach 44, so the hit area grows past the box
   rather than the box growing into the rail. It matters most here, because
   under 66rem #menu is the only way to reach another view and #theme the only
   way to daylight. The rail's gap is --s3, 17.6px, and this takes 16 of it, so
   no two targets meet. */
#menu::after, #theme::after { content: ""; position: absolute; inset: -.5rem; }
#theme { display: block; flex: none; }
#menu:hover, #theme:hover { border-color: var(--edge); color: var(--ink); }
#menu svg, #theme svg { width: 1.05rem; height: 1.05rem; display: block; }
#menu .close, nav[data-open] #menu .open { display: none; }
nav[data-open] #menu .close { display: block; }

/* 66rem. The French labels are the longer set: at 62rem the rail cleared by
   8px, which is not clearance, it is luck. Measured, not guessed. */
@media (max-width: 66rem) {
  #menu { display: block; }
  .nav-links {
    display: none; position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--board); border-bottom: 2px solid var(--edge);
    padding: 0 var(--s3) var(--s2);
  }
  nav[data-open] .nav-links { display: flex; }
  .nav-links a { padding: .7rem .25rem; border-bottom: 1px solid var(--rule); font-size: .9rem; }
  .nav-links a:last-child { border-bottom: 0; }
  .nav-links a[aria-current] { border-bottom-color: var(--lamp-go); }
}

.lang-toggle {
  display: inline-flex; align-items: stretch; flex: none;
  border: 1px solid var(--rule-hard);
  font-family: var(--board-face); font-weight: 600; font-size: .72rem; letter-spacing: .1em;
}
.lang-toggle a, .lang-toggle span {
  padding: .3rem .45rem; line-height: 1.3; text-decoration: none; color: var(--ink-faint);
  transition: background-color .12s linear, color .12s linear;
}
.lang-toggle a:hover { color: var(--ink); }
.lang-toggle [aria-current] { background: var(--edge); color: var(--board); }

/* The icon names the destination, not the current state: on the dark default
   it offers the sun. */
#theme .moon { display: none; }
#theme .sun { display: block; }
:root[data-theme="light"] #theme .sun { display: none; }
:root[data-theme="light"] #theme .moon { display: block; }

/* ---- station name plate ----------------------------------------------- */

/* No fill, for the reason the proof band lost its own: Salif read the change of
   colour as a seam rather than as a plate. Here it was worse than on the proof
   band, because the hero grows to fill the viewport, so the plate was 848px of
   a 900px screen. A plate covering the whole screen is not a plate; it is the
   page in a different colour, and a reader moving from Accueil to Travaux saw
   the background change with no element responsible for it.
   The nav keeps --board. It is the one surface that is genuinely a band: it is
   sticky, it never fills anything, and its edge is the point. */
header {
  border-bottom: 2px solid var(--edge);
  padding: var(--s5) 0 var(--s4);
}
/* Close to the text's own width: at 1.35/0.65 the left column ran 230px
   wider than its longest line and the first viewport had a hole in it. */
.hero-grid { display: grid; grid-template-columns: minmax(0, 1.08fr) minmax(0, .92fr); gap: var(--s4) var(--s5); align-items: end; }
@media (max-width: 58rem) { .hero-grid { grid-template-columns: 1fr; gap: var(--s3); align-items: start; } }

.id-row { display: flex; align-items: center; gap: var(--s3); margin: 0 0 var(--s3); }
.portrait {
  width: 4.2rem; height: 4.2rem; display: block; flex: none;
  object-fit: cover; border: 1px solid var(--rule-hard); filter: grayscale(1) contrast(1.05);
}
/* Where he is, and nothing else. It carried a green square and an availability
   line, and the square was a borrowed lamp: green means checkable here and a
   city is not. See The Two Lamps Rule in DESIGN.md, which holds the reasoning.
   inline-block rather than the inline-flex it needed to sit that square beside
   the text. */
.status {
  display: inline-block;
  font-family: var(--board-face); font-weight: 600; font-size: .74rem;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-soft); border: 1px solid var(--rule-hard);
  padding: .3rem .6rem; margin: 0;
}

/* The hero column governs the width, not a ch cap. 34ch drew 440px inside a
   582px column and cost the French tagline a fourth line for nothing, at 32
   characters a line, which is narrow for 24px type. Shrinking the type could
   never have fixed it: ch scales with the font, so the box shrank by the same
   factor and the line count never moved. The column is already tuned to this
   sentence, which is what the hero-grid comment above is about. */
.tagline {
  font-family: var(--text-face); font-size: clamp(1.15rem, 2.4vw, 1.5rem);
  color: var(--ink); margin: var(--s3) 0 var(--s3); line-height: 1.35;
}
.hero-hook { max-width: 54ch; color: var(--ink-soft); font-size: 1.02rem; }
.hero-hook strong { color: var(--ink); }

.cta-row { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s3); align-items: center; margin-top: var(--s4); }
.btn {
  display: inline-flex; align-items: center; gap: .5rem;
  font-family: var(--board-face); font-weight: 700; font-size: .92rem;
  text-transform: uppercase; letter-spacing: .08em;
  text-decoration: none; padding: .62rem 1.1rem; border: 2px solid var(--edge);
  transition: background-color .12s linear, color .12s linear;
}
a.btn-primary { background: var(--edge); color: var(--board); }
/* --board, not #fff: the lamp inverts with the theme, so a literal white sits
   on #5cb083 in dark at 2.63:1. --board inverts with it, 5.91:1 and 6.46:1. */
a.btn-primary:hover { background: var(--lamp-go); border-color: var(--lamp-go); color: var(--board); }
a.btn-ghost { background: none; color: var(--ink); }
a.btn-ghost:hover { background: var(--edge); color: var(--board); }

.meta {
  font-family: var(--board-face); font-weight: 600; font-size: .8rem;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink-faint); display: flex; flex-wrap: wrap;
  gap: var(--s2) var(--s3); align-items: center; margin: var(--s4) 0 0;
}
.meta a { color: var(--ink-soft); text-decoration-color: var(--rule-hard); }
.meta a:hover { color: var(--ink); }
.meta-links { display: flex; flex-wrap: wrap; gap: var(--s3); align-items: center; flex-basis: 100%; }
.copy-mail {
  font: inherit; font-family: var(--board-face); font-weight: 600; font-size: .72rem;
  letter-spacing: .1em; text-transform: uppercase;
  background: none; border: 1px solid var(--rule-hard); color: var(--ink-faint);
  padding: .16rem .45rem; cursor: pointer;
}
.copy-mail:hover { border-color: var(--edge); color: var(--ink); }
.copy-mail[data-done] { border-color: var(--lamp-go); color: var(--lamp-go); }
.cta-row .copy-mail { margin-left: 0; }

/* The panel is the operating summary beside the name plate. */
/* Not a card: a ruled block, like every other group on the page. */
.panel { border: 0; border-top: 2px solid var(--edge); background: none; padding: var(--s2) 0 0; }
.panel dl { margin: 0; }
/* Green, on Salif's call. The landing view had none of it in its own content,
   which made it the one page where a reader could not learn what the colour
   means before meeting it everywhere else.
   No new register: 0.72rem caps at 0.16em is the column head exactly, which is
   where Publications' group names and Method's keys already carry green. No
   square, no underline, no arrow, so a key still cannot be mistaken for a
   verified link.
   The cost is the one The Green Register Rule already names for the deck's
   group labels: a category name is not checkable, so the colour is labelling
   rather than certifying. That is now three places doing it, and the rule's
   boundary holds because it is the register and not the count. */
.panel dt {
  font-family: var(--board-face); font-weight: 600; font-size: .72rem;
  letter-spacing: .16em; text-transform: uppercase; color: var(--lamp-go);
}
.panel dd { margin: var(--s1) 0 0; font-size: .92rem; line-height: 1.5; color: var(--ink-soft); }
.panel dd strong { color: var(--ink); }
.panel .row { padding: var(--s2) 0; border-bottom: 1px solid var(--rule); }
/* A plain div now. It was a <details open> whose summary the script closed
   below 58rem, and every rule here fought that: the fill and the 2px --edge
   rule that details[open] and details:first-of-type gave it, the summary that
   had to be hidden on desktop, and two more borders that doubled the line
   above them. None of it survives the element being a div, so none of it is
   here any more. What remains is the one thing the block ever needed. */
.panel .panel-more { border: 0; background: none; margin: 0; }

/* ---- sections --------------------------------------------------------- */

section { padding: var(--s6) 0; border-top: 1px solid var(--rule); }
.sec-head { margin-bottom: var(--s4); }
/* Every lede, wherever it sits. The selector used to name two containers and
   Publications' lede sits in neither, so it ran the full 1126px column at 152
   characters a line, twice the band and the longest measure on the site. A rule
   that has to be told where its element lives is a rule that misses the next
   one. */
.lede { max-width: var(--measure); color: var(--ink-soft); margin: var(--s3) 0 0; font-size: 1.02rem; }
/* Green, like the deck's group label, which is the same element doing the same
   job one view over. Publications carried no green at all: 27 links to a
   patent, fifteen repositories, eleven published models and a certification
   badge, and not one of them marked, on the page that is nothing but public
   evidence. Marking all 27 would have been the other mistake, since a mark
   everything carries distinguishes nothing. The four group names carry it. */
.group-head {
  margin: var(--s5) 0 var(--s2);
  font-family: var(--board-face); font-weight: 600; font-size: .74rem;
  letter-spacing: .18em; text-transform: uppercase; color: var(--lamp-go);
  display: flex; align-items: center; gap: var(--s3);
  padding-bottom: var(--s1); border-bottom: 2px solid var(--edge);
}

/* ---- rows, not cards -------------------------------------------------- */

details {
  border: 0; border-bottom: 1px solid var(--rule); background: none; margin: 0;
}
/* Only where a row group opens cold. After a group head, the head's own 2px
   rule already opens it, and both together drew two lines 11px apart under
   "Platform and production AI" and nowhere else. */
details:first-of-type { border-top: 2px solid var(--edge); }
.group-head + details { border-top: 0; }
details[open] { background: var(--board); }
summary {
  cursor: pointer; padding: var(--s3) var(--s2);
  list-style: none; display: grid; grid-template-columns: 1fr auto;
  gap: var(--s1) var(--s3); align-items: baseline;
}

/* The block that used to size a case-study summary here is gone with the rows
   it described: Work holds a deck now and contains no <summary> at all,
   verified on both pages. It was not merely dead, it was harmful: #work
   .s-stack carried grid-column: 2 and still matched inside a card, forcing an
   implicit second column so the top of every card contradicted the single
   column below it. An id-weighted rule cannot be overridden by a class, so the
   fix was to delete it rather than fight it. */

/* Services are offers, not records: five of them, no second field, and the line
   under the title is a promise rather than a stack. They get the width and the
   weight instead of a column. */
#what-i-do .s-title { font-size: 1.3rem; }
#what-i-do .s-sub { font-size: 1.02rem; color: var(--ink); max-width: 46rem; }
#what-i-do .s-stack { color: var(--lamp-go); max-width: 46rem; }
#what-i-do summary { padding-top: var(--s4); padding-bottom: var(--s4); }
summary::-webkit-details-marker { display: none; }
summary:hover { background: var(--board); }
details[open] summary:hover { background: none; }

.s-title {
  font-family: var(--board-face); font-weight: 700; font-size: 1.15rem;
  line-height: 1.2; text-transform: uppercase; letter-spacing: .01em;
}
.s-sub { grid-column: 1 / -1; color: var(--ink-soft); font-size: .95rem; max-width: 35rem; }
.s-stack {
  grid-column: 1 / -1;
  font-family: var(--board-face); font-weight: 600; font-size: .74rem;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-faint);
  margin-top: var(--s1); max-width: 35rem;
}
/* Drawn in CSS, both states, so an open row reads correctly with the script
   off. */
.s-mark {
  width: 1.5rem; height: 1.5rem; display: grid; place-items: center;
  border: 1px solid var(--rule-hard); color: var(--ink-faint);
  flex: none; align-self: start; position: relative;
}
.s-mark::before, .s-mark::after {
  content: ""; position: absolute; background: currentColor;
}
.s-mark::before { width: .62rem; height: 2px; }
.s-mark::after { width: 2px; height: .62rem; transition: opacity .12s linear; }
details[open] .s-mark::after { opacity: 0; }
summary:hover .s-mark { border-color: var(--edge); color: var(--ink); }
details[open] .s-mark { color: var(--board); background: var(--edge); border-color: var(--edge); }
.body { padding: 0 var(--s2) var(--s4); max-width: 72ch; }
.body > :first-child { margin-top: 0; }
.body::before { content: ""; display: block; height: 1px; background: var(--rule); margin: 0 0 var(--s3); }

/* ---- principle rows --------------------------------------------------- */

/* Three principles to a row, each one a key, a claim and its reasoning. The
   column gap was 0 and the only separation was the cell's own right padding,
   17.6px, which is not a gutter: three columns of running text read as one
   stripe. --s5 between them, and the rule under a row now stops at each cell
   rather than running through the gutter. */
.principles { display: grid; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  column-gap: var(--s5); row-gap: 0; list-style: none; padding: 0; margin: 0;
  border-top: 2px solid var(--edge); }
.principles li { margin: 0; padding: var(--s3) 0; border-bottom: 1px solid var(--rule); }
/* The key. Same register as the deck's group label and the Publications group
   names: caps, tracked, small, no square and no underline. It is what this view
   was missing, and it turns six paragraphs into six things a reader can scan. */
.principles .p-key { font-family: var(--board-face); font-weight: 600; font-size: .74rem;
  letter-spacing: .14em; text-transform: uppercase; color: var(--lamp-go);
  margin: 0 0 var(--s1); }
/* Two lines of room whether the title needs them or not. Three of the six wrap
   and three do not, so the body text started 17px lower in the wrapped cells
   and no two paragraphs in a row shared a top edge. */
.principles h3 { margin-bottom: var(--s2); min-height: 2.6em; }
.principles p { color: var(--ink-soft); font-size: .95rem; margin: 0; }
@media (max-width: 40rem) { .principles h3 { min-height: 0; } }


/* ---- tables ----------------------------------------------------------- */

.tbl-scroll { overflow-x: auto; margin: 0 0 var(--s3); }
/* The heading used to introduce the timeline through a line of prose, which
   carried the gap. The prose is gone and the first row would otherwise sit on
   the heading, so the table takes the same gap the paragraph had. */
#background h3 + .tbl-scroll { margin-top: var(--s3); }
table { border-collapse: collapse; width: 100%; font-size: .93rem; }
td, th { text-align: left; vertical-align: top; padding: var(--s2) var(--s3) var(--s2) 0; border-bottom: 1px solid var(--rule); }
th {
  font-family: var(--board-face); font-weight: 600; font-size: .74rem;
  letter-spacing: .16em; text-transform: uppercase; color: var(--ink-faint);
  border-bottom: 2px solid var(--edge);
}
td:first-child { white-space: nowrap; font-weight: 650; width: 1%; padding-right: var(--s4); }
td.wrapcell:first-child { white-space: normal; }

/* Under 46rem a two-column table has no room for two columns, and the evidence
   table stopped being readable long before it stopped fitting. Measured at 360:
   398px of table inside a 325px box, because Deployment_Data_Science_Project
   carries no break opportunity and pins the first column at 267px, which leaves
   131px for the description. That is nine characters to a line, so every
   description ran ten lines and the view reached five screens, and 73px of it
   sat off-screen behind a scroll with no bar to say so. Scrolling did not help:
   the second column is 131px wide at every scroll position.
   Every gate passed it. check_render.py asks whether the document scrolls
   sideways and the overflow is inside .tbl-scroll, so the answer was no.
   The rows stack instead. The name takes its own line, the description takes
   the column at 40 characters, and Publications falls from 4.99 screens of a
   360x780 phone to 3.96, Parcours from 3.77 to 3.45. Measured, both languages.
   Stack keeps its columns: its first cell is a short label rather than an
   unbreakable token, its second is a comma list rather than prose, and stacking
   it would only make it taller. */
@media (max-width: 46rem) {
  #writing table, #writing tbody, #writing tr, #writing td,
  #background table, #background tbody, #background tr, #background td {
    display: block;
  }
  /* The rule moves from the cell to the row, or a stacked row draws two. */
  #writing td, #background td { border-bottom: 0; padding: 0; }
  #writing tr, #background tr {
    border-bottom: 1px solid var(--rule); padding: var(--s2) 0;
  }
  #writing td:first-child, #background td:first-child {
    white-space: normal; width: auto; padding-right: 0;
  }
  /* Without a column boundary the two cells read as one paragraph, so the
     description takes the ink the rest of the site gives a row description. */
  #writing td + td, #background td + td { color: var(--ink-soft); margin-top: var(--s1); }
}

.links-list { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr)); gap: var(--s2) var(--s4); }
.links-list li { margin-bottom: 0; }

.sub-rule { margin-top: var(--s5); }
/* Centred on the page. The button sits outside the prose block, after both
   columns, so it closes the view rather than ending the left column halfway up
   the page with the right one still running. The hero's row of two keeps its
   left edge, which is why this is scoped rather than put on .cta-row. */
#background .cta-row { justify-content: center; }
.pad-top { margin-top: var(--s3); }

/* ---- the closing row -------------------------------------------------- */

/* A site built from views has no reading order unless it states one. The
   answer for a while was that the nav is on every view and never leaves, which
   is true at 1440 and false on a phone: at the foot of Services the rail is
   sticky and present, and it shows 6 destinations at 1440 and 0 at 360, because
   they sit behind the menu button. Available is not invited.
   What was on screen instead, where a reader is warmest: a colophon whose five
   links all leave the site, and the back-to-top control, the most prominent
   thing there, offering to go backwards to a nav already visible.
   And mailto appeared twice on the landing view and nowhere else, so a reader
   arriving on a shared link to #work read twelve case studies and never met the
   invitation. One row answers both, in the 90px that already sat empty between
   the last rule and the footer.
   The next view's name is a row title, not a verified link: green means
   checkable and a view is not evidence, so no green underline and no corner
   arrow. */
.next { border-top: 2px solid var(--edge); margin-top: var(--s5); padding-top: var(--s3); }
.next-key {
  font-family: var(--board-face); font-weight: 600; font-size: .72rem;
  letter-spacing: .16em; text-transform: uppercase; color: var(--ink-faint);
  margin: 0 0 var(--s1);
}
/* a.next-link, not .next-link. The bare class is (0,1,0) and a:not(.v) is
   (0,1,1), which is exactly how the primary button once shipped its label
   invisible. */
a.next-link {
  font-family: var(--board-face); font-weight: 700; font-size: 1.15rem;
  line-height: 1.2; text-transform: uppercase; letter-spacing: .01em;
  text-decoration: none; display: inline-flex; align-items: center; gap: .45rem;
  border-bottom: 2px solid transparent;
}
a.next-link:hover, a.next-link:focus-visible { border-bottom-color: var(--edge); }
a.next-link::after {
  content: ""; width: .62em; height: .62em; flex: none;
  background: currentColor;
  -webkit-mask: var(--arrow-e) center / contain no-repeat;
  mask: var(--arrow-e) center / contain no-repeat;
  transition: transform .12s linear;
}
a.next-link:hover::after { transform: translateX(.2em); }
.next-sub { color: var(--ink-soft); font-size: .95rem; margin: var(--s1) 0 0; max-width: var(--measure); }
.next .cta-row { margin-top: var(--s3); }
/* Parcours centres its CV row, which was asked for and stays. That rule names
   every .cta-row in the view though, so it caught this one too and put the
   closing button at x=577 on Parcours against x=157 on the other five. The
   closing row is the same row on all six views, so it keeps the column's edge.
   Id-weighted, because the rule it has to beat is. */
#background .next .cta-row { justify-content: flex-start; }

/* The rule runs the full width, the text does not. The footer had no .wrap and
   so began at x=0 while every other line on the page began at the column's
   left edge, 246px in at 1440. It read as a strip belonging to the browser
   rather than to the page, and it had been that way since the first version. */
/* 68px, not 99. It was --s4 over --s5, the top of the scale, which is the
   setting for the end of a long document rather than for a view that is meant
   to be one screen: 82px of padding around 17px of text, 11 percent of a 900px
   window for six links. The asymmetry stays, because more air below a last
   line than above it is right; the amount does not. */
/* Centred and on the .7rem step. It is the one line on the page that addresses
   nobody: a name, a city and five links, read once and not returned to. Left
   aligned it started on the column's edge like a paragraph and asked to be read
   as one; centred it reads as a colophon, which is what it is. .7rem is the
   step the card's stack line already uses, not a new size, and the tracking
   stays because uppercase at that size needs it. */
footer {
  border-top: 2px solid var(--edge); padding: var(--s3) 0 var(--s4);
  font-family: var(--board-face); font-weight: 600; font-size: .7rem;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-faint);
  text-align: center;
}
footer a { color: var(--ink-soft); }

/* ---- motion: additive only -------------------------------------------- */

:root.js-anim .reveal { opacity: 0; }
:root.js-anim .reveal.in { opacity: 1; transition: opacity .4s linear; }

#top-btn {
  position: fixed; right: var(--s3); bottom: var(--s3); z-index: 30;
  width: 2.5rem; height: 2.5rem; cursor: pointer;
  display: grid; place-items: center; line-height: 0;
  background: var(--edge); color: var(--board); border: 0;
  opacity: 0; visibility: hidden;
  transition: opacity .2s linear, visibility .2s;
}
#top-btn.in { opacity: 1; visibility: visible; }
/* Clear of the text column rather than on top of it, and 44px where it is
   tapped rather than clicked. */
@media (max-width: 40rem) { #top-btn { right: var(--s2); bottom: var(--s2); width: 2.75rem; height: 2.75rem; } }
@media (max-width: 40rem) { footer { padding-bottom: var(--s6); } }
#top-btn:hover { background: var(--lamp-go); color: var(--board); }
#top-btn svg { width: 1.05rem; height: 1.05rem; }

:focus-visible { outline: 2px solid var(--lamp-go); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
  :root.js-anim .reveal { opacity: 1; }
  #top-btn { transition: none; }
}

@media (max-width: 40rem) {
  body { font-size: 16px; }
  .wrap { padding: 0 var(--s3); }
  .nav-in { padding: .45rem var(--s3); }
  header { padding: var(--s4) 0 var(--s3); }
  section { padding: var(--s5) 0; }
  .btn { width: 100%; justify-content: center; }
}

/* Skip link: 73 focusable elements sit between the top of the page and the
   content, and the nav repeats on every load. WCAG 2.4.1 is Level A. */
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 40;
  font-family: var(--board-face); font-weight: 700; font-size: .85rem;
  text-transform: uppercase; letter-spacing: .1em;
  background: var(--edge); color: var(--board);
  padding: .6rem 1rem; text-decoration: none;
}
.skip:focus { left: var(--s3); top: var(--s2); }

/* ---- views, and Work as a deck ----------------------------------------
   The page stopped being one document you scroll and became one view at a
   time, addressed by the URL. Pure CSS and the fragment: no script runs to
   make it work, the back button works, and a shared link opens on the view
   it names.

   A view shows when it is the target OR when it contains the target. The
   second half is not decoration: a shared link can name a card rather than a
   view, #c9 among them,
   and a card lives inside a view. With only .view:target, clicking 9 made the
   card the target, no view matched, Work vanished and the landing view came
   back. Two rules rather than one, because :has() cannot nest inside :has()
   and the obvious single selector is invalid and dropped in silence. */
.view { display: none; }
.view:target, .view:has(:target) { display: block; }
/* The landing view is the hero band and nothing else. On a screen taller than
   the hero it stopped mid-page and left ground between its rule and the
   footer's, which framed an empty strip and read as a section that failed to
   load. The band takes the height that is left instead, and its own rule goes:
   the footer already draws one, and two of them stacked make a 4px line. */
#home { display: flex; flex-direction: column; padding: 0; }
#home > header { flex: 1 0 auto; border-bottom: 0; }
html:has(.view:target:not(#home)) #home { display: none; }
/* Any target inside another view, not only a card. The card rule this replaces
   was true for the twelve anchors that exist and false for the id it does not
   name: #proof-band lives in Work, so a link to it showed Work and left the
   landing view underneath it. One descendant combinator covers every id in
   every view, including the ones nobody has written yet. */
html:has(.view:not(#home) :target) #home { display: none; }
/* No footer on the landing view. It repeated what the hero already holds on
   the same screen: the mail address, the four profiles, and the language, which
   the nav carries anyway. One selector rather than the two above, because this
   one needs both conditions to hold at once rather than either, and :not()
   takes a :has() even though :has() cannot take another. */
html:not(:has(.view:target:not(#home))):not(:has(.view:not(#home) :target)) footer { display: none; }
.view { padding: var(--s4) 0 var(--s5); border-top: 0; flex: 1 0 auto; }
/* A view that ends on the proof band does not pad under it: the band carries
   48px of its own and the footer draws the rule. Two paddings and two rules
   used to stack there, framing 48px of nothing at the foot of the longest view
   on the site. */

/* The deck. scroll-snap does the sliding, so it survives the script being off
   and answers touch, trackpad and the arrow keys. What it does not answer is a
   mouse wheel: a vertical wheel over a horizontal strip scrolls the page
   instead, measured at zero strip movement for 400px of page. Two arrows
   beside the counter answer that, and a page without the script keeps the
   system scrollbar for the same reason. */
/* Every card the same height. align-items: start let each take its own, which
   ranged 436 to 2403px and made the deck ragged as it slid. Stretching alone
   made them all 2403px, so the shortest carried two thousand pixels of nothing.
   No cap and no internal scroll answer that: a fixed 58vh cut the text off and
   put a scrollbar inside a card meant to be read at a glance. The 300-word cap
   does it in the content instead, and stretch matches every card to the
   tallest, which is the equal box asked for without the clipping. */
.strip {
  /* One card is the subject and the next is a sliver, on the right only.
     Centring put a slice of the previous card on the left as well, which reads
     as three things on screen rather than one and a hint of the next. start
     also needs room after the last card, or the twelfth can never reach the
     left edge: it arrives 160px in with the eleventh still beside it. That
     room is a pseudo-element and not padding-inline-end, which the browser
     leaves out of the scrollable area on a flex row, measured: the padding
     drew but the scroll range did not grow by a pixel. A pseudo is a flex item
     and counts. */
  --deck-card: calc((100% - var(--s3)) / 1.25);
  display: flex; align-items: stretch; gap: var(--s3);
  list-style: none; margin: 0; padding: 0 0 var(--s3);
  overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory;
  /* No scrollbar under the cards. On Windows Chrome it draws a full-weight grey
     system bar with two arrows, permanently, in none of the three surface
     colours, and a headless screenshot never shows it because that renders
     overlay scrollbars instead. The deck answers touch, trackpad, the arrow
     keys and twelve numbers, so the bar was the affordance and not the
     mechanism. scrollbar-width for Firefox and Chrome 121 up, the pseudo for
     everything older. */
}
:root.js-anim .strip { scrollbar-width: none; }
.strip::after { content: ""; flex: 0 0 calc(100% - var(--deck-card)); }
:root.js-anim .strip::-webkit-scrollbar { display: none; }
/* Every block in Work starts and ends on the page's edges, the strip included.
   Getting there took four states and each one was a real defect.
   The heading first ran the page's 74rem while the strip stopped at 52, so the
   deck ended 294px short of the text above it: a hole on one side only reads as
   a missing element. Then the whole Work column narrowed to the deck, which
   closed the hole and made Work the only view that does not start and end where
   the others do; clicking in stepped the page 147px inward and clicking out
   stepped it back, which is what a reader notices. Then the column matched the
   page again and the strip alone stayed capped, at 54rem, leaving 187px of
   ground to the right of the deck and nothing else.
   That last one is what Salif saw and rejected twice, and he is right that it
   is the same hole the first state had. The geometry admits no third answer:
   the column is 1126px, and a card plus a gap plus a quarter of the next has to
   equal it, so a quarter either costs an 887px card or it costs the strip's
   right edge. The edge stays and the card takes the width.
   The cost is the measure, stated rather than buried: 116 characters on a
   typical line and 129 at the longest, against 86 and 95 at 42rem and the 72 to
   78 the rest of the site sets. Both figures come from counting the characters
   in each rendered line box, since a canvas estimate reads 130 for the same
   card and the ladder below is in typical lines.
   What it buys, beyond the edge, is height. The tallest card goes from 620px to
   547 in English and 553 in French, so the whole card clears the fold at
   1440x900 for the first time, with 81px to spare.
   Two narrower columns inside the card were measured rather than assumed: 62
   characters, and no height at all (562px, worse than one column). Balancing
   fights break-inside on the paragraphs, so the second column ends a third of
   the way up and the card reads as one block of text beside a hole.
   The card is sized from the column and not capped, so the sliver is a quarter
   at every width instead of a quarter at one width. The counter's threshold is
   98 percent because scroll-snap always rests with a card at the left edge and
   fully visible: it reports the card that is whole, never the one that is
   merely mostly there, at any sliver. */
.strip:focus-visible { outline: 2px solid var(--lamp-go); outline-offset: 4px; }
/* The sliver of the next card is the only affordance a strip has. Reserved
   from the strip's own width, because a vw guess was wrong at both 1440 and
   360, leaving 3px and 4px showing. */
.card {
  /* The card is what is left of the column after a gap and a quarter of the
     next card, which at the page's 74rem is 887px, and no longer a width picked
     on its own. It was 42rem for one release, chosen off this ladder, all twelve
     cards taking the tallest one's height and that card reflowing in steps:

       36rem  76 characters  894px
       40rem  86 characters  825px
       42rem  90 characters  794px   <- step
       46rem 100 characters  794px
       48rem 104 characters  746px   <- step
       52rem 114 characters  734px
       55rem 116 characters  547px   <- the 150-word cap, and where it is now

     The last row is not the same measurement as the others: it was taken after
     the studies came down to 150 words, which is what took 200px off the card
     and is why 887px now costs height nothing. On the 300-word text a wider
     card bought nothing past 48rem.
     116 characters is past any readable band and it is the price of the strip
     reaching the page's right edge, argued where the strip is set. A card is
     scanned in a deck rather than read like a page, and this is the first
     geometry where the whole card is on screen at once at 1440x900. */
  flex: 0 0 var(--deck-card); scroll-snap-align: start; margin: 0;
  background: var(--card); padding: var(--s4);
  --ink-faint: var(--card-faint);
  --rule-hard: var(--card-rule);
  --rule: var(--card-hair);
  border-radius: var(--radius-card);
  box-shadow: var(--card-shadow);
}
.card-meta { margin: 0 0 var(--s3); font-family: var(--board-face); font-weight: 600;
  font-size: .74rem; letter-spacing: .14em; text-transform: uppercase; }
/* The group label carries the green. Salif asked for it in the cards and this
   is the one line in a card that exists on all twelve, where a verified link
   exists on eight. The cost is real and worth writing down: green on this site
   means checkable, and a category name is not checkable, so the colour is
   doing less work than it did. What keeps the two apart is the register. A
   lamp is a filled square beside a word; a verified link keeps the ink and puts
   the green on a 2px underline and a corner arrow. This is caps at 0.74rem with
   0.14em tracking, no square, no underline, no arrow, which is the register the
   column heads and the panel keys already use, and it leaves the label as the
   only green text in a card. */
.card-group { color: var(--lamp-go); }
.card-sum { display: grid; gap: var(--s1); padding: 0 0 var(--s3); }
/* The stack line read as a third paragraph of description because it sat in
   the same column at nearly the same weight. It is metadata, so it gets its
   own register: a rule above it, the faint ink, and tighter tracking. */
.card-sum > * { max-width: none; margin-top: 0; }
.card .s-stack {
  margin-top: var(--s2); padding-top: var(--s2);
  border-top: 1px solid var(--rule);
  /* Green, on Salif's call, like the group label above it. Same register as
     that one, which is what The Green Register Rule allows: caps, small,
     tracked, no square and no underline. It is the second green label in a
     card and the rule says plainly what it costs, so this is the last one. */
  color: var(--lamp-go); font-size: .7rem; letter-spacing: .12em;
}
.card .s-sub { color: var(--ink); }
/* One column, filling the card, on the secondary step rather than the body
   step. Prose on the page is 17px and the card was inheriting it, which put the
   study's own text above the lede that introduces it, at 16.32px, and left the
   description at 15.2px reading smaller than the paragraphs under it. On the
   secondary step the description and the body match, the hierarchy runs the
   right way, and the line goes from 67 characters to 75, which is the top of
   the readable band rather than past it. The two-column version was a workaround for a card that was too
   wide, and it read as two blocks where one was asked for. */
.card .body { padding: 0; max-width: none; font-size: .95rem; }
.card .body::before { display: none; }

/* 1.15rem is the row-title step the type ramp already has. The counter was on
   1.1rem, a step nobody declared, which is how a scale stops being one. */
.deck-count { font-family: var(--board-face); font-weight: 700; font-size: 1.15rem;
  letter-spacing: .1em; font-variant-numeric: tabular-nums; color: var(--ink);
  margin: 0 0 var(--s3); }
#cur { color: var(--lamp-go); }
/* Two controls, not twelve. The numbered row reached any study in one click
   and Salif asked for it gone: twelve boxes under a heading is a lot of chrome
   for a deck of twelve, and it read as pagination rather than as something that
   slides. Reaching the ninth study now costs eight clicks, which is the trade
   and it is his to make.
   The buttons are built by the script, the way the back-to-top button is, so
   that a page without JavaScript has no dead controls. That page keeps the
   system scrollbar instead: hiding it is scoped to :root.js-anim, which the
   script sets, so the deck always has exactly one way to move it with a mouse.
   The row is the counter's, so they sit on its baseline. */
.deck-count { display: flex; align-items: center; gap: var(--s3); }
.deck-nav { display: flex; gap: var(--s2); }
.deck-nav button {
  -webkit-user-select: none; user-select: none;
  display: grid; place-items: center; width: 2.4rem; height: 2.4rem;
  position: relative; padding: 0; background: none;
  /* --rule-hard, not --rule. The numbered row drew its boxes with --rule and
     nobody measured it: 1.53:1 on the ground in dark and 1.33 in light, against
     the 3:1 that 1.4.11 asks of the visible boundary of a control. The gate
     declared rule-hard for "control boundary on the page" and passed on a
     token the control did not use, which is the drift it exists to catch. It
     matters more now than it did with twelve boxes: these two are the only way
     to move the deck with a mouse. 4.12:1 and 3.04:1. */
  color: var(--ink-soft); border: 2px solid var(--rule-hard); cursor: pointer;
  transition: background-color .12s linear, color .12s linear, border-color .12s linear;
}
/* 2.4rem draws 38px and a touch target has to reach 44, so the hit area grows
   past the box rather than the box growing into the row. */
.deck-nav button::after { content: ""; position: absolute; inset: -.18rem; }
.deck-nav button:hover:not([disabled]) { border-color: var(--edge); color: var(--ink); }
.deck-nav button[disabled] { opacity: .35; cursor: default; }
.deck-nav svg { width: 1.1rem; height: 1.1rem; }

@media (max-width: 40rem) {
  /* calc(50% - 50vw), not a negative --s4. The wrap's gutter is --s4 on a
     desktop and --s3 on a phone, so a hardcoded pull overshot by 11.2px each
     side and the page scrolled sideways. This centres on the viewport whatever
     the gutter is, and cannot drift when that rule changes. */
  .strip { margin-inline: calc(50% - 50vw); padding-inline: var(--s3);
    scroll-padding-inline: var(--s3); }
  /* Three steps: the strip's left padding, the gap, and the sliver. Writing it
     as one step makes the sliver exactly --s3, which is also the card's padding
     at this width, so the sliver shows a card edge and never a word.
     It was --s3 + --s5, which left 30.4px showing: 17.6 of padding and 12.8 of
     text. Two characters of every line of the next study, clipped mid-word down
     the right edge of the screen, which reads as a rendering fault rather than
     as a hint that the deck slides. The affordance is sound at 1440, where the
     sliver is a quarter of a card and shows a card; at 360 a quarter of a card
     is not available and showing a quarter of the text is not the same idea. */
  .strip { --deck-card: calc(100vw - var(--s3) * 3); }
  .card { padding: var(--s3); }
}
