/* ============================================================
   Bhavya Kaushal - bioinformatics portfolio

   Structure mirrors the original bhavya-24.github.io section for
   section: side rail, numbered section labels, translucent content
   panels floating over a full-page DNA backdrop. The backdrop is
   the 4K clip rather than the CSS letter helix, and the theme is
   dark so the clip's black field composites away cleanly.

   Radius:  surfaces and media = 4px, interactive = full pill.
   Accent:  one amber. The blue is ambient light from the clip,
            never a UI colour.
   ============================================================ */

@font-face {
  font-family: 'Geist';
  src: url('../fonts/geist.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Geist Mono';
  src: url('../fonts/geist-mono.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
/* Display face. Space Grotesk is a grotesque with enough character
   in the g, a, k and its angular terminals to read as a different
   voice at heading sizes, while still sitting quietly beside Geist
   in a paragraph. The register suits computational biology: precise,
   technical, slightly odd. */
@font-face {
  font-family: 'Space Grotesk';
  src: url('../fonts/space-grotesk.woff2') format('woff2');
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --bg:        #070a0e;
  --panel:     rgba(9, 13, 19, 0.72);
  --panel-2:   rgba(148, 176, 208, 0.05);
  --line:      rgba(150, 178, 210, 0.15);
  --line-2:    rgba(150, 178, 210, 0.3);

  --ink:       #e9eef6;
  --ink-2:     #aab6c6;
  --ink-3:     #7d8b9e;

  --accent:    #e3a857;
  --accent-2:  #f0bd77;
  --accent-ink:#0c1015;

  /* Three roles, three faces, no overlap:
     display for headings, sans for reading, mono for data labels. */
  --font-display: 'Space Grotesk', 'Geist', system-ui, sans-serif;
  --font-sans:    'Geist', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-mono:    'Geist Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  --r-surface: 4px;
  --r-pill:    999px;

  --rail:      132px;
  --gut:       clamp(20px, 4vw, 56px);

  --fs-display: clamp(2.6rem, 6vw, 4.6rem);
  --fs-h2:      clamp(1.7rem, 3.2vw, 2.7rem);
  --fs-h3:      clamp(1.08rem, 1.6vw, 1.35rem);
  --fs-body:    clamp(0.98rem, 0.95rem + 0.16vw, 1.06rem);
  --fs-mono:    0.68rem;

  --ease: cubic-bezier(0.16, 1, 0.3, 1);

  color-scheme: dark;
}

*, *::before, *::after { box-sizing: border-box; }
/* No CSS smooth scrolling: ScrollSmoother owns scroll easing and
   the two fight over anchor jumps. */
html { -webkit-text-size-adjust: 100%; }

#smooth-content { will-change: transform; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: 1.65;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
body.is-loading { overflow: hidden; }

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: -0.022em;
  line-height: 1.12;
}
p { margin: 0; }
ul, ol, dl, dd { margin: 0; padding: 0; }
ul, ol { list-style: none; }
/* height:auto belongs with max-width:100%. Without it, an image
   given a CSS width still takes its height from the HTML height
   attribute and comes out stretched. */
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; }

.mono {
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

::selection { background: var(--accent); color: var(--accent-ink); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }

.ico { width: 1em; height: 1em; flex: none; }

.skip {
  position: fixed; top: 8px; left: 8px; z-index: 100;
  padding: 10px 18px;
  background: var(--accent); color: var(--accent-ink);
  border-radius: var(--r-pill); font-size: 0.85rem;
  transform: translateY(-160%);
  transition: transform 0.2s var(--ease);
}
.skip:focus-visible { transform: none; }

/* ---------- Loader ---------------------------------------- */

.loader {
  position: fixed; inset: 0; z-index: 90;
  background: var(--bg);
  display: grid; place-items: center;
  transition: opacity 0.7s var(--ease);
}
.loader.is-done { opacity: 0; pointer-events: none; }
.loader__inner { width: min(300px, 68vw); }
.loader__name { font-size: 0.95rem; color: var(--ink-2); margin-bottom: 14px; }
.loader__track { height: 1px; background: var(--line); position: relative; overflow: hidden; }
.loader__fill {
  position: absolute; inset: 0 auto 0 0; width: 0%;
  background: var(--accent);
  transition: width 0.25s linear;
}
.loader__pct { color: var(--ink-3); margin-top: 12px; }

/* ---------- Helix stage ----------------------------------- */

.stage {
  position: fixed; inset: 0; z-index: 0;
  pointer-events: none;
  /* Drops the clip's black field straight out of the page. */
  mix-blend-mode: screen;
  will-change: opacity;
}
.stage canvas { width: 100%; height: 100%; display: block; }

/* The vignette used to be a second full-screen element with its own
   mix-blend-mode. It is now erased straight into the canvas in
   main.js, which removes a composited blend layer per frame and is
   the single biggest win for background smoothness. */

/* ---------- Side rail ------------------------------------- */

.rail {
  position: fixed;
  top: 50%; left: 0;
  transform: translateY(-50%);
  z-index: 55;
  width: var(--rail);
  padding-left: clamp(20px, 2.4vw, 34px);
}
.rail ol { display: grid; gap: 13px; }
.rail a {
  font-family: var(--font-mono);
  font-size: 0.63rem;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink-3);
  display: inline-flex;
  align-items: center;
  gap: 9px;
  transition: color 0.3s var(--ease);
}
.rail a::before {
  content: '';
  width: 12px; height: 1px;
  background: currentColor;
  opacity: 0.5;
  transition: width 0.3s var(--ease);
}
.rail a:hover { color: var(--ink); }
.rail a.is-active { color: var(--accent); }
.rail a.is-active::before { width: 26px; opacity: 1; }

/* ---------- Buttons & links -------------------------------- */

.btn {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 12px 26px;
  border-radius: var(--r-pill);
  text-decoration: none;
  font-size: 0.92rem; line-height: 1; white-space: nowrap;
  border: 1px solid transparent;
  transition: transform 0.18s var(--ease), background 0.25s var(--ease),
              border-color 0.25s var(--ease), color 0.25s var(--ease);
}
.btn:active { transform: translateY(1px) scale(0.985); }
.btn--solid { background: var(--accent); color: var(--accent-ink); font-weight: 500; }
.btn--solid:hover { background: var(--accent-2); }

/* Magnetic wrapper. JS moves this, so the button keeps its own
   :active transform without the two fighting over `transform`. */
.magnetic { display: inline-flex; will-change: transform; }

/* Glow. Ported from the motion-primitives GlowEffect, recoloured to
   the site's amber plus the ambient helix blue: a four-stop rainbow
   would break the one-accent rule the rest of the page follows. */
.btn--solid {
  box-shadow: 0 0 0 0 rgba(227, 168, 87, 0);
  transition: transform 0.18s var(--ease), background 0.25s var(--ease),
              border-color 0.25s var(--ease), color 0.25s var(--ease),
              box-shadow 0.45s var(--ease);
}
.btn--solid:hover {
  box-shadow:
    0 0 24px -4px rgba(227, 168, 87, 0.6),
    0 0 48px -12px rgba(112, 146, 190, 0.5);
}

/* ScrollProgress. */
.progress {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 2px;
  z-index: 70;
  pointer-events: none;
  background: rgba(150, 178, 210, 0.1);
}
.progress span {
  display: block;
  height: 100%;
  width: 100%;
  transform: scaleX(0);
  transform-origin: 0 50%;
  background: linear-gradient(90deg, rgba(227, 168, 87, 0.35), var(--accent));
  will-change: transform;
}

/* Spotlight. A cursor-tracked pool of light inside a card. */
.spotlight {
  position: absolute;
  top: 0; left: 0;
  width: 200px; height: 200px;
  margin: -100px 0 0 -100px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(227, 168, 87, 0.17), transparent 68%);
  opacity: 0;
  pointer-events: none;
  z-index: 2;
  will-change: transform, opacity;
}

