COUNTSCENE WIDGET SDK · VERSION 1

Small experiences.
Made entirely yours.

Build interactive widgets with JavaScript, style them with CSS, and give them only the countdown and data they need.

Your first widget

  1. Open Widget Lab and choose a starter or Blank widget.
  2. Edit its JavaScript, CSS and default settings. Declare countdown or data access only when needed.
  3. Choose sample countdown and JSON data in the preview. Run preview; code errors and rejected views appear in the player. Stop and restart begin a new session.
  4. Save the project. Export its JSON package to keep a portable source copy.
  5. Open Countdown pages, add an SDK widget, and use Replace with saved project in its controls.
  6. Choose the actual countdown and data source, adjust its settings, and save the page. Changing a project later does not automatically replace page copies.
Afterglow.on('hello', () => {
  Afterglow.render({
    type: 'box',
    children: [
      { type: 'heading', text: 'A little magic, made by you.' },
      { type: 'text', text: 'Your first interaction is working.' }
    ]
  });
});

Afterglow.render({
  type: 'box',
  children: [
    { type: 'heading', text: 'Hello, possibility.' },
    { type: 'button', text: 'Make something happen', action: 'hello' }
  ]
});

The execution boundary

Creator JavaScript runs in a dedicated Web Worker inside a frame with script permission but no same-origin permission. The worker receives no DOM, account identity, auth tokens, app state, payment access or database API. The trusted frame renders a validated component tree. It never evaluates widget-provided HTML.

Networking and external imports are blocked by Content Security Policy; common network, nested-worker and broadcast APIs are also removed from the worker surface. The existing Custom HTML / CSS page widget remains a separate presentation-only option, with no JavaScript. These browser mechanisms are described in MDN’s Web Worker guide and worker CSP reference.

Visitors choose Start experience before code runs. The player can be stopped, pauses when its browser tab is hidden, and terminates unresponsive workers after roughly four seconds of missing heartbeats while visible. It rejects excessive messages and render rates. This is browser isolation with runtime limits, not a dedicated virtual machine or a hard per-widget memory quota. Resource-heavy code still needs careful review and testing.

SDK reference

The CountScene Widget SDK keeps the legacy Afterglow JavaScript global so existing widgets continue to run unchanged.

APIBehavior
Afterglow.versionThe runtime SDK version, currently 1.0.0.
Afterglow.render(view)Send a component tree to the renderer. Target at most 30 updates per second; rejected trees stop the widget.
Afterglow.on(action, callback)Register one handler per action ID. Receives {action, at}. Returns an unsubscribe function. A later registration replaces that action’s handler.
Afterglow.onContext(callback)Subscribe to copied context updates. Called once immediately with initial context and again when the host sends updates, normally each second. Returns an unsubscribe function.
Afterglow.getContext()Return a JSON copy of the current permitted context. No updates to it change the page or source records.
Afterglow.state.get()Return a copy of widget-local session state; initially an empty object.
Afterglow.state.set(value)Replace that state with JSON up to 8,000 characters. It lasts only in the running worker; it is not a saved profile or cross-device score.

View components

Every node uses type and may include text, className and children. Use box, row and grid to arrange children. Leaf components display text. CSS stays inside the widget’s frame.

TypeUseExtra properties
boxVertical groupchildren
rowWrapping horizontal groupchildren
gridThree-column grid by default; customize in CSSchildren
headingSection headingtext
textBody copytext
metricLarge number or short valuetext
badgeSmall status or categorytext
buttonKeyboard-accessible actiontext, action, disabled
progressNative progress barvalue from 0–100; text provides its accessible label

Views allow at most 120 nodes, 8 nested levels and 2,000 characters per text field. Actions use 1–60 letters, numbers, underscores or hyphens. CSS class names use letters, numbers, spaces, underscores and hyphens. Arbitrary tags, URLs, DOM attributes, event-handler strings and injected HTML are not part of the SDK.

Afterglow.render({
  type: 'box', className: 'portal', children: [
    { type: 'badge', text: 'A NEW CHAPTER' },
    { type: 'metric', text: '42' },
    { type: 'progress', value: 42, text: 'Milestone progress' },
    { type: 'button', text: 'Explore', action: 'explore' }
  ]
});

// In the CSS tab:
// .portal { padding: 20px; border: 1px solid #c6a1ed; }
// .sdk-metric { color: #e5c6ff; }
// .sdk-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

Context and permissions

Context always includes sdk, now and settings. Settings hold up to 16 named string, finite number or boolean values. Each string allows 500 characters. They are visible in exported packages and rendered page data; never store secrets or credentials in them.

Declaring timer access permits only the countdown selected by the page creator. Context then includes timer with title, targetAt, remainingMs, days, hours, minutes, seconds, progress and expired. Countdown values follow the source’s repeat/count-up behavior; remainingMs stops at zero for an expired one-time timer. No reveal text, private files or timer-editing controls are passed.

