/*
 * Alrightmade motion engine — CSS half.
 *
 * Spec: deliverables/motion-system-2026-09-23/styleguide.html
 * Tokens live in main.css :root. Markup opts in with data attributes only:
 *   data-motion-entry="rise|mask|settle|wipe"  first-screen entry (page entry, P1):
 *     pure CSS, plays from first paint, never waits for JavaScript;
 *   data-motion="rise|mask|wipe|settle|rule|rule-y|count|radiate"  scroll
 *     reveal, played once by motion.js; plus data-motion-group,
 *     data-stagger, data-stagger-max, data-motion-delay.
 * motion.js adds .is-in to play an element and .is-done once it has settled;
 * .is-done drops the animation so fill-mode no longer pins transforms and
 * hover states work normally afterwards.
 *
 * Content is visible by default. html.motion-ready (set by the <head> boot
 * guard before first paint, only when motion is allowed) enables the entry
 * animations. Scroll-reveal targets are only hidden once motion.js has booted
 * and added html.motion-armed, after settling whatever was already on screen,
 * so nothing is ever hidden while the page waits for its scripts.
 */

@media (prefers-reduced-motion: no-preference) {
  /* Entry: backwards fill holds the first keyframe through the delay and pins
     nothing afterwards. */
  .motion-ready [data-motion-entry] { animation-duration: var(--duration-reveal); animation-timing-function: var(--ease-out); animation-fill-mode: backwards; animation-delay: var(--motion-delay, 0ms); }
  .motion-ready [data-motion-entry="rise"] { animation-name: am-rise; }
  .motion-ready [data-motion-entry="mask"] { animation-name: am-mask; animation-duration: var(--duration-editorial); }
  .motion-ready [data-motion-entry="settle"] { animation-name: am-settle; animation-duration: var(--duration-hero); }
  .motion-ready [data-motion-entry="wipe"] { animation-name: am-wipe; animation-duration: var(--duration-editorial); animation-timing-function: var(--ease-panel); }

  .motion-armed [data-motion]:not(.is-in) { opacity: 0; }
  .motion-armed [data-motion="rule"]:not(.is-in) { opacity: 1; transform: scaleX(0); }
  .motion-armed [data-motion="rule-y"]:not(.is-in) { opacity: 1; transform: scaleY(0); }
  .motion-armed [data-motion="settle"]:not(.is-in) { opacity: 1; transform: scale(var(--settle-from)); }
  .motion-armed [data-motion="radiate"]:not(.is-in) { opacity: 1; clip-path: circle(0% at var(--motion-origin, 50% 50%)); }

  [data-motion].is-in:not(.is-done) {
    animation-duration: var(--duration-reveal);
    animation-timing-function: var(--ease-out);
    animation-fill-mode: both;
    animation-delay: var(--motion-delay, 0ms);
  }
  [data-motion="rise"].is-in:not(.is-done),
  [data-motion="count"].is-in:not(.is-done) { animation-name: am-rise; }
  [data-motion="mask"].is-in:not(.is-done) { animation-name: am-mask; animation-duration: var(--duration-editorial); }
  [data-motion="wipe"].is-in:not(.is-done) { animation-name: am-wipe; animation-duration: var(--duration-editorial); animation-timing-function: var(--ease-panel); }
  [data-motion="wipe"].is-in:not(.is-done) > :is(img, picture, video) { animation: am-zoom var(--duration-editorial) var(--ease-out) var(--motion-delay, 0ms) both; }
  [data-motion="rule"].is-in:not(.is-done) { animation-name: am-rule-x; animation-duration: var(--duration-editorial); animation-timing-function: var(--ease-panel); transform-origin: left center; }
  [data-motion="rule-y"].is-in:not(.is-done) { animation-name: am-rule-y; animation-duration: var(--duration-editorial); animation-timing-function: var(--ease-panel); transform-origin: center top; }
  [data-motion="settle"].is-in:not(.is-done) { animation-name: am-settle; animation-duration: var(--duration-hero); }
  /* L2: a map opens outward from its origin (e.g. the Guangdong hub). */
  [data-motion="radiate"].is-in:not(.is-done) { animation-name: am-radiate; animation-duration: var(--duration-hero); animation-timing-function: var(--ease-standard); }
}

