/* =========================================================
   HassounCube, dimension layer
   The site-wide motion and detail language, loaded on every page after
   the page's own stylesheets. One vocabulary, used everywhere:

   - Depth, not slide. Things arrive out of the page's depth: a slight
     tilt back on the X axis, a little distance in Z, settling flat. The
     same move for sections, cards, headings and the menu.
   - The bracket. Four short corner marks, the cube's edges reduced to
     registration marks. They lock onto whatever you point at (cards,
     tiles, panels) instead of a light following the pointer. The scene
     cube's faces carry the same marks, so the UI and the structure behind
     it share one drawing.
   - The seam. Sections are separated by a hairline that draws itself
     from the centre out as it comes into view.
   - No glow anywhere. Every effect here is line, tone, depth or timing.

   Everything that moves sits behind prefers-reduced-motion: with motion
   reduced, the page is the same page, already settled.
   ========================================================= */

:root {
  --ease-spring: cubic-bezier(0.34, 1.32, 0.36, 1);
  --ease-depth: cubic-bezier(0.16, 1, 0.3, 1);
  --dur-xl: 900ms;
  --bracket: rgba(214, 188, 255, 0.62);
  --bracket-len: 14px;
}

/* =========================================================
   1. Entrances: headings rise out of the page plane
   ========================================================= */
