/* Small helper classes. Keep this layer thin. */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.aspect-portrait { aspect-ratio: 4 / 5; }
.aspect-landscape { aspect-ratio: 16 / 10; }
.aspect-square { aspect-ratio: 1 / 1; }

/* Reveal-on-scroll / hero-reveal base state. Only takes effect once JS confirms it's
   ready - see ARCHITECTURE.md §8. If JS never runs, content stays fully visible. */
html.js-ready [data-reveal],
html.js-ready [data-hero-reveal] {
  opacity: 0;
  transform: translateY(24px);
}

/*
  The fade-only reveal, for anything that builds its own transform out of custom
  properties (the studio-table objects, the archive plates, the About portrait).

  Those elements compose a rotation, a hover lift, a scroll drift and a drag offset
  into one transform. GSAP's normal reveal animates `y`, which writes an inline
  `transform` and overwrites the whole thing: the result is a plate that has a
  --rot of -0.55deg and renders perfectly square. Nothing here writes transform, so
  there is nothing to fight over. See components/draggable.js.
*/
html.js-ready [data-reveal-fade],
html.js-ready [data-hero-fade] {
  opacity: 0;
}

/* Keeps a short phrase on one line, e.g. a hyphenated word that must not split. */
.nowrap {
  white-space: nowrap;
}