[data-motion="count"] { font-variant-numeric: tabular-nums; }

@keyframes am-rise { from { opacity: 0; transform: translate3d(0, var(--reveal-distance), 0); } to { opacity: 1; transform: none; } }
/* The mask overhangs the box by .1em / .25em so descenders (e.g. Roboto
   Condensed at .92 line-height on /ru/) are never cut and never pop. */
@keyframes am-mask { from { opacity: 1; transform: translateY(100%); clip-path: inset(-.1em -.1em 100% -.1em); } to { opacity: 1; transform: none; clip-path: inset(-.1em -.1em -.25em -.1em); } }
@keyframes am-wipe { from { opacity: 1; clip-path: inset(0 100% 0 0); } to { opacity: 1; clip-path: inset(0); } }
@keyframes am-zoom { from { transform: scale(1.06); } to { transform: none; } }
@keyframes am-rule-x { from { transform: scaleX(0); } to { transform: none; } }
@keyframes am-rule-y { from { transform: scaleY(0); } to { transform: none; } }
@keyframes am-settle { from { opacity: 1; transform: scale(var(--settle-from)); } to { opacity: 1; transform: none; } }
@keyframes am-radiate { from { opacity: 1; clip-path: circle(0% at var(--motion-origin, 50% 50%)); } to { opacity: 1; clip-path: circle(110% at var(--motion-origin, 50% 50%)); } }

/* ==========================================================================
   Components (styleguide §04). Values stay on tokens; open is always
   --duration-state and close --duration-ui, so closing is faster.
   ========================================================================== */

/* B1 — ink sweep. The hover colour is a ::before layer that sweeps in from
   the left and out to the right; buttons keep their own background. Buttons
   whose hover is a transparent fill (.button-light, page heroes, the
   customization ticket) do not sweep. */
.button::before { position: absolute; inset: 0; z-index: -1; background: var(--button-sweep, var(--marine)); content: ""; transform: scaleX(0); transform-origin: right center; transition: transform var(--duration-state) var(--ease-panel); }
.button:hover::before, .button:focus-visible::before { transform: scaleX(1); transform-origin: left center; }
.button-light::before, .page-hero .button::before, .customize-ticket__add::before { content: none; }
/* State colours win: an added-to-inquiry (success) or disabled button never sweeps. */
.button:is(.is-added, :disabled, [aria-disabled="true"])::before { content: none; }

/* C1 — card frame: a 2px marine rule draws along the image's bottom edge
   while the image scales to 1.03 (main.css). No lift, no shadow. */
.product-image::after, .fabric-card__image-wrap::after, .journal--trends .journal-thumb::after { position: absolute; right: 0; bottom: 0; left: 0; z-index: 1; height: 2px; background: var(--marine); content: ""; pointer-events: none; transform: scaleX(0); transform-origin: left center; transition: transform var(--duration-state) var(--ease-panel); }
.product-item:hover .product-image::after, .product-image:focus-visible::after, .fabric-card:hover .fabric-card__image-wrap::after, .journal--trends .trend-row:hover .journal-thumb::after { transform: scaleX(1); }

