/* ============================================================
   Vincent Fileccia — minimal portfolio
   ============================================================ */

@font-face {
  font-family: "Die Grotesk";
  src: url("../fonts/DieGrotesk-B-Medium.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --light: #ffffff;   /* "paper" — the light color (visitor-changeable) */
  --dark:  #0a0a0a;   /* "ink"   — the dark color  (visitor-changeable) */
  --bg: var(--light);
  --fg: var(--dark);
  /* greys derive from the palette so any paper/ink combo stays harmonious */
  --muted: color-mix(in srgb, var(--fg) 30%, var(--bg));
  --thumb-bg: color-mix(in srgb, var(--fg) 6%, var(--bg));
  --gutter: 64px;
  --fs: 15px;
  --lh: 1.45;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --invert-speed: 0.55s;
}

* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html { -webkit-text-size-adjust: 100%; }

body {
  font-family: "Die Grotesk", "Helvetica Neue", Arial, sans-serif;
  font-size: var(--fs);
  line-height: var(--lh);
  color: var(--fg);
  background: var(--bg);
  letter-spacing: 0.01em;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  transition: background-color var(--invert-speed) var(--ease),
              color var(--invert-speed) var(--ease);
}

/* Inverted (a project is selected) — paper and ink swap roles */
body.is-inverted {
  --bg: var(--dark);
  --fg: var(--light);
}

img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; letter-spacing: inherit; }

/* ---------- page-load fade-in (subtle, staggered top-to-bottom) ---------- */
/* .fx is added by an inline head script only when motion is allowed, so
   content is never hidden for no-JS or reduced-motion visitors. */
.fx [data-fade] {
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.5s var(--ease), transform 0.5s var(--ease);
  /* NOTE: no `will-change` here — it creates an isolated stacking context that
     traps the portrait's mix-blend-mode so it can't multiply against the paper. */
}
.fx.fx-in [data-fade] {
  opacity: 1;
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  .fx [data-fade] { opacity: 1 !important; transform: none !important; transition: none !important; }
}

/* ---------- page scaffold ---------- */
.page {
  max-width: 1440px;
  margin: 0 auto;
  padding: 0 var(--gutter);
  min-height: 100vh;
}

.row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 48px;
}

.top    { padding-top: 20vh; }
.intro  { margin-top: 20vh; }
.work   { margin-top: 20vh; }
.foot   { margin-top: 16vh; padding-bottom: 40px; }

.wordmark { font-weight: 500; }

.roles span { display: block; }

/* ---------- 404 ---------- */
.notfound { margin-top: 20vh; }
.nf-code {
  font-weight: 500;
  font-size: clamp(48px, 8vw, 104px);
  line-height: 0.95;
  letter-spacing: -0.02em;
}
.nf-text {
  color: var(--muted);
  margin-top: 14px;
  max-width: 34ch;
  transition: color var(--invert-speed) var(--ease);
}
.nf-back { display: inline-block; margin-top: 28px; font-weight: 500; }
.nf-back:hover .scroll-arrow { opacity: 0.55; }

/* ---------- name → live NY clock → sun-arc, stacked in the top-left ---------- */
.top .col-a .clock { display: block; margin-top: 2px; }   /* grey, so it separates from the name */
.sunarc {
  margin-top: 16px;
  width: 100%;
  max-width: 205px;   /* match the desktop portrait width exactly */
  color: var(--fg);   /* the arc rides on currentColor, so it inverts with the page */
}
.sunarc svg { display: block; width: 100%; height: auto; overflow: visible; }

/* ---------- intro / portrait / bio ---------- */
.portrait img {
  width: 205px;
  height: auto;
  filter: grayscale(1);
  background: var(--thumb-bg);
  /* grayscale photo picks up the paper tint through its light areas; on the
     inverted ink state we swap to screen so it adapts to the ink instead of
     crushing to black. */
  mix-blend-mode: multiply;
}
body.is-inverted .portrait img { mix-blend-mode: screen; }
/* the whole bio is a link to the work list; the arrow is the affordance */
.bio {
  display: block;
  margin-top: 34px;
  max-width: 42ch;
  font-weight: 500;
  color: inherit;
  cursor: pointer;
}
.scroll-arrow { display: inline-block; margin-left: 0.15em; white-space: nowrap; }
.bio:hover .scroll-arrow { opacity: 0.55; }

