/* ==================================================================
   REFINEMENTS
   These come last on purpose: later rules win, so this block adjusts
   the earlier ones without touching them.
   ================================================================== */


/* ==================================================================
   PALETTE — paper and ink
   Away from the black-plus-cyan-gradient look that every generated
   portfolio wears. This is an engineer's notebook: bone paper, dense
   ink, and one burnt red that does all the pointing. No gradients
   between two hues; where a gradient is needed it stays within the
   same red so it reads as ink density, not decoration.

   Light is now the default. The switch turns the lamp off and gives
   you the same page at night: warm charcoal, never blue-black.
   ================================================================== */

html[data-theme="light"] {
  --bg:        #F1EEE7;   /* bone paper */
  --bg2:       #FBFAF6;   /* card stock */
  --surface:   rgba(20,17,13,.032);
  --surface-2: rgba(20,17,13,.06);
  --line:      rgba(20,17,13,.15);
  --text:      #14110D;   /* ink */
  --muted:     #6A6357;   /* pencil */
  --accent:    #B4361F;   /* burnt red */
  --accent2:   #8A2716;   /* deeper red, same family */
  --net-line:  20,17,13;
  --shadow:    0 18px 44px rgba(20,17,13,.10);
}

html[data-theme="dark"] {
  --bg:        #141210;   /* warm charcoal, not blue-black */
  --bg2:       #1C1916;
  --surface:   rgba(241,238,231,.045);
  --surface-2: rgba(241,238,231,.075);
  --line:      rgba(241,238,231,.14);
  --text:      #F1EEE7;
  --muted:     #9C9488;
  --accent:    #E0553B;   /* the same red, lifted for dark */
  --accent2:   #C2452E;
  --net-line:  241,238,231;
  --shadow:    0 26px 64px rgba(0,0,0,.5);
}

/* The name was a two-hue gradient; make it solid ink with the red
   reserved for the words that matter. */
.hero-name {
  background: none;
  color: var(--text);
  animation: none;
  -webkit-text-fill-color: currentColor;
}

/* Buttons and chips: one flat red, no rainbow. */
.btn-primary,
.orb-core,
.chat-avatar,
.msg.me .bubble,
.chat-form button,
.modal-lock span,
.badge {
  background: var(--accent) !important;
  color: #FBFAF6 !important;
}
.msg .go { background: var(--accent) !important; color: #FBFAF6 !important; }
::-webkit-scrollbar-thumb { background: var(--accent); }
.bar i { background: var(--accent); box-shadow: none; }
#scrollBar { background: var(--accent); box-shadow: none; }

/* The aurora blobs were the most generated-looking thing on the page.
   Keep a whisper of warmth instead of two coloured lamps. */
.aurora span:nth-child(1) { background: var(--accent); opacity: .07; }
.aurora span:nth-child(2) { background: #8A7B5F; opacity: .07; }
.aurora span:nth-child(3) { display: none; }
html[data-theme="dark"] .aurora span { opacity: .10; }

/* Paper wants a little more grain and none of the glow. */
html[data-theme="light"] .grain { opacity: .07; }
.sig-live path, .sig-live-dot { filter: none; }
.photo-signature { filter: none; }
.switch-glow { box-shadow: 0 0 6px var(--accent); }

/* ---- edge flicker -------------------------------------------------
   The grain layer was exactly viewport-sized and shifted by ±4% four
   times a second, so every step uncovered a dark strip along the
   edges. Oversizing it means the movement can never reach an edge. */
.grain { inset: -15%; }

/* ---- the switch actually flips ------------------------------------
   rotateX does nothing without perspective on the parent, which is why
   it felt like a button. Perspective plus real vertical travel gives it
   the throw of a wall switch. */
.switch-plate { perspective: 260px; perspective-origin: 50% 50%; }
.switch-rocker {
  transform-origin: 50% 50%;
  transition: transform .34s cubic-bezier(.34,1.4,.5,1), box-shadow .34s var(--ease), background .4s;
}
html[data-theme="dark"] .switch-rocker {
  transform: translateY(7px) rotateX(-34deg);
  box-shadow: 0 -3px 0 rgba(0,0,0,.3), inset 0 -14px 16px rgba(0,0,0,.3);
}
html[data-theme="light"] .switch-rocker {
  transform: translateY(-7px) rotateX(34deg);
  box-shadow: 0 4px 0 rgba(0,0,0,.16), inset 0 14px 16px rgba(0,0,0,.12);
}
/* the indicator lamp follows the rocker */
html[data-theme="dark"] .switch-glow { bottom: 4px; }
html[data-theme="light"] .switch-glow { bottom: 26px; }
.switch-glow { transition: bottom .34s cubic-bezier(.34,1.4,.5,1); }

/* ---- a quieter, more precise cursor -------------------------------
   Difference blending keeps it legible on any background without the
   glow, and the ring tracks with a softer lag. */
#cursorDot {
  width: 5px; height: 5px; margin: -2.5px 0 0 -2.5px;
  background: #fff; box-shadow: none; mix-blend-mode: difference;
  transition: opacity .3s, transform .18s var(--ease);
}
#cursorRing {
  width: 30px; height: 30px; margin: -15px 0 0 -15px;
  border: 1px solid rgba(255,255,255,.5); background: transparent;
  mix-blend-mode: difference;
  transition: opacity .3s, width .35s var(--ease), height .35s var(--ease), margin .35s var(--ease), border-color .35s;
}
#cursorRing.hot {
  width: 48px; height: 48px; margin: -24px 0 0 -24px;
  background: transparent; border-color: rgba(255,255,255,.9);
}
#cursorLabel {
  font-size: 8.5px; letter-spacing: 1.6px; color: #fff; mix-blend-mode: difference;
}
#cursorRing.pressed { width: 22px; height: 22px; margin: -11px 0 0 -11px; }

/* ---- sections arrive from outside the page ------------------------
   The cards start well beyond the screen edge and set back in depth,
   then fly in and settle. html gets overflow-x: clip so nothing can
   create a horizontal scrollbar while it is out there.

   Note: the trigger cannot use IntersectionObserver here, because a
   card parked outside the viewport never intersects and would stay
   invisible for ever. extras.js measures layout position instead. */
html { overflow-x: clip; }

.reveal-x {
  opacity: 0;
  transform:
    perspective(1400px)
    translate3d(var(--from, 70vw), var(--fromY, 0px), var(--fromZ, -420px))
    rotateY(var(--rot, -14deg))
    scale(var(--fromScale, .92));
  transition:
    opacity .7s cubic-bezier(.22,1,.36,1),
    transform 1.05s cubic-bezier(.22,1,.36,1);
  will-change: transform, opacity;
}
.reveal-x.in {
  opacity: 1;
  transform: perspective(1400px) translate3d(0, 0, 0) rotateY(0deg) scale(1);
}

/* ---- keyboard hint ------------------------------------------------ */
.kbd-hint {
  position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%) translateY(14px);
  z-index: 8000; display: flex; align-items: center; gap: 9px;
  padding: 9px 16px; border-radius: 999px;
  background: color-mix(in srgb, var(--bg2) 88%, transparent);
  border: 1px solid var(--line); backdrop-filter: blur(12px);
  font-size: 12px; color: var(--muted);
  opacity: 0; pointer-events: none;
  transition: opacity .5s var(--ease), transform .5s var(--ease);
}
.kbd-hint.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.kbd-hint kbd {
  font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--text);
  border: 1px solid var(--line); border-bottom-width: 2px; border-radius: 6px;
  padding: 2px 7px; background: var(--surface);
}

@media (prefers-reduced-motion: reduce) {
  .reveal-x { opacity: 1; transform: none; }
  .grain { animation: none; }
}


/* ==================================================================
   ================  N O T E B O O K   M O D E  =====================

   Two modes, not two colour schemes. "Modern" is the site as it was.
   "Notebook" turns the whole thing into an engineer's ruled notebook:
   real rules, a red margin, punched holes, handwriting, cards that sit
   on the page like taped-in cuttings, and a page that turns as you
   move between sections.

   The light/dark switch still works inside each mode, so there are
   four states in total: modern day, modern night, paper day, paper
   night (a notebook read under a desk lamp).

   Everything below is scoped to html[data-mode="paper"] so the modern
   mode cannot be touched by accident.
   ================================================================== */

