/* ==========================================================================
   OptiRank Studio — Motion
   Every effect in this file is disabled or drastically shortened under
   prefers-reduced-motion (block at the bottom of the file).
   ========================================================================== */

/* 1. Scroll reveals ------------------------------------------------------ */

.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition:
    opacity var(--duration-reveal) var(--ease-out),
    transform var(--duration-reveal) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: opacity, transform;
}

.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* JS sets --reveal-delay per sibling; these are the static fallbacks used
   when markup declares the stagger inline. */
.reveal--d1 { --reveal-delay: 70ms; }
.reveal--d2 { --reveal-delay: 140ms; }
.reveal--d3 { --reveal-delay: 210ms; }
.reveal--d4 { --reveal-delay: 280ms; }

/* 6. Page enter fade ----------------------------------------------------- */

@keyframes page-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

body {
  animation: page-fade-in 150ms var(--ease-out) both;
}

/* 3. Hero background blobs ----------------------------------------------- */

.blob-field {
  position: absolute;
  inset: -20% -10% auto -10%;
  height: 120%;
  pointer-events: none;
  z-index: 0;
  overflow: hidden;
  filter: blur(48px);
  opacity: 0.85;
}

.blob {
  position: absolute;
  border-radius: 50%;
  will-change: transform;
}

.blob--a {
  top: 4%;
  left: 2%;
  width: 340px;
  height: 340px;
  background: var(--color-primary);
  opacity: 0.16;
  animation: blob-drift-a 26s ease-in-out infinite;
}

.blob--b {
  top: 30%;
  right: 6%;
  width: 300px;
  height: 300px;
  background: var(--color-accent);
  opacity: 0.14;
  animation: blob-drift-b 30s ease-in-out infinite;
}

.blob--c {
  bottom: 0%;
  left: 38%;
  width: 260px;
  height: 260px;
  background: var(--color-primary);
  opacity: 0.1;
  animation: blob-drift-c 22s ease-in-out infinite;
}

@keyframes blob-drift-a {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  33%      { transform: translate3d(40px, 30px, 0) scale(1.08); }
  66%      { transform: translate3d(-24px, 50px, 0) scale(0.96); }
}

@keyframes blob-drift-b {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  40%      { transform: translate3d(-48px, 36px, 0) scale(1.1); }
  75%      { transform: translate3d(24px, -28px, 0) scale(0.94); }
}

@keyframes blob-drift-c {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(36px, -34px, 0) scale(1.12); }
}

/* 5a. Signature animation — "broken" crawl error becomes "fixed" ---------- */

/* The hero scene loops on a 5s cycle: a node reports an error (red pulse),
   the error is diagnosed (scan sweep), then resolves to a green check. */

.scene-fix__error-ring,
.scene-fix__success-ring,
.scene-fix__scan,
.scene-graph__node,
.scene-redirect__landed {
  transform-box: fill-box;
  transform-origin: center;
}

.scene-fix__error-ring {
  animation: fix-error-pulse 5s var(--ease-out) infinite;
}

.scene-fix__error-mark {
  animation: fix-error-fade 5s var(--ease-out) infinite;
}

.scene-fix__check {
  stroke-dasharray: 40;
  stroke-dashoffset: 40;
  opacity: 0;
  animation: fix-check-draw 5s var(--ease-out) infinite;
}

.scene-fix__success-ring {
  transform-origin: center;
  opacity: 0;
  animation: fix-success-ring 5s var(--ease-out) infinite;
}

.scene-fix__scan {
  opacity: 0;
  animation: fix-scan 5s linear infinite;
}

@keyframes fix-error-pulse {
  0%, 6%    { opacity: 0; transform: scale(0.6); }
  14%, 40%  { opacity: 1; transform: scale(1); }
  46%       { opacity: 0.8; transform: scale(1.25); }
  52%, 100% { opacity: 0; transform: scale(1.4); }
}

@keyframes fix-error-fade {
  0%, 8%    { opacity: 0; }
  16%, 42%  { opacity: 1; }
  50%, 100% { opacity: 0; }
}

@keyframes fix-scan {
  0%, 40%   { opacity: 0; transform: translateY(-26px); }
  44%       { opacity: 0.9; }
  56%       { opacity: 0.9; transform: translateY(26px); }
  60%, 100% { opacity: 0; transform: translateY(26px); }
}

@keyframes fix-check-draw {
  0%, 56%   { stroke-dashoffset: 40; opacity: 0; }
  58%       { opacity: 1; }
  72%, 92%  { stroke-dashoffset: 0; opacity: 1; }
  100%      { stroke-dashoffset: 0; opacity: 0; }
}

@keyframes fix-success-ring {
  0%, 56%   { opacity: 0; transform: scale(0.7); }
  70%, 90%  { opacity: 1; transform: scale(1); }
  100%      { opacity: 0; transform: scale(1); }
}

/* Crawl-graph edges drawing in (hero illustration) */
.scene-graph__edge {
  stroke-dasharray: 120;
  stroke-dashoffset: 120;
  animation: graph-edge-draw 1.4s var(--ease-out) forwards;
  animation-delay: var(--edge-delay, 0ms);
}

@keyframes graph-edge-draw {
  to { stroke-dashoffset: 0; }
}

