/* ==========================================================================
   Onboarding tour — first-visit coachmarks
   --------------------------------------------------------------------------
   Self-contained: consumes main.css design tokens when present, but every
   token has a local fallback so the sheet renders standalone (file:// demo).

   z-index contract: the portal already uses 99999 (.clean-menu-dropdown,
   .export-hamburger-menu). The tour layer MUST sit above that.
   ========================================================================== */

.ob-root {
  --ob-speed: 1;

  /* Timings. All durations multiply by --ob-speed (demo speed control). */
  --ob-dur-move: calc(280ms * var(--ob-speed));
  --ob-dur-fade: calc(200ms * var(--ob-speed));
  --ob-dur-pulse: calc(1800ms * var(--ob-speed));
  --ob-dur-ghost: calc(2600ms * var(--ob-speed));
  --ob-dur-stack: calc(2600ms * var(--ob-speed));
  --ob-ease: cubic-bezier(0.4, 0, 0.2, 1);

  --ob-z: 100000;
  --ob-z-card: 100010;

  --ob-scrim: rgba(0, 0, 0, 0.55);
  --ob-surface: var(--color-surface, #fff);
  --ob-text: var(--color-text, #454545);
  --ob-text-muted: var(--color-text-muted, #7f7f7f);
  --ob-brand: var(--color-brand, #2bbb75);
  --ob-on-brand: var(--color-on-brand, #092114);
  --ob-border: var(--color-border, #d8d8d8);
  --ob-radius: var(--radius-lg, 0.75rem);
  --ob-shadow: var(--shadow-lg, 0 10px 40px rgba(0, 0, 0, 0.2));

  --ob-card-w: 19rem;
  --ob-gap: 0.75rem;

  /* Spotlight rect — written by JS in px (getBoundingClientRect output). */
  --ob-x: 0px;
  --ob-y: 0px;
  --ob-w: 0px;
  --ob-h: 0px;

  position: fixed;
  inset: 0;
  z-index: var(--ob-z);
  display: none;
  font-family: var(--font-family-base, "Inter", system-ui, sans-serif);
  /* The root spans the viewport, so it must be transparent to input; only the
     shutters and the card opt back in. Without this the layer would swallow
     the drag/click the highlighted step is asking for. */
  pointer-events: none;
}

.ob-root.is-visible {
  display: block;
  animation: ob-fade-in var(--ob-dur-fade) var(--ob-ease);
}

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

/* --------------------------------------------------------------------------
   1. SPOTLIGHT
   A transparent rect with a huge spread shadow painting the scrim around it.
   One node for the whole tour — it transitions between anchors, never respawns.
   -------------------------------------------------------------------------- */
.ob-spot {
  position: absolute;
  top: var(--ob-y);
  left: var(--ob-x);
  width: var(--ob-w);
  height: var(--ob-h);
  border-radius: var(--radius-md, 0.5rem);
  box-shadow: 0 0 0 9999px var(--ob-scrim);
  transition:
    top var(--ob-dur-move) var(--ob-ease),
    left var(--ob-dur-move) var(--ob-ease),
    width var(--ob-dur-move) var(--ob-ease),
    height var(--ob-dur-move) var(--ob-ease);
  /* The cut-out stays interactive so steps that ask for a click are doable. */
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   SCRIM SHUTTERS
   A single inset:0 catcher would sit ON TOP of the highlighted target and
   swallow the very drag/click the step asks for. Instead four panels frame
   the cut-out, leaving a real hole: events inside the spotlight reach the
   page, events outside hit a shutter and close the tour.

   Geometry is driven by the same --ob-x/y/w/h the spotlight uses.
   -------------------------------------------------------------------------- */
.ob-shutter {
  position: absolute;
  pointer-events: auto;
  cursor: default;
}

.ob-shutter--top {
  top: 0;
  left: 0;
  right: 0;
  height: max(0px, var(--ob-y));
}

.ob-shutter--bottom {
  top: calc(var(--ob-y) + var(--ob-h));
  left: 0;
  right: 0;
  bottom: 0;
}

.ob-shutter--left {
  top: var(--ob-y);
  left: 0;
  width: max(0px, var(--ob-x));
  height: var(--ob-h);
}

.ob-shutter--right {
  top: var(--ob-y);
  left: calc(var(--ob-x) + var(--ob-w));
  right: 0;
  height: var(--ob-h);
}

/* With no anchor there is nothing to leave interactive — cover everything. */
.ob-root.is-anchorless .ob-shutter--top { height: 100%; }
.ob-root.is-anchorless .ob-shutter--bottom,
.ob-root.is-anchorless .ob-shutter--left,
.ob-root.is-anchorless .ob-shutter--right { display: none; }

/* Centered fallback when a step has no live anchor. */
.ob-root.is-anchorless .ob-spot { opacity: 0; }
.ob-root.is-anchorless .ob-pulse { display: none; }

/* --------------------------------------------------------------------------
   2. PULSE RING
   The single "alive" element. Deliberately restrained: 1px hairline, low alpha.
   -------------------------------------------------------------------------- */
.ob-pulse {
  position: absolute;
  top: var(--ob-y);
  left: var(--ob-x);
  width: var(--ob-w);
  height: var(--ob-h);
  border: 1px solid var(--ob-brand);
  border-radius: var(--radius-md, 0.5rem);
  pointer-events: none;
  opacity: 0;
  transition:
    top var(--ob-dur-move) var(--ob-ease),
    left var(--ob-dur-move) var(--ob-ease),
    width var(--ob-dur-move) var(--ob-ease),
    height var(--ob-dur-move) var(--ob-ease);
  animation: ob-pulse var(--ob-dur-pulse) var(--ob-ease) infinite;
}

@keyframes ob-pulse {
  0%   { transform: scale(1);    opacity: 0.6; }
  70%  { transform: scale(1.04); opacity: 0;   }
  100% { transform: scale(1.04); opacity: 0;   }
}

/* --------------------------------------------------------------------------
   3. CARD
   -------------------------------------------------------------------------- */
.ob-card {
  position: absolute;
  z-index: var(--ob-z-card);
  box-sizing: border-box;
  width: var(--ob-card-w);
  max-width: calc(100vw - 2rem);
  padding: var(--space-8, 1.25rem);
  background: var(--ob-surface);
  color: var(--ob-text);
  border: 1px solid var(--ob-border);
  border-radius: var(--ob-radius);
  box-shadow: var(--ob-shadow);
  pointer-events: auto;
  opacity: 0;
  transform: translateY(0.375rem);
  transition:
    opacity var(--ob-dur-fade) var(--ob-ease),
    transform var(--ob-dur-fade) var(--ob-ease),
    top var(--ob-dur-move) var(--ob-ease),
    left var(--ob-dur-move) var(--ob-ease);
}

/* Card entrance is delayed so the spotlight lands first. */
.ob-card.is-shown {
  opacity: 1;
  transform: translateY(0);
  transition-delay: calc(80ms * var(--ob-speed)), calc(80ms * var(--ob-speed)), 0s, 0s;
}

.ob-card:focus-visible,
.ob-card:focus {
  outline: none;
  box-shadow: var(--ob-shadow), var(--focus-ring, 0 0 0 2px rgba(43, 187, 117, 0.28));
}

.ob-card__step {
  margin: 0 0 var(--space-2, 0.25rem);
  font-size: var(--font-size-2xs, 0.625rem);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ob-brand);
}

.ob-card__title {
  margin: 0 0 var(--space-3, 0.375rem);
  font-size: var(--font-size-xl, 1rem);
  font-weight: 600;
  line-height: var(--line-height-snug, 1.2);
  color: var(--ob-text);
}

.ob-card__body {
  margin: 0;
  font-size: var(--font-size-md, 0.8125rem);
  line-height: var(--line-height-relaxed, 1.5);
  color: var(--ob-text-muted);
}

.ob-card__footer {
  display: flex;
  align-items: center;
  gap: var(--space-6, 0.75rem);
  margin-top: var(--space-8, 1.25rem);
}

/* --------------------------------------------------------------------------
   4. PROGRESS DOTS
   -------------------------------------------------------------------------- */
.ob-dots {
  display: flex;
  align-items: center;
  gap: var(--space-2, 0.25rem);
  margin-right: auto;
}

.ob-dot {
  width: 0.375rem;
  height: 0.375rem;
  border-radius: var(--radius-pill, 999px);
  background: var(--ob-border);
  transition:
    width calc(240ms * var(--ob-speed)) var(--ob-ease),
    background calc(240ms * var(--ob-speed)) var(--ob-ease);
}

.ob-dot.is-done { background: var(--ob-text-subtle, #9ca3af); }

.ob-dot.is-active {
  width: 1.125rem;
  background: var(--ob-brand);
}

/* --------------------------------------------------------------------------
   5. BUTTONS
   -------------------------------------------------------------------------- */
.ob-btn {
  font-family: inherit;
  font-size: var(--font-size-sm, 0.75rem);
  font-weight: 500;
  line-height: 1;
  padding: 0 var(--space-6, 0.75rem);
  height: var(--control-height-sm, 1.6875rem);
  border-radius: var(--radius-control, 0.375rem);
  border: 1px solid transparent;
  cursor: pointer;
  transition: background calc(120ms * var(--ob-speed)) var(--ob-ease),
              color calc(120ms * var(--ob-speed)) var(--ob-ease);
}

.ob-btn:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring, 0 0 0 2px rgba(43, 187, 117, 0.28));
}

.ob-btn--ghost {
  background: transparent;
  color: var(--ob-text-muted);
}
.ob-btn--ghost:hover { color: var(--ob-text); }

.ob-btn--primary {
  background: var(--ob-brand);
  color: var(--ob-on-brand);
}
.ob-btn--primary:hover { background: var(--color-brand-hover, #27a868); }

/* --------------------------------------------------------------------------
   6. PAPER SHEET — the shared visual vocabulary
   A sheet with a folded corner, never rotated. Two clip-paths cut the corner
   out of the body and draw the fold triangle over it.
   Base size 3.3rem x 4.3rem — a third larger than the first pass.
   -------------------------------------------------------------------------- */
.ob-sheet {
  position: relative;
  width: 3.3rem;
  height: 4.3rem;
  background: var(--ob-surface);
  border: 1px solid var(--ob-brand);
  /* corner cut: 0.85rem fold at the top-right */
  clip-path: polygon(0 0, calc(100% - 0.85rem) 0, 100% 0.85rem, 100% 100%, 0 100%);
  box-shadow: var(--shadow-md, 0 4px 12px rgba(0, 0, 0, 0.15));
}

/* The folded flap itself. */
.ob-sheet::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  width: 0.85rem;
  height: 0.85rem;
  background: var(--ob-brand);
  opacity: 0.28;
  clip-path: polygon(0 0, 100% 100%, 0 100%);
}

/* Text lines hinting at content. */
.ob-sheet::before {
  content: "";
  position: absolute;
  left: 0.45rem;
  right: 0.45rem;
  top: 1.6rem;
  height: 1px;
  background: var(--ob-border);
  box-shadow: 0 0.4rem 0 var(--ob-border), 0 0.8rem 0 var(--ob-border);
}

/* --------------------------------------------------------------------------
   7. GHOST DRAG (step 2)
   Explains the one non-obvious action: hand-dragging a gallery file onto a
   mockup. Travels along a JS-set vector. NO rotation — it reads as a real
   file being carried, not as a thrown card.
   -------------------------------------------------------------------------- */
.ob-ghost {
  position: absolute;
  z-index: var(--ob-z-card);
  pointer-events: none;
  display: none;
  width: 3.3rem;
  height: 4.3rem;
  /* start/end vector — fallback path only, see .ob-no-offset-path below */
  --ob-ghost-dx: 0px;
  --ob-ghost-dy: 0px;
  /* JS writes offset-path with the SAME curve the arrow draws, so the sheet
     rides the arrow instead of cutting a straight line under/over its bow. */
  animation: ob-ghost-ride var(--ob-dur-ghost) var(--ob-ease) infinite;
}

.ob-root.is-ghost-on .ob-ghost { display: block; }

/* offset-distance walks the element along its offset-path. */
@keyframes ob-ghost-ride {
  0%   { offset-distance: 0%;   opacity: 0; }
  12%  { offset-distance: 0%;   opacity: 1; }
  62%  { offset-distance: 100%; opacity: 1; }
  75%  { offset-distance: 100%; opacity: 0; }
  /* tail = the pause before the loop restarts */
  100% { offset-distance: 100%; opacity: 0; }
}

/* Fallback: no offset-path support — straight travel between the trimmed
   endpoints, which still starts and ends exactly on the arrow. */
.ob-root.ob-no-offset-path .ob-ghost {
  animation-name: ob-ghost-fly;
}

@keyframes ob-ghost-fly {
  0%   { transform: translate(0, 0); opacity: 0; }
  12%  { transform: translate(0, 0); opacity: 1; }
  62%  { transform: translate(var(--ob-ghost-dx), var(--ob-ghost-dy)); opacity: 1; }
  75%  { transform: translate(var(--ob-ghost-dx), var(--ob-ghost-dy)); opacity: 0; }
  100% { transform: translate(var(--ob-ghost-dx), var(--ob-ghost-dy)); opacity: 0; }
}

/* --------------------------------------------------------------------------
   8. TRAJECTORY ARROW
   Without it the flying sheet has no explained cause. Sits under the ghost,
   spans the same vector, and draws itself in ahead of the sheet.
   -------------------------------------------------------------------------- */
.ob-arrow {
  position: absolute;
  z-index: calc(var(--ob-z-card) - 1);
  pointer-events: none;
  display: none;
  overflow: visible;
}

.ob-root.is-ghost-on .ob-arrow { display: block; }

.ob-arrow path,
.ob-arrow line {
  stroke: var(--ob-brand);
  stroke-width: 1.5;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ob-arrow__shaft {
  stroke-dasharray: var(--ob-arrow-len, 200);
  stroke-dashoffset: var(--ob-arrow-len, 200);
  animation: ob-arrow-draw var(--ob-dur-ghost) var(--ob-ease) infinite;
}

.ob-arrow__head {
  opacity: 0;
  animation: ob-arrow-head var(--ob-dur-ghost) var(--ob-ease) infinite;
}

@keyframes ob-arrow-draw {
  0%, 8%    { stroke-dashoffset: var(--ob-arrow-len, 200); }
  55%       { stroke-dashoffset: 0; }
  72%       { stroke-dashoffset: 0; opacity: 1; }
  85%, 100% { stroke-dashoffset: 0; opacity: 0; }
}

@keyframes ob-arrow-head {
  0%, 45%   { opacity: 0; }
  60%, 72%  { opacity: 1; }
  85%, 100% { opacity: 0; }
}

/* --------------------------------------------------------------------------
   9. FILE STACK (step 1)
   Three sheets fanned into a stack, dropping into the gallery together.
   This is the "bring your files in" idea — plural by construction.
   -------------------------------------------------------------------------- */
.ob-stack {
  position: absolute;
  z-index: var(--ob-z-card);
  pointer-events: none;
  display: none;
  width: 3.3rem;
  height: 4.3rem;
}

.ob-root.is-stack-on .ob-stack { display: block; }

.ob-stack .ob-sheet {
  position: absolute;
  top: 0;
  left: 0;
  animation: ob-stack-drop var(--ob-dur-stack) var(--ob-ease) infinite;
}

/* Back-to-front: each sheet sits slightly up-left and lands a beat later. */
.ob-stack .ob-sheet:nth-child(1) {
  --ob-stack-x: -0.7rem;
  --ob-stack-y: -0.7rem;
  opacity: 0.55;
  animation-delay: 0ms;
}
.ob-stack .ob-sheet:nth-child(2) {
  --ob-stack-x: -0.35rem;
  --ob-stack-y: -0.35rem;
  opacity: 0.78;
  animation-delay: calc(140ms * var(--ob-speed));
}
.ob-stack .ob-sheet:nth-child(3) {
  --ob-stack-x: 0rem;
  --ob-stack-y: 0rem;
  animation-delay: calc(280ms * var(--ob-speed));
}

@keyframes ob-stack-drop {
  0% {
    transform: translate(var(--ob-stack-x), calc(var(--ob-stack-y) - 1.6rem));
    opacity: 0;
  }
  22% {
    transform: translate(var(--ob-stack-x), var(--ob-stack-y));
    opacity: 1;
  }
  70% {
    transform: translate(var(--ob-stack-x), var(--ob-stack-y));
    opacity: 1;
  }
  88%, 100% {
    transform: translate(var(--ob-stack-x), var(--ob-stack-y));
    opacity: 0;
  }
}

/* --------------------------------------------------------------------------
   9b. QUIET MODE — a hint that sits beside the UI, not over it
   Used by standalone hints (e.g. after the first saved export). The card is
   still anchored and still dismissible, but the page stays fully operable:
   no dimming, no click-catching, no attention-grabbing pulse.
   -------------------------------------------------------------------------- */
.ob-root.ob-quiet {
  /* Kills the spotlight's surrounding wash — .ob-spot paints it via box-shadow. */
  --ob-scrim: transparent;
}

/* Shutters exist to close the tour on an outside click. A quiet hint must not
   swallow interaction, so they become inert; only the card stays clickable. */
.ob-root.ob-quiet .ob-shutter {
  pointer-events: none;
}

.ob-root.ob-quiet .ob-pulse {
  display: none;
}

/* With the wash gone the cut-out reads as nothing at all, so outline the
   target instead — the card still needs to point at something. */
.ob-root.ob-quiet .ob-spot {
  box-shadow: 0 0 0 1px var(--ob-brand);
}

/* --------------------------------------------------------------------------
   10. PAGE STAGING
   Two steps point at elements the user cannot see yet. These rules live here,
   not in main.css, so the tour owns its own temporary presentation.
   -------------------------------------------------------------------------- */

/* The per-tile actions sit at opacity:0 until .template-item:hover
   (main.css:3181-3194). The tour adds this class for the duration of the
   "add to queue" step so the button it describes is actually on screen. */
.template-item.ob-force-actions .template-actions-wrapper {
  opacity: 1;
}

/* Same idea for the compact queue view (the narrow tier's export strip): a
   tile's export button is hover-only, so the "export one file" step pins the
   first tile's button open. */
#download-list.view-compact .ob-force-actions .download-preview-quick-btn {
  display: flex;
}

/* Sample queue row for the "export one file" step when nothing is queued.
   Mirrors .download-row's shape (main.css:6878) without borrowing its class —
   the queue counter matches .download-row and would report a phantom item. */
.ob-demo-row {
  display: grid;
  grid-template-columns: var(--dq-preview-size, 5.625rem) 1fr auto;
  gap: var(--space-6, 0.75rem);
  align-items: center;
  padding: var(--space-6, 0.75rem) var(--space-7, 0.875rem);
  border: 1px solid var(--color-border, #d8d8d8);
  border-radius: 0.625rem;
  background: var(--color-surface, #fff);
  box-shadow: var(--shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.05));
  /* Illustration only: never clickable, never draggable, never focusable. */
  pointer-events: none;
  user-select: none;
  opacity: 0.85;
}

.ob-demo-row__thumb {
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-sm, 0.25rem);
  background: var(--color-surface-muted, #f3f3f3);
  border: 1px solid var(--color-border, #d8d8d8);
}

.ob-demo-row__meta {
  min-width: 0;
}

.ob-demo-row__name {
  display: block;
  font-size: var(--font-size-sm, 0.75rem);
  /* --ob-* tokens are scoped to .ob-root; this row lives in the page, so it
     reads the app's own variables directly. */
  color: var(--color-text-muted, #7f7f7f);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ob-demo-row__export {
  font: inherit;
  font-size: var(--font-size-xs, 0.6875rem);
  line-height: 1;
  padding: 0 var(--space-6, 0.75rem);
  height: var(--control-height-sm, 1.6875rem);
  border-radius: var(--radius-control, 0.375rem);
  border: 1px solid var(--color-brand, #2bbb75);
  background: transparent;
  color: var(--color-brand, #2bbb75);
  white-space: nowrap;
}

/* Compact queue view: tiles instead of rows, so the sample is a tile too —
   thumbnail, name under it, the export button below. */
#download-list.view-compact .ob-demo-row {
  flex: 0 0 auto;
  width: var(--dq-preview-size, 4rem);
  grid-template-columns: 1fr;
  gap: var(--space-2, 0.25rem);
  padding: 0;
  border: 0;
  background: none;
  box-shadow: none;
}
#download-list.view-compact .ob-demo-row__thumb { aspect-ratio: 1 / 1; }
#download-list.view-compact .ob-demo-row__export { padding: 0 var(--space-3, 0.375rem); }

/* --------------------------------------------------------------------------
   11. REDUCED MOTION — one switch, kills everything
   Both the OS preference and the demo's manual toggle (.ob-reduced) route
   through the same rules. No partial animation survives.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .ob-root,
  .ob-root * {
    animation: none !important;
    transition: none !important;
  }
  .ob-root .ob-pulse { opacity: 0 !important; }
  .ob-root .ob-ghost,
  .ob-root .ob-arrow,
  .ob-root .ob-stack { display: none !important; }
  .ob-root .ob-card  { opacity: 1; transform: none; }
}

.ob-root.ob-reduced,
.ob-root.ob-reduced * {
  animation: none !important;
  transition: none !important;
}
.ob-root.ob-reduced .ob-pulse { opacity: 0 !important; }
.ob-root.ob-reduced .ob-ghost,
.ob-root.ob-reduced .ob-arrow,
.ob-root.ob-reduced .ob-stack { display: none !important; }
.ob-root.ob-reduced .ob-card  { opacity: 1; transform: none; }
