:root{
  /* Brand palette: Navy, Snow White and Signal Orange carry the page; the
     grays are the quiet text, Glacier Blue and Gold the secondary accents.
     The variable names predate the palette — --green is the primary accent. */
  --void:#0e1b2b;
  --panel:#13233a;
  --panel-2:#182b44;
  --hairline:rgba(255,255,255,.09);
  --hairline-strong:rgba(255,255,255,.16);
  --text-hi:#f4f6f7;
  --text-lo:#aebbc7;
  /* #3d4b5c is too dark to read on navy; faint text sits between the grays */
  --text-faint:#7d8b9b;
  --green:#ff5a1f;
  --teal:#1fa8c9;
  --violet:#e0a91b;
  --pink:#ff5a1f;
  --max-w:920px;
}

*{box-sizing:border-box;}
html{scroll-behavior:smooth;}
/* Smooth scrolling is motion too — hash links and scrollIntoView included */
@media (prefers-reduced-motion: reduce){
  html{scroll-behavior:auto;}
}
body{
  margin:0;
  background:var(--void);
  color:var(--text-hi);
  font-family:'Inter',system-ui,sans-serif;
  font-size:16px;
  line-height:1.55;
  -webkit-font-smoothing:antialiased;
}

::selection{background:var(--green);color:#0e1b2b;}

a{color:inherit;}

/* ---------- Film grain ----------
   Heavily blurred gradients on a near-black ground band into visible steps on
   8-bit displays. A fixed noise tile dithers those steps away. Decorative and
   inert, so it sits above everything and takes no pointer events. */
.grain{
  position:fixed;inset:0;z-index:100;pointer-events:none;
  opacity:.032;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}
@media print{ .grain{display:none !important;} }

/* ---------- Starfield ----------
   A page-wide night sky behind everything. Only `transform` animates, so each
   layer is painted into a GPU texture once and thereafter merely moved by the
   compositor — the star count costs nothing per frame. Each layer is one tile
   taller than the viewport and travels exactly one tile height, so the loop
   closes seamlessly. Three tile sizes at three speeds give parallax depth. */
.starfield{
  position:fixed;inset:0;z-index:-1;overflow:hidden;pointer-events:none;
}
.stars{
  position:absolute;top:0;left:0;right:0;
  background-repeat:repeat;
  will-change:transform;
  opacity:calc(var(--tod-star-op,1) * var(--sky-clear,1));
}
/* Rain, snow and fog hide the sky whatever the cover figure says */
body.wx-rain .stars, body.wx-snow .stars, body.wx-fog .stars{opacity:0;}
.stars-far{
  height:calc(100% + 400px);
  background-image:
    radial-gradient(circle 1.32px at 343.3px 39.0px,rgba(255,255,255,0.43) 0%,rgba(255,255,255,0.43) 42%,rgba(255,255,255,0) 100%),
    radial-gradient(circle 1.22px at 256.4px 161.2px,rgba(186,215,255,0.48) 0%,rgba(186,215,255,0.48) 42%,rgba(186,215,255,0) 100%),
    radial-gradient(circle 1.3px at 225.6px 302.9px,rgba(255,255,255,0.48) 0%,rgba(255,255,255,0.48) 42%,rgba(255,255,255,0) 100%),
    radial-gradient(circle 1.42px at 54.3px 198.9px,rgba(255,255,255,0.6) 0%,rgba(255,255,255,0.6) 42%,rgba(255,255,255,0) 100%),
    radial-gradient(circle 1.17px at 371.4px 367.0px,rgba(200,240,248,0.55) 0%,rgba(200,240,248,0.55) 42%,rgba(200,240,248,0) 100%),
    radial-gradient(circle 1.44px at 281.7px 69.7px,rgba(200,240,248,0.48) 0%,rgba(200,240,248,0.48) 42%,rgba(200,240,248,0) 100%),
    radial-gradient(circle 1.14px at 147.3px 216.0px,rgba(214,204,255,0.49) 0%,rgba(214,204,255,0.49) 42%,rgba(214,204,255,0) 100%),
    radial-gradient(circle 1.41px at 157.1px 47.2px,rgba(255,255,255,0.62) 0%,rgba(255,255,255,0.62) 42%,rgba(255,255,255,0) 100%),
    radial-gradient(circle 1.24px at 52.5px 78.2px,rgba(255,255,255,0.6) 0%,rgba(255,255,255,0.6) 42%,rgba(255,255,255,0) 100%),
    radial-gradient(circle 1.39px at 133.5px 351.2px,rgba(186,215,255,0.57) 0%,rgba(186,215,255,0.57) 42%,rgba(186,215,255,0) 100%),
    radial-gradient(circle 1.11px at 51.9px 346.0px,rgba(200,240,248,0.59) 0%,rgba(200,240,248,0.59) 42%,rgba(200,240,248,0) 100%),
    radial-gradient(circle 1.5px at 324.5px 238.6px,rgba(255,255,255,0.53) 0%,rgba(255,255,255,0.53) 42%,rgba(255,255,255,0) 100%);
  background-size:400px 400px;
  animation:star-drift-far 300s linear infinite;
}
@keyframes star-drift-far{from{transform:translate3d(0,0,0);}to{transform:translate3d(0,-400px,0);}}
.stars-mid{
  height:calc(100% + 600px);
  background-image:
    radial-gradient(circle 1.58px at 387.5px 337.4px,rgba(214,204,255,0.75) 0%,rgba(214,204,255,0.75) 42%,rgba(214,204,255,0) 100%),
    radial-gradient(circle 1.66px at 234.9px 346.5px,rgba(255,255,255,0.64) 0%,rgba(255,255,255,0.64) 42%,rgba(255,255,255,0) 100%),
    radial-gradient(circle 1.45px at 417.7px 450.2px,rgba(255,255,255,0.73) 0%,rgba(255,255,255,0.73) 42%,rgba(255,255,255,0) 100%),
    radial-gradient(circle 1.6px at 493.6px 517.0px,rgba(255,255,255,0.68) 0%,rgba(255,255,255,0.68) 42%,rgba(255,255,255,0) 100%),
    radial-gradient(circle 1.74px at 107.7px 320.0px,rgba(255,255,255,0.77) 0%,rgba(255,255,255,0.77) 42%,rgba(255,255,255,0) 100%),
    radial-gradient(circle 1.87px at 182.5px 65.9px,rgba(186,215,255,0.61) 0%,rgba(186,215,255,0.61) 42%,rgba(186,215,255,0) 100%),
    radial-gradient(circle 1.45px at 220.2px 520.4px,rgba(255,255,255,0.68) 0%,rgba(255,255,255,0.68) 42%,rgba(255,255,255,0) 100%),
    radial-gradient(circle 1.74px at 566.7px 334.8px,rgba(255,255,255,0.6) 0%,rgba(255,255,255,0.6) 42%,rgba(255,255,255,0) 100%),
    radial-gradient(circle 1.45px at 490.5px 117.8px,rgba(255,255,255,0.66) 0%,rgba(255,255,255,0.66) 42%,rgba(255,255,255,0) 100%),
    radial-gradient(circle 1.86px at 94.6px 530.4px,rgba(255,255,255,0.58) 0%,rgba(255,255,255,0.58) 42%,rgba(255,255,255,0) 100%);
  background-size:600px 600px;
  animation:star-drift-mid 260s linear infinite;
}
@keyframes star-drift-mid{from{transform:translate3d(0,0,0);}to{transform:translate3d(0,-600px,0);}}
.stars-near{
  height:calc(100% + 900px);
  background-image:
    radial-gradient(circle 2.46px at 749.4px 225.7px,rgba(255,255,255,0.87) 0%,rgba(255,255,255,0.87) 42%,rgba(255,255,255,0) 100%),
    radial-gradient(circle 2.46px at 392.6px 447.6px,rgba(255,255,255,0.9) 0%,rgba(255,255,255,0.9) 42%,rgba(255,255,255,0) 100%),
    radial-gradient(circle 2.54px at 469.6px 152.5px,rgba(255,255,255,0.96) 0%,rgba(255,255,255,0.96) 42%,rgba(255,255,255,0) 100%),
    radial-gradient(circle 2.09px at 456.5px 795.7px,rgba(255,255,255,0.83) 0%,rgba(255,255,255,0.83) 42%,rgba(255,255,255,0) 100%),
    radial-gradient(circle 2.44px at 60.1px 90.4px,rgba(255,255,255,0.79) 0%,rgba(255,255,255,0.79) 42%,rgba(255,255,255,0) 100%),
    radial-gradient(circle 2.01px at 103.8px 840.3px,rgba(255,255,255,0.84) 0%,rgba(255,255,255,0.84) 42%,rgba(255,255,255,0) 100%),
    radial-gradient(circle 2.5px at 816.4px 381.4px,rgba(255,255,255,0.95) 0%,rgba(255,255,255,0.95) 42%,rgba(255,255,255,0) 100%);
  background-size:900px 900px;
  animation:star-drift-near 200s linear infinite;
}
@keyframes star-drift-near{from{transform:translate3d(0,0,0);}to{transform:translate3d(0,-900px,0);}}
/* A faint wash tying the time-of-day mood to the whole sky, not just the
   hero's aurora. Screen blend only ever brightens, so it reads as ambient
   light sitting behind the stars rather than a tint sitting on top of them. */
.starfield-glow{
  position:absolute;inset:0;
  background:radial-gradient(ellipse 70% 55% at 50% -8%, var(--tod-glow), transparent 70%);
  mix-blend-mode:screen;
}
/* A drifting sky is motion; hold it still but keep the stars. */
@media (prefers-reduced-motion: reduce){
  .stars{animation:none;}
}
@media print{ .starfield{display:none !important;} }

h1,h2,h3{
  font-family:'Fraunces',serif;
  font-weight:500;
  letter-spacing:-0.01em;
  margin:0;
  text-wrap:balance;
}
/* `pretty` trades a little speed for no stranded last words. Both text-wrap
   values are progressive enhancement — unsupported browsers ignore them. */
.kicker-summary,.project-desc,.job li,.edu-item li,.ach-list li{text-wrap:pretty;}

.wrap{max-width:var(--max-w);margin:0 auto;padding:0 28px;}

/* ---------- Language switching ----------
   `revert` restores each element's own default display, so a toggled <p>/<ul>/<div>
   stays block-level and keeps its vertical margins. A flat `inline` here silently
   collapsed the spacing on every translated paragraph and list. */
.sv-only{display:revert;}
.en-only{display:none;}
body.lang-en .sv-only{display:none;}
body.lang-en .en-only{display:revert;}

/* ---------- Top control bar ---------- */
.controlbar{
  position:sticky;top:0;z-index:50;
  background:rgba(14,27,43,.72);
  backdrop-filter:blur(14px) saturate(140%);
  border-bottom:1px solid var(--hairline);
}
.controlbar-inner{
  max-width:var(--max-w);margin:0 auto;padding:14px 28px;
  display:flex;align-items:center;justify-content:space-between;gap:16px;
}
.brand-mini{
  font-family:'JetBrains Mono',monospace;
  font-size:12px;letter-spacing:.06em;color:var(--text-lo);
  text-transform:uppercase;
  display:flex;align-items:baseline;gap:8px;min-width:0;
}
.brand-mark{color:var(--text-hi);flex:none;}
.brand-sep{color:var(--text-faint);flex:none;}
/* Reads out whichever section is in view. Fixed-width tabular digits keep the
   label from shifting sideways every time the number changes. */
.brand-readout{
  color:var(--text-lo);font-variant-numeric:tabular-nums;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  transition:opacity .18s ease;
}
.brand-readout.is-swapping{opacity:0;}
@media (max-width:600px){
  .brand-sep, .brand-readout{display:none;}
}

.controls{display:flex;align-items:center;gap:10px;}

.lang-toggle{
  display:flex;border:1px solid var(--hairline-strong);
  border-radius:999px;overflow:hidden;background:var(--panel);
}
.lang-toggle button{
  appearance:none;border:none;background:transparent;color:var(--text-lo);
  font-family:'JetBrains Mono',monospace;font-size:12px;letter-spacing:.05em;
  padding:7px 14px;cursor:pointer;transition:all .2s ease;
}
.lang-toggle button.active{
  background:linear-gradient(135deg,var(--green),var(--teal));
  color:#0e1b2b;font-weight:500;
}
.print-btn{
  display:flex;align-items:center;gap:7px;
  border:1px solid var(--hairline-strong);border-radius:999px;
  background:var(--panel);color:var(--text-hi);
  font-family:'JetBrains Mono',monospace;font-size:12px;letter-spacing:.03em;
  padding:8px 16px;cursor:pointer;transition:border-color .2s ease, transform .15s ease;
}
.print-btn{text-decoration:none;}
.print-btn:hover{border-color:var(--teal);transform:translateY(-1px);}
.print-btn svg{width:14px;height:14px;stroke:var(--teal);}

/* ---------- Round icon buttons in the bar ---------- */
.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;}
.icon-btn{
  display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;
  appearance:none;border:1px solid var(--hairline-strong);border-radius:999px;
  background:var(--panel);color:var(--text-lo);cursor:pointer;
  transition:border-color .2s ease,color .2s ease;
}
.icon-btn:hover{border-color:var(--teal);color:var(--text-hi);}
.icon-btn svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
/* On a phone the bar is full, so the download button keeps its icon plus
   a three-letter label. */
