/* =============================================================
   BAWE — utilities
   Small single-purpose helpers. Load last.
   ============================================================= */

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------- Grids ---------- */

.grid { display: grid; gap: var(--space-6); }
.grid--gap-8  { gap: var(--space-8); }
.grid--gap-12 { gap: var(--space-12); }

@media (min-width: 640px) {
  .sm\:grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 768px) {
  .md\:grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .md\:grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
  .lg\:grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lg\:grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .lg\:grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* ---------- Flow ---------- */

/* Each modifier carries display:grid itself. On its own, a gap does
   nothing — which is how the project page and contact sidebar ended up
   with every block touching the next. */
.stack     { display: grid; gap: var(--space-4); }
.stack--6  { display: grid; gap: var(--space-6); }
.stack--8  { display: grid; gap: var(--space-8); }
.stack--12 { display: grid; gap: var(--space-12); }

.flow > * + * { margin-top: var(--space-6); }

/* ---------- Spacing ---------- */

.mt-4  { margin-top: var(--space-4); }
.mt-6  { margin-top: var(--space-6); }
.mt-8  { margin-top: var(--space-8); }
.mt-12 { margin-top: var(--space-12); }
.mt-16 { margin-top: var(--space-16); }
.mb-4  { margin-bottom: var(--space-4); }
.mb-6  { margin-bottom: var(--space-6); }
.mb-8  { margin-bottom: var(--space-8); }
.mb-12 { margin-bottom: var(--space-12); }

/* ---------- Colour helpers ---------- */

.text-steel { color: var(--bawe-steel-600); }
.text-mute  { color: var(--bawe-steel-500); }
.text-blue  { color: var(--bawe-blue-600); }
.section--dark .text-steel,
.on-dark .text-steel { color: var(--on-dark-body); }
.section--dark .text-mute,
.on-dark .text-mute { color: var(--on-dark-mute); }

/* ---------- Misc ---------- */

.measure { max-width: 62ch; }
.nowrap  { white-space: nowrap; }

/* =============================================================
   Motion — scroll reveal
   The hidden state is scoped to .js so that if JavaScript never
   runs, nothing is ever invisible.
   ============================================================= */

.js [data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 700ms var(--ease), transform 700ms var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
}
.js [data-reveal].is-in {
  opacity: 1;
  transform: none;
}

/* Reveal that only fades — for full-bleed media, which should
   not shift. */
.js [data-reveal="fade"] { transform: none; }

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] {
    opacity: 1;
    transform: none;
  }
}
