/* Rayan — personal portfolio. "Greenhouse": a mossy, dark editorial design
   with oversized display type and full-bleed alternating case studies.

   Type system: a humanist sans for display/body, an italic serif used only
   for the single accent word in each big heading, and a monospace for
   structural/meta text (eyebrow, nav, labels, footer) — three voices, used
   consistently, not decoration. System font stacks only (no Google Fonts
   request): the project's own test suite asserts zero non-file network
   requests when the page loads over file://, so every face below is a
   locally-available equivalent rather than a webfont.

   LAYOUT: a top nav bar (not a sticky rail) over a single flowing column.
   The hero and contact sections sit inside a centered content measure; the
   projects section deliberately breaks out of that measure so each case
   study can run edge-to-edge, alternating text/media side each row. */

:root {
  --bg: #0d130e;
  --bg-alt: #0b110c;

  --text: #eef2e6;
  --text-secondary: #9aa892;
  --text-dim: #7f9a6e;

  /* Bright mossy green — the page's one accent color. 12.6:1 against
     --bg (AA requires 4.5:1 for body text), so it's safe for text as well
     as the solid-fill button. */
  --accent: #b6e26a;
  --accent-soft: #c8ee85;

  --border: #1c2a1d;
  --border-strong: #2f4a2c;

  --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, "Times New Roman", serif;
  --font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Segoe UI Mono", Consolas,
    "Liberation Mono", Menlo, monospace;

  --space-3xs: 0.25rem;
  --space-2xs: 0.5rem;
  --space-xs: 0.75rem;
  --space-sm: 1.25rem;
  --space-md: 2rem;
  --space-lg: 3rem;
  --space-xl: 4.5rem;

  --measure: 62ch;
  --content-max: 1200px;
  --gutter: clamp(1.25rem, 5vw, 3.75rem);
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  color-scheme: dark; /* [WIG: Dark Mode] fixes native scrollbar/inputs */
}

body {
  margin: 0;
  font-family: var(--font-sans);
  background: var(--bg);
  color: var(--text);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden; /* [WIG: Safe Areas] no stray horizontal scrollbar */
}

::selection {
  background: var(--accent);
  color: var(--bg);
}

/* Matte grain — a physical, greenhouse-glass texture instead of a flat
   digital fill. Inline SVG turbulence, no image request. `position: fixed`
   takes it out of flow and `pointer-events: none` keeps it from
   intercepting clicks/taps on the content beneath it. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)' opacity='0.05'/%3E%3C/svg%3E");
}

/* [WIG: Accessibility] skip link — visually hidden until focused. */
.skip-link {
  position: absolute;
  top: -3rem;
  left: 1rem;
  z-index: 10000;
  background: var(--accent);
  color: var(--bg);
  padding: 0.6rem 1rem;
  border-radius: 3px;
  font-weight: 600;
  text-decoration: none;
}

.skip-link:focus {
  top: 1rem;
}

/* ---------------------------------------------------------------------
   Top bar
   --------------------------------------------------------------------- */
header {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-md);
  padding: clamp(1rem, 3vw, 1.6rem) var(--gutter);
  border-bottom: 1px solid var(--border);
  background: var(--bg);
}

.logo {
  margin: 0;
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 0.9375rem;
  letter-spacing: 0.04em;
  color: var(--text);
}

.logo-dot {
  color: var(--accent);
}

/* On secondary pages the logo doubles as the way home. */
.logo a {
  color: inherit;
  text-decoration: none;
}

header nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: clamp(1rem, 3vw, 1.6rem);
  flex-wrap: wrap;
}

header nav a {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-secondary);
  text-decoration: none;
}

header nav a:hover,
header nav a:focus-visible {
  color: var(--accent);
}

/* ---------------------------------------------------------------------
   Hero — oversized editorial headline, photo + bio side by side beneath it
   --------------------------------------------------------------------- */
#hero {
  position: relative;
  max-width: var(--content-max);
  margin: 0 auto;
  padding: clamp(3rem, 8vw, 5.5rem) var(--gutter) clamp(2.5rem, 6vw, 4rem);
  scroll-margin-top: 5rem;
}

/* pointer-events: none is the actual fix for hover reachability, not
   just the exclusion-zone math in script.js: a block element's hit box
   spans its full layout width regardless of where its glyphs end (a
   short line still blocks the row's whole width), so any node behind
   .hero-content would still swallow the graph's pointermove no matter
   how carefully it's positioned. Nothing inside is interactive (the
   real links live in the contact section), so passing all pointer
   events through to the canvas costs nothing except mouse text
   selection here. */