.print-btn{white-space:nowrap;}
.print-btn-short{display:none;}
@media (max-width:600px){
  .controls{gap:8px;}
  .print-btn > .sv-only, .print-btn > .en-only{display:none !important;}
  .print-btn-short{display:inline;}
}

/* Scroll progress rail along the bottom edge of the control bar */
.scroll-progress{
  position:absolute;left:0;bottom:-1px;height:2px;width:0;
  background:linear-gradient(90deg,var(--green),var(--teal),var(--violet));
  box-shadow:0 0 10px rgba(255,90,31,.45);
  transition:width .12s linear;
}

/* ---------- Scroll reveal (class applied by JS, so no-JS keeps content visible) ---------- */
.reveal{
  opacity:0;transform:translateY(16px);
  transition:opacity .65s cubic-bezier(.2,.7,.3,1),transform .65s cubic-bezier(.2,.7,.3,1);
}
.reveal.is-in{opacity:1;transform:none;}
@media (prefers-reduced-motion: reduce){
  .reveal{opacity:1;transform:none;transition:none;}
}

/* ---------- Hero / Aurora ---------- */
.hero{
  position:relative;overflow:hidden;
  padding:88px 0 64px;
  border-bottom:1px solid var(--hairline);
}
/* ---------- Time-of-day mood ----------
   The aurora, and a faint ambient wash on the starfield further down, shift
   with the visitor's local clock — the sky looks different depending on
   when the page is opened. Day stays close to the original palette, since
   that is when almost everyone actually sees it; night, dawn and dusk each
   lean the hue cooler or warmer.
   Registered as `<color>`, the same trick as the portrait ring's rotation
   below: an unregistered custom property has no interpolable type, so a
   class swap could only ever flip it instantly rather than ease across.
   Initial values match the original, unscripted look, so a visitor with JS
   disabled sees exactly what shipped before this feature. */
