/*!
 * AimsCraft — Multi-purpose Ecommerce Frontend
 * File:      assets/css/utilities.css
 * Developer: Shahid Malla <https://shahidmalla.com>
 * Company:   ElySpace — owner of this source code
 * Copyright: © ElySpace. All rights reserved.
 */
/* ==========================================================================
   AimsCraft — utilities.css
   A small, opinionated helper layer. NOT a utility framework: layout and
   components live in layout.css / components.css. Utilities exist to stop
   one-off rules leaking into page CSS.

   Naming
     .u-*        single-purpose helper
     .stack .cluster .center .grid .scroll-x   layout primitives (no u- prefix)

   Breakpoints (mobile-first, used project-wide)
     480 / 640 / 768 / 1024 / 1280 / 1536
   ========================================================================== */

/* ==========================================================================
   1. DISPLAY
   ========================================================================== */
.u-block { display: block; }
.u-inline-block { display: inline-block; }
.u-inline { display: inline; }
.u-flex { display: flex; }
.u-inline-flex { display: inline-flex; }
.u-grid { display: grid; }
.u-inline-grid { display: inline-grid; }
.u-contents { display: contents; }
.u-hidden,
.u-none { display: none !important; }

/* ==========================================================================
   2. FLEX + ALIGNMENT
   ========================================================================== */
.u-col { flex-direction: column; }
.u-row { flex-direction: row; }
.u-wrap { flex-wrap: wrap; }
.u-nowrap-flex { flex-wrap: nowrap; }

.u-items-start { align-items: flex-start; }
.u-items-center { align-items: center; }
.u-items-end { align-items: flex-end; }
.u-items-stretch { align-items: stretch; }
.u-items-baseline { align-items: baseline; }

.u-justify-start { justify-content: flex-start; }
.u-justify-center { justify-content: center; }
.u-justify-end { justify-content: flex-end; }
.u-justify-between { justify-content: space-between; }
.u-justify-around { justify-content: space-around; }
.u-justify-evenly { justify-content: space-evenly; }

.u-self-start { align-self: flex-start; }
.u-self-center { align-self: center; }
.u-self-end { align-self: flex-end; }
.u-self-stretch { align-self: stretch; }

.u-flex-1 { flex: 1 1 0%; }
.u-flex-auto { flex: 1 1 auto; }
.u-flex-none { flex: none; }
.u-shrink-0 { flex-shrink: 0; }
.u-grow { flex-grow: 1; min-inline-size: 0; }
.u-grow-0 { flex-grow: 0; }
.u-min-w-0 { min-width: 0; }
.u-min-h-0 { min-height: 0; }
.u-order-first { order: -1; }
.u-order-last { order: 999; }
.u-ms-auto { margin-inline-start: auto; }
.u-me-auto { margin-inline-end: auto; }

/* ==========================================================================
   3. LAYOUT PRIMITIVES
   ========================================================================== */

/* Owl: vertical rhythm without margins on the outside */
.stack > * + * { margin-block-start: var(--stack-space, var(--sp-4)); }
.stack--2xs { --stack-space: var(--sp-1); }
.stack--xs { --stack-space: var(--sp-2); }
.stack--sm { --stack-space: var(--sp-3); }
.stack--md { --stack-space: var(--sp-5); }
.stack--lg { --stack-space: var(--sp-8); }
.stack--xl { --stack-space: var(--sp-12); }

/* Inline group that wraps gracefully */
.cluster {
  display: flex;
  flex-wrap: wrap;
  /* :where() drops this to zero specificity so the .u-items-* modifiers above
     can actually change it. As a plain declaration it out-ranked them purely by
     source order, which made every `class="cluster u-items-start"` a lie. */
  gap: var(--cluster-space, var(--sp-3));
}
:where(.cluster) { align-items: center; }
/* Icon + text on one line, with the text wrapping inside its own column.

   .cluster cannot do this: its children are flex items with `flex-basis: auto`,
   so a <span> is measured at its MAX-content width — the whole sentence on one
   line. Flex breaks lines before it shrinks anything, so any sentence longer
   than the remaining space is pushed onto a second flex line and the icon is
   left stranded above it. A two-track grid sizes the text column to what is
   left instead, which also gives wrapped lines a proper hanging indent. */
