:root {
  --black: #1E1E1E;
  --bg: #FFFFFF;
  --grey: #D9D9D9;
  --header-h: 64px;
}

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

*::-webkit-scrollbar {
  display: none;
  width: 0;
  height: 0;
}

html, body {
  height: 100%;
  background: var(--bg);
  overflow: hidden;
}

body {
  /* Inter has no Hebrew glyphs, so Hebrew characters automatically fall
     through to Heebo — no manual language-switching needed. */
  font-family: 'Inter', 'Heebo', sans-serif;
  font-weight: 400;
  color: var(--black);
  -webkit-font-smoothing: antialiased;
  text-wrap: pretty; /* inherited site-wide — better line-breaking (no orphans) for all text, mobile and desktop alike */
  /* custom 50x50 cursor, hotspot at its center (the crosshair mark); has no
     effect on touch devices, so this is safe to apply globally. It stays the
     same everywhere — buttons and zoomable images don't swap in their own
     pointer/zoom cursor, by design (see the rules further down). */
  cursor: url('icons/cursor.svg') 25 25, auto;
}

button {
  font-family: inherit;
  font-weight: 400;
  background: none;
  border: none;
  color: inherit;
  cursor: inherit;
  padding: 0;
}

/* Safari shows its own built-in pointing-hand cursor over <a href> elements
   regardless of an inherited cursor value — only an explicit cursor set
   directly on the link itself overrides that. Every link on the site
   (feed cards, contact links) needs this so the custom cursor never gets
   swapped out just because something is clickable. */
a {
  cursor: inherit;
}

/* ---------- ACCESSIBILITY ---------- */

/* keyboard navigation: a clear ring on the focused control, but only for
   keyboard focus (:focus-visible) — mouse clicks don't leave one behind */
:focus-visible {
  outline: 2px solid var(--black);
  outline-offset: 3px;
}

/* feed cards and gallery tiles sit flush against a clipping ancestor
   (.feed-viewport / the project sheet are both overflow:hidden), so an
   outward ring gets cut off exactly where it matters. Drawn inside instead,
   with a white companion ring so it reads against dark photography too. */
.card:focus-visible,
.g-img:focus-visible,
.m-tile:focus-visible,
.index-tile-media:focus-visible {
  outline: 2px solid var(--black);
  outline-offset: -4px;
  box-shadow: inset 0 0 0 2px var(--bg);
}

/* still-image gallery tiles open the lightbox, so they advertise themselves
   as activatable — see the role="button"/tabindex they get in script.js once
   their media turns out to be an image rather than a clip */
.g-img[role="button"],
.m-tile[role="button"] {
  cursor: inherit;
}

/* screen-reader-only text (the home page's h1 lives in one of these —
   visually the logo plays that role, semantically this does) */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ASSIST BAR — skip link, motion pause, accessibility statement. Off-screen
   until one of them takes keyboard focus, then it drops into the top-left
   above everything on the site, the intro screen (z 500) included, so it's
   actually readable at the moment it matters. */
.assist-bar {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 600;
  display: flex;
  gap: 8px;
}

.assist-link {
  position: absolute;
  left: -9999px;
  top: 0;
  white-space: nowrap;
  font-size: 15px;
  text-decoration: underline;
  color: var(--black);
  background: var(--bg);
  padding: 12px 18px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.18);
}

/* laid out in a row only once focused — until then each one is parked
   off-screen individually so they never reserve layout space */
.assist-bar:focus-within .assist-link:focus {
  position: static;
}

/* users who've asked their OS for less motion get it: every CSS transition
   and animation collapses to (effectively) instant. The JS side skips the
   intro screen entirely under the same media query. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* Phone frame — the whole app lives inside this */
.phone {
  position: relative;
  max-width: 430px;
  min-height: 100vh;
  margin: 0 auto;
  background: var(--bg);
  overflow-x: hidden;
}

/* ---------- FIXED HEADER (persists across all pages) ---------- */
.site-header {
  position: fixed;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  max-width: 430px;
  height: var(--header-h);
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 24px;
  z-index: 120; /* stays above every card's focus z-index (max 100) */
}

.logo {
  font-size: 17px;
  letter-spacing: -0.01em;
  white-space: nowrap;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 20px;
}

.contact-header-btn,
.index-btn {
  font-size: 17px;
  letter-spacing: -0.01em;
  white-space: nowrap;
}

