/* Inter (SIL Open Font License, rsms.me/inter), self-hosted: the variable
   font instanced to weights 300–700 and subset to Basic Latin + Latin-1
   (incl. Ó ó) + the punctuation used here (– — ’ “ ” · • … → ∞ etc.).
   Rebuild with fontTools if new characters are needed. */
@font-face {
  font-family: "Inter";
  src: url("fonts/inter-latin-var.woff2") format("woff2");
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}

/* ---------------------------------------------------------------------------
   Base: lock the page to the viewport, no scrolling, and center the collage.
--------------------------------------------------------------------------- */
html,
body {
  height: 100vh;
  margin: 0;
  overflow: hidden; /* no page scroll */
}

body {
  /* Film-texture paper: #FCFAF9 base. script.js adds a subtle #EBEAE9 grain
     as a tiled background-image on top of this color. Because the cutout layers
     are brightened so their paper clips to white and blended with darken, this
     background shows through every empty/paper area of the collage too. */
  background-color: #fcfaf9;
  /* Film grain: a static 180px tile of #EBEAE9 specks, drawn at 45px so each
     speck is well under a CSS pixel on high-DPI screens. */
  background-image: url("images/grain.webp");
  background-size: 45px 45px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ---------------------------------------------------------------------------
   Stage: a centered frame sized to the collage's own aspect ratio (4941:2757)
   and scaled down so it doesn't fill the whole screen. There is NO background
   photo here — the four transparent cutouts stack to reconstruct the collage,
   floating on the page's solid paper color. That avoids showing weee2.png's
   photographic paper edge as a visible rectangle. Because the box matches the
   image ratio exactly, the overlays and the JS hit-detection stay aligned.
   Tweak `height` to make the whole collage bigger/smaller.
--------------------------------------------------------------------------- */
.stage {
  position: relative;
  /* No z-index here: adding one isolates the layers' mix-blend-mode from the
     page background, which would make the collage area render as a solid white
     rectangle. The label is placed behind the collage via a negative z-index
     instead (see .beam-label). */
  /* Drive the WIDTH only and let aspect-ratio compute the height, so the box is
     ALWAYS 4941:2757. (Setting both height:78vh and max-width:92vw together
     lets max-width clamp the width while height stays 78vh, which breaks the
     aspect ratio on narrow windows and makes object-fit:cover crop the stone.)
     width = the smaller of the 78vh-tall size and 92vw — same visual size as
     before, but aspect-locked at every proportion. */
  aspect-ratio: 4941 / 2757;
  width: min(calc(78vh * 4941 / 2757), 92vw);
  /* Sine ease-in-out: soft at both ends, so the zoom in/out feels organic
     rather than mechanical. */
  transition: transform 0.7s cubic-bezier(0.37, 0, 0.63, 1),
    opacity 0.45s ease;
}

/* ---------------------------------------------------------------------------
   Background collage: weee3.png (transparent PNG) is the default view. It fills
   the stage box and its transparent areas let the page's film texture show
   through. It fades out on hover so a single cutout can take its place.
--------------------------------------------------------------------------- */
.bg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  pointer-events: none;
  transition: opacity 0.5s ease;
}

.stage.has-hover .bg {
  opacity: 0; /* on hover, clear the full collage so only one element shows */
}

/* ---------------------------------------------------------------------------
   Layers: the four cutouts are hover-only overlays, hidden by default. Each is
   an opaque JPEG (element on a near-white background); mix-blend-mode: darken +
   a brightness lift clips that paper to white so the page's film texture shows
   through, leaving just the element on paper when its layer is revealed.
--------------------------------------------------------------------------- */
.layer {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;

  /* Real transparent cutouts: the photos' paper is made transparent in the
     optimized files (tools/build-images.py), so no blend mode is needed —
     Safari's GPU path drops mix-blend-mode and showed a white box. */

  opacity: 0; /* hidden by default; only the hovered cutout is revealed */

  /* Hover is resolved in JS via alpha-channel hit detection (script.js), not
     by the browser's rectangular hit-testing. The images must NOT capture
     pointer events themselves, otherwise the top-stacked full-canvas PNG would
     swallow every event (even over its transparent areas). We listen on the
     stage instead and read the pixel under the cursor. */
  pointer-events: none;

  /* Smooth, no snapping. transform-origin center so scale grows evenly. */
  transform-origin: center;
  transition: opacity 0.3s ease, transform 0.3s ease;
}

