/* Palette: ScreenSpace ground + player-1 blue, white text, flat panel.
   The panel is a solid fill rather than frosted glass: the reference this layout came from blurs
   nothing, and a backdrop-filter here cost half the frame rate. The panel sits at the ground colour
   and lights up around the cursor, alongside the mesh behind the page doing the same to its
   triangles. The panel is only part-opaque, so the lit mesh reads through it, but far too faintly
   to light the panel itself - hence a light of its own as well.
   Decisions and reasoning: secondbrain/life/portfolio-site.md */
:root {
  --ground: #13181F;
  --blue: #4D4DFF;
  --blue-rgb: 77, 77, 255;
  --text: #FFFFFF;
  --muted: #8A93A3;
  --surface: rgba(19, 24, 31, 0.72); /* the ground, let through a little so the mesh reads behind the text */
  --surface-edge: rgba(255, 255, 255, 0.14);
  --surface-highlight: rgba(138, 147, 163, 0.1); /* the reference's inset top edge, in our grey */
  --radius: 20px;
  --gutter: 56px;
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

* { box-sizing: border-box; }
html { height: 100%; }
body {
  margin: 0;
  min-height: 100%;
  background: var(--ground);
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* Backdrop mesh: fixed, behind everything */
#mesh {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
}

/* Two columns filling the viewport */
.layout {
  position: relative;
  z-index: 1;
  height: 100vh;
  max-width: 1200px;
  margin: 0 auto;
  padding: var(--gutter) 40px;
  display: grid;
  grid-template-columns: minmax(260px, 5fr) 7fr;
  gap: var(--gutter);
}

/* Left column: identity */
.identity {
  display: flex;
  flex-direction: column;
  padding-top: 40px;
  min-height: 0;
}
.identity h1 {
  margin: 0;
  font-size: clamp(2.2rem, 4vw, 3.2rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.1;
}
.identity h2 {
  margin: 12px 0 0;
  font-size: 1.25rem;
  font-weight: 500;
  color: var(--text);
}
/* Kept while the slot is empty: the line is unresolved, not abandoned. See index.html. */
.tagline {
  margin: 14px 0 0;
  max-width: 28ch;
  color: var(--muted);
}

.social {
  list-style: none;
  margin: auto 0 0; /* pinned to the bottom of the column */
  padding: 0;
  display: flex;
  gap: 20px;
  flex-shrink: 0;
}

/* Detail card: height animates via the 0fr -> 1fr grid trick, so it opens without knowing its size */
.detail-wrap {
  display: grid;
  grid-template-rows: 0fr;
  /* A closed card has to take up no vertical room at all. The rows collapse to 0fr but margins are
     not part of that collapse, so the spacing is carried by .open rather than set here - otherwise
     a closed card leaves its own margins behind. On wide that is invisible, because .social is
     pinned with margin-top:auto and simply absorbs it; on narrow the card sits between two entries
     and it reads as a gap under the one it was opened from. It eases on the same curve and the same
     0.45s as the height, so the gap closes with the card instead of snapping shut after it. */
  margin: 0;
  min-height: 0;
  flex: 0 1 auto;
  opacity: 0;
  transform: translateY(10px);
  visibility: hidden;
  transition:
    grid-template-rows 0.45s cubic-bezier(0.2, 0.7, 0.2, 1),
    margin-block-start 0.45s cubic-bezier(0.2, 0.7, 0.2, 1),
    margin-block-end 0.45s cubic-bezier(0.2, 0.7, 0.2, 1),
    opacity 0.3s ease,
    transform 0.45s cubic-bezier(0.2, 0.7, 0.2, 1),
    visibility 0s linear 0.45s;
}
.detail-wrap.open {
  grid-template-rows: 1fr;
  margin-block: 32px 28px;
  opacity: 1;
  transform: none;
  visibility: visible;
  transition-delay: 0s; /* one value covers every property above, visibility's 0.45s included */
}
.detail {
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  position: relative; /* the light is positioned against this, and clipped by the overflow above */
  padding: 16px 20px 20px;
  background: var(--surface); /* the same surface as the content panel, mesh showing through alike */
  border: 1px solid var(--surface-edge);
  border-radius: 16px;
  box-shadow: inset 0 1px 0 var(--surface-highlight), 0 16px 40px rgba(0, 0, 0, 0.3);
  transition:
    padding-block-start 0.45s cubic-bezier(0.2, 0.7, 0.2, 1),
    padding-block-end 0.45s cubic-bezier(0.2, 0.7, 0.2, 1),
    border-block-start-width 0.45s cubic-bezier(0.2, 0.7, 0.2, 1),
    border-block-end-width 0.45s cubic-bezier(0.2, 0.7, 0.2, 1);
}
/* A 0fr row cannot shrink past the item's own padding and border: with box-sizing: border-box those
   are the floor of its border box, so min-height: 0 still left 16 + 20 + 2 = 38px standing. That is
   the gap itself - invisible on wide, where .social's auto margin absorbs it, and plainly visible on
   narrow, where the closed card sits between two entries. Collapsing them with the row is what gets
   it to actual zero, on the same curve and the same 0.45s so it stays one movement. */
.detail-wrap:not(.open) .detail {
  padding-block: 0;
  border-block-width: 0;
}
.detail-body {
  position: relative;
  z-index: 1; /* over the light, not under it */
  min-height: 0;
  flex: 1 1 auto;
  overflow-y: auto;
  scrollbar-width: none;
}
.detail-body::-webkit-scrollbar { display: none; }
/* while there is more below, fade the bottom edge of the content as a scroll cue */
.detail-body[data-more] {
  mask-image: linear-gradient(to bottom, #000 calc(100% - 48px), transparent);
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 48px), transparent);
}
.detail-head {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
  flex-shrink: 0;
}
.detail-head h3 {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
  color: var(--text);
}
.detail-close {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--surface-edge);
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.2s ease, background-color 0.2s ease;
}
.detail-close svg {
  width: 11px;
  height: 11px;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  fill: none;
}
.detail-close:hover, .detail-close:focus-visible { color: var(--text); background: rgba(255, 255, 255, 0.06); }
.detail-body p { margin: 0 0 10px; font-size: 0.95rem; color: var(--muted); }
.detail-body p:last-child { margin-bottom: 0; }
.detail-body img, .detail-body video {
  display: block;
  width: 100%;
  border-radius: 10px;
  margin: 12px 0;
}
.slot, .placeholder {
  border: 1px dashed rgba(255, 255, 255, 0.22);
  border-radius: 10px;
  color: var(--muted);
  font-size: 0.8rem;
}
.slot {
  aspect-ratio: 16 / 9;
  display: grid;
  place-items: center;
  margin: 4px 0 12px;
}
.detail-body p.placeholder { padding: 10px 12px; }