/* ---------- INDEX VIEW ----------
   A flat, at-a-glance grid of all ten projects over its own paper-grain
   backdrop — the fast alternative to browsing the endless feed. Toggled by
   the header's Index View/Back button; sits above the feed (z 110) but
   below the header (120), so the header keeps working while it's open. */
.index-view {
  position: fixed;
  inset: 0;
  z-index: 110;
  background:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)' opacity='0.06'/></svg>")
    var(--bg);
  background-size: 180px 180px;
  overflow-y: auto;
  padding: calc(var(--header-h) + 24px) 24px 48px;
}

.index-view-grid {
  max-width: 960px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 24px 16px;
}

@media (min-width: 900px) {
  .index-view-grid {
    grid-template-columns: repeat(4, 1fr);
    gap: 40px 24px;
  }
}

.index-tile {
  display: block;
  width: 100%;
  text-align: left;
}

.index-tile-media {
  aspect-ratio: 3 / 4;
  background: var(--grey);
  overflow: hidden;
}

.index-tile-media img,
.index-tile-media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.index-tile-label {
  margin-top: 8px;
  font-size: 13px;
  color: var(--black);
  opacity: 0.75;
}

.index-tile:hover .index-tile-label {
  opacity: 1;
}

/* ---------- HOME FEED ---------- */
/* fixed clipping viewport — the feed inside is moved by hand (transform) for
   momentum scrolling, and loops forever (see script.js) */
.feed-viewport {
  position: fixed;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  max-width: 430px;
  height: 100vh;
  overflow: hidden;
  touch-action: none;
  z-index: 1;
}

.feed {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  padding-top: var(--header-h);
  will-change: transform;
}

.card {
  display: block;
  width: 100%;
  background: var(--grey);
  position: relative;
  overflow: hidden;
  margin-top: -24px; /* cards overlap by default; z-index (set in JS) stacks the
                         in-focus card above its neighbors as you scroll past it */
  perspective: 600px; /* for the cursor-reactive tilt on the image/video below (desktop only, via script.js) */
}

.card img,
.card video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.15s ease-out; /* smooths the cursor-tilt in/out — easy to remove along with the JS that drives it */
}

/* tall format — 3:4, inset */
.card--tall {
  width: calc(100% - 48px);
  aspect-ratio: 3 / 4;
  margin-left: auto;
  margin-right: auto;
}

/* wide format — 4:3, edge-to-edge */
.card--wide {
  width: 100%;
  aspect-ratio: 4 / 3;
}

.card-loading {
  text-align: center;
  padding: 32px 0 48px;
  font-size: 13px;
  color: var(--black);
  opacity: 0.62; /* was 0.4 (~2.5:1) — same 1.4.3 floor as .project-meta */
}

/* ---------- PROJECT DETAIL PAGE ---------- */
.project-page {
  position: fixed;
  top: 0;
  left: 50%;
  transform: translate(-50%, 100%);
  width: 100%;
  max-width: 430px;
  height: 100%;
  background: var(--bg);
  z-index: 50;
  overflow-y: auto;
  scrollbar-width: none;
  /* while closed this sheet is only translated off-screen, which leaves its
     back button, gallery and links fully focusable — a keyboard user would
     tab straight into a page nobody can see. visibility:hidden takes it out
     of the tab order; the 0.32s delay holds it visible until the slide-down
     has actually finished. */
  visibility: hidden;
  transition: transform 0.32s ease, visibility 0s linear 0.32s;
}

/* opened from the index grid, this reads as a page in its own right rather
   than a sheet lying over the feed: it covers the whole viewport (no feed
   showing past its edges), sits above the index grid, and gets the same
   paper-grain backdrop the index uses, so index -> project -> back feels
   like one continuous surface. */
.project-page.from-index {
  max-width: none;
  z-index: 115;
  background:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)' opacity='0.06'/></svg>")
    var(--bg);
  background-size: 180px 180px;
}

/* the content column stays phone-width and centered inside that full-bleed
   page, so the layout itself is unchanged — only the surface around it is */
.project-page.from-index .project-content {
  max-width: 430px;
  margin: 0 auto;
}

.project-page::-webkit-scrollbar {
  display: none;
}

.project-page.open {
  transform: translate(-50%, 0);
  visibility: visible;
  transition: transform 0.32s ease, visibility 0s;
}

.project-content {
  padding-top: var(--header-h);
  padding-bottom: 64px;
}

.project-back {
  position: sticky;
  top: var(--header-h);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: 10px 12px;
  opacity: 0.7;
  background: var(--bg);
  border-radius: 50%;
  z-index: 60;
}