.hero-content {
  position: relative;
  z-index: 1;
  pointer-events: none;
}

/* Skills graph — an Obsidian-style node/link map of my skills (data in
   script.js, grouped there so new skills are just one more array entry).
   A quiet full-bleed backdrop behind the hero text (z-index puts
   .hero-content above it), not a boxed widget — low-opacity at rest so
   it recedes, and only turns on above a width where a whole-hero
   background actually has room to read as atmosphere rather than
   clutter under the headline. */
#skills-canvas {
  display: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

@media (min-width: 900px) {
  #skills-canvas {
    display: block;
  }
}

/* The color key lives as a ring around the node cloud, and the "My
   ⟨Group⟩ Skills" label is drawn centered over that cluster's own
   nodes — both directly on the canvas (see script.js), so there's no
   separate DOM caption to keep positioned or in sync. */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 0 0 clamp(1.25rem, 3vw, 1.75rem);
  text-wrap: balance;
}

h1 {
  font-family: var(--font-sans);
  font-size: clamp(2.75rem, 11vw, 7.5rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 0.92;
  margin: 0;
  text-wrap: balance; /* [WIG: Typography] prevents ragged widows */
  scroll-margin-top: 2rem;
}

h1 em,
h2 em {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 400;
  color: var(--accent);
}

.hero-media {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: clamp(1.25rem, 4vw, 2.5rem);
  margin-top: clamp(2rem, 5vw, 3rem);
}

.hero-photo {
  display: block;
  flex-shrink: 0;
  width: clamp(84px, 10vw, 120px);
  height: clamp(84px, 10vw, 120px);
  border-radius: 50%;
  object-fit: cover;
  border: 3px solid var(--text-secondary);
}

@media (prefers-reduced-motion: no-preference) {
  .hero-photo {
    animation: floaty 7s ease-in-out infinite;
  }
}

@keyframes floaty {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-6px);
  }
}

.bio {
  font-size: clamp(0.95rem, 1.1vw, 1.0625rem);
  line-height: 1.7;
  color: var(--text-secondary);
  margin: 0;
  max-width: 46ch;
}

/* ---------------------------------------------------------------------
   Tag strip
   --------------------------------------------------------------------- */
.tag-strip {
  display: flex;
  align-items: center;
  gap: clamp(0.75rem, 2vw, 1.25rem);
  padding: clamp(0.9rem, 2vw, 1.1rem) var(--gutter);
  background: var(--bg-alt);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--text-dim);
}

.tag-strip-label {
  flex: none;
  white-space: nowrap;
  color: var(--text);
}

/* The track clips to the strip's width; .tag-strip-scroll is twice as wide
   as one copy of the skill list (it holds two identical copies back to
   back) and is nudged left by exactly one copy's width on a loop, so the
   moment it "resets" to 0% is pixel-identical to the frame before —
   the seam is invisible and the ticker reads as scrolling forever. */
.tag-strip-track {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
}

.tag-strip-scroll {
  display: flex;
  width: max-content;
  animation: tag-strip-marquee var(--tag-strip-duration, 20s) linear infinite;
}

.tag-strip-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: nowrap;
  gap: 0 clamp(0.75rem, 2vw, 1.25rem);
}

.tag-strip-list li {
  white-space: nowrap;
}

.tag-strip-list li::after {
  content: "/";
  margin-left: clamp(0.75rem, 2vw, 1.25rem);
  color: var(--border-strong);
}

@keyframes tag-strip-marquee {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .tag-strip-scroll {
    animation: none;
  }

  .tag-strip-track {
    overflow-x: auto;
  }

  .tag-strip-list[data-duplicate] {
    display: none;
  }
}

/* ---------------------------------------------------------------------
   Mission & values — nonprofit-style statement, its own page (about.html,
   reached via the "About" nav link). Sits inside the centered content
   measure like the hero and contact sections.
   --------------------------------------------------------------------- */
#values {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: clamp(3rem, 7vw, 4.5rem) var(--gutter) clamp(2.5rem, 6vw, 4rem);
  scroll-margin-top: 5rem;
}

#values-heading {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.75rem, 4vw, 2.75rem);
  letter-spacing: -0.01em;
  line-height: 1.15;
  color: var(--text);
  margin: 0 0 clamp(1.25rem, 3vw, 2rem);
}

.mission {
  font-size: clamp(1.25rem, 2.4vw, 1.75rem);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.4;
  color: var(--text);
  margin: 0 0 clamp(2rem, 5vw, 3rem);
  max-width: 34ch;
  text-wrap: balance;
}

.mission em {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 400;
  color: var(--accent);
}

