Ink stories that wake up inside static sites.
Sai is a small browser embedder for compiled Ink stories. It is not an Ink
engine; it uses inkjs for that. Sai owns the article-shaped part: loading a
story into an existing page, reading a visual manifest, rendering transcript,
visual-novel, or cinematic layouts, and staying pleasant to drop into Quartz,
Eleventy, Astro, Hugo, plain HTML, or whatever shrine of markup you dragged
home at midnight.
The name is the pitch: ink that becomes a living scene.
Most Ink web players answer: "Can I play this story in a browser?"
Sai answers: "Can this story become part of a page?"
It is for interactive essays, lore posts, documentation rituals, cheap visual novels, pitch decks, character sermons, branching articles, and other story objects that want to live inside a site instead of becoming a separate web app.
Copy the built files from dist/ into your site's static assets:
sai.js
sai.css
ink.js
Add the assets to a page:
<link rel="stylesheet" href="/static/sai/sai.css" />
<script defer src="/static/sai/sai.js"></script>Embed a story:
<div
class="sai-player"
data-ink-format="visual-novel"
data-ink-story="/stories/agora/story.ink.json"
data-visual-manifest="/stories/agora/visual-manifest.json"
data-scene-label="The Aquarium Agora"
></div>Sai auto-initialises every .sai-player when the document loads. It also
listens for Quartz-style nav events after client-side page swaps.
<div class="sai-player" data-ink-story="/story.ink.json"></div>The default format is a compact interactive-fiction transcript. It is the simplest Raven Collective-style shape: text, choices, variables when useful, and no stage machinery.
<div
class="sai-player"
data-ink-format="visual-novel"
data-ink-story="/story.ink.json"
data-visual-manifest="/visual-manifest.json"
></div>Visual-novel mode renders one scene image, a fixed dialogue bar, speaker avatar, speaker name, line text, optional character sprites, optional DOM cards, and a right-side option rail. Ink tags drive the speaker, scene, staged sprites, and staged page elements:
# speaker: Void
# scene: agora
# avatar: void
# sprite: welcome@left
# sprites: Void.welcome@left, Aqua.listen@right
# dom: roadmap, receipts
The world is full of systems that say one thing and reward another.If a speaker has sprites in the visual manifest, Sai shows the speaker's
default sprite automatically. Use # sprite: none for lines that should stage
only background, DOM cards, and dialogue.
The dom tag reads card definitions from the visual manifest and clones
matching page DOM into the scene stage. This is for static-site tours where the
story frames canonical Quartz/HTML content without making the story file a
second source of truth.
<div
class="sai-player"
data-ink-format="cinematic"
data-ink-story="/deck.ink.json"
data-visual-manifest="/deck.visual-manifest.json"
></div>Cinematic mode is for slide/deck-like fragments: background plates, captions, references, and short blocks of text.
data-ink-story: required URL for compiled Ink JSON.data-ink-format:raven,visual-novel, orcinematic.data-visual-manifest: optional visual manifest URL.data-scene-label: optional fallback scene label for visual-novel mode.data-ink-title: optional player title/status text.data-ink-runtime: optional URL forink.js.data-sai-runtime: alias fordata-ink-runtime.data-show-variables="true": show Ink variables in visual modes.
Legacy .aetheria-ink-player containers are still initialised so existing
GameCult pages can migrate without ritual screaming. New work should use
.sai-player.
window.Sai.init(document.querySelector(".sai-player"));
window.Sai.initAll();
console.log(window.Sai.version);By default, sai.js loads ink.js from the same directory as itself. Override
that with data-ink-runtime, data-sai-runtime, or window.Sai.runtimeSrc.
See docs/manifest.md.
Sai also ships eve.js, a portable projection module for Eve/CultMesh clients.
It turns Sai visual-novel state into a retained gamecult.eve.surface.v1
component tree instead of browser DOM. Browser, iOS, Android, Direct2D, and
future renderers can consume the same tree and send back story.* or
style.patch commands.
See docs/eve.md.
npm install
npm run build
npm run checkCompile the example story after dependencies are installed:
npm run compile:exampleOpen examples/static-site/index.html through a local static server. Browsers
are right to dislike fetch() from file://; do not take it personally.
Sai owns:
- browser-side Ink loading through
inkjs - player DOM and mode rendering
- visual manifest interpretation
- Eve surface projection for portable renderers
- default CSS and theme variables
- static-site auto-init
Sai does not own:
- authoring Ink
- compiling
.inkto.ink.json - Eve/CultMesh transport or provider command acceptance
- Quartz page chrome
- GameCult-specific assets, lore, or characters
- a full VN/game engine loop
The brush is sharp. The painting is yours.