/* ---------------------------------------------------------------
   Sunao Takahashi — site styles
   Measurements come from the Figma "Overview" frame (1440 × 1024).
   --------------------------------------------------------------- */

/* ABC Favorit Medium, used for all text.
   The file in /fonts is currently Dinamo's TRIAL version, which is fine for
   previewing but not for the live site: buy a web licence from Dinamo and
   replace fonts/ABCFavorit-Medium.woff2 with the licensed file before launch. */
@font-face {
  font-family: "ABC Favorit";
  src: url("fonts/ABCFavorit-Medium.woff2") format("woff2");
  font-weight: 500;
  font-display: swap;
}

:root {
  --bg: #f6f6f6;          /* page background */
  --placeholder: #d9d9d9; /* shown while an image loads */
  --ink: #000;
  --ease-reveal: cubic-bezier(0.165, 0.84, 0.44, 1); /* Quart ease-out */
  --edge: 8px;            /* text inset from the screen edge */
  --text: 13px;

  /* Grid from the "Overview with images" frame: 109px-wide images on a
     166px column pitch and ~196px row pitch. Expressed as ratios so the
     whole wall scales with the window. */
  --gap-ratio: 0.5229;    /* column gap ÷ image width   (57 / 109) */
  --cell-ratio: 0.5556;   /* image width ÷ row pitch    (109 / 196.2) */
  --cols: 8;              /* desktop: 8 columns (the design has 9) */

  --font: "ABC Favorit", "Helvetica Neue", Helvetica, Arial, sans-serif;
}

@media (max-width: 1100px) { :root { --cols: 6; } }
@media (max-width: 720px)  { :root { --cols: 4; } }

* { box-sizing: border-box; }

html { scrollbar-gutter: stable; }

html, body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-weight: 500;
  font-size: var(--text);
  line-height: normal;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }
a:hover, a:focus-visible { text-decoration: underline; }
.footer a:hover, .footer a:focus-visible { text-decoration: none; }

/* Email and Instagram links in the Contact panel: same 60% fade as the titles. */
.contact__text a { transition: opacity 0.2s ease; }
.contact__text a:hover,
.contact__text a:focus-visible { opacity: 0.6; text-decoration: none; }

/* ---------- Image wall ---------- */

.wall {
  display: grid;
  grid-template-columns: repeat(var(--cols), 1fr);
  column-gap: calc(100% * var(--gap-ratio) / (var(--cols) + (var(--cols) - 1) * var(--gap-ratio)));
  row-gap: 0;
  padding-bottom: 48px; /* keeps the last row clear of the footer */
}

.cell {
  aspect-ratio: var(--cell-ratio);
  position: relative;
}

