/* Pradeep Reddy, minimal editorial layer.
   Loaded after styles.css. Redefines the design tokens toward a
   near-monochrome, high-contrast, type-led surface, strips card chrome,
   and adds the scroll-driven motion language.

   Everything here is additive. Deleting this file returns the site to the
   previous look with no other change.

   Contrast, measured rather than assumed: body text #1c1c1c reaches 12.4:1 on
   the page colour and 11.2:1 on the alternating sections, and 15.8:1 in dark,
   so every theme clears AAA for body text. The two dimmed greys are checked
   against BOTH surfaces, not just the page colour, because the alternating
   sections are darker and are where those greys are most used. */

/* ---------------------------------------------------------------- tokens */

:root {
  /* Founder-chosen page colour. #1c1c1c on #b8e2f4 measures 12.1:1, so the
     body text clears AAA with room to spare. The dimmed greys below were
     picked against the old near-white and are re-checked against this blue:
     both still clear the 4.5:1 floor. */
  --bg: #b8e2f4;
  --bg-elevated: #a6d9ef;   /* the alternating sections, a shade deeper */
  --card: transparent;
  --card-solid: #ffffff;
  --border: rgba(20, 45, 58, 0.18);
  --border-strong: rgba(20, 45, 58, 0.34);
  --text: #1c1c1c;
  --text-dim: #33474f;      /* 7.1:1 on --bg, 6.4:1 on --bg-elevated */
  /* Darkened until it clears 4.5:1 on the ELEVATED surface, not just on --bg.
     The first pass checked only the page background and shipped a value that
     failed at 4.41:1 on the alternating sections, where this colour is most
     used. */
  --text-dimmer: #485d66;   /* 5.0:1 on --bg, 4.6:1 on --bg-elevated */

  --accent: #1c1c1c;
  --accent-2: #33474f;
  --accent-vivid: #1c1c1c;
  --accent-2-vivid: #33474f;
  --on-accent: #b8e2f4;

  --gradient: none;
  --gradient-text: none;
  --grid-line: rgba(28, 28, 28, 0.05);
  --logo-tile-bg: #ffffff;

  --radius: 0px;
  --radius-sm: 0px;
  --maxw: 1240px;
  --shadow-lg: none;

  --rule: 1px solid var(--border);
  --step: clamp(5rem, 11vw, 11rem);   /* vertical rhythm between sections */
}

:root[data-theme="dark"] {
  --bg: #1c1c1c;
  --bg-elevated: #232323;
  --card: transparent;
  --card-solid: #232323;
  --border: rgba(250, 250, 250, 0.16);
  --border-strong: rgba(250, 250, 250, 0.32);
  --text: #fafafa;
  --text-dim: #b8b8b8;      /* 8.6:1 on --bg */
  --text-dimmer: #949494;   /* 5.5:1 on --bg */

  --accent: #fafafa;
  --accent-2: #b8b8b8;
  --accent-vivid: #fafafa;
  --accent-2-vivid: #b8b8b8;
  --on-accent: #1c1c1c;

  --grid-line: rgba(250, 250, 250, 0.045);
  --logo-tile-bg: #fafafa;
}

/* ------------------------------------------------------------ typography */

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

h1, h2, h3 { letter-spacing: -0.035em; }

.hero h1 {
  font-size: clamp(2.9rem, 9.5vw, 7.5rem);
  line-height: 0.94;
  font-weight: 700;
  letter-spacing: -0.045em;
  max-width: 16ch;
  margin-inline: auto;
}

/* The gradient span carried the emphasis. Monochrome has no colour to spend,
   so the contrast becomes weight and style instead. */
.text-gradient {
  background: none;
  -webkit-background-clip: border-box;
  background-clip: border-box;
  color: var(--text);
  -webkit-text-fill-color: var(--text);
  font-weight: 300;
  font-style: italic;
}

.section h2,
#skills-heading,
#experience-heading {
  font-size: clamp(2rem, 5.2vw, 3.6rem);
  line-height: 1.02;
  font-weight: 650;
  max-width: 20ch;
}

