/* ============================================================
   nakul patel — minimal (portfolio v5)
   one column · all lowercase · paper white & ink, light + dark
   ============================================================ */

:root {
  --bg: #fcfbfe;
  --text: #17171a;
  --muted: #55555d;
  --faint: #74747c;
  --line: #e7e7e3;
  --hover: #f3f3f0;
  --sel: #eceae5;
  --dot: #4a9e6a;

  /* glass: a translucent fill, a hairline edge, and a highlight where the
     light catches the top lip. --spec is the pointer-tracked reflection. */
  --glass: rgba(255, 255, 255, 0.55);
  --glass-line: rgba(23, 23, 26, 0.08);
  --glass-hi: rgba(255, 255, 255, 0.95);
  --glass-lo: rgba(23, 23, 26, 0.03);
  --glass-spec: rgba(255, 255, 255, 0.9);
  --glass-drop: rgba(20, 20, 30, 0.06);
  --aurora-1: rgba(139, 92, 246, 0.34);
  --aurora-2: rgba(168, 85, 247, 0.27);
  --aurora-3: rgba(124, 58, 237, 0.24);
  --aurora-4: rgba(109, 40, 217, 0.20);
  /* film grain. grey speckle at plain alpha: blend modes collapse at both
     ends of the range, so overlay is invisible on paper white and soft-light
     is invisible on near-black. */
  --grain: 0.05;

  --sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
  --accent: #8b5cf6;
  --max: 40rem;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --gap: clamp(44px, 7vw, 60px);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0f0f10;
    --text: #ededee;
    --muted: #a1a1a9;
    --faint: #7e7e87;
    --line: #232326;
    --hover: #17171a;
    --sel: #26262b;
    --dot: #5cba80;

    --glass: rgba(255, 255, 255, 0.045);
    --glass-line: rgba(255, 255, 255, 0.10);
    --glass-hi: rgba(255, 255, 255, 0.13);
    --glass-lo: rgba(0, 0, 0, 0.28);
    --glass-spec: rgba(255, 255, 255, 0.10);
    --glass-drop: rgba(0, 0, 0, 0.35);
    --accent: #a78bfa;
    --aurora-1: rgba(139, 92, 246, 0.26);
    --aurora-2: rgba(192, 132, 252, 0.16);
    --aurora-3: rgba(109, 40, 217, 0.22);
    --aurora-4: rgba(139, 92, 246, 0.16);
    /* lighter than the page, because grain on near-black can only show by
       lifting it. 0.035 is about two levels out of 255: texture, still dark. */
    --grain: 0.035;
  }
}

:root[data-theme="dark"] {
  --bg: #0f0f10;
  --text: #ededee;
  --muted: #a1a1a9;
  --faint: #7e7e87;
  --line: #232326;
  --hover: #17171a;
  --sel: #26262b;
  --dot: #5cba80;

  --glass: rgba(255, 255, 255, 0.045);
  --glass-line: rgba(255, 255, 255, 0.10);
  --glass-hi: rgba(255, 255, 255, 0.13);
  --glass-lo: rgba(0, 0, 0, 0.28);
  --glass-spec: rgba(255, 255, 255, 0.10);
  --glass-drop: rgba(0, 0, 0, 0.35);
  --accent: #a78bfa;
  --aurora-1: rgba(139, 92, 246, 0.26);
  --aurora-2: rgba(192, 132, 252, 0.16);
  --aurora-3: rgba(109, 40, 217, 0.22);
  --aurora-4: rgba(139, 92, 246, 0.16);
  --grain: 0.035;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.75;
  letter-spacing: -0.005em;
  font-feature-settings: 'ss01', 'cv05';
  -webkit-font-smoothing: antialiased;
  transition: background 0.2s ease, color 0.2s ease;
}

::selection { background: var(--text); color: var(--bg); }

img { display: block; max-width: 100%; }

a {
  color: var(--text);
  text-decoration: none;
}

/* ------------------------------------------------------------
   page
   ------------------------------------------------------------ */
.page {
  max-width: var(--max);
  margin: 0 auto;
  padding: clamp(64px, 14vh, 128px) 24px clamp(56px, 10vh, 88px);
}

.block {
  margin-top: var(--gap);
  /* clear the pinned top bar, so a jump from the palette doesn't land under it */
  scroll-margin-top: 84px;
}