/* ---------- project list ---------- */
.list-label {
  color: var(--muted);
  margin-bottom: 6px;
  transition: color var(--invert-speed) var(--ease);
}

.projects { list-style: none; }

.project-row {
  display: flex;
  align-items: baseline;
  gap: 0;
  width: 100%;
  text-align: left;
  padding: 1px 0;
}

.project-row .marker {
  flex: 0 0 auto;
  width: 0;
  opacity: 0;
  overflow: hidden;
  white-space: nowrap;
  transition: width 0.28s var(--ease), opacity 0.28s var(--ease);
}

.project.is-active .project-row .marker {
  width: 1.4em;   /* "→ " */
  opacity: 1;
}

.rowtext .name { font-weight: 500; }
.rowtext .subtitle {
  color: var(--muted);
  margin-left: 0.5em;
  transition: color var(--invert-speed) var(--ease);
}

/* ---------- thumbnail stage ---------- */
.col-b { position: relative; }

.thumb-stage {
  position: relative;
  width: 100%;
  max-width: 360px;
  aspect-ratio: 1 / 1;
  margin-left: auto;   /* right-aligned in the column */
  margin-top: auto;
}
.thumb-stage .thumb {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.4s var(--ease);
  pointer-events: none;
}
.thumb-stage .thumb.is-visible {
  opacity: 1;
  pointer-events: auto;
  cursor: pointer;
}

/* keep the work row tall enough that the thumb has room */
.work .col-b { min-height: 360px; display: flex; }

/* ---------- footer ---------- */
.foot .col-b {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}
.copyright { color: var(--muted); transition: color var(--invert-speed) var(--ease); }
.clock {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  transition: color var(--invert-speed) var(--ease);
}
@media (max-width: 720px) {
  /* stack copyright + clock, left-aligned, with proper edge margins */
  .foot .col-b {
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
  }
}

/* ---------- visitor color control: click the corner circle to cycle ---------- */
.theme {
  position: fixed;
  top: 20px; right: 20px;
  width: 20px; height: 20px;
  padding: 0;
  border-radius: 50%;
  border: 2px solid var(--fg);   /* solid ink ring — reads as an icon */
  overflow: hidden;
  z-index: 40;
  cursor: pointer;
  transition: transform 0.18s var(--ease), border-color var(--invert-speed) var(--ease);
}
.theme:hover { transform: scale(1.14); }
.theme:active { transform: scale(0.94); }
.theme-dot {
  display: block; width: 100%; height: 100%;
  /* live preview of the current paper + ink */
  background: linear-gradient(135deg, var(--light) 0 50%, var(--dark) 50% 100%);
}
@media (max-width: 720px) {
  .theme { top: 16px; right: 16px; }
}

/* ---------- neon glow floor (Dia-style scroll reveal) ----------
   An in-flow block at the very bottom of the page (below the footer, so text never
   overlaps). It scrolls WITH the page — important on iOS, where a position:fixed
   element gets stuck tinting Safari's translucent bottom bar because it never moves
   to trigger a re-sample. The neon lives in ::before and scales up from the bottom
   edge as you scroll into the block, so it still blooms up from the bottom, holds
   at the very end, and recedes as you scroll back. overscroll.js drives --reveal
   (scroll progress) and --pull (elastic bloom). Clamped height = consistent size. */
/* Dia's mechanic: a spacer reserves scroll room at the page end, and a fixed panel
   pinned to the viewport bottom scales up from its bottom edge as you scroll through
   that spacer — so the neon grows in, holds at the very bottom, and (being fixed)
   the elastic bounce never exposes a gap beneath it. overscroll.js drives --reveal
   (scroll progress) and --pull (elastic bloom). */