Declaring data access permits only the selected source’s JSON. Context includes data, dataStatus (ready, stale or unavailable) and dataUpdatedAt. Missing or blocked sources return null data. A widget without the permission receives no data fields. The code cannot select another source or request more access.

Afterglow.onContext(context => {
  const seconds = context.timer
    ? Math.ceil(context.timer.remainingMs / 1000)
    : null;
  Afterglow.render({
    type: 'box', children: [
      { type: 'heading', text: String(context.settings.heading || 'Coming soon') },
      { type: 'metric', text: seconds === null ? '—' : String(seconds) },
      { type: 'text', text: context.dataStatus === 'stale'
          ? 'Showing the last available update.'
          : 'Every second, a little closer.' }
    ]
  });
});

Controlled dynamic data

In Widget Lab → Data sources, create editable JSON, a public GitHub JSON feed, or a latest-release feed. Sources start private. Enable Allow on shared pages before using one on a public or unlisted page. Disabling or deleting a source removes its data on the next page refresh; previously viewed data cannot be recalled.

  • Editable JSON: small objects, arrays, text, finite numbers, booleans and null.
  • GitHub JSON: an HTTPS .json file on raw.githubusercontent.com. Use a path like /OWNER/REPO/main/data.json.
  • Latest release: https://api.github.com/repos/OWNER/REPO/releases/latest, projected to name, tag and publishedAt.
  • Optional path: dot-separated object keys or array indices, such as stats.milestone or questions.0.

Only those exact public hosts and endpoint shapes are accepted. No custom headers, credentials, query strings, redirects, private repositories, arbitrary webhooks or direct database connections. Feeds are fetched by the server on demand with an eight-second timeout and 64 KB download cap. Selected JSON is limited to 16,000 characters, 400 values and 7 nested levels. A shared cache and refresh lease limit upstream reads to roughly once a minute; the page checks for changes every 30 seconds. Feed limits and outages can delay updates. See GitHub’s release endpoint documentation.

{
  "questions": [
    {
      "question": "How many seconds are in an hour?",
      "choices": [
        "600",
        "3600",
        "86400"
      ],
      "answer": 1
    }
  ]
}

Use the JSON above with Curiosity cards. Answer is a zero-based choice index. Quiz answers are inspectable by visitors; this is a casual game, not an exam or prize system.

Four working starters

Pulse match

Stop the moving pulse as close to 50 as you can.

Time portal

Turn a linked countdown into a responsive progress portal.

Curiosity cards

An interactive quiz using your own questions or a connected dataset.

Data spotlight

Display a release announcement, milestone or changing metric.

Games run locally in the visitor’s active session. There are no shared scores, prizes, multiplayer sessions or automatic user-data collection. JavaScript timers can be throttled by browsers, so timing-game scores are for fun.

Packages, versions and marketplace

A package contains sdk: 1, name, description, version, category, code, css, permissions and settings. Version uses three numbers such as 1.0.0. JavaScript is capped at 18,000 characters and CSS at 6,000. Import/export uses a CountScene widget JSON file. A separate recovery export retains unfinished work.

Save a project, choose Create listing, confirm distribution rights and submit. The owner reviews the exact saved code, requested access and preview. Updating source code or listing details withdraws approval until reviewed again. Free acquisition works without billing. Paid submissions require connected Stripe and seller onboarding; paid collections unlock only after verified payment.

Installing creates an independent project snapshot. Repeated installs of the same listing return its existing installed project. Page copies and acquired versions never update automatically. To change a page, load the desired saved project again in that SDK widget’s controls. Marketplace packages contain no page data bindings. Whole-page template acquisitions clear data-source bindings so buyers choose their own sources.

Withdrawing a listing stops new acquisitions; existing copied code remains with its owners. A business owner can pause the entire SDK runtime from Widget Lab → Owner review. Online players recheck availability approximately every 30 seconds and stop if they cannot verify it. A withdrawn listing is not a remote deletion of all its copies.

Limits and exports

PlanWidget projectsData sources
Free105
Creator5050
Studio20050

Each countdown page allows up to six SDK widgets, within its overall 24-widget and 65,000-character document limits. Free page branding and existing page/countdown allowances still apply.

Editable page bundles preserve SDK packages and same-account source references. Shared marketplace page templates remove those source bindings. Offline visual HTML currently replaces SDK widgets with a clearly labeled notice; it does not run SDK JavaScript or dynamic feeds. Existing presentation-only HTML/CSS widgets and supported built-in games keep their offline behavior.

The Site remains within its current audience. Public page settings do not open the whole Site to external visitors. Browser QA, external payment testing, adversarial review and load testing remain separate from the automated source and API checks used for this release.