/* ---- the mode selector, top of the page ------------------------- */
.mode-tabs {
  position: relative; display: inline-flex; align-items: center;
  padding: 3px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface);
  margin-right: 10px;
}
.mode-tab {
  position: relative; z-index: 2;
  font-family: 'JetBrains Mono', monospace;
  font-size: 10.5px; letter-spacing: 1.6px; text-transform: uppercase;
  padding: 7px 13px; border-radius: 999px; color: var(--muted);
  transition: color .35s var(--ease);
  white-space: nowrap;
}
.mode-tab.is-on { color: #FBFAF6; }
.mode-thumb {
  position: absolute; z-index: 1; top: 3px; bottom: 3px;
  border-radius: 999px; background: var(--accent);
  transition: left .45s cubic-bezier(.22,1,.36,1), width .45s cubic-bezier(.22,1,.36,1);
}
@media (max-width: 700px) {
  .mode-tabs { margin-right: 6px; }
  .mode-tab { padding: 6px 9px; font-size: 9px; letter-spacing: 1px; }
}

/* ==================================================================
   PAPER PALETTE
   Ink on paper by day; the same notebook under a warm lamp by night.
   ================================================================== */
html[data-mode="paper"][data-theme="light"] {
  --bg:        #FBF8EF;   /* the sheet */
  --bg2:       #FFFDF6;   /* a cutting laid on top of it */
  --surface:   rgba(26,39,64,.035);
  --surface-2: rgba(26,39,64,.065);
  --line:      rgba(26,39,64,.18);
  --text:      #182338;   /* blue-black ink */
  --muted:     #5A6A85;   /* pencil */
  --accent:    #C0392B;   /* the red pen */
  --accent2:   #96271C;
  --shadow:    0 10px 26px rgba(26,39,64,.12);
  --rule:      rgba(60,96,160,.28);      /* the printed lines */
  --margin-ln: rgba(192,57,43,.42);      /* the red margin */
  --hole:      rgba(26,39,64,.13);
}
html[data-mode="paper"][data-theme="dark"] {
  --bg:        #1A1815;
  --bg2:       #232019;
  --surface:   rgba(240,234,220,.05);
  --surface-2: rgba(240,234,220,.085);
  --line:      rgba(240,234,220,.16);
  --text:      #F0EADC;
  --muted:     #A79C88;
  --accent:    #E8674F;
  --accent2:   #C64832;
  --shadow:    0 16px 40px rgba(0,0,0,.55);
  --rule:      rgba(240,234,220,.13);
  --margin-ln: rgba(232,103,79,.36);
  --hole:      rgba(0,0,0,.4);
}

/* ---- the sheet itself -------------------------------------------
   An absolutely positioned layer inside a relative body is sized to
   the body box, so it covers the whole document and scrolls with it —
   which is what a real page does. A fixed layer would slide under the
   text and give the trick away.                                     */
html[data-mode="paper"] body { position: relative; }
html[data-mode="paper"] body::before {
  content: '';
  position: absolute; inset: 0; z-index: -3; pointer-events: none;
  --m: 78px;                                   /* margin position */
  background-image:
    /* punched holes down the margin */
    radial-gradient(circle at 30px 120px, var(--hole) 0 6.5px, transparent 7.5px),
    /* the red vertical margin */
    linear-gradient(to right, transparent 0 var(--m),
                    var(--margin-ln) var(--m) calc(var(--m) + 1.5px),
                    transparent calc(var(--m) + 1.5px)),
    /* the ruled lines */
    repeating-linear-gradient(to bottom,
                    transparent 0 33px, var(--rule) 33px 34px);
  background-size: 100% 240px, 100% 100%, 100% 100%;
  background-repeat: repeat-y, no-repeat, repeat;
  opacity: .95;
}
@media (max-width: 760px) {
  html[data-mode="paper"] body::before { --m: 34px; background-image:
    radial-gradient(circle at 14px 120px, var(--hole) 0 5px, transparent 6px),
    linear-gradient(to right, transparent 0 var(--m), var(--margin-ln) var(--m) calc(var(--m) + 1.2px), transparent calc(var(--m) + 1.2px)),
    repeating-linear-gradient(to bottom, transparent 0 33px, var(--rule) 33px 34px); }
}

/* the electronic background has no business on paper */
html[data-mode="paper"] #netCanvas,
html[data-mode="paper"] .aurora,
html[data-mode="paper"] .photo-scan,
html[data-mode="paper"] .photo-ring,
html[data-mode="paper"] .photo-glare,
html[data-mode="paper"] .btn-shine,
html[data-mode="paper"] .orb-ring { display: none !important; }
html[data-mode="paper"] .grain { opacity: .13; mix-blend-mode: multiply; }
html[data-mode="paper"][data-theme="dark"] .grain { mix-blend-mode: overlay; opacity: .09; }

/* ---- handwriting -------------------------------------------------
   Headings and margin notes are handwritten; body copy stays in the
   clean face, because a whole CV in script is unreadable and this is
   still a document someone has to hire you from.                    */
html[data-mode="paper"] .hero-name,
html[data-mode="paper"] .section-title,
html[data-mode="paper"] .stats b,
html[data-mode="paper"] .modal-card h3 {
  font-family: 'Caveat', 'Sora', cursive;
  font-weight: 700; letter-spacing: .5px; text-transform: none;
  color: var(--text);
}
html[data-mode="paper"] .hero-name { font-size: clamp(3rem, 8.5vw, 6.4rem); line-height: .95; }
html[data-mode="paper"] .section-title { font-size: clamp(2.2rem, 5.5vw, 3.6rem); }
html[data-mode="paper"] .stats b { font-size: clamp(2rem, 4vw, 3rem); color: var(--accent); }

html[data-mode="paper"] .section-kicker,
html[data-mode="paper"] .kicker,
html[data-mode="paper"] .photo-caption,
html[data-mode="paper"] .tl-period,
html[data-mode="paper"] .project-num,
html[data-mode="paper"] .contact-note {
  font-family: 'Kalam', 'JetBrains Mono', cursive;
  text-transform: none; letter-spacing: .2px;
}
html[data-mode="paper"] .section-kicker,
html[data-mode="paper"] .project-num { color: var(--accent); font-size: 15px; }
html[data-mode="paper"] .hero-tagline { font-family: 'Kalam', cursive; text-transform: none; font-size: 15px; letter-spacing: 0; }

/* ---- things stop being glass and start being paper --------------- */
html[data-mode="paper"] .card,
html[data-mode="paper"] .project,
html[data-mode="paper"] .tl-item,
html[data-mode="paper"] .edu-card,
html[data-mode="paper"] .skill-card,
html[data-mode="paper"] .calendar-box {
  background: var(--bg2);
  border: 1px solid var(--line);
  border-radius: 3px;                       /* paper is cut, not rounded */
  backdrop-filter: none;
  box-shadow: 0 1px 0 var(--line), 0 10px 22px -14px rgba(26,39,64,.5);
}
html[data-mode="paper"][data-theme="dark"] .card,
html[data-mode="paper"][data-theme="dark"] .project,
html[data-mode="paper"][data-theme="dark"] .tl-item,
html[data-mode="paper"][data-theme="dark"] .edu-card,
html[data-mode="paper"][data-theme="dark"] .skill-card {
  box-shadow: 0 1px 0 rgba(0,0,0,.4), 0 14px 30px -16px #000;
}

/* Laid on the page by hand, so never perfectly square.

   This uses the standalone `rotate` property rather than a transform.
   Three other things on this page write to `transform` on these very
   elements — the mouse tilt (inline), the fly-in reveal, and the
   parallax — and an inline transform wins outright. `rotate` is its
   own property, so it composes with all of them instead of fighting
   them. The crookedness survives a hover; the card still flies in. */
html[data-mode="paper"] .project:nth-child(odd)     { rotate: -.5deg; }
html[data-mode="paper"] .project:nth-child(even)    { rotate: .45deg; }
html[data-mode="paper"] .skill-card:nth-child(odd)  { rotate: .4deg; }
html[data-mode="paper"] .skill-card:nth-child(even) { rotate: -.35deg; }
html[data-mode="paper"] .edu-card:nth-child(even)   { rotate: .5deg; }
html[data-mode="paper"] .tl-item:nth-child(odd)     { rotate: -.3deg; }

/* a strip of tape at the top of every cutting */
html[data-mode="paper"] .project,
html[data-mode="paper"] .edu-card { position: relative; }
html[data-mode="paper"] .project::before,
html[data-mode="paper"] .edu-card::before {
  content: ''; position: absolute; z-index: 4;
  top: -9px; left: 50%; width: 78px; height: 20px;
  transform: translateX(-50%) rotate(-2.2deg);
  background: rgba(214,199,142,.42);
  border-left: 1px dashed rgba(140,125,80,.35);
  border-right: 1px dashed rgba(140,125,80,.35);
  box-shadow: 0 1px 3px rgba(26,39,64,.14);
}
html[data-mode="paper"] .edu-card::before { width: 56px; height: 16px; transform: translateX(-50%) rotate(2deg); }

/* photographs get the same treatment: printed, slightly faded, taped */
html[data-mode="paper"] .project-media img {
  filter: saturate(.72) contrast(1.04) sepia(.12);
  border-bottom: 1px solid var(--line);
}
html[data-mode="paper"] .photo-frame {
  border-radius: 2px; border: 1px solid var(--line);
  background: var(--bg2); padding: 10px 10px 42px;   /* a polaroid lip */
  box-shadow: 0 14px 30px -16px rgba(26,39,64,.6);
  rotate: -1.4deg;
}
html[data-mode="paper"] .photo-frame img { border-radius: 0; }

/* the skill bars become highlighter strokes */
html[data-mode="paper"] .bar { background: transparent; border-bottom: 1px dotted var(--line); border-radius: 0; }
html[data-mode="paper"] .bar i {
  background: var(--accent); opacity: .5; border-radius: 0;
  mix-blend-mode: multiply;
}
html[data-mode="paper"][data-theme="dark"] .bar i { mix-blend-mode: screen; opacity: .6; }

/* tags read as pencil circles */
html[data-mode="paper"] .tag,
html[data-mode="paper"] .tl-tools {
  border: 1px solid var(--muted); border-radius: 14px 13px 15px 12px / 12px 15px 13px 14px;
  font-family: 'Kalam', cursive; letter-spacing: 0; text-transform: none;
  background: transparent;
}

/* buttons look drawn round rather than filled, except the main one */
html[data-mode="paper"] .btn { border-radius: 22px 20px 24px 19px / 19px 24px 20px 22px; }
html[data-mode="paper"] .btn-ghost { border: 1.5px solid var(--accent); color: var(--accent); }

/* a handwritten page number in the corner of every page */
html[data-mode="paper"] main { counter-reset: pg; }
html[data-mode="paper"] .section { position: relative; counter-increment: pg; }
html[data-mode="paper"] .section::after {
  content: '— ' counter(pg) ' —';
  position: absolute; right: 4px; bottom: 6px;
  font-family: 'Kalam', cursive; font-size: 14px; color: var(--muted); opacity: .65;
  pointer-events: none;
}

/* the header sits on the page, not floating in glass */
html[data-mode="paper"] #nav { backdrop-filter: blur(2px); }
html[data-mode="paper"] .socials li a { border-radius: 12px 11px 13px 10px / 10px 13px 11px 12px; }

/* ==================================================================
   THE PAGE TURN
   The leaf is a full sheet hinged on the left edge. It starts flat
   over the viewport and swings away, so what you are looking at is
   the page you just left going over. Backwards, it swings in.
   Pointer events stay off throughout: this is scenery, not a gate.
   ================================================================== */
.flip-stage {
  /* 480 puts the turning sheet above the page content (z 2) and the
     background layers (0 and 1), but below the fixed header (500),
     the assistant orb (9000) and the cursor. The header is the desk,
     not the page: it should stay still while the paper moves. */
  position: fixed; inset: 0; z-index: 480;
  perspective: 2400px; perspective-origin: 0% 50%;
  pointer-events: none; display: none;
}
.flip-stage.on { display: block; }
.flip-leaf {
  position: absolute; inset: 0;
  transform-origin: left center;
  transform-style: preserve-3d;
  background: var(--bg);
  background-image:
    linear-gradient(to right, transparent 0 78px, var(--margin-ln) 78px 79.5px, transparent 79.5px),
    repeating-linear-gradient(to bottom, transparent 0 33px, var(--rule) 33px 34px);
  box-shadow: 8px 0 40px rgba(0,0,0,.28);
  backface-visibility: hidden;
}
/* the shading that makes it read as a curving sheet rather than a slab */
.flip-shade {
  position: absolute; inset: 0;
  background: linear-gradient(to right, rgba(0,0,0,.30), rgba(0,0,0,0) 34%, rgba(0,0,0,.10));
  opacity: 0;
}
.flip-stage.fwd .flip-leaf { animation: leafFwd .78s cubic-bezier(.55,.06,.32,1) forwards; }
.flip-stage.bwd .flip-leaf { animation: leafBwd .78s cubic-bezier(.55,.06,.32,1) forwards; }
.flip-stage.on  .flip-shade { animation: leafShade .78s ease-out forwards; }

@keyframes leafFwd {
  from { transform: rotateY(0deg); }
  to   { transform: rotateY(-172deg); }
}
@keyframes leafBwd {
  from { transform: rotateY(-172deg); }
  to   { transform: rotateY(0deg); }
}
@keyframes leafShade {
  0%   { opacity: 0; }
  35%  { opacity: 1; }
  100% { opacity: 0; }
}

/* Someone who has asked for less motion gets the mode without the
   theatre: no turning page, no tilted cuttings. */
@media (prefers-reduced-motion: reduce) {
  .flip-stage { display: none !important; }
  html[data-mode="paper"] .project,
  html[data-mode="paper"] .skill-card,
  html[data-mode="paper"] .edu-card,
  html[data-mode="paper"] .photo-frame { rotate: 0deg !important; }
}

/* The selector inside the phone menu: full width, bigger targets. */
.mode-tabs.in-menu {
  display: flex; width: 100%; margin: 18px 0 0; padding: 4px;
  background: var(--surface-2);
}
.mode-tabs.in-menu .mode-tab { flex: 1; text-align: center; padding: 12px 10px; font-size: 11px; }


/* ---- notebook mode: the remaining glass surfaces ------------------
   The chat panel and the login card were still floating slabs of
   frosted glass on a sheet of paper, which broke the illusion at the
   two moments a visitor looks hardest. Both become paper objects. */

/* the assistant is a sticky note pressed onto the corner */
html[data-mode="paper"] .chat {
  background: #FFF6C9;
  border: 1px solid rgba(26,39,64,.16);
  border-radius: 2px;
  backdrop-filter: none;
  rotate: -.8deg;
  box-shadow: 0 18px 34px -18px rgba(26,39,64,.65);
}
html[data-mode="paper"][data-theme="dark"] .chat { background: #3A3320; }
html[data-mode="paper"] .chat::before {   /* the strip holding it down */
  content: ''; position: absolute; top: -10px; left: 50%;
  width: 92px; height: 22px; transform: translateX(-50%) rotate(1.6deg);
  background: rgba(214,199,142,.5);
  box-shadow: 0 1px 3px rgba(26,39,64,.16);
}
html[data-mode="paper"] .chat-head { border-bottom: 1px dashed rgba(26,39,64,.22); }
html[data-mode="paper"] .bubble { border-radius: 12px 11px 13px 10px / 10px 13px 11px 12px; }
html[data-mode="paper"] .chat-log::-webkit-scrollbar-thumb { background: var(--muted); }

/* the login card is an index card */
html[data-mode="paper"] .modal-card {
  background: var(--bg2); backdrop-filter: none;
  border-radius: 3px; border: 1px solid var(--line);
  background-image: repeating-linear-gradient(to bottom, transparent 0 33px, var(--rule) 33px 34px);
}

/* a pen stroke under every section title, drawn slightly off-square */
html[data-mode="paper"] .section-title { position: relative; display: inline-block; }
html[data-mode="paper"] .section-title::after {
  content: ''; position: absolute; left: -2px; right: -8px; bottom: -2px;
  height: 3px; background: var(--accent); opacity: .5;
  border-radius: 3px 8px 2px 6px / 4px 2px 6px 3px;
  rotate: -.7deg;
}


/* ==================================================================
   PRINTING
   A portfolio that a recruiter tries to print should not come out as
   a black rectangle. Everything that exists to move is removed, the
   fly-in cards are forced to their settled state (otherwise they
   print blank, parked off the page), and links are given their real
   destination in the margin, because paper cannot be clicked.
   ================================================================== */
@media print {
  #netCanvas, .aurora, .grain, #cursorDot, #cursorRing, #scrollBar,
  .orb, .chat, .flip-stage, .mode-tabs, .switch, .sound-btn,
  .nav-burger, .scroll-hint, .kbd-hint, .modal, #site-loader,
  .photo-ring, .photo-scan, .photo-glare, .btn-shine,
  .project::before, .edu-card::before { display: none !important; }

  html, body {
    background: #fff !important; color: #000 !important;
    overflow: visible !important;
  }
  body::before { display: none !important; }

  /* the animation systems all park elements out of frame; unpark them */
  .reveal, .reveal-x, .reveal-x:not(.in) {
    opacity: 1 !important;
    transform: none !important;
    rotate: 0deg !important;
    transition: none !important;
  }

  #nav { position: static !important; border: 0 !important; backdrop-filter: none !important; }
  .section { padding: 14mm 0 6mm !important; page-break-inside: auto; }
  .section::after { display: none !important; }
  .card, .project, .tl-item, .edu-card, .skill-card {
    background: #fff !important; box-shadow: none !important;
    border: 1px solid #bbb !important; page-break-inside: avoid;
  }
  .hero { min-height: auto !important; padding-top: 6mm !important; }
  .hero-name { font-size: 26pt !important; color: #000 !important; }
  .section-title { font-size: 17pt !important; color: #000 !important; page-break-after: avoid; }
  .section-title::after { display: none !important; }
  a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 8pt; color: #555; word-break: break-all;
  }
  /* icons and buttons carry no readable text, so an appended URL just
     produces a wall of characters with nothing to attach it to */
  a[href^="#"]::after, .btn::after,
  .socials a::after, .nav-logo::after { content: '' !important; }
  .project-media img { max-height: 55mm; object-fit: cover; }

  @page { margin: 14mm; }
}


/* ---- keyboard focus ----------------------------------------------
   The custom cursor hides the pointer, and until now nothing showed
   where the keyboard was either. :focus-visible only fires for
   keyboard users, so mouse users never see a ring. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}
.skip-link {
  position: fixed; top: 10px; left: 50%; transform: translate(-50%, -160%);
  z-index: 100002; padding: 11px 20px; border-radius: 999px;
  background: var(--accent); color: #FBFAF6; font-size: 13px;
  transition: transform .3s var(--ease);
}
.skip-link:focus { transform: translate(-50%, 0); }


/* Between the burger breakpoint and a comfortable desktop the header
   carries the logo, six links, the selector, sound, the switch and the
   CV button at once. Tighten everything rather than let it wrap. */
@media (min-width: 981px) and (max-width: 1180px) {
  .nav-actions { gap: 9px; }
  .mode-tabs { margin-right: 4px; }
  .mode-tab { padding: 6px 10px; font-size: 9.5px; letter-spacing: 1.1px; }
  .nav-links { gap: 4px; }
  .nav-links a { font-size: 13px; }
}


/* ---- two things the live page showed and the source did not ------

   1. The tape strips were being drawn and then clipped out of
      existence: every one of these cards carries overflow:hidden, so
      a strip hanging 9px above the top edge simply never appeared.
      Measured on the live site — the pseudo-element was there, 20px
      tall, at top:-9px, and invisible. The card has to stop clipping;
      the image inside it takes over that job.

   2. .card::before is the frosted glow from the modern theme, and it
      was still running on paper. It is excluded rather than replaced,
      because on .edu-card that same pseudo-element is now the tape.
   ================================================================== */
html[data-mode="paper"] .project,
html[data-mode="paper"] .edu-card { overflow: visible; }

html[data-mode="paper"] .project-media { overflow: hidden; border-radius: 2px; }

html[data-mode="paper"] .card:not(.edu-card)::before,
html[data-mode="paper"] .glow::after { display: none !important; }


/* ==================================================================
   ==================  N E U R A L   M O D E  =======================

   The third mode. A network trains, becomes a robot, greets you, and
   then stays behind the site for as long as you are reading it.

   Two custom properties carry the colours into the canvas, because a
   canvas cannot read a stylesheet: --neuro-node for the live parts
   (eyes, core, signals) and --neuro-link for the structure.
   ================================================================== */

html[data-mode="neural"][data-theme="dark"] {
  --bg:        #0E0D0C;
  --bg2:       #17140F;
  --surface:   rgba(241,238,231,.05);
  --surface-2: rgba(241,238,231,.08);
  --line:      rgba(241,238,231,.15);
  --text:      #F1EEE7;
  --muted:     #98907F;
  --accent:    #E0553B;
  --accent2:   #C2452E;
  --shadow:    0 26px 64px rgba(0,0,0,.6);
  --neuro-node: 224,85,59;
  --neuro-link: 241,238,231;
}
html[data-mode="neural"][data-theme="light"] {
  --bg:        #F1EEE7;
  --bg2:       #FBFAF6;
  --text:      #14110D;
  --muted:     #6A6357;
  --accent:    #B4361F;
  --accent2:   #8A2716;
  --neuro-node: 180,54,31;
  --neuro-link: 20,17,13;
}

/* the old dot-field would be a second network arguing with the robot */
html[data-mode="neural"] #netCanvas { display: none !important; }

/* ---- the stage --------------------------------------------------- */
.neuro {
  position: fixed; inset: 0; z-index: 9500;
  background: var(--bg);
  transition: background .6s var(--ease);
}
.neuro-canvas { position: absolute; inset: 0; display: block; }

/* Once the intro is over the same canvas keeps running, but it stops
   being a screen and becomes wallpaper: behind everything, dimmed,
   and deaf to the mouse so it can never swallow a click. */
.neuro.done {
  z-index: 0; background: transparent; pointer-events: none;
}
.neuro.done .neuro-ui { opacity: 0; pointer-events: none; }
.neuro.done .neuro-canvas { opacity: .5; transition: opacity 1.2s var(--ease); }
html[data-theme="light"] .neuro.done .neuro-canvas { opacity: .38; }

/* while the scene is playing the site underneath is held still */
body.neuro-intro { overflow: hidden; }
body.neuro-intro main,
body.neuro-intro #nav,
body.neuro-intro #footer,
body.neuro-intro .orb { opacity: 0; pointer-events: none; }
body.neuro-bg main,
body.neuro-bg #nav,
body.neuro-bg #footer,
body.neuro-bg .orb { animation: neuroIn 1s var(--ease) both; }
@keyframes neuroIn { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }

/* ---- the read-out ------------------------------------------------ */
.neuro-ui {
  position: absolute; inset: 0; display: grid;
  align-content: center; justify-items: center; gap: 22px;
  padding: 30px; text-align: center;
  transition: opacity .7s var(--ease);
}
.neuro-hud {
  position: absolute; top: 6vh; left: 50%; transform: translateX(-50%);
  display: grid; gap: 5px; min-width: 216px;
  font-family: 'JetBrains Mono', monospace; font-size: 11.5px; letter-spacing: 1.4px;
  color: var(--muted);
  transition: opacity .6s var(--ease), transform .6s var(--ease);
}
.neuro-hud.out { opacity: 0; transform: translateX(-50%) translateY(-12px); }
.nh-row { display: flex; justify-content: space-between; gap: 20px; }
.nh-row b { font-weight: 400; text-transform: uppercase; opacity: .7; }
.nh-row i { font-style: normal; color: var(--text); }
.nh-bar { display: block; height: 2px; background: var(--surface-2); margin-top: 4px; }
.nh-bar u { display: block; height: 100%; width: 0; background: var(--accent); transition: width .2s linear; }

/* ---- what it says ------------------------------------------------ */
.neuro-say {
  margin-top: 34vh; max-width: 720px; min-height: 3.2em;
  opacity: 0; transition: opacity .6s var(--ease);
}
.neuro-say.show { opacity: 1; }
.neuro-say b {
  display: block; font-weight: 300;
  font-size: clamp(1.3rem, 3.4vw, 2.3rem); letter-spacing: .5px; color: var(--text);
}
.neuro-say span {
  display: block; margin-top: 12px;
  font-size: clamp(.85rem, 1.6vw, 1rem); color: var(--muted); line-height: 1.6;
}

/* ---- the way in -------------------------------------------------- */
.neuro-go {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 13px 24px; border-radius: 999px;
  border: 1px solid var(--line); color: var(--text);
  font-family: 'JetBrains Mono', monospace; font-size: 11px;
  letter-spacing: 2.4px; text-transform: uppercase;
  opacity: 0; transform: translateY(12px);
  transition: opacity .6s var(--ease), transform .6s var(--ease), border-color .3s, background .3s;
}
.neuro-go.show { opacity: 1; transform: none; }
.neuro-go:hover { border-color: var(--accent); background: var(--surface); }
.neuro-go svg { width: 15px; height: 15px; animation: neuroNudge 1.8s var(--ease) infinite; }
@keyframes neuroNudge { 0%,100% { transform: translateY(0); } 50% { transform: translateY(4px); } }

.neuro-skip {
  position: absolute; right: 22px; bottom: 20px;
  font-family: 'JetBrains Mono', monospace; font-size: 10px;
  letter-spacing: 2px; text-transform: uppercase; color: var(--muted);
  padding: 8px 12px; border-radius: 999px; border: 1px solid transparent;
}
.neuro-skip:hover { color: var(--text); border-color: var(--line); }

@media (max-width: 620px) {
  .neuro-say { margin-top: 40vh; }
  .neuro-hud { top: 4vh; min-width: 180px; font-size: 10.5px; }
}

/* Someone who has asked for less motion still gets the site; the
   scene simply is not staged for them. */
@media (prefers-reduced-motion: reduce) {
  .neuro-ui { display: none; }
  body.neuro-intro { overflow: auto; }
  body.neuro-intro main, body.neuro-intro #nav, body.neuro-intro #footer { opacity: 1; }
  body.neuro-bg main, body.neuro-bg #nav, body.neuro-bg #footer, body.neuro-bg .orb { animation: none; }
}

/* three tabs need a little more room than two */
@media (min-width: 981px) and (max-width: 1300px) {
  .mode-tab { padding: 6px 9px; font-size: 9px; letter-spacing: 1px; }
}

/* ---- the 3D figure ------------------------------------------------
   The WebGL canvas is transparent, so the page's own background shows
   through and the robot appears to stand on the page rather than in a
   black box of its own. */
.robo3d { background: var(--bg); }
.robo3d .neuro-canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.robo3d.done { background: transparent; }
.robo3d.done .neuro-canvas { opacity: 1; }

/* With the figure standing on the right, the greeting takes the left
   half instead of sitting on top of it. */
.robo3d .neuro-ui { justify-items: start; text-align: left; padding-left: 6vw; }
.robo3d .neuro-say { margin-top: 0; max-width: 42vw; }
.robo3d .neuro-hud { left: 6vw; transform: none; }
.robo3d .neuro-hud.out { transform: translateY(-12px); }
@media (max-width: 1100px) {
  .robo3d .neuro-ui { justify-items: center; text-align: center; padding-left: 0; }
  .robo3d .neuro-say { max-width: 80vw; margin-top: 34vh; }
  .robo3d .neuro-hud { left: 50%; transform: translateX(-50%); }
  .robo3d .neuro-hud.out { transform: translateX(-50%) translateY(-12px); }
}


/* ==================================================================
   THE COMMAND LINE
   A sheet over everything, monospace, with the site's own palette so
   it belongs to all three modes rather than looking bolted on.
   ================================================================== */
.cmdk {
  position: fixed; inset: 0; z-index: 100003;
  display: grid; place-items: start center;
  padding: 12vh 20px 20px;
  opacity: 0; transition: opacity .18s var(--ease);
}
.cmdk.on { opacity: 1; }
.cmdk[hidden] { display: none; }
.cmdk-veil {
  position: absolute; inset: 0;
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  backdrop-filter: blur(9px);
}
.cmdk-box {
  position: relative; width: min(660px, 100%);
  background: var(--bg2); border: 1px solid var(--line);
  border-radius: 12px; box-shadow: 0 40px 90px -30px rgba(0,0,0,.7);
  overflow: hidden;
  transform: translateY(-8px) scale(.985);
  transition: transform .22s cubic-bezier(.22,1,.36,1);
}
.cmdk.on .cmdk-box { transform: none; }

.cmdk-line { display: flex; align-items: center; gap: 10px; padding: 16px 18px; border-bottom: 1px solid var(--line); }
.cmdk-caret { color: var(--accent); font-family: 'JetBrains Mono', monospace; font-size: 17px; }
.cmdk-input {
  flex: 1; background: none; border: 0; outline: 0; color: var(--text);
  font-family: 'JetBrains Mono', monospace; font-size: 15px; letter-spacing: .4px;
}
.cmdk-input::placeholder { color: var(--muted); }

.cmdk-list { max-height: min(52vh, 420px); overflow-y: auto; padding: 6px 0 8px; }
.cmdk-group {
  font-family: 'JetBrains Mono', monospace; font-size: 9.5px; letter-spacing: 2.4px;
  color: var(--muted); padding: 12px 18px 6px; text-transform: uppercase;
}
.cmdk-row {
  display: flex; align-items: baseline; gap: 16px; width: 100%;
  padding: 9px 18px; text-align: left; border-left: 2px solid transparent;
}
.cmdk-row b {
  font-family: 'JetBrains Mono', monospace; font-weight: 400; font-size: 13.5px;
  color: var(--text); flex: none; min-width: 190px;
}
.cmdk-row i { font-style: normal; font-size: 12.5px; color: var(--muted); }
.cmdk-row.on { background: var(--surface); border-left-color: var(--accent); }
.cmdk-row.on b { color: var(--accent); }
.cmdk-empty { padding: 26px 18px; color: var(--muted); font-size: 13px; }

.cmdk-foot {
  display: flex; align-items: center; gap: 16px;
  padding: 10px 18px; border-top: 1px solid var(--line);
  font-size: 11px; color: var(--muted);
}
.cmdk-foot kbd {
  font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--text);
  border: 1px solid var(--line); border-bottom-width: 2px; border-radius: 5px;
  padding: 1px 5px; margin-right: 3px; background: var(--surface);
}
.cmdk-count { margin-left: auto; font-family: 'JetBrains Mono', monospace; }

/* the way in for anyone who never reaches for a keyboard */
.cmdk-tip {
  position: fixed; left: 22px; bottom: 22px; z-index: 8500;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 13px; border-radius: 999px;
  border: 1px solid var(--line); background: color-mix(in srgb, var(--bg2) 82%, transparent);
  backdrop-filter: blur(10px);
  font-size: 11px; color: var(--muted);
  opacity: .55; transition: opacity .3s var(--ease), border-color .3s;
}
.cmdk-tip:hover { opacity: 1; border-color: var(--accent); color: var(--text); }
.cmdk-tip kbd {
  font-family: 'JetBrains Mono', monospace; font-size: 10px;
  border: 1px solid var(--line); border-radius: 4px; padding: 1px 5px;
}
@media (max-width: 720px) { .cmdk-tip { display: none; } .cmdk-row b { min-width: 130px; } }
@media print { .cmdk, .cmdk-tip { display: none !important; } }


/* ==================================================================
   ====================  P R E S S   M O D E  =======================

   The fourth presentation: the same content set as a printed
   quarterly. High-contrast serif for anything that would be typeset,
   monospace for anything that would be a printer's mark, hairline
   rules doing the work that boxes and shadows do elsewhere.

   The rule of this mode is that nothing floats. No cards, no glass,
   no drop shadows — an editorial page is flat, and its structure comes
   from rules, numbers and space.
   ================================================================== */

html[data-mode="press"][data-theme="dark"] {
  --bg:        #12100E;
  --bg2:       #12100E;
  --surface:   rgba(242,237,227,.045);
  --surface-2: rgba(242,237,227,.075);
  --line:      rgba(242,237,227,.18);
  --text:      #F2EDE3;
  --muted:     #97907F;
  --accent:    #D8503A;
  --accent2:   #B23A24;
  --shadow:    none;
}
html[data-mode="press"][data-theme="light"] {
  --bg:        #F7F3EA;
  --bg2:       #F7F3EA;
  --surface:   rgba(20,18,16,.04);
  --surface-2: rgba(20,18,16,.07);
  --line:      rgba(20,18,16,.2);
  --text:      #141210;
  --muted:     #6B6357;
  --accent:    #B23A24;
  --accent2:   #8A2716;
  --shadow:    none;
}

/* nothing that glows belongs in print */
html[data-mode="press"] #netCanvas,
html[data-mode="press"] .aurora,
html[data-mode="press"] .photo-scan,
html[data-mode="press"] .photo-ring,
html[data-mode="press"] .photo-glare,
html[data-mode="press"] .btn-shine,
html[data-mode="press"] .orb-ring,
html[data-mode="press"] .grain { display: none !important; }

/* ---- the masthead ------------------------------------------------ */
.press-head { display: none; }
html[data-mode="press"] .press-head {
  display: flex; align-items: center; gap: 22px;
  position: relative; z-index: 3;
  padding: 16px clamp(20px, 5vw, 60px) 12px;
  border-bottom: 1px solid var(--line);
  font-family: 'JetBrains Mono', monospace;
  font-size: 10.5px; letter-spacing: 2.4px; text-transform: uppercase;
  color: var(--muted);
}
html[data-mode="press"] .press-head .press-title { margin-left: auto; color: var(--text); }
@media (max-width: 760px) {
  html[data-mode="press"] .press-head { gap: 12px; font-size: 9px; letter-spacing: 1.4px; }
  html[data-mode="press"] .press-head .press-title { display: none; }
}

/* ---- the type ----------------------------------------------------
   One high-contrast serif carries every heading. Body copy stays in
   the sans it was set in: a whole CV in a display face is a poster,
   not a document, and someone has to read this to hire him. */
html[data-mode="press"] .hero-name,
html[data-mode="press"] .section-title,
html[data-mode="press"] .press-index em,
html[data-mode="press"] .project h3,
html[data-mode="press"] .edu-card h3,
html[data-mode="press"] .tl-role,
html[data-mode="press"] .stats b {
  font-family: 'Bodoni Moda', 'Sora', serif;
  font-weight: 700; letter-spacing: -0.01em;
  color: var(--text); background: none; -webkit-text-fill-color: currentColor;
}
html[data-mode="press"] .hero-name {
  font-size: clamp(3rem, 9vw, 7rem); line-height: .92; text-transform: none;
}
html[data-mode="press"] .section-title { font-size: clamp(2rem, 5vw, 3.4rem); }
html[data-mode="press"] .hero-role { font-family: 'JetBrains Mono', monospace; font-size: 12px; letter-spacing: 3px; text-transform: uppercase; }

/* the summary becomes a pull quote, which is what a magazine does
   with the one sentence it wants you to keep */
html[data-mode="press"] .hero-tagline {
  font-family: 'Bodoni Moda', serif; font-style: italic; font-weight: 400;
  font-size: clamp(1.15rem, 2.6vw, 1.8rem); line-height: 1.35;
  text-transform: none; letter-spacing: 0; color: var(--text);
  border-left: 3px solid var(--accent); padding-left: 18px; margin: 26px 0 20px;
}

/* ---- the index --------------------------------------------------- */
.press-index { display: none; }
html[data-mode="press"] .press-index {
  display: block; max-width: 1180px;
  margin: 0 auto clamp(40px, 8vw, 90px);
  padding: 0 clamp(20px, 5vw, 40px);
}
html[data-mode="press"] .press-index-label {
  font-family: 'JetBrains Mono', monospace; font-size: 10px; letter-spacing: 3px;
  text-transform: uppercase; color: var(--muted);
  padding-bottom: 10px; border-bottom: 1px solid var(--line);
}
html[data-mode="press"] .press-index a {
  display: grid; grid-template-columns: 52px minmax(120px, 220px) 1fr;
  align-items: baseline; gap: 18px;
  padding: 16px 4px; border-bottom: 1px solid var(--line);
  transition: background .25s var(--ease), padding-left .25s var(--ease);
}
html[data-mode="press"] .press-index a:hover { background: var(--surface); padding-left: 14px; }
html[data-mode="press"] .press-index b {
  font-family: 'JetBrains Mono', monospace; font-weight: 400;
  font-size: 12px; color: var(--accent);
}
html[data-mode="press"] .press-index em { font-style: normal; font-size: clamp(1.1rem, 2vw, 1.5rem); }
html[data-mode="press"] .press-index i {
  font-family: 'Bodoni Moda', serif; font-style: italic;
  font-size: 14px; color: var(--muted);
}
@media (max-width: 700px) {
  html[data-mode="press"] .press-index a { grid-template-columns: 38px 1fr; gap: 10px; }
  html[data-mode="press"] .press-index i { grid-column: 2; }
}

/* ---- rules instead of boxes -------------------------------------- */
html[data-mode="press"] .card,
html[data-mode="press"] .project,
html[data-mode="press"] .tl-item,
html[data-mode="press"] .edu-card,
html[data-mode="press"] .skill-card,
html[data-mode="press"] .calendar-box,
html[data-mode="press"] .chat,
html[data-mode="press"] .modal-card {
  background: none !important;
  border: 0; border-top: 1px solid var(--line);
  border-radius: 0; box-shadow: none !important; backdrop-filter: none;
  padding: 26px 0;
}
html[data-mode="press"] .card::before,
html[data-mode="press"] .glow::after { display: none !important; }
html[data-mode="press"] .chat,
html[data-mode="press"] .modal-card {
  background: var(--bg) !important; border: 1px solid var(--line); padding: 0;
}
html[data-mode="press"] .modal-card { padding: 30px; }

/* a project reads as an index entry with a plate, not as a card */
html[data-mode="press"] .project { grid-template-columns: 1fr 220px; gap: clamp(20px, 4vw, 48px); align-items: start; }
html[data-mode="press"] .project .project-media { order: 2; border-radius: 0; }
html[data-mode="press"] .project .project-media img { border-radius: 0; filter: grayscale(1) contrast(1.08); transition: filter .5s var(--ease); }
html[data-mode="press"] .project:hover .project-media img { filter: grayscale(0) contrast(1); }
html[data-mode="press"] .project .project-body { order: 1; padding: 0; }
html[data-mode="press"] .project h3 { font-size: clamp(1.4rem, 3vw, 2.1rem); }
@media (max-width: 860px) {
  html[data-mode="press"] .project { grid-template-columns: 1fr; }
  html[data-mode="press"] .project .project-media { order: 1; }
  html[data-mode="press"] .project .project-body { order: 2; }
}

/* section heads carry their number, the way a chapter does */
html[data-mode="press"] main { counter-reset: chapter; }
html[data-mode="press"] .section { counter-increment: chapter; }
html[data-mode="press"] .section-head {
  border-bottom: 1px solid var(--line); padding-bottom: 14px; margin-bottom: 34px;
}
html[data-mode="press"] .section-kicker::before {
  content: counter(chapter, decimal-leading-zero) ' · ';
  color: var(--accent);
}

/* photograph: printed, not lit */
html[data-mode="press"] .photo-frame { border-radius: 0; border: 1px solid var(--line); box-shadow: none; }
html[data-mode="press"] .photo-frame img { border-radius: 0; filter: grayscale(1) contrast(1.05); }

/* buttons are rules and small caps, not pills */
html[data-mode="press"] .btn {
  border-radius: 0; border: 1px solid var(--line);
  font-family: 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: 2.4px;
  text-transform: uppercase; background: none;
}
html[data-mode="press"] .btn-primary { background: var(--accent) !important; border-color: var(--accent); }
html[data-mode="press"] .btn:hover { border-color: var(--accent); }
html[data-mode="press"] .tag, html[data-mode="press"] .tl-tools {
  border-radius: 0; border: 1px solid var(--line); background: none;
}
html[data-mode="press"] .socials li a { border-radius: 0; border: 1px solid var(--line); }
html[data-mode="press"] .bar { background: var(--surface-2); border-radius: 0; }
html[data-mode="press"] .bar i { border-radius: 0; }

/* four tabs need the tightest setting of all */
@media (min-width: 981px) and (max-width: 1440px) {
  .mode-tab { padding: 6px 8px; font-size: 8.5px; letter-spacing: .8px; }
}


/* ---- two things the live page showed -----------------------------

   1. The masthead was inserted as the first thing in the document, but
      the header is position:fixed at the very top — so both claimed the
      same strip and the volume line ran straight through the logo and
      the mode tabs. The masthead has to join the fixed stack rather
      than sit under it, and the header has to start below it.

   2. The statistics were still four rounded cards. Everything else in
      this mode had been flattened to rules; they were simply missed.
   ------------------------------------------------------------------ */
html[data-mode="press"] { --press-head-h: 44px; }
@media (max-width: 760px) { html[data-mode="press"] { --press-head-h: 36px; } }

html[data-mode="press"] .press-head {
  position: fixed; top: 0; left: 0; right: 0; z-index: 600;
  height: var(--press-head-h);
  background: var(--bg);
  padding-top: 0; padding-bottom: 0;
}
html[data-mode="press"] #nav { top: var(--press-head-h); }
html[data-mode="press"] body { padding-top: var(--press-head-h); }
/* the phone menu drops from under the header, so it moves too */
@media (max-width: 980px) {
  html[data-mode="press"] .nav-links { top: var(--press-head-h); }
}

html[data-mode="press"] .stats {
  border-top: 1px solid var(--line);
}
html[data-mode="press"] .stats li {
  background: none !important; border: 0; border-radius: 0;
  border-right: 1px solid var(--line);
  box-shadow: none !important; backdrop-filter: none;
  padding: 24px 22px 24px 0;
}
html[data-mode="press"] .stats li:last-child { border-right: 0; }
html[data-mode="press"] .stats li::before,
html[data-mode="press"] .stats li::after { display: none !important; }
@media (max-width: 980px) {
  html[data-mode="press"] .stats li { border-right: 0; border-bottom: 1px solid var(--line); }
}


/* ==================================================================
   THE PHOTOGRAPH BELONGS TO ONE MODE

   A portrait is a portrait; a notebook, a robot's stage and a printed
   quarterly each have their own idea of what stands in that corner,
   and in all three it is the signature. Making it big is the point —
   it is the mark he signs his work with.

   This also settles where the admin panel can be opened from. The five
   clicks are bound to the photo frame, and an element that is not
   displayed cannot be clicked, so hiding the portrait shuts that door
   in every mode but the main one — without a second rule to keep in
   step, and without the door existing anywhere it should not.
   ================================================================== */
html[data-mode="paper"] .photo-frame,
html[data-mode="neural"] .photo-frame,
html[data-mode="press"] .photo-frame,
html[data-mode="paper"] .photo-caption,
html[data-mode="neural"] .photo-caption,
html[data-mode="press"] .photo-caption { display: none !important; }

html[data-mode="paper"] .photo-signature,
html[data-mode="neural"] .photo-signature,
html[data-mode="press"] .photo-signature {
  margin: 0 auto;
  width: min(100%, 620px);
  transform: scale(1.9);
  transform-origin: center;
  filter: none;
}
html[data-mode="paper"] .hero-visual,
html[data-mode="neural"] .hero-visual,
html[data-mode="press"] .hero-visual {
  display: grid; place-items: center; min-height: clamp(220px, 34vh, 420px);
}
@media (max-width: 980px) {
  html[data-mode="paper"] .photo-signature,
  html[data-mode="neural"] .photo-signature,
  html[data-mode="press"] .photo-signature { transform: scale(1.35); }
}

/* ==================================================================
   THE SHORTCUT CHIPS
   ================================================================== */
.key-chips { display: flex; flex-wrap: wrap; gap: 10px; margin: 22px 0 6px; }
.key-chip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 13px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--surface);
  font-size: 11px; color: var(--muted);
  transition: border-color .3s var(--ease), color .3s var(--ease);
}
button.key-chip:hover { border-color: var(--accent); color: var(--text); }
.key-chip kbd {
  font-family: 'JetBrains Mono', monospace; font-size: 10.5px; color: var(--text);
  border: 1px solid var(--line); border-bottom-width: 2px; border-radius: 5px;
  padding: 2px 6px; background: var(--bg2);
}
.key-chip i { font-style: normal; letter-spacing: .3px; }
.key-chip span { opacity: .7; }
@media (max-width: 620px) { .key-chip i { display: none; } }
html[data-mode="press"] .key-chip { border-radius: 0; }
@media print { .key-chips { display: none !important; } }