.section-kicker {
  font-size: 0.7rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-dimmer);
}

.hero-lede, .section-lede { font-size: clamp(1rem, 1.5vw, 1.15rem); line-height: 1.6; }

.eyebrow {
  border: 0;
  border-radius: 0;
  background: none;
  padding: 0;
  font-size: 0.8rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-dimmer);
}

/* ----------------------------------------------------------- page rhythm */

.section { padding-block: var(--step); }
.section-alt { background: var(--bg-elevated); }

/* Strip the card chrome. Separation comes from space and hairline rules. */
.skill-card,
.project-card,
.about-card,
.edu-card {
  background: none;
  border: 0;
  border-top: var(--rule);
  border-radius: 0;
  box-shadow: none;
  padding-inline: 0;
  padding-block: 1.6rem 2rem;
}

.skill-card:hover,
.project-card:hover { transform: none; box-shadow: none; border-top-color: var(--border-strong); }

.skill-card-featured { border-top: 2px solid var(--text); }

.chip-list li {
  background: none;
  border: var(--rule);
  border-radius: 0;
  color: var(--text-dim);
  font-size: 0.8rem;
}

.btn {
  border-radius: 0;
  border: 1px solid var(--text);
  font-weight: 550;
  letter-spacing: 0.01em;
}
.btn-primary { background: var(--text); color: var(--on-accent); box-shadow: none; }
.btn-primary:hover { background: transparent; color: var(--text); }
.btn-ghost { background: none; color: var(--text); }
.btn-ghost:hover { background: var(--text); color: var(--on-accent); }

/* Hero decoration: the orbs and grid were the busiest thing on the page. */
.orb { display: none; }
.grid-overlay { opacity: 0.5; }

/* Both of these painted their text through a gradient with a transparent
   colour. With --gradient-text set to none the glyphs became invisible, so
   the fill has to be handed back explicitly. */
.hero-stats dt {
  font-weight: 300;
  font-size: clamp(2.2rem, 5vw, 3.4rem);
  letter-spacing: -0.04em;
  background: none;
  -webkit-background-clip: border-box;
  background-clip: border-box;
  color: var(--text);
  -webkit-text-fill-color: var(--text);
}
.hero-stats dd { color: var(--text-dimmer); font-size: 0.82rem; letter-spacing: 0.02em; }

.site-header { border-bottom: var(--rule); backdrop-filter: saturate(1) blur(10px); }
.brand-mark { border-radius: 0; background: var(--text); color: var(--on-accent); }
.nav-cta { border-radius: 0; border: 1px solid var(--text); }
.theme-toggle, .nav-toggle { border-radius: 0; }

/* ---------------------------------------------------------- hero media */

/* The video slot exists now and is empty by design. Drop a file into
   <source> and the poster stops being the whole story. */
.hero-media {
  position: absolute;
  inset: 0;
  overflow: hidden;
  z-index: 0;
  pointer-events: none;
}
.hero-media video,
.hero-media .hero-media-poster {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.hero-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--bg);
  opacity: 0.82;
}
.hero-inner { position: relative; z-index: 1; }

/* ------------------------------------------------------- backdrop field */

/* The canvas is injected as the host's first child by backdrop.js and sits
   behind everything. Hosts need a stacking context and their content needs
   to be lifted above it, which is all these three rules do. */
.backdrop-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
}

[data-backdrop] { position: relative; }
[data-backdrop] > .wrap { position: relative; z-index: 1; }

/* The hero already layers its own media slot and grid overlay, so the
   lattice slots underneath both rather than fighting them. */
.hero .backdrop-canvas { z-index: 0; }
.hero-bg { z-index: 0; }

/* ------------------------------------------------------------- motion */