/* BorderTrail. A conic gradient masked down to the 1px border, so
   the highlight travels the perimeter. Flagship project only. */
.trail {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  z-index: 3;
  background: conic-gradient(from 0deg,
    transparent 0 66%, rgba(227, 168, 87, 0.9) 80%, transparent 92% 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  animation: trail-spin 6s linear infinite;
}
@keyframes trail-spin { to { transform: rotate(1turn); } }

.glowwrap { position: relative; }
.glowwrap > .card { position: relative; z-index: 1; }
.glowwrap__glow {
  position: absolute;
  inset: -20px;
  border-radius: 20px;
  overflow: hidden;
  z-index: 0;
  pointer-events: none;
  /* Kept low. The panel above is translucent with a backdrop blur,
     so anything stronger bleeds through and tints the copy instead
     of reading as a halo around the edge. */
  opacity: 0.22;
}
.glowwrap__glow::before {
  content: '';
  position: absolute;
  inset: -55%;
  background: conic-gradient(from 0deg,
    var(--accent), #6f8fbc, var(--accent-2), #4d6f9a, var(--accent));
  filter: blur(38px);
  animation: glow-spin 14s linear infinite;
}
@keyframes glow-spin { to { transform: rotate(1turn); } }
.btn--ghost { border-color: var(--line-2); color: var(--ink); }
.btn--ghost:hover { border-color: var(--ink-2); background: var(--panel-2); }

.link {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 18px;
  font-size: 0.85rem; color: var(--ink-2); text-decoration: none;
  border-bottom: 1px solid var(--line-2);
  padding-bottom: 2px; width: fit-content;
  transition: color 0.25s var(--ease), border-color 0.25s var(--ease);
}
.link:hover { color: var(--accent); border-color: var(--accent); }

/* ---------- Tags ------------------------------------------ */

.tags { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 18px; }
.tags li {
  font-family: var(--font-mono);
  font-size: 0.67rem;
  letter-spacing: 0.05em;
  color: var(--ink-2);
  padding: 5px 11px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  white-space: nowrap;
}

/* ---------- Section shell --------------------------------- */

/* Content is centred on the viewport so it sits directly over the
   helix and can be animated outward from its core. The rail is
   fixed, so main is not offset by it; instead the column is capped
   so it never reaches the rail on narrower desktops. */
.sec { position: relative; z-index: 1; padding-block: clamp(80px, 11vw, 150px); }
.sec__in {
  max-width: calc(100vw - var(--rail) * 2);
  width: min(1320px, calc(100vw - var(--rail) * 2));
  margin-inline: auto;
  padding-inline: var(--gut);
  /* Depth, so panels can swing out of the spine rather than sliding
     flat. Perspective only reaches direct children, so .grid carries
     its own further down. */
  perspective: 1600px;
  perspective-origin: 50% 50%;
}

/* Panels alternate down the page, leaving the helix uncovered in the
   middle. They are translucent, so the helix also reads through
   them, and the gap keeps a strip of it perfectly crisp. */
.side { width: 46%; }
.side--l { margin-right: auto; margin-left: 0; }
.side--r { margin-left: auto; margin-right: 0; }
.head + .side { margin-top: clamp(24px, 2.6vw, 38px); }

/* Headings sit on the helix with no panel behind them. .strip is
   excluded: it paints through background-clip, and a text-shadow
   under transparent glyphs renders as a blurred blob. */
.num, .sec__h, .sec__lede,
.home__h1, .home__p, .kicker, .unwind {
  text-shadow: 0 2px 26px rgba(7, 10, 14, 0.92), 0 0 10px rgba(7, 10, 14, 0.75);
}

.num { color: var(--accent); margin-bottom: 18px; }
.sec__h { font-size: var(--fs-h2); font-weight: 400; letter-spacing: -0.028em; max-width: 22ch; }
.sec__lede { margin-top: 16px; color: var(--ink-2); max-width: 54ch; }

/* Content panels float over the helix. The blur is what keeps the
   copy readable without dimming the backdrop everywhere. */
.card {
  margin-top: clamp(28px, 3.4vw, 46px);
  padding: clamp(24px, 3vw, 50px);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-surface);
  backdrop-filter: blur(16px) saturate(115%);
  -webkit-backdrop-filter: blur(16px) saturate(115%);
}
.card > p + p { margin-top: 16px; }
.card > p { color: var(--ink-2); max-width: 68ch; }
.card__h { font-size: var(--fs-h3); font-weight: 400; }

@media (prefers-reduced-transparency: reduce) {
  .card { background: #0b1016; backdrop-filter: none; -webkit-backdrop-filter: none; }
}

/* ---------- Home ------------------------------------------ */

.sec--home {
  min-height: 100dvh;
  display: flex;
  align-items: center;
}
/* Extra floor under the opening copy, the one block with no panel
   behind it. The page-wide edge dimming does most of the work, so
   this only has to finish the job. */
.sec--home .side { position: relative; }
.sec--home .side::before {
  content: '';
  position: absolute;
  inset: -14vh -14vw -14vh -30vw;
  background: linear-gradient(90deg, rgba(7, 10, 14, 0.9) 0%, rgba(7, 10, 14, 0.78) 58%, transparent 100%);
  z-index: -1;
}

/* TextShimmer. A slow highlight travelling the pipeline strip,
   which is the one line on the page that lists a sequence of
   stages, so light moving along it reads as flow. */
.strip {
  color: var(--accent);
  background: linear-gradient(100deg,
    var(--accent) 0%, var(--accent) 42%,
    #fff6e6 50%,
    var(--accent) 58%, var(--accent) 100%);
  background-size: 260% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: shimmer 7s linear infinite;
}
@keyframes shimmer {
  from { background-position: 130% 0; }
  to   { background-position: -130% 0; }
}

/* TextRoll. Characters swap for a duplicate sliding up from below,
   staggered left to right. Hover only, so it never fires on its
   own. */
.roll { display: inline-flex; }
.roll__c {
  display: inline-block;
  position: relative;
  overflow: hidden;
  height: 1.25em;
  line-height: 1.25em;
}
.roll__a, .roll__b {
  display: block;
  transition: transform 0.42s var(--ease);
  transition-delay: calc(var(--i) * 20ms);
}
.roll__b { position: absolute; left: 0; top: 100%; }
a:hover .roll__a, a:hover .roll__b,
a:focus-visible .roll__a, a:focus-visible .roll__b { transform: translateY(-100%); }
.kicker { color: var(--ink-3); margin-top: 14px; }
.home__h1 {
  margin-top: 22px;
  font-size: var(--fs-display);
  font-weight: 400;
  letter-spacing: -0.04em;
}
/* Kept short of the centre band so the bare hero copy never runs
   into the bright core of the helix. */
.home__p { margin-top: 20px; max-width: 40ch; color: var(--ink-2); font-size: clamp(1rem, 1.1vw, 1.14rem); }
.home__links { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 34px; }

.unwind { color: var(--ink-3); margin-top: 44px; display: flex; align-items: center; gap: 9px; }
.unwind__a { display: inline-block; animation: bob 2.4s ease-in-out infinite; }
@keyframes bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(5px); } }

