/*
 * URTM portal walkthrough — motion layer.
 *
 * Every rule here targets the id/class hooks injected by add_hooks.py. The
 * artwork itself is untouched: at rest each frame renders exactly as exported
 * from Figma. Animation is limited to opacity and transform so nothing repaints
 * layout, and the resting state of every animated element is its natural one.
 */

.urtm-portal {
  --stage-w: 1728;
  --stage-h: 1117;

  /* Motion tuning — every duration the timeline uses is declared here. */
  --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --frame-fade: 620ms;
  --loop-fade: 520ms;
  --drawer-slide: 620ms;
  --cursor-travel: 900ms;

  --panel-bg: #f7f7f7;

  display: block;
  width: 100%;
  margin: 0 auto;
  container-type: inline-size;
}

.portal-stage {
  position: relative;
  width: 100%;
  aspect-ratio: var(--stage-w) / var(--stage-h);
  overflow: hidden;
  background: #fff;
  /* The whole stage fades out at the end of a loop and back in on restart. */
  transition: opacity var(--loop-fade) var(--ease-in-out);
  /* Rounded frame reads as a browser window without adding chrome art. */
  border-radius: clamp(4px, 0.7cqw, 14px);
}

/* ---------------------------------------------------------------- frames */

.portal-frame {
  position: absolute;
  inset: 0;
  opacity: 0;
  visibility: hidden;
  transition:
    opacity var(--frame-fade) var(--ease-in-out),
    transform var(--frame-fade) var(--ease-in-out),
    visibility 0s linear var(--frame-fade);
}

.portal-frame > svg {
  display: block;
  width: 100%;
  height: 100%;
}

.portal-frame.is-active {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition:
    opacity var(--frame-fade) var(--ease-in-out),
    transform var(--frame-fade) var(--ease-in-out),
    visibility 0s;
}

/* A frame being replaced drifts fractionally forward as it fades. */
.portal-frame.is-leaving {
  opacity: 0;
  transform: scale(1.015);
}

/* An incoming frame settles back from a hair under full size. */
.portal-frame:not(.is-active) {
  transform: scale(0.994);
}

/* --------------------------------------------------------------- spinner */

/*
 * The authenticating screen's gold arc turns while credentials are checked.
 * fill-box origin means it spins about its own centre without needing the
 * artboard coordinates, so a re-export cannot knock it off axis.
 *
 * `is-leaving` is included so the arc keeps turning all the way through the
 * cross-fade into the home screen — stopping it dead as the frame fades reads
 * as a freeze rather than a hand-off.
 */
.portal-frame[data-frame='authenticating']:is(.is-active, .is-leaving) .spinner {
  transform-box: fill-box;
  transform-origin: center;
  animation:
    portal-spin 900ms linear infinite,
    portal-reveal 420ms var(--ease-out) both;
}

@keyframes portal-spin {
  to {
    transform: rotate(360deg);
  }
}

/*
 * The authenticating screen cuts in rather than cross-fading — it shares its
 * entire layout with the login screen, so fading the whole frame made the card
 * flicker. Only the heading and the spinner, the two genuinely new elements,
 * fade up in place.
 */
.portal-frame[data-frame='authenticating'].is-active .auth-reveal {
  animation: portal-reveal 420ms var(--ease-out) both;
}

