/* ============================================================
   Evan Tempo — portfolio
   Design tokens pulled from Figma (Desktop - Home, node 26:133)
   ============================================================ */

:root {
  /* Color */
  --color-bg-top: #04001f;
  --color-bg-bottom: #000000;
  --color-ink: #ffffff;
  --color-frame: #6b0a43;      /* default image-frame fill */
  --color-accent: #1452aa;     /* hover bar */

  /* Type */
  --font-display: "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-mono: "Fragment Mono", monospace;
  --font-serif: "Instrument Serif", Georgia, serif;

  /* Lengths are rem (px @ 1440 in comments) so they ride the zoned root
     font-size (see "Scaling zones" at the end of this file). Exceptions:
     hairlines (1px), min-width, and --space-page-x (fixed 61px below 1920
     per the Figma frames — it must NOT scale in zone B). */
  --text-display: 3rem;        /* 48px — project list */
  --tracking-display: -0.05em; /* -2.4px @ 48px */
  --text-mono: 1.0625rem;      /* 17px — nav, labels, rail */
  --tracking-mono: 0.3em;      /* 5.1px @ 17px */
  --text-tag: 1.0625rem;       /* 17px — "case study" (user override, was 12px) */
  --tracking-tag: 0.07em;      /* user override (was 0.02em) */

  /* Spacing */
  --space-page-x: 61px;        /* left edge of content — fixed px (see zones) */
  --space-list-bottom: 8.6875rem; /* 139px — project list offset from bottom */
  --space-footer-bottom: 2.1875rem; /* 35px */
  --space-indent: 4.9375rem;   /* 79px — project name indent under heading */
  --rail-gap: 1.75rem;         /* 28px — word↔dot gap (28 + 9 + 28 = 65 word-to-word) */
  --rail-dot: 0.5625rem;       /* 9px — separator dot diameter */
  --space-frame-y: 1.25rem;    /* 20px — frame inset from top/bottom */
  --rule-col: 2.5rem;          /* 40px — columns holding the vertical rules */
  --rail-col: 2.8125rem;       /* 45px — vertical word rail */
  --stage-left: 50.35%;        /* right panel starts here */

  /* Hover bar */
  --bar-h: 0.875rem;           /* 14px */
  --bar-bleed: 3.375rem;       /* 54px — bar starts this far left of the text */
  --bar-tail: 7.5rem;          /* 120px — bar runs this far past the text */

  /* Motion — values here are for the LONGEST bar; main.js scales
     --dur/--dur-out/--tag-delay per link (proportional to bar width)
     so every bar travels at the same perceived velocity. */
  --dur: 300ms;                /* bar grow-in (max) */
  --dur-out: 220ms;            /* bar retract (max) — faster out than in */
  --dur-tag: 160ms;            /* "case study" fade */
  --tag-delay: 140ms;          /* fallback: --dur − --dur-tag */
  --ease-snap: cubic-bezier(0.76, 0, 0.24, 1); /* liquid snap: grows from left, snappy arrival */
}

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

/* Zone A root (≤1440): the 1440 design, unscaled. Zones B and C override
   this in the "Scaling zones" section at the end of the file. */
html {
  font-size: 16px;
}

html,
body {
  height: 100%;
}

body {
  background: linear-gradient(to top, var(--color-bg-bottom), var(--color-bg-top));
  color: var(--color-ink);
  font-family: var(--font-display);
  overflow: hidden;
  min-width: 1200px; /* desktop-first; responsive rules TBD */
}

.page {
  position: relative;
  height: 100%;
}

a {
  color: inherit;
  text-decoration: none;
}

/* ------------------------------------------------------------
   Project list (bottom left)
   ------------------------------------------------------------ */

.projects {
  position: absolute;
  left: var(--space-page-x);
  bottom: var(--space-list-bottom);
}

.group {
  margin-bottom: -0.125rem; /* -2px */
}

.group-title {
  font-family: var(--font-display);
  font-size: var(--text-display);
  font-weight: 400;
  letter-spacing: var(--tracking-display);
  line-height: normal;
  margin-bottom: -0.625rem; /* -10px */
}

.group-list {
  list-style: none;
  padding-left: var(--space-indent);
}