@property --tod-1{syntax:'<color>';inherits:true;initial-value:#1fa8c9;}
@property --tod-2{syntax:'<color>';inherits:true;initial-value:#ff5a1f;}
@property --tod-3{syntax:'<color>';inherits:true;initial-value:#e0a91b;}
@property --tod-glow{syntax:'<color>';inherits:true;initial-value:transparent;}
/* How much the stars show per phase: the full field at night, only the
   brightest through civil twilight, none at all while the sun is up. */
@property --tod-star-op{syntax:'<number>';inherits:true;initial-value:1;}
/* How much of the aurora glow the phase allows: none against a lit sky */
@property --tod-aurora{syntax:'<number>';inherits:true;initial-value:1;}
/* Åre's cloud cover right now, 0–1; dims stars, aurora, sun and moon */
@property --wx-cloud{syntax:'<number>';inherits:true;initial-value:0;}
/* Stars need a properly clear sky: full below 20 % cover, gone from 60 % */
body{--sky-clear:clamp(0, calc((.6 - var(--wx-cloud,0)) / .4), 1);}
/* The hero's sky colour: zenith and horizon for a clear sky (-z, -h) and
   for a closed one (-oz, -oh). The cover mixes between them. Initial
   values are the night, so a visitor without JS sees the old dark sky. */
@property --sky-z{syntax:'<color>';inherits:true;initial-value:rgba(7,12,25,0);}
@property --sky-h{syntax:'<color>';inherits:true;initial-value:rgba(16,30,60,.45);}
@property --sky-oz{syntax:'<color>';inherits:true;initial-value:#0d1119;}
@property --sky-oh{syntax:'<color>';inherits:true;initial-value:#151a24;}
body{transition:--tod-1 2.6s ease,--tod-2 2.6s ease,--tod-3 2.6s ease,
                --tod-glow 2.6s ease,--tod-star-op 2.6s ease,--tod-aurora 2.6s ease,--wx-cloud 2.6s ease,
                --sky-z 2.6s ease,--sky-h 2.6s ease,--sky-oz 2.6s ease,--sky-oh 2.6s ease;}
@media (prefers-reduced-motion: reduce){ body{transition:none;} }

/* Stars only once it is dark: twilight, and so every summer night in Åre,
   keeps them hidden. */
body.tod-day  {--tod-1:#1fa8c9;--tod-2:#e0a91b;--tod-3:#1fa8c9;--tod-glow:rgba(255,196,120,.1);--tod-star-op:0;--tod-aurora:0;
               --sky-z:#1f5c9e;--sky-h:#4f8fcf;--sky-oz:#4d5868;--sky-oh:#6c7788;}
body.tod-dusk {--tod-1:#ff5a1f;--tod-2:#e0a91b;--tod-3:#1fa8c9;--tod-glow:rgba(255,110,140,.11);--tod-star-op:0;--tod-aurora:.3;
               --sky-z:#1c2250;--sky-h:#7a4262;--sky-oz:#262634;--sky-oh:#4a3c48;}
body.tod-night{--tod-1:#1fa8c9;--tod-2:#ff5a1f;--tod-3:#e0a91b;--tod-glow:rgba(91,108,255,.08);--tod-star-op:1;--tod-aurora:1;
               --sky-z:rgba(7,12,25,0);--sky-h:rgba(16,30,60,.45);--sky-oz:#0d1119;--sky-oh:#151a24;}
body.tod-dawn {--tod-1:#e0a91b;--tod-2:#ff5a1f;--tod-3:#1fa8c9;--tod-glow:rgba(255,150,110,.12);--tod-star-op:0;--tod-aurora:.3;
               --sky-z:#22305e;--sky-h:#a0605a;--sky-oz:#2a2c3a;--sky-oh:#564a4e;}
/* Rain darkens a closed sky, snow and fog pale it */
body.wx-rain.tod-day{--sky-oz:#3a4452;--sky-oh:#525d6c;}
body.wx-snow.tod-day, body.wx-fog.tod-day{--sky-oz:#5f6a7b;--sky-oh:#8590a0;}
body.wx-rain:is(.tod-dusk,.tod-dawn){--sky-oz:#1e2029;--sky-oh:#34313a;}
body.wx-snow:is(.tod-dusk,.tod-dawn), body.wx-fog:is(.tod-dusk,.tod-dawn){--sky-oz:#34363f;--sky-oh:#5a5760;}

/* ---------- The hero's sky ----------
   Åre's sky right now: the phase picks the colours, the cloud cover mixes a
   clear sky toward a grey one, and the whole thing fades out at the bottom
   of the hero so the CV below reads as before. A clear night is nearly
   see-through: the page's own starfield is the sky then. */
.hero-sky{
  position:absolute;inset:0;z-index:0;pointer-events:none;
  background:linear-gradient(180deg,var(--sky-z) 0%,var(--sky-h) 48%,rgba(14,27,43,0) 96%);
  background:linear-gradient(180deg,
    color-mix(in oklab,var(--sky-z),var(--sky-oz) calc(var(--wx-cloud,0) * 100%)) 0%,
    color-mix(in oklab,var(--sky-h),var(--sky-oh) calc(var(--wx-cloud,0) * 100%)) 48%,
    rgba(14,27,43,0) 96%);
}
@media print{ .hero-sky{display:none !important;} }
/* A lit sky is far brighter than the void the hero's text was set for:
   lift the quiet greys and give the words a soft shadow to stand on. */
/* Screen only: on paper the sky is gone, and a shadow would print as a
   second copy of every word in the PDF's text layer. */
@media screen{
  body:is(.tod-day,.tod-dusk,.tod-dawn) .hero-content{--text-lo:#d5dfeb;--text-faint:#b3c1d3;text-shadow:0 1px 14px rgba(4,10,24,.4);}
  body:is(.tod-day,.tod-dusk,.tod-dawn) .hero .eyebrow{color:#c4f0f7;}
}
/* The last and first hour of sun: the horizon warms toward gold */
body.tod-day.sun-low{--sky-z:#29568c;--sky-h:#c58a62;--sky-oh:#7a7272;}

.aurora-layer{
  position:absolute;inset:-20% -10% -10% -10%;
  z-index:0;pointer-events:none;
  filter:blur(60px) saturate(130%);
  --aurora-op:.55;
  opacity:calc(var(--aurora-op) * var(--tod-aurora,1) * (1 - var(--wx-cloud,0)));
}
.aurora-band{
  position:absolute;
  width:140%;height:60%;
  border-radius:50%;
  mix-blend-mode:screen;
  animation:drift 22s ease-in-out infinite;
  will-change:transform;
}
.aurora-band.b1{
  background:radial-gradient(ellipse at 30% 40%, var(--tod-1), transparent 60%);
  top:-10%;left:-20%;
  animation-name:drift-1;
  animation-duration:28s;
}
.aurora-band.b2{
  background:radial-gradient(ellipse at 60% 50%, var(--tod-2), transparent 60%);
  top:0%;left:10%;
  animation-name:drift-2;
  animation-duration:34s;
  animation-delay:-11s;
}
.aurora-band.b3{
  background:radial-gradient(ellipse at 50% 50%, var(--tod-3), transparent 60%);
  top:10%;left:-10%;
  animation-name:drift-3;
  animation-duration:23s;
  animation-delay:-6s;
}
@keyframes drift-1{
  0%{transform:translate(-6%,-4%) rotate(-8deg) scale(1);}
  25%{transform:translate(10%,-14%) rotate(10deg) scale(1.18);}
  50%{transform:translate(18%,6%) rotate(4deg) scale(0.92);}
  75%{transform:translate(-4%,16%) rotate(-10deg) scale(1.1);}
  100%{transform:translate(-6%,-4%) rotate(-8deg) scale(1);}
}
@keyframes drift-2{
  0%{transform:translate(8%,6%) rotate(6deg) scale(1.05);}
  25%{transform:translate(-14%,-8%) rotate(-12deg) scale(0.9);}
  50%{transform:translate(-6%,14%) rotate(2deg) scale(1.15);}
  75%{transform:translate(12%,-12%) rotate(12deg) scale(0.95);}
  100%{transform:translate(8%,6%) rotate(6deg) scale(1.05);}
}
@keyframes drift-3{
  0%{transform:translate(4%,-10%) rotate(4deg) scale(0.95);}
  25%{transform:translate(-12%,4%) rotate(-14deg) scale(1.12);}
  50%{transform:translate(6%,14%) rotate(8deg) scale(1);}
  75%{transform:translate(14%,-6%) rotate(-6deg) scale(0.9);}
  100%{transform:translate(4%,-10%) rotate(4deg) scale(0.95);}
}
@media (prefers-reduced-motion: reduce){
  .aurora-band{animation:none;}
}
/* Three blurred, animated layers are a standing GPU cost. Once the hero has
   scrolled away there is nothing to look at, so stop paying it. */
body.aurora-idle .aurora-band{animation-play-state:paused;}

/* ---------- Share image ----------
   tools/build-og.py adds og-mode and captures 1200x630: the hero fills the
   card, the bar and everything interactive stay out of the picture. */
body.og-mode .controlbar, body.og-mode .meta-row,
body.og-mode .portrait-dots, body.og-mode .kicker-summary{display:none !important;}
body.og-mode .hero{min-height:630px;padding:150px 0 0;border-bottom:none;}
body.og-mode .hero-content{padding-right:0;}
body.og-mode h1.name{font-size:92px;}
body.og-mode .role-line{font-size:32px;margin-top:22px;color:var(--text-hi);}
body.og-mode .role-line::after{
  content:"Data strategy · Microsoft Fabric · Quant modelling";display:block;margin-top:26px;
  font-family:'JetBrains Mono',monospace;font-size:17px;letter-spacing:.06em;color:var(--text-faint);
}
body.og-mode .role-former{font-size:18px;}
body.og-mode .eyebrow{font-size:16px;}
body.og-mode .portrait-frame{position:fixed;top:118px;right:70px;}
body.og-mode .portrait-ring{width:270px;height:270px;}
body.og-mode .aurora-layer{opacity:.7;}

.hero-content{position:relative;z-index:1;}
/* ---------- Hero entrance ----------
   Once per load the header settles in line by line, the portrait last.
   Pure CSS, so it runs without JS too; `both` holds each line hidden
   until its turn and leaves it in place afterwards. */
@keyframes hero-in{
  from{opacity:0;transform:translateY(14px);}
  to{opacity:1;transform:none;}
}
.hero-content > *{animation:hero-in .6s cubic-bezier(.2,.7,.3,1) both;}
.hero-content > :nth-child(2){animation-delay:.05s;}
.hero-content > :nth-child(3){animation-delay:.12s;}
.hero-content > :nth-child(4){animation-delay:.19s;}
.hero-content > :nth-child(5){animation-delay:.26s;}
.hero-content > :nth-child(6){animation-delay:.33s;}
.hero-content > :nth-child(n+7){animation-delay:.4s;}
.hero-content > .portrait-frame{animation-name:portrait-in;animation-duration:.8s;animation-delay:.25s;}
@keyframes portrait-in{
  from{opacity:0;transform:scale(.94);}
  to{opacity:1;transform:none;}
}
@media (prefers-reduced-motion: reduce){ .hero-content > *{animation:none;} }
@media print{ .hero-content > *{animation:none !important;} }
body.og-mode .hero-content > *{animation:none;}

/* ---------- Portrait carousel ---------- */
.portrait-frame{
  position:absolute;top:2px;right:0;z-index:2;
  display:flex;flex-direction:column;align-items:center;gap:11px;
}
/* Registering the angle lets it interpolate; without @property a custom
   property animates discretely, which here degrades to a harmless no-op
   since 0deg and 360deg paint identically. */
@property --ring-a{syntax:'<angle>';inherits:false;initial-value:0deg;}
.portrait-ring{
  position:relative;width:150px;height:150px;border-radius:50%;flex:none;
  background:conic-gradient(from var(--ring-a),var(--green),var(--teal),var(--violet),var(--green));
  box-shadow:0 10px 34px rgba(0,0,0,.5),0 0 0 1px var(--hairline);
  animation:ring-spin 26s linear infinite;
}
@keyframes ring-spin{to{--ring-a:360deg;}}
@media (prefers-reduced-motion: reduce){
  .portrait-ring{animation:none;}
}
body.aurora-idle .portrait-ring{animation-play-state:paused;}
.portrait-img{
  position:absolute;inset:4px;width:calc(100% - 8px);height:calc(100% - 8px);
  border-radius:50%;object-fit:cover;opacity:0;transition:opacity 1.1s ease;
}
.portrait-img.is-active{opacity:1;}
.portrait-dots{display:flex;gap:7px;}
.portrait-dot{
  width:6px;height:6px;border-radius:50%;flex:none;
  appearance:none;border:none;padding:0;cursor:pointer;
  background:var(--hairline-strong);transition:background .2s ease,transform .2s ease;
}
.portrait-dot.is-active{background:var(--green);transform:scale(1.35);}
.portrait-dot:hover{background:var(--teal);}
@media (min-width:761px){
  .hero-content{padding-right:206px;}
  /* The portrait gives way to however tall the weather badge above it is */
  .portrait-frame{top:calc(2px + var(--badge-shift,0px));transition:top .4s ease;}
}
@media (max-width:760px){
  .portrait-frame{position:static;margin-bottom:24px;}
}
@media print{
  .portrait-frame{display:none;}
}
.eyebrow{
  font-family:'JetBrains Mono',monospace;
  font-size:12.5px;letter-spacing:.12em;text-transform:uppercase;
  color:var(--teal);margin-bottom:18px;display:flex;align-items:center;gap:10px;
}
.eyebrow::before{
  content:'';width:7px;height:7px;border-radius:50%;
  background:var(--green);box-shadow:0 0 12px 2px var(--green);
  display:inline-block;
}
h1.name{
  font-family:'Archivo',system-ui,sans-serif;
  font-weight:800;
  font-stretch:125%;
  font-variation-settings:'wdth' 125;
  letter-spacing:.04em;
  text-transform:uppercase;
  font-size:clamp(30px,7.6vw,64px);
  line-height:1;
  color:var(--text-hi);
}
.role-line{
  margin-top:14px;
  font-size:clamp(17px,2.4vw,21px);
  color:var(--text-lo);
  font-weight:500;
}
/* Was an inline style, which the print sheet could not reach to resize */
.role-former{margin-top:8px;font-size:14px;color:var(--text-faint);}
.meta-row{
  margin-top:26px;display:flex;flex-wrap:wrap;align-items:center;gap:10px 22px;
  font-family:'JetBrains Mono',monospace;font-size:13px;color:var(--text-lo);
}
.meta-row a{text-decoration:none;border-bottom:1px solid transparent;transition:border-color .2s;}
.meta-row a:hover{border-color:var(--teal);color:var(--teal);}
.meta-row .dot{color:var(--text-faint);}
/* Profiles get their own line, spaced like the email and phone above them */
.contact-social{flex-basis:100%;display:flex;flex-wrap:wrap;align-items:center;gap:6px 22px;}
.contact-social[hidden]{display:none;}

/* ---------- Contact line ---------- */
.contact-block{position:relative;display:inline-flex;align-items:center;gap:22px;}
.vcard-btn{
  display:inline-flex;align-items:center;gap:6px;
  appearance:none;border:1px solid var(--hairline-strong);border-radius:999px;
  background:transparent;color:var(--text-lo);
  font-family:'JetBrains Mono',monospace;font-size:12px;letter-spacing:.03em;
  padding:5px 12px 5px 10px;cursor:pointer;transition:border-color .2s ease,color .2s ease;
}
.vcard-btn:hover{border-color:var(--green);color:var(--green);}
.vcard-btn[hidden]{display:none;}
.vcard-btn svg{width:12px;height:12px;stroke:currentColor;flex:none;}
@media (max-width:720px){
  .contact-block{flex-wrap:wrap;}
}
@media print{
  .vcard-btn{display:none !important;}
}

.kicker-summary{
  margin-top:34px;max-width:640px;
  color:var(--text-lo);font-size:15.5px;
}
/* ---------- Sections ---------- */
/* scroll-margin keeps a jumped-to heading clear of the sticky control bar */
section{position:relative;padding:56px 0;border-bottom:1px solid var(--hairline);scroll-margin-top:72px;}
section:last-of-type{border-bottom:none;}
.section-head{
  display:flex;align-items:baseline;gap:14px;margin-bottom:32px;
}
.section-num{
  font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--text-faint);
}
.section-title{font-size:26px;color:var(--text-hi);}
/* Fading out reads calmer than a rule that stops dead at the margin */
.section-line{
  flex:1;height:1px;
  background:linear-gradient(90deg,var(--hairline-strong),var(--hairline) 55%,transparent);
}

/* Experience — timeline rail */
.timeline{position:relative;}
.timeline::before{
  content:'';position:absolute;left:161px;top:6px;bottom:30px;width:1px;
  background:linear-gradient(180deg,var(--hairline-strong),var(--hairline) 90%,transparent);
}
.job{
  position:relative;
  display:grid;grid-template-columns:150px 1fr;gap:24px;
  padding:22px 0;border-top:1px solid var(--hairline);
  transition:border-color .25s ease;
  scroll-margin-top:84px;   /* a career-map jump lands clear of the sticky bar */
}
.job:first-child{border-top:none;padding-top:0;}
.job::after{
  content:'';position:absolute;left:157px;top:28px;
  width:9px;height:9px;border-radius:50%;
  background:var(--void);border:2px solid var(--text-faint);
  transition:border-color .25s ease,box-shadow .25s ease,transform .25s ease;
}
.job:first-child::after{top:6px;}
.job:hover::after{
  border-color:var(--green);box-shadow:0 0 12px 2px rgba(255,90,31,.45);transform:scale(1.15);
}
.job.is-current::after{
  border-color:var(--green);box-shadow:0 0 10px 1px rgba(255,90,31,.4);
}
.job-period{
  font-family:'JetBrains Mono',monospace;font-size:12.5px;color:var(--text-faint);
  padding-top:4px;transition:color .25s ease;
}
/* Recruiters end up counting these months on their fingers; count for them. */
.job-dur{display:block;margin-top:3px;font-size:11px;color:var(--text-faint);opacity:.75;}
.job:hover .job-period{color:var(--text-lo);}
.job-org{font-size:19px;font-weight:600;color:var(--text-hi);}
.job-title{color:var(--teal);font-size:14.5px;margin-top:2px;font-weight:500;}
.job-note{
  font-family:'JetBrains Mono',monospace;font-size:11.5px;letter-spacing:.04em;
  color:var(--text-faint);margin-top:3px;
}
/* Further roles at the same employer hang off the one above: no divider,
   a smaller dot on the rail, and the repeated company row taken out of view
   (not out of the page; the career map and screen readers still read it). */
.job.job-cont{border-top:none;padding-top:4px;}
.job.job-cont::after{top:10px;width:7px;height:7px;left:158px;}
.job.job-cont .job-org{
  position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;
}
.job.job-cont .job-period{padding-top:1px;}
.job.job-cont .job-title{margin-top:0;}
/* Total time at an employer, beside its name, when there was more than one role */
.org-tenure{
  margin-left:12px;font-family:'JetBrains Mono',monospace;font-size:11.5px;font-weight:400;
  letter-spacing:.04em;color:var(--text-faint);white-space:nowrap;
}
/* The pre-2015 line is prose, not a role title — it must not take the
   role colour on screen or the ink accent on paper. */
.job-earlier{color:var(--text-lo);}

/* ---------- Career map ----------
   The timeline below is read top to bottom, one role at a time; this is
   the same data read left to right, all at once — tenure, gaps and the
   three employers' relative share of the decade, at a glance. Rendered by
   script from the same data-from/data-to attributes the durations use, so
   it cannot drift from the list it summarises. Empty (and so invisible)
   without JS. Screen only: on paper the list itself already carries this. */
.career-map{
  margin:0 0 36px;padding:18px 20px 14px;
  border:1px solid var(--hairline);border-radius:14px;background:var(--panel);
}
.career-map:empty{display:none;}
.career-rows{display:grid;grid-template-columns:176px 1fr;gap:11px 24px;align-items:center;}
.career-org{
  display:flex;align-items:center;gap:8px;min-width:0;
  font-size:12.5px;color:var(--text-lo);
}
.career-org .org-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.career-track{position:relative;height:14px;}
.career-bar{
  position:absolute;top:0;height:14px;border-radius:4px;
  appearance:none;padding:0;cursor:pointer;
  background:rgba(31,168,201,.42);border:1px solid rgba(31,168,201,.75);
  transform:scaleX(0);transform-origin:left center;
  transition:transform .9s cubic-bezier(.2,.7,.3,1),background .2s ease,box-shadow .2s ease;
}
.career-map.is-lit .career-bar{transform:scaleX(1);}
.career-bar:hover{background:rgba(31,168,201,.7);}
.career-bar.is-current{
  background:rgba(255,90,31,.42);border-color:var(--green);
  box-shadow:0 0 10px 1px rgba(255,90,31,.3);
}
.career-bar.is-current:hover{background:rgba(255,90,31,.7);}
/* Bars draw in one after another; the stagger is what makes it read as a
   timeline filling rather than a chart appearing. */
.career-bar:nth-of-type(2){transition-delay:.12s;}
.career-bar:nth-of-type(3){transition-delay:.24s;}
.career-axis{
  grid-column:2;position:relative;height:14px;margin-top:2px;
  font-family:'JetBrains Mono',monospace;font-size:10.5px;color:var(--text-faint);
}
.career-tick{position:absolute;top:2px;transform:translateX(-50%);}
.career-tick::before{
  content:'';position:absolute;left:50%;top:-9px;height:6px;width:1px;
  background:var(--hairline-strong);
}
.career-map figcaption{
  margin-top:14px;font-family:'JetBrains Mono',monospace;font-size:11px;
  letter-spacing:.03em;color:var(--text-faint);
  display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;
}
/* Replay: the bars grow behind a cursor that sweeps the years */
.career-replay{
  appearance:none;border:1px solid var(--hairline-strong);border-radius:999px;background:transparent;
  color:var(--text-lo);font:inherit;letter-spacing:inherit;padding:4px 11px;cursor:pointer;
  display:inline-flex;align-items:center;gap:6px;transition:border-color .2s ease,color .2s ease;
}
.career-replay:hover{border-color:var(--teal);color:var(--text-hi);}
.career-replay svg{width:10px;height:10px;fill:currentColor;}
.career-map.is-replaying .career-bar{transition:none;}
.career-rows{position:relative;}
.career-cursor{
  position:absolute;top:-6px;bottom:22px;width:1px;background:var(--green);
  box-shadow:0 0 8px var(--green);pointer-events:none;
}
.career-cursor b{
  position:absolute;top:-16px;left:50%;transform:translateX(-50%);
  font-family:'JetBrains Mono',monospace;font-size:10.5px;font-weight:500;color:var(--green);letter-spacing:.06em;
}
@media (prefers-reduced-motion: reduce){
  .career-bar{transform:none;transition:none;}
}
@media (max-width:720px){
  .career-rows{grid-template-columns:auto 1fr;gap:11px 12px;}
  .career-org .org-name{display:none;}
}

/* A jumped-to line lights briefly so the eye lands on it. One class for
   both motion settings: the fade is the only part that needs motion, so
   that is the only part reduced-motion removes. */
.is-hit{background:rgba(255,90,31,.16);box-shadow:0 0 0 6px rgba(255,90,31,.16);border-radius:6px;}
/* Evidence: a skill tag lights the lines and cards where it was used */
.skill-tags > span.has-evidence{cursor:pointer;border-color:var(--hairline-strong);transition:border-color .2s ease,color .2s ease;}
.skill-tags > span.has-evidence:hover{border-color:var(--teal);}
.skill-tags > span.has-evidence::after{
  content:attr(data-hits);margin-left:7px;font-family:'JetBrains Mono',monospace;font-size:10px;
  color:var(--text-faint);vertical-align:1px;
}
.skill-tags > span.is-on{border-color:var(--green);color:var(--green);box-shadow:inset 0 0 0 1px var(--green);}
.skill-tags > span.is-on::after{color:var(--green);}
.is-evidence{background:rgba(255,90,31,.12);box-shadow:0 0 0 6px rgba(255,90,31,.12);border-radius:6px;transition:background .3s ease,box-shadow .3s ease;}
.career-bar.is-evidence{box-shadow:0 0 0 2px var(--green),0 0 12px rgba(255,90,31,.5);}
.evidence-pill{
  position:fixed;left:50%;bottom:22px;transform:translateX(-50%);z-index:60;
  display:flex;align-items:center;gap:12px;padding:9px 14px 9px 16px;border-radius:999px;
  background:var(--panel-2);border:1px solid var(--green);box-shadow:0 12px 32px rgba(0,0,0,.45);
  font-family:'JetBrains Mono',monospace;font-size:12px;letter-spacing:.04em;color:var(--text-lo);white-space:nowrap;
}
.evidence-pill b{color:var(--green);font-weight:500;}
.evidence-pill button{
  appearance:none;border:1px solid var(--hairline-strong);border-radius:999px;background:transparent;
  color:var(--text-lo);font:inherit;padding:3px 9px;cursor:pointer;
}
.evidence-pill button:hover{border-color:var(--teal);color:var(--text-hi);}
@media print{ .skill-tags > span.has-evidence::after{content:none;} .evidence-pill{display:none !important;} }

/* "New since your last visit": a small card for returning readers */
.news-card{
  position:fixed;right:22px;bottom:22px;z-index:60;width:min(360px,calc(100vw - 32px));
  background:var(--panel-2);border:1px solid var(--hairline-strong);border-radius:14px;
  box-shadow:0 16px 40px rgba(0,0,0,.5),0 0 0 1px rgba(224,169,27,.1);padding:14px 16px 12px;
  animation:cmdk-rise .2s cubic-bezier(.2,.7,.3,1);
}
.news-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px;}
.news-title{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--violet);}
.news-close{
  appearance:none;border:1px solid var(--hairline-strong);border-radius:999px;background:transparent;width:26px;height:26px;
  color:var(--text-lo);font-size:15px;line-height:1;cursor:pointer;
}
.news-close:hover{border-color:var(--pink);color:var(--text-hi);}
.news-list{list-style:none;margin:0;padding:0;}
.news-list li{display:grid;grid-template-columns:auto 1fr;gap:10px;font-size:13px;line-height:1.45;color:var(--text-lo);padding:5px 0;border-top:1px solid var(--hairline);}
.news-list li:first-child{border-top:0;}
.news-list time{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-faint);padding-top:2px;}
.news-list a{color:var(--teal);text-decoration:none;}
.news-list a:hover{text-decoration:underline;}
.news-foot{margin-top:8px;font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-faint);}
@media print{ .news-card{display:none !important;} }
@media (max-width:640px){ .news-card{right:12px;bottom:12px;} }
@media (prefers-reduced-motion: no-preference){
  .is-hit{animation:hit-fade 1.7s ease-out forwards;}
}
@keyframes hit-fade{
  0%{background:rgba(255,90,31,.28);box-shadow:0 0 0 6px rgba(255,90,31,.28);}
  100%{background:transparent;box-shadow:0 0 0 6px transparent;}
}

/* ---------- Organisation marks ----------
   Monograms rather than brand logos: a single asset cannot serve both the dark
   screen theme and the light print sheet, whereas these inherit currentColor
   and invert on their own. The mark repeats the name beside it, so it is
   decorative and hidden from assistive tech. */
.org-row{
  display:inline-flex;align-items:center;gap:10px;
  text-decoration:none;color:inherit;
}
/* .career-mark is the same monogram on the career map's employer rows —
   shared look, separate class, so counts of the linked marks stay honest. */
.org-mark, .career-mark{
  flex:none;display:inline-flex;align-items:center;justify-content:center;
  height:26px;min-width:26px;padding:0 6px;border-radius:7px;
  border:1px solid var(--hairline-strong);background:var(--panel);
  font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:500;
  letter-spacing:.06em;color:var(--text-faint);
  transition:border-color .2s ease,color .2s ease;
}
/* Matches the underline-on-hover used by the contact links in the hero */
.org-name{
  border-bottom:1px solid transparent;
  transition:border-color .2s ease,color .2s ease;
}
a.org-row:hover .org-mark{border-color:var(--teal);color:var(--teal);}
a.org-row:hover .org-name{border-color:var(--teal);color:var(--teal);}
.job ul{margin:14px 0 0;padding-left:18px;color:var(--text-lo);font-size:14.5px;}
.job li{margin-bottom:7px;}
.job li::marker{color:var(--green);}

/* Projects */
.projects-grid{display:grid;grid-template-columns:1fr 1fr;gap:18px;}
/* An odd card count leaves the last one alone in its row, half the grid
   empty beside it. Spanning it full width reads as a deliberate closer
   instead of a gap — and generalises to any odd count, not just five.
   Applies in print too (nothing here is screen-only). */
.projects-grid > .project-card:last-child:nth-child(odd){grid-column:1 / -1;}
/* Wide screens give the three lead cards one row of their own. */
@media screen and (min-width:1000px){
  .projects-featured{grid-template-columns:repeat(3,1fr);}
  .projects-featured > .project-card:last-child:nth-child(odd){grid-column:auto;}
}

/* The CV's pointer to projects.html, standing where the section used to.
   Screen only: paper has no use for a link it cannot follow. */
.projects-teaser{
  display:grid;grid-template-columns:1fr auto;align-items:center;gap:6px 24px;
  margin:40px 0 8px;padding:22px 24px;text-decoration:none;
  background:var(--panel);border:1px solid var(--hairline);border-radius:14px;
  transition:border-color .2s ease, transform .2s ease;
}
.projects-teaser:hover{border-color:var(--teal);transform:translateY(-2px);}
.projects-teaser-kicker{
  grid-column:1 / -1;display:inline-flex;align-items:center;gap:8px;
  font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--green);
}
.projects-teaser-text{color:var(--text-lo);font-size:15px;max-width:640px;}
.projects-teaser-link{
  display:inline-flex;align-items:center;gap:6px;white-space:nowrap;
  font-family:'JetBrains Mono',monospace;font-size:12.5px;color:var(--teal);
  transition:gap .2s ease;
}
.projects-teaser:hover .projects-teaser-link{gap:10px;}
@media (max-width:600px){ .projects-teaser{grid-template-columns:1fr;} }
@media print{ .projects-teaser{display:none !important;} }

/* projects.html borrows the hero's sky but not its height or its portrait */
.hero.page-hero{padding:72px 0 44px;}
.page-hero h1.name{font-size:clamp(36px,5.6vw,58px);}
.page-hero .kicker-summary{margin-top:22px;}
.back-link{
  display:inline-flex;align-items:center;gap:6px;margin-top:26px;
  font-family:'JetBrains Mono',monospace;font-size:12.5px;color:var(--teal);text-decoration:none;
  transition:gap .2s ease;
}
.back-link:hover{gap:10px;}
.controls .back-btn{text-decoration:none;}

/* ---------- Guess the company ----------
   Opened from its card on the start page: a real price chart, four names,
   eight rounds. Rendered into #quiz-body by script. The "About this page"
   panel on the CV borrows the same dialog chrome. */
.quiz-scrim{
  position:fixed;inset:0;z-index:210;
  background:rgba(9,18,29,.7);backdrop-filter:blur(4px);
  display:flex;justify-content:center;align-items:flex-start;
  padding:6vh 16px 24px;overflow-y:auto;
  animation:cmdk-fade .14s ease-out;
}
.quiz-scrim[hidden]{display:none;}
/* "About this page" shares the quiz panel's chrome */
.about-lead{font-size:15px;line-height:1.6;color:var(--text);margin:0 0 16px;}
.about-h{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--text-faint);margin:18px 0 8px;}
.about-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px 16px;margin:0;padding:0;list-style:none;}
.about-grid li{font-size:13.5px;line-height:1.5;color:var(--text-lo);padding-left:14px;position:relative;}
.about-grid li::before{content:'';position:absolute;left:0;top:9px;width:5px;height:5px;border-radius:50%;background:var(--teal);opacity:.8;}
.about-grid li b{color:var(--text);font-weight:600;}
.about-keys{display:grid;grid-template-columns:auto 1fr;gap:7px 14px;align-items:center;margin:0;}
.about-keys dt{margin:0;white-space:nowrap;font-size:12px;color:var(--text-lo);}
.about-keys dd{margin:0;font-size:13.5px;color:var(--text-lo);}
.about-keys kbd{
  display:inline-block;font-family:'JetBrains Mono',monospace;font-size:11px;padding:2px 7px;min-width:22px;text-align:center;
  border:1px solid var(--hairline-strong);border-bottom-width:2px;border-radius:6px;background:var(--panel);color:var(--text);
}
.about-keys kbd + kbd{margin-left:4px;}
.about-foot{display:flex;flex-wrap:wrap;gap:10px 18px;align-items:center;margin-top:18px;padding-top:14px;border-top:1px solid var(--hairline);font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--text-faint);}
.about-foot a{color:var(--teal);text-decoration:none;}
.about-foot a:hover{text-decoration:underline;}
@media (max-width:520px){ .about-grid{grid-template-columns:1fr;} }
.quiz{
  width:min(680px,100%);background:var(--panel-2);
  border:1px solid var(--hairline-strong);border-radius:16px;
  box-shadow:0 24px 64px rgba(0,0,0,.6),0 0 0 1px rgba(224,169,27,.12);
  padding:20px 22px 16px;animation:cmdk-rise .16s cubic-bezier(.2,.7,.3,1);
}
.quiz-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:14px;}
.quiz-title{font-family:'Fraunces',serif;font-size:24px;color:var(--text-hi);line-height:1.1;}
.quiz-meta{
  display:flex;flex-wrap:wrap;gap:6px 16px;margin-top:6px;
  font-family:'JetBrains Mono',monospace;font-size:11.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--text-faint);
}
.quiz-meta b{color:var(--green);font-weight:500;}
.quiz-close{
  flex:none;width:34px;height:34px;appearance:none;border:1px solid var(--hairline-strong);
  border-radius:999px;background:var(--panel);color:var(--text-lo);font-size:18px;line-height:1;
  cursor:pointer;transition:border-color .2s ease,color .2s ease;
}
.quiz-close:hover{border-color:var(--pink);color:var(--text-hi);}
.quiz-chart{
  position:relative;border:1px solid var(--hairline);border-radius:12px;
  background:var(--panel);padding:10px 6px 2px;margin-bottom:12px;
}
.quiz-chart svg{display:block;width:100%;height:auto;}
.quiz-axis{font-family:'JetBrains Mono',monospace;font-size:10.5px;fill:var(--text-faint);}
.quiz-grid{stroke:var(--hairline);stroke-width:1;}
.quiz-base{stroke:var(--hairline-strong);stroke-width:1;stroke-dasharray:3 4;}
.quiz-line{fill:none;stroke:var(--green);stroke-width:2;stroke-linejoin:round;stroke-linecap:round;}
.quiz-area{fill:url(#quiz-grad);}
.quiz-opts button.is-right b{color:var(--teal);}
.quiz-opts button.is-wrong b{color:var(--pink);}
@media (prefers-reduced-motion: no-preference){
  .quiz-line{stroke-dasharray:1;stroke-dashoffset:1;animation:quiz-draw 1.1s ease-out forwards;}
  .quiz-area{opacity:0;animation:quiz-fill .6s ease-out .7s forwards;}
}
@keyframes quiz-draw{to{stroke-dashoffset:0;}}
@keyframes quiz-fill{to{opacity:1;}}
.quiz-scale{
  position:absolute;top:9px;right:12px;
  font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--text-faint);
}
.quiz-time{display:flex;align-items:center;gap:12px;margin:-4px 2px 12px;}
.quiz-time-bar{flex:1;height:4px;border-radius:2px;background:var(--hairline);overflow:hidden;}
.quiz-time-bar i{display:block;height:100%;width:100%;border-radius:2px;background:linear-gradient(90deg,var(--green),var(--teal));transform-origin:left center;}
.quiz-time-val{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--text-faint);white-space:nowrap;}
.quiz-time-val b{color:var(--green);font-weight:500;}
.quiz-opts{display:grid;grid-template-columns:1fr 1fr;gap:8px;}
.quiz-opts button{
  appearance:none;text-align:left;border:1px solid var(--hairline-strong);border-radius:10px;
  background:var(--panel);color:var(--text-hi);padding:11px 13px;cursor:pointer;
  font-family:'Inter',sans-serif;font-size:14.5px;display:flex;align-items:center;gap:10px;
  transition:border-color .2s ease,background .2s ease;
}
.quiz-opts button kbd{
  font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-faint);
  border:1px solid var(--hairline-strong);border-radius:4px;padding:0 5px;line-height:1.6;flex:none;
}
.quiz-opts button:hover:not(:disabled){border-color:var(--teal);}
.quiz-opts button:disabled{cursor:default;}
.quiz-opts button.is-right{border-color:var(--teal);background:rgba(31,168,201,.12);}
.quiz-opts button.is-wrong{border-color:var(--pink);background:rgba(255,90,31,.1);}
.quiz-row{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:12px;flex-wrap:wrap;}
.quiz-hint{font-size:13px;color:var(--text-lo);min-height:1.4em;flex:1;}
.quiz-hint b{color:var(--text-hi);font-weight:500;}
.quiz-btn{
  appearance:none;border:1px solid var(--hairline-strong);border-radius:999px;
  background:var(--panel);color:var(--text-lo);font-family:'JetBrains Mono',monospace;
  font-size:12px;padding:7px 14px;cursor:pointer;transition:border-color .2s ease;
}
.quiz-btn:hover:not(:disabled){border-color:var(--teal);color:var(--text-hi);}
.quiz-btn.is-primary{background:linear-gradient(135deg,var(--green),var(--teal));color:#0e1b2b;border-color:transparent;font-weight:500;}
.quiz-btn:disabled{opacity:.4;cursor:default;}
.quiz-reveal{margin-top:12px;padding:12px 14px;border:1px solid var(--hairline);border-radius:12px;background:var(--panel);}
.quiz-reveal-name{font-size:15px;color:var(--text-hi);}
.quiz-reveal-name b{font-weight:600;}
.quiz-reveal-name span{color:var(--text-faint);font-family:'JetBrains Mono',monospace;font-size:12px;margin-left:8px;}
.quiz-stats{display:flex;flex-wrap:wrap;gap:8px 22px;margin-top:8px;font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--text-faint);}
.quiz-stats b{display:block;font-size:15px;letter-spacing:0;text-transform:none;color:var(--text-hi);font-weight:500;margin-top:2px;}
.quiz-stats b.is-up{color:var(--teal);}
.quiz-stats b.is-down{color:var(--pink);}
.quiz-foot{margin:14px 0 0;font-family:'JetBrains Mono',monospace;font-size:10.5px;color:var(--text-faint);letter-spacing:.02em;line-height:1.5;}
.quiz-daily{
  appearance:none;border:1px solid var(--hairline-strong);border-radius:999px;background:transparent;
  color:var(--text-faint);font:inherit;letter-spacing:inherit;text-transform:inherit;
  padding:2px 9px;cursor:pointer;transition:border-color .2s ease,color .2s ease;
}
.quiz-daily:hover{border-color:var(--teal);color:var(--text-hi);}
.quiz-daily.is-on{border-color:var(--green);color:var(--green);}
.quiz-marks{display:flex;justify-content:center;gap:5px;margin-top:14px;}
.quiz-marks i{display:block;width:16px;height:16px;border-radius:4px;background:var(--hairline-strong);}
.quiz-marks i.is-up{background:var(--teal);}
.quiz-marks i.is-down{background:var(--pink);}
.quiz-final-daily{font-family:'JetBrains Mono',monospace;font-size:11.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--green);margin-bottom:8px;}
.quiz-final{text-align:center;padding:26px 0 10px;}
.quiz-final-score{font-family:'Fraunces',serif;font-size:64px;color:var(--green);line-height:1;}
.quiz-final-grade{font-size:18px;color:var(--text-hi);margin-top:12px;}
.quiz-final-best{font-family:'JetBrains Mono',monospace;font-size:11.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--text-faint);margin-top:8px;}
.quiz-final .quiz-row{justify-content:center;margin-top:20px;}
/* Level picker and the 3-2-1 that follows it */
.quiz-intro{padding:4px 0 2px;}
.quiz-intro-lead{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--text-faint);margin:0 2px 10px;}
.quiz-levels{display:grid;gap:8px;}
.quiz-levels button{
  appearance:none;text-align:left;border:1px solid var(--hairline-strong);border-radius:12px;
  background:var(--panel);color:var(--text-hi);padding:12px 14px;cursor:pointer;
  font-family:'Inter',sans-serif;display:flex;flex-direction:column;gap:5px;
  transition:border-color .2s ease,background .2s ease;
}
.quiz-levels button:hover{border-color:var(--teal);}
.quiz-levels button.is-on{border-color:var(--green);background:rgba(255,90,31,.1);}
.quiz-level-top{display:flex;align-items:center;gap:10px;font-size:15px;}
.quiz-level-top b{font-weight:600;}
.quiz-level-top em{margin-left:auto;font-style:normal;font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.06em;color:var(--green);}
.quiz-levels kbd{
  font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-faint);
  border:1px solid var(--hairline-strong);border-radius:4px;padding:0 5px;line-height:1.6;flex:none;
}
.quiz-levels button.is-on kbd{border-color:var(--green);color:var(--green);}
.quiz-level-desc{font-size:13px;line-height:1.45;color:var(--text-lo);padding-left:31px;}
.quiz-intro .quiz-row{margin-top:14px;}
.quiz-go{padding:9px 20px;font-size:13px;}
.quiz-count{min-height:300px;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;}
.quiz-count-num{font-family:'Fraunces',serif;font-size:120px;line-height:1;color:var(--green);}
@media (prefers-reduced-motion: no-preference){
  .quiz-count-num{animation:quiz-pop 1s ease-out both;}
}
@keyframes quiz-pop{
  0%{opacity:0;transform:scale(1.6);}
  25%{opacity:1;transform:scale(1);}
  80%{opacity:1;}
  100%{opacity:.15;transform:scale(.85);}
}
@media (max-width:520px){ .quiz-opts{grid-template-columns:1fr;} .quiz-level-desc{padding-left:0;} .quiz-count-num{font-size:96px;} }
@media (max-width:520px){ .quiz{padding:16px 14px 12px;} }
.quiz:focus{outline:none;}
@media (prefers-reduced-motion: reduce){ .quiz-scrim,.quiz{animation:none;} }
.project-card{
  display:block;text-decoration:none;
  background:var(--panel);border:1px solid var(--hairline);border-radius:14px;
  padding:22px;transition:border-color .2s ease, transform .2s ease;
}
.project-card:hover{border-color:var(--teal);transform:translateY(-2px);}
.project-title{font-size:17px;font-weight:600;color:var(--text-hi);margin-bottom:10px;}
.tag-row{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px;}
.tag{
  flex:0 0 auto;white-space:nowrap;
  font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.02em;
  padding:3px 9px;border-radius:999px;
  border:1px solid var(--hairline-strong);color:var(--teal);
}
.project-desc{color:var(--text-lo);font-size:14px;}
/* Detail the web page has room for and the paper sheet does not. */
.project-note{
  margin:10px 0 0;font-family:'JetBrains Mono',monospace;
  font-size:11.5px;line-height:1.55;color:var(--text-faint);
}

