/* ============================================================
   Ryan Kelly — portfolio
   Typography carried over 1:1 from the original rykelly.vercel.app
   (NTR for every heading and every line of body copy),
   Campione-style motion, Cherry-style project flow & footer stub.
   ============================================================ */

:root {
  --dark-navy: #020c1b;
  --navy: #0a192f;
  --light-navy: #112240;
  --lightest-navy: #233554;
  --slate: #8892b0;
  --light-slate: #a8b2d1;
  --lightest-slate: #ccd6f6;
  --white: #e6f1ff;
  --green: #64ffda;
  --green-tint: rgba(100, 255, 218, 0.1);

  /* One typeface across the site — same as the original site. */
  --font-display: "NTR", "Segoe UI", Arial, sans-serif;
  --font-body: "NTR", "Segoe UI", Arial, sans-serif;

  --page-max: 1200px;
  --radius: 18px;
  --ease: cubic-bezier(0.645, 0.045, 0.355, 1);
}

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

html { scroll-behavior: smooth; }

body {
  background-color: var(--navy);
  color: var(--lightest-slate);
  font-family: var(--font-body);
  font-size: 18px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

a { color: var(--green); text-decoration: none; transition: color 0.2s ease; }
a:hover { color: var(--white); }

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

/* ---------------------------------------------------------
   Typography helpers
   --------------------------------------------------------- */
.display {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 0.95;
  letter-spacing: 0.01em;
}

/* ---------------------------------------------------------
   Navbar
   --------------------------------------------------------- */
#navbar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 5vw;
  background-color: rgba(10, 25, 47, 0.82);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--lightest-navy);
}

/* how far down the page you are — rides the navbar's bottom border and is
   scaled from main.js, so it never animates on its own */
.scroll-progress {
  position: absolute;
  left: 0;
  bottom: -1px;
  width: 100%;
  height: 2px;
  pointer-events: none;
  background: linear-gradient(90deg, var(--green), rgba(100, 255, 218, 0.5));
  box-shadow: 0 0 12px rgba(100, 255, 218, 0.45);
  transform: scaleX(var(--progress, 0));
  transform-origin: left center;
}

.nav-name {
  font-family: var(--font-display);
  font-size: 24px;
  letter-spacing: 0.04em;
  color: var(--green);
  text-transform: uppercase;
}
.nav-name:hover { color: var(--white); }

.nav-links {
  display: flex;
  list-style: none;
  gap: 26px;
}

.nav-links a {
  color: var(--lightest-slate);
  font-size: 17px;
  transition: color 0.2s ease;
}
.nav-links a:hover,
.nav-links a.active { color: var(--green); }

.nav-icons {
  display: flex;
  align-items: center;
  gap: 18px;
}

.nav-icons a { color: var(--lightest-slate); font-size: 18px; }
.nav-icons a:hover { color: var(--green); }

.nav-toggle {
  display: none;
  background: none;
  border: 0;
  color: var(--lightest-slate);
  font-size: 20px;
  cursor: pointer;
}

/* ---------------------------------------------------------
   Background field
   A fixed canvas behind the whole page, holding the section you
   are reading redrawn as particles (main.js section 8). It sits
   above the body colour but below #content, so mostly it shows
   through the gaps between sections and behind the prose.
   --------------------------------------------------------- */
.section-field {
  position: fixed;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.6s var(--ease);
}
.section-field.is-live { opacity: 1; }

/* the content has to outrank the fixed field */
#content,
.site-footer { position: relative; z-index: 1; }

/* ---------------------------------------------------------
   Hero (intro)
   The portrait sits beside the copy as a field of ASCII
   particles, drawn character by character in main.js: a
   square canvas that the copy never overlaps, so neither
   needs a scrim to stay readable.
   --------------------------------------------------------- */
#hero {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 4vw;
  padding: 120px 10vw 60px;
  overflow: hidden;
}

/* a flex item, not a layer — main.js sets the canvas width, style.css
   keeps it square and lets it shrink when the column gets narrow */
.hero-field {
  /* the field keeps the size it drew itself at; the copy is what gives
     way as the viewport narrows. the max-width is the backstop below
     ~1100px, where the column can no longer hold a full-size field */
  flex-shrink: 0;
  min-width: 0;
  max-width: 45%;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  /* hidden until the portrait has decoded, then faded in so the hero
     never flashes an empty box */
  opacity: 0;
  transition: opacity 1.2s var(--ease);
}
.hero-field.is-ready { opacity: 1; }

.hero-field canvas {
  display: block;
  max-width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  cursor: crosshair;
  touch-action: none;
}

.hero-content {
  max-width: 620px;
}

.hero-greeting {
  color: var(--green);
  font-size: 40px;
  margin-bottom: 10px;
}

.hero-name {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: clamp(44px, 7.5vw, 80px);
  color: var(--lightest-slate);
  line-height: 1.02;
  letter-spacing: 0.005em;
  margin-bottom: 14px;
}

/* the name types itself in on load. main.js wraps each character in its own
   span, so the line keeps its final width from the first frame and nothing
   reflows as the characters arrive. */
.hero-name .char {
  display: inline-block;
  white-space: pre;
  opacity: 0;
  animation: type-in 0.01s step-end both;
  animation-delay: calc(var(--type-start, 0.45s) + var(--i, 0) * 80ms);
}
@keyframes type-in { to { opacity: 1; } }

.cursor { color: var(--green); animation: blink 1s step-end infinite; }

@keyframes blink { from, to { opacity: 1; } 50% { opacity: 0; } }

.hero-title {
  font-size: clamp(26px, 3.6vw, 46px);
  color: var(--slate);
  font-weight: 300;
  margin-bottom: 20px;
}

.hero-desc {
  color: var(--slate);
  font-size: 20px;
  max-width: 500px;
  margin-bottom: 40px;
}

.hero-links { display: flex; gap: 20px; flex-wrap: wrap; }