/* ==================================================================
   PRESS: ONE VIEW AT A TIME
   ================================================================== */
html[data-mode="press"] .press-off { display: none !important; }
.press-back { display: none; }
html[data-mode="press"] .press-back {
  display: inline-flex; align-items: center; gap: 9px;
  max-width: 1180px; margin: 26px auto 0; padding: 0 clamp(20px, 5vw, 40px);
  font-family: 'JetBrains Mono', monospace; font-size: 10.5px;
  letter-spacing: 2.4px; text-transform: uppercase; color: var(--muted);
  transition: color .3s var(--ease);
}
html[data-mode="press"] .press-back:hover { color: var(--accent); }
html[data-mode="press"] .press-back.press-off { display: none !important; }
html[data-mode="press"] #navLinks a.is-here { color: var(--accent); }

/* a single view opens rather than arrives from the side: the fly-in
   belongs to a page you scroll, not to a page you turn to */
html[data-mode="press"] .reveal-x { opacity: 1 !important; transform: none !important; }
html[data-mode="press"] .section { animation: pressIn .5s var(--ease) both; }
@keyframes pressIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }


/* ==================================================================
   ONE SECTION AT A TIME — IN EVERY MODE

   The site opens on its contents: the name, the signature, and a
   numbered list of what is inside. A section is a page you turn to,
   not a place you scroll past. This was built for press mode first and
   then made general, so the class keeps its old name.
   ================================================================== */
