/* =========================================================
   LJS0817 Portfolio — case "1b" (top nav / dark hero / capability cards)
   ========================================================= */
:root {
  --bg: #f7f3ef;
  --bg-soft: #fdfbf8;
  --ink: #17110d;
  --ink-70: #44403c;
  --ink-60: #57534e;
  --ink-50: #6b615a;
  --ink-40: #8a7f77;
  --line: rgba(23, 17, 13, 0.1);
  --line-strong: rgba(23, 17, 13, 0.16);

  --orange: #ea580c;
  --orange-bright: #f97316;
  --orange-deep: #c2440c;
  --orange-darkest: #9a3412;

  --dark: #17110d;
  --dark-soft: #211812;
  --dark-line: rgba(245, 239, 233, 0.14);
  --dark-ink: #f5efe9;
  --dark-muted: #b8ada4;
  --dark-label: #78716c;

  --sans: 'Noto Sans KR', sans-serif;
  --display: 'Space Grotesk', 'Noto Sans KR', sans-serif;
  --mono: 'IBM Plex Mono', monospace;

  --pad-x: 72px;
  --nav-h: 64px;
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  /* Keeps anchor jumps (e.g. #capability, right-rail project index)
     from landing under the fixed top nav. Flush with the nav height
     (no extra gap) so project sections -- which have no border
     between them -- don't let the previous project peek through
     above the sticky banner text when jumped to. */
  scroll-padding-top: var(--nav-h);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
}

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

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

/* =========================================================
   Top nav
   ========================================================= */
.topnav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--nav-h);
  z-index: 1000;
  background: var(--dark);
  border-bottom: 1px solid var(--dark-line);
}

.topnav-inner {
  max-width: 1400px;
  height: 100%;
  margin: 0 auto;
  padding: 0 var(--pad-x);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.brand {
  font-family: var(--display);
  font-size: 17px;
  font-weight: 700;
  color: var(--dark-ink);
  letter-spacing: 0.5px;
}

.brand span {
  color: var(--orange-bright);
}

.topnav-links {
  display: flex;
  gap: 32px;
}

.topnav-links a {
  font-size: 14px;
  color: var(--dark-muted);
  transition: color 0.2s ease;
}

.topnav-links a:hover {
  color: #fff;
}

/* =========================================================
   Project index (right rail inside each project's body — NOT the
   banner, which stays full-bleed. One static copy per project, with
   that project's own row pre-marked .is-active, so no JS is needed to
   track scroll position. Sticky within .project-body only, so it
   naturally hands off between projects with the rest of the content.
   ========================================================= */
.project-index {
  position: sticky;
  top: calc(var(--nav-h) + 190px);
  flex-shrink: 0;
  width: 148px;
  margin-right: -32px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 12px;
  border-radius: 12px;
  background: var(--bg-soft);
  border: 1px solid var(--line);
}

.project-index-group {
  display: block;
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--ink-40);
  padding: 10px 8px 4px;
}

.project-index-group:first-child {
  padding-top: 2px;
}

.project-index-item {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 6px 8px;
  border-radius: 6px;
  font-family: var(--mono);
  white-space: nowrap;
  transition: background 0.15s ease;
}

.project-index-item:hover,
.project-index-item.is-active {
  background: rgba(234, 88, 12, 0.09);
}

.project-index-num {
  font-size: 11px;
  color: var(--ink-40);
}

.project-index-name {
  font-size: 11px;
  color: var(--ink-60);
  overflow: hidden;
  text-overflow: ellipsis;
}

.project-index-item.is-active .project-index-num,
.project-index-item.is-active .project-index-name {
  color: var(--orange-deep);
  font-weight: 700;
}

@media (max-width: 1180px) {
  .project-index {
    display: none;
  }
}

/* =========================================================
   Hero
   ========================================================= */