.hero-links a {
  color: var(--green);
  font-size: 17px;
  border: 1px solid var(--green);
  padding: 10px 22px;
  border-radius: 999px;
  transition: all 0.25s ease;
}
.hero-links a:hover { background-color: var(--green-tint); color: var(--white); }

/* ---------------------------------------------------------
   Shared section chrome
   --------------------------------------------------------- */
section { padding: 100px 5vw; }

#about, #projects, #experience, #skills, #learning, #articles {
  max-width: var(--page-max);
  margin: 0 auto;
}

.section-header {
  position: relative;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: 48px;
  padding-bottom: 16px;
}

/* the rule draws itself in from the left as the section arrives, instead of
   arriving with the header's fade */
.section-header::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--lightest-navy);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 1.1s var(--ease) 0.12s;
}
.section-header.is-visible::after { transform: scaleX(1); }

.section-title {
  font-family: var(--font-body);
  font-weight: bold;
  font-size: clamp(28px, 3.6vw, 46px);
  color: var(--lightest-slate);
  letter-spacing: 0.01em;
  white-space: nowrap;
}

.section-link {
  font-size: 16px;
  color: var(--slate);
  white-space: nowrap;
  border-bottom: 1px solid transparent;
}
.section-link:hover { color: var(--green); border-bottom-color: var(--green); }

/* ---------------------------------------------------------
   About (kept from v1)
   --------------------------------------------------------- */
.about-content {
  display: grid;
  grid-template-columns: 3fr 2fr;
  gap: 60px;
  align-items: center;
}

.about-text p { color: var(--slate); font-size: 20px; margin-bottom: 20px; }

.highlight { color: var(--green); }

.about-image { display: flex; justify-content: center; }

.image-placeholder img {
  width: 100%;
  max-width: 300px;
  border-radius: var(--radius);
  border: 1px solid var(--lightest-navy);
  filter: grayscale(20%);
  transition: filter 0.3s ease;
}
.image-placeholder img:hover { filter: none; }

/* ---------------------------------------------------------
   Projects — homepage deck (name + full-bleed image)
   Each card pins under the navbar and the next one slides up
   over it, the way the reference portfolio's services section
   stacks its cards. Per-card motion comes from JS via the
   --deck-* custom properties (pure function of scroll).
   --------------------------------------------------------- */
.project-flow {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7vh;
  padding-bottom: 6vh;
}

.project-box {
  --stack: 0;
  --deck-y: 0px;
  --deck-rx: 0deg;
  --deck-s: 1;
  --deck-b: 1;
  position: relative;
  /* wide enough to read, never taller than the viewport, so the next card
     is always seen sliding up underneath */
  width: 100%;
  max-width: min(900px, max(420px, calc((100vh - 250px) * 1.5)));
  display: block;
  color: inherit;
  overflow: hidden;
  border-radius: 28px;
  border: 1px solid rgba(100, 255, 218, 0.14);
  background:
    radial-gradient(120% 150% at 10% 0%, rgba(100, 255, 218, 0.07), transparent 55%),
    linear-gradient(180deg, #14294a 0%, var(--light-navy) 42%, #0d1d36 100%);
  box-shadow:
    inset 0 1px 0 rgba(230, 241, 255, 0.07),
    0 -26px 50px -30px rgba(2, 12, 27, 0.9),
    0 40px 90px -45px rgba(2, 12, 27, 1);
  transform: perspective(1500px) translate3d(0, var(--deck-y), 0) rotateX(var(--deck-rx)) scale(var(--deck-s));
  filter: brightness(var(--deck-b));
  translate: 0 var(--deck-lift, 0px);
  transition: translate 0.5s var(--ease), border-color 0.5s var(--ease), box-shadow 0.5s var(--ease);
  will-change: transform;
}
.project-box:nth-child(2) { --stack: 1; }
.project-box:nth-child(3) { --stack: 2; }
.project-box:nth-child(4) { --stack: 3; }

/* the cards only pin and stack where there is room for a full card plus the
   next one arriving; narrow screens keep a plain, calmer list */
@media (min-width: 769px) {
  .project-box {
    position: sticky;
    top: calc(92px + var(--stack) * 16px);
  }
}

.project-box:hover {
  --deck-lift: -8px;
  border-color: rgba(100, 255, 218, 0.42);
  box-shadow:
    inset 0 1px 0 rgba(230, 241, 255, 0.12),
    0 -26px 50px -30px rgba(2, 12, 27, 0.9),
    0 60px 110px -50px rgba(2, 12, 27, 1),
    0 0 80px -34px rgba(100, 255, 218, 0.4);
}

.project-box-head {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 18px;
  padding: 22px 30px;
  border-bottom: 1px solid rgba(100, 255, 218, 0.08);
}

.project-box-index {
  font-family: var(--font-body);
  font-size: 16px;
  letter-spacing: 0.12em;
  color: var(--green);
}

.project-box-name {
  font-family: var(--font-body);
  font-weight: bold;
  font-size: clamp(22px, 2.8vw, 34px);
  letter-spacing: 0.01em;
  color: var(--lightest-slate);
  line-height: 1.1;
  /* clip the per-character reveal; padding keeps descenders intact */
  overflow: hidden;
  padding-bottom: 0.16em;
  margin-bottom: -0.16em;
}

/* staggered character reveal, the way the reference site reveals
   its service titles (JS wraps each character in a .char span) */
.project-box-name .char {
  display: inline-block;
  white-space: pre;
  opacity: 0;
  translate: 0 0.55em;
  transition: opacity 0.5s var(--ease), translate 0.6s var(--ease);
  transition-delay: calc(var(--i, 0) * 26ms);
}
.project-box-name.is-visible .char { opacity: 1; translate: 0 0; }

.project-box-arrow {
  font-size: 22px;
  color: var(--slate);
  /* the hover offset and the magnetic pull are separate channels, summed
     here, so neither cancels the other */
  translate: calc(var(--mag-x, 0px) + var(--hov-x, 0px)) calc(var(--mag-y, 0px) + var(--hov-y, 0px));
  transition: translate 0.35s var(--ease), color 0.35s ease;
}
.project-box:hover .project-box-arrow { color: var(--green); --hov-x: 6px; --hov-y: -6px; }

.project-box-media {
  position: relative;
  overflow: hidden;
  aspect-ratio: 3 / 2;
  background: var(--navy);
}

.project-box-media img {
  width: 100%;
  height: 100%;
  /* every screenshot is authored at exactly 3:2, so `cover` bleeds to the
     frame edges and crops nothing — no letterbox padding, no cut words */
  object-fit: cover;
  object-position: center;
  transition: filter 0.6s var(--ease);
}

/* the shot drifts inside its frame while the card is travelling (main.js sets
   --media-y and --media-s). The frame is the same 3:2 as the shot, so the
   scale exists only to provide the slack the drift needs — it is exactly the
   drift at its furthest, which means the image is at 1:1 and uncropped the
   moment the card settles */
.project-box-media img:not([src$=".svg"]) {
  transform: translate3d(0, var(--media-y, 0px), 0) scale(var(--media-s, 1));
}
/* the vector stand-in keeps its own composition rather than being cropped */
.project-box-media img[src$=".svg"] { object-fit: contain; }

.project-box:hover .project-box-media img { filter: brightness(1.07) saturate(1.05); }

/* a light sweeps the frame on hover — motion that crops nothing */
.project-box-media::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  translate: -60% 0;
  background: linear-gradient(102deg, transparent 44%, rgba(230, 241, 255, 0.07) 50%, transparent 56%);
  transition: opacity 0.45s var(--ease), translate 1s var(--ease);
}
.project-box:hover .project-box-media::after { opacity: 1; translate: 60% 0; }