/* NOTE, and it cost an hour to find: the observed element must never be
   clipped to zero area. IntersectionObserver measures the rect AFTER
   clipping, so `clip-path: inset(0 0 100% 0)` on a [data-reveal] element
   reports intersectionRatio 0, the callback never fires, .in-view is never
   added, and the element stays hidden forever. The wipe therefore lives on
   the CHILDREN, which nothing observes. The observed element itself moves
   only with opacity and transform, neither of which affects intersection. */
[data-reveal] {
  opacity: 0;
  transform: translateY(22px);
  transition:
    opacity 0.9s var(--ease),
    transform 0.9s var(--ease);
}
[data-reveal].in-view {
  opacity: 1;
  transform: none;
}

/* Children of a revealed block wipe in sequence, so a section resolves in
   stages instead of all at once. */
[data-reveal].in-view > * { animation: mn-rise 0.85s var(--ease) both; }
[data-reveal].in-view > *:nth-child(1) { animation-delay: 0.04s; }
[data-reveal].in-view > *:nth-child(2) { animation-delay: 0.10s; }
[data-reveal].in-view > *:nth-child(3) { animation-delay: 0.16s; }
[data-reveal].in-view > *:nth-child(4) { animation-delay: 0.22s; }
[data-reveal].in-view > *:nth-child(5) { animation-delay: 0.28s; }
[data-reveal].in-view > *:nth-child(6) { animation-delay: 0.34s; }
[data-reveal].in-view > *:nth-child(n + 7) { animation-delay: 0.4s; }

/* Headline lines run their own animation; keep the generic stagger off them
   so the two do not fight over transform. */
[data-reveal].in-view > .mn-line { animation: none; }

@keyframes mn-rise {
  from { opacity: 0; transform: translateY(14px); clip-path: inset(0 0 100% 0); }
  to   { opacity: 1; transform: none; clip-path: inset(0 0 0 0); }
}

/* The headline resolves line by line on load. */
.mn-line { display: block; overflow: hidden; }
.mn-line > span {
  display: block;
  transform: translateY(105%);
  animation: mn-line-in 1.05s var(--ease) both;
}
.mn-line:nth-child(1) > span { animation-delay: 0.08s; }
.mn-line:nth-child(2) > span { animation-delay: 0.18s; }

@keyframes mn-line-in {
  from { transform: translateY(105%); }
  to   { transform: none; }
}

/* Section kickers pin while their section is in view, so the label stays
   with the content as you scroll past it. */
.section-kicker { position: sticky; top: 5.2rem; z-index: 2; }

/* These two were also painted with the gradient and went invisible with it.
   The kicker dash becomes a rule that draws itself open when its section
   arrives, which is the same gesture at a smaller scale. */
.primary-nav a.active::after { background: var(--text); }

.section-kicker::before {
  width: 3.5rem;
  height: 1px;
  border-radius: 0;
  background: var(--text-dimmer);
  margin-right: 0.9rem;
  vertical-align: middle;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 1.1s var(--ease) 0.15s;
}
.section-kicker.in-view::before { transform: scaleX(1); }

/* Scroll progress bar. Uses the native scroll timeline where supported and
   simply stays hidden where it is not, so there is no JS cost either way. */
.mn-progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 2px;
  width: 100%;
  transform-origin: left;
  transform: scaleX(0);
  background: var(--text);
  z-index: 60;
}
@supports (animation-timeline: scroll()) {
  .mn-progress {
    animation: mn-progress-grow linear both;
    animation-timeline: scroll(root block);
  }
}
@keyframes mn-progress-grow { to { transform: scaleX(1); } }

/* ------------------------------------------------- reduced motion wins */

/* styles.css already neutralises durations globally under this query. These
   rules additionally undo the *start* states, because a clip-path or a
   translate that never animates would otherwise leave content hidden. */
@media (prefers-reduced-motion: reduce) {
  [data-reveal],
  [data-reveal].in-view {
    transform: none;
    opacity: 1;
  }
  [data-reveal].in-view > * { animation: none; }
  .mn-line > span { transform: none; animation: none; }
  .section-kicker { position: static; }
  .mn-progress { display: none; }
  .hero-media video { display: none; }
}