@keyframes dimRise {
  from { opacity: 0; transform: perspective(900px) translate3d(0, 0.42em, -40px) rotateX(-38deg); }
  to   { opacity: 1; transform: none; }
}
@keyframes markTurn {
  from { opacity: 0; transform: rotate(-135deg) scale(0.2); }
  to   { opacity: 0.85; transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
  .hero-copy h1 {
    transform-origin: 50% 100%;
    animation-name: dimRise;
    animation-duration: var(--dur-xl);
    animation-timing-function: var(--ease-depth);
  }
  /* Home: the two headline lines rise one after the other. The h1 itself
     then only carries the stagger origin. */
  .hero-copy h1:has(> span) { animation: none; }
  .hero-copy h1 > span {
    display: inline-block;
    transform-origin: 50% 100%;
    animation: dimRise var(--dur-xl) var(--ease-depth) calc(var(--entrance) + 90ms) both;
  }
  .hero-copy h1 > span + br + span { animation-delay: calc(var(--entrance) + 200ms); }
  /* The accent line keeps its slow sheen too. Both animations are listed
     in one declaration: as separate rules the sheen replaced the entrance,
     and the line appeared without one. */
  .hero-copy h1 > .accent {
    animation:
      dimRise var(--dur-xl) var(--ease-depth) calc(var(--entrance) + 200ms) both,
      accentSheen 9s ease-in-out calc(var(--entrance) + 1.1s) infinite alternate;
  }

  /* The eyebrow's cube mark turns into place as its label arrives. */
  .hero-copy .eyebrow::before {
    animation: markTurn 800ms var(--ease-depth) calc(var(--entrance) + 120ms) both;
  }

  /* Entrance elements whose final keyframe equals their own style hand
     control back after playing (fill backwards, not both), so hover and
     press states can move them again afterwards. */
  .hero-actions,
  .product-hero-actions .btn { animation-fill-mode: backwards; }

  /* Product header: back link and badges step in under the header. */
  .product-header .back-link,
  .product-header .product-badge {
    animation: riseIn 600ms var(--ease) calc(var(--entrance) + 60ms) backwards;
  }
  .product-header .product-badge:nth-child(1) { animation-delay: calc(var(--entrance) + 140ms); }
  .product-header .product-badge:nth-child(2) { animation-delay: calc(var(--entrance) + 200ms); }
}

/* After the first page of a session the header is already there: it does
   not drop in again on every navigation (the view transition below keeps
   it in place between pages). */
html.no-loader .site-header { animation: none; }

/* =========================================================
   2. Scroll reveals: out of depth, in batches
   js/animations.js sets --stagger on elements that arrive together.
   ========================================================= */
@media (prefers-reduced-motion: no-preference) {
  html.reveal-on [data-reveal] {
    transform: perspective(1400px) translate3d(0, 28px, -50px) rotateX(7deg);
    transform-origin: 50% 0%;
    transition:
      opacity 700ms var(--ease) var(--stagger, 0ms),
      transform var(--dur-xl) var(--ease-depth) var(--stagger, 0ms),
      border-color var(--dur-m) var(--ease),
      background var(--dur-m) var(--ease),
      box-shadow var(--dur-m) var(--ease);
  }
  html.reveal-on [data-reveal].is-revealed { transform: none; }
  /* The footer is a list, not a pane: it rises without the tilt. */
  html.reveal-on .site-footer [data-reveal] { transform: translate3d(0, 16px, 0); }
  html.reveal-on .site-footer [data-reveal].is-revealed { transform: none; }

  /* Section titles inside a revealing block wipe up out of a slit, a beat
     after their block starts to arrive. */
  html.reveal-on [data-reveal] .section-title {
    clip-path: inset(0 -4% 100% -4%);
    transform: translateY(0.35em);
    transition: clip-path 900ms var(--ease-depth) calc(var(--stagger, 0ms) + 80ms),
                transform 900ms var(--ease-depth) calc(var(--stagger, 0ms) + 80ms);
  }
  html.reveal-on [data-reveal].is-revealed .section-title {
    clip-path: inset(-12% -4% -22% -4%);
    transform: none;
  }
  html.reveal-on [data-reveal] .eyebrow::before {
    opacity: 0;
    transform: rotate(-135deg) scale(0.2);
    transition: opacity 500ms var(--ease) calc(var(--stagger, 0ms) + 120ms),
                transform 800ms var(--ease-depth) calc(var(--stagger, 0ms) + 120ms);
  }
  html.reveal-on [data-reveal].is-revealed .eyebrow::before { opacity: 0.85; transform: none; }

  /* Products overview: media and text converge from opposite sides, as two
     planes meeting. Even rows mirror it. */
  html.reveal-on .overview-row[data-reveal] .overview-media,
  html.reveal-on .overview-row[data-reveal] .overview-body {
    transition: transform 1000ms var(--ease-depth) calc(var(--stagger, 0ms) + 60ms),
                opacity 700ms var(--ease) calc(var(--stagger, 0ms) + 60ms);
  }
  html.reveal-on .overview-row[data-reveal] .overview-media { transform: perspective(1200px) translateX(-26px) rotateY(10deg); opacity: 0; }
  html.reveal-on .overview-row[data-reveal] .overview-body  { transform: translateX(22px); opacity: 0; }
  html.reveal-on .overview-row[data-reveal]:nth-child(even) .overview-media { transform: perspective(1200px) translateX(26px) rotateY(-10deg); }
  html.reveal-on .overview-row[data-reveal]:nth-child(even) .overview-body  { transform: translateX(-22px); }
  html.reveal-on .overview-row[data-reveal].is-revealed .overview-media,
  html.reveal-on .overview-row[data-reveal].is-revealed .overview-body { transform: none; opacity: 1; }

  /* Screenshot galleries: the phones fan in from depth, outer ones turned
     toward the centre, and settle side by side. */
  html.reveal-on .gallery-grid[data-reveal] { transform: none; }
  html.reveal-on .gallery-grid[data-reveal] .gallery-item {
    opacity: 0;
    transform: perspective(1600px) translate3d(0, 40px, -120px) rotateY(var(--fan, 0deg));
    transition: opacity 700ms var(--ease) var(--d, 0ms),
                transform 1100ms var(--ease-depth) var(--d, 0ms);
  }
  html.reveal-on .gallery-grid[data-reveal] .gallery-item:nth-child(1) { --fan: 22deg;  --d: 0ms; }
  html.reveal-on .gallery-grid[data-reveal] .gallery-item:nth-child(2) { --fan: 0deg;   --d: 110ms; }
  html.reveal-on .gallery-grid[data-reveal] .gallery-item:nth-child(3) { --fan: -22deg; --d: 220ms; }
  html.reveal-on .gallery-grid[data-reveal].is-revealed .gallery-item { opacity: 1; transform: none; }

  /* Install steps cascade in once their panel arrives. */
  html.reveal-on .pwa-section[data-reveal] .pwa-step {
    opacity: 0;
    transform: translateX(-14px);
    transition: opacity 500ms var(--ease) var(--d, 0ms), transform 700ms var(--ease-depth) var(--d, 0ms),
                background var(--dur-s) var(--ease);
  }
  html[dir="rtl"].reveal-on .pwa-section[data-reveal] .pwa-step { transform: translateX(14px); }
  html.reveal-on .pwa-section[data-reveal].is-revealed .pwa-step { opacity: 1; transform: none; }
  .pwa-step:nth-child(1) { --d: 260ms; } .pwa-step:nth-child(2) { --d: 320ms; }
  .pwa-step:nth-child(3) { --d: 380ms; } .pwa-step:nth-child(4) { --d: 440ms; }
  .pwa-step:nth-child(5) { --d: 500ms; } .pwa-step:nth-child(6) { --d: 560ms; }
  .pwa-step:nth-child(7) { --d: 620ms; } .pwa-step:nth-child(8) { --d: 680ms; }
}

/* =========================================================
   3. The seam between sections
   js/animations.js adds .in-view as each section reaches the screen.
   ========================================================= */
main > section + section { position: relative; }
main > section + section::before {
  content: "";
  position: absolute;
  top: 0;
  inset-inline: var(--gutter);
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--line-strong) 20%, var(--line-strong) 80%, transparent);
  transform-origin: 50% 50%;
}
@media (prefers-reduced-motion: no-preference) {
  html.reveal-on main > section + section::before {
    transform: scaleX(0);
    transition: transform 1400ms var(--ease-depth);
  }
  html.reveal-on main > section + section.in-view::before { transform: scaleX(1); }
}