.press-off { display: none !important; }

.press-back {
  display: inline-flex; align-items: center; gap: 9px;
  max-width: 1180px; margin: 26px auto 0; padding: 0 clamp(20px, 5vw, 40px);
  font-family: 'JetBrains Mono', monospace; font-size: 10.5px;
  letter-spacing: 2.4px; text-transform: uppercase; color: var(--muted);
  transition: color .3s var(--ease);
}
.press-back:hover { color: var(--accent); }
#navLinks a.is-here { color: var(--accent); }

/* the contents page, wherever it is shown */
.press-index {
  display: block; max-width: 1180px;
  margin: 0 auto clamp(40px, 8vw, 90px);
  padding: 0 clamp(20px, 5vw, 40px);
}
.press-index-label {
  font-family: 'JetBrains Mono', monospace; font-size: 10px; letter-spacing: 3px;
  text-transform: uppercase; color: var(--muted);
  padding-bottom: 10px; border-bottom: 1px solid var(--line);
}
.press-index a {
  display: grid; grid-template-columns: 52px minmax(120px, 220px) 1fr;
  align-items: baseline; gap: 18px;
  padding: 16px 4px; border-bottom: 1px solid var(--line);
  transition: background .25s var(--ease), padding-left .25s var(--ease);
}
.press-index a:hover { background: var(--surface); padding-left: 14px; }
.press-index b {
  font-family: 'JetBrains Mono', monospace; font-weight: 400;
  font-size: 12px; color: var(--accent);
}
.press-index em { font-style: normal; font-size: clamp(1.1rem, 2vw, 1.5rem); font-weight: 600; }
.press-index i { font-size: 14px; color: var(--muted); }
@media (max-width: 700px) {
  .press-index a { grid-template-columns: 38px 1fr; gap: 10px; }
  .press-index i { grid-column: 2; }
}
/* each mode keeps its own voice for the contents */
html[data-mode="paper"] .press-index em { font-family: 'Caveat', cursive; font-size: clamp(1.5rem, 2.6vw, 2rem); }
html[data-mode="paper"] .press-index i { font-family: 'Kalam', cursive; }
html[data-mode="press"] .press-index em,
html[data-mode="press"] .press-index i { font-family: 'Bodoni Moda', serif; }
html[data-mode="press"] .press-index i { font-style: italic; }

