Nib social card

About

Integrating Ink directly into a SvelteKit project scatters BindExternalFunction calls, game-data registration, and story mechanics through the load/continue/choose plumbing; nothing is reusable and the reactive boundary is hard to reason about. Nib was extracted from The Work once the generic runtime proved separable. The fix is a single injection seam: loadStory() handles BOM stripping, the error handler, and an optional storylet stub, then calls onInit(ink) after story creation but before playback. All game-specific binding lives there and nowhere else. The reactivity problem is handled by a tick counter incremented at the end of every continue(); because the UI reads tick inside a $derived, any getVariable() call re-evaluates whenever the story advances. inkjs is a plain mutable object with no Svelte awareness; the tick counter is the minimal bridge. The full runtime is two files. Drop the directory into any SvelteKit project, write an onInit, and own the code.

Highlights

  • Single onInit injection seam: game-specific BindExternalFunction calls and data registration in one callback, nowhere else.
  • tick counter bridges non-reactive inkjs to Svelte 5 runes: UI reads tick inside $derived, so getVariable() re-evaluates on every continue().
  • Reactive story state via $state/$derived: canContinue, currentTags, history update automatically.
  • Tag parsing for CLEAR, mood:x, and class:x: CLEAR wipes history, mood: writes data-mood for CSS, bare tags become class names.
  • Copy-paste distribution: two files, no package.json, designed to be owned and edited per project.

By the numbers

Commits
1
Lines added
+343
Lines removed
−0
Source files
254