h2 {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  font-weight: 500;
  color: var(--faint);
  letter-spacing: 0.01em;
  margin-bottom: 14px;
}

/* ------------------------------------------------------------
   icons — one stroke weight, sized in place, coloured by context
   ------------------------------------------------------------ */
.ico {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.ico-fill {
  fill: currentColor;
  stroke: none;
}

/* ------------------------------------------------------------
   intro
   ------------------------------------------------------------ */
/* ------------------------------------------------------------
   theme switcher
   ------------------------------------------------------------ */
/* the sentinel sits one pixel above the bar; when it leaves the viewport the
   bar has pinned, which is cheaper than watching every scroll event */
.topbar-sentinel {
  height: 1px;
  margin-bottom: -1px;
}

.topbar {
  position: sticky;
  top: 0;
  z-index: 55;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 0;
  margin-bottom: 26px;
}

/* the frosted backing lives on a pseudo-element: .rise owns the transition
   property on .topbar itself, so fading it there would be overridden */
.topbar::before {
  content: '';
  position: absolute;
  inset: 0 -16px;
  z-index: -1;
  border-radius: 0 0 14px 14px;
  background: var(--glass);
  backdrop-filter: blur(16px) saturate(180%);
  -webkit-backdrop-filter: blur(16px) saturate(180%);
  border-bottom: 1px solid var(--glass-line);
  box-shadow: 0 6px 22px var(--glass-drop);
  opacity: 0;
  transition: opacity 0.25s ease;
  pointer-events: none;
}
.topbar.pinned::before { opacity: 1; }

/* resume sits at the top, opposite the theme switch, where a recruiter
   looks first rather than after scrolling the whole page */
.resume-link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 13.5px;
  color: var(--muted);
  transition: color 0.15s ease;
}
.resume-link:hover { color: var(--text); }
.resume-link > span:not(.out) {
  text-decoration: underline;
  text-decoration-style: dotted;
  text-decoration-color: var(--faint);
  text-underline-offset: 4px;
  text-decoration-thickness: 1px;
  transition: text-decoration-color 0.18s ease;
}
.resume-link:hover > span:not(.out) { text-decoration-color: var(--text); }
.resume-link .out {
  display: inline-block;
  font-size: 10px;
  color: var(--faint);
  opacity: 0;
  transform: translate(-4px, 3px);
  transition: opacity 0.18s ease, transform 0.22s var(--ease);
}
.resume-link:hover .out { opacity: 1; transform: none; color: var(--text); }

.theme {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: 999px;
}
.theme button {
  font: inherit;
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--faint);
  background: none;
  border: 0;
  padding: 1px 13px;
  border-radius: 999px;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}
.theme button:hover { color: var(--text); }
.theme button[aria-pressed="true"] {
  color: var(--text);
  background: var(--sel);
}
.theme button:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 2px;
}

.intro h1 {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.role { color: var(--muted); }

/* ------------------------------------------------------------
   widgets
   ------------------------------------------------------------ */
.widgets {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-top: 20px;
}
.widget {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 11px 13px 12px;
  border-radius: 12px;
  will-change: transform;
}
.w-head {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  letter-spacing: 0.03em;
  color: var(--faint);
}
.w-head .ico { width: 13px; height: 13px; }
.w-val {
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--text);
  text-wrap: pretty;
}
.dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--dot);
  flex-shrink: 0;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--dot) 18%, transparent);
}

.block p { color: var(--muted); text-wrap: pretty; }
.block p + p { margin-top: 14px; }

/* ------------------------------------------------------------
   work
   ------------------------------------------------------------ */
.list { list-style: none; }
.list li {
  padding: 11px 0;
  border-bottom: 1px solid var(--line);
}
.list li:last-child { border-bottom: 0; }

/* two rows per entry, so company, dates, role and city each get a slot
   of their own instead of running together behind separator dots */
.li-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 18px;
}
.li-org {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-weight: 500;
  color: var(--text);
}
.li-role {
  font-size: 14.5px;
  color: var(--muted);
}
.li-when,
.li-where {
  flex-shrink: 0;
  font-size: 13px;
  color: var(--faint);
}
.li-when { font-variant-numeric: tabular-nums; }

.at { color: var(--faint); margin: 0 2px; }

/* ------------------------------------------------------------
   education
   ------------------------------------------------------------ */