/* ---------- About facts ----------------------------------- */

.facts {
  margin-top: 34px;
  padding-top: 26px;
  border-top: 1px solid var(--line);
  display: grid;
  /* Two up, not four: the panel is only ~46% of the page now. */
  grid-template-columns: repeat(2, 1fr);
  gap: 22px 28px;
}
.fact dt { color: var(--ink-3); }
.fact dd { margin-top: 7px; font-size: 0.94rem; }

/* ---------- Skills ---------------------------------------- */

.skills { display: grid; gap: 24px; }
.skill { display: grid; grid-template-columns: 1fr; gap: 10px; }
.skill + .skill { padding-top: 24px; border-top: 1px solid var(--line); }
.skill__h { font-size: 0.95rem; font-weight: 500; color: var(--ink); padding-top: 4px; }
.skill .tags { margin-top: 0; }

/* ---------- Projects -------------------------------------- */

/* Panels are roughly 46% of the page now, so every inner layout
   runs as a single column. */
.card--feat { display: block; }
.card--feat .feat__body { margin-top: clamp(20px, 2.4vw, 32px); }
/* Matches the framing of the project figures so the featured card
   sits in the same system. */
.feat__media {
  border-radius: var(--r-surface);
  background: #080c12;
  aspect-ratio: 16 / 9;
  display: grid;
  place-items: center;
  overflow: hidden;
}
/* Source is black line-art on opaque white. Invert flips it to
   white-on-black and screen drops the black field. Sized by max
   dimensions so it always fits the frame without distortion. */