/* Live/linked project card */
.project-live-badge{
  display:inline-flex;align-items:center;gap:7px;
  font-family:'JetBrains Mono',monospace;font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;
  color:var(--green);margin-bottom:11px;
}
.pulse-dot{
  width:7px;height:7px;border-radius:50%;background:var(--green);
  box-shadow:0 0 8px 1px var(--green);animation:pulse-dot 1.8s ease-in-out infinite;flex:none;
}
@keyframes pulse-dot{
  0%,100%{opacity:1;transform:scale(1);}
  50%{opacity:.5;transform:scale(.7);}
}
@media (prefers-reduced-motion: reduce){
  .pulse-dot{animation:none;}
}
.project-open-link{
  display:inline-flex;align-items:center;gap:6px;margin-top:14px;
  font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--teal);
  transition:gap .2s ease;
}
.project-card:hover .project-open-link{gap:10px;}
/* Links sit on the card's floor, so a row of cards ends on one line however
   long each description runs. */
@media screen{
  .project-card{display:flex;flex-direction:column;}
  .project-card > .project-open-link, .project-card > .project-links{margin-top:auto;padding-top:14px;}
  .project-card > .project-open-link{align-self:flex-start;}
  /* With a chart, the chart takes the slack, so the curves line up too. */
  .project-card > .project-chart{margin-top:auto;padding-top:16px;}
  .project-card > .project-chart ~ .project-open-link{margin-top:0;}
  /* Right under "how it works" the links belong to it, not to the floor. */
  .project-card > .project-more + .project-links{padding-top:2px;}
}