.edu-note {
  margin-top: 12px;
  font-size: 13px;
  color: var(--faint);
}

/* ------------------------------------------------------------
   projects
   ------------------------------------------------------------ */
.projects { list-style: none; }
.projects a {
  display: block;
  padding: 12px 12px 13px;
  margin: 0 -12px;
  border-radius: 8px;
  transition: background 0.15s ease;
}
.projects a:hover { background: var(--hover); }

.p-name {
  display: block;
  font-weight: 500;
}
.p-desc {
  display: block;
  color: var(--muted);
  font-size: 14.5px;
  text-wrap: pretty;
}
.p-tags {
  display: block;
  margin-top: 3px;
  font-size: 12.5px;
  color: var(--faint);
}

.arrow {
  display: inline-block;
  margin-left: 5px;
  font-size: 11px;
  color: var(--faint);
  transform: translateY(-1px);
  transition: transform 0.18s ease, color 0.18s ease;
}
.projects a:hover .arrow {
  color: var(--text);
  transform: translate(2px, -3px);
}


/* ------------------------------------------------------------
   elsewhere
   ------------------------------------------------------------ */
.links {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 22px;
}
.links a {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--muted);
  transition: color 0.15s ease;
}
.links a:hover { color: var(--text); }

/* a dotted rule under anything clickable, going solid on hover */
.links a > span:not(.out),
.projects .p-title {
  text-decoration: underline;
  text-decoration-style: dotted;
  text-decoration-color: var(--faint);
  text-underline-offset: 4px;
  text-decoration-thickness: 1px;
  transition: text-decoration-color 0.18s ease;
}
.links a:hover > span:not(.out),
.projects a:hover .p-title { text-decoration-color: var(--text); }

/* anything that opens a new tab grows a little arrow on hover */
.links a[target="_blank"] .out {
  display: inline-block;
  margin-left: 1px;
  font-size: 10px;
  color: var(--faint);
  opacity: 0;
  transform: translate(-4px, 3px);
  transition: opacity 0.18s ease, transform 0.22s var(--ease);
}
.links a[target="_blank"]:hover .out {
  opacity: 1;
  transform: none;
  color: var(--text);
}

/* ------------------------------------------------------------
   footer
   ------------------------------------------------------------ */
.foot {
  margin-top: calc(var(--gap) + 12px);
  padding-top: 20px;
  border-top: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  font-size: 13px;
  color: var(--faint);
}

/* ------------------------------------------------------------
   konami note
   ------------------------------------------------------------ */
.note {
  position: fixed;
  left: 50%;
  bottom: 28px;
  transform: translateX(-50%);
  font-size: 13px;
  color: var(--bg);
  background: var(--text);
  padding: 7px 16px;
  border-radius: 999px;
  animation: note-in 0.24s ease;
}
.note[hidden] { display: none; }
@keyframes note-in {
  from { opacity: 0; transform: translate(-50%, 8px); }
  to { opacity: 1; transform: translate(-50%, 0); }
}

/* ------------------------------------------------------------
   reveal on scroll
   ------------------------------------------------------------ */
html.js .rise {
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
  transition-delay: var(--d, 0ms);
}
html.js .rise.in {
  opacity: 1;
  transform: none;
}

/* ------------------------------------------------------------
   small screens
   ------------------------------------------------------------ */
@media (max-width: 480px) {
  .li-row { gap: 12px; }
  .li-when, .li-where { font-size: 12.5px; }
}

@media (max-width: 600px) {
  .widgets { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  html.js .rise { opacity: 1; transform: none; }
}

/* ============================================================
   scroll rail
   ============================================================ */
.rail {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 60;
  width: 100%;
  height: 2px;
  background: var(--text);
  opacity: 0.22;
  transform: scaleX(0);
  transform-origin: 0 50%;
  will-change: transform;
}


/* ============================================================
   theme wipe — the View Transitions snapshot is driven from JS,
   so the default cross-fade has to get out of the way
   ============================================================ */
::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}
::view-transition-old(root) { z-index: 1; }
::view-transition-new(root) { z-index: 2; }

/* ============================================================
   command palette
   ============================================================ */
.hint {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font: inherit;
  font-size: 13px;
  color: var(--faint);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  transition: color 0.15s ease;
}
.hint:hover { color: var(--text); }