.group-list li {
  margin-bottom: -0.15rem; /* -2.4px */
}

.project-link {
  position: relative;
  z-index: 0;
  display: inline-block;
  font-size: var(--text-display);
  letter-spacing: var(--tracking-display);
  line-height: normal;
  white-space: nowrap;
}

/* Wraps the translatable project name so the "case study" tag span isn't
   swallowed by i18n.js's [data-en][data-ru] query (see i18n-contract.md).
   inline-block (not the span default of inline) so i18n.js's flap
   width-pin actually takes effect — width has no effect on a plain
   inline element. At rest this renders identically to plain inline text:
   the difference only matters once JS sets an explicit width mid-flap. */
.project-name {
  display: inline-block;
}

/* Hover bar — grows from the left, sits BEHIND the text.
   Static final width; we animate transform: scaleX() (compositor-driven,
   no layout thrash) so the growth reads liquid-smooth. */
.project-link::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: calc(-1 * var(--bar-bleed));
  top: 50%;
  height: var(--bar-h);
  width: calc(100% + var(--bar-bleed) + var(--bar-tail));
  background: var(--color-accent);
  transform: translateY(-50%) scaleX(0);
  transform-origin: left center;
  transition: transform var(--dur-out) var(--ease-snap); /* retract */
}

.project-link:hover::before,
.project-link:focus-visible::before,
.project-link.is-cued::before {
  transform: translateY(-50%) scaleX(1);
  transition-duration: var(--dur); /* grow-in, slightly slower than out */
}

/* "case study" tag at the tail of the bar */
.tag {
  position: absolute;
  left: calc(100% + var(--bar-tail) - 0.25rem); /* 4px optical tuck */
  top: 50%;
  transform: translate(-100%, -50%);
  font: italic 400 var(--text-tag) / 1 var(--font-serif);
  letter-spacing: var(--tracking-tag);
  white-space: nowrap;
  opacity: 0;
  transition: opacity var(--dur-tag) ease;
}

.project-link:hover .tag,
.project-link:focus-visible .tag,
.project-link.is-cued .tag {
  opacity: 1;
  transition-delay: var(--tag-delay); /* lands exactly as the bar finishes */
}

.project-link:focus-visible {
  outline: none;
}

/* ------------------------------------------------------------
   Footer (bottom left)
   ------------------------------------------------------------ */

/* Zone A: fixed 108px gaps (both Figma frames put the footer's left edge
   at 61px; zones B/C widen it to a measured span — see "Scaling zones"). */
.site-footer {
  position: absolute;
  left: var(--space-page-x);
  bottom: var(--space-footer-bottom);
  display: flex;
  justify-content: space-between;
  gap: 0;
  /* Right edge = min(637px, 49.65vw) so the row never crosses the stage's
     left rule. The stage starts at --stage-left (50.35% in zone A) → its
     column left edge is 49.65vw from the right; below ~1265px viewport the
     637px edge would land past the rule, so we clamp to the rule column.
     At 1440 max(803, 715) = 803 → right edge 637px, matching the Figma
     layout and continuous with zone B (calc(100vw-637px) = 803 @1440). */
  right: max(calc(100vw - 637px), calc(100vw - var(--stage-left)));
}

/* Inverted hover: dimmed at rest, full white on hover/focus */
.footer-link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem; /* 8px — label ↔ leave-site arrow */
  font-family: var(--font-mono);
  font-size: var(--text-mono);
  letter-spacing: var(--tracking-mono);
  text-transform: uppercase;
  opacity: 0.55;
  transition: opacity var(--dur-out) var(--ease-snap);
}

/* ↗ leave-site arrow on external links (inherits the link's opacity) */
.footer-arrow {
  width: 0.625rem;  /* 10px */
  height: 0.625rem;
  flex: none;
}

.footer-link:hover,
.footer-link:focus-visible {
  opacity: 1;
}

/* EN/RU language toggle — button chrome reset. A <button> doesn't inherit
   `color` the way an <a> does (the UA stylesheet gives it
   `color: buttontext`, i.e. black), so color must be forced to inherit
   explicitly — .footer-link never sets its own color, it relies on
   inheriting ink from further up, which only reaches an anchor. Font
   must NOT be reset here: this rule is declared after .footer-link (or
   .cs-nav-link on case-study bottom navs) at equal specificity, so a
   `font` shorthand here would outrank and clobber that class's own
   font-family/font-size — dropping it is safe, since the UA's
   `button { font: 400 13.33px Arial }` already loses to any author rule,
   so the link class's font declarations apply normally on their own. */
