Add vellum: layered browsing visualization experiment - #351
Draft
spencerc99 wants to merge 5 commits into
Draft
Conversation
New visualization at /vellum: each unit of browsing (person x page visit, or domain/day groupings) renders as a translucent sheet — ghosted page iframe, scroll-viewport band, and cursor-trail ink — stacked into one normalized browser-window frame with multiply blending, like overlapping prints on vellum paper. A spread control fans the stack apart; every parameter is adjustable in a spec-driven dev panel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWX2qwLcDT8B83HBHBmJ1V
Deploying we-were-online-website with
|
| Latest commit: |
82ae068
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://a6302187.we-were-online-website.pages.dev |
| Branch Preview URL: | https://claude-layered-browsing-viz.we-were-online-website.pages.dev |
commit: |
- Apply fan offsets via left/top instead of transform: transform creates a CSS stacking context, which isolates each sheet's mix-blend-mode and silently kills cross-sheet multiply blending (the core vellum effect). Rotation still uses transform but only engages while fanned. - Blend the whole PagePlate container instead of the inner ghost title (opacity already isolated the container, so the inner blend never reached the stack). - Compute sheet time bounds with a loop (Math.min spread over a day-sized group risks a call-stack RangeError). - Align scroll-history ticks with the band's travel mapping; gate them behind a new showScrollHistory param (default off — they stack into barcode noise across sheets). - Only engage hover-lift once spread > 0.05 so the concentrated stack doesn't dim on every mouse move; clear hover when spread returns to 0. - Quieter defaults after screenshot review: pageOpacity 0.2, scrollFrameOpacity 0.35, band fill 0.03; ghost titles now place and size deterministically per sheet so stacked titles overprint like letterpress runs instead of piling into mush. - pr-screenshots/: captures for the PR description (removed in a follow-up commit; raw URLs stay pinned to this sha). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWX2qwLcDT8B83HBHBmJ1V
The PR body references these images via raw URLs pinned to the previous commit's sha, which remain valid; the merged tree stays clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWX2qwLcDT8B83HBHBmJ1V
…e trail style
Real-data testing surfaced problems fixture data masked:
- Page iframes default OFF: real sites behind bot protection render
Cloudflare challenge pages ('max challenge attempts') inside embedded
iframes — a challenge can never pass in a sandboxed cross-origin
cookie-less iframe, and the failure is undetectable cross-origin. The
top-of-stack sheets are exactly the iframe-eligible ones, so the top
of the pile got washed out. Ghost titles are decoupled from the page
toggle (registry now gates plates via an isEnabled predicate).
- Ink defaults to riso (per-sheet pigment): with single-participant real
data, participant mode gave every sheet the same color, collapsing the
stack into one unreadable scribble.
- Sheet backing color is now a param (sheetColor + existing sheetOpacity)
instead of hardcoded white.
- TrailsPlate rewritten from canvas strokes to the house cursor-trail
style: SVG + perfect-freehand filled outlines with tapered ends via
@movement/utils/trailAnimation, blunt cap on the still-drawing head,
click/hold marks reusing CLICK_DEFAULTS ring+core proportions.
- Settings storage key bumped to v2 so changed defaults override stored
v1 values.
- pr-screenshots/: refreshed captures (removed in follow-up commit; PR
body pins raw URLs to this sha).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWX2qwLcDT8B83HBHBmJ1V
PR body pins raw URLs to the previous commit's sha; tree stays clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWX2qwLcDT8B83HBHBmJ1V
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
New visualization experiment at
wewere.online/vellum, inspired by printing the cursor/scroll visualizations on translucent vellum paper and overlapping the sheets.Each unit of browsing — by default one person's visit to one page — becomes a translucent sheet, normalized into a single shared browser-window frame. Sheets stack on a warm paper background and composite with
mix-blend-mode: multiply, so overlapping ink darkens the way translucent prints do. Each sheet is composed of plates (bottom → top):@movementgeometry as the portrait/archive visualizations) plus click/hold marks, progressively drawn during playbackA spread control fans the stack apart (progressive per-sheet offset + slight seeded rotation) so you can peek between the layers, mirroring lifting physical vellum sheets; hovering a fanned sheet lifts it and dims the others.
Screenshots
Captured against deterministic fixture data (the sandbox can't reach the worker); the branch preview shows it with real data.
Full ink (animation off) — riso pigment per sheet, ghost titles overprinting at seeded offsets:

Mid-playback — trails reveal progressively; the still-drawing head gets a blunt cap, finished strokes taper:

Fanned apart (spread 0.55) — cascading captions per sheet, physical shadows:

Dev panel — every parameter, spec-driven:

Domain grouping + mono ink — one sheet per domain, single-pigment monoprint look:

Built as a flexible base
grouping.ts):page-visit/domain/daytoday; calendar views and other groupings slot in as new modesplates/registry.ts) with per-plateisEnabled(settings)predicates:Sheet.tsxiterates it, so future plates (typing, navigation) plug in without touching the compositorsettings.ts): ~30 params across data / composition / fan / page / scroll / trails / playback groups, each declared once and auto-rendered in the dev panel (⚙ bottom-right), persisted to localStorageuseArchiveEventshook (day picker fed bydayCounts, server-side domain filter supported)Findings from real-data testing
Fixture data masked three problems that live data surfaced; all fixed here:
participantink mode gave every sheet the same color, collapsing the stack into one unreadable scribble. Participant mode remains available for multi-person days.@movement/utils/trailAnimation(shared with the portrait/archive visualizations), and click/hold marks reuseCLICK_DEFAULTSring+core proportions — instead of the earlier bare canvas strokes.sheetColor+sheetOpacity); settings storage key bumped to v2 so the changed defaults override stored v1 values.Compositing note (the subtle bit)
Fan offsets are applied via
left/top, nottransform— a transform on the sheet root creates a CSS stacking context, which isolates each sheet'smix-blend-modeand silently kills cross-sheet multiply blending (the entire vellum effect). Rotation still uses transform but only engages while fanned, where sheets have physically separated. Same rule governs why the PagePlate container carries the blend itself (itsopacityalready isolates its children). Comments inSheet.tsx/PagePlate.tsxexplain this so it doesn't get "optimized" back.Known limitations
domain/daysheets use a first-seen representative URL/color for their page render and ink.Verification
includeonly coverssrc/, so this was checked with an explicit include — pre-existing quirk)extension/website/**) — no changeset, noPENDING.mdbullet, per repo release conventions🤖 Generated with Claude Code
https://claude.ai/code/session_01VWX2qwLcDT8B83HBHBmJ1V