/* ---------------------------------------------------------------------------
   Hover interaction — driven by classes that script.js toggles.

   .has-hover on the stage  -> the cursor is over an element; weee3.png fades
                               out (rule above) so the paper is left bare.
   .is-hovered on a layer   -> that specific cutout is revealed, so only the
                               pointed-at element remains on the paper.
--------------------------------------------------------------------------- */
.stage .layer.is-hovered {
  opacity: 1;
}

/* ---------------------------------------------------------------------------
   Beam overlay: a full-viewport canvas that renders the particle beam above
   the collage. It never captures pointer events, so hover detection on the
   images below is unaffected. Its overall opacity is animated by script.js
   for the smooth ~0.3s fade-in when a beam activates.
--------------------------------------------------------------------------- */
.beam-canvas {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  z-index: -3; /* behind the collage AND the big label (Work / Photography) */
  pointer-events: none;
  /* Always present; when idle the canvas is simply cleared (fully transparent).
     The beam's fade-in/out is driven in JS via systemAlpha, so we don't animate
     canvas opacity here. Since it sits below the camera layer (which uses
     mix-blend-mode: darken), the beam shows through the camera's light areas
     and is occluded by its dark body — emerging from behind the lens. */
}

/* ---------------------------------------------------------------------------
   Beam label: light Inter type near the right edge, positioned by script.js to
   sit roughly where the beam trajectory lands. Fades/slides in shortly after
   the beam starts, and fades out faster when hover ends.
--------------------------------------------------------------------------- */
.beam-label {
  position: fixed;
  /* Centered on the viewport (roughly middle of the collage). It sits BEHIND
     the collage (negative z-index) so the camera overlaps the text, and shows
     through the collage's paper areas via the layers' darken blend. */
  left: 50%;
  top: 52%;
  transform: translate(-50%, -50%);
  z-index: -2; /* behind both the collage and the beam */
  pointer-events: none;

  font-family: "Inter", system-ui, sans-serif;
  font-weight: 700; /* bold */
  letter-spacing: -0.04em; /* -4% */
  text-transform: none;
  font-size: clamp(3rem, 11vw, 12rem);
  color: #e7e7e7;
  white-space: nowrap;

  opacity: 0; /* instant — the label just shows up in place on hover (no slide) */
}

.beam-label.is-visible {
  opacity: 1;
}

/* Subpage titles read "Home" on hover (script.js): the word fades out and
   "Home" fades in on top, in the same place, type and color. */
.beam-label::after {
  content: "Home";
  position: absolute;
  left: 0;
  top: 0;
  color: #e7e7e7;
  opacity: 0;
  transition: opacity 0.25s ease;
  pointer-events: none;
}
.beam-label.show-home {
  color: transparent;
}
.beam-label.show-home::after {
  opacity: 1;
}

/* "Work" (stone subpage): a bit above the default, low enough that its
   bottom-left corner tucks behind the stone (label is z-index -2). */
.beam-label[data-el="stone"] {
  top: 48%;
}

/* ---------------------------------------------------------------------------
   Debug tag: follows the cursor and prints its position as viewport
   percentages. Toggle by holding "D" or with ?debug=1. Used to read off exact
   origin coordinates for BEAM_CONFIGS.
--------------------------------------------------------------------------- */
.debug-tag {
  position: fixed;
  z-index: 100;
  pointer-events: none;
  display: none;
  padding: 4px 8px;
  transform: translate(12px, 12px); /* offset from the cursor */

  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
  color: #fff;
  background: rgba(0, 0, 0, 0.75);
  border-radius: 4px;
  white-space: nowrap;
}

.debug-tag.is-active {
  display: block;
}

/* ---------------------------------------------------------------------------
   Corner editor (?edit=1) — drag handles + config panel for the stone fill.
--------------------------------------------------------------------------- */
.stone-handle {
  position: fixed;
  z-index: 200;
  width: 14px;
  height: 14px;
  margin: -7px 0 0 -7px; /* center on the point */
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5);
  cursor: grab;
}
.stone-handle:active {
  cursor: grabbing;
}