.lang-toggle {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: inherit;
  line-height: inherit;
}

/* Split-flap character slot (i18n.js) — a fixed slot on a real split-flap
   board: i18n.js sets an inline `width` per span (canvas-measured advance
   width of that character's FINAL glyph, plus letter-spacing), so a
   cycling glyph can never shift its neighbours. inline-block (not the
   span default of inline) so that inline width actually takes effect;
   text-align: center keeps the glyph centered as it cycles through
   different widths within its own fixed box. The parent element itself
   is pinned to its final width for the same reason — see i18n.js. */
.flap-char {
  display: inline-block;
  text-align: center;
}

/* ------------------------------------------------------------
   Stage (right panel): rule | frame | rule | word rail
   ------------------------------------------------------------ */

.stage {
  position: absolute;
  inset: 0 0 0 var(--stage-left);
  display: flex;
  align-items: center;
}

.rule {
  position: relative;
  width: var(--rule-col);
  flex-shrink: 0;
  height: 100%;
}

.rule::before {
  content: "";
  position: absolute;
  left: calc(50% - 0.5px);
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--color-ink);
}

.frame-wrap {
  flex: 1;
  min-width: 0;
  height: 100%;
  padding: var(--space-frame-y) 0;
}

/* Empty by default — the frame shows nothing until a project link is
   hovered/focused (main.js paints a color or photo, and clears back to
   this transparent rest state on leave). --color-frame is kept as a token
   for case-study placeholders, but no longer fills the homepage frame. */
.frame {
  width: 100%;
  height: 100%;
  background: transparent;
  transition: background-color 200ms ease;
}

/* Vertical word rail — a seamless constant-speed marquee.
   The rotated track holds TWO copies of the word sequence and slides
   translateX(0 → -50%): each sequence carries its own trailing gap
   (padding-right), so -50% of the track is exactly one period — no jump.
   main.js measures the sequence after fonts load (and on resize) and sets
   --marquee-dur for a constant 2.5rem/s regardless of content length. */
.rail {
  position: relative;
  width: var(--rail-col);
  flex-shrink: 0;
  height: 100%;
  overflow: hidden;
}

/* Optical centering: the rule line sits 1.25rem (half --rule-col) left of
   the rail edge, so a rail-centered text is 2.65625rem from the line but
   only 1.40625rem from the page edge. Shifting the wrapper 0.625rem (10px)
   left puts the text center 2.03125rem from BOTH — line↔text = text↔edge. */
.rail-marquee {
  position: absolute;
  top: 50%;
  left: calc(50% - 0.625rem);
  transform: translate(-50%, -50%) rotate(-90deg);
  width: max-content;
}

.rail-track {
  display: flex;
  align-items: center;
  color: var(--color-accent); /* whole marquee — words AND dots — is accent */
  font-family: var(--font-mono);
  font-size: var(--text-mono);
  letter-spacing: var(--tracking-mono);
  text-transform: uppercase;
  white-space: nowrap;
  animation: rail-scroll var(--marquee-dur, 240s) linear infinite;
}

@keyframes rail-scroll {
  to {
    transform: translateX(-50%);
  }
}

.rail-seq {
  display: flex;
  align-items: center;
  gap: var(--rail-gap);
  padding-right: var(--rail-gap); /* completes the loop junction */
}

.rail-word {
  display: inline-block; /* transforms need a box */
  transition: transform 120ms ease-out;
}

.rail-word:hover {
  transform: scale(1.06);
}

.rail-dot {
  flex: none;
  width: var(--rail-dot);
  height: var(--rail-dot);
  border-radius: 50%;
  background: currentColor; /* follows the track's accent color */
}

@media (prefers-reduced-motion: reduce) {
  .rail-track {
    animation: none;
  }
}