/* Decorative chart motif — evokes the shape of output each project produces.
   Not plotted from data, so it carries no labels, axes or numbers. */
.project-chart{
  display:block;width:100%;height:42px;margin-top:16px;
  opacity:.5;transition:opacity .25s ease;
}
.project-card:hover .project-chart{opacity:.8;}
/* Live cards: the drawn chart yields to a real one once data arrived; the
   static drawing stays for paper and for visitors whose fetch fails. */
.project-card.has-live .project-chart.is-static{display:none;}
.project-chart.is-live{opacity:.85;}
.project-chart.is-live .live-now{fill:var(--green);}
.live-note{
  display:flex;align-items:center;gap:8px;margin-top:9px;
  font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.04em;color:var(--text-faint);
}
.live-note b{color:var(--text-lo);font-weight:500;}
.live-note .pulse-dot{width:6px;height:6px;}
.project-chart path,.project-chart line,.project-chart rect{
  vector-effect:non-scaling-stroke;
}

/* Skills */
.skills-grid{display:grid;grid-template-columns:1fr 1fr;gap:32px 40px;}
.skill-group-title{
  font-family:'JetBrains Mono',monospace;font-size:11.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--text-faint);margin-bottom:14px;
}
.skill-tags{display:flex;flex-wrap:wrap;gap:8px;}
/* Direct children only: a tag may contain nested language spans, and a
   descendant selector here draws a second pill inside the first. */