.values-grid {
  display: grid;
  gap: clamp(1.5rem, 3.5vw, 2.5rem) clamp(2rem, 5vw, 3.5rem);
}

@media (min-width: 761px) {
  .values-grid {
    grid-template-columns: 1fr 1fr;
  }
}

.value {
  border-top: 1px solid var(--border);
  padding-top: clamp(1rem, 2.5vw, 1.5rem);
}

.value h2 {
  font-family: var(--font-sans);
  font-size: clamp(1.0625rem, 1.6vw, 1.25rem);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.3;
  color: var(--text);
  margin: 0 0 var(--space-2xs);
}

.value p {
  font-size: clamp(0.9rem, 1.05vw, 1rem);
  line-height: 1.7;
  color: var(--text-secondary);
  margin: 0;
  max-width: 52ch;
}

/* ---------------------------------------------------------------------
   Projects — "Selected work", full-bleed alternating case studies. This
   section intentionally breaks out of --content-max so each case study's
   media side can run edge-to-edge instead of sitting inside a column.
   --------------------------------------------------------------------- */
#projects {
  padding-top: clamp(3rem, 7vw, 4.5rem);
}

#projects-heading {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.75rem, 4vw, 2.75rem);
  letter-spacing: -0.01em;
  color: var(--text);
  margin: 0 0 clamp(1.5rem, 4vw, 2.5rem);
  padding: 0 var(--gutter);
}

/* Each case study IS the link — the whole row is the click/tap target.
   [WIG: Touch] keeps the target generous instead of a thin "view
   project" line.

   Mobile-first: below, everything (media, index numeral, h3, p) just
   stacks in DOM order in a single column — no grid-column/grid-row
   assignment needed, so there's nothing for a narrow viewport to
   override. The two/three-column side-by-side arrangements (including
   the --reverse and --split variants) only exist from 761px up, inside
   one min-width query, so a desktop-only compound selector can never
   outrank a mobile reset — there is no mobile reset to outrank. */
.project-card {
  display: grid;
  align-items: stretch;
  width: 100%;
  color: inherit;
  text-decoration: none;
  border-top: 1px solid var(--border);
}

.project-card:last-child {
  border-bottom: 1px solid var(--border);
}

/* Full-bleed: the screenshot fills its half of the row edge to edge, no
   matting. object-position favors the top, since these are designed
   graphics with a headline at the top and supporting detail below — if
   the row's height (set by the text column) forces a vertical crop,
   losing some bottom padding reads better than losing the headline. */
.project-media {
  min-height: clamp(220px, 32vw, 340px);
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 0%;
  border-bottom: 1px solid var(--border);
  background: var(--bg-alt);
}

/* dwriter's two side-by-side screenshots stay letterboxed (not cropped):
   each is a full app window, so cropping either one loses UI chrome
   that a single full-bleed photo-style crop doesn't. */
.project-card--split .project-media {
  object-fit: contain;
  object-position: 50% 50%;
  box-sizing: border-box;
  padding: clamp(1rem, 3vw, 2rem);
}

.project-index {
  padding: clamp(2rem, 5vw, 3rem) var(--gutter) 0;
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 2.25rem;
  line-height: 1;
  color: var(--text-dim);
}

.project-card h3 {
  padding: clamp(0.75rem, 2vw, 1.125rem) var(--gutter) 0;
  font-family: var(--font-sans);
  font-size: clamp(1.5rem, 3.4vw, 2.375rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.05;
  margin: 0;
  text-wrap: balance;
  color: var(--text);
}

.project-card p {
  padding: clamp(0.9rem, 2vw, 1.125rem) var(--gutter) clamp(2rem, 5vw, 3rem);
  font-size: clamp(0.9rem, 1.05vw, 1rem);
  line-height: 1.7;
  color: var(--text-secondary);
  margin: 0;
  max-width: 44ch;
}

@media (min-width: 761px) {
  .project-card {
    grid-template-columns: 1fr 1fr;
  }

  .project-media {
    grid-column: 2;
    grid-row: 1 / 4;
    border-bottom: none;
    border-left: 1px solid var(--border);
  }

  .project-index,
  .project-card h3,
  .project-card p {
    grid-column: 1;
  }

  /* Alternate sides via an explicit modifier (not :nth-child) so a card
     needing a different column count — see --split below — isn't
     fighting a structural rule keyed to its position in the list. */
  .project-card--reverse .project-media {
    grid-column: 1;
    border-left: none;
    border-right: 1px solid var(--border);
  }

  .project-card--reverse .project-index,
  .project-card--reverse h3,
  .project-card--reverse p {
    grid-column: 2;
  }

  /* dwriter — two screenshots side by side filling the media half. Adds
     a third grid track and puts both <img>s on it, images left / text
     right to keep alternating with the row above. */
  .project-card--split {
    grid-template-columns: 1fr 1fr 1fr;
  }

  .project-card--split .project-media {
    border-left: none;
  }

  .project-card--split .project-media:nth-of-type(1) {
    grid-column: 1;
    border-right: 1px solid var(--border);
  }

  .project-card--split .project-media:nth-of-type(2) {
    grid-column: 2;
  }

  .project-card--split .project-index,
  .project-card--split h3,
  .project-card--split p {
    grid-column: 3;
  }
}

.project-card:hover,
.project-card:focus-visible {
  background: var(--bg-alt);
}

.project-card:hover h3,
.project-card:focus-visible h3 {
  color: var(--accent-soft);
}

a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* ---------------------------------------------------------------------
   Contact — big statement heading with social links beside a real
   Netlify form. Explicit grid placement on the section's direct children
   (h2, p, ul, form) puts the form beside the heading without a wrapper.
   --------------------------------------------------------------------- */
#contact {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: clamp(3.5rem, 8vw, 5rem) var(--gutter) clamp(3rem, 6vw, 4rem);
  scroll-margin-top: 2rem;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  column-gap: clamp(2rem, 6vw, 4rem);
  row-gap: var(--space-sm);
}