/* ------------------------------------------------------------
   Scaling zones — from the two Figma frames (1440 = 26:133,
   1920 = 54:382). Both frames are canon; zone B linearly
   interpolates between them, zone C scales the 1920 design
   proportionally. Every value is continuous at 1440 and 1920.

   Measured @1920: type/spacing ×1.0547 (root 16 → 16.875px),
   left content edge fixed 61px, stage left 725 → 1003px,
   footer right edge 637 → 930px.
   ------------------------------------------------------------ */

/* Zone B: 1440–1920, linear interpolation */
@media (min-width: 1440px) {
  html {
    font-size: calc(16px + (100vw - 1440px) * 0.0018229); /* 16 → 16.875 */
  }

  :root {
    --stage-left: calc(725px + (100vw - 1440px) * 0.5791667); /* 725 → 1003 */
  }

  /* Footer widens to a measured span: left pinned at 61px, right edge
     sliding 637 → 930px (offset 803 → 990px), items spread across it. */
  .site-footer {
    justify-content: space-between;
    gap: 0;
    right: calc(100vw - 637px - (100vw - 1440px) * 0.6104167);
  }
}

/* Zone C: ≥1920, the 1920 design scales proportionally */
@media (min-width: 1920px) {
  html {
    font-size: 0.878906vw; /* 16.875 @1920 */
  }

  :root {
    --stage-left: 52.2396%;       /* 1003 / 1920 */
    --space-page-x: 3.17708vw;    /* 61 @1920 */
  }

  .site-footer {
    right: 51.5625vw; /* edge at 930/1920 = 48.4375vw */
  }
}

/* ============================================================
   Mobile & tablet (≤1199px) — Figma "iPhone 17 - 1", node 129:39,
   402×874, extended up to 1199px (see the nested tablet-band query
   below the main block) so the desktop layout's min-width: 1200px
   + percentage-based stage no longer forces horizontal scroll in
   the 768–1199px band. Own breakpoint, own root basis: 16px at the
   402 reference width (clamp ceilings at 20px — pinned there for
   any viewport ≥ ~503px, so 768–1199px renders as a uniform 1.25×
   LARGER version of the phone layout, not a stretched or broken
   one — see the "Scaling zones" comment style used elsewhere in
   this file). Every length below is authored in rem against that
   16px-at-402 basis (px @402 in the comment).
   ------------------------------------------------------------
   .stage is repurposed from the desktop's right-half panel into a
   static block at the top of the page: .rule×2 become full-bleed
   horizontal hairlines, #frame becomes the (self-cycling) image
   box, .rail is hidden (word marquee is desktop-only — deliberate,
   see the nested tablet-band comment below). .stage itself stays
   position:static — its absolutely-positioned children (.rule,
   .frame-wrap) resolve their containing block up to .page
   (position:relative, height:100%), which is exactly the "top of
   viewport = 0" reference the Figma y-values assume, since .stage
   is .page's only in-flow child (projects/footer are absolute, so
   they don't affect where .stage sits in flow).
   ============================================================ */