/* =========================================================
   4. Brackets: the hover language for every surface
   ========================================================= */
.product-card,
.overview-row,
.feature-card,
.platform-badge,
.gallery-item { position: relative; isolation: isolate; }

.product-card::after,
.overview-row::after,
.feature-card::after,
.platform-badge::after,
.gallery-item::after {
  content: "";
  position: absolute;
  inset: 10px;
  z-index: 5;
  pointer-events: none;
  border-radius: calc(var(--radius-l) - 10px);
  background:
    linear-gradient(var(--bracket), var(--bracket)) top left / var(--bracket-len) 1.5px,
    linear-gradient(var(--bracket), var(--bracket)) top left / 1.5px var(--bracket-len),
    linear-gradient(var(--bracket), var(--bracket)) top right / var(--bracket-len) 1.5px,
    linear-gradient(var(--bracket), var(--bracket)) top right / 1.5px var(--bracket-len),
    linear-gradient(var(--bracket), var(--bracket)) bottom left / var(--bracket-len) 1.5px,
    linear-gradient(var(--bracket), var(--bracket)) bottom left / 1.5px var(--bracket-len),
    linear-gradient(var(--bracket), var(--bracket)) bottom right / var(--bracket-len) 1.5px,
    linear-gradient(var(--bracket), var(--bracket)) bottom right / 1.5px var(--bracket-len);
  background-repeat: no-repeat;
  opacity: 0;
  transform: scale(1.035);
  transition: opacity 260ms var(--ease), transform 520ms var(--ease-spring);
}
.gallery-item::after { inset: 16px; border-radius: 22px; }
.platform-badge::after { inset: 8px; border-radius: 8px; }

.product-card:hover::after,
.product-card:focus-visible::after,
.overview-row:hover::after,
.overview-row:focus-within::after,
.feature-card:hover::after,
.platform-badge:hover::after,
.gallery-item:hover::after { opacity: 1; transform: none; }

/* One hover timing for every surface (cards, rows, tiles, badges, panels):
   tint, border and shadow change over --dur-m, before and after their
   reveal. Controls (buttons, pills, links) keep the quicker --dur-s. */
.product-card,
.overview-row,
.feature-card,
.platform-badge {
  transition: border-color var(--dur-m) var(--ease), background var(--dur-m) var(--ease),
              box-shadow var(--dur-m) var(--ease);
}

/* Touch: a press locks the brackets on for as long as the finger is down. */
@media (hover: none) {
  .product-card:active::after,
  .overview-row:active::after,
  .feature-card:active::after,
  .platform-badge:active::after,
  .gallery-item:active::after { opacity: 1; transform: none; transition-duration: 120ms; }
}

/* Index marks on the product lists: 01 to 05, set in the display face
   like a plate number. Pure CSS counters, nothing added to the content,
   hidden from assistive technology by being generated. */