/* a single view opens; it does not fly in from the side */
.reveal-x { opacity: 1; transform: none; }
main .section { animation: viewIn .45s var(--ease) both; }
@keyframes viewIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* the scroll hint was advice for a page that no longer scrolls */
.scroll-hint { display: none !important; }


/* ==================================================================
   THE ARCADE
   A sheet like the command line, so the two feel like parts of the
   same machine. The canvas keeps its own aspect ratio and scales to
   whatever room is left, which is what makes it work on a phone.
   ================================================================== */
.arc {
  position: fixed; inset: 0; z-index: 100004;
  display: grid; place-items: center; padding: 20px;
  opacity: 0; transition: opacity .2s var(--ease);
}
.arc.on { opacity: 1; }
.arc[hidden] { display: none; }
.arc-veil {
  position: absolute; inset: 0;
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  backdrop-filter: blur(10px);
}
.arc-box {
  position: relative; display: flex; flex-direction: column;
  max-width: min(94vw, 720px); max-height: 92vh;
  background: var(--bg2); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 40px 90px -30px rgba(0,0,0,.75); overflow: hidden;
  transform: scale(.985); transition: transform .22s cubic-bezier(.22,1,.36,1);
}
.arc.on .arc-box { transform: none; }

.arc-head {
  display: flex; align-items: center; gap: 14px;
  padding: 12px 14px; border-bottom: 1px solid var(--line);
}
.arc-head b {
  font-family: 'JetBrains Mono', monospace; font-weight: 400;
  font-size: 12px; letter-spacing: 2.4px; text-transform: uppercase; color: var(--text);
}
.arc-tabs { display: flex; gap: 4px; margin-left: auto; }
.arc-tab {
  font-family: 'JetBrains Mono', monospace; font-size: 10px; letter-spacing: 1.4px;
  text-transform: uppercase; color: var(--muted);
  padding: 6px 10px; border-radius: 999px; border: 1px solid transparent;
  transition: color .25s, border-color .25s;
}
.arc-tab:hover { color: var(--text); }
.arc-tab.on { color: var(--accent); border-color: var(--accent); }
.arc-x { font-size: 22px; line-height: 1; color: var(--muted); padding: 0 6px; }
.arc-x:hover { color: var(--accent); }