@media (max-width: 1199px) {
  html {
    font-size: clamp(12px, 3.9801vw, 20px); /* 16px at the 402 frame */
  }

  :root {
    --text-display: 2.11875rem;      /* 33.9px — group headings + project names */
    --space-list-bottom: 6.0906rem;  /* 97.45px — project list offset from bottom */
    --space-footer-bottom: 2.78938rem; /* 44.63px */
    --space-indent: 3.48106rem;      /* 55.697px — project name indent under heading */
    --space-page-x: 2.385rem;        /* 38.16px — footer left edge only (projects is centered) */
    --text-mono: 0.746125rem;        /* 11.938px — footer labels (.rail is hidden, unaffected) */
    /* Desktop's 54px/120px bleed+tail pushed the bar (and the now-hidden
       .tag) past the 402px edge on the longest lines. Shrunk to fit —
       main.js reads both off :root, so per-link duration scaling picks
       these up automatically (see measureMotion()). */
    --bar-bleed: 1.25rem;             /* 20px @402 (desktop: 54px) */
    --bar-tail: 1.5rem;               /* 24px @402 (desktop: 120px) */
  }

  body {
    min-width: 0;    /* desktop's 1200px floor would force horizontal scroll */
    overflow: visible; /* desktop's overflow:hidden 100vh-lock doesn't apply here */
  }

  /* The "case study" serif label is a desktop hover affordance; on mobile
     the bar is an automatic cue, not a pointer response, so the label has
     no job — and no room at 402px (it was pushing scrollWidth past the
     viewport even after the bleed/tail shrink above). */
  .tag {
    display: none;
  }

  /* ---- stage: static block at top, repurposed as rule/frame/rule ---- */
  .stage {
    position: static;
    inset: auto;
    display: block;
  }

  .rule {
    position: absolute;
    left: 0;
    right: 0;
    width: auto;
    height: 1px;
    background: var(--color-ink);
  }

  .rule::before {
    content: none; /* desktop's vertical-line pseudo; the box itself is the line here */
  }

  .stage .rule:first-child {
    top: 1.31875rem; /* 21.1px — rule 1 centre */
    transform: translateY(-50%);
  }

  .frame-wrap + .rule {
    top: 25.7231rem; /* 411.57px — rule 2 centre */
    transform: translateY(-50%);
  }

  .frame-wrap {
    position: absolute;
    top: 2.5625rem;    /* 41px */
    left: 1.25rem;      /* 20px inset each side (362 = 402 - 40) */
    right: 1.25rem;
    height: 22.125rem; /* 354px */
    padding: 0;
  }

  /* Cover rides a pseudo-element over a permanent pink fill, so
     fading it out reveals the pink rather than fading the box. */
  #frame {
    position: relative;
    background: var(--color-frame);
  }

  .frame::after {
    content: "";
    position: absolute;
    inset: 0;
    background-image: var(--m-cover, none);
    background-size: cover;
    background-position: var(--m-cover-pos, 50% 50%);
    opacity: var(--m-cover-op, 0);
    transition: opacity 260ms var(--ease-snap);
  }

  .rail {
    display: none; /* word marquee is desktop-only */
  }

  /* ---- project list: centered, not left-margined ---- */
  .projects {
    left: 50%;
    right: auto;
    transform: translateX(-50%);
  }

  .group {
    margin-bottom: -0.08825rem; /* -1.412px */
  }

  .group-title {
    margin-bottom: -0.44138rem; /* -7.062px (desktop: -10px) */
  }

  .group-list li {
    margin-bottom: -0.107125rem; /* -1.714px (desktop: -2.4px) */
  }

  /* ---- footer ---- */
  /* Four items (Evan Tempo / Telegram / E-mail / the EN-RU toggle) no
     longer fit one line at 402px — estimated ~353px for the first three
     against ~364px of available row width, so the toggle (and possibly
     "E-mail" too) wraps to its own line. column-gap keeps the Figma
     33.058px word-to-word spacing; row-gap is deliberately much smaller
     (not Figma-specified — this wrap scenario didn't exist before the
     toggle) so a wrapped second line doesn't push up into the project
     list above. */
  .site-footer {
    right: auto;
    justify-content: flex-start;
    column-gap: 2.06613rem; /* 33.058px */
    row-gap: 0.5rem;        /* 8px — small on purpose, see comment above */
    flex-wrap: wrap;
  }

  .footer-arrow {
    /* scaled proportionally with --text-mono: 10px * (11.938/17) ≈ 7.022px */
    width: 0.4389rem;
    height: 0.4389rem;
  }
}

