• English
  • Onboarding

    A first run that introduces the product and ends with an agent of your own.

    See it

    A three-step scene: two characters arrive, one hands the work to the other, and the receiver lands in the avatar slot. Stepping manually pauses the autoplay, because someone who reached for the buttons is driving.

    First run
    step 1 / 3, playing
    import { useState } from 'react';
    import { CharacterMark } from '@aora-bot/react';
    import { AvatarSlot, CastStage, DemoDesktop, useCastScene, type CastStep } from '@aora-bot/surfaces';
    
    const members = [
      { id: 'guide', shape: 'cloud', color: 'cyan', bobPx: 4 },
      { id: 'mine', shape: 'pear', color: 'green', bobPx: 3, bobMs: 4600 },
    ] as const;
    
    const steps: CastStep[] = [
      {
        id: 'arrive',
        durationMs: 2600,
        positions: { guide: { x: -70, y: 0, state: 'spawning' }, mine: { x: 70, y: 0, state: 'spawning' } },
      },
      {
        id: 'handoff',
        durationMs: 2200,
        positions: { guide: { x: -70, y: 0, state: 'sending' }, mine: { x: 70, y: 0, state: 'receiving' } },
      },
      {
        id: 'land',
        durationMs: 0,
        positions: {
          guide: { x: -90, y: -40, scale: 0.7, state: 'happy' },
          mine: { x: 0, y: 70, scale: 0.8, state: 'idle' },
        },
      },
    ];
    
    export default function Demo() {
      const scene = useCastScene(steps, { autoplay: true });
      const [arrived, setArrived] = useState(false);
      const landed = scene.stepIndex === steps.length - 1;
    
      return (
        <div className="site-demo site-demo--stack">
          <DemoDesktop title="First run" widthPx={300} style={{ maxWidth: '100%' }}>
            <CastStage
              members={members}
              steps={steps}
              stepIndex={scene.stepIndex}
              widthPx={280}
              heightPx={220}
              sizePx={72}
              onArrive={(id, index) => id === 'mine' && index === steps.length - 1 && setArrived(true)}
            >
              <span className="site-demo__slot">
                <AvatarSlot arrived={landed && arrived} sizePx={64}>
                  <CharacterMark shape="pear" color="green" sizePx={56} />
                </AvatarSlot>
              </span>
            </CastStage>
          </DemoDesktop>
          <div className="site-controls">
            <button className="site-btn" onClick={scene.prev}>
              prev
            </button>
            <button className="site-btn" onClick={scene.next}>
              next
            </button>
            <button
              className="site-btn"
              onClick={() => {
                setArrived(false);
                scene.goTo(0);
                scene.play();
              }}
            >
              replay
            </button>
            <span className="site-demo__label">
              step {scene.stepIndex + 1} / {steps.length}, {scene.playing ? 'playing' : 'paused'}
            </span>
          </div>
        </div>
      );
    }

    The moves are CSS transitions with a linear() easing sampled from the engine's own spring, so the glide overshoots like the faces do without a second frame loop. The slot is positioned with ordinary CSS: it does not know where it is, which is what lets a host put it in a form beside the scene instead of inside it.

    When to use it

    The first run. Nothing has happened yet, the product has to show what it does without being used, and someone has to end up with an agent of their own. The character wakes up with the product, walks through a short scripted scene, and lands in the slot that becomes the user's avatar — so the thing they were watching is the thing they now have.

    Components

    • CastStage and useCastScene — several characters moving through a script, with AvatarSlot as the place one of them ends up.
    • DemoDesktop, JobBubbles, SuggestionCards, LandingWait, HandOff and Composer — the walkthrough pieces: a window to put a demo in, work in flight, three starting points, the empty screen, one agent passing to another, and an input the character pays attention to.
    • CharacterMark — onSettle tells the host when spawning or waking has run its course.

    How the states move

    FromToWhen
    —spawningthe scene opens; the gather belt draws itself in
    spawningidlesettles by itself after 2 s (STATE_SETTLE_TO)
    idlewakingthe user's first input
    wakingidlesettles by itself after 1.6 s
    idlecuriousa suggestion card is offered
    idleworkingthe job bubbles show work in flight
    sendingreceivingone agent hands off to another
    listeningcomposingthe composer gains focus, then a keystroke

    spawning and waking are moments, not conditions. The engine owns their clock and reports through onSettle, so a host's own state can follow without a duplicate timer. Under reduced motion the settle still fires; only the flourish is skipped.

    What each state looks like

    StateEyesMouthEffectIdHUD
    spawningSpawningdotWide → dotdotgather08—
    wakingWakingcurve → dotHalf → dotdot—0107—
    idleIdledot → dot → dot → dotLeft → dot → dotRightnone—02—
    curiousCuriousdotWide → dotLeft → dotWide → dotRightdot—03—
    sendingSendingdot → archsmilesend44—
    receivingReceivingdotWide → dotdotfall31—
    listeningListeningdotLeft → dot → dotLeftnone—3537—
    composingComposingdotDown → dotcat—39—

    spawning is the one belt here: five particles spiral in for two seconds and then the belt behaves like progress. waking bounces on entry and opens its eyes in three steps. sending and receiving are a matched pair — one leans away as the other catches — which is why HandOff needs nothing travelling between them.

    Next