@media (prefers-reduced-motion: no-preference) {
  /* N1 — mega panel items drop in after the clip opens (desktop only). */
  @media (min-width: 901px) {
    html:not(.js-mega) .has-mega:hover .mega-links > *, .has-mega.is-open .mega-links > *, .has-mega:focus-within .mega-links > * { animation: am-drop var(--duration-state) var(--ease-out) both; animation-delay: calc(80ms + var(--motion-index, 0) * 30ms); }
  }

  /* N2 — mobile menu entries rise in behind the sliding panel. */
  @media (max-width: 900px) {
    .primary-nav.is-open > * { animation: am-drop-far var(--duration-state) var(--ease-out) both; animation-delay: calc(120ms + var(--motion-index, 0) * 40ms); }
  }

  /* X1 — native <details> grow and shrink where ::details-content and
     interpolate-size exist; elsewhere they open instantly as before. */
  .buyer-faq__item, .accordion details { interpolate-size: allow-keywords; }
  .buyer-faq__item::details-content, .accordion details::details-content { block-size: 0; overflow-y: clip; transition: block-size var(--duration-ui) var(--ease-exit), content-visibility var(--duration-ui) allow-discrete; }
  .buyer-faq__item[open]::details-content, .accordion details[open]::details-content { block-size: auto; transition: block-size var(--duration-state) var(--ease-standard), content-visibility var(--duration-state) allow-discrete; }

  /* N3 — inquiry drawer slides in from the right; N4 — dialogs rise and
     scale from .98. Entry uses @starting-style; exit keeps the dialog in the
     top layer only where the overlay transition is supported. */
  .inquiry-cart-dialog[open] { transform: none; transition: transform var(--duration-state) var(--ease-panel); }
  .inquiry-cart-dialog[open]::backdrop { opacity: 1; transition: opacity var(--duration-state) var(--ease-standard); }
  .enquiry-dialog[open], .fabric-selection-dialog[open], .product-lightbox[open] { opacity: 1; transform: none; transition: opacity var(--duration-state) var(--ease-out), transform var(--duration-state) var(--ease-out); }
  .enquiry-dialog[open]::backdrop, .fabric-selection-dialog[open]::backdrop, .product-lightbox[open]::backdrop { opacity: 1; transition: opacity var(--duration-state) var(--ease-standard); }
  @starting-style {
    .inquiry-cart-dialog[open] { transform: translateX(100%); }
    .enquiry-dialog[open], .fabric-selection-dialog[open], .product-lightbox[open] { opacity: 0; transform: translateY(12px) scale(.98); }
    .inquiry-cart-dialog[open]::backdrop, .enquiry-dialog[open]::backdrop, .fabric-selection-dialog[open]::backdrop, .product-lightbox[open]::backdrop { opacity: 0; }
  }
  @supports (overlay: auto) {
    .inquiry-cart-dialog { transform: translateX(100%); transition: transform var(--duration-ui) var(--ease-exit), overlay var(--duration-ui) allow-discrete, display var(--duration-ui) allow-discrete; }
    .enquiry-dialog, .fabric-selection-dialog, .product-lightbox { opacity: 0; transform: translateY(12px) scale(.98); transition: opacity var(--duration-ui) var(--ease-exit), transform var(--duration-ui) var(--ease-exit), overlay var(--duration-ui) allow-discrete, display var(--duration-ui) allow-discrete; }
    .inquiry-cart-dialog::backdrop, .enquiry-dialog::backdrop, .fabric-selection-dialog::backdrop, .product-lightbox::backdrop { opacity: 0; transition: opacity var(--duration-ui) var(--ease-exit), overlay var(--duration-ui) allow-discrete, display var(--duration-ui) allow-discrete; }
    .inquiry-cart-dialog[open] { transition: transform var(--duration-state) var(--ease-panel), overlay var(--duration-state) allow-discrete, display var(--duration-state) allow-discrete; }
    .enquiry-dialog[open], .fabric-selection-dialog[open], .product-lightbox[open] { transition: opacity var(--duration-state) var(--ease-out), transform var(--duration-state) var(--ease-out), overlay var(--duration-state) allow-discrete, display var(--duration-state) allow-discrete; }
  }
}

@media (prefers-reduced-motion: no-preference) {
  /* Customization: a changed estimate fades in; it is never counted. */
  .customize-ticket [data-ticket-estimated-total].is-updated { animation: am-fade var(--duration-ui) var(--ease-standard); }
  /* Product lightbox: each newly loaded image fades in. */
  [data-lightbox-image].is-arriving { animation: am-fade var(--duration-ui) var(--ease-standard); }
}

/* S2 — the sticky phone header firms its bottom rule once the page scrolls. */
@media (max-width: 900px) {
  .site-header { transition: border-color var(--duration-ui) var(--ease-standard); }
  .site-header.is-scrolled { border-bottom-color: var(--line-strong); }
}

