/* Mehdi Khalile , personal CV site. Warm paper, near-black ink, one electric blue. */

:root{
  --paper:#F2F1EC;
  --panel:#FFFFFF;
  --ink:#121211;
  --ink-2:#63625B;
  --ink-3:#8E8D84;
  --line:#E0DED4;
  --line-2:#EBE9E1;
  --accent:#2B4FFF;
  --accent-soft:rgba(43,79,255,.10);
  --glow:rgba(43,79,255,.11);
  --shadow:0 1px 2px rgba(18,18,17,.04),0 14px 36px -20px rgba(18,18,17,.26);

  --display:"Space Grotesk","Segoe UI",system-ui,sans-serif;
  --sans:"Instrument Sans","Segoe UI",system-ui,-apple-system,sans-serif;
  --mono:"JetBrains Mono",ui-monospace,"SFMono-Regular",Menlo,monospace;

  /* How far the figure falls back when the torch is not on it. No grayscale:
     he keeps his colour and only loses light. Two values, because that means
     opposite operations on the two grounds. On paper, lowering the luminance
     would make him a dark silhouette and more present, not less, so the light
     theme fades him toward the page instead. */
  --dim:.4;--dimf:none;

  --ease:cubic-bezier(.22,1,.36,1);
  --shell:min(1280px,90vw);
  --bar-h:64px;
}

:root[data-theme="dark"]{
  --paper:#0C0C0B;
  --panel:#161614;
  --ink:#F3F2EC;
  --ink-2:#A2A199;
  --ink-3:#77766E;
  --line:#2A2A26;
  --line-2:#201F1D;
  --accent:#7E96FF;
  --accent-soft:rgba(126,150,255,.14);
  --glow:rgba(126,150,255,.13);
  --shadow:0 1px 2px rgba(0,0,0,.4),0 18px 44px -24px rgba(0,0,0,.85);
  --dim:1;--dimf:brightness(.34);
  color-scheme:dark;
}

*{margin:0;padding:0;box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;scroll-padding-top:calc(var(--bar-h) + 24px)}
body{
  font-family:var(--sans);font-size:17px;line-height:1.62;font-weight:400;
  background:var(--paper);color:var(--ink);
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
  overflow-x:hidden;
}
img{display:block;max-width:100%;height:auto}
a{color:inherit;text-decoration:none}
ul{list-style:none}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
::selection{background:var(--accent);color:#fff}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:2px}

.shell{width:var(--shell);margin-inline:auto}
.mono{font-family:var(--mono);font-weight:500;font-size:.688rem;letter-spacing:.12em;text-transform:uppercase}
.dot{display:inline-block;width:3px;height:3px;border-radius:50%;background:currentColor;vertical-align:.28em;margin:0 .8em;opacity:.5}

/*  scroll progress  */
.progress{position:fixed;top:0;left:0;right:0;height:2px;z-index:60;pointer-events:none}
.progress i{display:block;height:100%;width:0;background:var(--accent)}

/*  top bar  */
.bar{
  position:fixed;top:0;left:0;right:0;z-index:50;height:var(--bar-h);
  display:flex;align-items:center;gap:24px;
  padding-inline:max(24px,calc((100vw - var(--shell)) / 2));
  /* Opaque, not a backdrop-filter. A blur over the whole bar is re-rasterised
     on every scrolled frame and was the single most expensive thing on the
     page; the bar is a solid strip of the page colour and nobody can tell. */
  background:var(--paper);
  border-bottom:1px solid transparent;
  transition:border-color .3s var(--ease);
}
.bar.stuck{border-bottom-color:var(--line)}

.monogram{
  display:grid;place-items:center;width:34px;height:34px;flex:none;
  border:1px solid var(--line);border-radius:8px;font-size:.7rem;letter-spacing:.06em;
  transition:border-color .25s,color .25s,background .25s;
}
.monogram:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}

.bar-nav{display:flex;gap:26px;margin-inline:auto}
.bar-nav a{position:relative;font-size:.875rem;color:var(--ink-2);transition:color .25s var(--ease)}
.bar-nav a::after{
  content:"";position:absolute;left:0;right:0;bottom:-7px;height:1.5px;
  background:var(--accent);transform:scaleX(0);transform-origin:0 50%;
  transition:transform .32s var(--ease);
}
.bar-nav a:hover,.bar-nav a.on{color:var(--ink)}
.bar-nav a.on::after{transform:scaleX(1)}

.bar-tools{display:flex;align-items:center;gap:10px;margin-left:auto}

/*  menu button and overlay 
    The bar below 920 was a monogram, two tools and a filled CTA pressed edge to
    edge, and the five section links had simply gone: display:none with nothing
    put in their place. The CTA leaves the bar at that width, the button takes
    its slot, and both the links and the ask move into the overlay, so the bar
    is a monogram and three small controls at every size. */
/* The same pill as the language and theme controls. Bare text beside three
   bordered controls read as a fourth thing of a different kind, and it is the
   most important of the four on a phone. */
.menu-btn{
  display:none;align-items:center;height:34px;padding:0 12px;cursor:pointer;
  background:none;border:1px solid var(--line);border-radius:8px;color:var(--ink-2);
  font-size:.688rem;transition:border-color .25s,color .25s,background .25s;
}
.menu-btn:hover{border-color:var(--ink-3);color:var(--ink)}

/* Inverted: --ink and --paper swap with the theme, so this is a dark panel on
   the light page and a light one on the dark page without a second rule. */
.ovl{
  position:fixed;inset:0;z-index:45;background:var(--ink);color:var(--paper);
  display:flex;align-items:center;visibility:hidden;
  clip-path:inset(0 0 100% 0);
  transition:clip-path .62s var(--ease),visibility 0s .62s;
}
body.menu .ovl{
  clip-path:inset(0 0 0 0);visibility:visible;
  transition:clip-path .62s var(--ease);
}
.ovl-in{
  width:var(--shell);margin-inline:auto;
  display:grid;grid-template-columns:1.15fr .85fr;gap:clamp(2.4rem,6vw,6rem);
  align-items:center;
}
.ovl-links{display:flex;flex-direction:column;gap:2px}
.ovl-links a{
  display:block;overflow:hidden;padding:.22em 0;color:inherit;
  font-family:var(--display);font-weight:500;letter-spacing:-.026em;line-height:1.16;
  font-size:clamp(1.8rem,7vw,3.2rem);
}
.ovl-links a span{display:block;transform:translateY(115%);transition:transform .62s var(--ease)}
body.menu .ovl-links a span{transform:none}
body.menu .ovl-links a:nth-child(1) span{transition-delay:.14s}
body.menu .ovl-links a:nth-child(2) span{transition-delay:.20s}
body.menu .ovl-links a:nth-child(3) span{transition-delay:.26s}
body.menu .ovl-links a:nth-child(4) span{transition-delay:.32s}
body.menu .ovl-links a:nth-child(5) span{transition-delay:.38s}
.ovl-links a:hover span{color:var(--accent)}

.ovl-side{display:flex;flex-direction:column;align-items:flex-start;gap:4px}
.ovl-lab{opacity:.5;margin-bottom:12px}
.ovl-mail{color:inherit;font-size:1.05rem;padding:.2rem 0;border-bottom:1px solid transparent}
.ovl-mail:hover{border-bottom-color:var(--accent);color:var(--accent)}
.ovl-sec{color:inherit;opacity:.62;padding:.2rem 0;font-size:.95rem}
.ovl-sec:hover{opacity:1;color:var(--accent)}
.ovl-loc{opacity:.42;margin-top:14px}

/* The bar sits above the overlay so its own button can close it, which means
   the bar has to stop painting its strip of page colour the moment it does. */
body.menu .bar{background:transparent;border-bottom-color:transparent}
body.menu .bar .monogram,body.menu .bar .tool,body.menu .menu-btn{
  color:var(--paper);border-color:rgba(128,128,128,.42);
}
body.menu .bar .cta{display:none}
body.lock{overflow:hidden}
.bar-nav + .bar-tools{margin-left:0}

.tool{
  display:flex;align-items:center;gap:7px;height:34px;padding:0 11px;
  border:1px solid var(--line);border-radius:8px;color:var(--ink-2);
  transition:border-color .25s,color .25s,background .25s;
}
.tool:hover{border-color:var(--ink-3);color:var(--ink)}
.tool .off{opacity:.42}
.tool-sep{width:1px;height:12px;background:var(--line);flex:none}
.tool-ico{padding:0;width:34px;justify-content:center}