.feat__media img {
  width: 78%;
  height: 78%;
  /* contain does the fitting, so the aspect ratio is preserved
     whatever the frame ends up being. Percentage max-height on an
     auto-sized grid track is not reliable enough to depend on. */
  object-fit: contain;
  filter: invert(1);
  mix-blend-mode: screen;
  opacity: 0.92;
}
.feat__tag { color: var(--accent); margin-bottom: 12px; }
.feat__body p { margin-top: 16px; color: var(--ink-2); }

/* Project cards alternate sides individually, so the grid is a
   plain column and each card places itself. The gap is generous so
   a clean band of helix shows between them. */
.grid {
  margin-top: clamp(36px, 5vw, 80px);
  display: flex;
  flex-direction: column;
  gap: clamp(40px, 6vw, 90px);
  /* Project cards are one level deeper than .sec__in, and
     perspective does not inherit past direct children. */
  perspective: 1600px;
  perspective-origin: 50% 50%;
}
/* position/overflow also contain the spotlight pool. */
.card--proj { position: relative; margin-top: 0; padding: 0; overflow: hidden; display: flex; flex-direction: column; }
.card--proj:hover { border-color: var(--line-2); }
.card__media { aspect-ratio: 16 / 9; overflow: hidden; background: #080c12; }
/* These are schematics, not photographs. They are already quiet, so
   they are not dimmed, and they are not scaled on hover because
   enlarging a diagram just softens its rules. */
.card__media img {
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0.9;
  transition: opacity 0.45s var(--ease);
}
.card--proj:hover .card__media img { opacity: 1; }
.card__body { padding: clamp(20px, 2.2vw, 30px); display: flex; flex-direction: column; flex: 1; }
.card__body p { margin-top: 13px; color: var(--ink-2); font-size: 0.94rem; }
.card__body .tags { margin-top: auto; padding-top: 18px; }

/* ---------- Publications ---------------------------------- */

.pubs { display: grid; }
.pub {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
  padding-block: 22px;
}
.pub + .pub { border-top: 1px solid var(--line); }
.pub:first-child { padding-top: 0; }
.pub:last-child { padding-bottom: 0; }
.pub__kind { color: var(--ink-3); padding-top: 5px; }
.pub__h { font-size: 1.02rem; font-weight: 500; line-height: 1.4; letter-spacing: -0.01em; }
.pub__h a {
  text-decoration: none;
  display: inline-flex; align-items: baseline; gap: 7px;
  transition: color 0.25s var(--ease);
}
.pub__h a:hover { color: var(--accent); }
.pub__meta { margin-top: 7px; color: var(--ink-3); font-size: 0.9rem; }

/* ---------- Experience ------------------------------------ */

.cols { display: grid; grid-template-columns: 1fr; gap: clamp(30px, 4vw, 48px); }
.col__h { color: var(--ink-3); padding-bottom: 16px; border-bottom: 1px solid var(--line); }
.tl { margin-top: 26px; display: grid; gap: 32px; }
.tl__when { color: var(--accent); }
.tl__what { margin-top: 9px; font-size: 1.02rem; font-weight: 500; letter-spacing: -0.01em; }
.tl__p { margin-top: 9px; color: var(--ink-2); font-size: 0.92rem; }
.lead { margin-top: 40px; padding-top: 30px; border-top: 1px solid var(--line); }
.lead .tl__p { margin-top: 16px; }

/* ---------- Contact & footer ------------------------------ */

.sec--contact { padding-bottom: clamp(60px, 8vw, 100px); }
.sec--contact .sec__h { max-width: 18ch; font-size: clamp(1.8rem, 3.8vw, 3rem); }
.sec--contact .home__links { margin-top: 34px; }

.foot {
  position: relative; z-index: 1;
  border-top: 1px solid var(--line);
  padding: 26px var(--gut);
}
.foot__in {
  width: min(1320px, calc(100vw - var(--rail) * 2));
  margin-inline: auto;
  display: flex; flex-wrap: wrap; gap: 8px 24px;
  justify-content: space-between; align-items: center;
  color: var(--ink-3); font-size: 0.88rem;
}

/* ---------- Reveal ---------------------------------------- */

/* Panels rest in place until JS takes over. Once it does they start
   tucked behind the helix and are pushed out to their own side. */
.reveal { opacity: 1; }
.js-anim .reveal { opacity: 0; will-change: transform, opacity; }

/* Headings are split into lines and swept up behind a mask that
   SplitText inserts. Until the split runs they render normally, so
   a failure here leaves readable type rather than a blank heading. */
.split-line { display: block; will-change: transform; }

/* ============================================================
   Responsive
   ============================================================ */

@media (max-width: 1180px) {
  /* Panels widen as the page narrows, before they stack. */
  .side { width: 52%; }
}

@media (max-width: 899px) {
  :root { --rail: 0px; }
  .rail { display: none; }
  .sec__in, .foot__in { width: min(720px, 100vw); }

  /* No room to alternate. Panels go full width and stack. */
  .side, .side--l, .side--r { width: 100%; margin-inline: 0; }
  .feat__media { aspect-ratio: 16 / 10; }
  .grid { gap: clamp(28px, 6vw, 48px); }
  .pub__kind { padding-top: 0; }
  .sec--home .side::before { inset: -14vh -20vw; }
}

@media (max-width: 560px) {
  .facts { grid-template-columns: 1fr; gap: 18px; }
  .tl { gap: 26px; }
}

/* ============================================================
   Reduced motion. No scrub, no sequence download, no drift.
   ============================================================ */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .js-anim .reveal { opacity: 1; transform: none; }
  .unwind__a { animation: none; }
  /* Glow and border trail hold still rather than travelling.
     Magnetic pull, spotlight and scramble are not wired up at all
     under reduced motion. */
  .glowwrap__glow::before { animation: none; }
  .trail { animation: none; opacity: 0.5; }
  .progress span { transform: scaleX(1); }
  .strip { animation: none; }
  .roll__a, .roll__b { transition: none; }
}