.arc-stage { position: relative; display: grid; place-items: center; padding: 14px; min-height: 0; }
.arc-stage canvas {
  display: block; max-width: 100%; max-height: min(66vh, 560px);
  border-radius: 6px; touch-action: none;
}
.arc-over {
  position: absolute; inset: 14px; display: grid; place-content: center; gap: 14px;
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: blur(3px); border-radius: 6px; text-align: center;
}
.arc-over[hidden] { display: none; }
.arc-over p { font-family: 'JetBrains Mono', monospace; font-size: 14px; color: var(--text); }
.arc-btn {
  justify-self: center; padding: 10px 20px; border-radius: 999px;
  background: var(--accent); color: #FBFAF6;
  font-family: 'JetBrains Mono', monospace; font-size: 11px;
  letter-spacing: 2px; text-transform: uppercase;
}
.arc-btn:hover { filter: brightness(1.08); }

.arc-foot {
  display: flex; align-items: center; gap: 18px;
  padding: 10px 16px; border-top: 1px solid var(--line);
  font-family: 'JetBrains Mono', monospace; font-size: 10.5px;
  letter-spacing: 1.4px; text-transform: uppercase; color: var(--muted);
}
.arc-foot i { font-style: normal; color: var(--text); margin-left: 6px; }
.arc-keys { margin-left: auto; text-transform: none; letter-spacing: .4px; }
@media (max-width: 620px) { .arc-keys { display: none; } .arc-head b { font-size: 10px; } }
@media print { .arc { display: none !important; } }


/* ==================================================================
   TERMINAL MODE
   The shell replaces the page. The header stays, because it is how
   you get back out — and `exit` and `mode modern` do the same thing
   from inside.
   ================================================================== */