.product-grid,
.overview-list { counter-reset: hc-item; }
.product-card,
.overview-row { counter-increment: hc-item; }
.product-card-media::before,
.overview-media::before {
  content: counter(hc-item, decimal-leading-zero);
  position: absolute;
  top: 14px;
  inset-inline-start: 16px;
  z-index: 2;
  font-family: var(--font-display);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  color: var(--text-faint);
  transition: color var(--dur-m) var(--ease), letter-spacing var(--dur-m) var(--ease);
}
.product-card:hover .product-card-media::before,
.overview-row:hover .overview-media::before { color: var(--am-100); }

/* The logo moves a little forward inside its frame on hover: depth, not
   lift. The card itself stays put. */
@media (prefers-reduced-motion: no-preference) {
  .identity-logo { transition: transform 900ms var(--ease-depth); }
  .product-card:hover .identity-logo,
  .overview-row:hover .identity-logo { transform: scale(1.045); }

  /* Icons turn once through their X axis when their card is pointed at. */
  .feature-icon svg,
  .platform-badge-icon svg { transition: transform 800ms var(--ease-depth); }
  .feature-card:hover .feature-icon svg,
  .platform-badge:hover .platform-badge-icon svg { transform: perspective(120px) rotateX(360deg); }

  /* Gallery phones tip toward the viewer on hover. */
  .gallery-item { transition: transform 700ms var(--ease-depth); }
  html.reveal-on .gallery-grid[data-reveal].is-revealed .gallery-item:hover,
  .gallery-item:hover { transform: perspective(1200px) rotateX(4deg) translateY(-6px); transition-delay: 0ms; }
}

/* =========================================================
   5. Controls
   ========================================================= */
/* A physical press: the key travels a pixel into the page and its shadow
   tightens. Instant on the way down, a little spring on release. */
.btn { transition: box-shadow var(--dur-s) var(--ease), background var(--dur-s) var(--ease),
                   border-color var(--dur-s) var(--ease), color var(--dur-s) var(--ease),
                   filter var(--dur-s) var(--ease), transform 420ms var(--ease-spring); }
.btn:active { transform: translateY(1px) scale(0.975); transition-duration: 90ms; }
.btn-primary:active { box-shadow: inset 0 1px 0 rgba(255,255,255,0.16), 0 0 0 rgba(0,0,0,0); }

/* Arrow icons inside buttons step forward on hover, like the card links. */
.btn svg { transition: transform var(--dur-m) var(--ease-spring); }
.btn:hover svg { transform: translateX(3px); }
[dir="rtl"] .btn:hover svg { transform: scaleX(-1) translateX(3px); }

/* The brand mark reveals that it is a cube: a second square slides out
   behind the first, and the pair reads as a wireframe in depth. */
.brand-mark::before {
  content: "";
  position: absolute;
  inset: 8px;
  border: 1.5px solid rgba(255, 255, 255, 0.45);
  border-radius: 3px;
  opacity: 0;
  transition: transform var(--dur-m) var(--ease-spring), opacity var(--dur-s) var(--ease);
}
.brand:hover .brand-mark::before { opacity: 1; transform: translate(3px, -3px); }

/* Card links: the same 3px spring step as every other arrow. */
.product-card-link svg { transition: transform var(--dur-m) var(--ease-spring); }
.product-card:hover .product-card-link svg { transform: translateX(3px); }
[dir="rtl"] .product-card:hover .product-card-link svg { transform: scaleX(-1) translateX(3px); }

/* Back link arrow steps back on hover. */
.back-link svg { transition: transform var(--dur-m) var(--ease-spring); }
.back-link:hover svg { transform: translateX(-3px); }
[dir="rtl"] .back-link:hover svg { transform: scaleX(-1) translateX(-3px); }

/* Footer links: a short tick draws in at the reading-start edge. */
.footer-col a { position: relative; transition: color var(--dur-s) var(--ease), transform var(--dur-m) var(--ease-spring); }
.footer-col a::before {
  content: "";
  position: absolute;
  top: 50%;
  inset-inline-start: -14px;
  width: 8px;
  height: 1.5px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right center;
  transition: transform var(--dur-m) var(--ease-spring);
}
[dir="rtl"] .footer-col a::before { transform-origin: left center; }
.footer-col a:hover { transform: translateX(3px); }
[dir="rtl"] .footer-col a:hover { transform: translateX(-3px); }
.footer-col a:hover::before { transform: scaleX(1); }