#contact-heading {
  grid-column: 1;
  grid-row: 1;
  font-size: clamp(2.25rem, 6vw, 4rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.05;
  margin: 0;
}

#contact p:not(.hidden):not(.field) {
  grid-column: 1;
  grid-row: 2;
  margin: 0;
}

.social-links {
  grid-column: 1;
  grid-row: 3;
  align-self: start;
  list-style: none;
  margin: var(--space-sm) 0 0;
  padding: 0;
  display: flex;
  gap: var(--space-sm);
}

.social-links a {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  padding: 0.7em 1.4em;
  text-decoration: none;
}

.social-links a:hover,
.social-links a:focus-visible {
  border-color: var(--accent);
}

/* A small "stamped" tag rather than plain text — the page's one
   deliberate use of a bordered pill, so location reads as metadata. */
.location-tag {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  padding: 0.35em 0.9em;
}

/* Netlify Forms honeypot — hidden from everyone (sighted, screen reader,
   or bot that doesn't respect CSS). It only exists to catch bots that
   auto-fill every field; a real visitor never sees it. */
.hidden {
  display: none;
}

.contact-form {
  grid-column: 2;
  grid-row: 1 / 4;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  align-self: start;
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
  margin: 0;
}

.field label {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.field input,
.field textarea {
  font: inherit;
  color: var(--text);
  background: var(--bg-alt);
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  padding: 0.65em 0.85em;
}

.field textarea {
  resize: vertical;
  min-height: 6rem;
}

.field input:focus-visible,
.field textarea:focus-visible {
  border-color: var(--accent);
}

.contact-form button {
  align-self: flex-start;
  font: inherit;
  font-weight: 600;
  color: var(--bg);
  background: var(--accent);
  border: none;
  border-radius: 4px;
  padding: 0.7em 1.6em;
  cursor: pointer;
}

.contact-form button:hover {
  background: var(--accent-soft);
}

footer {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: var(--space-sm) var(--gutter) var(--space-lg);
  border-top: 1px solid var(--border);
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
}

/* No page-load reveal animation: an opacity fade-in briefly alpha-blends
   text into --bg, which reads as a real (if transient) contrast failure
   to both automated scanners and anyone with low vision. Motion stays
   limited to stateful, user-triggered transitions (hover/focus color
   shifts) and the ambient headshot float above, both gated below. */
@media (prefers-reduced-motion: no-preference) {
  header nav a,
  .project-card,
  .project-card h3,
  .social-links a,
  a,
  button {
    transition: color 150ms ease, border-color 150ms ease, background-color 200ms ease;
  }
}

/* ---------------------------------------------------------------------
   Below 760px: single column throughout — case studies stack media above
   text instead of running side by side, and the contact form drops
   beneath the heading/social links instead of sitting beside them.
   --------------------------------------------------------------------- */
@media (max-width: 760px) {
  #contact {
    grid-template-columns: 1fr;
  }

  #contact-heading {
    grid-row: 1;
  }

  #contact p:not(.hidden):not(.field) {
    grid-column: 1;
    grid-row: 2;
  }

  .social-links {
    grid-column: 1;
    grid-row: 3;
  }

  .contact-form {
    grid-column: 1;
    grid-row: 4;
    margin-top: var(--space-md);
  }
}
