/* =============================================================================
   effects.css — motion, hover states and transitions for the SAIS home page
   (v2: the improved version of the one-to-one rebuild in site/).

   Load order: tokens.css -> style.css -> effects.css (this file).
   style.css owns layout and the resting/state colours; this file adds how
   things MOVE between states. main.js toggles the state classes.

   Sections 1–10 are the faithful port of the Framer reference (values and
   sources: docs/motion.md). Sections 11–19 are the v2 additions (focus ring,
   phone menu polish, band seams, and the creative items 1–7 in
   docs/v2-decisions.md). Every v2 animation is switched off under
   prefers-reduced-motion (section 20).

   Framer "spring" transitions are reproduced with CSS linear() easings that
   were sampled (41 points) from the exact spring maths of the motion library
   Framer ships (ref/js/motion.*.mjs, duration-based spring solver):
     --ease-spring-bounce : spring { duration: .4s, bounce: .2 }  (overshoots 1.5%)
     --ease-spring        : spring { duration: .4s / .6s, bounce: 0 } (critically damped)
   ========================================================================== */

:root {
  /* spring { type: spring, duration: 0.4, bounce: 0.2 } — Framer hover/variant default */
  --ease-spring-bounce: linear(0, 0.0224, 0.0794, 0.1581, 0.2486, 0.3435, 0.4375, 0.5269, 0.6093,
    0.6833, 0.7484, 0.8044, 0.8518, 0.8911, 0.9231, 0.9487, 0.9688, 0.9841, 0.9954, 1.0036,
    1.0091, 1.0125, 1.0144, 1.0151, 1.015, 1.0142, 1.0131, 1.0118, 1.0104, 1.0089, 1.0076,
    1.0063, 1.0051, 1.0041, 1.0032, 1.0024, 1.0018, 1.0013, 1.0009, 1.0006, 1);
  /* spring { bounce: 0 } — same curve for 0.4s (accordion) and 0.6s (phone menu) */
  --ease-spring: linear(0, 0.0229, 0.0788, 0.1532, 0.2361, 0.3208, 0.403, 0.4802, 0.5509,
    0.6146, 0.6711, 0.7207, 0.7638, 0.801, 0.8329, 0.8601, 0.8832, 0.9027, 0.9191, 0.9329,
    0.9445, 0.9541, 0.9621, 0.9688, 0.9743, 0.9789, 0.9827, 0.9858, 0.9884, 0.9905, 0.9922,
    0.9936, 0.9948, 0.9958, 0.9965, 0.9972, 0.9977, 0.9981, 0.9985, 0.9988, 1);
  /* owner reveal curve (ref custom CSS ".sdia-reveal") */
  --ease-reveal: cubic-bezier(.16, 1, .3, 1);
}


/* -----------------------------------------------------------------------------
   1. Page-in: the whole body fades in once (owner CSS "sdiaPageIn", 0.28s ease)
   ----------------------------------------------------------------------------- */
body { animation: page-in 0.28s ease both; }
@keyframes page-in { from { opacity: 0; } to { opacity: 1; } }


/* -----------------------------------------------------------------------------
   2. Appear-on-scroll presets  (attribute data-appear="<preset>")
      main.js adds .is-in when the element enters the viewport (threshold 0.12,
      once). Elements already inside the first viewport at load are revealed
      together, staggered 70ms apart (capped at 480ms) — owner "page-in stagger".
      We animate the individual `translate` property (not `transform`) so the
      reveal composes with any transform style.css already uses.
   ----------------------------------------------------------------------------- */

/* "reveal" = owner ".sdia-reveal": opacity 0 + 22px down -> in place, 0.7s */
.js [data-appear="reveal"] {
  opacity: 0;
  translate: 0 22px;
  transition:
    opacity 0.7s var(--ease-reveal) var(--appear-delay, 0s),
    translate 0.7s var(--ease-reveal) var(--appear-delay, 0s);
}
.js [data-appear="reveal"].is-in {
  opacity: 1;
  translate: none;
}


/* -----------------------------------------------------------------------------
   3. Nav: dark bar over the hero -> light bar after the Customers band.
      main.js toggles .nav--light on header.nav (colours live in style.css).
      Owner CSS "nav state fade": bg/box-shadow .5s, text .35s, icon filter .5s.
   ----------------------------------------------------------------------------- */
.nav,
.nav__bar,
.nav-link--cta,
.nav__menu-btn {
  transition: background-color 0.5s ease, box-shadow 0.5s ease;
}
.nav-link,
.nav-link__label,
.nav__lang-face,
.nav__menu-btn {
  transition: color 0.35s ease, background-color 0.45s ease;
}
.nav__logo img,
.nav__globe { transition: filter 0.5s ease; }