/* ---------------------------------------------------------
   Work page
   --------------------------------------------------------- */
.page-hero { max-width: var(--page-max); margin: 0 auto; padding: 150px 5vw 60px; }

.back-link { color: var(--slate); font-size: 16px; }
.back-link:hover { color: var(--green); }

.page-title {
  font-family: var(--font-body);
  font-weight: bold;
  font-size: clamp(38px, 6vw, 72px);
  line-height: 1.05;
  letter-spacing: 0.01em;
  color: var(--lightest-slate);
  margin: 24px 0 20px;
}

.page-lede { color: var(--slate); font-size: 20px; max-width: 660px; }

.work-list {
  max-width: var(--page-max);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 90px;
}

.work-item {
  padding-top: 40px;
  border-top: 1px solid var(--lightest-navy);
  scroll-margin-top: 100px;
}

.work-head {
  display: flex;
  align-items: baseline;
  gap: 18px;
  flex-wrap: wrap;
  margin-bottom: 24px;
}

.work-index { font-family: var(--font-body); font-size: 16px; letter-spacing: 0.12em; color: var(--green); }

.work-name {
  font-family: var(--font-body);
  font-weight: bold;
  font-size: clamp(28px, 4vw, 48px);
  line-height: 1.1;
  letter-spacing: 0.01em;
  color: var(--lightest-slate);
}

.work-year {
  margin-left: auto;
  color: var(--slate);
  font-size: 16px;
  letter-spacing: 0.14em;
}

.work-media {
  overflow: hidden;
  border-radius: var(--radius);
  aspect-ratio: 3 / 2;
  border: 1px solid var(--lightest-navy);
  background: var(--light-navy);
  display: block;
}

.work-media img {
  width: 100%;
  height: 100%;
  /* authored at exactly 3:2 → full bleed, nothing cropped */
  object-fit: cover;
  object-position: center;
}

/* the shot uncovers from its bottom edge and settles out of a slight scale as
   the item arrives; both are driven off the wrap's existing data-reveal */
.work-media-wrap .work-media {
  clip-path: inset(0 0 100% 0);
  transition: clip-path 1.1s var(--ease) 0.08s;
}
.work-media-wrap.is-visible .work-media { clip-path: inset(0 0 0 0); }

.work-media-wrap .work-media img {
  transform: scale(1.06);
  transition: transform 1.4s var(--ease);
}
.work-media-wrap.is-visible .work-media img { transform: scale(1); }
.work-media img[src$=".svg"] { object-fit: contain; }

.work-body {
  display: grid;
  grid-template-columns: 1fr 1.1fr;
  gap: 40px 60px;
  margin-top: 34px;
  align-items: start;
}

.work-desc { color: var(--light-slate); font-size: 22px; line-height: 1.55; }

.work-meta { display: flex; flex-direction: column; gap: 18px; }

.work-meta-row {
  display: grid;
  grid-template-columns: 110px 1fr;
  gap: 16px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--lightest-navy);
}

.work-meta-row dt {
  color: var(--green);
  font-size: 14px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  padding-top: 4px;
}

.work-meta-row dd { color: var(--slate); font-size: 17px; }

.tag {
  display: inline-block;
  margin: 0 8px 8px 0;
  padding: 5px 12px;
  border: 1px solid var(--lightest-navy);
  border-radius: 999px;
  font-size: 14px;
  color: var(--light-slate);
  transition: all 0.25s ease;
}
.tag:hover { border-color: var(--green); color: var(--green); }

.work-links { display: flex; gap: 18px; flex-wrap: wrap; }
.work-links a { border-bottom: 1px solid var(--green); font-size: 17px; }

.work-note {
  margin-top: 18px;
  padding-left: 16px;
  border-left: 2px solid var(--green);
  color: var(--slate);
  font-size: 17px;
}

/* feature breakdown (case-study detail) */
.feature-block { margin-top: 56px; }

.feature-heading {
  font-family: var(--font-body);
  font-weight: bold;
  font-size: clamp(20px, 2.4vw, 26px);
  letter-spacing: 0.01em;
  color: var(--lightest-slate);
  margin-bottom: 26px;
}

.feature-grid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 22px;
}

.feature {
  background: var(--light-navy);
  border: 1px solid var(--lightest-navy);
  border-radius: 14px;
  padding: 24px;
  transition: transform 0.35s var(--ease), border-color 0.35s ease, background 0.35s ease;
}
.feature:hover {
  transform: translateY(-6px);
  border-color: rgba(100, 255, 218, 0.35);
  background: var(--lightest-navy);
}