.hero {
  background: var(--dark);
  color: var(--dark-ink);
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

/* Content is anchored under the nav rather than vertically centered:
   the headline/stats stay a fixed distance from the top, and the
   capability block below grows to soak up whatever room is left in
   the hero's first viewport (its own light background fills that
   space, so a taller viewport never shows a bare dead zone under the
   hero). Content taller than one viewport simply grows past it and
   the page scrolls through it like any other section. */
.hero-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding-top: calc(var(--nav-h) + 40px);
}

.hero-inner {
  flex: 1;
  max-width: 900px;
  padding: 0 var(--pad-x) 28px;
  align-items: start;
  justify-content: center;
  display: flex;
  flex-direction: column;
}

/* Hero top row: intro copy on the left, tech stack panel on the right */
.hero-top {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 48px;
  max-width: 1400px;
  width: 100%;
  margin: 0 auto;
  padding-right: var(--pad-x);
}

.hero-top .hero-inner {
  flex: 1 1 auto;
  min-width: 0;
}

.hero-stack {
  flex: 0 1 420px;
  min-width: 0;
  margin-bottom: 28px;
}

/* Tech stack rendered as a Unity-style Inspector panel */
.inspector {
  font-family: var(--mono);
  background: #1c1510;
  border: 1px solid var(--dark-line);
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.35);
}

.insp-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border-bottom: 1px solid var(--dark-line);
}

.insp-name {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 14px;
  font-weight: 600;
  color: #fff;
}

.insp-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--orange-bright);
}

.insp-tag {
  font-size: 10px;
  letter-spacing: 1.5px;
  color: var(--dark-label);
}

.insp-comp {
  border-bottom: 1px solid var(--dark-line);
}

.insp-comp summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 13px 16px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--dark-ink);
  user-select: none;
}

.insp-comp summary::-webkit-details-marker {
  display: none;
}

.insp-comp summary::before {
  content: '';
  width: 0;
  height: 0;
  border-left: 4px solid var(--orange-bright);
  border-top: 3px solid transparent;
  border-bottom: 3px solid transparent;
  transition: transform 0.15s ease;
}

.insp-comp[open] summary::before {
  transform: rotate(90deg);
}

.insp-comp summary:hover {
  background: rgba(245, 239, 233, 0.03);
}

.insp-body {
  padding: 0 16px 12px 30px;
}

.insp-row {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 3px 0;
  font-size: 11.5px;
  line-height: 1.5;
}

.insp-key {
  color: var(--dark-muted);
  white-space: nowrap;
}

.insp-val {
  color: var(--orange-bright);
  text-align: right;
}

.insp-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 0 16px 14px 30px;
}

.insp-chip {
  display: inline-block;
  padding: 3px 9px;
  border-radius: 4px;
  border: 1px solid rgba(245, 239, 233, 0.16);
  background: rgba(245, 239, 233, 0.05);
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--dark-ink);
  white-space: nowrap;
}

.insp-chip--accent {
  border-color: var(--orange);
  background: rgba(234, 88, 12, 0.18);
  color: #fff;
}

.insp-chip--basic {
  border-style: dashed;
  border-color: rgba(245, 239, 233, 0.14);
  background: transparent;
  color: var(--dark-label);
}

.insp-legend {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 18px;
  margin: 12px;
  padding: 9px;
  border: 1px solid var(--dark-line);
  border-radius: 6px;
  font-size: 10.5px;
  letter-spacing: 0.5px;
  color: var(--dark-muted);
}

.insp-legend span {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}

.insp-mark {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  border: 1px solid rgba(245, 239, 233, 0.3);
  background: rgba(245, 239, 233, 0.12);
}

.insp-mark--basic {
  border-style: dashed;
  background: transparent;
}

@media (max-width: 980px) {
  .hero-top {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    padding-right: 0;
  }

  .hero-stack {
    flex: none;
    margin: 0 var(--pad-x) 32px;
  }
}

.hero-eyebrow {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 3px;
  color: var(--orange-bright);
}

.hero h1 {
  font-family: var(--display);
  font-size: 52px;
  font-weight: 700;
  line-height: 1.18;
  letter-spacing: -1px;
  margin: 18px 0 0;
  color: #fff;
}