.skill-tags > span{
  flex:0 0 auto;white-space:nowrap;
  font-size:13.5px;padding:6px 12px;border-radius:8px;
  background:var(--panel-2);border:1px solid var(--hairline);
  color:var(--text-hi);
}
/* Language-toggled skill-tags need flex, not the generic inline/none from .sv-only/.en-only */
.skill-tags.sv-only{display:flex;}
.skill-tags.en-only{display:none;}
body.lang-en .skill-tags.sv-only{display:none;}
body.lang-en .skill-tags.en-only{display:flex;}

/* Direct children only. A bare `.skill-tags-icons span` also matched the
   nested .sv-only/.en-only qualifier spans inside a tag and out-specified
   their display:none, rendering both languages at once — the same trap the
   print sheet hit, so the fix is the same shape. */
.skill-tags-icons > span{display:inline-flex;align-items:center;gap:7px;transition:border-color .2s ease,color .2s ease;}
.skill-tags-icons > span:hover{border-color:var(--teal);color:var(--teal);}
.skill-icon{width:13px;height:13px;flex:none;color:var(--teal);}

/* Education */
.edu-item{padding:20px 0;border-top:1px solid var(--hairline);}
.edu-item:first-of-type{border-top:none;padding-top:0;}
.edu-head{display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:8px;}
.edu-school{font-size:18px;font-weight:600;}
.edu-period{font-family:'JetBrains Mono',monospace;font-size:12.5px;color:var(--text-faint);}
.edu-degree{color:var(--teal);font-size:14px;margin-top:2px;}
.edu-item ul{margin:10px 0 0;padding-left:18px;color:var(--text-lo);font-size:14px;}
.edu-item li::marker{color:var(--violet);}

/* Achievements — aurora intensity bars as a nod to KP-index */
.ach-grid{display:grid;grid-template-columns:1fr 1fr;gap:32px 40px;}
.ach-list{list-style:none;margin:0;padding:0;}
.ach-list li{
  display:flex;align-items:flex-start;gap:12px;
  padding:11px 0;border-top:1px solid var(--hairline);font-size:14.5px;color:var(--text-lo);
}
.ach-list li:first-child{border-top:none;}
.kp{
  flex:none;width:26px;height:14px;border-radius:4px;margin-top:3px;
  background:linear-gradient(90deg,var(--green),var(--teal),var(--violet));
}
.ach-list b{color:var(--text-hi);font-weight:600;}

/* Footer */
footer{padding:40px 0 60px;text-align:center;}
/* Daily market strip: a quiet ticker row above the footer note, filled
   from data/markets.json (refreshed by a weekday Action) */