.thumb {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

/* Grid photos pop in one by one in a scattered order (after michaelbrunt.co/archive):
   each does a quick 0.33s fade with a gentle ease-out; app.js sets the random timing. */
.thumb img {
  opacity: 0;
  transition: opacity 0.33s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.thumb img.is-loaded { opacity: 1; }


.thumb img {
  display: block;
  width: 100%;
  height: auto;
}

/* Hovering a photo fades all the others to 50% (computers only, not touch). */
@media (hover: hover) and (pointer: fine) {
  .wall:has(.thumb:hover) .thumb:not(:hover):not(.is-dimmed) { opacity: 0.5; }
  /* Opening the lightbox covers the grid, which ends the hover. Hold that 50% fade
     until the scatter fade-out reaches each photo, so nothing flashes back to full. */
  .hold-hover .thumb:not(.is-picked):not(.is-dimmed) { opacity: 0.5; }
}

.thumb:focus-visible { outline: 1px solid var(--ink); outline-offset: 3px; }

/* ---------- Footer ---------- */

/* Footer text uses the "difference" blend mode: it's set in white, so it
   shows near-black on the pale background and flips to the inverse colour
   wherever it passes over a photo, staying readable on anything. */
.footer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: var(--edge);
  z-index: 30;
  pointer-events: none;
  color: #fff;
  mix-blend-mode: difference;
}
.footer > * { pointer-events: auto; position: absolute; bottom: 0; }
.footer__name { left: max(var(--edge), env(safe-area-inset-left, 0px)); }
.footer__contact { left: 73.75%; } /* 1062 / 1440 in the design */

@media (max-width: 720px) {
  /* Phones: 20px clear of the sides and the bottom, plus any notch, rounded
     corner or home-bar inset, so the text never sits in the screen's corners. */
  .footer { bottom: max(20px, calc(env(safe-area-inset-bottom, 0px) + 8px)); }
  .footer__name { left: max(20px, env(safe-area-inset-left, 0px)); }
  .footer__contact { left: auto; right: max(20px, env(safe-area-inset-right, 0px)); }
}

/* ---------- Opening sequence ----------
   On first load: "Sunao Takahashi" wipes up from the bottom, then "Contact",
   then the photos load in (timing in app.js). */
.reveal { display: inline-block; overflow: hidden; vertical-align: bottom; }
.reveal__inner {
  display: inline-block;
  transform: translate3d(0, 105%, 0);
  transition: transform 0.8s var(--ease-reveal);
}
.reveal.is-in .reveal__inner { transform: translate3d(0, 0, 0); }

/* Footer words drop to 60% opacity on hover (no underline). Contact also stays
   at 60% while its panel is open. "Sunao Takahashi" is only a link while the
   Contact panel is open: it takes you back to the grid. */
.footer__name,
.footer__contact { transition: opacity 0.2s ease; }
.footer__contact:hover,
.footer__contact:focus-visible,
.footer__contact.is-current,
.footer__name.is-active:hover,
.footer__name.is-active:focus-visible { opacity: 0.6; text-decoration: none; }
.footer__name.is-active { cursor: pointer; }
.footer__name:focus-visible, .footer__contact:focus-visible { outline: none; }
@media (prefers-reduced-motion: reduce) { .reveal__inner { transition: none; } }

/* ---------- Image pop-up ("Image click" frame in Figma) ----------
   The grid stays visible underneath, washed out by a 60% white layer.
   The photo sits centred at 751/1024 of the screen height.

   All movement here uses transforms only, which the browser hands to the
   graphics card, so the wipes stay smooth even over a full screen of photos.
   A "wipe" is a window (.lightbox__clip) sliding one way while the photo
   inside slides the opposite way by the same amount, so the photo itself
   stays still and only its visible edge moves. */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: grid;
  place-items: center;
  overflow: hidden;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.25s ease;
}
.lightbox.is-open { opacity: 1; }
.lightbox[hidden] { display: none; }

.lightbox__wash {
  position: absolute;
  inset: 0;
  background: rgba(255, 255, 255, 0.6);
  will-change: transform;
}

.lightbox__figure { margin: 0; position: relative; }

.lightbox__clip,
.lightbox__img {
  will-change: transform;
  transition: transform 1s var(--ease-reveal);
}
.lightbox__clip { overflow: hidden; transform: translate3d(0, 101%, 0); }
.lightbox__img {
  display: block;
  height: 73.3vh;          /* 751 / 1024 */
  width: auto;
  max-width: 90vw;
  object-fit: contain;
  transform: translate3d(0, -101%, 0);
}
/* Opening: once loaded, the photo is uncovered from the bottom edge upwards. */
.lightbox__clip.is-loaded,
.lightbox__clip.is-loaded .lightbox__img { transform: translate3d(0, 0, 0); }

/* Closing, in two steps, both wiping from the top downwards:
   1. the photo is wiped away (0.6s),
   2. then the white wash slides down off the grid (0.8s). */
.lightbox.is-closing { pointer-events: none; }
.lightbox.is-closing .lightbox__clip.is-loaded {
  transform: translate3d(0, 101%, 0);
  transition-duration: 0.6s;
}
.lightbox.is-closing .lightbox__clip.is-loaded .lightbox__img {
  transform: translate3d(0, -101%, 0);
  transition-duration: 0.6s;
}
.lightbox.is-closing .lightbox__wash {
  transform: translate3d(0, 100%, 0);
  transition: transform 0.8s var(--ease-reveal) 0.6s;
}

/* Scatter mode (LIGHTBOX_OPEN = "scatter" in app.js): no white layer; instead
   each grid photo fades back to 40% on its own, quickly, at a random moment
   (about the same look as the 60% white wash). */
.lightbox--scatter .lightbox__wash { display: none; }
.thumb { transition: opacity 0.25s ease-out; }
.thumb.is-dimmed { opacity: 0.4; }
/* While Contact closes, the photos briefly match its stronger white layer. */
.deep-dim .thumb.is-dimmed { opacity: 0.15; }

/* ---------- "Close" cursor in the image pop-up ----------
   On computers (mouse or trackpad), the cursor becomes the word "Close" in the
   title font while an image is open. Like the footer, it's white with the
   "difference" blend so it stays readable over the photo and the grid.
   Touch screens keep normal behaviour (tap anywhere to close). */
