:root {
  --bg: #0a0a0a;
  --text: #7ee787;
  --text-dim: #4ea95f;
  --text-dimmer: #2d5a33;
  --border: rgba(126, 231, 135, 0.08);
  --font-mono: 'JetBrains Mono', 'SF Mono', 'Menlo', 'Monaco', 'Consolas', 'Courier New', monospace;

  --page-px: clamp(1.5rem, 5vw, 5rem);
  --content-max: 1400px;
}

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

html {
  scroll-behavior: smooth;
  /* Two snap positions, both in CSS, with a HARD lock (`mandatory`):
       1. hero start       (landing — fills the viewport)
       2. .projects start  (`// projects` near the top — footer is
                            visible below as part of this "page" on
                            any viewport where projects+footer fit
                            within 100dvh)
     Mandatory snap means the user cannot rest at any other position;
     the page firmly locks to one of the two snap points. Trade-off:
     on very small viewports where projects+footer exceeds 100dvh,
     the footer sits below the fold at the projects snap point. The
     padding budget below has been tuned so this only happens on
     unusually short viewports. */
  scroll-snap-type: y mandatory;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-mono);
  font-weight: 400;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  min-height: 100vh;
}

/* =========================
   HERO
   ========================= */
.hero {
  /* `100svh` (small viewport height) — stable across iOS Safari's
     address-bar show/hide, unlike `100dvh` which reactively grows
     and shrinks. Stability matters here because the snap target is
     `.projects` which sits at `hero.height` from the top — if the
     hero grows mid-scroll, projects' offset moves and the snap
     point drifts, leaving the scroll hint visible at the top of the
     projects view. svh is the smallest possible viewport, so when
     chrome is hidden the hero is slightly shorter than the visible
     area; that's a fair trade for a snap that doesn't drift.
     `100vh` first as the fallback for old browsers without svh. */
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--page-px);
  position: relative;
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

.hero-content {
  max-width: var(--content-max);
  width: 100%;
}

.terminal-line {
  display: block;
  line-height: 1.1;
  margin-bottom: 0.35em;
}

.terminal-line:last-child {
  margin-bottom: 0;
}

.typed {
  display: inline-block;
  position: relative;
  min-height: 1em;
  /* Align by top edge, not baseline. An empty inline-block (the
     state of word2 right before its first character types in) has
     a baseline at its bottom edge; once content arrives the
     baseline shifts to the text baseline. That shift bumped the
     other words on the line. Top-aligning kills the bump. */
  vertical-align: top;
}

.hero-welcome {
  font-size: clamp(1.1rem, 2.4vw, 1.9rem);
  color: var(--text-dim);
  font-weight: 400;
  letter-spacing: 0.02em;
}

.hero-title {
  font-size: clamp(2.8rem, 9vw, 7rem);
  font-weight: 500;
  letter-spacing: -0.03em;
  color: var(--text);
  text-shadow: 0 0 40px rgba(126, 231, 135, 0.12);
}

/* Blinking cursor (applied via JS as it moves between lines) */
.cursor-after::after {
  content: '_';
  display: inline-block;
  margin-left: 0.08em;
  color: var(--text);
  animation: blink 1.1s step-end infinite;
  font-weight: 400;
}

@keyframes blink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* Scroll hint */
.scroll-hint {
  position: absolute;
  /* env() adds the home-indicator inset on iOS; resolves to 0 elsewhere. */
  bottom: calc(2.5rem + env(safe-area-inset-bottom));
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.45rem;
  /* Net ~12.5% larger than the original 0.72rem (was bumped to
     1.08rem then trimmed back 25%). */
  font-size: 0.81rem;
  color: var(--text-dim);
  opacity: 0;
  transition: opacity 1.2s ease;
  letter-spacing: 0.15em;
  text-transform: lowercase;
  pointer-events: none;
}

.scroll-hint.visible {
  opacity: 0.65;
}

.scroll-arrow {
  animation: bounce 2s ease infinite;
  /* Same trim — net ~12.5% larger than the original 0.95rem. */
  font-size: 1.07rem;
}

@keyframes bounce {
  0%, 100% { transform: translateY(0); opacity: 0.6; }
  50%      { transform: translateY(4px); opacity: 1; }
}

/* =========================
   PROJECTS
   ========================= */
.projects {
  /* Top padding small so the `// projects` label sits near the top
     of the viewport when this section snaps to start. Bottom
     padding tight (the footer brings its own top padding) so
     projects + footer fits within 100svh on as many viewports as
     possible — mandatory snap hides anything past projects-start. */
  padding: clamp(2rem, 5vh, 4rem) var(--page-px) clamp(1.5rem, 4vh, 3rem);
  max-width: var(--content-max);
  scroll-snap-align: start;
}