.hero-desc {
  font-size: 16px;
  line-height: 1.8;
  color: var(--dark-muted);
  max-width: 620px;
  margin: 20px 0 0;
}

.hero-buttons {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-top: 22px;
}

.hero-email {
  display: inline-flex;
  align-items: center;
  padding: 13px 4px;
  font-family: var(--mono);
  font-size: 14px;
  color: var(--dark-muted);
}

.btn {
  display: inline-block;
  padding: 13px 24px;
  border-radius: 8px;
  font-size: 14.5px;
  font-weight: 700;
  transition: all 0.2s ease;
}

.btn--primary {
  background: var(--orange);
  color: #fff;
}

.btn--primary:hover {
  background: var(--orange-deep);
}

.btn--secondary {
  background: rgba(245, 239, 233, 0.06);
  color: var(--dark-ink);
  border: 1px solid rgba(245, 239, 233, 0.22);
}

.btn--secondary:hover {
  background: rgba(245, 239, 233, 0.12);
  border-color: rgba(245, 239, 233, 0.4);
}

.stats-bar {
  border-top: 1px solid var(--dark-line);
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  max-width: 1400px;
  margin: 0 auto;
}

.stat {
  padding: 22px var(--pad-x);
  display: flex;
  align-items: baseline;
  gap: 10px;
  border-right: 1px solid var(--dark-line);
}

.stat:last-child {
  border-right: none;
}

.stat-num {
  font-family: var(--display);
  font-size: 24px;
  font-weight: 700;
  color: var(--orange-bright);
}

.stat-label {
  font-size: 13px;
  color: var(--dark-muted);
}

/* =========================================================
   Capability (WHAT I BUILD) — nested inside the hero section, but
   switches to the light palette right under the dark stats bar (its
   own section, same as the "All Work" section that follows it) and
   grows to fill whatever room is left in the hero's first viewport.
   ========================================================= */
.capability-block {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 40px var(--pad-x);
  text-align: center;
  background: var(--bg-soft);
  color: var(--ink);
}

.capability-eyebrow {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 3px;
  color: var(--orange-deep);
}

.capability-block h2 {
  font-family: var(--display);
  font-size: 26px;
  font-weight: 700;
  margin: 8px 0 22px;
  color: var(--ink);
}

.capability-grid {
  display: grid;
  /* auto-fit keeps each card close to a consistent, wide-rather-than-tall
     shape at any width — settling at 4 columns on desktop and stepping
     down gracefully as the window narrows — instead of a fixed column
     count that collapses straight to one full-width (very flat) card. */
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 14px;
  max-width: 1400px;
  width: 100%;
  /* .capability-block is itself a column flex container, which makes
     this grid a flex item — flex items default to min-width: auto and
     refuse to shrink below their content's own min-content size. For
     an auto-fit grid that min-content size is "all columns at once",
     so without this the grid stayed wide and silently overflowed
     sideways instead of dropping to fewer columns. */
  min-width: 0;
  margin: 0 auto;
  text-align: left;
}

.cap-card {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 18px;
  display: flex;
  flex-direction: column;
}

.cap-num {
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--ink-40);
  margin-bottom: 10px;
}

.cap-card h3 {
  font-family: var(--display);
  font-size: 16.5px;
  font-weight: 700;
  margin: 0 0 10px;
  color: var(--ink);
}

.cap-card p {
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink-60);
  margin: 0;
  flex: 1;
}

.cap-related {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--orange-deep);
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
}

/* =========================================================
   All work
   ========================================================= */
.work {
  padding: calc(var(--nav-h) + 40px) var(--pad-x) 40px;
  background: var(--bg-soft);
  border-bottom: 1px solid var(--line);
}

.work-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 24px;
  flex-wrap: wrap;
  max-width: 1400px;
  margin-left: auto;
  margin-right: auto;
  flex-shrink: 0;
  width: 100%;
}

.work-eyebrow {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 3px;
  color: var(--orange-deep);
}

.work h2 {
  font-family: var(--display);
  font-size: 28px;
  font-weight: 700;
  margin: 8px 0 0;
}