/* ------------------------------------------------------------
   Tablet band (768–1199px) — graceful degradation, no Figma frame.

   The phone treatment is height-locked: .stage/#frame sits at a fixed
   height pinned to the top, while .projects and .site-footer are
   absolutely positioned against the BOTTOM of a 100%-viewport-height
   .page. On a tall, narrow phone (874×402) that leaves plenty of room
   between them. A landscape tablet is wide and SHORT, so those two
   zones meet in the middle and overlap — measured up to 326px of
   overlap at 1199×700, with case-study-page names rendering behind
   the pink placeholder box at some sizes. Scaling the frame down
   doesn't fix this: the geometry (two viewport-relative zones with no
   knowledge of each other's height) is wrong for this aspect ratio,
   not just oversized.

   Fix: abandon absolute/viewport-relative positioning entirely for
   this band and put the whole page into normal document flow — block
   layout can never let two siblings overlap, regardless of how short
   the viewport is; if the total content is taller than the viewport,
   the page simply scrolls, which is correct, ordinary browser
   behaviour, not a bug to route around. .page becomes a column flex
   container (auto height, vertical padding) so its children stack in
   sequence; .stage/.rule/.frame-wrap and .projects/.site-footer all
   drop position:absolute for position:static, and pick up ordinary
   top/bottom margins for spacing instead of the phone's viewport-
   relative offsets. The two hairlines stay `.stage`'s literal
   in-flow siblings either side of .frame-wrap (unchanged DOM order —
   no index.html edit needed), so they can't detach from it.

   DOM order is .projects, .site-footer, .stage, but the required
   visual order is rule/frame/rule, THEN the list, THEN the footer —
   handled with flex `order` on .page's three children rather than
   reordering the markup, since .stage is aria-hidden/non-interactive
   and .projects/.site-footer hold the real links; keeping them first
   in DOM/tab order while only reordering them visually is the
   standard, more accessible way to do this.

   Judgment calls carried over from the previous pass at this band,
   since there's still nothing to reproduce here: the frame keeps its
   ~760px comfortable composition width (middle of the 720–820px range
   asked for), capped rather than edge-relative, and its height is
   still just the phone treatment's own auto-scaled 22.125rem
   (≈442.5px at this band's pinned 20px root) — untouched, since a
   760-wide box at that height is already a ~1.7:1 landscape ratio.
   .projects keeps the phone treatment's shrink-to-fit-then-centred
   look (`width: fit-content` + auto side margins, the flow-layout
   equivalent of the phone's `left:50%; transform:translateX(-50%)`
   trick) rather than switching to a fixed max-width column with
   left-aligned text — chosen for visual continuity with the
   already-signed-off phone layout, not because the alternative was
   wrong. .site-footer gets the same treatment.

   The word rail stays desktop-only through this whole band (no
   override here) — it's a vertical rail authored against a right-hand
   "stage" that doesn't exist in this static-block phone/tablet layout,
   so there's no sensible place to put it back.
   ------------------------------------------------------------ */
@media (min-width: 768px) and (max-width: 1199px) {
  /* Neutralise the desktop canvas's fixed-viewport-height lock so the
     page can grow past one screen and scroll normally — the whole
     point of the flow-based fix below is that it no longer needs to
     fit inside a fixed box. */
  html,
  body {
    height: auto;
  }

  .page {
    display: flex;
    flex-direction: column;
    height: auto;
    padding: 3rem 0; /* vertical-only: horizontal stays 0 so .rule can still span the true full viewport width edge to edge */
  }

  .stage {
    order: 1;
    /* position:static already set above; a flex item with no explicit
       width stretches to fill .page's full cross-axis width by
       default, which is what .rule needs to reach both true edges. */
  }

  .rule {
    position: static;
    left: auto;
    right: auto;
    width: 100%;
    margin: 0 0 1.5rem; /* gap below each rule; the gap ABOVE rule 1 comes from .page's own top padding, so this isn't doubled */
  }

  .stage .rule:first-child,
  .frame-wrap + .rule {
    /* Clear the phone treatment's absolute-positioning offsets — same
       selectors (so equal-or-greater specificity) as the block above
       that set them, since a lower-specificity plain `.rule` override
       wouldn't win the cascade against `.stage .rule:first-child`. */
    top: auto;
    transform: none;
  }

  .frame-wrap {
    position: static;
    top: auto;
    left: auto;
    right: auto;
    transform: none;
    width: min(760px, calc(100% - 2.5rem)); /* 2.5rem = 50px @ this band's pinned 20px root — matches the 1.25rem-per-side inset the phone layout uses */
    margin: 0 auto 1.5rem; /* centred; gap below matches the rules' own */
  }

  .projects {
    order: 2;
    position: static;
    left: auto;
    bottom: auto;
    transform: none;
    width: fit-content; /* shrink-to-fit box, centred — see comment above */
    margin: 0 auto 2rem;
  }

  .site-footer {
    order: 3;
    position: static;
    left: auto;
    right: auto;
    bottom: auto;
    transform: none;
    width: fit-content;
    margin: 0 auto;
    justify-content: center; /* if it still wraps, a shorter second line centres under the first instead of hugging the box's left edge */
  }
}