/* Mobile menu: links unfold out of the header in sequence. */
@media (max-width: 900px) and (prefers-reduced-motion: no-preference) {
  .mobile-nav-menu.is-open a {
    transform-origin: 50% 0;
    animation: menuUnfold 560ms var(--ease-depth) backwards;
  }
  .mobile-nav-menu.is-open a:nth-child(1) { animation-delay: 40ms; }
  .mobile-nav-menu.is-open a:nth-child(2) { animation-delay: 90ms; }
  .mobile-nav-menu.is-open a:nth-child(3) { animation-delay: 140ms; }
  .mobile-nav-menu.is-open a:nth-child(4) { animation-delay: 190ms; }
}
@keyframes menuUnfold {
  from { opacity: 0; transform: perspective(700px) translateY(-10px) rotateX(-50deg); }
  to   { opacity: 1; transform: none; }
}

/* Scroll progress: a small cube face rides the leading edge of the bar. */
.scroll-progress::after {
  content: "";
  position: absolute;
  top: -2px;
  left: 0;
  width: 6px;
  height: 6px;
  border: 1.5px solid var(--am-300);
  background: var(--ink);
  border-radius: 1.5px;
  transform: translateX(calc(var(--scroll-progress) * (100vw - 6px))) rotate(calc(var(--scroll-progress) * 360deg));
  opacity: min(1, calc(var(--scroll-progress) * 40));
  /* Same easing as the bar's fill, so the marker never leads it. */
  transition: transform 120ms linear;
}
[dir="rtl"] .scroll-progress::after {
  left: auto;
  right: 0;
  transform: translateX(calc(var(--scroll-progress) * (6px - 100vw))) rotate(calc(var(--scroll-progress) * -360deg));
}

/* =========================================================
   6. Panels: the call-to-action and install panels share one surface,
   a pane of the structure, with the cube's grid set into it.
   ========================================================= */
.final-cta::before,
.pwa-section::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(214, 188, 255, 0.06) 1px, transparent 1px),
    linear-gradient(90deg, rgba(214, 188, 255, 0.06) 1px, transparent 1px);
  background-size: 48px 48px;
  background-position: center;
  mask-image: radial-gradient(ellipse at 50% 0%, #000 0%, transparent 72%);
  -webkit-mask-image: radial-gradient(ellipse at 50% 0%, #000 0%, transparent 72%);
}
.pwa-section { position: relative; }
.pwa-section > * { position: relative; z-index: 1; }
@media (prefers-reduced-motion: no-preference) {
  /* The CTA panel opens like a pane swinging up into place. */
  html.reveal-on .final-cta[data-reveal] {
    transform: perspective(1200px) rotateX(16deg) translate3d(0, 40px, -60px);
    transform-origin: 50% 100%;
  }
  html.reveal-on .final-cta[data-reveal].is-revealed { transform: none; }
}

/* =========================================================
   7. Page transitions (cross-document view transitions)
   Chrome and Safari 18.2+; other browsers just navigate.

   The page swaps as one layer: the new page fades in over the old one,
   by opacity alone. Nothing in the swap moves or scales, because the
   background cube is part of it: js/scene-cube.js starts the new page's
   cube exactly where the old one stood and holds it still until the swap
   has finished, so the two copies are identical while they cross and the
   cube's turn to the new page's side begins after it. (Earlier the old
   page sank and scaled while the new one rose: two cubes at different
   angles and sizes moving against each other. Naming the content as its
   own layer instead made Chrome capture a tall element from its top, so
   the old page flashed the wrong region.) The new page's own entrance
   (headings, reveals) supplies the motion.
   The header and the scroll bar are their own layers and hold still.
   ========================================================= */
@view-transition { navigation: auto; }
.site-header { view-transition-name: hc-header; }
.scroll-progress { view-transition-name: hc-progress; }

@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) { animation: none; }
  ::view-transition-new(root) { animation: vtFadeIn 300ms var(--ease) both; }
  ::view-transition-group(hc-header),
  ::view-transition-group(hc-progress) { animation-duration: 0s; }
}
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}
@keyframes vtFadeIn {
  from { opacity: 0; }
}

/* Reduced motion has no splash (css/splash-cube.css hides it), so nothing
   should wait for one: the entrance delay drops to zero and the page is
   simply there. */
@media (prefers-reduced-motion: reduce) {
  :root, html.no-loader { --entrance: 0s; }
}