kbd {
  font-family: var(--sans);
  font-size: 11px;
  line-height: 1;
  padding: 3px 5px;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: var(--hover);
  color: inherit;
}

.palette {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  justify-content: center;
  padding: 13vh 20px 20px;
  background: rgba(20, 20, 22, 0.28);
  background: color-mix(in srgb, var(--bg) 58%, transparent);
  backdrop-filter: blur(6px);
  animation: palette-fade 0.16s ease;
}
.palette[hidden] { display: none; }
@keyframes palette-fade { from { opacity: 0; } to { opacity: 1; } }

.palette-box {
  width: 100%;
  max-width: 30rem;
  height: max-content;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.18);
  overflow: hidden;
  animation: palette-pop 0.2s var(--ease);
}
@keyframes palette-pop {
  from { opacity: 0; transform: translateY(-8px) scale(0.985); }
  to { opacity: 1; transform: none; }
}

.palette-input {
  display: block;
  width: 100%;
  font: inherit;
  font-size: 15px;
  color: var(--text);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--line);
  padding: 15px 18px;
  outline: none;
}
.palette-input::placeholder { color: var(--faint); }

.palette-list {
  list-style: none;
  max-height: 44vh;
  overflow-y: auto;
  padding: 6px;
}
.palette-list li {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  border-radius: 8px;
  font-size: 14px;
  color: var(--muted);
  cursor: pointer;
}
.palette-list li .ico { color: var(--faint); }
.palette-list li[aria-selected="true"] { background: var(--sel); color: var(--text); }
.palette-list li[aria-selected="true"] .ico { color: var(--text); }

/* the characters your query actually matched */
.palette-list mark {
  background: none;
  color: var(--text);
  font-weight: 600;
}

.p-kind {
  margin-left: auto;
  font-size: 11.5px;
  color: var(--faint);
}
.palette-empty {
  padding: 15px 18px;
  font-size: 14px;
  color: var(--faint);
}

.palette-foot {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 10px 16px;
  border-top: 1px solid var(--line);
  font-size: 11.5px;
  color: var(--faint);
}

@media (max-width: 520px) {
  .palette { padding-top: 8vh; }
  .palette-foot { display: none; }
}

/* ============================================================
   focus — one visible ring for every interactive element.
   Only :focus-visible, so a mouse click never draws one.
   ============================================================ */
:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 3px;
  border-radius: 4px;
}
/* the skip link targets this, and a programmatic focus shouldn't ring */
#main:focus { outline: none; }

/* ============================================================
   skip link — first tab stop, off-screen until it has focus
   ============================================================ */
.skip {
  position: fixed;
  top: 10px;
  left: 10px;
  z-index: 200;
  padding: 10px 16px;
  font-size: 13px;
  color: var(--bg);
  background: var(--text);
  border-radius: 8px;
  text-decoration: none;
  transform: translateY(-180%);
  transition: transform 0.18s var(--ease);
}
.skip:focus { transform: none; }

/* announced, never seen */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ============================================================
   print — someone will PDF this and attach it to an application
   ============================================================ */
