Presence
[SCAFFOLD - Baki to revise] Five states, one modifier layer, the room remembering you
Visitor presence
Second axis beside platform-recency. Platform-recency asks “how new is this content?”; visitor-presence asks “has this person been here, and how deeply?”. One storage schema, one state machine, two tiers of consent, one visualization layer.
The state machine has five states, computed from a per-node record in
src/lib/presence.ts:
unseen- no record. The default for every node until first arrival.glanced- any focus time, scroll depth, or interaction. Arrived, briefly.read- ≥ 8s focused AND ≥ 40% scroll depth.dwelled- ≥ 30s focused AND ≥ 80% scroll AND at least one interaction.returned-returnCount ≥ 2. A second-day revisit trumps everything else.
The thresholds live as named tokens (THRESHOLDS in presence.ts) so they can be retuned
against real content without touching the classifier.
Storage routes by consent tier: ambient writes to sessionStorage and dies with the tab;
local writes to localStorage under the baki.visitor.v1.<slug> keyspace and survives
across visits; telemetry piggy-backs on localStorage and opts into an aggregate echo
(server POST reserved for a later pass). Visitors switch tiers in one click via the
VoidStamp - a section inside the Preferences page (system rail → preferences). The
forget-me action flushes every baki.visitor.v1.* key in both stores and resets the tier
back to ambient, so one interaction is a true “start fresh”.
Energy visualization
Eight fields run atop the substrate. Each one takes one semantic signal (tone, echoes, signals, recency, focus, presence) and translates it into motion or light:
- Field 1 · SubstrateTone - tone-reactive void. Soft radial glow tracking the focused card, pulsing on the card’s declared tone cadence.
- Field 2 · EchoWeave - authored-connection threads. Bezier splines from the focused
card to each of its manual
echoes, with a gradient “packet” that flows source → target on a 2.8s loop. Threads sit atz=1under an SVG-levelblur(2px)so they read as haze behind content rather than wires across it. - Field 3 · TagConstellation - hovering a tag chip makes its shared-token siblings pulse in unison at the resonance cadence.
- Field 4 · TitleGravity - recency-as-force. Newest card breathes brightest, archive cards fade, pinned pages sit as stable rims rather than gravity wells.
- Field 5 · SignalPulse - one-shot propagation along echo threads when a recent signal is first viewed (localStorage-gated, honors Hard-No #9).
- Field 6 · SemanticLines - shared-tag splines on hover. Reactive social-proof layer drawn between the hovered element and its semantic siblings.
- Field 7 · TitleBreath - focused card’s title gains weight, tracking, and shadow over 180ms on focus. Counter-scale preserved - no glyph-size change, no glyph-scaling transform.
- Field 8 · PresenceOverlay - see below. The modifier layer for the visitor-presence axis, not a new visual field of its own.
Energy Viz obeys the Charter: resonance/transit/reaction motions suppress under
prefers-reduced-motion; semantic channels (focus state, recency, presence state)
survive as static end-states.
The location indicator
A small warm pill on the rail’s left edge tracks where focus currently lives. It targets the scroll-active subrail heading (or the active rail item when no heading has been reached), anchoring to the target’s left edge so it respects subrail indent flush with whatever row it points to.
Y position interpolates smoothly between the current rail target and the next one based on scroll progress within the active section - the pill slides down continuously as a section is read, arriving at the next heading just as the focus line crosses it. Per-frame ticker write; no CSS transition fights the continuous flow.
When the pill lands on a subrail heading, that heading picks up an immediate warm tan (via PresenceOverlay §4) so indicator and target reinforce each other visually. Baki’s persistent dwell-tan (HeadingPresenceHalo) builds up separately - headings you’ve spent time in stay warm; the indicator’s live glow tells you where you are right now.
The modifier layer
Presence is a modifier on platform-recency, not a new visual layer of its own.
PresenceOverlay writes data-presence-state on every [data-rail-id] and
[data-page-id] element, plus a data-presence-focus-state on <html> so fixed-position
overlays (SubstrateTone lives outside the page subtree) can compose off it. Its shipped
rules modulate three of the Energy Viz fields:
- SubstrateTone dampens on familiar pages: read → 0.78, dwelled → 0.5 with −12° hue shift, returned → 0.35 with −24° hue shift. Familiar ground reads distinctly cooler than new encounter.
- TitleBreath warms its header shadow alpha on returned pages (0.5, up from 0.15
base) via the existing
--title-breath-shadow-alphavar. No edit required inside TitleBreath itself. - Rail items pick up visible state differentiation: unseen → 1px hollow ring at 18%
alpha, glanced → 1px at 10%, returned → 3px warm left-edge accent stripe (distinct from
orbital/newest stripes). Suppressed wherever
[data-gravity]or[data-rail-heat]is already painting so recency + visit markers still win the cascade.
Reactivity: the overlay repaints on presence:change, presence:forget, tier changes,
and canvas:focus-changed. A 2s poll catches late-mounted rail items when the spine
filter flips (pages → tags → system).