/* auto-fit + minmax: settles at however many equal columns fit at this
   minimum width, so when a filter leaves a whole column empty (e.g. a
   1-project category) the remaining card(s) stretch to fill the
   freed-up width too, not just sit alone. */
.work-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
  gap: 16px;
  max-width: 1400px;
  width: 100%;
  margin: 0 auto;
}

.work-card {
  display: flex;
  flex-direction: column;
  background: #fff;
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid var(--line);
  color: inherit;
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}

.work-card:hover {
  transform: translateY(-3px);
  border-color: rgba(234, 88, 12, 0.35);
  box-shadow: 0 8px 24px rgba(23, 17, 13, 0.08);
  color: inherit;
}

.work-card img {
  width: 100%;
  height: 190px;
  object-fit: cover;
}

.work-card-body {
  flex-shrink: 0;
  padding: 12px 14px 14px;
}

.work-card-badge {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 1.2px;
  color: var(--ink-40);
}

.work-card-name {
  font-size: 14.5px;
  font-weight: 700;
  color: var(--ink);
  margin: 5px 0;
}

.work-card-tagline {
  font-size: 12px;
  color: var(--ink-50);
  line-height: 1.6;
}

.work-group-label {
  grid-column: 1 / -1;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 2px;
  color: var(--ink-40);
  padding-bottom: 8px;
  margin-top: 8px;
  border-bottom: 1px solid var(--line);
}

.work-group-label:first-child {
  margin-top: 0;
}

/* =========================================================
   Project detail
   ========================================================= */
/* No border between projects: a hairline here would sit exactly where
   the previous project's sticky banner text un-sticks and the next
   project's banner begins, showing up as a thin visible gap for a
   frame or two. The dark banner following a light project body is
   already a clean seam on its own. */

.project-banner {
  position: relative;
  background: var(--dark);
  min-height: 280px;
  overflow: hidden;
}

.project-banner-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.55;
}

.project-banner-fade {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(23, 17, 13, 0.1) 0%, rgba(23, 17, 13, 0.55) 55%, var(--dark) 100%);
}

/* Sticks to the top (under the fixed nav) once scrolled there, and
   stays pinned while the rest of that project's content scrolls past
   underneath — handing off to the next project's own banner text as
   soon as this project's section ends. The repo link rides along on
   the right so it stays reachable the whole time too. */
.banner-sticky-sentinel {
  height: 0;
}

.project-banner-text {
  position: sticky;
  top: var(--nav-h);
  z-index: 40;
  background: linear-gradient(180deg, rgba(23, 17, 13, 1) 0%, rgba(23, 17, 13, 0.6) 100%);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--dark-line);
  padding: 22px var(--pad-x);
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

/* The translucent gradient + blur above is always "on", but an opaque
   cover layer sits on top of it (behind the text) and hides it until
   actually stuck. It's the COVER that fades — via opacity, from 1 to
   0 — rather than trying to fade the translucent look itself in: a
   layer that starts fully opaque and only reveals what's already
   underneath as it fades out always animates smoothly, whereas
   transitioning between two different `background` values (solid vs.
   gradient, or gradient vs. gradient) doesn't reliably tween in
   browsers and just snaps. Toggled via JS (IntersectionObserver
   watching a zero-height sentinel placed right before this element)
   once this banner text is actually pinned under the top nav. */
.project-banner-text::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--dark);
  opacity: 1;
  transition: opacity 0.25s ease;
}

.project-banner-text.is-stuck::before {
  opacity: 0;
}

.project-banner-info {
  min-width: 0;
}

.project-banner-text h2 {
  font-family: var(--display);
  font-size: 44px;
  font-weight: 700;
  color: #fff;
  margin: 0 0 12px;
}

.project-banner-text p {
  font-size: 15.5px;
  line-height: 1.7;
  color: var(--dark-muted);
  max-width: 640px;
  margin: 0;
}

.project-body {
  max-width: 1400px;
  margin: 0 auto;
  padding: 40px var(--pad-x) 56px;
  display: flex;
  align-items: flex-start;
  gap: 32px;
}