.stone-editor-panel {
  position: fixed;
  right: 12px;
  bottom: 12px;
  z-index: 201;
  padding: 10px 12px;
  background: rgba(0, 0, 0, 0.82);
  color: #fff;
  border-radius: 6px;
  font: 11px / 1.5 ui-monospace, "SF Mono", Menlo, monospace;
}
.stone-editor-panel pre {
  margin: 8px 0 0;
  white-space: pre;
}
.stone-editor-panel button {
  font: inherit;
  cursor: pointer;
  padding: 3px 8px;
}

/* ---------------------------------------------------------------------------
   DETAIL VIEW (body.is-detail) — entered by clicking an element.
   The collage shifts left so the clicked element sits in the left area, the
   beam letters ride ON TOP of it as a texture, the label drops to the bottom
   left, and a placeholder gallery fills the right half.
--------------------------------------------------------------------------- */
body.is-detail {
  /* Clean white paper for the gallery view (the grain stays, as before). */
  background-color: #ffffff;
}

/* Zoom into the camera and move it to the left (p2 proportions). The origin is
   the camera's position within the collage, so scaling enlarges the camera in
   place; the translate then slides it left. Tune scale/translate to taste. */
body.is-detail .stage {
  transform-origin: 30% 32%; /* the camera's center within the collage */
  transform: translate(-18vw, 9vh) scale(2.6);
  /* Hide the photo — only the text shape (drawn on the beam canvas) remains.
     The stage still defines the geometry the letters map onto, and it still
     receives clicks (opacity doesn't affect hit-testing). */
  opacity: 0;
}

/* Hovering the shape in detail view reveals the camera photo beneath the text
   (a cue that clicking it returns home). */
body.is-detail.is-detail-hover .stage {
  opacity: 1;
}

/* Exit phase: the camera image shrinks back to its homepage size/position while
   the letters fade out — a clean shrink-back of the image, not the text. */
body.is-exiting .stage {
  transform-origin: 30% 32%;
  transform: none;
  opacity: 1; /* show the image shrinking home */
}

/* Keep the letters above the image while they fade out. */
body.is-exiting .beam-canvas {
  z-index: 5;
}

/* On exit the gallery disappears immediately (no lingering fade). */
body.is-exiting .gallery {
  opacity: 0;
  transition: none;
}

/* Transparent-background camera, hidden by default. Shown only when the camera
   is revealed (hover in detail) or during the exit shrink. */
.camera-clean {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease;
}

/* When the image is revealed — hovering the shape in detail, or during the exit
   shrink — show the transparent camera and hide the opaque cutout. No opaque
   background, so no rectangle; only the camera appears. */
body.is-detail.is-detail-hover .stage .camera-clean,
body.is-exiting .stage .camera-clean {
  opacity: 1;
}
body.is-detail.is-detail-hover .stage .layer,
body.is-exiting .stage .layer {
  opacity: 0;
}

/* In detail view the letters sit ON TOP of the photo (texture overlay). */
body.is-detail .beam-canvas {
  z-index: 5;
}

/* Label moves to the bottom-left, large. z-index lifts it above the camera
   panel so it isn't hidden behind it. */
/* The title is a link home (hover reads "Home" — script.js). */
body.is-detail:not(.is-exiting) .beam-label {
  pointer-events: auto;
  cursor: pointer;
}
body.is-detail .beam-label {
  left: 3vw;
  top: 88vh;
  transform: translate(0, -50%);
  font-size: clamp(3rem, 8vw, 7rem);
  z-index: 7; /* above the camera letters (beam canvas z-index 5) */
  /* Only the detail-view move animates; the homepage label appears in place. */
  transition: none; /* the move to the corner is a FLIP on transform (script.js) */
}

/* Gallery: a clean grid of empty square cells (a "shelf") with thin gray
   borders, filling the right half — ready for photography to be dropped in. */
.gallery {
  position: fixed;
  top: 4vh;
  right: 3vw;
  width: 45vw;
  max-height: 94vh; /* extend toward the bottom so it reads as continuing */
  z-index: 6;

  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr)); /* stable 3 columns (R#C# coords); minmax keeps cells equal in Safari */
  gap: 0.75vw;
  align-content: start;
  overflow-y: auto;

  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease 0.15s;
}