html[data-mode="shell"] { --bg: #0B0D0C; --bg2: #101312; --text: #C8D6CC; --muted: #6E7F74; --accent: #6EE7A0; --line: rgba(110,231,160,.16); }
html[data-mode="shell"][data-theme="light"] { --bg: #F4F3EE; --bg2: #FFFFFF; --text: #14180F; --muted: #5C6357; --accent: #1F7A4C; --line: rgba(20,24,15,.16); }

/* everything the shell is not */
body.sh-on main,
body.sh-on #footer,
body.sh-on .orb,
body.sh-on #netCanvas,
body.sh-on .aurora,
body.sh-on .grain,
body.sh-on .cmdk-tip,
body.sh-on .press-back { display: none !important; }

.sh { display: none; }
body.sh-on .sh {
  display: block; position: relative; z-index: 3;
  max-width: 1100px; margin: 96px auto 40px; padding: 0 clamp(14px, 4vw, 28px);
}
.sh[hidden] { display: none !important; }

.sh-bar {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 14px; border: 1px solid var(--line); border-bottom: 0;
  border-radius: 10px 10px 0 0; background: var(--bg2);
}
.sh-dot { width: 11px; height: 11px; border-radius: 50%; background: var(--muted); opacity: .6; }
.sh-dot.r { background: #E0553B; } .sh-dot.y { background: #D8A13A; } .sh-dot.g { background: #4CAF7D; }
.sh-bar b {
  margin-left: 10px; font-family: 'JetBrains Mono', monospace;
  font-weight: 400; font-size: 11.5px; letter-spacing: 1px; color: var(--muted);
}

.sh-screen {
  height: min(70vh, 640px); overflow-y: auto;
  padding: 16px clamp(14px, 3vw, 22px) 20px;
  border: 1px solid var(--line); border-radius: 0 0 10px 10px;
  background: var(--bg2);
  font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.55;
  color: var(--text);
  cursor: text;
}
.sh-screen::-webkit-scrollbar { width: 8px; }
.sh-screen::-webkit-scrollbar-thumb { background: var(--line); border-radius: 4px; }

.sh-row { white-space: pre-wrap; word-break: break-word; }
.sh-echo { color: var(--muted); margin-top: 6px; }
.sh-err { color: #E0553B; }
.sh-dim { color: var(--muted); }
.sh-f { color: var(--accent); }
.sh-sha { color: #D8A13A; }
.sh-art { color: var(--accent); }
.sh-row a { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }

.sh-line { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.sh-ps1 { color: var(--accent); white-space: nowrap; }
.sh-in {
  flex: 1; background: none; border: 0; outline: 0;
  font: inherit; color: var(--text); caret-color: var(--accent);
}
@media (max-width: 620px) { .sh-screen { font-size: 12px; height: 66vh; } }
@media print { .sh { display: none !important; } }

/* five tabs is the most this row will take */
@media (min-width: 981px) and (max-width: 1500px) {
  .mode-tab { padding: 6px 7px; font-size: 8px; letter-spacing: .6px; }
}


/* ==================================================================
   PERFORMANCE TIERS
   ------------------------------------------------------------------
   Written last on purpose. Every rule below is a plain
   html[data-perf="…"] selector, the same specificity as the theme and
   mode blocks above it, so being last in the file is what lets it win
   without a single !important.

   What was actually slow, measured rather than guessed:

     1. .aurora span — three circles up to 46vw across carrying
        filter: blur(110px), *animated with scale*. A blur is priced by
        area and has to be re-rasterised whenever the thing under it
        changes size. Three of them, forever.

     2. .grain — a full-viewport SVG-noise layer whose transform steps
        four times a second. Full-screen repaint, on a loop.

     3. Twenty-two backdrop-filter surfaces sitting *in front of*
        (1) and (2). This is the multiplier and the real answer to
        "why one frame a second": a backdrop filter must re-blur its
        backdrop whenever the backdrop moves, and thanks to the aurora
        and the grain the backdrop never stopped moving. Twenty-two
        live blurs, recomputed every frame, over a surface that is
        itself being blurred.

   The fix is not to delete the look. A circle blurred by 110px *is* a
   radial falloff — so at the mid tier the blur is replaced with a mask
   that produces the same soft edge for free, and the frost is dropped
   only at the tier where the machine cannot pay for it at all.
   ================================================================== */

/* ---------------------------------------------------- mid and low */

/* The aurora keeps its colour, its size and its drift. It loses the
   thing that cost: a real gaussian blur, swapped for a radial mask
   with the same falloff. Composited, not rasterised. */
html[data-perf="mid"] .aurora span,
html[data-perf="low"] .aurora span {
  filter: none;
  -webkit-mask-image: radial-gradient(closest-side, #000 0%, rgba(0,0,0,.55) 45%, transparent 100%);
          mask-image: radial-gradient(closest-side, #000 0%, rgba(0,0,0,.55) 45%, transparent 100%);
  opacity: .42;
}
html[data-theme="light"][data-perf="mid"] .aurora span,
html[data-theme="light"][data-perf="low"] .aurora span { opacity: .3; }

/* The grain stays, but stops moving. Static film grain reads as film
   grain; it is the four-times-a-second full-screen repaint that was
   never worth its price. */
html[data-perf="mid"] .grain { animation: none; }

/* The hero name stops animating its own gradient — that repaints the
   largest piece of text on the page on every frame. */
html[data-perf="mid"] .hero-name,
html[data-perf="low"] .hero-name { animation: none; background-position: 0 0; }

/* ------------------------------------------------------------ low */

html[data-perf="low"] {
  /* Without a backdrop filter the translucent surfaces are 3% white on
     a dark background — invisible. So at this tier they become opaque,
     derived from whatever --bg2 the current mode and theme set, which
     keeps all five modes correct without repeating a palette here. */
  --surface:   color-mix(in srgb, var(--bg2) 93%, var(--text) 7%);
  --surface-2: color-mix(in srgb, var(--bg2) 86%, var(--text) 14%);
}

/* Every frosted panel on the site, in one place. */
html[data-perf="low"] #nav,
html[data-perf="low"] .card,
html[data-perf="low"] .project,
html[data-perf="low"] .modal-backdrop,
html[data-perf="low"] .chat,
html[data-perf="low"] .orb,
html[data-perf="low"] .toast,
html[data-perf="low"] .calendar-box,
html[data-perf="low"] .cmdk-box,
html[data-perf="low"] .cmdk-veil,
html[data-perf="low"] .news-pop-card,
html[data-perf="low"] .news-pop-veil,
html[data-perf="low"] .news-card,
html[data-perf="low"] .adminbar,
html[data-perf="low"] .sh,
html[data-perf="low"] .arcade,
html[data-perf="low"] .arcade-veil {
  -webkit-backdrop-filter: none !important;
          backdrop-filter: none !important;
}

/* The grain is a full-screen layer that has to be composited even when
   it is not moving. At this tier it goes. */
html[data-perf="low"] .grain { display: none; }

/* The drift is cheap now that the blur is gone, but on a machine this
   slow a permanently animating full-screen element is still one thing
   too many. */
html[data-perf="low"] .aurora span { animation: none; opacity: .3; }

/* The custom cursor is two fixed elements repainting on a permanent
   animation loop, and it is the first thing that feels broken when the
   frame rate drops — a laggy cursor reads as a broken site. Give the
   real one back. */
html[data-perf="low"] #cursorDot,
html[data-perf="low"] #cursorRing { display: none; }
html[data-perf="low"] body,
html[data-perf="low"] a,
html[data-perf="low"] button,
html[data-perf="low"] .tilt { cursor: auto !important; }
html[data-perf="low"] a,
html[data-perf="low"] button { cursor: pointer !important; }

/* Decorative loops that repaint on a timer and add nothing to a page
   that is already struggling to scroll. */
html[data-perf="low"] .photo-scan,
html[data-perf="low"] .photo-ring,
html[data-perf="low"] .orb-ring,
html[data-perf="low"] .cal-card-pulse,
html[data-perf="low"] .scroll-hint span { animation: none; }
html[data-perf="low"] .photo-glare { display: none; }

/* Long shadows are a blur too: a 80px spread is rasterised the same way
   and there is one on every card. */
html[data-perf="low"] { --shadow: 0 8px 20px rgba(0,0,0,.28); }

/* Transitions on colour across the whole document force a repaint of
   everything for most of a second every time the light switch flips. */
html[data-perf="low"] body { transition: none; }

/* ------------------------------------------- the manual override */

.perf-tab {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 6px 9px; border: 1px solid var(--line); border-radius: 999px;
  font-size: 9px; letter-spacing: 1.2px; text-transform: uppercase;
  color: var(--muted); background: transparent; cursor: pointer;
  transition: color .25s var(--ease), border-color .25s var(--ease);
}
.perf-tab:hover { color: var(--text); border-color: var(--accent); }
.perf-tab i {
  width: 6px; height: 6px; border-radius: 50%; background: var(--muted);
  transition: background .25s var(--ease);
}
html[data-perf="high"] .perf-tab i { background: #2CE38A; }
html[data-perf="mid"]  .perf-tab i { background: #E8B33A; }
html[data-perf="low"]  .perf-tab i { background: #E0553B; }
@media (max-width: 980px) { .perf-tab { display: none; } }


/* ==================================================================
   ANNOUNCEMENTS
   ------------------------------------------------------------------
   A section on the page, and a short list hanging off a bell in the
   nav. The bell's badge counts what *this browser* has not opened yet,
   remembered in localStorage — there is no read-receipt going back to
   a server, because nobody needs to know who read what.
   ================================================================== */

/* --------------------------------------------------------- the bell */
.bell-btn {
  position: relative; display: grid; place-items: center;
  width: 38px; height: 38px; border-radius: 12px;
  border: 1px solid var(--line); color: var(--muted);
  transition: color .3s var(--ease), border-color .3s var(--ease), background .3s var(--ease);
}
.bell-btn:hover { color: var(--text); border-color: var(--accent); background: var(--surface); }
.bell-btn.on { color: var(--accent); border-color: var(--accent); }

/* The bell only rings when there is something to ring about. */
.bell-btn.unread .bell-body { transform-origin: 12px 4px; animation: bellSwing 3.4s var(--ease) infinite; }
@keyframes bellSwing {
  0%, 62%, 100% { transform: rotate(0); }
  66% { transform: rotate(11deg); }
  72% { transform: rotate(-9deg); }
  78% { transform: rotate(6deg); }
  84% { transform: rotate(-4deg); }
  90% { transform: rotate(2deg); }
}
html[data-perf="low"] .bell-btn.unread .bell-body { animation: none; }

.bell-badge {
  position: absolute; top: -5px; right: -5px; min-width: 17px; height: 17px;
  padding: 0 4px; border-radius: 999px;
  background: var(--accent); color: #04050a;
  font-size: 10px; font-weight: 700; line-height: 17px; text-align: center;
  box-shadow: 0 0 0 2px var(--bg);
  animation: badgePop .5s var(--ease);
}
@keyframes badgePop { from { transform: scale(0); } to { transform: scale(1); } }

/* --------------------------------------------------- the section */
.news-grid { display: grid; gap: 14px; }

/* Three columns: when it happened, what it looked like, what it says.
   The middle one only exists when there is a picture, so the text column
   is the last one either way and does not need to be placed by hand. */
.news-card {
  position: relative; display: grid; align-items: start;
  grid-template-columns: 118px 1fr;
  gap: 20px;
  padding: 20px 22px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  transition: border-color .35s var(--ease), transform .35s var(--ease);
}
.news-card.has-image { grid-template-columns: 118px 132px 1fr; }
.news-card:hover { border-color: color-mix(in srgb, var(--accent) 55%, var(--line)); transform: translateX(4px); }

.news-text { display: grid; gap: 8px; justify-items: start; min-width: 0; }

/* A fixed square. Announcement pictures arrive at whatever size and shape
   he had to hand — a phone photo, a wide screenshot, a scan — and a row of
   cards whose heights are decided by the tallest of those looks like an
   accident. object-fit does the cropping so the layout does not have to. */
.news-shot {
  display: block; width: 132px; height: 132px; overflow: hidden;
  border-radius: 12px; border: 1px solid var(--line); background: var(--bg2);
}
.news-shot img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .5s var(--ease);
}
.news-shot:hover img { transform: scale(1.06); }
html[data-perf="low"] .news-shot:hover img { transform: none; }

.news-card .news-when { align-self: start; }
.news-card .news-date {
  display: block; font-family: 'JetBrains Mono', monospace;
  font-size: 11px; letter-spacing: 1px; color: var(--muted);
}
.news-card .news-tag {
  display: inline-block; margin-top: 8px; padding: 3px 9px;
  border: 1px solid var(--line); border-radius: 999px;
  font-size: 9px; letter-spacing: 1.4px; text-transform: uppercase; color: var(--muted);
}
.news-card h3 { font-size: clamp(17px, 2vw, 21px); font-weight: 600; line-height: 1.35; }
.news-card p { color: var(--muted); font-size: 15px; }
.news-card .news-more {
  justify-self: start; font-size: 12px; letter-spacing: 1px; text-transform: uppercase;
  color: var(--accent); border-bottom: 1px solid currentColor; padding-bottom: 2px;
}

/* Pinned: a rule down the left edge, not a different card. */
.news-card.pinned { border-left: 3px solid var(--accent); }
.news-card.pinned .news-tag { color: var(--accent); border-color: var(--accent); }

/* Unread, from this browser's point of view. */
.news-card.fresh::after {
  content: ''; position: absolute; top: 16px; right: 16px;
  width: 8px; height: 8px; border-radius: 50%; background: var(--accent);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 70%, transparent);
  animation: freshPing 2.4s ease-out infinite;
}
@keyframes freshPing {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 60%, transparent); }
  70%  { box-shadow: 0 0 0 9px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
html[data-perf="low"] .news-card.fresh::after { animation: none; }

.news-empty {
  padding: 40px 24px; text-align: center; color: var(--muted);
  border: 1px dashed var(--line); border-radius: var(--radius);
}

/* ------------------------------------------------ the attachments
   A row of chips. Each one says what it is before it says what it is
   called, because "Q1-rapor-final-v3" tells you nothing until you know
   whether it is a spreadsheet or a slide deck. The kind comes from the
   extension, so it cannot disagree with the file. */
.news-files { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; margin: 2px 0 0; padding: 0; }

.news-file {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 12px 6px 6px;
  border: 1px solid var(--line); border-radius: 999px;
  font-size: 12.5px; color: var(--text);
  transition: border-color .25s var(--ease), background .25s var(--ease);
}
.news-file:hover { border-color: var(--accent); background: var(--surface); }

.news-file-kind {
  padding: 3px 8px; border-radius: 999px;
  font-family: 'JetBrains Mono', monospace; font-size: 9.5px; font-weight: 600;
  letter-spacing: .8px; text-transform: uppercase; color: #0b0b0d;
  background: var(--muted);
}
.news-file-name { max-width: 210px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Office's own colours, so the chip is recognisable at a glance without
   needing an icon set downloaded from anywhere. */
.news-file.pdf  .news-file-kind { background: #E2574C; color: #fff; }
.news-file.doc  .news-file-kind { background: #2B579A; color: #fff; }
.news-file.xls  .news-file-kind { background: #217346; color: #fff; }
.news-file.ppt  .news-file-kind { background: #D24726; color: #fff; }
.news-file.zip  .news-file-kind { background: #7A6C55; color: #fff; }
.news-file.img  .news-file-kind { background: #6A4FBF; color: #fff; }
.news-file.link .news-file-kind { background: var(--accent); color: #04050a; }

@media (max-width: 820px) {
  .news-card, .news-card.has-image { grid-template-columns: 1fr; gap: 14px; }
  .news-card .news-when { display: flex; align-items: center; gap: 10px; }
  .news-card .news-tag { margin-top: 0; }
  .news-shot { width: 100%; height: 190px; }
  .news-file-name { max-width: 150px; }
}

/* ------------------------------------------------- the pop-out */
.news-pop { position: fixed; inset: 0; z-index: 900; }
.news-pop-veil { position: absolute; inset: 0; background: rgba(0,0,0,.35); animation: fadeIn .3s var(--ease); }

.news-pop-card {
  position: absolute; top: 66px; right: clamp(12px, 4vw, 46px);
  width: min(390px, calc(100vw - 24px)); max-height: min(70vh, 560px);
  display: flex; flex-direction: column;
  border: 1px solid var(--line); border-radius: 16px;
  background: color-mix(in srgb, var(--bg2) 92%, transparent);
  -webkit-backdrop-filter: blur(20px) saturate(150%); backdrop-filter: blur(20px) saturate(150%);
  box-shadow: var(--shadow); overflow: hidden;
  animation: popIn .35s var(--ease);
}
@keyframes popIn { from { opacity: 0; transform: translateY(-10px) scale(.98); } to { opacity: 1; transform: none; } }
html[data-perf="low"] .news-pop-card { background: var(--bg2); animation: none; }

.news-pop-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 16px; border-bottom: 1px solid var(--line);
}
.news-pop-title { font-size: 10px; letter-spacing: 2px; text-transform: uppercase; color: var(--muted); }
.news-pop-x { font-size: 22px; line-height: 1; color: var(--muted); }
.news-pop-x:hover { color: var(--text); }

.news-pop-list { overflow-y: auto; padding: 6px; }
.news-pop-item {
  display: block; padding: 13px 12px; border-radius: 11px;
  border-bottom: 1px solid var(--line);
}
.news-pop-item:last-child { border-bottom: 0; }
.news-pop-item:hover { background: var(--surface); }
.news-pop-item .t { display: flex; align-items: center; gap: 7px; font-size: 14px; font-weight: 600; }
.news-pop-item .t i {
  width: 7px; height: 7px; border-radius: 50%; background: var(--accent); flex: none;
}
.news-pop-item.read .t i { background: transparent; }
.news-pop-item .b { margin-top: 4px; font-size: 13px; color: var(--muted); }
.news-pop-item .d { margin-top: 6px; font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--muted); letter-spacing: .8px; }

.news-pop-foot { padding: 11px 16px; border-top: 1px solid var(--line); text-align: center; }
.news-pop-foot a { font-size: 11px; letter-spacing: 1.4px; text-transform: uppercase; color: var(--accent); }

/* ==================================================================
   ADMIN BAR
   ------------------------------------------------------------------
   Signing in used to redirect straight into the editor. Now it puts
   this strip at the top of the real site instead, so the first thing
   you see after logging in is the thing you are editing.
   ================================================================== */
.adminbar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 1200;
  display: flex; align-items: center; gap: 10px;
  height: 44px; padding: 0 clamp(12px, 3vw, 26px);
  background: color-mix(in srgb, var(--bg2) 88%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(150%); backdrop-filter: blur(18px) saturate(150%);
  border-bottom: 1px solid var(--line);
  font-size: 12px;
  animation: barDrop .45s var(--ease);
}
@keyframes barDrop { from { transform: translateY(-100%); } to { transform: none; } }
html[data-perf="low"] .adminbar { background: var(--bg2); animation: none; }

.adminbar-live {
  width: 7px; height: 7px; border-radius: 50%; background: #2CE38A; flex: none;
  box-shadow: 0 0 0 0 rgba(44,227,138,.65); animation: ping 2s infinite;
}
html[data-perf="low"] .adminbar-live { animation: none; }
.adminbar-who { color: var(--muted); white-space: nowrap; }
.adminbar-who b { color: var(--text); font-weight: 600; }
.adminbar-spacer { flex: 1; }

.adminbar-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border-radius: 9px;
  border: 1px solid var(--line); color: var(--text);
  font-size: 12px; white-space: nowrap;
  transition: border-color .25s var(--ease), background .25s var(--ease);
}
.adminbar-btn:hover { border-color: var(--accent); background: var(--surface); }
.adminbar-btn.primary { background: var(--accent); border-color: var(--accent); color: #04050a; font-weight: 600; }
.adminbar-btn.primary:hover { filter: brightness(1.1); }
.adminbar-btn.ghost { color: var(--muted); }

/* Everything that was pinned to the top of the viewport moves down by
   exactly the height of the bar. */
body.has-adminbar { padding-top: 44px; }
body.has-adminbar #nav { top: 44px; }
body.has-adminbar .news-pop-card { top: 110px; }
/* The mode tabs live inside #nav, so they follow it down on their own. */

@media (max-width: 720px) {
  .adminbar-who { display: none; }
  .adminbar-btn span { display: none; }
  .adminbar-btn.ghost span, .adminbar-btn.ghost { display: inline-flex; }
}


/* ==================================================================
   [hidden] MUST WIN
   ------------------------------------------------------------------
   `hidden` is only an attribute; the browser implements it as a plain
   `display: none` rule in its own stylesheet, and *any* author rule
   that sets display beats it. So every element this site toggles with
   .hidden = true needs a matching rule here or it never goes away.

   This is not hypothetical. `.adminbar` sets `display: flex`, and
   without the line below the signed-in toolbar — with its Edit and
   Sign out buttons — was rendered for every anonymous visitor.
   scripts/test.js checks for exactly this, and it now reads this file.
   ================================================================== */
.adminbar[hidden] { display: none !important; }
.bell-btn[hidden] { display: none !important; }
.news-pop[hidden] { display: none !important; }
.news-card[hidden] { display: none !important; }
.press-index a[hidden] { display: none !important; }
.nav-links a[hidden] { display: none !important; }


/* ------------------------------------------------------------------
   mid, second pass — measured on the live site

   The first version of the mid tier removed the aurora blur and left
   every backdrop-filter alone. Counting them in the browser showed 29
   still live, and the split matters: a handful are *chrome* — the nav,
   the chat panel, the command palette, this bar — which are few, fixed
   in place, and where the frosted glass actually reads as a material.

   The rest are *content* cards: .card, .project, .tl-item, .news-card.
   Those are the ones that multiply. Open the skills section and there
   are six of them at blur(12px), each the width of the column, each
   re-blurring whatever scrolls behind it. That is the repeated cost,
   and it is the one worth spending at the full tier only.

   So mid keeps the chrome frosted and gives the cards an opaque
   surface derived from the current mode's own palette.
------------------------------------------------------------------- */
html[data-perf="mid"] .card,
html[data-perf="mid"] .project,
html[data-perf="mid"] .tl-item,
html[data-perf="mid"] .news-card,
html[data-perf="mid"] .calendar-box,
html[data-perf="mid"] .socials li,
html[data-perf="mid"] #cvButton {
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
}
html[data-perf="mid"] .card,
html[data-perf="mid"] .project,
html[data-perf="mid"] .tl-item,
html[data-perf="mid"] .news-card,
html[data-perf="mid"] .calendar-box {
  background: color-mix(in srgb, var(--bg2) 94%, var(--text) 6%);
}

/* The grain rule above only named the mid tier. At low the layer is
   display:none so nothing was ever painted, but leaving a running
   animation declared on a hidden element is untidy and it made the
   measurement read as though the grain were still moving. */
html[data-perf="low"] .grain { animation: none; }