.project-body-main {
  flex: 1;
  min-width: 0;
}

@media (max-width: 1180px) {
  .project-body {
    display: block;
  }
}

.meta-row {
  display: grid;
  grid-template-columns: repeat(3, auto);
  gap: 48px;
  padding-bottom: 24px;
  border-bottom: 1px solid var(--line);
}

.meta-label {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 1.6px;
  color: var(--ink-40);
  margin-bottom: 6px;
}

.meta-value {
  font-size: 15.5px;
  color: var(--ink-70);
  line-height: 1.6;
}

.stack-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 24px 0 0;
}

.stack-pill {
  font-size: 12.5px;
  padding: 6px 12px;
  border-radius: 999px;
  background: rgba(234, 88, 12, 0.08);
  color: var(--orange-deep);
  border: 1px solid rgba(234, 88, 12, 0.2);
}

/* Same treatment as .btn--primary in the hero -- both are the "go to the
   source" call to action sitting on a dark ground, so they read as one
   control rather than two. The border matches the fill so swapping from
   the old outlined style doesn't shift the box by a pixel. */
.repo-btn {
  flex-shrink: 0;
  background: var(--orange);
  color: #fff;
  border: 1px solid var(--orange);
  padding: 9px 18px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 600;
  transition: all 0.2s ease;
}

.repo-btn:hover {
  background: var(--orange-deep);
  border-color: var(--orange-deep);
  color: #fff;
}

.section-h4 {
  font-family: var(--display);
  font-size: 16px;
  font-weight: 700;
  color: var(--ink);
  margin: 40px 0 16px;
}

.tech-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}

.tech-card {
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 18px;
  display: flex;
  flex-direction: column;
}