.projects-header {
  /* Bumped from clamp(2.5rem, 5vh, 4rem) so there's more breathing
     room between the `// projects` label and the first card. */
  margin-bottom: clamp(3rem, 7vh, 5rem);
}

.section-label {
  display: inline-block;
  font-size: 0.85rem;
  color: var(--text-dim);
  letter-spacing: 0.05em;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.8s ease, transform 0.8s ease;
}

.section-label.visible {
  opacity: 0.6;
  transform: translateY(0);
}

.project {
  display: grid;
  grid-template-columns: minmax(130px, 170px) 1fr;
  gap: clamp(1.5rem, 4vw, 3rem);
  /* Responsive vertical padding so cards have more breathing room
     on tall viewports. Was a flat 2.5rem; now scales up to 4rem
     where there's space (= ~64px top + bottom on desktop). */
  padding: clamp(2.5rem, 5vh, 4rem) 0;
  border-top: 1px solid var(--border);
}

.project-meta {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding-top: 0.75rem;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.8s ease 0.1s, transform 0.8s ease 0.1s;
}

.project.visible .project-meta {
  opacity: 1;
  transform: translateY(0);
}

.project-number {
  color: var(--text-dim);
  font-size: 0.95rem;
  letter-spacing: 0.02em;
}

.project-status {
  color: var(--text-dimmer);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: lowercase;
}

.project-body {
  max-width: 680px;
}

.project-title {
  font-size: clamp(2rem, 5vw, 3.8rem);
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.05;
  margin-bottom: 1.4rem;
  min-height: 1em;
}

/* Project title links inherit the heading's look — no browser-default
   underline or blue — so visually nothing changes at rest. */
.project-title a {
  color: inherit;
  text-decoration: none;
}

/* On hover/focus, the "_" cursor reappears after the title — same blink
   rhythm as the typing cursor so it feels consistent with the rest of
   the site. */
.project-title a:hover::after,
.project-title a:focus-visible::after {
  content: '_';
  display: inline-block;
  margin-left: 0.08em;
  color: var(--text);
  animation: blink 1.1s step-end infinite;
}

.project-description {
  font-size: clamp(0.95rem, 1.3vw, 1.08rem);
  line-height: 1.7;
  color: var(--text);
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.9s ease 0.55s, transform 0.9s ease 0.55s;
}

.project.visible .project-description {
  opacity: 0.85;
  transform: translateY(0);
}

/* =========================
   FOOTER
   ========================= */
footer {
  padding: 2.5rem var(--page-px) 3rem;
  border-top: 1px solid var(--border);
}

.footer-inner {
  /* No max-width here. The footer's border-top spans the full
     viewport (because the <footer> element does), and we want the
     copyright to reach the right edge of that line — symmetric with
     the label sitting at the left page-padding edge. With max-width
     applied, the inner was clipped to 1400px and the copyright got
     stranded ~30% in from the right on wider viewports. */
  width: 100%;
  display: flex;
  align-items: center;
  gap: 1.5rem;
  flex-wrap: wrap;
}

/* All three pieces share the same baseline styling — same size,
   colour, opacity, letter-spacing — so the footer reads as one
   uniform row. The link gets a hover boost (full opacity, brighter
   colour) but matches the label otherwise. The copyright is pushed
   to the right edge with `margin-left: auto`. */
.footer-label,
.footer-link,
.footer-copy {
  font-size: 0.78rem;
  color: var(--text-dim);
  opacity: 0.45;
  letter-spacing: 0.03em;
}

.footer-link {
  text-decoration: none;
  transition: opacity 0.2s ease, color 0.2s ease;
}

.footer-link:hover,
.footer-link:focus-visible {
  opacity: 1;
  color: var(--text);
}

.footer-copy {
  margin-left: auto;
}

/* =========================
   ACCESSIBILITY & RESPONSIVE
   ========================= */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-snap-type: none;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

@media (max-width: 640px) {
  /* Force "tinker" and "studios" onto separate lines BEFORE typing starts,
     so the typewriter effect never triggers a mid-word reflow. */
  .hero-space {
    display: none;
  }

  .hero-word {
    display: block;
  }

  .project {
    grid-template-columns: 1fr;
    gap: 0.75rem;
  }

  .project-meta {
    flex-direction: row;
    align-items: center;
    gap: 1rem;
    padding-top: 0;
  }

  .scroll-hint {
    bottom: calc(1.75rem + env(safe-area-inset-bottom));
  }
}

::selection {
  background: rgba(126, 231, 135, 0.2);
  color: var(--text);
}