/* Bracket links "[About]": the brackets sit 4px inside the padding and slide
   out to the edge on hover (Framer nav-link component, hover variant
   left/right 4px -> 0, spring .4s bounce .2). Text colour does not change
   (the owner CSS pins it with !important on the live site). */
.nav-link__bracket {
  transition: left 0.4s var(--ease-spring-bounce), right 0.4s var(--ease-spring-bounce);
}
.nav-link:hover .nav-link__bracket--open  { left: 0; }
.nav-link:hover .nav-link__bracket--close { right: 0; }

/* Phone menu panel. The reference slides it down from -860px (spring .6s);
   v2 replaces the opening with a gentler ease-in (section 12). Closing keeps
   the reference fade: opacity 0 over .2s cubic-bezier(.12,.23,.5,1). */
@media (max-width: 809.98px) {
  .nav--menu-closing .phone-menu {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: -1;
    animation: phone-menu-out 0.2s cubic-bezier(.12, .23, .5, 1) both;
  }
}
@keyframes phone-menu-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* Bottom-of-viewport fade: hidden over the dark first viewport, fades in
   once the nav is light (owner blurGate: opacity .45s ease). */
.page-fade { transition: opacity 0.45s ease; }


/* -----------------------------------------------------------------------------
   4. Buttons
   ----------------------------------------------------------------------------- */
/* Green "Book a call" / hero CTA (Framer button "Tint" variant):
   #14532d -> #0f3d22 on hover, spring .4s bounce .2 */