.tech-card-body {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* Portrait media (tall video/screenshot): stack the text and media
   side by side instead of on top of each other, so a narrow tall
   clip doesn't leave most of the fixed-height box empty. JS tags the
   card .is-portrait once the media's real pixel dimensions are known
   (see initTechCardOrientation in script.js) -- the card's own box
   height stays whatever the grid row already settled on (its
   landscape sibling), the media just fills that height instead of a
   fixed 280px. */
.tech-card.is-portrait {
  flex-direction: row;
  gap: 14px;
}

.tech-card.is-portrait .tech-card-body {
  flex: 1 1 50%;
  min-width: 0;
}

.tech-card.is-portrait .tech-card-img {
  width: auto;
  height: auto;
  max-width: 45%;
  flex: 0 0 auto;
  align-self: stretch;
  margin-top: 0;
  max-height: 400px;
}

.tech-card-title {
  font-size: 14.5px;
  font-weight: 700;
  color: var(--orange-deep);
  margin-bottom: 9px;
}

.tech-card p {
  font-size: 13px;
  line-height: 1.75;
  color: var(--ink-60);
  margin: 0;
}

.tech-card-img {
  display: block;
  width: 100%;
  height: 280px;
  object-fit: contain;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 6px;
  margin-top: auto;
}

.tech-card p {
  margin-bottom: 12px;
}

.point-list {
  margin: 0;
  padding-left: 20px;
  color: var(--ink-70);
  font-size: 14px;
  line-height: 1.9;
}

.point-list li {
  margin-bottom: 4px;
}

.point-list code {
  font-family: var(--mono);
  font-size: 12.5px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 1px 5px;
  color: var(--orange-deep);
}

/* Small architecture diagram: a "rope" (snake / boustrophedon) layout.
   Steps fill a fixed number of columns left-to-right, then wrap to the
   next row and continue right-to-left, then left-to-right again, and
   so on -- like a folded rope. Column count is fixed (3) so the whole
   thing scales down with the available width instead of forcing a
   horizontal scrollbar; step count doesn't need to be a multiple of 3,
   an incomplete last row just continues from directly under the row
   above. Each step's row/column is set explicitly (grid auto-placement
   can't be trusted to backfill a reversed row on its own) -- the
   nth-child rules below are enumerated up to 24 steps (8 rows), far
   more than any project needs; add more in the same pattern if one
   ever does. */
.system-flow {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 44px 28px;
  margin: 4px 0 8px;
  padding: 26px;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 10px;
}

.flow-step {
  position: relative;
  background: #fff;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  padding: 12px 14px;
  text-align: left;
}

.flow-step:nth-child(1) { grid-row: 1; grid-column: 1; }
.flow-step:nth-child(2) { grid-row: 1; grid-column: 2; }
.flow-step:nth-child(3) { grid-row: 1; grid-column: 3; }
.flow-step:nth-child(4) { grid-row: 2; grid-column: 3; }
.flow-step:nth-child(5) { grid-row: 2; grid-column: 2; }
.flow-step:nth-child(6) { grid-row: 2; grid-column: 1; }
.flow-step:nth-child(7) { grid-row: 3; grid-column: 1; }
.flow-step:nth-child(8) { grid-row: 3; grid-column: 2; }
.flow-step:nth-child(9) { grid-row: 3; grid-column: 3; }
.flow-step:nth-child(10) { grid-row: 4; grid-column: 3; }
.flow-step:nth-child(11) { grid-row: 4; grid-column: 2; }
.flow-step:nth-child(12) { grid-row: 4; grid-column: 1; }
.flow-step:nth-child(13) { grid-row: 5; grid-column: 1; }
.flow-step:nth-child(14) { grid-row: 5; grid-column: 2; }
.flow-step:nth-child(15) { grid-row: 5; grid-column: 3; }
.flow-step:nth-child(16) { grid-row: 6; grid-column: 3; }
.flow-step:nth-child(17) { grid-row: 6; grid-column: 2; }
.flow-step:nth-child(18) { grid-row: 6; grid-column: 1; }
.flow-step:nth-child(19) { grid-row: 7; grid-column: 1; }
.flow-step:nth-child(20) { grid-row: 7; grid-column: 2; }
.flow-step:nth-child(21) { grid-row: 7; grid-column: 3; }
.flow-step:nth-child(22) { grid-row: 8; grid-column: 3; }
.flow-step:nth-child(23) { grid-row: 8; grid-column: 2; }
.flow-step:nth-child(24) { grid-row: 8; grid-column: 1; }

.flow-step-title {
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
  margin-bottom: 5px;
}

.flow-step-detail {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink-60);
}

.flow-step small {
  display: block;
  margin-top: 8px;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 400;
  color: var(--orange-deep);
}

/* Connector arrows: right along an L->R row, left along an R->L row,
   down at the end of a row (steps directly above/below share a
   column, so a straight down arrow is enough). */
.flow-step::after {
  position: absolute;
  color: var(--orange);
  font-size: 18px;
  font-weight: 700;
  line-height: 1;
}

.flow-step:nth-child(6n+1):not(:last-child)::after,
.flow-step:nth-child(6n+2):not(:last-child)::after {
  content: "\2192";
  right: -22px;
  top: 50%;
  transform: translateY(-50%);
}

.flow-step:nth-child(6n+4):not(:last-child)::after,
.flow-step:nth-child(6n+5):not(:last-child)::after {
  content: "\2190";
  left: -22px;
  top: 50%;
  transform: translateY(-50%);
}

.flow-step:nth-child(6n+3):not(:last-child)::after,
.flow-step:nth-child(6n):not(:last-child)::after {
  content: "\2193";
  left: 50%;
  bottom: -32px;
  transform: translateX(-50%);
}

@media (max-width: 720px) {
  .system-flow {
    grid-template-columns: 1fr;
  }

  .flow-step {
    grid-column: 1 !important;
  }

  .flow-step:not(:last-child)::after {
    content: "\2193" !important;
    left: 50% !important;
    right: auto !important;
    top: auto !important;
    bottom: -24px !important;
    transform: translateX(-50%) !important;
  }
}