.feature h4 {
  font-family: var(--font-body);
  font-weight: bold;
  font-size: 17px;
  letter-spacing: 0.01em;
  color: var(--lightest-slate);
  margin-bottom: 10px;
}

.feature p { color: var(--slate); font-size: 16px; }

.work-footer-cta {
  max-width: var(--page-max);
  margin: 100px auto 0;
  padding: 44px;
  border: 1px solid var(--lightest-navy);
  border-radius: var(--radius);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
}
.work-footer-cta p {
  font-family: var(--font-body);
  font-weight: bold;
  font-size: clamp(20px, 2.6vw, 30px);
  color: var(--lightest-slate);
}

.btn-primary {
  display: inline-block;
  padding: 14px 30px;
  border-radius: 999px;
  background: var(--green);
  color: var(--dark-navy);
  font-size: 17px;
  transition: transform 0.25s var(--ease), background 0.25s ease;
}
.btn-primary:hover { background: var(--white); color: var(--dark-navy); transform: translateY(-3px); }

/* ---------------------------------------------------------
   Contact page — one boxed form, nothing else.
   The headline pulls word by word into focus and the fields
   stagger in behind it, the way the reference portfolio does.
   --------------------------------------------------------- */
.contact-wrap {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 130px 5vw 120px;
}

.contact-box {
  max-width: 780px;
  margin: 0 auto;
  padding: clamp(24px, 3.4vw, 48px);
  border: 1px solid rgba(100, 255, 218, 0.4);
  border-radius: 22px;
  background: rgba(17, 34, 64, 0.35);
  box-shadow: 0 40px 90px -50px rgba(2, 12, 27, 1);
}

.contact-headline {
  font-weight: bold;
  font-size: clamp(30px, 4.4vw, 52px);
  line-height: 1.1;
  color: var(--lightest-slate);
  margin-bottom: clamp(24px, 3vw, 38px);
}

/* each word is pulled into focus (JS wraps them in .word spans) */
.contact-headline .word {
  display: inline-block;
  opacity: 0;
  filter: blur(12px);
  translate: 0 0.16em;
  transition: opacity 0.6s var(--ease), filter 0.9s var(--ease), translate 0.8s var(--ease);
  transition-delay: calc(var(--i, 0) * 70ms);
}
.contact-headline.is-visible .word { opacity: 1; filter: blur(0); translate: 0 0; }

.contact-form { display: flex; flex-direction: column; gap: 22px; }

.contact-row { display: grid; grid-template-columns: 1fr 1fr; gap: 22px 32px; }

.contact-field { display: flex; flex-direction: column; gap: 6px; }

.contact-field label {
  font-weight: bold;
  font-size: 15px;
  color: var(--lightest-slate);
}

/* underlined fields, like the reference — the accent only shows on focus */
.contact-field input,
.contact-field select,
.contact-field textarea {
  width: 100%;
  background: transparent;
  border: 0;
  border-bottom: 1px solid rgba(168, 178, 209, 0.45);
  border-radius: 0;
  color: var(--white);
  font-family: inherit;
  font-size: 17px;
  line-height: 1.5;
  padding: 10px 0 12px;
  transition: border-color 0.3s ease, box-shadow 0.3s ease;
}

.contact-field textarea { min-height: 132px; resize: vertical; }

.contact-field input::placeholder,
.contact-field textarea::placeholder { color: var(--slate); }

.contact-field input:focus,
.contact-field select:focus,
.contact-field textarea:focus {
  outline: none;
  border-bottom-color: var(--green);
  box-shadow: 0 1px 0 0 var(--green);
}

.contact-select { position: relative; }
.contact-select select {
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  padding-right: 30px;
}
.contact-select::after {
  content: "";
  position: absolute;
  right: 6px;
  bottom: 20px;
  width: 8px;
  height: 8px;
  border-right: 1px solid var(--slate);
  border-bottom: 1px solid var(--slate);
  transform: rotate(45deg);
  pointer-events: none;
}
.contact-select select option { background: var(--light-navy); color: var(--lightest-slate); }

.contact-submit {
  width: 100%;
  padding: 16px;
  border-radius: 14px;
  border: 1px solid rgba(230, 241, 255, 0.45);
  background: transparent;
  color: var(--lightest-slate);
  font-family: inherit;
  font-weight: bold;
  font-size: 17px;
  cursor: pointer;
  transition: background 0.35s var(--ease), border-color 0.35s var(--ease), color 0.35s var(--ease);
}
.contact-submit:hover,
.contact-submit:focus-visible {
  outline: none;
  background: rgba(100, 255, 218, 0.1);
  border-color: var(--green);
  color: var(--green);
}
.contact-submit[disabled] { opacity: 0.55; cursor: progress; }

.contact-status {
  min-height: 1.6em;
  margin-top: -8px;
  font-size: 16px;
  color: var(--slate);
}
.contact-status.is-ok { color: var(--green); }
.contact-status.is-error { color: #ff9a9a; }

/* ---------------------------------------------------------
   Experience (kept from v1)
   --------------------------------------------------------- */
/* the rail: a faint track the whole way down, and a mint fill that follows
   the scroll and lights each entry's marker as it reaches it (main.js sets
   --timeline and the is-lit class from one shared trigger line) */
.experience-list {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 40px;
  padding-left: 34px;
}

.experience-list::before,
.experience-list::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 1px;
  pointer-events: none;
}
.experience-list::before { background: var(--lightest-navy); }
.experience-list::after {
  background: var(--green);
  box-shadow: 0 0 10px rgba(100, 255, 218, 0.5);
  transform: scaleY(var(--timeline, 0));
  transform-origin: top center;
}

.experience-item {
  position: relative;
  display: grid;
  grid-template-columns: 150px 1fr;
  gap: 20px;
  padding-bottom: 40px;
  border-bottom: 1px solid var(--lightest-navy);
}