@keyframes am-fade { from { opacity: .3; } to { opacity: 1; } }
@keyframes am-drop { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes am-drop-far { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }

/* ==========================================================================
   Home (upgrade-plan §5, P2). Markup lives in front-page.php and its parts.
   ========================================================================== */
@media (prefers-reduced-motion: no-preference) {
  /* Spec band: its top edge draws across while the tape under it pulls out
     (data-motion-entry="wipe", same delay and length), then the four cells rise in. */
  .motion-ready .hero-spec-band { position: relative; border-top-color: transparent; }
  .motion-ready .hero-spec-band::before { position: absolute; top: -1px; right: 0; left: 0; height: 1px; background: currentColor; content: ""; opacity: .25; transform-origin: left center; animation: am-rule-x var(--duration-editorial) var(--ease-panel) 320ms backwards; }

  /* S1: on desktop the hero photo drifts down slower than the page as it
     leaves. It animates translate, so it composes with the entry settle
     (transform). Browsers without view timelines keep it still. */
  @supports (animation-timeline: view()) {
    @media (min-width: 901px) {
      /* overflow: clip (not hidden) so .hero is not a scroll container and
         view() tracks the document scroll. */
      .motion-ready .hero { overflow: clip; }
      .motion-ready .hero > img[data-motion-entry] { animation: am-settle var(--duration-hero) var(--ease-out) backwards, am-drift linear both; animation-timeline: auto, view(); animation-range: normal, exit 0% exit 100%; }
    }
  }
}
/* ==========================================================================
   Service and catalog pages (P3).
   ========================================================================== */
@media (prefers-reduced-motion: no-preference) {
  /* S1: the page-hero diagram (a ::before background) drifts as the hero
     leaves; the header supplies a named view timeline for its pseudo. */
  @supports (animation-timeline: view()) {
    @media (min-width: 901px) {
      .motion-ready :is(.diagram-hero, .customize-hero) { view-timeline: --page-hero block; }
      .motion-ready :is(.diagram-hero, .customize-hero)::before { animation: am-drift linear both; animation-timeline: --page-hero; animation-range: exit 0% exit 100%; }
    }
  }
}

/* ==========================================================================
   Content pages and page transitions (P4).
   ========================================================================== */
.reading-progress { display: none; }
@media (prefers-reduced-motion: no-preference) {
  /* Page-entry convention: heroes without authored data-motion-entry get the
     default entry. On these text heroes the H1 or the lead p is the LCP, so
     both stay fully visible from first paint and only settle into place
     (am-lift: position only, no opacity or clip); the small eyebrow and
     breadcrumb fade up. Browsers without :has() keep these heroes still. */
  .motion-ready :is(.page-hero, .policy-hero, .about-guangzhou--intro, .not-found, .confirmation-page):not(:has([data-motion-entry])) h1 { animation: am-lift var(--duration-reveal) var(--ease-out) backwards; }
  .motion-ready :is(.page-hero, .policy-hero, .about-guangzhou--intro, .not-found, .confirmation-page):not(:has([data-motion-entry])) :is(.breadcrumbs, .eyebrow) { animation: am-rise var(--duration-reveal) var(--ease-out) backwards; }
  .motion-ready .page-hero:not(:has([data-motion-entry])) .page-hero-grid > :not(:first-child),
  .motion-ready .policy-hero:not(:has([data-motion-entry])) h1 ~ * { animation: am-lift var(--duration-reveal) var(--ease-out) backwards; }

  /* S3: a 2px reading-progress rule along the top of Insight articles. */
  @supports (animation-timeline: scroll()) {
    .reading-progress { position: fixed; top: 0; right: 0; left: 0; z-index: 60; display: block; height: 2px; background: var(--marine); pointer-events: none; transform-origin: left center; animation: am-progress linear both; animation-timeline: scroll(root); }
  }

  /* P2: same-origin navigations crossfade. The header is deliberately not
     named: on desktop it is not sticky, so a named group would fly between
     scroll positions (links below the fold, back navigation, open dialogs).
     The plain crossfade leaves identical pixels, e.g. an unchanged header,
     exactly in place. */
  @view-transition { navigation: auto; }
  ::view-transition-old(root), ::view-transition-new(root) { animation-duration: var(--duration-ui); animation-timing-function: var(--ease-standard); }
}
@keyframes am-progress { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes am-lift { from { transform: translate3d(0, var(--reveal-distance), 0); } to { transform: none; } }

@keyframes am-drift { from { translate: 0 0; } to { translate: 0 12%; } }

@media print {
  [data-motion], [data-motion="wipe"] > :is(img, picture, video), [data-motion-entry],
  :is(.page-hero, .policy-hero, .about-guangzhou--intro, .not-found, .confirmation-page) * { opacity: 1 !important; transform: none !important; translate: none !important; clip-path: none !important; animation: none !important; }
  .reading-progress { display: none !important; }
}