.ai-scope {
  margin-top: 20px;
  background: rgba(234, 88, 12, 0.07);
  border-left: 4px solid var(--orange);
  border-radius: 4px;
  padding: 13px 16px;
  font-size: 13px;
  line-height: 1.7;
  color: var(--ink-70);
}

.ai-scope strong {
  color: var(--orange-deep);
}

.shot-grid {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 14px;
}

/* Landscape screenshots stay full-width and consistent with each other
   (each on its own row) -- capping by height instead would leave them
   at mismatched widths side by side, which reads as messier than just
   stacking them. */
.shot-grid img,
.shot-grid video {
  display: block;
  /* flex: 0 0 100% (not just width: 100%) so it can't shrink below a
     full row under the flex-wrap algorithm -- every landscape
     screenshot in a grid ends up pixel-identical in width. */
  flex: 0 0 100%;
  width: 100%;
  height: auto;
  border-radius: 8px;
}

/* Portrait (phone) screenshots opt in via this class: capped by height
   instead of stretched to the column width (which would make them
   absurdly tall), so they shrink to a normal size and, thanks to
   flex-wrap above, sit side by side with each other. */
.shot-grid img.shot-portrait,
.shot-grid video.shot-portrait {
  /* Overrides the flex: 0 0 100% above -- without this it would still
     be forced to a full-width row like the landscape default. */
  flex: 0 1 auto;
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: 570px;
}

/* =========================================================
   Contact
   ========================================================= */
.contact {
  padding: calc(var(--nav-h) + 40px) var(--pad-x) 60px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.contact-eyebrow {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 2.5px;
  color: var(--ink-40);
}

.contact-email {
  font-family: var(--display);
  font-size: 28px;
  font-weight: 700;
  margin-top: 12px;
}

.contact-links {
  display: flex;
  justify-content: center;
  gap: 12px;
  margin-top: 22px;
}

.contact-links a {
  border: 1px solid rgba(23, 17, 13, 0.2);
  padding: 10px 22px;
  border-radius: 8px;
  font-size: 13.5px;
  color: var(--ink);
  transition: all 0.2s ease;
}

.contact-links a:hover {
  border-color: var(--orange);
  color: var(--orange-deep);
}

/* =========================================================
   Responsive
   ========================================================= */
@media (max-width: 1180px) {
  :root {
    --pad-x: 40px;
  }

  .work-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .work-card img {
    height: 150px;
  }

  .hero h1 {
    font-size: 40px;
  }

  .stats-bar {
    grid-template-columns: repeat(2, 1fr);
  }

  .stat:nth-child(2) {
    border-right: none;
  }

  .stat:nth-child(3),
  .stat:nth-child(4) {
    border-top: 1px solid var(--dark-line);
  }
}

@media (max-width: 720px) {
  :root {
    --pad-x: 20px;
  }

  .topnav-links {
    gap: 16px;
  }

  .topnav-links a {
    font-size: 12.5px;
  }

  .hero h1 {
    font-size: 30px;
    letter-spacing: -0.5px;
  }

  .hero-desc {
    font-size: 14px;
  }

  .stats-bar {
    grid-template-columns: repeat(2, 1fr);
  }

  .work-grid {
    grid-template-columns: 1fr;
  }

  .project-banner {
    min-height: 200px;
  }

  .project-banner-text h2 {
    font-size: 30px;
  }

  .meta-row {
    grid-template-columns: 1fr;
    gap: 16px;
  }

  .stack-row {
    gap: 8px;
  }

  .project-banner-text {
    flex-direction: column;
    align-items: flex-start;
  }

  .repo-btn {
    width: 100%;
    text-align: center;
  }

  .tech-grid {
    grid-template-columns: 1fr;
  }

  /* Narrow viewport: the side-by-side portrait layout has no room to
     breathe, so fall back to the normal stacked card. */
  .tech-card.is-portrait {
    flex-direction: column;
  }

  .tech-card.is-portrait .tech-card-body {
    flex: initial;
  }

  .tech-card.is-portrait .tech-card-img {
    width: 100%;
    height: 280px;
    max-width: none;
    align-self: auto;
    margin-top: auto;
  }
}