.cta{
  display:inline-flex;align-items:center;height:34px;padding:0 15px;
  border-radius:8px;background:var(--ink);color:var(--paper);
  font-size:.82rem;font-weight:600;letter-spacing:-.005em;white-space:nowrap;
  transition:transform .25s var(--ease),background .25s;
}
.cta:hover{transform:translateY(-1px);background:var(--accent);color:#fff}

/*  hero 
   The figure is a cutout standing at the right edge, lit from behind and
   running off the bottom of the section rather than sitting in a frame. A
   portrait boxed in its own column beside the text is the layout every CV
   template ships with. */
.hero{
  position:relative;min-height:100svh;display:flex;align-items:center;
  padding:calc(var(--bar-h) + 5vh) 0 12vh;overflow:hidden;isolation:isolate;
}
.hero::before{
  content:"";position:absolute;z-index:0;right:-6vw;top:-10vh;width:74vw;height:120vh;
  background:radial-gradient(46% 42% at 62% 45%,var(--glow),transparent 72%);
  pointer-events:none;
}
/* Top is set against the bar rather than as a percentage of the hero, or a
   short viewport crops the top of his head. */
.figure{
  position:absolute;z-index:1;right:calc((100vw - var(--shell)) / 2 - 3vw);
  top:calc(var(--bar-h) + 10px);bottom:0;
  display:flex;align-items:flex-end;pointer-events:none;
}
.figure picture{display:contents}
.figure img{height:100%;width:auto;max-width:none}
.figure img.gone{display:none}

/* The figure rests dim and a spotlight above him turns on and off from a pull
   cord. Two copies of the cutout: the base, held back, and a full one shown
   through a mask that falls off downward, so the light reads as coming from
   over his head rather than from everywhere at once. */
.fig-base{display:block;height:100%}
.fig-base img{opacity:var(--dim);filter:var(--dimf)}

.torch{
  position:absolute;inset:0;pointer-events:none;opacity:0;
  transition:opacity .6s var(--ease);
  -webkit-mask-image:radial-gradient(125% 88% at 50% -8%,#000 0%,#000 40%,rgba(0,0,0,.42) 74%,transparent 96%);
          mask-image:radial-gradient(125% 88% at 50% -8%,#000 0%,#000 40%,rgba(0,0,0,.42) 74%,transparent 96%);
}
.torch img{height:100%;width:auto;max-width:none}
.lit-on .torch{opacity:1}

/* The beam itself. A cone narrowing to a point above the frame, so the source
   is off screen and only its light is in it. */
/* The light itself, as a soft pool falling from a source above the frame rather
   than a drawn cone. A clip-path cone has hard edges and reads as a grey
   polygon lying on the page; light has no outline. Two stops: a tight one at
   the source, near where the cord hangs, and a wide one over him. */
.beam{
  position:absolute;inset:0;z-index:0;pointer-events:none;opacity:0;
  transition:opacity .6s var(--ease);
  background:
    radial-gradient(30% 26% at 57% -6%, rgba(255,236,196,.20), transparent 70%),
    radial-gradient(52% 82% at 68% -14%, rgba(255,232,188,.13), transparent 68%);
}
:root[data-theme="dark"] .beam{
  background:
    radial-gradient(30% 26% at 57% -6%, rgba(255,240,205,.30), transparent 72%),
    radial-gradient(56% 88% at 68% -14%, rgba(255,236,196,.16), transparent 70%);
}
.lit-on .beam{opacity:1}


/* The cord. Two linked segments, not one rigid stick: the upper one carries the
   swing and the lower one follows a beat behind, so the line visibly bends at
   the join. The bead lives inside the lower segment, which is why it stays on
   the end of its own line. Rotating the bead alone around a point above itself
   also trailed, but it slid off the wire and hung in mid air. */
.cord{
  /* Hangs from just under the bar, not from the very top: half a cord behind an
     opaque header reads as a rendering fault, not as a ceiling. */
  position:absolute;z-index:6;top:calc(var(--bar-h) - 6px);left:56%;
  width:38px;height:280px;
  padding:0;background:none;border:0;cursor:pointer;
  transform-origin:50% 0;
}
/* Zero-size anchors: each segment is only a pivot, and its line hangs off it. */
.seg{position:absolute;left:50%;top:0;width:0;height:0}
.seg > i{position:absolute;left:-.5px;top:0;width:1px;background:var(--ink-3);opacity:.7}
.seg-up > i{height:134px}
.seg-low{top:134px;transform-origin:50% 0}
.seg-low > i{height:88px}
.seg-low > b{
  position:absolute;left:-5.5px;top:82px;width:11px;height:26px;border-radius:6px;
  background:var(--ink-3);
  transition:background .25s var(--ease),height .25s var(--ease);
}
.cord:hover b,.cord:focus-visible b{background:var(--accent);height:30px}
.cord:focus-visible{outline-offset:2px}

/* The invitation. It says what to do once and never again: after the first pull
   the cord has taught its own lesson. */
.cord-hint{
  position:absolute;right:calc(100% + 10px);top:196px;
  color:var(--ink-3);font-size:.6rem;white-space:nowrap;
  opacity:0;animation:hintIn .8s var(--ease) 1.9s forwards;
}
@keyframes hintIn{to{opacity:.9}}
.cord.used .cord-hint{animation:none;opacity:0;transition:opacity .4s var(--ease)}

/* Until it has been pulled once it breathes, so a still cord does not read as a
   drawn line. It stops for good on the first pull. */
@keyframes sway{
  0%,100%{transform:rotate(-1.1deg)}
  50%    {transform:rotate(1.1deg)}
}
.cord:not(.pull):not(.used){animation:sway 5.2s ease-in-out infinite}
.cord:not(.pull):not(.used):hover{animation-play-state:paused}

/* A damped pendulum: a short downward tug, then arcs that each lose about a
   quarter of the last. ease-in-out between the keyframes, because a pendulum is
   slowest at the ends of its travel and fastest through the middle; a single
   ease over the whole animation flattens all of that into a slide. */
@keyframes pull{
  0%  {transform:rotate(0) translateY(0)}
  5%  {transform:rotate(0) translateY(15px)}
  12% {transform:rotate(-1.5deg) translateY(0)}
  20% {transform:rotate(-9.6deg)}
  31% {transform:rotate(7.3deg)}
  42% {transform:rotate(-5.4deg)}
  53% {transform:rotate(3.9deg)}
  63% {transform:rotate(-2.8deg)}
  73% {transform:rotate(2deg)}
  82% {transform:rotate(-1.3deg)}
  90% {transform:rotate(.8deg)}
  96% {transform:rotate(-.4deg)}
  100%{transform:rotate(0)}
}
/* The lower segment, at a third of the amplitude and a beat behind. */
@keyframes lag{
  0%  {transform:rotate(0)}
  8%  {transform:rotate(0)}
  22% {transform:rotate(-3.4deg)}
  33% {transform:rotate(2.6deg)}
  44% {transform:rotate(-1.9deg)}
  55% {transform:rotate(1.4deg)}
  65% {transform:rotate(-1deg)}
  75% {transform:rotate(.6deg)}
  85% {transform:rotate(-.35deg)}
  100%{transform:rotate(0)}
}
.cord.pull{animation:pull 2.8s ease-in-out}
.cord.pull .seg-low{animation:lag 2.9s ease-in-out .07s}


}
/* No drop-shadow. On a 639x1250 cutout that is a per-frame filter pass while
   the figure is being parallaxed, and it is what made the hero scroll stutter.
   The radial glow behind it already separates the figure from the ground. */

.hero-in{position:relative;z-index:2}

.kicker{color:var(--ink-2);display:flex;align-items:center;flex-wrap:wrap}
.live{
  display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--accent);
  margin-right:.85em;flex:none;
  animation:pulse 2.6s var(--ease) infinite;
}
@keyframes pulse{
  0%{box-shadow:0 0 0 0 var(--accent-soft)}
  70%{box-shadow:0 0 0 9px transparent}
  100%{box-shadow:0 0 0 0 transparent}
}

.name{
  font-family:var(--display);font-weight:700;
  font-size:clamp(3.9rem,13.1vw,11.4rem);
  line-height:.84;letter-spacing:-.048em;
  margin:clamp(16px,2.6vw,30px) 0 clamp(20px,2.6vw,30px);
}
/* Each line masks its own reveal, so the type slides out from behind a hard
   edge rather than fading in place. */
.name .line{display:block;overflow:hidden;padding-bottom:.06em}
.name .line > span{display:block}
.name .n2{
  color:transparent;-webkit-text-stroke:1.5px var(--ink);padding-left:.055em;
}
@supports not (-webkit-text-stroke: 1px black){ .name .n2{color:var(--ink)} }

.role{
  color:var(--accent);letter-spacing:.2em;
  display:flex;align-items:center;gap:14px;
}
.role::after{content:"";height:1px;width:clamp(40px,7vw,110px);background:var(--accent);opacity:.45}

.lede{
  margin-top:22px;font-size:clamp(1.02rem,1.28vw,1.18rem);line-height:1.6;
  color:var(--ink-2);max-width:38ch;
}
html[lang="fr"] .lede{max-width:42ch}

.hero-act{display:flex;gap:12px;flex-wrap:wrap;margin-top:34px}
.btn{
  display:inline-flex;align-items:center;height:46px;padding:0 24px;border-radius:999px;
  border:1px solid var(--line);font-size:.94rem;font-weight:500;background:var(--paper);
  transition:border-color .25s,background .25s,color .25s,transform .25s var(--ease);
}
.btn:hover{transform:translateY(-2px);border-color:var(--ink-3)}

/* Buttons that carry an icon. The icon animates on hover only: a permanent
   loop on two things sitting next to each other reads as a page that has not
   finished loading. */
.btn-ico{gap:11px;padding-left:20px}
.ico{width:18px;height:18px;flex:none;overflow:visible}
/* SVG children default to the viewBox origin for transforms, so every rotation
   would pivot from the corner of the canvas. fill-box makes the shape its own
   reference. */
.ico *{transform-box:fill-box}

/* Four things set an icon going, and every rule below carries the same pair of
   selectors: a hero button, a footer icon link or a contact row that is a link,
   under pointer or keyboard focus; and a contact row as it arrives on screen,
   which app.js marks `.on` with a stagger. Entry and hover therefore play the
   same gesture, and each gesture is written once.

   The flap. A V under the top edge, flipped to a lambda: the envelope opens. */
.ico-mail .flap{
  transform-origin:50% 0;
  transition:transform .42s var(--ease);
}
:is(.btn-ico,.soc,.reach li:has(a)):is(:hover,:focus-visible) .ico-mail .flap,
.reach li.on .ico-mail .flap{
  transform:scaleY(-1);
}

/* LinkedIn. The box holds still and the three parts of the glyph arrive inside
   it, the dot first because it is the one that can pop rather than rise. */
:is(.btn-ico,.soc,.reach li:has(a)):is(:hover,:focus-visible) .ico-li .li-d,
.reach li.on .ico-li .li-d{animation:liPop .5s var(--ease)}
:is(.btn-ico,.soc,.reach li:has(a)):is(:hover,:focus-visible) .ico-li .li-i,
.reach li.on .ico-li .li-i{animation:liRise .5s var(--ease) .06s both}
:is(.btn-ico,.soc,.reach li:has(a)):is(:hover,:focus-visible) .ico-li .li-n,
.reach li.on .ico-li .li-n{animation:liRise .5s var(--ease) .14s both}
@keyframes liPop{0%{transform:scale(.2)}62%{transform:scale(1.3)}100%{transform:scale(1)}}
@keyframes liRise{from{transform:translateY(50%);opacity:0}to{transform:none;opacity:1}}

/* The pin drops onto its own shadow, which swells to meet it. The shadow is
   there at rest as well, or the icon changes shape rather than moves. */
.ico-pin .pin-sh{opacity:.22}
:is(.btn-ico,.soc,.reach li:has(a)):is(:hover,:focus-visible) .ico-pin .pin-b,
.reach li.on .ico-pin .pin-b{animation:pinDrop .62s var(--ease)}
:is(.btn-ico,.soc,.reach li:has(a)):is(:hover,:focus-visible) .ico-pin .pin-sh,
.reach li.on .ico-pin .pin-sh{animation:pinShade .62s var(--ease)}
@keyframes pinDrop{0%{transform:translateY(-40%)}62%{transform:translateY(5%)}100%{transform:none}}
@keyframes pinShade{0%{opacity:0;transform:scaleX(.35)}58%{opacity:.4;transform:scaleX(1.06)}100%{opacity:.22;transform:none}}

/* One sweep of the hour. transform-box is view-box here, not the fill-box every
   other icon child uses: a hand is a straight line, its fill box has zero width,
   and a percentage origin on a zero dimension cannot find the dial's centre. */
.ico-clock .ck-m,.ico-clock .ck-h{transform-box:view-box;transform-origin:12px 12px}
:is(.btn-ico,.soc,.reach li:has(a)):is(:hover,:focus-visible) .ico-clock .ck-m,
.reach li.on .ico-clock .ck-m{animation:sweep 1.05s var(--ease)}
:is(.btn-ico,.soc,.reach li:has(a)):is(:hover,:focus-visible) .ico-clock .ck-h,
.reach li.on .ico-clock .ck-h{animation:sweepH 1.05s var(--ease)}
@keyframes sweep{from{transform:rotate(0)}to{transform:rotate(360deg)}}
@keyframes sweepH{from{transform:rotate(0)}to{transform:rotate(90deg)}}
.btn-solid{background:var(--accent);border-color:var(--accent);color:#fff}
.btn-solid:hover{border-color:var(--accent);filter:brightness(1.08)}

.scroller{
  position:absolute;z-index:2;left:max(24px,calc((100vw - var(--shell)) / 2));
  bottom:30px;color:var(--ink-3);display:flex;align-items:center;gap:10px;
}
.scroller::after{
  content:"";width:1px;height:34px;background:currentColor;
  transform-origin:50% 0;animation:drop 2.4s var(--ease) infinite;
}
@keyframes drop{0%,100%{transform:scaleY(.25);opacity:.35}50%{transform:scaleY(1);opacity:1}}

/*  reveal  */
.js .rv{opacity:0;transform:translateY(22px)}

/*  sections  */
.sec{padding:clamp(60px,8vw,116px) 0;border-top:1px solid var(--line)}

/* The section straight after the hero runs inverted, so the page opens light,
   turns over once, and comes back. It is a local override of the same variables
   everything else reads, which is why nothing inside it needs its own rule: the
   text, the hairlines, the accent and the panels all follow.
   The accent swaps too. #2B4FFF on near-black is 3.1:1 and unreadable at the
   sizes it is used at here. */
#profile{
  --paper:#121211;--panel:#1A1A18;
  --ink:#F3F2EC;--ink-2:#A2A199;--ink-3:#77766E;
  --line:#2C2C28;--line-2:#232320;
  --accent:#7E96FF;--accent-soft:rgba(126,150,255,.14);
  --shadow:0 1px 2px rgba(0,0,0,.4),0 18px 44px -24px rgba(0,0,0,.85);
  background:var(--paper);color:var(--ink);border-top-color:transparent;
}
:root[data-theme="dark"] #profile{
  --paper:#F2F1EC;--panel:#FFFFFF;
  --ink:#121211;--ink-2:#63625B;--ink-3:#8E8D84;
  --line:#E0DED4;--line-2:#EBE9E1;
  --accent:#2B4FFF;--accent-soft:rgba(43,79,255,.10);
  --shadow:0 1px 2px rgba(18,18,17,.04),0 14px 36px -20px rgba(18,18,17,.26);
}
/* The section that follows draws the rule the inverted band cannot. */
#profile + .sec{border-top-color:transparent}
.sec-grid{display:grid;grid-template-columns:minmax(0,240px) minmax(0,1fr);gap:clamp(28px,5vw,76px)}
.sec-head{position:sticky;top:calc(var(--bar-h) + 34px);align-self:start}
/* The numeral is set in outline, the same device the surname uses in the hero.
   One idea stated twice is an identity; stated once it is a decoration. */