.glow-spacer { height: clamp(300px, 60vh, 600px); margin-top: 6vh; }
.glow-floor {
  --glow:  #e4231a;           /* primary (red) — the hot bottom core */
  --glow2: #ff9d1f;           /* second color the stretch reveals (orange) */
  --reveal: 0;
  --pull: 0;
  position: fixed;
  left: 0; right: 0; bottom: 0;
  height: clamp(300px, 60vh, 600px);   /* matches the spacer — a longer, heavier pull */
  margin-bottom: -2vh;                  /* bleed below the viewport so a bounce never shows a gap */
  transform-origin: bottom;
  transform: scaleY(var(--reveal));
  pointer-events: none;
  z-index: 1;
  overflow: hidden;
  will-change: transform;               /* own GPU layer → the stretch stays smooth, no flicker */
}
/* base glow: a hot core at the very bottom, up through green into a cyan band so the
   stretch reads as real motion (two colors sliding), then fades out */
.glow-floor::before {
  content: "";
  position: absolute; inset: 0;
  /* saturated core (no white hotspot) rising from --glow into --glow2, then fading */
  background: radial-gradient(155% 120% at 50% 100%,
    var(--glow) 0%,
    var(--glow) 20%,
    color-mix(in srgb, var(--glow) 55%, var(--glow2)) 38%,
    var(--glow2) 54%,
    color-mix(in srgb, var(--glow2) 45%, transparent) 68%,
    transparent 84%);
}
/* warmer, brighter bloom that fades in with the elastic pull past the end */
.glow-floor::after {
  content: "";
  position: absolute; inset: 0;
  opacity: var(--pull);
  background: radial-gradient(125% 105% at 50% 100%,
    color-mix(in srgb, var(--glow2) 55%, #fff) 0%,
    var(--glow2) 26%,
    color-mix(in srgb, var(--glow) 70%, var(--glow2)) 52%,
    transparent 80%);
}
@media (prefers-reduced-motion: reduce) {
  .glow-floor { transform: none; }
  .glow-floor::after { display: none; }
}
/* Touch devices (notably iOS Safari): a fixed colored panel behind the bottom bar
   gets stuck-tinted, since Safari doesn't reliably re-sample a fixed layer. So here
   we drop the floor into the normal flow — it scrolls with the page, so the bar
   re-samples to clear when you scroll up. Desktop keeps the fixed stretch. */
@media (hover: none) {
  .glow-spacer { display: none; }
  .glow-floor {
    position: relative;
    height: clamp(240px, 46vh, 440px);
    margin-top: 8vh;
    margin-bottom: 0;
    transform: none;          /* no scaleY reveal — the gradient simply scrolls into view */
    will-change: auto;
  }
}

/* ============================================================
   Fullscreen project modal
   ============================================================ */
.modal {
  position: fixed;
  inset: 0;
  /* modal always uses paper as bg + ink as text, independent of the list invert */
  background: var(--light);
  color: var(--dark);
  z-index: 100;
  opacity: 0;
  visibility: hidden;
  touch-action: manipulation;   /* never double-tap-zoom inside the modal */
  transition: opacity 0.35s var(--ease), visibility 0s linear 0.35s;
}
.modal .modal-sub,
.modal .modal-desc,
.modal .modal-count,
.modal .modal-credits { color: color-mix(in srgb, var(--dark) 30%, var(--light)); }
.modal.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.35s var(--ease);
}
.modal-inner {
  height: 100%;
  max-width: 1440px;
  margin: 0 auto;
  padding: 24px var(--gutter) 32px;
  display: flex;
  flex-direction: column;
}
.modal-top {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  grid-template-areas: "brand title close";
  align-items: baseline;
  column-gap: 16px;
  flex: 0 0 auto;
}
.modal-top .wordmark {
  grid-area: brand;
  justify-self: start;
  cursor: pointer;                 /* tap the name to return to the page */
  transition: opacity 0.2s var(--ease);
}
.modal-top .wordmark:hover { opacity: 0.55; }
.modal-close { grid-area: close; justify-self: end; }
.modal-title {
  grid-area: title;
  justify-self: center;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: 0 0.5em;
  text-align: center;
}
/* footer grid — desktop: caption+nav share a row, credits below.
   mobile: caption alone, then credits+nav share a row. */