@media print {
  .rail, .topbar, .hint, .palette, .note, .skip { display: none !important; }

  html, body {
    background: #fff !important;
    color: #000 !important;
  }
  .page {
    max-width: none;
    padding: 0;
  }
  html.js .rise {
    opacity: 1 !important;
    transform: none !important;
  }

  h2 { color: #444; }
  .w-val, .li-org, .p-name { color: #000; }
  .role, .block p, .li-role, .p-desc, .edu-note { color: #222; }

  .widget {
    background: none;
    border: 1px solid #ccc;
  }
  .widget, .list li, .projects li { break-inside: avoid; }

  a { color: #000; text-decoration: none; }
  /* paper can't be clicked, so spell the destinations out */
  .links a::after {
    content: " — " attr(href);
    font-size: 10px;
    color: #555;
  }
  .ico, .arrow { display: none; }
}

/* ============================================================
   404
   ============================================================ */
.lost {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.lost-code {
  font-size: 13px;
  color: var(--faint);
  margin-bottom: 14px;
}
.lost h1 {
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.015em;
}
.lost .role { margin-top: 8px; }
.lost-back { margin-top: 30px; }
.lost-back a {
  color: var(--muted);
  border-bottom: 1px solid var(--line);
  padding-bottom: 2px;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.lost-back a:hover { color: var(--text); border-bottom-color: var(--text); }

/* ============================================================
   the background — a drifting mesh under a layer of film grain.

   Three layers, all fixed, all behind the content:

     html::before  two pools, upper left and upper right   (z -3)
     body::before  two pools, lower right and lower left   (z -2)
     body::after   the grain                               (z -1)

   The pools are split across two elements so they can drift on
   different clocks. One layer on its own can only slide as a block;
   two sliding past each other keep changing how they overlap, which
   is what stops the mesh reading as a set of circles.

   Only transform and opacity animate. Both are composited, so the
   blur behind them is rasterised once rather than on every frame.

   Blur also needs something behind it or it does nothing, so this
   doubles as the thing the glass below picks up and bends.
   ============================================================ */
html::before,
body::before {
  content: '';
  position: fixed;
  inset: -25%;
  z-index: -3;
  pointer-events: none;
  filter: blur(52px);
  will-change: transform, opacity;
}

html::before {
  background:
    radial-gradient(62% 52% at 4% 2%, var(--aurora-1), transparent 64%),
    radial-gradient(56% 46% at 98% 10%, var(--aurora-2), transparent 66%);
  animation: drift-a 46s ease-in-out infinite;
}

body::before {
  z-index: -2;
  background:
    radial-gradient(60% 50% at 94% 98%, var(--aurora-3), transparent 66%),
    radial-gradient(54% 44% at 8% 92%, var(--aurora-4), transparent 66%);
  animation: drift-b 67s ease-in-out infinite;
}

/* Travel is measured against each layer's own box, which is 150% of the
   viewport with 25% of overhang on every side, so 12% of travel and a
   tenth of growth never pull an edge into view. The opacity swing does
   most of the visible work: the pools are soft enough that sliding them
   alone barely moves a pixel. */
@keyframes drift-a {
  0%, 100% { transform: translate3d(0, 0, 0); opacity: 0.82; }
  50%      { transform: translate3d(12%, -8%, 0); opacity: 1; }
}

@keyframes drift-b {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); opacity: 1; }
  50%      { transform: translate3d(-12%, 8%, 0) scale(1.1); opacity: 0.78; }
}

/* The blanket reduced-motion rule below cuts every animation to 0.01ms,
   which would leave these two parked on their last keyframe and a shade
   dimmer than intended. Sit them still at full strength instead. */
@media (prefers-reduced-motion: reduce) {
  html::before,
  body::before {
    animation: none;
    opacity: 1;
    will-change: auto;
  }
}

/* grain: one 200px tile of SVG noise, desaturated so it is speckle
   and not coloured confetti, repeated across the viewport */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: var(--grain);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 200px 200px;
}

/* ============================================================
   glass — an Apple-ish liquid-glass treatment
   ============================================================ */

.glass,
.widget,
.theme,
.palette-box {
  position: relative;
  background: var(--glass);
  backdrop-filter: blur(18px) saturate(180%);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
  border: 1px solid var(--glass-line);
  box-shadow:
    inset 0 1px 0 var(--glass-hi),
    inset 0 -1px 0 var(--glass-lo),
    0 10px 30px var(--glass-drop);
}

/* the specular reflection, tracked to the pointer by script.js */
.glass::after,
.widget::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(220px circle at var(--mx, 50%) var(--my, -20%),
              var(--glass-spec), transparent 68%);
  opacity: 0;
  transition: opacity 0.35s ease;
  mix-blend-mode: soft-light;
}
.glass:hover::after,
.widget:hover::after { opacity: 1; }

/* the switch already had its own fill; glass replaces it */
.theme { background: var(--glass); }

/* ============================================================
   skills
   ============================================================ */
.skill-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 14px;
}
.skill-tabs button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: inherit;
  font-size: 13px;
  color: var(--muted);
  background: none;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 6px 13px;
  cursor: pointer;
  transition: color 0.16s ease, border-color 0.16s ease, background 0.16s ease;
}
.skill-tabs button:hover { color: var(--text); }
.skill-tabs button[aria-selected="true"] {
  color: var(--text);
  background: var(--sel);
  border-color: transparent;
}
.tab-n {
  font-size: 11px;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}
.skill-tabs button[aria-selected="true"] .tab-n { color: var(--muted); }

.skill-panel {
  border-radius: 16px;
  padding: 16px;
  min-height: 108px;
}