.sec-head .lab{display:block;color:var(--ink-3);margin-bottom:14px}
.sec-head .num{
  display:block;font-family:var(--display);font-weight:700;
  font-size:clamp(2.8rem,4.4vw,4rem);line-height:.9;letter-spacing:-.04em;
  color:transparent;-webkit-text-stroke:1.2px var(--accent);margin-bottom:6px;
}
@supports not (-webkit-text-stroke: 1px black){ .sec-head .num{color:var(--accent)} }
.sec-head h2{
  font-family:var(--display);font-weight:500;
  font-size:clamp(1.6rem,2.7vw,2.25rem);line-height:1.06;letter-spacing:-.03em;
}
.sec-head .rule{
  display:block;height:1px;background:var(--line);margin-top:20px;
  transform:scaleX(0);transform-origin:0 50%;
}
.sec-body > * + *{margin-top:22px}

/*  wide sections 
   Profile, Skills and Contact do not use the two-column grid. They were three
   copies of one skeleton, and a page where every section is built the same way
   has no shape. Journey and Projects keep the grid, so it still reads as the
   spine rather than as the only idea. */
.sec-wide .shell > * + *{margin-top:clamp(30px,4vw,54px)}
.wide-head{display:flex;align-items:baseline;gap:clamp(14px,2vw,26px);flex-wrap:wrap}
.wide-head .lab{color:var(--ink-3)}
.wide-head .num{
  font-family:var(--display);font-weight:700;font-size:clamp(2.2rem,3.4vw,3.1rem);
  line-height:1;letter-spacing:-.04em;color:transparent;
  -webkit-text-stroke:1.2px var(--accent);
}
@supports not (-webkit-text-stroke: 1px black){ .wide-head .num{color:var(--accent)} }
.wide-head h2{
  font-family:var(--display);font-weight:500;letter-spacing:-.03em;line-height:1;
  font-size:clamp(1.6rem,2.7vw,2.25rem);
}
.wide-head .rule{
  flex:1 1 120px;height:1px;background:var(--line);align-self:center;
  transform:scaleX(0);transform-origin:0 50%;
}

/* The statement is the section. app.js cuts it into words and scrubs their
   opacity against the scroll, so it is read at the pace it is scrolled rather
   than arriving all at once. It rests at .34 rather than near zero: any lower
   and a screenshot taken mid scroll looks like text that failed to load. */
.statement{
  font-family:var(--display);font-weight:500;
  font-size:clamp(1.42rem,3vw,2.6rem);line-height:1.18;letter-spacing:-.03em;
  max-width:31ch;color:var(--ink);
}
.statement .w{display:inline-block;white-space:pre}
.js .statement .w{opacity:.34}
html[lang="fr"] .statement{max-width:33ch}

/*  the method
    Four steps, drawn down a spine that fills with the scroll. It replaced a
    paragraph whose second half was adjectives, and adjectives are the part of a
    CV nobody believes: each step names the work and what comes out of it. */
.method{position:relative;padding-left:clamp(28px,4vw,64px)}
.mt-lab{color:var(--ink-3);margin-bottom:clamp(20px,2.6vw,32px)}

/* The spine sits on the container, not between the items, so its fill is one
   uninterrupted travel rather than four segments meeting at the padding. */