/* the marker, punched through the rail with a ring of page colour */
.experience-item::before {
  content: "";
  position: absolute;
  left: -33px;
  top: 9px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--lightest-navy);
  box-shadow: 0 0 0 4px var(--navy);
  transform: translateX(-50%);
  transition: background 0.4s ease, box-shadow 0.4s ease;
}
.experience-item.is-lit::before {
  background: var(--green);
  box-shadow: 0 0 0 4px var(--navy), 0 0 14px rgba(100, 255, 218, 0.75);
}
.experience-item:last-child { border-bottom: none; }

.experience-date { color: var(--slate); font-size: 16px; padding-top: 4px; white-space: nowrap; }

.experience-title { color: var(--lightest-slate); font-size: 22px; font-weight: bold; margin-bottom: 12px; }

.experience-place { color: var(--green); font-weight: normal; }

.experience-desc { color: var(--slate); font-size: 18px; line-height: 1.6; }

/* ---------------------------------------------------------
   Skills (kept from v1)
   --------------------------------------------------------- */
.skills-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 40px; }

.skill-group-title {
  color: var(--lightest-slate);
  font-size: 20px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--lightest-navy);
  padding-bottom: 8px;
}

.skill-list { list-style: none; display: flex; flex-direction: column; gap: 10px; }

.skill-list li {
  color: var(--slate);
  font-size: 18px;
  transition: color 0.2s ease;
}
.skill-list li::before { content: "▹ "; color: var(--green); }
.skill-list li:hover { color: var(--green); }

/* each skill types in, character by character (JS wraps them in .char).
   the spans are laid out from the first frame even while they are invisible,
   so the line holds its final width and never re-wraps as the characters land
   — which is why it is free to wrap normally on a narrow screen.
   --pre is how many characters came before this line in its group, so a
   column types top to bottom rather than four lines racing each other. */
.skill-list li .char {
  display: inline-block;
  white-space: pre;
  opacity: 0;
  transition: opacity 0.12s linear;
  transition-delay: calc((var(--pre, 0) + var(--i, 0)) * 55ms);
}
.skill-list li.is-visible .char { opacity: 1; }

/* the caret rides the line that is typing and is gone otherwise. its window
   is the line's own slice of the group's session: `both` holds the 0% frame
   before the delay and the 100% frame after it, so only one line shows it. */
.skill-list li::after {
  content: "";
  display: inline-block;
  width: 0.5em;
  height: 1.05em;
  margin-left: 3px;
  vertical-align: -0.18em;
  background: var(--green);
  opacity: 0;
}
.skill-list li.is-visible::after {
  animation: caret-line calc(var(--n, 4) * 55ms + 240ms) linear both;
  animation-delay: calc(var(--pre, 0) * 55ms);
}
@keyframes caret-line {
  0% { opacity: 0; }
  6%, 78% { opacity: 1; }
  100% { opacity: 0; }
}

/* ---------------------------------------------------------
   Currently learning — awards-style list
   --------------------------------------------------------- */
.awards-list { display: flex; flex-direction: column; }

.award-row {
  display: grid;
  grid-template-columns: 90px 1fr auto;
  align-items: center;
  gap: 24px;
  padding: 34px 8px;
  border-bottom: 1px solid var(--lightest-navy);
  transition: background 0.35s var(--ease), padding 0.35s var(--ease);
}
.award-row:first-child { border-top: 1px solid var(--lightest-navy); }
.award-row:hover { background: var(--light-navy); padding-left: 24px; padding-right: 24px; }

.award-index { font-family: var(--font-body); font-size: 16px; letter-spacing: 0.12em; color: var(--slate); }
.award-row:hover .award-index { color: var(--green); }

.award-title {
  font-family: var(--font-body);
  font-weight: bold;
  font-size: clamp(20px, 2.6vw, 30px);
  line-height: 1.15;
  letter-spacing: 0.01em;
  color: var(--lightest-slate);
  margin-bottom: 8px;
  transition: color 0.3s ease;
}
.award-row:hover .award-title { color: var(--green); }

.award-desc { color: var(--slate); font-size: 16px; max-width: 640px; }

.award-status {
  font-size: 13px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--green);
  border: 1px solid var(--green);
  border-radius: 999px;
  padding: 6px 14px;
  white-space: nowrap;
}

/* ---------------------------------------------------------
   Articles — a fanned deck of cards, turned into place by the
   scroll. The band is the accent colour full-bleed, the cards
   run past its bottom edge and are cut off there, and each one
   turns to face you when you hover it. Nothing moves on its own:
   the fan-in below is a pure function of the scroll position.
   --------------------------------------------------------- */
#articles.articles-band {
  max-width: none;
  margin: 0;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  padding: 100px 5vw 0;
  background: var(--green);
  color: var(--navy);
  overflow: hidden;
}

.articles-inner { max-width: var(--page-max); margin: 0 auto; }

.articles-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  text-align: center;
  margin-bottom: 46px;
}

.articles-link {
  color: var(--navy);
  font-size: 15px;
  border: 1px solid rgba(2, 12, 27, 0.5);
  border-radius: 999px;
  padding: 7px 18px;
  transition: background 0.3s var(--ease), color 0.3s var(--ease), border-color 0.3s ease;
}
.articles-link:hover { background: var(--navy); border-color: var(--navy); color: var(--green); }

.articles-title {
  font-family: var(--font-body);
  font-weight: bold;
  font-size: clamp(34px, 5vw, 64px);
  line-height: 1;
  letter-spacing: 0.01em;
}

/* The visible height of the deck. The cards are taller than this, so the
   band's own edge cuts them off — the way a stack of prints would be. */
.articles-fan {
  display: flex;
  align-items: flex-start;
  height: 430px;
}

/* the rotated card: JS only ever writes the fan-in vars here */
.article-card {
  display: block;
  position: relative;
  flex: 0 0 340px;
  margin-right: -53px;
  z-index: 1;
  color: inherit;
  --fan-rx: -30deg;
  --fan-ry: 50deg;
  --fan-y: 0px;
  transform: rotateX(var(--fan-rx)) rotateY(var(--fan-ry)) translateY(var(--fan-y));
  will-change: transform;
}
.article-card:last-child { margin-right: 0; }
.article-card:hover,
.article-card:focus-visible { z-index: 3; }
.article-card:focus-visible { outline: none; }

