@layer utilities {
  .visually-hidden {
    position: absolute !important;
    inline-size: 1px !important;
    block-size: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
  }

  .text-center { text-align: center; }
  .text-balance { text-wrap: balance; }
  .text-pretty  { text-wrap: pretty; }

  .mx-auto { margin-inline: auto; }

  /* mattemotto: la pagina respira. nada salta como en flash.
     Multi-direction scroll reveals. Set direction with [data-reveal]
     and per-element delay with [data-stagger]. The site needs to feel
     alive — generous distance, slow settle, soft curve, no bounce. */
  .reveal {
    --reveal-tx:        0;
    --reveal-ty:        3.5rem;        /* 56px default — registers on a desktop */
    --reveal-rx:        0deg;
    --reveal-scale:     1;
    --reveal-origin:    center center;
    --reveal-delay:     0ms;
    --reveal-duration:  clamp(700ms, 1.4s, 1600ms);
    --reveal-ease:      cubic-bezier(0.16, 1, 0.3, 1);
    opacity: 0;
    transform: translate3d(var(--reveal-tx), var(--reveal-ty), 0)
               rotateX(var(--reveal-rx))
               scale(var(--reveal-scale));
    transform-origin: var(--reveal-origin);
    transition:
      opacity   var(--reveal-duration) var(--reveal-ease) var(--reveal-delay),
      transform var(--reveal-duration) var(--reveal-ease) var(--reveal-delay);
    will-change: opacity, transform;
  }

  /* — Direction vocabularies — */
  .reveal[data-reveal="up"]      { --reveal-tx: 0;      --reveal-ty:  3.5rem; }
  .reveal[data-reveal="down"]    { --reveal-tx: 0;      --reveal-ty: -3.5rem; }
  .reveal[data-reveal="left"]    { --reveal-tx: 4.5rem; --reveal-ty: 0; --reveal-origin: left center; }
  .reveal[data-reveal="right"]   { --reveal-tx: -4.5rem; --reveal-ty: 0; --reveal-origin: right center; }
  /* rise — translate + scale, the "settling into place" curtain move */
  .reveal[data-reveal="rise"] {
    --reveal-tx: 0;
    --reveal-ty: 5rem;
    --reveal-scale: 0.96;
    --reveal-origin: center bottom;
  }
  /* cascade — softer, shorter distance, for tightly-stacked items rippling in */
  .reveal[data-reveal="cascade"] {
    --reveal-tx: 0;
    --reveal-ty: 2rem;
    --reveal-duration: clamp(600ms, 1.1s, 1300ms);
  }
  /* fold — rotateX + translate, plate-folding-onto-page move */
  .reveal[data-reveal="fold"] {
    --reveal-tx: 0;
    --reveal-ty: 1.75rem;
    --reveal-rx: 8deg;
    --reveal-origin: center top;
  }

  /* — Stagger tokens — */
  .reveal[data-stagger="0"]    { --reveal-delay: 0ms; }
  .reveal[data-stagger="80"]   { --reveal-delay: 80ms; }
  .reveal[data-stagger="100"]  { --reveal-delay: 100ms; }
  .reveal[data-stagger="120"]  { --reveal-delay: 120ms; }
  .reveal[data-stagger="150"]  { --reveal-delay: 150ms; }
  .reveal[data-stagger="160"]  { --reveal-delay: 160ms; }
  .reveal[data-stagger="180"]  { --reveal-delay: 180ms; }
  .reveal[data-stagger="200"]  { --reveal-delay: 200ms; }
  .reveal[data-stagger="220"]  { --reveal-delay: 220ms; }
  .reveal[data-stagger="240"]  { --reveal-delay: 240ms; }
  .reveal[data-stagger="300"]  { --reveal-delay: 300ms; }
  .reveal[data-stagger="320"]  { --reveal-delay: 320ms; }
  .reveal[data-stagger="360"]  { --reveal-delay: 360ms; }
  .reveal[data-stagger="400"]  { --reveal-delay: 400ms; }
  .reveal[data-stagger="440"]  { --reveal-delay: 440ms; }
  .reveal[data-stagger="450"]  { --reveal-delay: 450ms; }
  .reveal[data-stagger="480"]  { --reveal-delay: 480ms; }
  .reveal[data-stagger="540"]  { --reveal-delay: 540ms; }
  .reveal[data-stagger="600"]  { --reveal-delay: 600ms; }
  .reveal[data-stagger="640"]  { --reveal-delay: 640ms; }
  .reveal[data-stagger="720"]  { --reveal-delay: 720ms; }
  .reveal[data-stagger="800"]  { --reveal-delay: 800ms; }
  .reveal[data-stagger="880"]  { --reveal-delay: 880ms; }

  /* IO fallback landing state */
  .reveal.in {
    opacity: 1;
    transform: translate3d(0, 0, 0) rotateX(0deg) scale(1);
  }

  /* — Reveal mechanism: pure IntersectionObserver + time-based transition.
       The modern animation-timeline:view() path was tried and pulled — it's
       fragile on fast scrolls (animation outpaced before frames render) and
       handles long staggers badly (delays counted in time, not scroll). The
       IO + transition approach is rock-solid: once an element is in view,
       the transition runs its full duration regardless of scroll speed.
       mattemotto: si el ojo lo ve, lo ve moverse. — */

  @media (prefers-reduced-motion: reduce) {
    .reveal,
    .reveal.in {
      opacity: 1 !important;
      transform: none !important;
      transition: none !important;
      animation: none !important;
    }
  }
}