.market-strip{
  display:flex;flex-wrap:wrap;justify-content:flex-start;align-items:center;gap:6px 8px;
  margin:14px 0 4px;padding-top:12px;border-top:1px solid var(--hairline);
  font-family:'JetBrains Mono',monospace;font-size:11.5px;color:var(--text-lo);
}
.market-strip[hidden]{display:none;}
.market-strip .mk{
  display:inline-flex;align-items:baseline;gap:7px;padding:5px 11px;border:1px solid var(--hairline);
  border-radius:999px;background:var(--panel);white-space:nowrap;
}
.market-strip .mk-l{color:var(--text-faint);}
.market-strip .mk-p{color:var(--text);}
.market-strip .mk-c{font-size:11px;}
.market-strip .mk-c.is-up{color:var(--teal);}
.market-strip .mk-c.is-down{color:var(--pink);}
.market-strip .mk-meta{flex-basis:100%;font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--text-faint);margin-top:2px;}
.footer-note{
  font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--text-faint);
}
.footer-note a{color:var(--teal);text-decoration:none;}
/* The ink is dark navy, as on paper; on the night sky it is flipped light. */
.signature{
  display:block;height:64px;width:auto;margin:0 auto 14px;
  filter:invert(1) grayscale(1);opacity:.7;
}
/* Paper-only; on screen the live page is already where the reader is. */
.print-qr{display:none;}

@keyframes cmdk-fade{from{opacity:0;}to{opacity:1;}}
@keyframes cmdk-rise{from{opacity:0;transform:translateY(-8px) scale(.985);}to{opacity:1;transform:none;}}

/* Focus states */
button:focus-visible, a:focus-visible{
  outline:2px solid var(--green);outline-offset:2px;
}

/* First tab stop on the page. Parked above the viewport until focused, so
   a keyboard user gets it and a pointer user never sees it. */
.skip-link{
  position:absolute;left:12px;top:-64px;z-index:300;
  padding:10px 14px;border-radius:8px;
  background:var(--panel-2);color:var(--text-hi);border:1px solid var(--hairline-strong);
  font-family:'JetBrains Mono',monospace;font-size:12px;text-decoration:none;
  transition:top .15s ease;
}
.skip-link:focus{top:12px;}

/* The hairlines that carry this layout are deliberately faint. Someone who
   has asked the OS for more contrast should get real lines and lighter
   secondary text, and no grain over the top of either. */
@media (prefers-contrast: more){
  :root{
    --hairline:rgba(255,255,255,.24);
    --hairline-strong:rgba(255,255,255,.4);
    --text-lo:#b9c4d3;
    --text-faint:#95a0b2;
  }
  .grain{display:none;}
}

/* ---------- Responsive ---------- */
@media (max-width:720px){
  .job{grid-template-columns:1fr;gap:6px;}
  .timeline::before, .job::after{display:none;}
  .projects-grid, .skills-grid, .ach-grid{grid-template-columns:1fr;}
  .controlbar-inner{padding:12px 20px;}
}

/* ---------- Print ----------
   The PDF is the version most readers actually receive, so it is treated as a
   designed document in its own right rather than a de-styled web page.
   Ink is brand navy #0e1b2b; Signal Orange #ff5a1f only marks dots,
   bullets and rules, since at 3:1 on white it is too faint for small text. */