/* Screenshot cycler. One 16:9 frame with every shot stacked inside it, crossfaded on opacity.
   The frame reserves its own height, so an image landing late never jumps the card mid-open. */
.shots {
  --shots-interval: 5s; /* how long each screenshot holds before the next */
  margin: 4px 0 12px;
}
.shots-frame {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--surface-edge);
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.25);
  cursor: pointer;
}
.detail-body .shots-frame img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0; /* .detail-body img spaces a standalone image; these are stacked, not spaced */
  border-radius: 0;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.35s ease;
}
.detail-body .shots-frame img.is-shown { opacity: 1; }
.shots-frame:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
.shots-dots {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin-top: 8px;
}
.shots-dots button {
  position: relative;
  width: 6px;
  height: 6px;
  padding: 0;
  border: 0;
  border-radius: 3px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.25);
  cursor: pointer;
  transition: width 0.3s ease, background-color 0.2s ease;
}
.shots-dots button:hover { background: rgba(255, 255, 255, 0.5); }
/* The active dot stretches into a pill and fills over the dwell time. The fill is the clock -
   shots.js advances on its animationend - so the bar and the advance cannot drift apart. */
.shots-dots button.is-on { width: 22px; background: rgba(255, 255, 255, 0.2); }
/* The bar's width comes from --shots-progress, which shots.js steps four times a second, rather
   than from a CSS animation. One interval both paints the bar and advances the shot, so the two
   cannot drift; at 20 steps across a 5s dwell that is about a pixel each on a 22px bar. */
.shots-dots button.is-on::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--blue);
  transform: scaleX(var(--shots-progress, 0));
  transform-origin: left center;
}
/* Trailer. The iframe is only built when the card opens, so YouTube loads nothing on page load. */
.embed {
  position: relative;
  aspect-ratio: 16 / 9;
  margin: 4px 0 12px;
  overflow: hidden;
  border: 1px solid var(--surface-edge);
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.25);
}
.embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
.social a {
  display: inline-flex;
  color: var(--muted);
  transition: color 0.2s ease;
}
.social a:hover, .social a:focus-visible { color: var(--text); }
.social svg { width: 24px; height: 24px; }