body.is-detail .gallery {
  opacity: 1;
  pointer-events: auto;
}

/* Hide the scrollbar entirely (still scrolls via trackpad/wheel). */
.gallery {
  overscroll-behavior: contain;
  scrollbar-width: none; /* Firefox */
}
.gallery::-webkit-scrollbar {
  display: none; /* Chrome / Safari */
}

/* Spacer row at the end of the grid — gives breathing room at the bottom of
   the scroll (Chromium ignores a scroll container's own padding-bottom). */
.gallery::after {
  content: "";
  grid-column: 1 / -1;
  height: 2.5vh;
}

.gallery-tile {
  position: relative;
  aspect-ratio: 1 / 1; /* square cells */
  min-width: 0;
  min-height: 0;
  align-self: start;
  border: 1px solid #d4d4d4; /* clean, thin gray border */
  background-color: transparent;
  overflow: hidden; /* clips any image dropped inside */
  /* Smooth both entering and leaving a project hover. */
  transition: opacity 0.5s ease, background-color 0.5s ease,
    border-color 0.5s ease;
}

/* A photo dropped into a cell shows whole (cutouts on transparent bg), with a
   little breathing room from the border. */
.gallery-tile > img {
  /* Out of the flow: the cell's size comes from aspect-ratio alone. (Safari
     treats height:100% inside an aspect-ratio grid item as indefinite and
     would stretch the row to the image's own height.) */
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  padding: 8%;
  box-sizing: border-box;
  object-fit: contain;
  object-position: center;
}

/* Add class="toned" to an image to show it grayscale by default and reveal its
   original color on hover. */
.gallery-tile > img.toned {
  filter: grayscale(1); /* static: never animated */
}
/* Colour on hover: a colour copy (script.js) fades in over the grey image —
   opacity only, no filter animation. */
.gallery-tile > img.tone-color {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.45s ease;
}
.gallery-tile:hover > img.tone-color {
  opacity: 1;
}

/* ---------------------------------------------------------------------------
   PROJECT PREVIEW — hovering a gallery project (e.g. the crow → "Modern Fable")
   drops a full-screen backdrop behind everything, keeps that project's tiles
   (as white squares) while the other cells vanish, and shows the project name.
--------------------------------------------------------------------------- */

/* Full-screen backdrops (one per project), bottom layer (above the page bg,
   below the collage text / gallery). Each keeps its image assigned, so on exit
   only the opacity fades — a smooth fade-out (a single element that swapped its
   background-image would snap, since background-image can't transition). */
.project-bg {
  position: fixed;
  inset: 0;
  z-index: 0;
  background-size: cover;
  background-position: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease;
}

/* Backdrop images are set by script.js from each element's data-bg (the
   optimized WebP for the screen size), only once Photography is open or a
   tile is hovered — never on page load. */
body.is-project-crow .bg-crow,
body.is-project-berlin .bg-berlin,
body.is-project-archive .bg-archive,
body.is-project-bench .bg-bench,
body.is-project-window .bg-window,
body.is-project-cup .bg-cup {
  opacity: 1;
}

/* On a project hover: every cell except that project's trigger tile vanishes
   (cells keep their grid space so the tile stays put); the trigger becomes a
   clean white square. */
body.is-project-crow .gallery-tile:not(.crow-trigger),
body.is-project-berlin .gallery-tile:not(.berlin-trigger),
body.is-project-archive .gallery-tile:not(.archive-trigger),
body.is-project-bench .gallery-tile:not(.bench-trigger),
body.is-project-window .gallery-tile:not(.window-trigger),
body.is-project-cup .gallery-tile:not(.cup-trigger) {
  opacity: 0;
}
body.is-project-crow .gallery-tile.crow-trigger,
body.is-project-berlin .gallery-tile.berlin-trigger,
body.is-project-archive .gallery-tile.archive-trigger,
body.is-project-bench .gallery-tile.bench-trigger,
body.is-project-window .gallery-tile.window-trigger,
body.is-project-cup .gallery-tile.cup-trigger {
  background-color: #ffffff;
  border-color: transparent;
}

/* Project names — sit just beside their trigger tile, vertically centered on
   it. Hidden until that project is hovered. Positioned per project. */