.cursor-label {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 40;
  pointer-events: none;
  color: #fff;
  mix-blend-mode: difference;
  white-space: nowrap;
  opacity: 0;
  transition: opacity 0.2s ease;
  will-change: transform;
}
.cursor-label.is-visible { opacity: 1; }
.has-label-cursor .lightbox,
.has-label-cursor .lightbox *,
.has-label-cursor .contact.is-open { cursor: none; }
.has-label-cursor .contact__clip { cursor: auto; }
.has-label-cursor .contact__clip a { cursor: pointer; }

/* Keyboard-only controls: Esc closes, arrow keys move between images. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .lightbox, .lightbox__wash, .lightbox__clip, .lightbox__img,
  .lightbox.is-closing .lightbox__clip.is-loaded,
  .lightbox.is-closing .lightbox__clip.is-loaded .lightbox__img,
  .lightbox.is-closing .lightbox__wash, .thumb img, .thumb { transition: none !important; }
}

/* ---------- Contact panel ("Contact Click" frame in Figma) ----------
   Opening: the 60% white wash slides up from the bottom of the screen (0.8s),
   then the text is uncovered from its bottom edge upwards (0.6s).
   Closing plays the same thing in reverse: the text wipes away, then the wash
   slides back down. Transforms only, so it stays smooth. The text uses the same
   sliding-window trick as the image pop-up. */

.contact {
  position: fixed;
  inset: 0;
  z-index: 20;
  overflow: hidden;
  pointer-events: none;
}
.contact.is-open { pointer-events: auto; }
.contact[hidden] { display: none; }

.contact__wash {
  position: absolute;
  inset: 0;
  background: rgba(255, 255, 255, 0.85); /* stronger than the image pop-up so the text reads clearly */
  will-change: transform;
  transform: translate3d(0, 100%, 0);
  transition: transform 0.8s var(--ease-reveal) 0.6s;  /* closing: after the text */
}
.contact.is-open .contact__wash {
  transform: translate3d(0, 0, 0);
  transition-delay: 0s;                                  /* opening: first */
}

.contact__clip {
  position: absolute;
  left: 24px;
  top: 24.7vh;              /* 253 / 1024 */
  max-width: calc(100vw - 48px);
  overflow: hidden;
  cursor: auto;
}
.contact__clip,
.contact__text {
  will-change: transform;
  transition: transform 0.6s var(--ease-reveal);        /* closing: first */
}
.contact__clip { transform: translate3d(0, 100%, 0); }
.contact__text { transform: translate3d(0, -100%, 0); }
.contact.is-open .contact__clip,
.contact.is-open .contact__text {
  transform: translate3d(0, 0, 0);
  transition-delay: 0.7s;                                /* opening: after the wash */
}

/* Alternative text effect (CONTACT_TEXT = "words" in app.js): no wipe on the
   text; instead each word fades in on its own in reading order, top to
   bottom, with the same quick fade as the grid photos. */
.contact--words .contact__clip,
.contact--words .contact__text,
.contact--words.is-open .contact__clip,
.contact--words.is-open .contact__text { transform: none; transition: none; }
/* Alternative text effect (CONTACT_TEXT = "fade"): no wipe; the whole block of
   text fades in quickly once the wash is up, and fades out first on close. */
.contact--fade .contact__clip,
.contact--fade .contact__text,
.contact--fade.is-open .contact__clip { transform: none; transition: none; }
.contact--fade .contact__text {
  opacity: 0;
  transition: opacity 0.3s ease-out;                     /* closing: first */
}
.contact--fade.is-open .contact__text {
  opacity: 1;
  transform: none;
  transition: opacity 0.3s ease-out 0.7s;                /* opening: after the wash */
}
.contact--fade .contact__wash { transition-delay: 0.3s; }
.contact--fade.is-open .contact__wash { transition-delay: 0s; }

/* Scatter mode, closing: the white layer stays put and dissolves (after the
   text has faded) while the photos underneath take over the faded look and
   then come back up at random (app.js). */
.contact--scatter.contact--leaving .contact__wash {
  transform: translate3d(0, 0, 0);
  opacity: 0;
  transition: opacity 0.25s ease-out 0.3s;
}
.no-thumb-anim .thumb { transition: none !important; }

.contact__word {
  opacity: 0;
  transition: opacity 0.33s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.contact__word.is-in { opacity: 1; }

.contact__text p { margin: 0; white-space: pre-line; }
.contact__text p + p { margin-top: 1.15em; } /* the blank line between blocks */

@media (prefers-reduced-motion: reduce) {
  .contact__wash, .contact__clip, .contact__text,
  .contact.is-open .contact__wash, .contact.is-open .contact__clip, .contact.is-open .contact__text,
  .contact__word { transition: none; }
}

body.is-locked { overflow: hidden; }