.project-back img {
  width: 20px;
  height: 20px;
  display: block;
}

.project-hero {
  width: 100%;
  background: var(--grey);
  overflow: hidden;
}

/* matches the project's assigned feed format, so images show at their native crop */
.ratio-tall {
  aspect-ratio: 3 / 4;
}

.ratio-wide {
  aspect-ratio: 4 / 3;
}

/* native vertical video (9:16) — shown uncropped rather than squeezed into
   the site's 3:4 / 4:3 formats, which cut roughly 44% off a portrait clip's
   height. Used by project 11, whose clips were all shot vertically. */
.ratio-vertical {
  aspect-ratio: 9 / 16;
}

.project-hero img,
.project-hero video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.project-body {
  padding: 24px;
}

.project-title {
  font-size: 22px;
  font-weight: 400;
  margin-bottom: 8px;
}

.project-meta {
  font-size: 14px;
  color: var(--black);
  /* was 0.45, which resolved to ~#9A9A9A on white — 2.8:1, under the 4.5:1
     WCAG 1.4.3 minimum for text this size. 0.62 lands on ~#737373 (4.7:1),
     the lightest this can go and still pass. */
  opacity: 0.62;
  margin-bottom: 24px;
}

.project-text {
  font-size: 15px;
  line-height: 1.45;
  word-spacing: -1px;
  margin-bottom: 20px;
  opacity: 0.85;
}

/* collaborator credits — a real list, set tighter than body copy so the
   names read as a block rather than as separate paragraphs. role="list" is
   deliberate: Safari drops list semantics from a ul once list-style is none,
   so without it VoiceOver stops announcing this as a list of three. */
.project-credits {
  list-style: none;
  font-size: 15px;
  line-height: 1.45;
  opacity: 0.85;
  margin-bottom: 20px;
}

.project-credits li {
  margin-bottom: 2px;
}

/* trailing footnote (e.g. an AI-generation disclosure) — smaller and quieter
   than the body so it reads as an aside rather than part of the write-up,
   but 0.7 keeps it at ~6.4:1 on white, well clear of the 4.5:1 floor */
.project-note {
  font-size: 13px;
  line-height: 1.45;
  opacity: 0.7;
  margin-bottom: 20px;
}

/* desktop-only counterpart to the title/meta/text above — see the
   @media (min-width: 900px) block for its actual layout and the CSS that
   hides the inline copy there; on mobile this just never displays. */
.project-side-text {
  display: none;
}

.project-gallery {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-top: 24px;
}

/* project 5 — all gallery clips shown as a 2-per-row grid instead of stacked */
.project-gallery.project-gallery--grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 4px;
}

.project-gallery .g-img {
  width: 100%;
  background: var(--grey);
  overflow: hidden;
}

.project-gallery .g-img img,
.project-gallery .g-img video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* project 9 — no hero/text, just every image in a 2-column Pinterest-style
   wall, each tile kept at its own native aspect ratio (no forced crop) */
.project-masonry {
  column-count: 2;
  column-gap: 4px;
}

.project-masonry .m-tile {
  break-inside: avoid;
  margin-bottom: 4px;
  background: var(--grey);
}

.project-masonry .m-tile img,
.project-masonry .m-tile video {
  width: 100%;
  height: auto;
  display: block;
}

/* ---------- CONTACT POP-UP ---------- */
.overlay {
  position: fixed;
  inset: 0;
  background: rgba(30, 30, 30, 0.4);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
  z-index: 150;
}

.overlay.open {
  opacity: 1;
  pointer-events: auto;
}

.contact-modal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0.9);
  /* same side gutters as the feed's tall thumbnails (24px each side of the
     430px-max phone column), so the folder sits on the same grid as the
     project cards — the desktop media query overrides this entirely */
  width: calc(100% - 48px);
  max-width: 382px;
  z-index: 200;
  opacity: 0;
  pointer-events: none;
  /* pointer-events alone only stops the mouse — the two red-card links and
     the résumé button stay in the tab order while the folder is invisible.
     visibility:hidden is what actually removes them (WCAG 2.4.3). */
  visibility: hidden;
  transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1), opacity 0.25s ease, visibility 0s linear 0.3s;
  text-align: center;
}

.contact-modal.open {
  transform: translate(-50%, -50%) scale(1);
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
  transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1), opacity 0.25s ease, visibility 0s;
}