@keyframes portal-reveal {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* ---------------------------------------------------------- side drawer */

/*
 * The drawer is the with-menu export clipped to the panel's own width, so a
 * fully-open drawer over the report frame reproduces that export exactly.
 * Its shadow is re-created in CSS because the clip cuts the painted one.
 */
.portal-drawer {
  position: absolute;
  left: 0;
  top: 0;
  /* Exactly the panel width from the export, so the drawer lands where the
     with-menu frames show it — no wider, no narrower. */
  width: calc(300 / var(--stage-w) * 100%);
  height: 100%;
  overflow: hidden;
  transform: translateX(-101%);
  /*
   * The shadow belongs to the open state. Parked off-stage the panel still
   * casts one, which bleeds a sliver down the left edge of the frame, so it
   * starts at zero and grows in step with the slide. Both keyframes use the
   * same drop-shadow signature so the filter can interpolate.
   */
  filter: drop-shadow(0 0 0 rgba(40, 40, 43, 0));
  transition:
    transform var(--drawer-slide) var(--ease-in-out),
    filter var(--drawer-slide) var(--ease-in-out);
  will-change: transform;
  z-index: 3;
}

.portal-drawer > svg {
  display: block;
  /* Blow the clipped column back up to the full artboard width so the drawer
     artwork keeps its exported scale inside the narrow container. */
  width: calc(var(--stage-w) / 300 * 100%);
  height: 100%;
}

.urtm-portal[data-drawer='open'] .portal-drawer {
  transform: translateX(0);
  filter: drop-shadow(6px 0 24px rgba(40, 40, 43, 0.13));
}

/*
 * The drawer artwork ships in its Revenue Reporting state. While Home is the
 * active item the gold indicator sits higher by --nav-home-offset, so choosing
 * a report slides it down rather than redrawing the sidebar — which is why the
 * menu never re-enters when the dashboard opens.
 */
.portal-drawer .active-bar {
  transition: transform 460ms var(--ease-in-out);
}

.urtm-portal[data-nav='home'] .portal-drawer .active-bar {
  transform: translateY(var(--nav-home-offset, 0));
}

/* ------------------------------------------------------- login field typing */

/*
 * The placeholder gives way to the typed value, exactly as a real input does.
 * Both live in the login frame and sit at the export's own field baseline.
 */
.portal-frame [data-role='email-placeholder'] {
  transition: opacity 200ms linear;
}

.urtm-portal[data-typing='active'] [data-role='email-placeholder'],
.urtm-portal[data-typing='done'] [data-role='email-placeholder'] {
  opacity: 0;
}

#lg-caret {
  opacity: 0;
}

.urtm-portal[data-typing='active'] #lg-caret {
  animation: portal-caret 1060ms steps(1, end) infinite;
}

@keyframes portal-caret {
  0%,
  50% {
    opacity: 1;
  }
  50.01%,
  100% {
    opacity: 0;
  }
}

/* --------------------------------------------------------------- cursor */

/*
 * The pointer is positioned by the script every frame, so it carries no
 * transition of its own — the easing and the arc live in the timeline. Its
 * hotspot is the arrow tip, matching how a real cursor is anchored.
 */
.portal-cursor {
  position: absolute;
  left: 0;
  top: 0;
  width: calc(24 / var(--stage-w) * 100%);
  opacity: 0;
  pointer-events: none;
  /* Shift so the arrow tip — not the box corner — sits on the waypoint. */
  transform: translate(-16.7%, -9.6%);
  transition: opacity 260ms linear;
  z-index: 6;
}

.portal-cursor > svg {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.28));
}

.portal-cursor.is-visible {
  opacity: 1;
}

.portal-cursor.is-pressing > svg {
  transform: scale(0.86);
  transition: transform 140ms var(--ease-out);
}

/* Click ripple, sized in stage units so it scales with the module. */
.portal-ripple {
  position: absolute;
  left: 0;
  top: 0;
  width: calc(96 / var(--stage-w) * 100%);
  aspect-ratio: 1;
  margin: calc(-48 / var(--stage-w) * 100%) 0 0 calc(-48 / var(--stage-w) * 100%);
  border-radius: 50%;
  background: radial-gradient(circle, rgba(206, 173, 87, 0.42) 0%, rgba(206, 173, 87, 0) 68%);
  opacity: 0;
  pointer-events: none;
  z-index: 4;
}

.portal-ripple.is-firing {
  animation: portal-ripple 620ms var(--ease-out);
}

@keyframes portal-ripple {
  from {
    opacity: 0.95;
    transform: scale(0.25);
  }
  to {
    opacity: 0;
    transform: scale(1.5);
  }
}

/* ---------------------------------------------------- reduced motion */

/*
 * Users who ask for reduced motion get the finished dashboard, statically.
 * The timeline never starts, so nothing moves at all.
 */
@media (prefers-reduced-motion: reduce) {
  .portal-frame,
  .portal-drawer,
  .portal-cursor,
  .portal-ripple {
    transition: none !important;
    animation: none !important;
  }

  .portal-cursor,
  .portal-ripple,
  .portal-drawer {
    display: none;
  }

  .portal-frame {
    opacity: 0;
    visibility: hidden;
    transform: none;
  }

  .portal-frame.is-active {
    opacity: 1;
    visibility: visible;
  }
}