.mt-spine{
  position:absolute;left:0;top:calc(clamp(20px,2.6vw,32px) + 1.4em);bottom:0;
  width:1px;background:var(--line);
  /* The last dot sits near the top of the last step, so the line would otherwise
     run 150px past it and stop at nothing. Masked out instead of measured: the
     tail's length depends on how many lines that step wraps to. */
  -webkit-mask-image:linear-gradient(180deg,#000 78%,transparent 100%);
          mask-image:linear-gradient(180deg,#000 78%,transparent 100%);
}
.mt-spine i{
  position:absolute;inset:0;background:var(--accent);
  transform-origin:50% 0;
}

.mt-i{
  position:relative;display:grid;
  grid-template-columns:auto minmax(0,1fr) auto;
  gap:clamp(18px,2.4vw,34px);align-items:baseline;
  padding:clamp(20px,2.4vw,30px) 0;border-top:1px solid var(--line-2);
}
.mt-i:first-child{border-top:0}

/* Every step is written settled and `.js` is what dims it, the same way .rv
   works. Without JS, and under reduced motion, nothing ever adds `.on`, and a
   block whose resting state is four grey rows and four invisible chips would
   simply be broken there.

   The dot on the spine, which is why the numeral column is a fixed width: at
   auto the four numerals are the same width in English and not in French, and
   the dots would step in and out of the line. */
.mt-n{width:2.4em;color:var(--accent);transition:color .45s var(--ease)}
.mt-i::before{
  content:"";position:absolute;
  left:calc(clamp(28px,4vw,64px) * -1);top:calc(clamp(20px,2.4vw,30px) + .5em);
  width:7px;height:7px;border-radius:50%;margin-left:-3px;
  background:var(--accent);border:1px solid var(--accent);
  transition:background .45s var(--ease),border-color .45s var(--ease),transform .45s var(--ease);
}
.mt-b h3{
  font-family:var(--display);font-weight:500;letter-spacing:-.02em;line-height:1.14;
  font-size:clamp(1.16rem,1.85vw,1.5rem);
  color:var(--ink);transition:color .45s var(--ease);
}
.mt-b p{
  margin-top:9px;color:var(--ink-2);font-size:.99rem;line-height:1.55;max-width:46ch;
  transition:color .45s var(--ease);
}
/* What comes out of the step, which is the half that makes it a method rather
   than a description. It arrives from the right as the step lights. */
.mt-out{
  color:var(--ink-2);white-space:nowrap;
  padding:6px 12px;border:1px solid var(--line);border-radius:999px;
  transition:opacity .5s var(--ease),transform .5s var(--ease),color .45s var(--ease);
}

.js .mt-n{color:var(--ink-3)}
.js .mt-i::before{background:var(--paper);border-color:var(--line)}
.js .mt-b h3,.js .mt-b p{color:var(--ink-3)}
.js .mt-out{opacity:0;transform:translateX(14px);color:var(--ink-3)}
.js .mt-spine i{transform:scaleY(0)}

.js .mt-i.on::before{background:var(--accent);border-color:var(--accent);transform:scale(1.28)}
.js .mt-i.on .mt-n{color:var(--accent)}
.js .mt-i.on .mt-b h3{color:var(--ink)}
.js .mt-i.on .mt-b p{color:var(--ink-2)}
.js .mt-i.on .mt-out{opacity:1;transform:none;color:var(--ink-2)}

/*  the profile body
    The facts on the left, the method on the right. They answer each other, what
    he has and how he works, and neither is a paragraph. The left column is the
    narrower of the two: its values are four short strings, the method's steps
    are a heading, a sentence and a chip. */
.profile-body{
  display:grid;grid-template-columns:minmax(0,.82fr) minmax(0,1.18fr);
  gap:clamp(30px,5vw,76px);align-items:start;
}
/* Both columns open on the same line, so their labels share a baseline and the
   two lists start together. The left one is a third the height of the right, so
   it rides down with the reading rather than leaving a hole: the same sticky the
   two-column sections already use for their heads. */
.pb-col{min-width:0}
.pb-col:first-child{position:sticky;top:calc(var(--bar-h) + 34px)}

/* Each fact carries a mark, so the row is an icon column and a stacked pair
   rather than a label column and a value column. The label sat on the value's
   baseline before, which reads as a table; a fact is not a cell. */
.sec-wide .facts{margin:0}
.sec-wide .facts li{
  display:grid;grid-template-columns:auto minmax(0,1fr);gap:18px;
  align-items:center;padding:16px 0;border-bottom:1px solid var(--line-2);
}
.sec-wide .facts .t{color:var(--ink-3)}
.sec-wide .facts .v{font-size:.99rem;max-width:none}
.fx-i{
  display:grid;place-items:center;width:40px;height:40px;border-radius:50%;
  border:1px solid var(--line);color:var(--ink-2);
}
.fx-i .ico{width:19px;height:19px}
.fx-b{display:flex;flex-direction:column;gap:5px;min-width:0}

/* The marks arrive with their rows. The cap's tassel swings, the same gesture
   the hero seal plays, which is the whole reason the same cap is down here. */
.facts li.on .fx-i{animation:fxRing .55s var(--ease)}
.facts li.on .ico-cap .tassel{animation:tSwing .95s var(--ease) .12s}
.facts li.on .ico-globe .gl-m{animation:glTurn .8s var(--ease) .1s}
.facts li.on .ico-aim .am-o{animation:amIn .6s var(--ease) .06s}
.facts li.on .ico-aim .am-i{animation:amIn .6s var(--ease) .16s}
.facts li.on .ico-aim .am-d{animation:amIn .6s var(--ease) .26s}
.facts li.on .ico-pin .pin-b{animation:pinDrop .62s var(--ease) .1s}
.facts li.on .ico-pin .pin-sh{animation:pinShade .62s var(--ease) .1s}
@keyframes fxRing{from{transform:scale(.7);opacity:0}to{transform:none;opacity:1}}
/* fill-box puts the origin on the tassel's own box, whose top centre is exactly
   where the cord meets the board. */
.ico-cap .tassel{transform-origin:50% 0}
@keyframes tSwing{
  0%{transform:rotate(15deg)}
  40%{transform:rotate(-7deg)}
  68%{transform:rotate(3deg)}
  100%{transform:rotate(0)}
}
/* The meridian is a flat oval at the halfway point, so the globe reads as
   turning rather than as a shape being squashed. */
@keyframes glTurn{
  0%{transform:scaleX(1)}
  50%{transform:scaleX(.12)}
  100%{transform:scaleX(1)}
}
@keyframes amIn{from{transform:scale(.35);opacity:0}to{transform:none;opacity:1}}


.big{font-size:clamp(1.14rem,1.85vw,1.5rem);line-height:1.46;letter-spacing:-.012em}
.sec-body p{color:var(--ink-2);max-width:66ch}
/* The lead paragraph is display type, so it takes a shorter measure than the
   body under it. It has to out-specify .sec-body p to get one. */
.sec-body .big{color:var(--ink);max-width:52ch}


/* No bullet glyph, no dash, no hairline under the item. Spacing separates the
   lines and the lead term in a heavier weight carries the eye down the column. */
.lines{display:grid;gap:12px}
.lines li{font-size:.95rem;line-height:1.52;color:var(--ink-2)}
.lines b{font-weight:600;color:var(--ink)}
.lines.tight{gap:9px;margin-top:14px}
.lines.tight li{font-size:.92rem}

/* The mark sits on the page with nothing behind it. A plate is a second shape
   competing with the logo's own, and two logos of different proportions never
   look like they belong in the same one. Each ships in two files instead: the
   dark variant lifts the black ink to the paper colour and leaves the brand
   colours alone, because black artwork on a dark ground with no plate under it
   is invisible. */
.mark{display:block;height:26px}
.mark img{height:100%;width:auto;max-width:none}
.mark .dk{display:none}
:root[data-theme="dark"] .mark .lt{display:none}
:root[data-theme="dark"] .mark .dk{display:block}

/*  02 journey 
   A vertical line with the year in the gutter. No pin, no hijack: the section
   scrolls at page speed and the only thing tied to scroll is the accent fill
   growing down the line, which is a transform on one element. */
.jr-note{display:block;color:var(--ink-3);margin-top:18px}

.tl{position:relative}
/* The rule the whole journey hangs from, and the part already travelled. */
.tl-line{
  position:absolute;left:112px;top:8px;bottom:16px;width:1px;
  background:var(--line);pointer-events:none;
}
.tl-line i{
  display:block;height:100%;background:var(--accent);
  transform:scaleY(0);transform-origin:50% 0;
}

.tl-i{
  position:relative;display:grid;grid-template-columns:112px minmax(0,1fr);
  gap:42px;padding-bottom:44px;
}
.tl-i:last-child{padding-bottom:0}
.tl-y{
  color:var(--ink-3);font-size:.78rem;text-align:right;padding-right:26px;
  padding-top:.28em;transition:color .3s var(--ease);
}
.tl-i::before{
  content:"";position:absolute;left:107px;top:.42em;width:11px;height:11px;
  border-radius:50%;background:var(--paper);border:1px solid var(--line);z-index:2;
  transition:border-color .35s var(--ease),background .35s var(--ease),transform .35s var(--ease);
}
.tl-i.on .tl-y{color:var(--accent)}
.tl-i.on::before{border-color:var(--accent);background:var(--accent);transform:scale(1.3)}

.tl-c{min-width:0}
.tl-top{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-bottom:12px}
.tl-i .kind{display:inline-block;color:var(--ink-3);margin-bottom:12px;
  transition:color .3s var(--ease)}
.tl-top .kind{margin-bottom:0}
.tl-i.on .kind{color:var(--accent)}
.tl-i h3{
  font-family:var(--display);font-weight:500;letter-spacing:-.024em;line-height:1.16;
  font-size:clamp(1.12rem,1.7vw,1.42rem);
}
.tl-i .who{margin-top:7px;font-size:.92rem;color:var(--ink-3)}
.tl-i .who a{border-bottom:1px solid var(--line)}
.tl-i .who a:hover{color:var(--accent);border-color:var(--accent)}
.tl-i .say{margin-top:14px;font-size:.95rem;line-height:1.55;color:var(--ink-2);max-width:52ch}
.tl-i .lines li{font-size:.94rem;line-height:1.55}
.tl-cta{margin-top:22px;height:44px;padding:0 22px;font-size:.9rem}

/* The work entries are the substance, so they carry a surface. The panel is
   .tl-c and not .tl-i, so the line and the node stay outside it. */
.is-work .tl-c{
  background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:22px 24px;
  transition:box-shadow .35s var(--ease),border-color .35s var(--ease);
}
.is-work:hover .tl-c,.is-work:focus-within .tl-c,.is-work.on .tl-c{
  box-shadow:var(--shadow);border-color:var(--accent);
}
/* The two degrees are achievements, so they carry a surface of their own: an
   accent wash rather than the work cards' panel, so the line reads as two kinds
   of thing rather than one kind repeated. The Baccalaureate stays plain text.
   Three awards on one line is none. */
.is-degree .tl-c{
  background:var(--accent-soft);border:1px solid transparent;border-radius:14px;
  padding:20px 22px;
  transition:border-color .35s var(--ease),box-shadow .35s var(--ease);
}
.is-degree.on .tl-c{border-color:var(--accent);box-shadow:var(--shadow)}
.is-degree .kind{color:var(--accent)}
.is-degree h3{font-size:clamp(1.18rem,1.9vw,1.55rem)}

.seal{display:block;width:30px;height:30px;color:var(--accent);flex:none}
.seal svg{width:100%;height:100%;overflow:visible}
.seal svg *{transform-box:fill-box;transform-origin:50% 50%}
/* It pops once, as the entry becomes the one being read. A medal that sits
   still is a bullet point with a picture on it. */
.is-degree.on .seal svg{animation:seal .7s var(--ease)}
@keyframes seal{
  0%  {transform:scale(1) rotate(0)}
  35% {transform:scale(1.18) rotate(-7deg)}
  62% {transform:scale(.97) rotate(4deg)}
  100%{transform:scale(1) rotate(0)}
}
.is-degree:hover .seal .rib{animation:ribbon .6s var(--ease)}
@keyframes ribbon{
  0%,100%{transform:translateY(0)}
  45%    {transform:translateY(2.2px)}
}

.is-work{padding-bottom:34px}
.is-degree{padding-bottom:34px}
.is-degree .tl-y{padding-top:1.4em}
.is-degree::before{top:1.55em}
.is-work .tl-y{padding-top:1.6em}
.is-work::before{top:1.75em}

.tl-i .hint{
  display:block;margin-top:14px;color:var(--ink-3);opacity:.85;
  transition:opacity .25s var(--ease);
}
.tl-i:hover .hint,.tl-i:focus-within .hint{opacity:0}
.tl-i .more .lines li:first-child{padding-top:6px}
/* A device with no pointer can never satisfy the hover, so it gets the detail
   outright and the invitation to hover is a lie there. */

/*  03 cases 
   Numbered rows on hairlines, not three boxes in a row. A CV lists its work;
   it does not merchandise it. The write-up opens on hover, the same gesture the
   journey uses, so the page has one way of asking for more rather than two. */
.cases{border-top:1px solid var(--line)}
.case{
  position:relative;display:grid;grid-template-columns:64px minmax(0,1fr) auto;
  gap:22px;align-items:start;padding:26px 0;border-bottom:1px solid var(--line-2);
  transition:padding-left .4s var(--ease);
}
.case::before{
  content:"";position:absolute;left:0;right:0;top:-1px;height:1px;
  background:var(--accent);transform:scaleX(0);transform-origin:0 50%;
  transition:transform .5s var(--ease);
}
.case:hover,.case:focus-within{padding-left:14px}
.case:hover::before,.case:focus-within::before{transform:scaleX(1)}

.case .idx{
  font-size:.72rem;color:var(--ink-3);padding-top:.42em;
  transition:color .3s var(--ease);
}
.case:hover .idx,.case:focus-within .idx{color:var(--accent)}
.case h3{
  font-family:var(--display);font-weight:500;letter-spacing:-.026em;line-height:1.14;
  font-size:clamp(1.15rem,2vw,1.6rem);
}
.case .who{margin-top:8px;font-size:.9rem;color:var(--ink-3)}
.case .more p{margin:0;font-size:.95rem;line-height:1.6;max-width:62ch;padding-top:16px}
.case .hint{
  font-size:.66rem;color:var(--ink-3);padding-top:.6em;white-space:nowrap;
  transition:opacity .25s var(--ease);
}
.case:hover .hint,.case:focus-within .hint{opacity:0}

/*  04 skills 
   Two rows gliding in opposite directions, holding on hover. Each row carries
   its set twice and travels exactly half its own width, which is the only way
   the loop has no seam. app.js sets --dur per row from its measured width so
   both rows move at the same speed whatever language they are in. */
/*  skills
    A deck of four cards side by side, one per group, opened by the pointer. It
    was two rows of pills scrolling in opposite directions, and a moving list
    cannot be read: the eye has to track it instead of scanning it, and the
    grouping only survived as one highlighted chip somewhere in the stream.

    The first card is open at rest and hovering moves the open state rather than
    creating it, so the section always shows one whole group. A deck that opens
    from nothing says four category names and nothing else, which is ten seconds
    of a reader's time spent finding out there is something behind it. */
.skx{
  /* The reset only clears markers off ul, and this is an ol: the groups are
     ordered and their numerals are the largest thing on each card. */
  list-style:none;
  margin-top:clamp(28px,3.4vw,44px);
  display:flex;gap:clamp(10px,1.1vw,16px);
  /* Sized off the open card, which is the tallest thing the deck holds: a French
     name over six terms is 277px at 1920 and 265px at 1024, and the clamp leaves
     each of those about 26px of floor. It came down from clamp(348,26vw,384)
     once the numeral moved out of flow, which left the open card standing on
     100px of nothing.

     Fixed height rather than min-height on purpose: the four lists are different
     heights in French, and letting the tallest set the row makes the whole deck
     jump every time the pointer moves. **If the terms or the type change, check
     the open card in French at 1920 and at 921**: it carries overflow:hidden, so
     it drops its last line silently. */
  height:clamp(306px,22vw,330px);
}
/* The page's own card: --panel on a hairline at 14px, the same as the language
   cards and the work entries in the journey. A fifth surface would be a fifth
   idea. */
.skx-c{
  position:relative;min-width:0;flex:1 1 0;overflow:hidden;
  display:flex;flex-direction:column;
  padding:clamp(18px,1.8vw,26px) clamp(16px,1.7vw,26px);
  background:var(--panel);border:1px solid var(--line);border-radius:14px;
  /* flex-grow is a layout property and this is four boxes, which is cheap; the
     alternative is a transform, and a transform would squash the type inside. */
  transition:flex-grow .5s var(--ease),border-color .4s var(--ease),
             box-shadow .4s var(--ease),background .4s var(--ease);
  outline-offset:-3px;
}

/* The numeral is the closed card's field, not a label above its name: at 3.4rem
   in the corner it left two thirds of a 348px card empty, and no amount of
   spacing makes an empty card look deliberate. Outlined, the device the section
   heads already use, and out of flow so its size costs the open card nothing. */
.skx-n{
  position:absolute;left:clamp(16px,1.7vw,26px);
  bottom:clamp(40px,4.4vw,54px);
  font-family:var(--display);font-weight:700;
  font-size:clamp(5.4rem,7.6vw,8.6rem);line-height:.76;letter-spacing:-.05em;
  color:transparent;-webkit-text-stroke:1.5px var(--ink-3);
  transition:opacity .42s var(--ease),transform .5s var(--ease),
             -webkit-text-stroke-color .4s var(--ease);
}
@supports not (-webkit-text-stroke: 1px black){ .skx-n{color:var(--ink-3)} }

/* Display face at the journey entry's size. At 1.02rem in the sans it was a
   label on a 240px card and the card read as empty around it; the name is the
   card's subject and it is set as one. */
.skx-h{
  position:relative;
  font-family:var(--display);font-weight:500;
  font-size:clamp(1.14rem,1.45vw,1.34rem);letter-spacing:-.022em;line-height:1.18;
  color:var(--ink-2);transition:color .4s var(--ease);
}

/* The one affordance the deck has: nothing else says the cards open. Absolute,
   so it sits on the card's floor without adding to the height the open card has
   to fit its terms into, and it is the same hint the journey and the projects
   already use. */
.skx-c .hint{
  position:absolute;left:clamp(16px,1.7vw,26px);bottom:clamp(16px,1.6vw,22px);
  font-size:.66rem;color:var(--ink-3);white-space:nowrap;
  transition:opacity .3s var(--ease);
}
.skx-c:first-child .hint,.skx-c:hover .hint,.skx-c:focus-within .hint{opacity:0}
.skx:is(:hover,:focus-within) .skx-c:first-child:not(:hover):not(:focus-within) .hint{opacity:1}

/* 0fr to 1fr, so the terms take no height at all when closed rather than being
   merely transparent. The wrapper exists because that trick needs exactly one
   child: grid-template-rows:0fr sets the first row only, and six list items
   create five more implicit rows at auto, so the list stayed its full height and
   the names floated at six different heights across the deck. */
.skx-l{
  display:grid;grid-template-rows:0fr;
  opacity:0;transition:grid-template-rows .5s var(--ease),opacity .38s var(--ease);
}
/* Margin, not zero: a 0fr row zeroes the item's height but a margin sits outside
   its box and still counts, so the closed card kept a gap under its name. The
   space the open list needs lives on its first item instead. */
.skx-l > ul{overflow:hidden;min-height:0;margin:0}
.skx-c ul li{
  padding:8px 0;border-top:1px solid var(--line-2);
  color:var(--ink-2);font-size:.9rem;line-height:1.36;
}
.skx-c ul li:first-child{border-top:0;padding-top:14px}

/*  open  */
.skx-c:first-child,.skx-c:hover,.skx-c:focus-visible,.skx-c:focus-within{
  flex-grow:2.5;border-color:var(--accent);box-shadow:var(--shadow);
}
/* Open, it stands down: six terms over a 137px outlined glyph is two things
   competing for the same 500px. */
.skx-c:first-child .skx-n,.skx-c:hover .skx-n,.skx-c:focus-within .skx-n{
  opacity:0;transform:translateY(14px);
}
.skx-c:first-child .skx-h,.skx-c:hover .skx-h,.skx-c:focus-within .skx-h{color:var(--ink)}
.skx-c:first-child .skx-l,.skx-c:hover .skx-l,.skx-c:focus-within .skx-l{grid-template-rows:1fr;opacity:1}

/* and the first one gives the open state up the moment the pointer or the
   keyboard takes it. :focus-within on the deck as well as :hover, or tabbing to
   a card opens a second one and two cards share the width. */
.skx:is(:hover,:focus-within) .skx-c:first-child:not(:hover):not(:focus-within){
  flex-grow:1;border-color:var(--line);box-shadow:none;
}
.skx:is(:hover,:focus-within) .skx-c:first-child:not(:hover):not(:focus-within) .skx-n{
  opacity:1;transform:none;
}
.skx:is(:hover,:focus-within) .skx-c:first-child:not(:hover):not(:focus-within) .skx-h{color:var(--ink-2)}
.skx:is(:hover,:focus-within) .skx-c:first-child:not(:hover):not(:focus-within) .skx-l{
  grid-template-rows:0fr;opacity:0;
}

/* No pointer, no deck. Hover is the whole mechanism, so where it does not exist
   the four groups become four cards with everything showing. */
/* Every reset here has to out-specify the open state, which is written against
   .skx-c:first-child and wins on specificity whatever the media query says. The
   first card kept the accent border and lost its numeral entirely. */
@media (hover:none),(pointer:coarse),(max-width:920px){
  .skx{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
       gap:clamp(12px,1.6vw,18px);height:auto}
  .skx .skx-c,.skx .skx-c:first-child{
    flex:none;border-color:var(--line);box-shadow:none;padding:22px;
  }
  /* Every card is open here, so the numeral returns to the corner at label size
     rather than standing behind the terms. */
  .skx .skx-c .skx-n,.skx .skx-c:first-child .skx-n{
    position:static;font-size:2.6rem;line-height:.9;margin-bottom:10px;
    -webkit-text-stroke-color:var(--accent);opacity:1;transform:none;
  }
  .skx .skx-c .skx-h,.skx .skx-c:first-child .skx-h{color:var(--ink)}
  .skx .skx-c .skx-l,.skx .skx-c:first-child .skx-l{grid-template-rows:1fr;opacity:1}
  /* Nothing to hover, and every card is open. */
  .skx-c .hint{display:none}
}
@media (max-width:620px){ .skx{grid-template-columns:minmax(0,1fr)} }

/*  languages  */
.langs{margin-top:clamp(34px,4vw,52px)}
.langs .tag{display:block;color:var(--ink-3);margin-bottom:16px}
/* Five, not auto-fit: at this width auto-fit lands four and leaves Spanish on
   its own row. */
.lang-grid{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:14px}
@media (max-width:1080px){ .lang-grid{grid-template-columns:repeat(3,minmax(0,1fr))} }
@media (max-width:620px){
  .lang-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  /* Five into two leaves the fifth alone in a half-width cell, which reads as a
     grid that failed rather than a set that is odd-numbered. It takes the row. */
  .lang-grid > :last-child:nth-child(odd){grid-column:1 / -1}
}
.lang{
  display:flex;flex-direction:column;align-items:flex-start;gap:14px;
  background:var(--panel);border:1px solid var(--line);border-radius:14px;
  padding:22px 22px 24px;
  transition:transform .35s var(--ease),box-shadow .35s var(--ease),border-color .35s var(--ease);
}
.lang:hover{transform:translateY(-3px);box-shadow:var(--shadow);border-color:var(--ink-3)}
.lang .l{font-family:var(--display);font-size:1.14rem;font-weight:500;letter-spacing:-.02em}
.lang .lvl{color:var(--ink-3);font-size:.62rem;line-height:1.5;margin-top:-6px}

/* Inline SVG, not emoji. Windows renders a regional-indicator pair as the two
   letters, so an emoji flag is a GB or FR box on the machine most likely to
   open this. The hairline is inset rather than a border so it survives the
   rounded corner. */
.flag{
  display:block;width:46px;height:30.67px;border-radius:4px;overflow:hidden;
  box-shadow:0 0 0 1px rgba(18,18,17,.14) inset;
}
:root[data-theme="dark"] .flag{box-shadow:0 0 0 1px rgba(255,255,255,.18) inset}
.flag svg{display:block;width:100%;height:100%}

/*  no travel 
   Under reduced motion and on paper the row wraps and shows its set once; a
   second copy of every skill is noise once nothing is moving. */

/*  shared reveal 
   0fr to 1fr rather than max-height: it animates to the content's real height,
   whatever that turns out to be. The child needs overflow:hidden and min-height:0
   or the row collapses to nothing while the text still paints. */
.more{
  display:grid;grid-template-rows:0fr;opacity:0;
  transition:grid-template-rows .42s var(--ease),opacity .3s var(--ease);
}
.more > *{overflow:hidden;min-height:0}
/* On the first item, not on the list. min-height:0 lets the clipped child shrink
   to nothing but padding is not min-height, so 18px on the list itself is 18px
   the collapsed row can never give back. */
.more .lines li:first-child{padding-top:18px}
/* The hint holds its space when it fades, so the journey card only needs
   the difference, not the full gap again. */
.tl-i:hover .more,.tl-i:focus-within .more,
.case:hover .more,.case:focus-within .more{grid-template-rows:1fr;opacity:1}

/* No pointer, no hover, so the write-up has to be open from the start. This
   block has to sit AFTER the base .more rule above: it was written 230 lines
   higher, lost the cascade at equal specificity, and every project description
   and journey detail was invisible on a phone with no way to reach it. */
@media (hover:none),(pointer:coarse){
  .more{grid-template-rows:1fr;opacity:1}
  .more > *{overflow:visible}
  .tl-i .hint,.case .hint{display:none}
  .case:hover,.case:focus-within{padding-left:0}
  .case:hover::before,.case:focus-within::before{transform:scaleX(0)}
}

/*  05 contact
   The ask on the left, the ways to reach him on the right, and the name once
   across the foot. It was centred, which said "this is the end", but it put the
   four details in a strip under the buttons where they read as a footer the page
   had already finished with. */
.sec-end{position:relative;overflow:hidden;padding:clamp(76px,10vw,140px) 0 0}
.end-in{
  display:grid;grid-template-columns:minmax(0,1.04fr) minmax(0,.96fr);
  gap:clamp(34px,6vw,92px);align-items:start;
  padding-bottom:clamp(56px,7vw,92px);
}
.end-say{min-width:0}

/* A flex row drops the whitespace between its children, so SECTION and 05 ran
   together. The gap puts it back. */
.end-lab{color:var(--ink-3);display:flex;align-items:center;gap:.5em}
.end-lab .dot{margin:0 .3em}
.end-lab .num-in{color:var(--accent)}
.statement-c{max-width:18ch;margin-top:clamp(20px,2.6vw,30px)}

.mail{
  display:inline-block;margin-top:clamp(18px,2.4vw,28px);
  font-family:var(--display);font-weight:500;
  font-size:clamp(1.32rem,3vw,2.3rem);letter-spacing:-.04em;line-height:1.06;
  background-image:linear-gradient(var(--accent),var(--accent));
  background-size:0% 2px;background-position:0 100%;background-repeat:no-repeat;
  transition:background-size .5s var(--ease),color .3s;
  word-break:break-word;
}
.mail:hover{color:var(--accent);background-size:100% 2px}

.cta-row{display:flex;gap:12px;flex-wrap:wrap;margin-top:clamp(28px,3.4vw,40px)}

/* A column beside the ask, not a strip beneath it, so it takes the same shape
   the profile's facts already have: mark, then the pair. */
.reach{width:100%;display:grid;gap:0;border-top:1px solid var(--line)}
.reach li{
  display:grid;grid-template-columns:auto minmax(0,1fr);gap:18px;
  align-items:center;min-width:0;
  padding:16px 0;border-bottom:1px solid var(--line-2);
}
.reach .t{color:var(--ink-3)}
.rc-b{display:flex;flex-direction:column;gap:5px;min-width:0}
.rc-b > :last-child{font-size:.99rem}
.reach a{
  background-image:linear-gradient(var(--accent),var(--accent));
  background-size:0% 1px;background-position:50% 100%;background-repeat:no-repeat;
  transition:background-size .35s var(--ease),color .25s;
}
.reach a:hover{color:var(--accent);background-size:100% 1px}

/* The name, once, across the foot. Two stacked copies: an outline underneath and
   a solid one over it, clipped from the right and opened by the scroll, so the
   page ends on the name filling in rather than on a watermark behind the text.

   clip-path, not a background-position sweep on background-clip:text. The sweep
   needs the fill to be a gradient, which loses the accent as a flat colour, and
   it cannot be composited off the main thread. */
.signoff{
  position:relative;line-height:.82;
  padding:0 0 clamp(10px,1.6vw,22px);
  font-family:var(--display);font-weight:700;letter-spacing:-.045em;
  /* Sized to run the width of the page rather than of the shell: it is the
     only thing here that is not inside the column, and a name stopping short of
     both margins reads as a heading that failed to fit. */
  font-size:clamp(2.4rem,15.2vw,15rem);
  text-align:center;white-space:nowrap;user-select:none;
}
.signoff .so-l{color:transparent;-webkit-text-stroke:1.4px var(--line)}
/* Without text-stroke the outline copy is invisible and the fill has nothing to
   open over, so the whole thing collapses to one solid line. */
@supports not (-webkit-text-stroke: 1px black){
  .signoff .so-l{color:var(--line)}
  .signoff .so-f{display:none}
}
.signoff .so-f{
  position:absolute;left:0;right:0;top:0;color:var(--ink);
  clip-path:inset(0 0 0 0);
}
.js .signoff .so-f{clip-path:inset(0 100% 0 0)}
/* French is the same string here, so no measure to re-check: it is his name. */
html[lang="fr"] .signoff{letter-spacing:-.045em}

/* The icon disc. A hairline ring rather than a fill: four filled discs in a row
   would out-weigh the mail address above them, which is the actual action. */
.rc-i{
  display:grid;place-items:center;width:42px;height:42px;border-radius:50%;
  border:1px solid var(--line);color:var(--ink-2);margin-bottom:2px;
  transition:border-color .3s var(--ease),color .3s var(--ease);
}
.rc-i .ico{width:19px;height:19px}
/* Only the two rows that are links respond to a pointer. A location that
   lights up under the cursor promises something it cannot do. */
.reach li:has(a):hover .rc-i{border-color:var(--accent);color:var(--accent)}

/* footer */
.foot{border-top:1px solid var(--line);padding:26px 0 34px}
.foot-in{
  display:flex;align-items:center;justify-content:space-between;gap:18px;
  color:var(--ink-3);
}
.foot a:hover{color:var(--accent)}
.foot-c{white-space:nowrap}

.foot-soc{display:flex;gap:8px}
.soc{
  display:grid;place-items:center;width:38px;height:38px;border-radius:50%;
  border:1px solid var(--line);color:var(--ink-2);
  transition:border-color .3s var(--ease),color .3s var(--ease),transform .3s var(--ease);
}
.soc:hover,.soc:focus-visible{border-color:var(--accent);color:var(--accent);transform:translateY(-2px)}
.soc .ico{width:17px;height:17px}

/* Back to top. The label keeps its underline; the arrow is the moving part. */
.foot-top{display:inline-flex;align-items:center;gap:9px;white-space:nowrap}
.foot-top .ft-t{
  background-image:linear-gradient(currentColor,currentColor);
  background-size:0% 1px;background-position:0 100%;background-repeat:no-repeat;
  transition:background-size .35s var(--ease);
}
.foot-top:hover .ft-t,.foot-top:focus-visible .ft-t{background-size:100% 1px}
/* A fixed box the height of one arrow, holding two stacked. Sliding the pair up
   by exactly one arrow swaps them, and the box's own overflow is what hides the
   one leaving and the one waiting. */
.ft-a{display:block;width:15px;height:15px;overflow:hidden}
.ft-a .ico{
  width:15px;height:15px;display:block;
  transition:transform .42s var(--ease);
}
.foot-top:hover .ft-a .ico,.foot-top:focus-visible .ft-a .ico{transform:translateY(-100%)}

/*  responsive  */
@media (max-width:1180px){
  .figure{right:calc((100vw - var(--shell)) / 2 - 5vw)}
}
@media (max-width:1080px){
  .bar-nav{gap:20px}
  .bar-nav a{font-size:.82rem}
}
/* A 720px laptop cannot hold the hero at full padding: measured at 817px tall,
   which put the two buttons below the fold on the first screen. */
@media (min-width:761px) and (max-height:800px){
  .hero{padding:calc(var(--bar-h) + 2vh) 0 7vh}
  .name{font-size:clamp(3.4rem,10.4vw,8rem)}
  .lede{margin-top:16px}
  .hero-act{margin-top:24px}
}
@media (max-width:980px){
  /* The figure has run out of room beside the text, so it drops behind it and
     is held back with opacity rather than being cropped into a torso. Once it
     is a backdrop there is nothing left to light, and a cord hanging over it
     is a control for something that is no longer the subject. */
  .figure{right:-4vw;top:22%;opacity:.26}
  :root,:root[data-theme="dark"],:root[data-theme="light"]{--dim:1;--dimf:none}
  .cord,.beam,.torch{display:none}
  .lede{max-width:34ch}
}
@media (max-width:760px){
  /* A 112px gutter plus a 42px gap is 154px the phone does not have. The year
     stacks above its entry and the line moves to the very edge. */
  .tl-line{left:5px;top:6px}
  .tl-i{grid-template-columns:minmax(0,1fr);gap:0;padding-left:30px}
  .tl-y{text-align:left;padding-right:0;padding-top:0;margin-bottom:10px}
  .tl-i::before,.is-work::before{left:0;top:.28em}
  .is-work .tl-y{padding-top:0}
  .is-work .tl-c{padding:20px}
}
@media (max-width:920px){
  .bar-nav{display:none}
  /* The links have left the bar, so the button that reaches them arrives in the
     same breath. The CTA goes with them: it is the overlay's job now, and it is
     what made the bar four controls wide on a 320px screen. */
  .menu-btn{display:flex}
  .cta{display:none}
  .ovl-in{grid-template-columns:minmax(0,1fr);gap:clamp(2rem,6svh,3rem);align-items:start}
  .ovl-side{padding-top:clamp(1rem,3svh,1.6rem);border-top:1px solid rgba(128,128,128,.3);width:100%}
  /* Below this the two columns are 380px each and the method's chip has
     nowhere to sit beside its own sentence. */
  .profile-body{grid-template-columns:minmax(0,1fr);gap:40px}
  /* Stacked, sticky would pin the facts over the method they are meant to sit
     beside. There is no second column left for them to ride down. */
  .pb-col:first-child{position:static}
  /* The details go under the ask rather than beside it. A 1.04/0.96 split of a
     920px screen gives the four rows 430px, which the longest of them fills. */
  .end-in{grid-template-columns:minmax(0,1fr);gap:38px}
  .statement-c{max-width:22ch}
  .case{grid-template-columns:46px minmax(0,1fr) auto;gap:16px}
  .sec-grid{grid-template-columns:1fr;gap:26px}
  .sec-head{position:static;display:flex;align-items:baseline;gap:14px;flex-wrap:wrap}
  .sec-head .num{margin-bottom:0}
  .sec-head .rule{width:100%;margin-top:14px;transform:none}
  /* Stacked, the section's own rule lands directly on the list's top border and
     the section opens with two hairlines 30px apart. The head keeps its rule,
     the list gives its own up. */
  .cases{border-top:0}
}
@media (max-width:760px){
  /* One screen, and the proposition before the picture. Photo-led, the first
     screen of a CV was a picture with no name on it: measured at 375x560 the
     name was cut in half by the fold and both buttons sat under it.

     The text block is fixed and the portrait takes whatever is left, so the
     hero is exactly one screen on a tall phone and degrades by losing photo
     rather than by losing content on a short one. min-height, not height: if
     the text alone ever outgrows the screen the hero grows with it instead of
     clipping the buttons. .hero::before is absolute and stays out of the flow. */
  .hero{min-height:100svh;display:flex;flex-direction:column;align-items:stretch;
    padding:calc(var(--bar-h) + 3svh) 0 0}
  .hero-in{order:1;flex:none}
  /* A floor, or on a 667px screen the leftover was 148px and he rendered as a
     thumbnail at the foot of the page. Under this the hero spills past one
     screen by a few percent, which the fade at the base already reads as. */
  .figure{order:2;flex:1 1 0;min-height:clamp(150px,25svh,210px);overflow:hidden;
    position:relative;right:auto;top:auto;bottom:auto;opacity:1;
    align-items:flex-end;justify-content:center;
    margin-top:clamp(18px,4svh,34px);padding-inline:0}
  /* flex-basis 0, so the portrait contributes nothing to the hero's own height
     and simply takes what the text has not used. Sized against 46svh instead it
     added its full height to the block and the hero came out at 1.07 screens on
     a 390x844 and 1.24 on a 375x667: the figure could not shrink because there
     was no definite height for it to shrink against. */
  .figure img{height:100%;width:auto;max-width:100%;filter:none}
  /* The cutout runs off the bottom edge on a short screen. Cut, that is a
     photograph sliced by a section boundary; faded, it is the page taking over
     from it. Sits under .hero-in, which is z-index 2. */
  .hero::after{
    content:"";position:absolute;left:0;right:0;bottom:0;z-index:1;
    height:clamp(48px,11svh,110px);pointer-events:none;
    background:linear-gradient(to top,var(--paper) 12%,transparent);
  }
  .scroller{display:none}
}
/* The same trade the 761-to-800px laptop tier makes, on a phone. At 375x667 the
   text block alone was 519px of a 667px screen and everything left went to the
   portrait. This returns about 80px of it. */
@media (max-width:760px) and (max-height:740px){
  .hero{padding-top:calc(var(--bar-h) + 1.6svh)}
  .name{font-size:clamp(3rem,12.4vw,3.9rem);margin:12px 0 16px}
  .lede{margin-top:14px;font-size:1rem}
  .hero-act{margin-top:22px}
}
@media (max-width:620px){
  body{font-size:16px}
  .wide-head .rule{display:none}
  .lang{padding:18px}
  .case{grid-template-columns:38px minmax(0,1fr);gap:12px}
  .case .hint{display:none}
  .case .more p{padding-top:12px}
  :root{--shell:min(1280px,92vw)}
  .name .n2{-webkit-text-stroke-width:1px}
  .cta{padding:0 12px;font-size:.78rem}
  /* Three deep steps and a nowhere-near-them chip is not a row. The chip moves
     under the pair and sits with the text it belongs to. */
  .mt-i{grid-template-columns:auto minmax(0,1fr);gap:14px}
  .mt-out{grid-column:2;justify-self:start;margin-top:12px}
  /* The icon stays beside the pair, not above it. Stacked, one fact is three
     lines with a disc floating over them and the column reads as four orphans
     rather than four facts. */
  .sec-wide .facts li{grid-template-columns:auto minmax(0,1fr);gap:15px;padding:14px 0}
  .fx-i{width:36px;height:36px}
  .fx-i .ico{width:17px;height:17px}
  .foot-in{flex-wrap:wrap;row-gap:20px}
  .foot-soc{order:-1;flex-basis:100%;justify-content:center}
  .lede{max-width:none}
  .big{max-width:none}
}
/* Wrapped, the separator was left hanging at the end of the first line. Below
   this the line break is the separator and the dot has nothing to do. */
@media (max-width:430px){
  .kicker{gap:0 .9em}
  .kicker .dot{display:none}
}
@media (max-width:352px){
  .bar{gap:10px}
  .tool-sep,.tool .off{display:none}
}

/*  reduced motion  */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  :root,:root[data-theme="dark"],:root[data-theme="light"]{--dim:1;--dimf:none}
  .torch{display:none}
  .js .rv{opacity:1;transform:none}
  /* Nothing ever adds .on here, so the method has to be settled outright. */
  .js .mt-n,.js .mt-i.on .mt-n{color:var(--accent)}
  .js .mt-i::before{background:var(--accent);border-color:var(--accent)}
  .js .mt-b h3{color:var(--ink)}
  .js .mt-b p{color:var(--ink-2)}
  .js .mt-out{opacity:1;transform:none;color:var(--ink-2)}
  .js .mt-spine i{transform:scaleY(1)}
  .js .signoff .so-f{clip-path:inset(0 0 0 0)}
  *{animation:none !important;transition-duration:.01ms !important}
}

/*  print 
   A clean black-on-white CV out of Ctrl+P. The journey unrolls: GSAP leaves a
   pin-spacer and an inline transform behind, and CSS !important is what beats
   an inline style that carries none. */
@media print{
  /* The dark-theme rule is :root[data-theme="dark"], which out-specifies a bare
     :root whatever the media query. Print has to match that specificity or a
     reader in dark mode prints near-white ink onto white paper. */
  :root,:root[data-theme="dark"],:root[data-theme="light"]{
    --paper:#fff;--panel:#fff;--ink:#000;--ink-2:#2E2E2E;--ink-3:#6B6B6B;
    --line:#C9C9C9;--line-2:#E4E4E4;--accent:#000;--shadow:none;--glow:transparent;
    --dim:1;--dimf:none;
    color-scheme:light;
  }
  @page{margin:12mm}
  body{font-size:9.6pt;line-height:1.38;background:#fff;color:#000}
  .cord,.beam,.torch,
  .progress,.bar,.foot a,.hero-act,.scroller,.live,.tl-cta,
  .tl-i .hint,.case .hint,.skx-c .hint,.sec-head .lab{display:none}
  .case,.lang{break-inside:avoid}
  .case{grid-template-columns:44px 1fr;gap:12pt;padding:8pt 0}
  .case .more p{padding-top:4pt;font-size:9.4pt}
  /* Paper has no pointer, so the deck is four columns with everything open, and
     the card surfaces flatten to a rule each. display and height have to be
     reset explicitly: the screen rule is a flex row of a fixed height and print
     inherits it otherwise. */
  .skx{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10pt;
       margin-top:6pt;height:auto}
  /* Same specificity problem as the stacked layout: the open state is written
     against :first-child and beats a bare .skx-c here too. */
  .skx .skx-c,.skx .skx-c:first-child{
    display:block;padding:5pt 0 0;background:none;border:0;border-radius:0;
    border-top:1px solid #C9C9C9;box-shadow:none;
  }
  .skx .skx-c .skx-n,.skx .skx-c:first-child .skx-n{
    position:static;font-size:13pt;-webkit-text-stroke:0;color:#000;
    line-height:1;opacity:1;transform:none;margin:0;
  }
  .skx .skx-c .skx-h,.skx .skx-c:first-child .skx-h{font-size:9pt;margin:2pt 0;color:#000}
  .skx .skx-c .skx-l,.skx .skx-c:first-child .skx-l{grid-template-rows:1fr;opacity:1}
  .skx-l > ul{overflow:visible;margin:0}
  .skx-c ul li{padding:3pt 0;font-size:8.2pt;line-height:1.3}
  .lang-grid{grid-template-columns:repeat(5,minmax(0,1fr));gap:6pt}
  .lang{border:1px solid #DDD;padding:7pt;gap:6pt;box-shadow:none}
  .lang .l{font-size:10pt}
  .lang .lvl{font-size:6.6pt}
  .sec-head .num{font-size:16pt;-webkit-text-stroke:0;color:#000;margin-bottom:2pt}
  .cta-row{display:none}
  /* Paper cannot hover. The detail is the substance of a work card and has
     to be on the page. */
  .more{grid-template-rows:1fr !important;opacity:1 !important}
  .more > *{overflow:visible}
  .js .rv{opacity:1 !important;transform:none !important}
  .hero::before{display:none}

  .hero{min-height:0;padding:0 0 10pt;display:block;overflow:visible;position:relative}
  .hero-in{width:72%}
  .name{font-size:29pt;margin:5pt 0 8pt;letter-spacing:-.03em}
  .name .line{display:inline-block;overflow:visible;padding:0}
  .name .line:first-child{margin-right:.2em}
  .name .n2{color:#000;-webkit-text-stroke:0}
  .name .line > span{transform:none !important;opacity:1 !important}
  .role{color:#000;margin-bottom:6pt}
  /* The seal is positioned off a measurement of the on-screen cutout, which the
     print layout moves. Education is a fact row in section 01 and prints there. */
  .lede{font-size:10.5pt;max-width:none;margin-top:0}
  .figure{position:absolute;right:0;top:0;bottom:auto;opacity:1;z-index:0;display:block}
  .figure img{height:150pt}
  .torch{display:none}

  .sec,.journey{padding:10pt 0;border-top:1px solid #C9C9C9;break-inside:avoid}
  /* The inverted band is a screen device; on paper it is a black slab. */
  #profile,:root[data-theme="dark"] #profile{
    --paper:#fff;--panel:#fff;--ink:#000;--ink-2:#2E2E2E;--ink-3:#6B6B6B;
    --line:#C9C9C9;--line-2:#E4E4E4;--accent:#000;--shadow:none;
    background:#fff;color:#000;border-top-color:#C9C9C9;
  }
  .sec-grid{grid-template-columns:104px 1fr;gap:14pt}
  /* A4 is 794px wide, inside the 920px breakpoint, so the section head is
     still the stacked mobile flex row here and the title runs over the body. */
  .sec-head{position:static;display:block}
  .sec-head .num{margin-bottom:3pt}
  .sec-head h2{font-size:11.5pt;line-height:1.14}
  .sec-head .rule,.wide-head .rule,.signoff{display:none}
  .wide-head{gap:10pt;margin-bottom:6pt}
  .wide-head .num{font-size:16pt;-webkit-text-stroke:0;color:#000}
  .wide-head h2{font-size:11.5pt}
  .sec-wide .shell > * + *{margin-top:8pt}
  .statement{font-size:11.5pt;max-width:none;line-height:1.3}
  .statement .w{opacity:1 !important}
  .profile-body{grid-template-columns:.9fr 1.1fr;gap:16pt}
  /* Paper drops every icon disc: a ring per row is screen furniture, and on a
     CV the four labels want to be a column the eye can run down. The pair goes
     back to label beside value, which is what the row was before the marks. */
  .fx-i,.rc-i{display:none}
  .sec-wide .facts li{padding:4pt 0;grid-template-columns:minmax(0,1fr);gap:0}
  .fx-b{display:grid;grid-template-columns:76px minmax(0,1fr);gap:10pt;align-items:baseline}
  .sec-end{padding:10pt 0 0;text-align:left;overflow:visible}
  /* The spine and its dots are a scroll device; on paper the four steps are
     four short entries and the numerals already order them. */
  .mt-spine{display:none}
  .mt-i::before{display:none}
  .method{padding-left:0}
  .mt-i{padding:5pt 0;grid-template-columns:auto minmax(0,1fr) auto;gap:10pt}
  .mt-b h3{font-size:10.5pt}
  .mt-b p{font-size:9.4pt;margin-top:2pt;max-width:none}
  .mt-out{padding:2pt 6pt;font-size:7pt}
  .mt-lab{margin-bottom:6pt}
  .end-in{grid-template-columns:1fr 1fr;gap:14pt;padding-bottom:0}
  .rc-i{display:none}
  .reach li{padding:4pt 0;grid-template-columns:minmax(0,1fr)}
  /* Wider than the facts' 76px: LOCALISATION is the longest label on the page
     and at 76 it ran into its own value. */
  .rc-b{display:grid;grid-template-columns:92px minmax(0,1fr);gap:10pt;align-items:baseline}
  .mail{background-position:0 100%}
  .sec-body > * + *{margin-top:8pt}
  .big{font-size:11pt}

  /* The timeline is already vertical; print only has to keep entries whole and
     open the details, since paper cannot hover. */
  .tl-line{background:#C9C9C9}
  .tl-line i{display:none}
  /* The line and the node keep the screen's 112px offset unless they are told
     otherwise, and at print widths that lands the node on top of the text. */
  .tl-line{left:88px}
  .tl-i{grid-template-columns:70px minmax(0,1fr);gap:26pt;padding-bottom:10pt;break-inside:avoid}
  .tl-y{padding-right:0;font-size:8.6pt}
  .tl-i::before,.is-work::before{left:83px;background:#fff;border-color:#999;transform:none !important}
  .is-work .tl-c{border:1px solid #DDD;padding:9pt;box-shadow:none}
  .is-degree .tl-c{background:none;border:0;border-left:2pt solid #000;padding:0 0 0 8pt}
  .seal{width:20px;height:20px;color:#000}
  .tl-i h3{font-size:11pt}
  .tl-cta,.say{}
  .lines{gap:5pt}
  .lines li{font-size:9.4pt;line-height:1.36}
  .mark{height:22px}
  .mark .dk{display:none !important}
  .mark .lt{display:block !important}
  .langs{margin-top:14pt;padding-top:12pt}
  .flag{width:30px;height:20px;box-shadow:0 0 0 1px rgba(0,0,0,.2) inset}
  .reach{padding-top:0}
  .cta-row{display:none}
  .langs li{break-inside:avoid}
  .mail{font-size:14pt;background-image:none}
  .foot{padding:10pt 0 0}
  
  a{color:#000;border-bottom:0 !important}
}