/* keyboard/AT close affordance — the folder artwork itself has no visible
   close control, only the overlay click and Escape, neither of which is
   discoverable. Sits just off the folder's top-right corner. */
.contact-close {
  position: absolute;
  top: -42px;
  right: 0;
  width: 32px;
  height: 32px;
  /* solid chip rather than a bare glyph: the dimmed overlay behind it is
     semi-transparent, so whatever project imagery happens to sit underneath
     decides the contrast. Its own background makes 1.4.11 unconditional. */
  background: var(--black);
  border-radius: 50%;
  /* above every folder layer — the résumé card scales to 1.2 when it opens
     and grows up past the folder's top edge, right over this corner */
  z-index: 10;
}

.contact-close::before,
.contact-close::after {
  content: '';
  position: absolute;
  left: 6px;
  top: 15px;
  width: 20px;
  height: 1.5px;
  background: var(--bg);
}

.contact-close::before { transform: rotate(45deg); }
.contact-close::after { transform: rotate(-45deg); }

/* required by Israeli service-accessibility regulation 35: the statement has
   to be reachable from the site. Contact is in the header on every screen,
   so this is the one surface that's always one click away. */
.contact-a11y-note {
  margin-top: 14px;
  font-size: 13px;
}

/* same reasoning as .contact-close — it carries its own solid background so
   the semi-transparent overlay (and whatever is showing through it) can't
   drop it below 4.5:1 */