/* Right column: tab strip + panel */
.window {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.tabs {
  display: flex;
  gap: 6px;
  padding: 0 0 0 24px; /* clear of the panel's rounded corner */
}
.tab {
  position: relative;
  padding: 10px 20px 12px;
  color: var(--muted);
  text-decoration: none;
  font-size: 0.95rem;
  font-weight: 500;
  border: 1px solid var(--surface-edge);
  border-bottom-color: transparent; /* open at the bottom while attached to the panel */
  border-radius: 12px 12px 0 0;
  background: var(--ground); /* solid on purpose: a see-through chip lets text scroll through the
                                pinned row on narrow screens, which took four attempts to kill */
  box-shadow: inset 0 1px 0 var(--surface-highlight);
  transition: color 0.25s ease-out, border-radius 0.3s ease, border-bottom-color 0.3s ease,
              background-color 0.15s ease, padding 0.25s ease;
}
/* Stuck, each tab flips: instead of sitting on the panel it hangs from the top of the screen.
   The rounding moves to the bottom corners and the open edge moves to the top, so the flat,
   square top sits flush against the screen edge - the mirror of the resting state. */
.tabs.is-stuck .tab {
  padding: 16px 20px 18px; /* taller once pinned: a comfortable tap target at the screen edge */
  border-radius: 12px;
  border-bottom-color: var(--surface-edge); /* closed all round - a button, not a tab on a panel */
}
.tab .rule {
  position: absolute;
  left: 20px;
  right: 20px;
  bottom: 6px;
  height: 2px;
  border-radius: 2px;
  background: var(--blue);
  transform: scaleX(0);
  transform-origin: center;
  transition: transform 0.25s ease-out;
}
.tab:hover { color: var(--text); }
.tab.is-active { color: var(--text); }
.tab.is-active .rule { transform: scaleX(1); }

.surface {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--surface-edge);
  border-radius: var(--radius);
  box-shadow:
    inset 0 1px 0 var(--surface-highlight),
    0 0 0 1px rgba(255, 255, 255, 0.03),
    0 24px 60px rgba(0, 0, 0, 0.35);
  position: relative; /* the glow is positioned against this, and clipped by the overflow above */
}
/* The cursor light the two panels carry. Separate from the mesh's own glow behind the page, which
   reads through the part-transparent fill but is far too faint to light the panel itself.
   A gradient this size is expensive to repaint, so it is painted once and only ever moved with a
   transform; glow.js does nothing but write that transform. */
.glow {
  position: absolute;
  top: 0;
  left: 0;
  --glow-size: 300px; /* one place to tune it; glow.js reads this for its approach distance too */
  width: var(--glow-size);
  height: var(--glow-size);
  margin: calc(var(--glow-size) / -2) 0 0 calc(var(--glow-size) / -2);
  border-radius: 50%;
  background: radial-gradient(closest-side circle,
    rgba(var(--blue-rgb), 0.14), rgba(var(--blue-rgb), 0.05) 55%, rgba(var(--blue-rgb), 0));
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.35s ease;
  will-change: transform;
}
.glow.is-lit { opacity: 1; }
.surface-scroll { position: relative; z-index: 1; } /* content reads over the light, not under it */
.surface-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 8px 44px 44px;
  scrollbar-width: none; /* scrolls, but shows no bar */
  outline: none;
}
.surface-scroll::-webkit-scrollbar { display: none; }
.surface-scroll[data-more] {
  mask-image: linear-gradient(to bottom, #000 calc(100% - 56px), transparent);
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 56px), transparent);
}

/* Sections and entries */
.section { padding-top: 36px; }
.section + .section { padding-top: 56px; }
.section > h3 {
  margin: 0 0 18px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--surface-edge);
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.section p { margin: 0 0 12px; color: var(--muted); }
.section p:last-child { margin-bottom: 0; }

