Imported from wwizark/wwizark.github.io (
AGENTS.md). Install upstream withnpx skills add wwizark/wwizark.github.io. Copyright stays with the author.
AGENTS.md — working guide for this repository
This file is the source of truth for how this website is built and the vocabulary we use to talk about it. Any agent (or person) making changes MUST:
- Read this file first and follow its conventions.
- Keep it updated — whenever we introduce a new concept, rename something, change a convention, or add a canvas/section, update the glossary and conventions here in the same change.
If something in the code contradicts this file, treat it as a bug in one of them and raise it rather than silently diverging.
Glossary
The front page (index.html) is a looping 1-D canvas ring. Learn these
terms — the code and our conversations use them precisely.
| Term | Meaning |
|---|---|
| Canvas | One full-screen "page" in the system: Home, Photography, Work, Notes, … |
| Detail view | The current canvas expanded (expansion ≈ 1): big logo, its content readable/scrollable. |
| Bird-eye view | The current canvas collapsed (expansion ≈ 0): shrunk logo, content hidden, neighbours enlarged, watermark shown, and the dock (nav bar) always visible with the title docked. |
Ring (ORDER) |
The canvases sit on a 1-D loop: ['photos','home','work','notes'], wrapping (Notes → Photography). Each canvas's index is its ring slot. |
pos |
The continuous ring position the screen is centred on; an integer centres that canvas. Panning moves pos the shortest way round the ring, so it loops. |
ringRel(i) |
A canvas's signed distance from pos, in (-N/2, N/2]. 0 = centred (the full open page); ±1 = the two neighbours peeking as logos at the left/right edges; ` |
| Marker | The element representing a canvas: the camera = Photography, ZW = Home, "Work"/"Notes" boxes. The centred logo is expanded by default (expansion, compact↔full); tapping it collapses/expands (it does NOT navigate) — collapsing also hides that page's content and enlarges the neighbour logos (the nav view). On scroll the centred logo shrinks and docks into the centre of the sticky nav (stays visible; the dots fade out). Neighbours peek and stay fixed — scrolling the current canvas doesn't move or fade them. Tapping the docked logo or its docked title returns the page to the top. The camera lens cover is open while resting on Photos in the detail view (`cameraOpen = (resting |
Orbit ring (.canvas-dots) |
The nav-centre indicator: N coloured dots (one per canvas, a --dot-* colour each) ride a tilted ring — a flat ellipse in perspective (no ring line drawn, no occluder). Dots spin with pos (angle = (k − pos)·360°/N), so panning turns it one notch per canvas and it loops. The current canvas's dot sits at the front (nearest, biggest, brightest); others recede to the back (smaller, fainter) via a depth factor. The dots fade as the logo docks in. Positions/scale/opacity/z-index set per frame in app.js. Tapping it collapses the current page (raises the dock / nav view); if scrolled it returns to the top first. |
Dock (.canvas-dock) |
A bar under the main nav. Shown as navDock → 1; hidden (opacity 0) when the current title sits under its logo. When shown it holds three titles: left-aligned (left canvas), centred (current), right-aligned (right canvas). Titles remain on full-width visual strips so long labels are never clipped; dock clicks are routed by their horizontal position, with the current title returning to the top and either neighbour position navigating to its canvas. |
navDock |
0 = current title under its logo, dock hidden; 1 = title docked, dock shown. = max(scrollDock, collapseDock) where collapseDock = clamp((1−expansion)/COLLAPSE_DOCK_FRAC) and COLLAPSE_DOCK_FRAC=1. Raised by scrolling into content, and by collapsing — the title reaches the dock exactly as the logo finishes shrinking (in sync). In the bird-eye view (expansion 0) it's pinned at 1, so the dock is always visible. |
Title (entry) |
One per canvas, a slot on a strip that pans (translateX by rel × dockTravel) so titles slide in sync with the logos. The current title (rel≈0) sits under its logo (big) and animates up into the dock (centred, shrinking) as navDock→1; scrolling raises it 1:1 and it docks on arrival. The two neighbour titles show only in the dock, fading in with navDock. No edge captions. In the bird-eye view the current title is fully docked (navDock 1); mid-collapse it's between under-logo and dock, and bigFs scales with the current logo width so it shrinks with the logo. |
Wallpaper (.canvas-wallpaper) |
One continuous, horizontally repeating tie-dye field beneath every transparent canvas. Photography, Home, Work, and Notes have gray, blue, yellow, and green dominant regions at different vertical centres. placeView() translates the wallpaper from continuous pos; canvases never crossfade between separate backgrounds. |
Watermark (.canvas-watermark) |
Four potential-content section titles separated by vertical rules, filling the empty page body while the logo is collapsed (shrunk). The titles and intervening rules light gradually from top to bottom, then dim in the same order. It fades continuously with the bird-eye transition (ease(1 − expansion)) and has a 420ms opacity transition for ordinary entry/exit. Committed click navigation disables that transition to suppress the outgoing watermark immediately, keeps it hidden through collapse/pan, then restores the transition only after the destination has painted its landed state. |
currentId |
The canvas we are resting on or heading to. During a pan, the outgoing canvas id is local to the navigation function. |
| Dock / docking | As you scroll an open canvas, its marker shrinks and rises up into the sticky nav bar; scrolling back up reverses it. |
travelX |
Half a viewport — the pixel distance between adjacent ring positions (so a neighbour peeks half-off the edge). |
CANVASES |
The data table in app.js that registers every canvas. The engine loops over it; there is no per-canvas layout code. |
| Derivatives | The small responsive image copies (AVIF + JPEG, two sizes) produced by scripts/build-images.sh from the full-size originals. |
Architecture rules
- Every canvas behaves identically. Tapping a neighbour's peeking marker/label pans to it. Tapping the centred canvas's own marker collapses/expands its logo (if scrolled, the first tap returns it to the top). Tapping the orbit ring collapses the current page too. Back and Escape go Home. Don't give one canvas a bespoke interaction — behaviour belongs to the engine.
- Scroll PACES collapse/expand. Beyond tapping, scroll drives
expansiondirectly (applyTransitionScroll, not a fixed tween): in the bird-eye view scrolling down expands into the detail view; in the detail view at the top scrolling up collapses into the bird-eye view. The title reaches the dock in sync with the logo shrinking (seenavDock). On idle / touch-end it snaps to the nearer end (snapExpansion). Content only scrolls in the full detail view (overflowYauto atexpansion ≥ .999), so the transition owns the wheel/touch elsewhere. Works with wheel and touch. Detail scrolling: detail content owns native scrolling at all times: its position is never counteracted by a buffer transform after entry. The logo/title start docking from the first detail scroll and travel with that same scroll, preserving clearance without holding or consuming reading input. Clicking the logo/ring enters atscrollTop = 0. Only the true top (scrollTop ≤ 1) permits an upward gesture to begin collapsing; returning through the final pixels of detail content must fully enlarge the logo/title first. Collapsing resetsscrollTopto 0. - A locked canvas does not scroll. When a canvas's
overflowYishidden(e.g. the compact/shrunk Home logo), neither native scroll nor the marker wheel/touch forwarding may move it. - Canvas scrollbars are hidden. The interactive Home, Photography, Work, and Notes scroll containers retain native wheel, trackpad, touch, and keyboard scrolling but do not display a scrollbar. A scrollbar appearing only on detail entry consumes viewport width on some systems and shifts all centred canvas geometry. Standalone Photography pages keep normal browser scrollbars.
- Detail-entry settle buffer. Scroll-paced expansion caps individual
deltaYsteps, keeps the content anchor stable while the title docks, waits through a transition settle buffer, commits meaningful partial entry to detail on idle, and announces when the detail view is ready. - The engine is data-driven. All layout, panning, docking, visibility, and
wiring is written once against
CANVASESinapp.js. Adding a canvas must not require new geometry maths. - To add a canvas: (1) add a row to
CANVASESwith akind; (2) insert its id intoORDERat the ring position you want; (3) add its<section>of content inindex.html; (4) add its marker + title-entry elements; (5) append its selectors to the shared.work-*rules instyles.css; (6) add a link innav.js's page map. No layout maths change. - Keep pans smooth. Click navigation goes: scroll the current canvas to
the top → shrink/fade it fully into the bird-eye view → pan → the destination
arrives collapsed and stays in the nav view
(
finish()does not auto-expand; tap the logo/ring or scroll down to enter). Mid-pan the dock titles pan across with the markers, then settle under the new logo. The single continuous wallpaper translates viatranslate3dfrom livepos; canvas sections stay transparent and only their content opacity changes. Detail content has its own opacity track and stays hidden during pans. Never animate sectionleft/width. - Cursor pass-through. Markers AND titles forward wheel/touch to the active
canvas (
forwardScroll), so hovering either still scrolls the page. - Scroll transform stability. Detail content keeps one stable compositor transform; native scroll events only schedule the shared renderer. Do not alternate transform functions or write a redundant transform in the scroll handler: frequent mobile scroll events make text visibly stutter.
- Touch direction locking. A shared viewport touch gesture uses an intentionally asymmetric axis lock: slightly dominant vertical movement locks quickly after
TOUCH_VERTICAL_INTENT, while horizontal navigation requires the longerTOUCH_HORIZONTAL_INTENTand clear dominance set byTOUCH_HORIZONTAL_AXIS_RATIO. This keeps diagonal scrolling vertical unless the user makes an unmistakable left/right swipe. A deliberate horizontal swipe drives the continuous ring preview and may cross multipleORDERslots. Touch uses its own, slightly longerTOUCH_SWIPE_DRAG_FRACTIONso small diagonal corrections do not pull toward a neighbouring canvas; desktop sensitivity remains independent. Horizontal gestures are ignored while a return-to-top, expansion, or pan is active; uncertain gestures do not navigate. Locked horizontal gestures complete on a capture-phasetouchendortouchcancelat the window boundary, preserve the final changed-touch coordinate, and apply a short capped velocity projection so releasing at or just beyond a screen edge retains natural follow-through. - Mobile vertical collapse. A downward finger pull at the true top of a fully expanded canvas is owned by the transition engine and collapses the detail view continuously into the bird-eye view. This takes priority over native pull-to-refresh inside the canvas viewport. The centred marker and orbit ring remain alternative collapse controls, and desktop upward wheel input retains the same scroll-paced collapse.
- Desktop/mobile parity. Desktop horizontal wheel/trackpad input and mobile horizontal touch input use the same continuous ring preview, handoff, dock, and settle behavior. The detail-to-dock handoff is distance-driven by the live gesture, so the rendered state stays attached to the finger without waiting for a timer or replaying queued input. A gesture begins from the current rendered logo/title positions and must finish that handoff before ring panning starts. Detail text has its own opacity track as well as the canvas fade, following continuous
expansionon entry and exit rather than appearing in a final frame. Itsvisibilitymay switch only at a near-zero opacity threshold, never at the expanded endpoint. Incomplete gestures reverse the same visual path: the marker/title return from the dock, the saved reading position restores, and detail content fades back in proportion to the returning expansion; it remains unavailable to interaction until fully restored. Vertical content scrolling preserves the current reading position; it does not reset to the top merely because a touch gesture begins. - Desktop horizontal navigation. Horizontal trackpad/wheel input (
deltaX, or Shift+wheel) drives the same live swipe preview as touch and settles after wheel input pauses. Ordinary vertical wheel input keeps scrolling and Ctrl+wheel remains available for browser zoom. - Continuous desktop navigation. Horizontal wheel input drives one continuous ring preview across as many ring slots as the gesture covers. When wheel input pauses, the nearest canvas slot is selected and the page settles there; input is not queued for later pans.
- Swipe handoff and reading position. A horizontal swipe progressively fades and collapses the outgoing detail view during the drag, then advances the ring toward its destination in the latter part of the same gesture. Release/idle keeps the 30/70 behavior: a gesture reaching 70% commits, while a short/cancelled gesture rolls back. Continuous travel may span any number of complete loops; all destination offsets use positive modulo normalization so large negative offsets never resolve to an invalid slot and roll back. The engine remembers a canvas's scroll position when leaving by swipe and restores it only when that canvas is expanded again; a fresh canvas enters at the true top (
scrollTop = 0). - Input-driven swipe handoff. Ring travel begins with the first locked horizontal movement. The outgoing detail view collapses concurrently and reaches the dock by
SWIPE_HANDOFF_FRACTION; there is no dead zone, time-based handoff, or replay queue. Touch movement is coalesced to the latest sample once per animation frame, viewport metrics stay cached outside the gesture hot path, and expensive backdrop filters / watermark animation pause during the swipe. - Collapsed content is unavailable. A canvas content region is
inertandaria-hidden="true"unless its canvas is current, settled, and fully expanded. This keeps hidden cards and links out of keyboard focus and assistive-technology navigation during bird-eye view and pans. - It's a loop. Panning always takes the shortest way around the ring, so
Notes → Photography wraps. Each canvas is placed by
ringRel(its signed distance frompos): centre = the full open page,±1neighbours peek as logos, far side hidden. Content is hidden during a pan and only the centred canvas's content becomes available in the settled detail view.
Code conventions
- Site identity: The portfolio is branded wizark's Portfolio; Photography remains a section within it.
- No commits or pushes unless the user explicitly asks.
- Cache-busting: every HTML file loads CSS/JS with
?v=N. When you editstyles.css,app.js, ornav.js, bumpNin all HTML files that load it (rootindex.html+ everything inphotography/). - Colours: the current UI palette is a web approximation of Pantone 2026
Cloud Dancer / Powdered Pastels, with darker derived UI accents for accessible
contrast. One continuous tie-dye wallpaper has a dominant field for each canvas:
Photography = gray, Home = blue, Work = yellow, Notes = green. Each field has
its own vertical centre, and the wallpaper translates with
pos. The orbit dot for each canvas uses the same family in a darker contrast-safe value. Use the variables in:root(--ink,--paper,--muted,--line,--accent,--logo-line,--powder-*,--tone-*,--dot-*). Do not hard-code new hex values; add a variable if you need a new colour. - Fonts: use the font CSS variables (
--font-serif,--font-sans). Do not introduce newfont-familyliterals. See "Typography" below. - CSS structure:
styles.cssis grouped by/* ===== Section ===== */headers. Some selectors are declared in layered "refinements"; those are commented — keep the ordering, don't merge across media queries. - JavaScript style: one statement per line; name magic numbers as constants
at the top of the IIFE; keep related interaction state in a named object;
keep the hot path (
placeView) allocation-light. Shared render helpers own content availability (opacity,visibility, scrolling, pointer events,inert, andaria-hidden), and repeated style/attribute writes are cached. Touch and desktop gestures may normalize input differently, but both feed the same swipe-travel renderer and settle logic.
Typography
- Family: self-hosted Montebello (WOFF files in
assets/fonts/, declared via@font-faceat the top ofstyles.css). Montebello Sans (--font-sans) carries body copy, captions, controls, and metadata. Montebello Rounded (--font-rounded;--font-serifpoints at it) is the display face for canvas labels, logos, major headings, watermarks, and photography titles. This soft heading / clear copy pairing belongs to the Cloud Dancer powdered-pastel design. Use the variables — never a rawfont-familyliteral. - The zip also shipped Montebello Script and Script-Textured; those were removed (unused).
- Fonts are WOFF only (no WOFF2 supplied) and load with
font-display:swap. Montebello Sans is a display face — if body text / small captions read poorly, that's the signal to pick a text face for--font-sans.
Accessibility & resilience
- Markers are real
<button>s witharia-labels that update on state change. prefers-reduced-motionis honoured (animations become instant snaps).- A
<noscript>fallback links to the Photography section for JS-off visitors. - Keep
inerton inactive canvases so assistive tech only sees the active one.
Assets & image pipeline
- Originals live in
assets/<section>/originals/locally only — they are gitignored (not committed/served). Keep them on your machine as the source for./scripts/build-images.sh, which regenerates the derivatives that ship. - Photography stills →
assets/photography/; camera/home art →assets/camera/. <img>uses<picture>with an AVIF<source>+ JPEG fallback,srcset(two widths) +sizes, anddecoding="async".
Folder map
index.html front page (the canvas engine)
styles.css app.js nav.js
photography/ the photography site (index + photo-01..04)
assets/camera/ assets/photography/ (+ originals/ subfolders)
scripts/build-images.sh