.btn--green {
  transition: background-color 0.4s var(--ease-spring-bounce);
}
.btn--green:hover { background-color: var(--c-green-deeper, #0f3d22); }


/* -----------------------------------------------------------------------------
   5. Tags with "+" corner marks (Framer tag component, hover variant)
      On hover the four marks spin a quarter turn: top-left and bottom-right
      +90deg, bottom-left and top-right -90deg (spring .4s bounce .2).
      The outlined tags ("Enable · Govern", timeline "Stage 1/2/3") lose the
      owner outline on hover and show the component's own black fill with
      white text — an instant switch on the live site.
   ----------------------------------------------------------------------------- */
.mark { transition: transform 0.4s var(--ease-spring-bounce); }
.tag:hover > .mark--tl,
.tag:hover > .mark--br { transform: rotate(90deg); }
.tag:hover > .mark--bl,
.tag:hover > .mark--tr { transform: rotate(-90deg); }

.tag--outline:hover {
  background: #000;
  border-width: 0;   /* live: the owner outline drops away, box shrinks by 1px a side */
  color: #fff;
}


/* -----------------------------------------------------------------------------
   6. Footer
   ----------------------------------------------------------------------------- */
/* Footer bracket links: brackets slide 4px further out on hover
   (left/right 0 -> -4px, spring .4s bounce .2). Colour unchanged. */
.footer-link__bracket {
  transition: left 0.4s var(--ease-spring-bounce), right 0.4s var(--ease-spring-bounce);
}
/* desktop/tablet only — the phone footer links have no hover variant */
@media (min-width: 810px) {
  .footer-link:hover .footer-link__bracket--open  { left: -4px; }
  .footer-link:hover .footer-link__bracket--close { right: -4px; }
}

/* Social icons: grey rgb(120,120,120) -> black on hover (spring .4s bounce .2). */
.footer__social-link { transition: color 0.4s var(--ease-spring-bounce); }
.footer__social-link:hover { color: #000; }


/* -----------------------------------------------------------------------------
   7. Accordions (Our Assumptions + FAQ)
      main.js toggles .is-open on .acc-item. Measured on the live site:
      - the row height snaps open/closed instantly (no height animation);
      - the outline switches dashed (closed) <-> solid (open) instantly;
      - the "+" icon turns 45deg into an "x" (spring .4s, bounce 0);
      - the answer slides up from where Framer parks it when closed
        (top 264% of the closed row, centred on itself) into place while its
        blur goes 5px -> 0 (spring .4s, bounce 0). Closing plays it in reverse,
        hidden by the row's clip. main.js writes the park distance per row to
        --acc-park (1.64 x closed row height + 16px = 155px for one-line rows).
   ----------------------------------------------------------------------------- */
.acc-icon::before {
  transition: transform 0.4s var(--ease-spring);
}
.acc-item.is-open .acc-icon::before { transform: rotate(45deg); }

.acc-a-inner > p {
  filter: blur(5px);
  translate: 0 calc(var(--acc-park, 155px) - 50%);
  transition: filter 0.4s var(--ease-spring), translate 0.4s var(--ease-spring);
}
.acc-item.is-open .acc-a-inner > p {
  filter: blur(0);
  translate: none;
}
.acc-item.is-open::after { border-style: solid; }


/* -----------------------------------------------------------------------------
   8. "The AI" pin in The Solution
      Blurred (5px) until at least half of it is on screen, sharp after —
      Framer "variant appear" (threshold .5, repeats), spring .4s bounce .2.
      main.js toggles .is-visible; its scroll-linked offset is also in main.js.
   ----------------------------------------------------------------------------- */
.ai-tag {
  filter: blur(5px);
  transition: filter 0.4s var(--ease-spring-bounce);
}
.ai-tag.is-visible { filter: blur(0); }


/* -----------------------------------------------------------------------------
   9. Logo ticker — driven by main.js (26 px/s leftwards, draggable).
   ----------------------------------------------------------------------------- */
.ticker-track { will-change: transform; }
.ticker { touch-action: pan-y; user-select: none; -webkit-user-select: none; }
.ticker img { -webkit-user-drag: none; pointer-events: none; }


/* -----------------------------------------------------------------------------
   9b. Cal.com embed — our container is .cal-mount (Cal itself puts the class
       "cal-embed" on its iframe); the iframe just fills the container.
   ----------------------------------------------------------------------------- */
.cal-mount > iframe { width: 100%; height: 100%; min-width: 0; }


/* =============================================================================
   v2 ADDITIONS
   ========================================================================== */

/* -----------------------------------------------------------------------------
   11. Keyboard focus — one brand-green ring on every interactive element.
       Green-700 on the light papers (>= 4:1), the brighter "we are here" green
       on the dark band (8:1). Mouse clicks do not show it (:focus-visible).
   ----------------------------------------------------------------------------- */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--focus-ring, #15803d);
  outline-offset: 3px;
}
.nav:not(.nav--light):not(.nav--menu-open) :focus-visible,
.hero :focus-visible,
.customers :focus-visible,
.footer :focus-visible {
  outline-color: var(--focus-ring-dark, #22c55e);
}
/* accordion rows clip their overflow: draw the ring inside the row */
.acc-q:focus-visible { outline-offset: -4px; }
/* language pickers: the real <select> is an invisible overlay, ring the face */
.nav__lang-select:focus-visible + .nav__lang-face,
.phone-menu__lang-select:focus-visible + .phone-menu__lang-face {
  outline: 2px solid var(--focus-ring, #15803d);
  outline-offset: 2px;
}
.nav:not(.nav--light) .nav__lang-select:focus-visible + .nav__lang-face {
  outline-color: var(--focus-ring-dark, #22c55e);
}


/* -----------------------------------------------------------------------------
   12. Phone menu polish
       - the bar turns paper while the menu is open, so the logo and the close
         "X" are ink on paper (the original drew a paper "X" on a light bar);
       - burger and X cross-fade with a quarter turn instead of swapping;
       - the panel eases in (12px drop + fade) and the links follow 35ms apart
         instead of the original 860px slide.
   ----------------------------------------------------------------------------- */
@media (max-width: 809.98px) {
  .nav.nav--menu-open,
  .nav.nav--menu-open .nav__bar {
    background: var(--paper, #fafaf9);
    box-shadow: inset 0 -1px 0 var(--rule, #dfdcd4);
  }
  .nav.nav--menu-open .nav__menu-btn {
    background: rgba(20, 21, 18, 0.06);
    color: var(--sdia-ink, #141512);
  }

  .nav .nav__menu-btn { position: relative; }
  .nav .nav__menu-btn .nav__menu-icon {
    display: block;
    transition: opacity 0.2s ease, transform 0.4s var(--ease-spring-bounce);
  }
  .nav .nav__menu-btn .nav__menu-icon--close {
    position: absolute;
    inset: 0;
    margin: auto;
    opacity: 0;
    transform: rotate(-90deg) scale(0.4);
  }
  .nav.nav--menu-open .nav__menu-btn .nav__menu-icon--open {
    opacity: 0;
    transform: rotate(90deg) scale(0.4);
  }
  .nav.nav--menu-open .nav__menu-btn .nav__menu-icon--close {
    opacity: 1;
    transform: none;
  }

  .nav--menu-open .phone-menu {
    animation: menu-panel-in 0.45s var(--ease-spring) both;
  }
  .nav--menu-open .phone-menu__label,
  .nav--menu-open .phone-menu__link,
  .nav--menu-open .phone-menu__divider,
  .nav--menu-open .phone-menu__lang {
    animation: menu-item-in 0.5s var(--ease-reveal) both;
    animation-delay: calc(60ms + var(--i, 0) * 35ms);
  }
}
/* the bottom-of-viewport paper fade would wash out the lower menu items:
   hide it while the menu is open or closing (header.nav and .page-fade are
   both children of <body>, nav first) */
.nav.nav--menu-open ~ .page-fade,
.nav.nav--menu-closing ~ .page-fade { opacity: 0; }

@keyframes menu-panel-in {
  from { opacity: 0; transform: translateY(-12px); }
  to   { opacity: 1; transform: none; }
}
@keyframes menu-item-in {
  from { opacity: 0; translate: 0 10px; }
  to   { opacity: 1; translate: none; }
}


/* -----------------------------------------------------------------------------
   13. Band seams — every light section starts with the same 1px paper rule,
       so a change of paper (page -> stone -> mint) reads as a deliberate new
       sheet rather than a drift in colour. Same inset-shadow technique as
       style.css 2a (no height change).
   ----------------------------------------------------------------------------- */
.two-ways, .ladder, .assumptions, .sources,
.features, .integrations, .tiers, .partner, .faq, .book {
  box-shadow: inset 0 1px 0 var(--rule-band, #e8e5dd);
}


/* -----------------------------------------------------------------------------
   14. Creative 1 — hero bar spotlight (desktop, fine pointer only)
       Drawn inside ascii.js: the original component's cursor-gradient path,
       a soft radial "ink" under the pointer that thickens the glyphs nearby.
       Nothing to style here; documented for completeness.
   ----------------------------------------------------------------------------- */


/* -----------------------------------------------------------------------------
   15. Creative 2 — "What we connect"
       Labels are readable (style.css .hub__label). Small green pulses travel
       along each connector toward the hub, then along the main line toward the
       SAIS tag. They run only while the section is on screen (main.js adds
       .is-live) and never under reduced motion.
   ----------------------------------------------------------------------------- */
.hub__spoke::before,
.hub--large .hub__main-line::before {
  content: "";
  position: absolute;
  z-index: 0;
  top: 50%;
  width: 6px;
  height: 6px;
  margin: -3px 0 0 -3px;
  border-radius: 50%;
  background: var(--c-green, #16a34a);
  box-shadow: 0 0 6px 1px rgba(22, 163, 74, 0.45);
  opacity: 0;
  pointer-events: none;
}
.integrations.is-live .hub__spoke::before {
  animation: pulse-spoke 2.8s cubic-bezier(.45, 0, .55, 1) infinite;
}
.integrations.is-live .hub__spoke--1::before { animation-delay: 0s; }
.integrations.is-live .hub__spoke--2::before { animation-delay: 0.7s; }
.integrations.is-live .hub__spoke--3::before { animation-delay: 1.4s; }
.integrations.is-live .hub__spoke--4::before { animation-delay: 2.1s; }
/* main line: node (right end) -> SAIS tag (middle) */
.integrations.is-live .hub--large .hub__main-line::before {
  animation: pulse-main 1.4s cubic-bezier(.45, 0, .55, 1) infinite;
  animation-delay: 1s;
}
@keyframes pulse-spoke {
  0%   { left: 97%; opacity: 0; }
  12%  { opacity: 1; }
  80%  { opacity: 1; }
  100% { left: 4%;  opacity: 0; }
}
@keyframes pulse-main {
  0%   { left: 100%; opacity: 0; }
  15%  { opacity: 1; }
  75%  { opacity: 1; }
  100% { left: 56%;  opacity: 0; }
}


/* -----------------------------------------------------------------------------
   16. Creative 3 — Field results count-up
       main.js wraps each number in an inline .count span that keeps the real
       number as its text, so "~14%" / "~SAR 123k" stay tight and wrap exactly
       as written. While it counts (.is-counting, 1.1s ease-out) the real
       digits are transparent and the running value (data-v) is drawn over
       them from the same left edge. Nothing in the line moves.
   ----------------------------------------------------------------------------- */
.count {
  position: relative;
  white-space: nowrap;
  margin: 0;
  padding: 0;
}
.count.is-counting { -webkit-text-fill-color: transparent; }
.count.is-counting::after {
  content: attr(data-v);
  position: absolute;
  top: 0;
  left: 0;              /* anchored left: the overlay never moves, it only grows */
  white-space: nowrap;
  -webkit-text-fill-color: currentColor;
  pointer-events: none;
}


/* -----------------------------------------------------------------------------
   17. Creative 4 — Adoption ladder
       "You are here" breathes: a soft green halo swells and fades (4s cycle).
       Hovering another step previews the highlight on it (dashed green
       outline + mint face) while the current step's halo rests.
   ----------------------------------------------------------------------------- */
.ladder__stage--active {
  animation: ladder-breathe 4s ease-in-out infinite;
}
/* the reference's inner hairline on this step is a stray blue (Framer
   selection colour); v2 keeps it green so the step reads as one highlight */
.ladder__stage--active::after { border-color: rgba(22, 163, 74, 0.35); }
@keyframes ladder-breathe {
  0%, 100% { box-shadow: 0 0 0 0 rgba(22, 163, 74, 0); }
  50%      { box-shadow: 0 0 0 6px rgba(22, 163, 74, 0.13); }
}
.js .ladder__stage[data-appear="reveal"] {
  transition:
    opacity 0.7s var(--ease-reveal) var(--appear-delay, 0s),
    translate 0.7s var(--ease-reveal) var(--appear-delay, 0s),
    background-color 0.3s ease,
    outline-color 0.3s ease;
}
.ladder__stage:not(.ladder__stage--active) {
  outline: 2px dashed transparent;
  outline-offset: -2px;
}
@media (hover: hover) and (pointer: fine) {
  .ladder__stage:not(.ladder__stage--active):hover {
    background-color: var(--paper-mint, #eef5ef);
    outline-color: rgba(22, 163, 74, 0.6);
  }
  .ladder__stages:has(.ladder__stage:not(.ladder__stage--active):hover) .ladder__stage--active {
    animation-play-state: paused;
    outline-color: rgba(22, 163, 74, 0.4);
  }
}


/* -----------------------------------------------------------------------------
   18. Creative 5 — feature cards
       On hover the ASCII icon's glow layer widens and warms up, the glyphs
       get a touch more saturated, and the dashed outline turns solid.
       (The glow layer's blur is inline from ascii.js, hence !important.)
   ----------------------------------------------------------------------------- */
.feature .ascii__glow,
.feature .ascii__text { transition: filter 0.45s ease; }
@media (hover: hover) and (pointer: fine) {
  .feature:hover .ascii__glow { filter: blur(15px) saturate(1.6) brightness(1.08) !important; }
  .feature:hover .ascii__text { filter: saturate(1.25); }
  .feature:hover::after { border-style: solid; }
}


/* -----------------------------------------------------------------------------
   19. Creative 6 — eyebrow tags: when a section header reveals, the four "+"
       marks start tucked against the tag and spring out to their corners
       (0.25s after the header starts rising). Uses `translate`, so the hover
       spin (transform: rotate) still works.
   ----------------------------------------------------------------------------- */
.js .section-head[data-appear] .tag--marks > .mark {
  transition:
    transform 0.4s var(--ease-spring-bounce),
    translate 0.7s var(--ease-spring-bounce) calc(var(--appear-delay, 0s) + 0.25s);
}
.js .section-head[data-appear]:not(.is-in) .tag--marks > .mark--tl { translate: 5px 5px; }
.js .section-head[data-appear]:not(.is-in) .tag--marks > .mark--tr { translate: -5px 5px; }
.js .section-head[data-appear]:not(.is-in) .tag--marks > .mark--bl { translate: 5px -5px; }
.js .section-head[data-appear]:not(.is-in) .tag--marks > .mark--br { translate: -5px -5px; }


/* -----------------------------------------------------------------------------
   20. Reduced motion: no reveals, no page fade, no spins, no v2 motion.
   ----------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  body { animation: none; }
  .js [data-appear] { opacity: 1 !important; translate: none !important; transition: none !important; }
  .nav--menu-open .phone-menu,
  .nav--menu-closing .phone-menu,
  .nav--menu-open .phone-menu__label,
  .nav--menu-open .phone-menu__link,
  .nav--menu-open .phone-menu__divider,
  .nav--menu-open .phone-menu__lang { animation: none; }
  .mark, .acc-icon::before, .nav-link__bracket, .footer-link__bracket,
  .nav .nav__menu-btn .nav__menu-icon { transition: none; }
  .js .section-head[data-appear] .tag--marks > .mark { translate: none !important; }
  .ladder__stage--active { animation: none; }
  .hub__spoke::before, .hub--large .hub__main-line::before { display: none; }
  .feature .ascii__glow, .feature .ascii__text { transition: none; }
}