/* the card itself, which turns to face the viewer on hover */
.article-face {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 14px;
  height: 400px;
  padding: 26px;
  border-radius: 14px;
  border: 1px solid rgba(100, 255, 218, 0.22);
  background: linear-gradient(160deg, var(--light-navy), var(--navy) 62%);
  box-shadow: 0 30px 60px -40px rgba(2, 12, 27, 0.9);
  overflow: hidden;
  --hov-rx: 0deg;
  --hov-ry: 0deg;
  --hov-y: 0px;
  transform: rotateX(var(--hov-rx)) rotateY(var(--hov-ry)) translateY(var(--hov-y));
  transition: transform 0.55s var(--ease), border-color 0.4s ease,
    box-shadow 0.4s ease, background 0.4s ease;
}
.article-card:hover .article-face,
.article-card:focus-visible .article-face {
  --hov-rx: -17deg;
  --hov-ry: 37deg;
  --hov-y: -16px;
  border-color: rgba(100, 255, 218, 0.55);
  box-shadow: 0 50px 90px -45px rgba(2, 12, 27, 1);
}

.article-tag {
  color: var(--green);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  padding-right: 44px;
}

.article-title {
  font-family: var(--font-body);
  font-weight: bold;
  font-size: 19px;
  line-height: 1.35;
  color: var(--lightest-slate);
}

/* held back until the card is turned to face you */
.article-desc {
  color: var(--slate);
  font-size: 15px;
  line-height: 1.5;
  opacity: 0;
  transition: opacity 0.4s var(--ease);
}
.article-card:hover .article-desc,
.article-card:focus-visible .article-desc { opacity: 1; }

.article-date {
  margin-top: auto;
  color: var(--slate);
  font-size: 13px;
  letter-spacing: 0.1em;
}

.article-arrow {
  position: absolute;
  top: 22px;
  right: 22px;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid rgba(100, 255, 218, 0.45);
  color: var(--green);
  font-size: 15px;
  transition: background 0.35s var(--ease), border-color 0.35s ease, color 0.35s var(--ease);
}
.article-card:hover .article-arrow,
.article-card:focus-visible .article-arrow {
  background: var(--green);
  border-color: var(--green);
  color: var(--navy);
}

/* ---------------------------------------------------------
   Footer stub
   --------------------------------------------------------- */
.site-footer {
  border-top: 1px solid var(--lightest-navy);
  margin-top: 60px;
  /* the extra bottom room is for the pinned road: even before it parks itself
     at the footer, the last of the footer is never sitting under the strip */
  padding: 60px 5vw 120px;
  background: var(--dark-navy);
}

.footer-name {
  font-family: var(--font-body);
  font-weight: bold;
  font-size: clamp(40px, 11vw, 150px);
  line-height: 0.95;
  letter-spacing: 0.02em;
  text-align: center;
  color: transparent;
  -webkit-text-stroke: 1px var(--lightest-navy);
  margin-bottom: 60px;
  user-select: none;
  /* drifts across as the footer comes up (main.js sets --name-x) */
  translate: var(--name-x, 0vw) 0;
}

.footer-grid {
  max-width: var(--page-max);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr 1fr 1fr;
  gap: 40px;
  padding-bottom: 50px;
  border-bottom: 1px solid var(--lightest-navy);
}

.footer-photo img {
  width: 160px;
  height: 160px;
  object-fit: cover;
  border-radius: var(--radius);
  border: 1px solid var(--lightest-navy);
  filter: grayscale(30%);
  margin-bottom: 18px;
}

.footer-cta {
  font-family: var(--font-body);
  font-weight: bold;
  font-size: 20px;
  color: var(--lightest-slate);
  max-width: 240px;
  line-height: 1.3;
}

.footer-heading {
  font-family: var(--font-body);
  font-weight: bold;
  font-size: 14px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--green);
  margin-bottom: 16px;
}

.footer-col ul { list-style: none; display: flex; flex-direction: column; gap: 10px; }
.footer-col li { color: var(--slate); font-size: 16px; }
.footer-col a { color: var(--slate); }
.footer-col a:hover { color: var(--green); }

.footer-bottom {
  max-width: var(--page-max);
  margin: 0 auto;
  padding-top: 24px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  color: var(--slate);
  font-size: 15px;
}
.to-top { color: var(--green); }
.to-top:hover { color: var(--white); }

/* ---------------------------------------------------------
   The start button for the run, and the road itself
   The button is the invitation; the road is a strip pinned to
   the bottom of the page, not a panel over it. Everything in
   it is monospace, because it is pretending to be a terminal.
   --------------------------------------------------------- */
.hero-play {
  margin-top: 22px;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 10px 20px;
  border: 1px solid var(--lightest-navy);
  border-radius: 999px;
  background: rgba(100, 255, 218, 0.04);
  color: var(--light-slate);
  font-family: var(--font-body);
  font-size: 16px;
  letter-spacing: 0.02em;
  cursor: pointer;
  translate: var(--mag-x, 0px) var(--mag-y, 0px);
  transition: translate 0.35s var(--ease), border-color 0.3s ease,
    color 0.3s ease, background 0.3s ease;
}
.hero-play:hover {
  border-color: var(--green);
  color: var(--green);
  background: var(--green-tint);
}
.hero-play-mark { color: var(--green); }

/* the road — a small run pinned to the bottom of the page ---------------- */
/* it is deliberately a strip and not a screen: it never takes the page over,
   it slides off at the contact form, and it can be folded away entirely. */
.road {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 90;
  font-family: monospace;
  /* it is a section in the markup, so the shared section padding has to be
     cancelled: otherwise the strip floats a hundred pixels off the bottom
     with a dead band under it that still swallows clicks */
  padding: 0;
  margin: 0;
  transform: translateY(0);
  transition: transform 0.45s var(--ease);
}
/* nudged out of the way while the contact form is on screen */
.road.is-parked { transform: translateY(calc(100% - 6px)); }
.road.is-folded .road-stage { display: none; }
.road.is-folded.is-parked { transform: translateY(100%); }