.contact-a11y-note a {
  display: inline-block;
  background: var(--bg);
  color: var(--black);
  padding: 6px 12px;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ---------- CONTACT FOLDER ----------
   A kraft folder: the red "reach out" card and a taped portrait sit fixed on
   the left, the résumé card is tucked into a pocket flap on the right and
   peeks further out on hover. All five PNGs are exported at the same 2500x2500
   canvas size (nothing trimmed), so every layer's position below is computed
   directly from its bbox within that shared canvas — not eyeballed. */
.contact-folder {
  position: relative;
  width: 100%; /* fills the modal, which already matches the feed's thumbnail gutters */
  aspect-ratio: 2250 / 1575; /* matches contact-folder-back.webp's own cropped size */
  margin: 0 auto;
  /* short viewports and 200% zoom (WCAG 1.4.4 / 1.4.10): the folder's fixed
     aspect ratio would otherwise make it taller than the screen, and it sits
     in a fixed, unscrollable modal — so there'd be no way to reach the rest
     of it. Capping the width caps the height. The 130px covers the modal's
     own furniture that the aspect ratio knows nothing about: the close
     button sitting above the folder and the statement link below it. No
     effect at any normal viewport height. */
  max-width: calc((100vh - 130px) * 1.428);
}

.contact-folder-layer {
  position: absolute;
  display: block;
}

.contact-folder-back {
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
}

/* each paper element casts its own soft shadow onto the folder beneath it.
   This lives on the individual layers rather than once on .contact-folder —
   a shadow filter on the parent gets its rasterized region clipped to the
   parent's own (untransformed) box, so the résumé card's shadow broke/showed
   a hard white seam once its "open" transform scaled it past that box. */
/* Currently unused — the taped portrait was removed from index.html pending a
   replacement. Kept so a new image can be dropped straight back into the same
   position without re-deriving these numbers from the artwork's bbox. */
.contact-portrait {
  left: 3.2%;
  top: 39.56%;
  width: 26.09%;
  height: auto;
  z-index: 2;
  filter: drop-shadow(0px 4px 10px rgba(0, 0, 0, 0.15));
}

.contact-red-card {
  position: absolute;
  left: 2%;
  top: 0%; /* sits flush with the folder's own top edge — no more paperclip overhang in this artwork revision */
  width: 44.44%;
  z-index: 2;
  filter: drop-shadow(0px 4px 10px rgba(0, 0, 0, 0.15));
}

.contact-red-card-img {
  position: static;
  width: 100%;
  height: auto;
  display: block;
}

/* invisible hotspots over the two baked-in text lines on the red card
   artwork (the text itself is pixels, not live HTML, so these are what make
   it clickable) — positioned as a % of the card's own box, measured directly
   off the artwork's dark-pixel rows. */
.contact-red-card-link {
  position: absolute;
  left: 9%;
  /* these are empty hit-target anchors (no text/content), and Safari falls
     back to its system pointing-hand cursor on those even with `cursor:
     inherit` set (the fix that's enough for every other link on the site) —
     only a concrete cursor value here keeps the custom cursor on desktop.
     No effect on touch, so there's no need to scope this to desktop only. */
  cursor: url('icons/cursor.svg') 25 25, auto;
}

.contact-red-card-link--handle {
  top: 60.5%;
  width: 42%;
  height: 13.5%;
}

.contact-red-card-link--email {
  top: 73.5%;
  width: 82%;
  height: 13.5%;
}

.contact-resume-card {
  left: 51.51%;
  top: 2.48%;
  width: 46.67%;
  height: auto;
  z-index: 2;
  cursor: inherit; /* keeps the custom cursor instead of the system pointer/hand — same fix as buttons and links */
  transform-origin: 50% 100%; /* peeks up from its base, like it's still tucked in the pocket */
  transform: translate(0%, 0%) rotate(0deg);
  transition: transform 0.45s cubic-bezier(0.32, 0.72, 0, 1);
  filter: drop-shadow(0px 4px 10px rgba(0, 0, 0, 0.15));
}

/* the card is a <button> now (it has to be keyboard-operable), so the
   artwork inside it needs to fill it exactly the way the bare <img> used to
   — same box, so every % position around it is unchanged */
.contact-resume-card img {
  display: block;
  width: 100%;
  height: auto;
}

/* hover previews a slight tilt — scoped to :hover on the card itself, not
   the whole .contact-folder, otherwise hovering the red card/portrait side
   (still within the folder's own layout box) would trigger it too, nowhere
   near the résumé card. (Not once it's already open — clicking takes over
   from there.) Click grows it and brings it to the front of the pocket
   flap, big enough to cover the folder's bottom edge — click again puts it
   back — the same three-state loop as the site's earlier CV folder
   mini-game. */
.contact-folder:not(.open) .contact-resume-card:hover {
  transform: translate(-4%, -9%) rotate(-4deg);
}

.contact-folder.open .contact-resume-card {
  /* growing from the bottom (like the peek above) would push the top way
     off-screen — center-anchored instead, so the extra size is shared
     between growing up and down and the enlarged card actually stays
     within the frame. The card's own resting position already sits almost
     exactly on the folder's vertical center, so translateY is left at 0 —
     that's what keeps it centered on the Y axis; only X gets a small
     forward nudge. */
  transform-origin: 50% 50%;
  transform: translate(2%, 0%) scale(1.2) rotate(0deg);
  /* fully out from behind the pocket now, so it belongs above it */
  z-index: 4;
}

.contact-folder-dash {
  left: 49.78%;
  top: 64.89%;
  width: 50.22%;
  height: auto;
  z-index: 3; /* the pocket flap sits in front of the résumé card */
  pointer-events: none;
}

/* ---------- IMAGE LIGHTBOX ---------- */
/* gallery images (not the hero, not videos) are tappable to view larger */

.lightbox {
  position: fixed;
  inset: 0;
  background: var(--bg);
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  opacity: 0;
  pointer-events: none;
  visibility: hidden; /* keeps the close button out of the tab order while shut */
  transition: opacity 0.25s ease, visibility 0s linear 0.25s;
}

.lightbox.open {
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
  transition: opacity 0.25s ease, visibility 0s;
}

.lightbox-img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

/* the lightbox closes on any click, but a click target with no visible
   affordance isn't discoverable and isn't reachable by keyboard at all */
.lightbox-close {
  position: absolute;
  top: 16px;
  right: 16px;
  width: 40px;
  height: 40px;
  background: var(--black);
  border-radius: 50%;
  z-index: 1;
}

.lightbox-close::before,
.lightbox-close::after {
  content: '';
  position: absolute;
  left: 10px;
  top: 19px;
  width: 20px;
  height: 1.5px;
  background: var(--bg);
}

.lightbox-close::before { transform: rotate(45deg); }
.lightbox-close::after { transform: rotate(-45deg); }

/* ---------- GRAIN TEXTURE (decorative, whole site) ---------- */
/* a tiny CSS-only noise tile (SVG feTurbulence, no image file needed),
   blended subtly over everything for a tactile, paper-like feel. Easy to
   remove — just this rule and the .grain-overlay div in index.html. */
.grain-overlay {
  position: fixed;
  inset: 0;
  z-index: 400;
  pointer-events: none;
  opacity: 0; /* stays out until the intro reveal (see body.intro-done below), then settles at 0.05 */
  transition: opacity 0.6s ease;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: 180px 180px;
}

/* ---------- FULL-SCREEN INTRO ----------
   A full-viewport screen shown on first load: the name types in at the
   center over ~2.2s, then this screen fades out and only then does the
   rest of the site (below) reveal. Everything else starts at opacity 0 and
   is only ever set to its resting opacity once body gets .intro-done
   (added by script.js right when the intro finishes) — this also doubles
   as the fix for any first-paint flash before script.js runs. */
.phone {
  opacity: 0;
  /* opacity:0 alone leaves the header buttons and every feed card focusable
     underneath the intro screen — a keyboard user could tab into the site
     while it's still invisible. visibility:hidden is what removes them; it
     flips to visible the instant .intro-done lands, so the fade still runs. */
  visibility: hidden;
  transition: opacity 0.6s ease, visibility 0s linear 0.6s;
}
body.intro-done .phone {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.6s ease, visibility 0s;
}
body.intro-done .grain-overlay {
  opacity: 0.05;
}

.intro-screen {
  position: fixed;
  inset: 0;
  z-index: 500;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  transition: opacity 0.6s ease;
}
.intro-screen.intro-fade-out {
  opacity: 0;
  pointer-events: none;
}
/* the animated brand artwork is a 16:9 canvas with the type at its center —
   contain-fit it to the viewport so the whole frame always shows, on any
   screen, without cropping the lettering */
.intro-screen-gif {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* ---------- CURSOR-FOLLOWING PROJECT TOOLTIP (desktop hover only) ---------- */
.hover-tooltip {
  position: fixed;
  top: 0;
  left: 0;
  background: var(--bg);
  padding: 18px 28px;
  font-size: 30px;
  white-space: nowrap;
  color: var(--black);
  pointer-events: none;
  z-index: 250;
  opacity: 0;
  transform: translate(-9999px, -9999px);
  transition: opacity 0.15s ease;
  box-shadow: 0px 5px 25px -15px rgba(0, 0, 0, 0.15);
}

.hover-tooltip.visible {
  opacity: 1;
}

/* only devices with a real pointer + hover (i.e. desktop) ever see this —
   a touch tap firing a stray hover/mousemove won't leave it stuck on screen */
@media not (hover: hover) {
  .hover-tooltip {
    display: none;
  }
}

/* ---------- DESKTOP ----------
   Everything above already centers itself (left:50% + translateX(-50%))
   and caps at 430px, so on a wider viewport it just renders as a centered
   mobile-width column with blank space on either side — the mobile
   experience is untouched. Desktop-only additions (side content, hover
   states, wider treatments) go here, scoped so they never affect mobile.
   The feed itself stays the same centered card column (it already spans
   the full viewport height top-to-bottom); only the header and CV sheet
   stretch full-bleed here, since they're simple bars, not the card feed. */
@media (min-width: 900px) {
  .site-header {
    left: 0;
    transform: none;
    max-width: none;
    padding: 0 50px;
  }

  /* the header becomes a transparent overlay riding above the feed instead
     of an opaque bar, so the cards can run all the way up to the true top
     edge of the screen (and, being an infinite loop, the bottom too) */
  .site-header {
    background: transparent;
  }

  .feed {
    padding-top: 0;
  }

  /* much bigger stage on desktop — mobile keeps the compact size, this is
     sized to roughly match the reference mockup's proportions on a normal
     desktop viewport. max-width must be lifted here: the mobile base rule
     caps the modal at the phone column's 382px, and without this the 900px
     folder inside overflows the box rightward instead of centering. */
  .contact-modal {
    width: min(940px, 84vw);
    max-width: none;
  }

  .contact-folder {
    width: min(900px, 70vw);
  }

  /* project text moves out of the flow below the hero and into the right
     gutter instead, typewriter-animated in (see script.js); the inline
     mobile copy is hidden here so it isn't shown twice. */
  .project-side-text {
    display: block;
    position: fixed;
    top: 50%;
    left: calc(50% + 215px);
    right: 0;
    transform: translateY(-50%);
    padding: 0 50px;
    /* above .project-page.from-index (115) as well as the normal sheet (50):
       when a project is opened from the index grid the page goes full-bleed,
       and at any lower z-index this panel would be buried underneath it */
    z-index: 116;
    opacity: 1;
    transition: opacity 0.2s ease;
  }

  /* only shown while a project is actually open — closing (back button or
     the logo) fades this out quickly instead of leaving it visible over
     the main feed */
  .project-side-text.fade-out {
    opacity: 0;
  }

  .project-body .project-title,
  .project-body .project-meta,
  .project-body .project-text,
  .project-body .project-credits,
  .project-body .project-note {
    display: none;
  }
}