.icon-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: var(--cluster-space, var(--sp-2));
}
.icon-row > .icon { margin-block-start: .15em; }   /* optical align to line 1 */

.cluster--xs { --cluster-space: var(--sp-1); }
.cluster--sm { --cluster-space: var(--sp-2); }
.cluster--lg { --cluster-space: var(--sp-5); }
.cluster--between { justify-content: space-between; }
.cluster--end { justify-content: flex-end; }
.cluster--center { justify-content: center; }
.cluster--top { align-items: flex-start; }

/* Dead-centre anything */
.center {
  display: flex;
  align-items: center;
  justify-content: center;
}
.center--col { flex-direction: column; }
.u-mx-auto { margin-inline: auto; }

/* Grids. --col-min drives .grid--auto, --grid-gap drives all of them. */
.grid {
  display: grid;
  gap: var(--grid-gap, var(--sp-5));
}
.grid--auto {
  grid-template-columns: repeat(auto-fit, minmax(min(var(--col-min, 240px), 100%), 1fr));
}
.grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid--5 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid--6 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid--1 { grid-template-columns: minmax(0, 1fr); }
.u-col-span-2 { grid-column: span 2; }
.u-col-full { grid-column: 1 / -1; }

@media (min-width: 640px) {
  /* 2-up, not 3-up: .grid--4 holds FOUR items, so a 3-column step leaves the
     fourth alone with two empty tracks beside it for the whole 640-1023 band.
     Two even rows read as intentional; 3 + 1 reads as broken. */
  .grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid--5 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .grid--6 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

@media (min-width: 768px) {
  .grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .grid--5 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .grid--6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
  .grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .grid--5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}

/* Horizontal snap rail — the mobile "app" pattern used across the site */
.scroll-x {
  display: flex;
  gap: var(--rail-gap, var(--sp-4));
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--container-pad);
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.scroll-x::-webkit-scrollbar { display: none; }
.scroll-x > * {
  flex: 0 0 auto;
  width: var(--rail-item, auto);
  scroll-snap-align: start;
}
/* Let a rail run edge-to-edge inside a padded container */
.scroll-x--bleed {
  margin-inline: calc(var(--container-pad) * -1);
  padding-inline: var(--container-pad);
}
.scroll-x--center > * { scroll-snap-align: center; }
.u-no-scrollbar { scrollbar-width: none; -ms-overflow-style: none; }
.u-no-scrollbar::-webkit-scrollbar { display: none; }

/* ==========================================================================
   4. SPACING (subset of the 4px scale)
   ========================================================================== */
.u-gap-0 { gap: var(--sp-0); }
.u-gap-1 { gap: var(--sp-1); }
.u-gap-2 { gap: var(--sp-2); }
.u-gap-3 { gap: var(--sp-3); }
.u-gap-4 { gap: var(--sp-4); }
.u-gap-5 { gap: var(--sp-5); }
.u-gap-6 { gap: var(--sp-6); }
.u-gap-8 { gap: var(--sp-8); }
.u-gap-10 { gap: var(--sp-10); }
.u-gap-12 { gap: var(--sp-12); }
.u-gap-16 { gap: var(--sp-16); }

.u-m-0 { margin: var(--sp-0); }
.u-m-1 { margin: var(--sp-1); }
.u-m-2 { margin: var(--sp-2); }
.u-m-3 { margin: var(--sp-3); }
.u-m-4 { margin: var(--sp-4); }
.u-m-5 { margin: var(--sp-5); }
.u-m-6 { margin: var(--sp-6); }
.u-m-8 { margin: var(--sp-8); }
.u-m-10 { margin: var(--sp-10); }
.u-m-12 { margin: var(--sp-12); }
.u-m-16 { margin: var(--sp-16); }

.u-mt-0 { margin-block-start: var(--sp-0); }
.u-mt-1 { margin-block-start: var(--sp-1); }
.u-mt-2 { margin-block-start: var(--sp-2); }
.u-mt-3 { margin-block-start: var(--sp-3); }
.u-mt-4 { margin-block-start: var(--sp-4); }
.u-mt-5 { margin-block-start: var(--sp-5); }
.u-mt-6 { margin-block-start: var(--sp-6); }
.u-mt-8 { margin-block-start: var(--sp-8); }
.u-mt-10 { margin-block-start: var(--sp-10); }
.u-mt-12 { margin-block-start: var(--sp-12); }
.u-mt-16 { margin-block-start: var(--sp-16); }

.u-mb-0 { margin-block-end: var(--sp-0); }
.u-mb-1 { margin-block-end: var(--sp-1); }
.u-mb-2 { margin-block-end: var(--sp-2); }
.u-mb-3 { margin-block-end: var(--sp-3); }
.u-mb-4 { margin-block-end: var(--sp-4); }
.u-mb-5 { margin-block-end: var(--sp-5); }
.u-mb-6 { margin-block-end: var(--sp-6); }
.u-mb-8 { margin-block-end: var(--sp-8); }
.u-mb-10 { margin-block-end: var(--sp-10); }
.u-mb-12 { margin-block-end: var(--sp-12); }
.u-mb-16 { margin-block-end: var(--sp-16); }

.u-p-0 { padding: var(--sp-0); }
.u-p-1 { padding: var(--sp-1); }
.u-p-2 { padding: var(--sp-2); }
.u-p-3 { padding: var(--sp-3); }
.u-p-4 { padding: var(--sp-4); }
.u-p-5 { padding: var(--sp-5); }
.u-p-6 { padding: var(--sp-6); }
.u-p-8 { padding: var(--sp-8); }
.u-p-10 { padding: var(--sp-10); }
.u-p-12 { padding: var(--sp-12); }
.u-p-16 { padding: var(--sp-16); }

.u-px-0 { padding-inline: var(--sp-0); }
.u-px-1 { padding-inline: var(--sp-1); }
.u-px-2 { padding-inline: var(--sp-2); }
.u-px-3 { padding-inline: var(--sp-3); }
.u-px-4 { padding-inline: var(--sp-4); }
.u-px-5 { padding-inline: var(--sp-5); }
.u-px-6 { padding-inline: var(--sp-6); }
.u-px-8 { padding-inline: var(--sp-8); }
.u-px-10 { padding-inline: var(--sp-10); }
.u-px-12 { padding-inline: var(--sp-12); }
.u-px-16 { padding-inline: var(--sp-16); }

.u-py-0 { padding-block: var(--sp-0); }
.u-py-1 { padding-block: var(--sp-1); }
.u-py-2 { padding-block: var(--sp-2); }
.u-py-3 { padding-block: var(--sp-3); }
.u-py-4 { padding-block: var(--sp-4); }
.u-py-5 { padding-block: var(--sp-5); }
.u-py-6 { padding-block: var(--sp-6); }
.u-py-8 { padding-block: var(--sp-8); }
.u-py-10 { padding-block: var(--sp-10); }
.u-py-12 { padding-block: var(--sp-12); }
.u-py-16 { padding-block: var(--sp-16); }

/* ==========================================================================
   5. SIZING
   ========================================================================== */
.u-w-full { width: 100%; }
.u-w-auto { width: auto; }
.u-h-full { height: 100%; }
.u-h-auto { height: auto; }
.u-max-w-full { max-width: 100%; }
.u-max-w-narrow { max-width: var(--container-narrow); }
.u-max-w-prose { max-width: 62ch; }
.u-min-h-screen { min-height: 100dvh; }

/* ==========================================================================
   6. TYPOGRAPHY
   ========================================================================== */
.u-text-left { text-align: left; }
.u-text-center { text-align: center; }
.u-text-right { text-align: right; }
.u-text-start { text-align: start; }
.u-text-end { text-align: end; }
.u-balance { text-wrap: balance; }
.u-pretty { text-wrap: pretty; }

.u-fs-2xs { font-size: var(--fs-2xs); }
.u-fs-xs { font-size: var(--fs-xs); }
.u-fs-sm { font-size: var(--fs-sm); }
.u-fs-md { font-size: var(--fs-md); }
.u-fs-lg { font-size: var(--fs-lg); }
.u-fs-xl { font-size: var(--fs-xl); }
.u-fs-2xl { font-size: var(--fs-2xl); }
.u-fs-3xl { font-size: var(--fs-3xl); }
.u-fs-4xl { font-size: var(--fs-4xl); }
.u-fs-5xl { font-size: var(--fs-5xl); }
.u-fs-6xl { font-size: var(--fs-6xl); }

.u-fw-400 { font-weight: var(--fw-regular); }
.u-fw-500 { font-weight: var(--fw-medium); }
.u-fw-600 { font-weight: var(--fw-semibold); }
.u-fw-700 { font-weight: var(--fw-bold); }
.u-fw-800 { font-weight: var(--fw-extrabold); }

.u-lh-tight { line-height: var(--lh-tight); }
.u-lh-snug { line-height: var(--lh-snug); }
.u-lh-normal { line-height: var(--lh-normal); }
.u-lh-relaxed { line-height: var(--lh-relaxed); }

.u-ls-tight { letter-spacing: var(--ls-tight); }
.u-ls-wide { letter-spacing: var(--ls-wide); }
.u-ls-wider { letter-spacing: var(--ls-wider); }

.u-font-sans { font-family: var(--font-sans); }
.u-font-display { font-family: var(--font-display); font-weight: var(--fw-regular); }
.u-font-mono { font-family: var(--font-mono); }
.u-tnum { font-variant-numeric: tabular-nums; }

.u-uppercase { text-transform: uppercase; }
.u-capitalize { text-transform: capitalize; }
.u-italic { font-style: italic; }
.u-underline { text-decoration: underline; text-underline-offset: 0.18em; }
.u-strike { text-decoration: line-through; }
.u-nowrap { white-space: nowrap; }
.u-break { overflow-wrap: anywhere; word-break: break-word; }

/* Eyebrow / label text: the small caps line used above headings */
.u-caps {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
}

.u-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.u-clamp-2,
.u-clamp-3 {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.u-clamp-2 { -webkit-line-clamp: 2; line-clamp: 2; }
.u-clamp-3 { -webkit-line-clamp: 3; line-clamp: 3; }

/* ==========================================================================
   7. COLOUR
   ========================================================================== */
.u-ink { color: var(--ink); }
.u-ink-2 { color: var(--ink-2); }
.u-ink-3 { color: var(--ink-3); }
.u-ink-inv { color: var(--ink-inv); }
.u-brand { color: var(--brand-text); }
.u-accent-2 { color: var(--accent-2); }
.u-success { color: var(--success); }
.u-warning { color: var(--warning); }
.u-danger { color: var(--danger); }
.u-info { color: var(--info); }
.u-star { color: var(--star); }
.u-inherit { color: inherit; }

.u-bg { background-color: var(--bg); }
.u-bg-soft { background-color: var(--bg-soft); }
.u-surface { background-color: var(--surface); }
.u-surface-2 { background-color: var(--surface-2); }
.u-surface-3 { background-color: var(--surface-3); }
.u-bg-brand { background-color: var(--brand); color: var(--brand-ink); }
.u-bg-brand-soft { background-color: var(--brand-soft); }
.u-bg-success-soft { background-color: var(--success-soft); color: var(--success-ink); }
.u-bg-warning-soft { background-color: var(--warning-soft); color: var(--warning-ink); }
.u-bg-danger-soft { background-color: var(--danger-soft); color: var(--danger-ink); }
.u-bg-info-soft { background-color: var(--info-soft); color: var(--info-ink); }
.u-bg-none { background: none; }

/* ==========================================================================
   8. BORDERS, RADII, SHADOWS
   ========================================================================== */
.u-border { border: 1px solid var(--line); }
.u-border-2 { border: 1px solid var(--line-2); }
.u-border-brand { border: 1px solid var(--brand); }
.u-border-t { border-top: 1px solid var(--line); }
.u-border-b { border-bottom: 1px solid var(--line); }
.u-border-s { border-inline-start: 1px solid var(--line); }
.u-border-e { border-inline-end: 1px solid var(--line); }
.u-border-0 { border: 0; }

.u-r-0 { border-radius: 0; }
.u-r-xs { border-radius: var(--r-xs); }
.u-r-sm { border-radius: var(--r-sm); }
.u-r-md { border-radius: var(--r-md); }
.u-r-lg { border-radius: var(--r-lg); }
.u-r-xl { border-radius: var(--r-xl); }
.u-r-2xl { border-radius: var(--r-2xl); }
.u-r-full { border-radius: var(--r-full); }

.u-sh-0 { box-shadow: none; }
.u-sh-1 { box-shadow: var(--sh-1); }
.u-sh-2 { box-shadow: var(--sh-2); }
.u-sh-3 { box-shadow: var(--sh-3); }
.u-sh-4 { box-shadow: var(--sh-4); }
.u-sh-brand { box-shadow: var(--sh-brand); }

/* ==========================================================================
   9. MEDIA + ASPECT RATIO
   ========================================================================== */
.u-ratio-1 { aspect-ratio: 1 / 1; }
.u-ratio-4-3 { aspect-ratio: 4 / 3; }
.u-ratio-3-4 { aspect-ratio: 3 / 4; }
.u-ratio-3-2 { aspect-ratio: 3 / 2; }
.u-ratio-2-3 { aspect-ratio: 2 / 3; }
.u-ratio-16-9 { aspect-ratio: 16 / 9; }
.u-ratio-21-9 { aspect-ratio: 21 / 9; }
.u-ratio-golden { aspect-ratio: 1.618 / 1; }

.u-cover { width: 100%; height: 100%; object-fit: cover; }
.u-contain { width: 100%; height: 100%; object-fit: contain; }
.u-center-img { object-position: center; }

/* ==========================================================================
   10. POSITION, OVERFLOW, LAYERS
   ========================================================================== */
.u-relative { position: relative; }
.u-absolute { position: absolute; }
.u-fixed { position: fixed; }
.u-sticky { position: sticky; }
.u-static { position: static; }
.u-inset-0 { inset: 0; }
.u-top-0 { top: 0; }
.u-bottom-0 { bottom: 0; }

.u-z-base { z-index: var(--z-base); }
.u-z-below { z-index: var(--z-below); }
.u-z-sticky { z-index: var(--z-sticky); }
.u-z-header { z-index: var(--z-header); }

.u-ov-hidden { overflow: hidden; }
.u-ov-clip { overflow: clip; }
/* `position: relative` is load-bearing, not decoration. A scroll wrapper that
   is statically positioned is not a containing block, so an absolutely
   positioned descendant — every .u-sr-only label inside a wide data table —
   escapes the clip, lands at its unclipped x, and drags the whole document
   into a horizontal scroll. Making the wrapper a containing block keeps the
   overflow where it belongs: inside the scroller. */
.u-ov-auto { position: relative; overflow: auto; }
.u-ov-x-auto,
.u-scroll-x { position: relative; overflow-x: auto; overflow-y: hidden; }
.u-ov-contain { overscroll-behavior: contain; }
.u-isolate { isolation: isolate; }

/* ==========================================================================
   11. INTERACTION
   ========================================================================== */
.u-pointer { cursor: pointer; }
.u-cursor-default { cursor: default; }
.u-select-none { user-select: none; -webkit-user-select: none; }
.u-pointer-none { pointer-events: none; }
.u-pointer-auto { pointer-events: auto; }
.u-tap { min-width: 44px; min-height: 44px; }
.u-touch-pan-x { touch-action: pan-x; }
.u-touch-manip { touch-action: manipulation; }

/* Rendering budget for long pages / far-down sections */
.u-cv-auto {
  content-visibility: auto;
  contain-intrinsic-size: auto 480px;
}

/* ==========================================================================
   12. EFFECTS
   ========================================================================== */
.u-glass {
  background-color: var(--glass-bg);
  border: 1px solid var(--glass-line);
}

@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .u-glass {
    -webkit-backdrop-filter: blur(var(--blur-2)) saturate(180%);
    backdrop-filter: blur(var(--blur-2)) saturate(180%);
  }
}

.u-gradient-text {
  background-image: var(--grad-brand);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

/* Fine film grain — sits above the element, never intercepts pointer events */
.u-noise {
  position: relative;
  isolation: isolate;
}

.u-noise::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  pointer-events: none;
  opacity: var(--noise-opacity, .35);
  mix-blend-mode: soft-light;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

.u-fade-b {
  -webkit-mask-image: linear-gradient(180deg, black 70%, transparent 100%);
  mask-image: linear-gradient(180deg, black 70%, transparent 100%);
}

.u-fade-e {
  -webkit-mask-image: linear-gradient(90deg, black 85%, transparent 100%);
  mask-image: linear-gradient(90deg, black 85%, transparent 100%);
}

.u-opacity-0 { opacity: 0; }
.u-opacity-60 { opacity: .6; }
.u-opacity-100 { opacity: 1; }

/* ==========================================================================
   13. RESPONSIVE VISIBILITY
     .u-hide-sm   hidden below 768
     .u-show-sm   hidden from 768 up   (mobile only)
     .u-hide-lg   hidden from 1024 up
     .u-show-lg   hidden below 1024    (desktop only)
   ========================================================================== */
@media (max-width: 767.98px) {
  .u-hide-sm { display: none !important; }
  .u-text-center-sm { text-align: center; }
  .u-full-sm { width: 100% !important; }
}

@media (min-width: 768px) {
  .u-show-sm { display: none !important; }
}

@media (max-width: 1023.98px) {
  .u-show-lg { display: none !important; }
}

@media (min-width: 1024px) {
  .u-hide-lg { display: none !important; }
}

/* ==========================================================================
   14. SCROLL REVEAL

   The other half of the [data-reveal] system in app.js. app.js observes every
   [data-reveal], adds .is-revealed when it scrolls into view, staggers siblings
   inside a [data-reveal-group], and honours [data-reveal-delay] — but until now
   no stylesheet described what "revealed" looks like, so the whole mechanism
   ran on 25 elements of home-v2.html and animated nothing.

   Three rules make this safe rather than a way to hide content:

     · everything is scoped to html.has-reveal, which app.js sets only once the
       observer is actually installed — not a generic readiness flag. With
       JavaScript off, or if boot throws first, no element is ever hidden. app.js
       also force-reveals any straggler after 6s, so nothing can stay at
       opacity 0 because an observer callback never arrived.
     · only opacity and transform animate, so a reveal can never move layout
       (no CLS) and stays on the compositor.
     · reduced motion and print both show everything immediately. app.js already
       skips straight to .is-revealed when the media query matches; the rule
       below is the belt to that braces, in case markup is injected later.
   ========================================================================== */

html.has-reveal [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 14px, 0);
  transition:
    opacity var(--dur-4) var(--ease),
    transform var(--dur-4) var(--ease);
}
html.has-reveal [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

/* Directional variants — [data-reveal="left"] and friends. */
html.is-ready [data-reveal="left"]  { transform: translate3d(-18px, 0, 0); }
html.is-ready [data-reveal="right"] { transform: translate3d(18px, 0, 0); }
html.is-ready [data-reveal="fade"]  { transform: none; }
html.is-ready [data-reveal="scale"] { transform: scale(.97); }

/* Nothing hides when motion is unwelcome, when the page is printed, or when a
   forced-colours mode is doing its own thing. */
@media (prefers-reduced-motion: reduce) {
  html.has-reveal [data-reveal] { opacity: 1; transform: none; transition: none; }
}
@media print {
  html.has-reveal [data-reveal] { opacity: 1; transform: none; transition: none; }
}

/* ==========================================================================
   15. MOTION SAFETY
   Utilities that move must opt out under prefers-reduced-motion.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .scroll-x { scroll-behavior: auto; }
  .u-noise::after { display: none; }
}