.road-strip {
  border-top: 1px solid rgba(100, 255, 218, 0.18);
  background: linear-gradient(180deg, rgba(17, 34, 64, 0.97), var(--dark-navy));
  box-shadow: 0 -20px 44px -34px rgba(2, 12, 27, 1);
}

.road-hud {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 27px;
  padding: 3px 14px;
  border-bottom: 1px solid rgba(100, 255, 218, 0.1);
}
.road-name { color: var(--green); font-size: 11px; letter-spacing: 0.16em; }
.road-name::before { content: "ryan@portfolio:~$ "; color: var(--slate); }

.road-stats { margin-left: auto; display: flex; align-items: center; gap: 14px; }
.road-stat {
  color: var(--slate);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.road-stat b { color: var(--lightest-slate); font-weight: 400; }

.road-shield {
  color: var(--green);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.road-shield[hidden] { display: none; }

.road-fold {
  width: 26px;
  height: 21px;
  border: 1px solid var(--lightest-navy);
  border-radius: 6px;
  background: none;
  color: var(--slate);
  font-size: 10px;
  line-height: 1;
  cursor: pointer;
  transition: border-color 0.25s ease, color 0.25s ease;
}
.road-fold:hover { border-color: var(--green); color: var(--green); }

/* the stage is short on purpose — a lane is one small car tall */
.road-stage {
  position: relative;
  height: 84px;
  overflow: hidden;
  background: var(--dark-navy);
  /* idle, the strip lets a swipe scroll the page as usual, so someone flicking
     past on a phone is never caught by it */
  touch-action: pan-y;
  outline: none;
}
/* mid-run a swipe is a lane change, so the page must not move under it */
.road.is-running .road-stage { touch-action: none; }
/* the road is the only place arrow keys mean lane changes, so it is reachable
   by tab and says so when it has focus */
.road-stage:focus-visible { box-shadow: inset 0 0 0 1px rgba(100, 255, 218, 0.45); }
.road-canvas { display: block; width: 100%; height: 100%; }

/* one line across the strip: the invitation, and the result */
.road-prompt {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 0 16px;
  background: rgba(2, 12, 27, 0.82);
}
.road-prompt[hidden] { display: none; }
.road-message { color: var(--lightest-slate); font-size: 13px; }
.road-message.is-fail { color: var(--green); letter-spacing: 0.16em; }
.road-hint { color: var(--slate); font-size: 11px; }

.road-go {
  padding: 6px 20px;
  border: 1px solid var(--green);
  border-radius: 999px;
  background: none;
  color: var(--green);
  font-family: monospace;
  font-size: 12px;
  letter-spacing: 0.14em;
  cursor: pointer;
  transition: background 0.25s ease, color 0.25s ease;
}
.road-go:hover { background: var(--green); color: var(--dark-navy); }

/* a roadblock stops the run and asks about Ryan. the card sits above the
   strip, so the road stays in view while you answer. */
.road-card {
  position: relative;
  max-width: min(760px, calc(100% - 28px));
  margin: 0 auto 8px;
  padding: 14px 38px 14px 16px;
  border: 1px solid rgba(100, 255, 218, 0.24);
  border-radius: 12px;
  background: linear-gradient(180deg, #0d1d36, var(--navy));
  box-shadow: 0 30px 60px -40px rgba(2, 12, 27, 1);
}
.road-card[hidden] { display: none; }

.road-card-close {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 24px;
  height: 24px;
  border: 1px solid var(--lightest-navy);
  border-radius: 7px;
  background: none;
  color: var(--slate);
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
  transition: border-color 0.25s ease, color 0.25s ease;
}
.road-card-close:hover { border-color: var(--green); color: var(--green); }

.road-question { color: var(--lightest-slate); font-size: 13px; margin-bottom: 10px; }
.road-question::before { content: "? "; color: var(--green); }
.road-options { display: flex; flex-wrap: wrap; gap: 8px; }
.road-option {
  flex: 1 1 190px;
  padding: 8px 12px;
  border: 1px solid var(--lightest-navy);
  border-radius: 9px;
  background: none;
  color: var(--slate);
  font-family: monospace;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease;
}
.road-option:hover { border-color: var(--green); color: var(--green); background: var(--green-tint); }
.road-option.is-right { border-color: var(--green); color: var(--green); background: var(--green-tint); }
.road-option.is-wrong { border-color: #ff8f8f; color: #ff8f8f; }

.road-verdict {
  min-height: 14px;
  margin-top: 9px;
  color: var(--slate);
  font-size: 11px;
  letter-spacing: 0.06em;
}
.road-verdict.is-right { color: var(--green); }



@media (max-width: 768px) {
  .road-stage { height: 72px; }
  .road-hud { gap: 10px; padding: 3px 10px; }
  .road-name::before { content: "$ "; }
  /* keep the two readouts that matter during a run, drop the record */
  .road-stats .road-stat:nth-child(3) { display: none; }
  .road-hint { display: none; }
  /* a question has to stay readable on a phone, so the options stack */
  .road-card { padding: 12px 36px 12px 12px; }
  .road-option { flex-basis: 100%; }
}

/* ---------------------------------------------------------
   Magnetic links
   main.js leans each of these a few pixels toward the pointer
   (--mag-x / --mag-y) and eases it back on the way out. The
   defaults are zero, so nothing happens at all without the JS
   — including for anyone who asked for less motion.
   --------------------------------------------------------- */
.nav-name,
.nav-links a,
.nav-icons a,
.section-link,
.to-top {
  translate: var(--mag-x, 0px) var(--mag-y, 0px);
  transition: translate 0.35s var(--ease);
}

/* these transition a colour as well, so both are listed rather than one
   shorthand quietly replacing the other */
.nav-links a { transition: translate 0.35s var(--ease), color 0.2s ease; }

/* this one already transitions `all`, so translate comes along for free */
.hero-links a { translate: var(--mag-x, 0px) var(--mag-y, 0px); }

/* ---------------------------------------------------------
   Reveal-on-scroll + motion
   --------------------------------------------------------- */
[data-reveal] {
  opacity: 0;
  --reveal-y: 34px;
  transform: translate3d(0, var(--reveal-y, 34px), 0);
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
}
[data-reveal].is-visible { opacity: 1; --reveal-y: 0px; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  [data-reveal] { opacity: 1; transform: none; transition: none; }
  /* cards stay stacked in place, but nothing travels with the scroll */
  .project-box { transform: none; filter: none; }
  .project-box-media::after { display: none; }
  /* the shots sit still, uncropped, and their frames are never clipped.
     written to match the parallax selector's specificity, or the transform
     above would win */
  .project-box-media img:not([src$=".svg"]) { transform: none; }
  /* reveals land instantly instead of travelling or de-blurring */
  .project-box-name .char,
  .contact-headline .word { opacity: 1; filter: none; translate: 0 0; transition: none; }
  /* the name is simply there, rather than typing itself in */
  .hero-name .char { opacity: 1; animation: none; }
  /* the deck sits still and flatter instead of turning into place */
  .article-card { --fan-rx: -16deg; --fan-ry: 34deg; --fan-y: 0px; }
  .article-card:hover .article-face { --hov-rx: 0deg; --hov-ry: 0deg; --hov-y: 0px; }
  /* the hero field is still there, it just holds a single frame */
  .hero-field { transition: none; }

  /* the rest of the page's scroll furniture, all landed */
  .section-header::after { transform: scaleX(1); transition: none; }
  .work-media-wrap .work-media { clip-path: none; transition: none; }
  .work-media-wrap .work-media img { transform: none; transition: none; }
  .skill-list li .char { opacity: 1; transition: none; }
  .skill-list li::after { display: none; }
  .experience-list::after { transform: scaleY(1); }
  .experience-item::before { background: var(--green); }
  .footer-name { translate: 0vw 0; }
  /* the watermark field is not worth any frames if nothing may move */
  .section-field { display: none; }
}

/* ---------------------------------------------------------
   Responsive
   --------------------------------------------------------- */
@media (max-width: 1024px) {
  .work-body { grid-template-columns: 1fr; gap: 28px; }
  .feature-grid { grid-template-columns: repeat(2, 1fr); }
  .footer-grid { grid-template-columns: 1fr 1fr 1fr; }
  .footer-photo { grid-column: 1 / -1; }
}

@media (max-width: 768px) {
  body { font-size: 17px; }

  #navbar { padding: 14px 5vw; }
  .nav-name { font-size: 18px; }

  .nav-links {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    gap: 0;
    background: rgba(2, 12, 27, 0.97);
    border-bottom: 1px solid var(--lightest-navy);
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.35s var(--ease);
  }
  .nav-links.open { max-height: 420px; }
  .nav-links li { border-top: 1px solid var(--lightest-navy); }
  .nav-links a { display: block; padding: 16px 5vw; }

  .nav-toggle { display: block; }

  #hero {
    flex-direction: column;
    justify-content: center;
    align-items: flex-start;
    padding: 130px 6vw 60px;
    min-height: 100vh;
  }

  /* the field moves above the copy and shrinks to fit the column; the
     size it draws itself at comes from main.js, this only caps it */    .hero-field {
      order: -1;
      align-self: center;
      justify-content: center;
      max-width: 100%;
      margin-bottom: 34px;
    }

  .hero-greeting { font-size: 22px; }

  section { padding: 70px 6vw; }

  .section-header { flex-direction: column; align-items: flex-start; gap: 10px; }
  .section-title { white-space: normal; }

  .about-content { grid-template-columns: 1fr; gap: 40px; }
  .about-image { order: -1; }
  .about-text p { font-size: 17px; }

  .project-flow { gap: 56px; padding-bottom: 0; }
  .project-box { border-radius: 22px; }
  .project-box-head { padding: 16px 18px; gap: 12px; }

  .page-hero { padding: 120px 6vw 40px; }
  .contact-wrap { padding: 108px 6vw 80px; }
  .contact-row { grid-template-columns: 1fr; gap: 22px; }
  .contact-headline .word { transition-delay: calc(var(--i, 0) * 50ms); }
  .work-list { gap: 60px; }
  .work-meta-row { grid-template-columns: 1fr; gap: 6px; }
  .work-desc { font-size: 18px; }
  .feature-grid { grid-template-columns: 1fr; gap: 16px; }
  .feature { padding: 20px; }

  .experience-list { padding-left: 26px; }
  .experience-item { grid-template-columns: 1fr; gap: 4px; }
  .experience-item::before { left: -25px; }
  .experience-title { font-size: 18px; }
  .experience-desc { font-size: 16px; }

  .skills-grid { grid-template-columns: repeat(2, 1fr); gap: 30px; }

  .award-row { grid-template-columns: 44px 1fr; gap: 12px; padding: 24px 4px; }
  .award-status { grid-column: 2; justify-self: start; }
  .award-row:hover { padding-left: 12px; padding-right: 12px; }

  /* on a phone the deck is just a list of cards */
  #articles.articles-band { padding: 56px 6vw 0; }
  .articles-head { margin-bottom: 30px; }
  .articles-fan { display: block; height: auto; }
  .article-card { margin-right: 0; transform: none; }
  .article-card + .article-card { margin-top: 16px; }
  .article-face { height: auto; transform: none; padding: 22px; }
  .article-desc { opacity: 1; }
  .article-date { margin-top: 4px; }
  .article-title { font-size: 18px; }

  .footer-grid { grid-template-columns: 1fr 1fr; gap: 30px; }
  .footer-photo img { width: 120px; height: 120px; }
  .footer-bottom { flex-direction: column; text-align: center; }
}

@media (max-width: 480px) {
  .footer-grid { grid-template-columns: 1fr; }
}