@page{
  size:A4;
  margin:14mm 15mm 15mm;
  /* Chromium does support @page margin boxes, contrary to the usual assumption,
     so continuation pages can identify themselves and carry a page number. */
  @bottom-left{
    content:"Anton Ålin · CV";
    font-family:'JetBrains Mono',monospace;font-size:7pt;color:#98a1ad;
  }
  @bottom-right{
    content:counter(page) " / " counter(pages);
    font-family:'JetBrains Mono',monospace;font-size:7pt;color:#98a1ad;
  }
}
/* The first page carries the full header, so it needs no running identity. */
@page :first{
  @bottom-left{content:"";}
  @bottom-right{content:"";}
}
@media print{
  /* Chrome/Safari otherwise drop background fills we rely on for the accent rules */
  *{-webkit-print-color-adjust:exact;print-color-adjust:exact;}

  .controlbar, .aurora-layer, .scroll-progress{display:none !important;}
  .hero-sun, .hero-cloud, .career-map, .skip-link, .game-btn, .quiz-scrim, .hero-moon, .sky-line,
  .live-note, .project-note, .project-chart.is-live, .ski-scrim, .hero-snow, .market-strip,
  .hero-rain, .hero-fog, .hero-deck, .hero-flash, .hero-bolt, .hero-gust{display:none !important;}
  .vcard-btn{display:none !important;}
  /* Profiles marked `screen` in CONTACT_LINKS stay off paper */
  .screen-only{display:none !important;}

  /* Scroll-reveal must never hide content on paper */
  .reveal{opacity:1 !important;transform:none !important;transition:none !important;}

  html{scroll-behavior:auto;}
  body{
    background:#fff;color:#0e1b2b;
    font-size:9.3pt;line-height:1.36;
    orphans:2;widows:2;
  }
  .wrap{padding:0;max-width:none;}
  a{text-decoration:none;color:#0e1b2b;}

  /* --- Header --- */
  .hero{padding:0 0 8pt;border-bottom:1.2pt solid #0e1b2b;overflow:visible;}
  /* Reserve the top-right corner for the photo; the summary reclaims full
     width beneath it, since the portrait is only as tall as the name block. */
  .eyebrow, h1.name, .role-former, .role-line, .meta-row{margin-right:92pt;}
  h1.name{
    background:none;-webkit-text-fill-color:initial;color:#0e1b2b;
    font-size:26pt;line-height:1;animation:none;
  }
  .eyebrow{
    font-size:7.5pt;letter-spacing:.14em;color:#0e1b2b !important;
    margin-bottom:7pt;gap:6pt;
  }
  .eyebrow::before{background:#ff5a1f;box-shadow:none;width:5pt;height:5pt;}
  .role-former{margin-top:2pt;font-size:8.4pt;color:#79828f;}
  .role-line{margin-top:4pt;font-size:12pt;color:#0e1b2b !important;font-weight:600;}
  .meta-row{
    margin-top:7pt;font-size:8.4pt;color:#3d4b5c;gap:3pt 13pt;
  }
  .contact-block{gap:13pt;}
  .contact-social{gap:3pt 13pt;}
  .kicker-summary{
    margin-top:6pt;max-width:none;font-size:8.6pt;color:#3d4b5c;
  }

  /* --- Portrait ---
     The carousel rotates on screen, so the printed sheet pins the plain
     headshot rather than whichever frame happened to be showing. */
  .portrait-frame{
    display:block !important;position:absolute;top:1pt;right:0;
    margin:0;gap:0;z-index:2;
  }
  .portrait-ring{
    width:80pt;height:80pt;border-radius:50%;
    background:none;box-shadow:none;border:.75pt solid #c9ced6;
  }
  .portrait-img{display:none;}
  .portrait-img[data-idx="0"]{
    display:block;inset:0;width:100%;height:100%;
    opacity:1;border-radius:50%;
  }
  .portrait-dots{display:none;}
  /* Swedish practice commonly includes a CV photo; US and UK guidance
     discourages it, so the English sheet prints without one. */
  body.lang-en .portrait-frame{display:none !important;}
  body.lang-en .eyebrow,
  body.lang-en h1.name,
  body.lang-en .role-former,
  body.lang-en .role-line,
  body.lang-en .meta-row{margin-right:0;}

  /* --- Sections --- */
  section{
    border-bottom:none;padding:3.5pt 0 .7pt;
    break-inside:auto;
  }
  .section-head{
    margin-bottom:3.5pt;gap:8pt;
    break-after:avoid;break-inside:avoid;
  }
  .section-num{font-size:7.5pt;color:#0e1b2b;}
  .section-title{font-size:13.5pt;color:#0e1b2b;}
  .section-line{background:#d7dce2;}

  /* --- Experience --- */
  .timeline::before, .job::after{display:none;}
  .job{
    grid-template-columns:88pt 1fr;gap:10pt;
    padding:2.7pt 0;border-top:.5pt solid #e4e8ed;
    break-inside:avoid;
  }
  .job:first-child{padding-top:0;border-top:none;}
  .job-period{font-size:7.6pt;color:#79828f;padding-top:2.5pt;}
  .job-dur{font-size:7pt;color:#98a1ad;margin-top:1pt;opacity:1;}
  .job-org{font-size:10.5pt;color:#0e1b2b;}
  .job-title{font-size:8.8pt;color:#0e1b2b !important;margin-top:.5pt;}
  .job-note{display:none;}
  .job-earlier{color:#3d4b5c !important;font-size:8.6pt;font-weight:400;}
  .job.job-cont{border-top:none;padding-top:.6pt;}
  .job.job-cont .job-period{padding-top:.8pt;}
  .org-tenure{margin-left:6pt;font-size:7pt;color:#98a1ad;}

  /* Marks survive onto paper as hairline boxes; the URLs deliberately do not,
     since spelling one out beside every employer would clutter the timeline. */
  .org-row{gap:5pt;}
  .org-mark{
    height:11.5pt;min-width:11.5pt;padding:0 2.5pt;border-radius:2pt;
    border:.5pt solid #aebbc7;background:#fff;color:#0e1b2b;
    font-size:6pt;letter-spacing:.05em;
  }
  .org-name{border-bottom:none;}
  .job ul{margin:2.5pt 0 0;padding-left:11pt;font-size:8.7pt;color:#3d4b5c;}
  .job li{margin-bottom:.4pt;}
  .job li::marker{color:#ff5a1f;}

  /* --- Projects ---
     Two columns rather than three: four cards fill a 2x2 block evenly, where
     three columns left one card alone on a second row beside two empty cells.
     Tried three for the current five-card count too, on the reasoning that
     3-then-2 costs no extra row — measured, not assumed, and it backfired:
     every description here is written assuming roughly half-page width, and
     a third of the page wraps them all a line or two longer, costing more
     height in extra wrapped lines than the row it was meant to save. Two
     columns stays; the fifth card spans the full width instead (screen and
     print alike — see the unscoped rule above), and print recovers that
     row's cost from the card's own copy instead, immediately below. */
  .projects-grid{grid-template-columns:1fr 1fr;gap:6pt;}
  .project-card{
    border:.5pt solid #e0e5ea;background:#fbfcfd;border-radius:4pt;padding:4pt 6pt;
    break-inside:avoid;transform:none !important;
  }
  .project-title{font-size:9.2pt;color:#0e1b2b;margin-bottom:2pt;}
  .tag-row{gap:3pt;margin-bottom:2pt;}
  .tag{font-size:6.4pt;padding:1.5pt 4.5pt;color:#0e1b2b;border-color:#aebbc7;}
  .project-desc{font-size:8pt;color:#3d4b5c;}
  .project-chart{display:none;}
  .project-live-badge{color:#0e1b2b !important;margin-bottom:2pt;}
  .pulse-dot{animation:none;box-shadow:none;background:#ff5a1f;}
  /* Prints a readable short form; the full href wrapped mid-word on paper. */
  .project-open-link{
    display:block;margin-top:2.5pt;color:#79828f;font-size:7pt;
    overflow-wrap:anywhere;
  }
  .project-open-link::after{content:" · " attr(data-print);}
  .project-colab-link{display:none;}

  /* --- Skills ---
     Pills become label-and-list rows on paper. Three narrow columns of pills
     stacked one per line and left two columns half empty; as inline lists the
     same content takes roughly a third of the height and reads editorially. */
  #skills .skills-grid, #misc .skills-grid{display:block;}
  #skills .skills-grid > div, #misc .skills-grid > div{
    display:grid;grid-template-columns:70pt 1fr;gap:9pt;
    padding:1.7pt 0;border-top:.5pt solid #eceff3;
    break-inside:avoid;
  }
  #skills .skills-grid > div:first-child,
  #misc .skills-grid > div:first-child{border-top:none;padding-top:0;}
  .skill-group-title{
    font-size:6.8pt;color:#0e1b2b;margin:0;padding-top:1.5pt;letter-spacing:.09em;
  }
  .skill-icon{display:none;}
  .skill-tags{display:block;gap:0;}
  .skill-tags.sv-only{display:block;}
  body.lang-en .skill-tags.en-only{display:block;}
  /* Appearance and display need different reaches here.
     The screen rule `.skill-tags span` styles every descendant, including the
     .sv-only/.en-only spans nested inside the language tags — so the pill
     reset has to reach them too, or they print as dark pills on white.
     But `display` must stay on direct children only: setting it on all
     descendants out-specifies .en-only{display:none} and prints both
     language variants side by side. */
  .skill-tags span{
    white-space:normal;
    background:none;border:none;padding:0;border-radius:0;
    font-size:8.4pt;color:#0e1b2b;
  }
  .skill-tags > span{display:inline;}
  .skill-tags > span + span::before{content:"  ·  ";color:#aeb6c1;}
  #misc .project-desc{font-size:8.4pt;color:#0e1b2b;}

  /* --- Education --- */
  .edu-item{padding:3.5pt 0;border-top:.5pt solid #e4e8ed;break-inside:avoid;}
  .edu-item:first-of-type{padding-top:0;border-top:none;}
  .edu-school{font-size:10.5pt;color:#0e1b2b;}
  .edu-period{font-size:7.6pt;color:#79828f;}
  .edu-degree{font-size:8.8pt;color:#0e1b2b !important;}
  .edu-item ul{margin-top:3pt;padding-left:11pt;font-size:8.4pt;color:#3d4b5c;}
  .edu-item li::marker{color:#ff5a1f;}

  /* --- Achievements --- */
  .ach-grid{gap:6pt 26pt;}
  .ach-grid > div{break-inside:avoid;}
  .ach-list li{
    padding:0.9pt 0;border-top:.5pt solid #eceff3;font-size:8.4pt;color:#3d4b5c;
    gap:0;break-inside:avoid;
  }
  .ach-list b{color:#0e1b2b;}
  /* On screen these read as aurora-intensity bars; on paper they flatten to
     identical grey blobs that carry no information, so they come off. */
  .kp{display:none;}

  /* --- Footer ---
     Contact already sits in the header, so the closing line stays a closing
     line rather than repeating it. */
  footer{
    display:block;padding:1.5pt 0 0;text-align:left;
    border-top:.5pt solid #d7dce2;break-inside:avoid;
  }
  .footer-inner{
    display:flex;align-items:flex-end;justify-content:space-between;gap:16pt;
  }
  /* Sits beside the QR, no taller than it, so the sheet keeps its length. */
  .signature{height:22pt;width:auto;margin:0 0 1pt;filter:none;opacity:1;}
  .footer-note{font-size:7.4pt;color:#79828f;}
  .footer-note a{color:#79828f;}
  #footer-contact, .footer-sep, #footer-contact-link, #footer-about{display:none;}

  /* --- Long variant ---
     A little air between roles and bullets on the two-page sheet, which has
     room for it since the projects moved to projects.html. */
  .timeline .job{padding:3.6pt 0;}
  .timeline .job li{margin-bottom:1pt;}

  /* --- Paper-to-web bridge --- */
  .print-qr{
    display:flex;align-items:center;gap:6pt;flex:none;
  }
  .print-qr svg{width:28pt;height:28pt;display:block;}
  .print-qr-cap{
    font-family:'JetBrains Mono',monospace;font-size:6.4pt;line-height:1.5;
    color:#79828f;text-align:right;
  }
}

/* ---------- Start page (index.html) ----------
   A hub rather than a document: contact, projects and the CV downloads.
   Borrows the CV's sky, cards and type; everything below is scoped to it. */
a.brand-home{text-decoration:none;}
a.brand-home:hover{color:var(--green);}
.hub-nav{
  font-family:'JetBrains Mono',monospace;font-size:12px;letter-spacing:.03em;
  color:var(--text-lo);text-decoration:none;padding:7px 6px;transition:color .2s ease;
}
.hub-nav:hover{color:var(--text-hi);}
@media (max-width:600px){ .hub-nav{display:none;} }

.hub-hero{padding-bottom:56px;}
.hub-cta{display:flex;flex-wrap:wrap;gap:10px;margin-top:30px;}
.hub-btn{
  display:inline-flex;align-items:center;gap:8px;
  border:1px solid var(--hairline-strong);border-radius:999px;
  background:var(--panel);color:var(--text-hi);text-decoration:none;
  font-family:'JetBrains Mono',monospace;font-size:13px;letter-spacing:.02em;
  padding:10px 18px;transition:border-color .2s ease, transform .15s ease;
}
.hub-btn:hover{border-color:var(--teal);transform:translateY(-1px);}
.hub-btn svg{width:15px;height:15px;stroke:var(--teal);fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;flex:none;}
.hub-btn-primary{background:linear-gradient(135deg,var(--green),var(--teal));color:#04120c;border-color:transparent;font-weight:500;}
.hub-btn-primary svg{stroke:#04120c;}

.section-lead{margin:-14px 0 26px;max-width:640px;color:var(--text-lo);font-size:15px;}

/* Contact cards */
.contact-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;}
@media (max-width:760px){ .contact-grid{grid-template-columns:1fr;} }
.contact-card{
  display:grid;grid-template-columns:auto 1fr;grid-template-rows:auto auto;
  column-gap:14px;align-items:center;text-decoration:none;min-width:0;
  background:var(--panel);border:1px solid var(--hairline);border-radius:14px;
  padding:16px 18px;transition:border-color .2s ease, transform .2s ease;
}
.contact-card:hover{border-color:var(--teal);transform:translateY(-2px);}
.contact-card svg{
  grid-row:1 / span 2;width:20px;height:20px;
  stroke:var(--green);fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;
}
.contact-label{
  font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--text-faint);
}
.contact-value{
  color:var(--text-hi);font-size:15px;font-weight:500;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.contact-extra{display:flex;flex-wrap:wrap;align-items:center;gap:10px 18px;margin-top:18px;}
.contact-note{font-size:13.5px;color:var(--text-lo);}

/* CV downloads */
.cv-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:14px;}
@media (max-width:760px){ .cv-grid{grid-template-columns:1fr;} }
.cv-card{
  display:flex;flex-direction:column;gap:10px;
  background:var(--panel);border:1px solid var(--hairline);border-radius:14px;padding:20px;
}
a.cv-card{text-decoration:none;transition:border-color .2s ease, transform .2s ease;}
a.cv-card:hover{border-color:var(--teal);transform:translateY(-2px);}
a.cv-card:hover .project-open-link{gap:10px;}
.cv-card .project-open-link{margin-top:auto;}
.cv-card-head{display:flex;justify-content:space-between;align-items:baseline;gap:10px;}
.cv-card-title{font-size:17px;font-weight:600;color:var(--text-hi);}
.cv-card-meta{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-faint);white-space:nowrap;}
.cv-card-desc{margin:0;color:var(--text-lo);font-size:14px;}
.cv-card-links{display:flex;flex-wrap:wrap;gap:8px;margin-top:auto;}
.cv-dl{
  font-family:'JetBrains Mono',monospace;font-size:12px;text-decoration:none;
  color:var(--text-hi);border:1px solid var(--hairline-strong);border-radius:999px;
  padding:6px 13px;transition:border-color .2s ease,color .2s ease;
}
.cv-dl:hover{border-color:var(--green);color:var(--green);}

/* At a glance, under the downloads */
.facts{margin:34px 0 0;display:grid;gap:10px;}
.facts > div{display:grid;grid-template-columns:110px 1fr;gap:16px;align-items:baseline;}
.facts dt{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--text-faint);}
.facts dd{margin:0;color:var(--text-lo);font-size:14.5px;}
@media (max-width:520px){ .facts > div{grid-template-columns:1fr;gap:2px;} }

/* The game card opens a dialog rather than a link, so its call to action
   is a real button dressed as the other cards' "open" links. */
.quiz-launch{appearance:none;border:none;background:none;padding:0;cursor:pointer;font:inherit;font-family:'JetBrains Mono',monospace;font-size:12px;}
.quiz-launch:hover{gap:10px;}

/* ---------- Project cards: the method folds away ----------
   The card says what the thing does in plain words; the how sits one
   click down for whoever wants it. */
.project-more{margin-top:12px;}
.project-more summary{
  display:inline-flex;align-items:center;gap:6px;cursor:pointer;list-style:none;
  font-family:'JetBrains Mono',monospace;font-size:11.5px;letter-spacing:.03em;color:var(--teal);
}
.project-more summary::-webkit-details-marker{display:none;}
.project-more summary::before{content:'+';display:inline-block;width:10px;transition:transform .2s ease;}
.project-more[open] summary::before{content:'−';}
.project-more summary:hover{color:var(--green);}
.project-more summary:focus-visible{outline:2px solid var(--green);outline-offset:3px;border-radius:4px;}
.project-more .project-note{margin-top:8px;}
a.project-open-link{text-decoration:none;}
.project-links{display:flex;flex-wrap:wrap;column-gap:20px;}
a.project-open-link:hover{gap:10px;}

/* ---------- Projects on the start page ----------
   Three cards get the room, the rest get a line or two. Eight equal cards
   asked the reader to do the ranking, and nobody does. */
.projects-sub{
  margin:38px 0 14px;font-family:'JetBrains Mono',monospace;font-weight:500;font-size:11.5px;
  letter-spacing:.08em;text-transform:uppercase;color:var(--text-faint);
}
.projects-more{gap:12px;}
.project-card.is-compact{padding:16px 18px;}
.project-card.is-compact .project-title{font-size:15.5px;margin-bottom:8px;}
.project-card.is-compact .tag-row{margin-bottom:8px;}
.project-card.is-compact .project-desc{font-size:13.5px;margin:0;}
.project-card.is-compact .project-open-link{margin-top:10px;}
.project-card.is-compact .project-more{margin-top:8px;}
/* The one thing a downloader must not skip, so it stays out of the fold. */
.project-caveat{
  margin:10px 0 0;padding:8px 11px;border-left:2px solid var(--pink);border-radius:0 6px 6px 0;
  background:var(--panel-2);font-size:12.5px;line-height:1.5;color:var(--text-lo);
}
.project-caveat strong{color:var(--text-hi);font-weight:600;}
.project-note code{font-size:inherit;color:var(--text-lo);}

@media print{ .project-more summary{display:none !important;} }