.modal-foot {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas:
    "caption nav"
    "credits credits";
  column-gap: 16px;
  row-gap: 8px;
  align-items: baseline;
}
.modal-close, .modal-next, .modal-back {
  font-weight: 500;
  touch-action: manipulation;   /* no double-tap-to-zoom on the controls */
}
.modal-close:hover, .modal-next:hover, .modal-back:hover { opacity: 0.55; }

/* Enlarge the tap target invisibly — extends the clickable area ~14px in every
   direction without changing the layout, so thumbs don't miss and hit the page. */
.modal-close, .modal-next, .modal-back { position: relative; }
.modal-next::before, .modal-back::before, .modal-close::before {
  content: "";
  position: absolute;
  inset: -14px;
}

.modal-stage {
  flex: 1 1 auto;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  margin: 18px 0;   /* breathing room above/below the image */
}
.modal-img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  width: auto;
  height: auto;
}
/* desktop: show the image a touch smaller so it isn't upscaled (crisper) */
@media (min-width: 721px) {
  .modal-img { max-width: 90%; max-height: 90%; }
}
/* click hot zones for prev/next over the image */
.modal-hit {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 50%;
  cursor: pointer;
}
.modal-hit-prev { left: 0; cursor: w-resize; }
.modal-hit-next { right: 0; cursor: e-resize; }

/* desktop: caption (left) and nav (right) share one line */
/* reserve one line so the caption never shifts the nav or resizes the image */
.modal-desc { grid-area: caption; color: var(--muted); min-height: 1.4em; max-width: 60ch; }
.modal-name { font-weight: 500; }
.modal-sub  { color: var(--muted); }
.modal-nav  { grid-area: nav; justify-self: end; display: flex; gap: 1.25em; align-items: baseline; }
/* fixed-width, centered counter so Back and Next never shift as the number changes */
.modal-count {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  min-width: 3.4em;
  text-align: center;
}
/* per-project credits — small line at the very bottom.
   Space is reserved (up to ~2 lines) so adding credits never shifts the
   name/nav row above it or resizes the image. */
.modal-credits {
  grid-area: credits;
  font-size: 12px;
  line-height: 1.4;
  max-width: 60ch;
  white-space: pre-line;   /* honor line breaks between credits */
  min-height: 2.8em;       /* reserve ~2 lines */
}

/* ============================================================
   Responsive
   ============================================================ */
@media (max-width: 720px) {
  :root { --gutter: 24px; --fs: 15px; }

  .top { padding-top: 16vh; }
  .intro, .work { margin-top: 14vh; }

  /* header + footer keep two columns; intro + work stack */
  .intro, .work { grid-template-columns: 1fr; }
  .intro .col-a, .work .col-a { display: contents; }

  .bio { max-width: none; margin-top: 24px; }

  /* portrait ≈ 2 of 6 columns (one-third of the content width) on mobile */
  .portrait img { width: 33.33%; }
  /* match the arc to that same one-third-of-content width so it reads as a sibling */
  .sunarc { max-width: none; width: calc((100vw - 2 * var(--gutter)) / 3); }

  .work .col-b { min-height: 0; margin-top: 22px; }
  .thumb-stage { max-width: 100%; margin-left: 0; }

  .foot { grid-template-columns: 1fr; row-gap: 4px; }

  /* project title drops to its own centered line, below "Vincent Fileccia" / Close */
  .modal-top {
    grid-template-columns: 1fr auto;
    grid-template-areas: "brand close" "title title";
    row-gap: 16px;
  }
  /* reserve room for a 2-line title so wrapping doesn't push the image down */
  .modal-title { justify-self: center; margin-top: 4px; min-height: 2.9em; align-content: flex-start; }

  /* mobile: caption on its own row, then credits (left) + nav (right) share a row.
     top-align so an empty credits cell never lets the nav drop — it stays put. */
  .modal-foot {
    grid-template-areas:
      "caption caption"
      "credits nav";
    align-items: start;
  }
}


@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 0.001s !important; }
}
