• English
  • Refusals and limits

    The screens that say no, and how to decline without looking broken.

    See it

    The three tones and the roster dot that goes with each. Switching the tone swaps the copy and the bezel light; the action slot is whatever the way out is.

    Held back

    This action tried to reach something it is not allowed to touch.

    Ledger, restricted
    import { useState } from 'react';
    import { AgentAvatar, AvatarStatusDot, MarkStageProvider } from '@aora-bot/react';
    import { RestrictedScene, type RestrictedTone } from '@aora-bot/surfaces';
    
    const tones: RestrictedTone[] = ['restricted', 'unauthorised', 'offline'];
    const dot: Record<RestrictedTone, { tone: 'error' | 'offline'; label: string }> = {
      restricted: { tone: 'error', label: 'restricted' },
      unauthorised: { tone: 'error', label: 'sign in required' },
      offline: { tone: 'offline', label: 'offline' },
    };
    
    export default function Demo() {
      const [tone, setTone] = useState<RestrictedTone>('restricted');
    
      return (
        <MarkStageProvider>
          <div className="site-demo site-demo--stack">
            <RestrictedScene
              tone={tone}
              state={tone === 'offline' ? 'sleeping' : 'refusing'}
              color="orange"
              rotateMs={0}
              action={
                <button
                  className="site-btn"
                  onClick={() => setTone(tones[(tones.indexOf(tone) + 1) % tones.length]!)}
                >
                  next tone
                </button>
              }
            />
            <div className="site-demo__row">
              <span className="site-demo__avatar">
                <AgentAvatar
                  agent={{ id: 'ledger', name: 'Ledger' }}
                  state={tone === 'offline' ? 'sleeping' : 'idle'}
                  sizePx={40}
                />
                <span className="site-demo__dot">
                  <AvatarStatusDot tone={dot[tone].tone} label={dot[tone].label} />
                </span>
              </span>
              <span className="site-demo__label">Ledger, {dot[tone].label}</span>
            </div>
          </div>
        </MarkStageProvider>
      );
    }

    rotateMs={0} holds the first shape; the default rotates every twelve seconds and stops under reduced motion, because a silhouette morph is motion even when nothing crosses the screen. The roster dot uses error for a policy refusal and offline for a dead service — the same distinction the scene draws with its bezel light.

    When to use it

    The page you land on when something says no: a plan limit, a region block, a sign-in that expired, a service that is down. It is also the screen where a character costs least, for the reason RestrictedScene gives.

    Components

    • RestrictedScene — the whole dead end: bezel, character, title, body, a way out. tone picks the copy and the bezel light.
    • DeviceBezel — the frame on its own, when the scene is yours to lay out.
    • CharacterMark — refusing for a no, alerting for a something-went-wrong.
    • AvatarStatusDot — the error and offline tones, for the same news in a roster corner.
    • HudPlate — a timer dial when the limit lifts by itself (a rate limit, a cooldown), because a ring that drains tells the person to wait rather than to leave; sync with state: 'error' when the service is down, which says the lifecycle failed without inventing a figure.

    How the states move

    FromToWhen
    anyalertinga request failed; the bang belt snaps in, protocol id 47
    alertingrefusingthe failure is a policy, not an accident — id 49
    alertingidleit was transient and a retry went through
    refusingidlethe person is granted access, upgrades, or signs in
    anysleepingthe service is offline; tone="offline" turns the bezel light off

    alerting is loud on purpose and is meant to be brief. refusing is the state that gets stared at, so it is quiet: a frown, narrowed eyes, no belt, and the scene rotates the silhouette every twelve seconds so a long stare does not feel like a freeze.

    What each state looks like

    StateEyesMouthEffectIdHUD
    alertingAlertingdotWide → dotobang34—
    refusingRefusingcross → dotHalfwavybang38—
    sleepingSleepingcurvenonezz00clock
    idleIdledot → dot → dot → dotLeft → dot → dotRightnone—02—

    bang arrives with one overshoot and then pulses on a two-second period rather than blinking: at a glance that is the difference between "look at this" and an error light. refusing keeps its frown even in line-face mode, where most negative states relax to a flat line — a refusal without a frown reads as indifference.

    Next