.skill-chips {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
}
.skill-chips li {
  font-size: 13.5px;
  color: var(--text);
  background: var(--glass);
  border: 1px solid var(--glass-line);
  border-radius: 9px;
  padding: 6px 12px;
  box-shadow: inset 0 1px 0 var(--glass-hi);
  cursor: default;
  /* FLIP does the movement; these two carry the fade in and out */
  transition: opacity 0.22s ease, transform 0.22s var(--ease);
}
.skill-chips li[hidden] { display: none; }
.skill-chips li.dropping {
  opacity: 0;
  transform: scale(0.9);
}

.skill-empty {
  font-size: 13.5px;
  color: var(--faint);
}
.skill-empty[hidden] { display: none; }

/* ============================================================
   interests
   ============================================================ */
.marquee {
  margin-top: 20px;
  overflow: hidden;
  /* soften both ends so chips arrive and leave rather than popping */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
}

.interests {
  list-style: none;
  display: flex;
  width: max-content;
  animation: interests-scroll 38s linear infinite;
}

/* the spacing sits on the item, not as a flex gap: with a gap the duplicated
   half is half a gap wider than the original and the loop visibly jumps */
.interests li { margin-right: 7px; }

.marquee:hover .interests,
.marquee:focus-within .interests { animation-play-state: paused; }

@keyframes interests-scroll {
  to { transform: translateX(-50%); }
}
.interests li {
  font-size: 12.5px;
  color: var(--muted);
  background: var(--glass);
  border: 1px solid var(--glass-line);
  border-radius: 9px;
  padding: 5px 11px;
  box-shadow: inset 0 1px 0 var(--glass-hi);
  transition: color 0.15s ease;
}
.interests li:hover { color: var(--text); }

@media (prefers-reduced-motion: reduce) {
  .skill-chips li { transition: none; }

  /* no scrolling: show the nine once and let them wrap */
  .marquee { -webkit-mask-image: none; mask-image: none; }
  .interests { animation: none; width: auto; flex-wrap: wrap; row-gap: 7px; }
  .interests li[aria-hidden="true"] { display: none; }
}

/* blur is expensive; drop it where it buys nothing */
@media print {
  html::before, body::before, body::after { display: none; }
  .glass, .widget, .skill-panel, .skill-chips li {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: none;
    box-shadow: none;
  }
  .skill-tabs { display: none; }
}

/* a role badge beside a project name, for the ones that aren't solo builds */
.p-role {
  display: inline-block;
  margin-left: 8px;
  font-size: 11px;
  font-weight: 400;
  color: var(--muted);
  background: var(--glass);
  border: 1px solid var(--glass-line);
  border-radius: 999px;
  padding: 2px 8px;
  vertical-align: 1px;
}

/* ============================================================
   typewriter — the role line types itself out, backspaces,
   and moves to the next title
   ============================================================ */
.role { min-height: 1.75em; }

.caret {
  display: inline-block;
  width: 1.5px;
  height: 1.02em;
  margin-left: 3px;
  vertical-align: -2px;
  background: var(--accent);
  animation: caret-blink 1.1s steps(1) infinite;
}
@keyframes caret-blink {
  0%, 50% { opacity: 1; }
  50.01%, 100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .caret { display: none; }
}

/* ============================================================
   top row — résumé plus icon-only socials, opposite the theme
   switch. The bottom "elsewhere" list carries the same links
   with labels; up here they only need to be recognisable.
   ============================================================ */
.top-links {
  display: flex;
  align-items: center;
  gap: 13px;
  min-width: 0;
}

.top-sep {
  width: 1px;
  height: 14px;
  background: var(--line);
  flex-shrink: 0;
}

.socials {
  display: flex;
  align-items: center;
  gap: 2px;
}
.socials a {
  display: inline-flex;
  padding: 6px;
  border-radius: 8px;
  color: var(--faint);
  transition: color 0.15s ease, background 0.15s ease, transform 0.2s var(--ease);
}
.socials a:hover {
  color: var(--text);
  background: var(--glass);
  transform: translateY(-2px);
}
.socials .ico { width: 16px; height: 16px; }

@media (max-width: 560px) {
  .topbar { flex-wrap: wrap; row-gap: 12px; }
  .top-links { order: 2; width: 100%; }
  .theme { order: 1; margin-left: auto; }
}