.scene-graph__node {
  transform-origin: center;
  animation: node-breathe 4s ease-in-out infinite;
  animation-delay: var(--node-delay, 0ms);
}

@keyframes node-breathe {
  0%, 100% { opacity: 0.9; }
  50%      { opacity: 1; }
}

/* 5b. Redirect diagram — dot travelling along the path -------------------- */

.scene-redirect__path {
  stroke-dasharray: 6 6;
  animation: redirect-dash 1.6s linear infinite;
}

@keyframes redirect-dash {
  to { stroke-dashoffset: -12; }
}

/* Packets translate along straight runs, so the travel works without
   offset-path support and stays controllable from CSS. */
.scene-redirect__packet {
  animation: redirect-travel 3.2s linear infinite;
  animation-delay: var(--packet-delay, 0ms);
}

@keyframes redirect-travel {
  0%        { transform: translate(0, 0); opacity: 0; }
  10%       { opacity: 1; }
  65%       { transform: translate(var(--dx, 0px), var(--dy, 0px)); opacity: 1; }
  75%, 100% { transform: translate(var(--dx, 0px), var(--dy, 0px)); opacity: 0; }
}

.scene-redirect__landed {
  opacity: 0;
  transform-origin: center;
  animation: redirect-landed 3.2s var(--ease-out) infinite;
}

@keyframes redirect-landed {
  0%, 62%   { opacity: 0; transform: scale(0.9); }
  70%, 92%  { opacity: 1; transform: scale(1); }
  100%      { opacity: 0; transform: scale(1); }
}

/* 5c. Chart path draw-in (dashboard) ------------------------------------- */

.chart__line {
  stroke-dasharray: var(--path-length, 2000);
  stroke-dashoffset: var(--path-length, 2000);
  transition: stroke-dashoffset 1600ms var(--ease-out);
}

.is-visible .chart__line,
.chart__line.is-drawn {
  stroke-dashoffset: 0;
}

.chart__area {
  opacity: 0;
  transition: opacity 900ms var(--ease-out) 500ms;
}

.is-visible .chart__area,
.chart__area.is-drawn {
  opacity: 1;
}

/* Radial gauge ----------------------------------------------------------- */

.gauge__value {
  stroke-dasharray: var(--gauge-circumference, 440);
  stroke-dashoffset: var(--gauge-circumference, 440);
  transition: stroke-dashoffset 1400ms var(--ease-out);
}

.is-visible .gauge__value {
  stroke-dashoffset: var(--gauge-offset, 0);
}

/* Sparkline -------------------------------------------------------------- */

.sparkline__line {
  stroke-dasharray: 200;
  stroke-dashoffset: 200;
  transition: stroke-dashoffset 1200ms var(--ease-out) 200ms;
}

.is-visible .sparkline__line {
  stroke-dashoffset: 0;
}

/* Live pulse dot (dashboard "last crawled") ------------------------------ */

.pulse-dot {
  position: relative;
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: var(--color-accent);
  flex-shrink: 0;
}

.pulse-dot::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background-color: var(--color-accent);
  animation: pulse-ring 2.4s ease-out infinite;
}

@keyframes pulse-ring {
  0%   { transform: scale(1); opacity: 0.7; }
  70%  { transform: scale(2.6); opacity: 0; }
  100% { transform: scale(2.6); opacity: 0; }
}

/* Process connector dots ------------------------------------------------- */

.connector-dot {
  animation: connector-travel 3s ease-in-out infinite;
}

@keyframes connector-travel {
  0%, 100% { opacity: 0.25; }
  50%      { opacity: 1; }
}

/* ==========================================================================
   Reduced motion — hard stop for everything above
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  body {
    animation: none;
  }

  /* Reveals resolve to their final state immediately, whether or not the
     IntersectionObserver has fired. */
  .reveal,
  .reveal.is-visible {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .blob-field {
    display: none;
  }

  /* Signature loops settle on their resolved ("fixed") frame instead of
     cycling. */
  .scene-fix__error-ring,
  .scene-fix__error-mark,
  .scene-fix__scan {
    animation: none;
    opacity: 0;
  }

  .scene-fix__check,
  .scene-fix__success-ring {
    animation: none;
    opacity: 1;
    stroke-dashoffset: 0;
    transform: none;
  }

  .scene-graph__edge {
    animation: none;
    stroke-dashoffset: 0;
  }

  .scene-graph__node {
    animation: none;
    opacity: 1;
  }

  .scene-redirect__path {
    animation: none;
  }

  .scene-redirect__packet {
    animation: none;
    transform: translate(var(--dx, 0px), var(--dy, 0px));
    opacity: 1;
  }

  .scene-redirect__landed {
    animation: none;
    opacity: 1;
    transform: none;
  }

  .chart__line,
  .sparkline__line {
    stroke-dashoffset: 0;
    transition: none;
  }

  .chart__area {
    opacity: 1;
    transition: none;
  }

  .gauge__value {
    stroke-dashoffset: var(--gauge-offset, 0);
    transition: none;
  }

  .pulse-dot::after {
    animation: none;
    opacity: 0;
  }

  .connector-dot {
    animation: none;
    opacity: 1;
  }

  .card--hover:hover {
    transform: none;
  }

  .btn:active {
    transform: none;
  }

  .link-arrow:hover::after {
    transform: none;
  }
}