.entries { list-style: none; margin: 0; padding: 0; }
.entry {
  display: grid;
  grid-template-columns: 110px 1fr;
  gap: 20px;
  padding: 18px 20px;
  margin: 0 -20px;
  border-radius: 14px;
  cursor: pointer;
  outline: none;
  transition: opacity 0.25s ease, background-color 0.25s ease, box-shadow 0.25s ease;
}
.entries:hover .entry { opacity: 0.55; }
.entries .entry:hover, .entries .entry:focus-visible, .entries:hover .entry.is-selected {
  opacity: 1;
  background: rgba(255, 255, 255, 0.04);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.entry.is-selected {
  opacity: 1;
  background: rgba(var(--blue-rgb), 0.08);
  box-shadow: inset 0 0 0 1px rgba(var(--blue-rgb), 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.entries .entry.is-selected:hover, .entries:hover .entry.is-selected {
  background: rgba(var(--blue-rgb), 0.1);
  box-shadow: inset 0 0 0 1px rgba(var(--blue-rgb), 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.when {
  display: block;
  padding-top: 3px;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.entry h4 {
  margin: 0 0 6px;
  font-size: 1rem;
  font-weight: 600;
  color: var(--text);
}
.entry .org { color: var(--muted); font-weight: 500; }
.entry-link {
  color: inherit;
  text-decoration: none;
  transition: color 0.2s ease;
}
.entry-link:hover { color: var(--blue); }
.arrow {
  display: inline-block;
  margin-left: 6px;
  font-size: 0.85em;
  transition: transform 0.2s ease;
}
.entry-link:hover .arrow { transform: translate(2px, -2px); }
.premise { color: var(--text) !important; font-style: italic; }

.tags {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.tags li {
  padding: 3px 11px;
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--text);
  border-radius: 999px;
  background: rgba(var(--blue-rgb), 0.16);
  border: 1px solid rgba(var(--blue-rgb), 0.28);
}
.resume { margin-top: 28px; }
.resume a { color: var(--text); font-weight: 600; }

/* One column on narrow screens: the document scrolls, the tabs stick */
@media (max-width: 900px) {
  :root { --gutter: 32px; }
  .layout {
    height: auto;
    display: block;
    padding: var(--gutter) 20px;
  }
  .identity { padding-top: 0; margin-bottom: 40px; gap: 18px; }
  .window { display: block; }
  .tabs {
    position: sticky;
    top: 0;
    z-index: 2;
    padding-top: 8px;
    /* Eased on the same 250ms as the vignette. Not a transform: .tabs::before is position:fixed,
       and a transformed ancestor would become its containing block and tear it off the viewport. */
    transition: top 0.25s ease, padding-top 0.25s ease;
  }
  /* Pinned, the row rides 12px above the edge so each button's rounded top crops away against it -
     no sliver of screen showing through the curve. The vignette fades content out underneath. */
  .tabs.is-stuck {
    top: -12px;
    padding-top: 0;
  }
  /* Always in the page so it has something to fade from - sticking only changes its opacity.
     Built by .is-stuck alone it appeared fully formed the instant the strip detached. */
  .tabs::before {
    content: '';
    position: fixed;
    inset: 0 0 auto 0;
    height: 84px;
    background: linear-gradient(to bottom, var(--ground) 0%, rgba(19, 24, 31, 0.82) 50%, rgba(19, 24, 31, 0) 100%);
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.25s ease;
    z-index: -1;
  }
  .tabs.is-stuck::before { opacity: 1; }
  /* Clipping again, not visible: the light is a child of the panel, and with nothing to clip it
     the glow spilled across the whole screen. Nothing else here overflows - the panel is
     auto-height on narrow - and .tabs is a sibling, so its stickiness is untouched. */
  .surface { overflow: hidden; display: block; }
  .surface-scroll { overflow: visible; padding: 8px 22px 28px; }
  .entry { grid-template-columns: 1fr; gap: 6px; padding: 14px 12px; margin: 0 -12px; }
  .entries:hover .entry { opacity: 1; }
  /* the detail card sits under the clicked entry here. The bleed is always on; the vertical
     spacing only exists while the card is open, same as the base rule. */
  .entries .detail-wrap { margin-inline: -12px; }
  .entries .detail-wrap.open { margin-block: 6px 14px; }
  .identity .detail-wrap { display: none; } /* it moves under the entry when opened here */
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; scroll-behavior: auto !important; }
  /* No self-cycling here (shots.js checks the same query), so there is no countdown to draw.
     The active dot goes back to being a plain dot and the screenshots only move when clicked. */
  .shots-dots button.is-on { width: 6px; background: var(--blue); }
  .shots-dots button.is-on::after { display: none; }
}