.project-name {
  position: absolute;
  z-index: 1;
  transform: translateY(-50%);

  font-family: "Inter", system-ui, sans-serif;
  font-weight: 600; /* semibold */
  letter-spacing: -0.04em;
  font-size: clamp(0.85rem, 1.35vw, 1.4rem);
  line-height: 1.2;
  color: #ffffff;
  white-space: nowrap;

  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease;
}

/* Right of the crow (R1C1) */
.name-crow {
  left: 37%;
  top: 7.25vw;
  text-align: left;
}
/* Right of berlin (R3C2) */
.name-berlin {
  left: 71%;
  top: 37.75vw;
  text-align: left;
}
/* LEFT of archive (R2C3) — right-aligned so it ends just before the box */
.name-archive {
  right: 37.5%;
  top: 22.5vw;
  text-align: right;
}
/* LEFT of window (R5C3) — right-aligned */
.name-window {
  right: 37.5%;
  top: 68.25vw; /* row 5 center */
  text-align: right;
}
/* Right of bench (R6C2) */
.name-bench {
  left: 71%;
  top: 83.5vw; /* row 6 center */
  text-align: left;
}
/* Right of cup (R4C1) */
.name-cup {
  left: 37%;
  top: 53vw; /* row 4 center */
  text-align: left;
}

body.is-project-crow .name-crow,
body.is-project-berlin .name-berlin,
body.is-project-archive .name-archive,
body.is-project-bench .name-bench,
body.is-project-window .name-window,
body.is-project-cup .name-cup {
  opacity: 1;
}

/* Reduced motion: no zooms, slides or fades on the homepage / Photography;
   states simply switch. (Subpages have their own blocks; the title's FLIP
   move is skipped in script.js.) */
@media (prefers-reduced-motion: reduce) {
  .stage,
  .stage .bg,
  .stage .layer,
  .camera-clean,
  .beam-label,
  .gallery,
  .gallery-tile,
  .gallery-tile > img,
  .project-bg,
  .project-name {
    transition: none !important;
  }
}

/* Collage objects are links (script.js sets .is-pointer while one is under
   the cursor; hit-testing is per pixel, so it can't be done in CSS). */
.stage.is-pointer,
body.is-detail.is-detail-hover .stage {
  cursor: pointer;
}

/* Homepage navigation hint (home.config.js): breathes (fades out and back
   in, smoothly) while shown; .is-gone fades it out for good. */
.home-hint {
  position: fixed;
  left: 50%;
  bottom: 5vh;
  transform: translateX(-50%);
  z-index: 8;
  font-family: "Inter", system-ui, sans-serif;
  font-size: 13px;
  font-weight: 300;
  letter-spacing: -0.04em;
  color: rgba(0, 0, 0, 0.3);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
}
.home-hint.is-on {
  animation: home-hint-breathe var(--hint-cycle, 4000ms) ease-in-out infinite;
}
.home-hint.is-gone {
  transition: opacity 0.4s ease;
}
@keyframes home-hint-breathe {
  0% {
    opacity: 0;
  }
  45%,
  55% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}
/* Only on the homepage itself (the breathing animation would override a
   plain opacity, so it's switched off and hidden on subpages). */
body.is-detail .home-hint,
body.is-work .home-hint,
body.is-playground .home-hint,
body.is-about .home-hint {
  animation: none;
  visibility: hidden;
}
@media (prefers-reduced-motion: reduce) {
  .home-hint.is-on {
    animation: none;
    opacity: 1;
  }
  .home-hint.is-gone {
    transition: none;
  }
}

/* 尤 calligraphy watermark behind "Yóu" in the contact labels (IMG.youMark).
   Sized and placed in em, so it follows the text; it never affects layout
   or line height. Light grey: the ink at 14%. */
.you-mark {
  position: relative;
}
.you-mark-img {
  position: absolute;
  z-index: -1;
  left: calc(100% - 0.55em); /* starts on the "u", runs past it to the right */
  bottom: -0.55em; /* the lowest stroke crosses below the baseline */
  height: 4.3em;
  width: auto;
  max-width: none;
  opacity: 0.14;
  mix-blend-mode: multiply;
  pointer-events: none;
  user-select: none;
}
