Skip to content

feat: 016 — real pig sprite sheet - #15

Merged
archae0pteryx merged 10 commits into
mainfrom
feat/016-real-sprite-sheet
May 3, 2026
Merged

feat: 016 — real pig sprite sheet#15
archae0pteryx merged 10 commits into
mainfrom
feat/016-real-sprite-sheet

Conversation

@archae0pteryx

@archae0pteryx archae0pteryx commented May 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Replaces the 🐷 emoji placeholder with the real 4-direction pixel art sprite sheet
  • Extends movement direction from 2-way (left/right) to 4-way (front/right/back/left) using dominant velocity axis
  • Sprite frame advances every 150ms matching existing FRAME_INTERVAL; bob animation preserved

Sprite sheet layout

Row Direction
0 front (toward viewer)
1 right
2 back (away from viewer)
3 left

Display: 48×48px per frame (PIG_SIZE). Sheet scaled to 192×192 via background-size (normalises the 921×910 source to clean integer offsets). image-rendering: pixelated keeps crisp edges.

Test plan

  • App launches without crash
  • Pigs visible on screen with correct sprite (not emoji)
  • Direction changes as pigs change velocity (front/back visible when moving vertically)
  • Walk animation cycles through 4 frames smoothly
  • Name label still visible below sprite
  • Click on pig still opens PigDetail
  • task check green

Closes #9

Summary by CodeRabbit

  • New Features

    • Pig sprites now render from a sprite sheet with four-directional facing and preserved walk bob.
    • Selecting a pig freezes its movement.
  • Bug Fixes

    • Pigs stay within window bounds and bounce off edges for consistent movement.
  • UI

    • Overlay now spans the union of enabled monitors as a single window.
    • Display menu may group monitors under a "Displays" submenu or list them flat based on count.
  • Style

    • Pixelated rendering and layout tweaks for consistent sprite display.

Replace emoji placeholder with 4-direction sprite sheet animation.

- PigDirection type: "front" | "right" | "back" | "left"
- direction4() derives facing from dominant velocity axis (4-way)
- PigSprite renders via background-position on the 4×4 sheet
  (background-size: 192×192 normalises 921×910 sheet to clean 48px frames)
- image-rendering: pixelated keeps crisp pixel art at display scale
- Bob animation preserved via frame-indexed top offset
- vite-env.d.ts adds PNG import types for TypeScript
@coderabbitai

coderabbitai Bot commented May 3, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Pigs now render from a 4×-direction sprite sheet and use a 4-way PigDirection. Movement computes facing from velocity, supports freezing the selected pig, and reflects velocity at screen edges. Tauri overlay management now uses a single spanning overlay-0. Tray display menu construction was refactored.

Changes

Pig rendering & movement (frontend)

Layer / File(s) Summary
Types / Data Shape
src/hooks/usePigMovement.ts
Adds `export type PigDirection = "front"
Core Movement
src/hooks/usePigMovement.ts
Adds direction4(vx,vy); initPig and tickPig set/recompute 4-way direction; tickPig gains frozen: boolean early-return; boundary handling now hard-clamps position and reflects velocity per-edge.
Hook API / Integration
src/hooks/usePigMovement.ts, src/components/App.tsx
usePigMovement signature changed to accept selectedId; App adds selectedId state and passes it into the hook.
Presentation / Sprite
src/components/PigSprite.tsx, src/styles.css
Replaces emoji with sprite-sheet frame rendering: computes col/row from frame and direction, sets backgroundImage, backgroundPosition, backgroundSize; removes scaleX flip; adds .pig-sprite-frame { image-rendering: pixelated; display:block; flex-shrink:0 }.
Viewport sourcing
src/hooks/usePigMovement.ts
Reads viewport from window.innerWidth/innerHeight with window.screen.* fallbacks in spawn and rAF loop.
Other No tests or docs updated in this cohort.

Overlay & Tray (Tauri native side)

Layer / File(s) Summary
Constants / Ids
src-tauri/src/app/overlay_manager.rs
Adds OVERLAY_LABEL = "overlay-0" constant for the single spanning overlay.
Overlay core logic
src-tauri/src/app/overlay_manager.rs
OverlayManager::apply now computes union bounding rect of enabled monitors, ensures a single overlay-0 is shown/resized to that union, destroys it if none enabled, and removes legacy overlay-{n} windows.
Tray menu wiring
src-tauri/src/app/tray.rs
Refactors display menu construction into append_display_items with DISPLAY_SUBMENU_MIN_COUNT; builds monitor items with CheckMenuItemBuilder, conditionally nests under a “Displays” submenu or flattens into the root, and adjusts separators.
Imports / Builders
src-tauri/src/app/tray.rs
Adds CheckMenuItemBuilder import and related builder usage.
Config
src-tauri/tauri.conf.json
Removes the static overlay-0 window entry from app.windows (overlay is now managed dynamically).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant App as App (React)
  participant Hook as usePigMovement
  participant RAF as rAF loop
  participant Renderer as PigSprite / DOM
  App->>Hook: mount with focuses, selectedId
  RAF->>Hook: tick(dt, viewport)
  Hook->>Hook: integrate position, reflect edges, compute direction4
  Hook->>Renderer: provide PigState[] (selected pig frozen)
  Renderer->>App: click -> set selectedId
  App->>Hook: update selectedId (refied for rAF)
Loading
sequenceDiagram
  autonumber
  participant Config as MonitorsState/DisplayConfig
  participant Overlay as OverlayManager
  participant OS as Windowing system
  Config->>Overlay: enabled monitor indices
  Overlay->>Overlay: compute union bounding box
  alt enabled set non-empty
    Overlay->>OS: ensure_shown("overlay-0", bbox)
    Overlay->>OS: destroy legacy "overlay-1..n" if exist
  else none enabled
    Overlay->>OS: destroy("overlay-0") if exists
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

"I hopped through pixels, bright and spry,
Swapped emoji snouts for frames that fly.
I nudged the pigs to bounce and play,
One window spans them night and day.
🐇🎨"

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes to overlay_manager.rs and tray.rs involve unrelated refactoring (monitor/display handling and overlay window management) beyond the scope of sprite sheet implementation in issue #9. Remove or isolate the Rust-side overlay_manager.rs and tray.rs changes into a separate PR focused on display/overlay management, keeping this PR focused purely on sprite sheet implementation.
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: replacing the emoji placeholder with a real sprite sheet animation system for pigs.
Linked Issues check ✅ Passed All core coding requirements from issue #9 are met: sprite sheet replaces emoji, 4-direction support added, frame animation wired via background-position, pixelated rendering applied, and PIG_SIZE updated to match sprite dimensions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/016-real-sprite-sheet

Comment @coderabbitai help to get the list of available commands and usage tips.

Multiple webview windows each run their own React instance → duplicate pig
sets. Fix: compute the union bounding box of all enabled monitors and resize
a single overlay-0 window to cover them. Pigs roam the full spanning area.

- overlay_manager: apply() builds one spanning MonitorInfo from enabled
  monitor bounds; cleans up legacy overlay-1+ windows
- usePigMovement: window.innerWidth/innerHeight instead of window.screen so
  pig movement bounds match the actual spanning window dimensions

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hooks/usePigMovement.ts (1)

45-46: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clamp the initial spawn span for small viewports.

screenW - 2 * EDGE_MARGIN - PIG_SIZE can go negative once this hook uses window.innerWidth/innerHeight on a narrow window. That makes the first spawn position start off-screen until the next tick corrects it, so the pig can disappear on initial paint.

Fix
-    x: EDGE_MARGIN + Math.random() * (screenW - 2 * EDGE_MARGIN - PIG_SIZE),
-    y: EDGE_MARGIN + Math.random() * (screenH - 2 * EDGE_MARGIN - PIG_SIZE),
+    x: EDGE_MARGIN + Math.random() * Math.max(0, screenW - 2 * EDGE_MARGIN - PIG_SIZE),
+    y: EDGE_MARGIN + Math.random() * Math.max(0, screenH - 2 * EDGE_MARGIN - PIG_SIZE),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/usePigMovement.ts` around lines 45 - 46, The initial spawn
calculation in usePigMovement can produce a negative span (screenW - 2 *
EDGE_MARGIN - PIG_SIZE / screenH) on narrow viewports causing off-screen spawns;
fix the x and y initializers by computing a clamped span (e.g., spanX =
Math.max(0, screenW - 2 * EDGE_MARGIN - PIG_SIZE) and spanY = Math.max(0,
screenH - 2 * EDGE_MARGIN - PIG_SIZE)) and then use EDGE_MARGIN + Math.random()
* spanX (and spanY) so the random multiplier never receives a negative range;
update the initial x/y expressions where they are set in the usePigMovement
hook.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src-tauri/src/app/overlay_manager.rs`:
- Around line 63-84: The current code builds a single union MonitorInfo named
spanning from enabled monitors which unintentionally includes intervening
disabled displays; change this to produce explicit per-monitor regions (e.g.,
collect a Vec<MonitorInfo> called enabled_monitors from
enabled.iter().filter(...).map(|m| MonitorInfo { name: ..., size: ..., position:
... })) and use that list for rendering and hit-testing instead of the single
spanning rectangle, or alternatively keep spanning but also build a region mask
(Vec<PhysicalRect> or similar) of enabled monitor rectangles and apply that mask
wherever overlay interactivity or rendering occurs; update any code paths that
currently consume spanning to iterate these enabled monitor regions (references:
the enabled variable, MonitorInfo, spanning, and DisplayConfig.enabled_indices).

---

Outside diff comments:
In `@src/hooks/usePigMovement.ts`:
- Around line 45-46: The initial spawn calculation in usePigMovement can produce
a negative span (screenW - 2 * EDGE_MARGIN - PIG_SIZE / screenH) on narrow
viewports causing off-screen spawns; fix the x and y initializers by computing a
clamped span (e.g., spanX = Math.max(0, screenW - 2 * EDGE_MARGIN - PIG_SIZE)
and spanY = Math.max(0, screenH - 2 * EDGE_MARGIN - PIG_SIZE)) and then use
EDGE_MARGIN + Math.random() * spanX (and spanY) so the random multiplier never
receives a negative range; update the initial x/y expressions where they are set
in the usePigMovement hook.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 949bc8bf-78ba-4aa6-b9a9-678ec7b7c570

📥 Commits

Reviewing files that changed from the base of the PR and between a56a1ba and 7b3077f.

📒 Files selected for processing (2)
  • src-tauri/src/app/overlay_manager.rs
  • src/hooks/usePigMovement.ts

Comment on lines +63 to +84
// Union bounding box of all enabled monitors in physical pixels.
let min_x = enabled.iter().map(|m| m.position.x).min().unwrap_or(0);
let min_y = enabled.iter().map(|m| m.position.y).min().unwrap_or(0);
let max_x = enabled
.iter()
.map(|m| m.position.x + m.size.width as i32)
.max()
.unwrap_or(1920);
let max_y = enabled
.iter()
.map(|m| m.position.y + m.size.height as i32)
.max()
.unwrap_or(1080);

let spanning = MonitorInfo {
name: None,
size: tauri::PhysicalSize::new(
(max_x - min_x).max(1) as u32,
(max_y - min_y).max(1) as u32,
),
position: tauri::PhysicalPosition::new(min_x, min_y),
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Single bounding rectangle can unintentionally include disabled displays

At Line 63 through Line 84, using one min/max union rectangle means non-contiguous monitor selections (e.g., enabled indices [0, 2]) still cover index 1 if it sits between them geometrically. That weakens the meaning of per-display toggles in DisplayConfig.enabled_indices and can render pigs on displays the user explicitly disabled.

Please either keep per-monitor overlay regions for discontiguous selections, or add explicit region masking so only enabled monitor rectangles are interactive/renderable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/app/overlay_manager.rs` around lines 63 - 84, The current code
builds a single union MonitorInfo named spanning from enabled monitors which
unintentionally includes intervening disabled displays; change this to produce
explicit per-monitor regions (e.g., collect a Vec<MonitorInfo> called
enabled_monitors from enabled.iter().filter(...).map(|m| MonitorInfo { name:
..., size: ..., position: ... })) and use that list for rendering and
hit-testing instead of the single spanning rectangle, or alternatively keep
spanning but also build a region mask (Vec<PhysicalRect> or similar) of enabled
monitor rectangles and apply that mask wherever overlay interactivity or
rendering occurs; update any code paths that currently consume spanning to
iterate these enabled monitor regions (references: the enabled variable,
MonitorInfo, spanning, and DisplayConfig.enabled_indices).

- Clicked pig freezes (position + frame) while PigDetail is open;
  resumes when card is dismissed. selectedId flows via ref so the rAF
  loop sees it without restarting.
- Pigs no longer escape screen: reflect velocity at hard bounds instead
  of only clamping position. Clamped-but-outward velocity was the escape
  path when a random turn fired at the boundary.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/App.tsx`:
- Around line 19-20: The app currently mixes viewport sources: usePigMovement
clamps pigs to window.innerWidth/innerHeight while PigDetail is fed
screenW/screenH from useViewport (window.screen.*), causing misaligned
positioning; refactor so both use the same viewport values by creating or
reusing a single helper hook (e.g., useViewport or useWindowSize) and pass its
innerWidth/innerHeight output into usePigMovement and into the PigDetail props
(update the pigs creation call and the PigDetail render to consume the same
screenW/screenH), or change useViewport to return window.innerWidth/innerHeight
instead of window.screen values so usePigMovement and PigDetail share identical
measurements.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 42673ed6-88bf-4e07-953d-06d4258e0701

📥 Commits

Reviewing files that changed from the base of the PR and between 7b3077f and f11a67f.

📒 Files selected for processing (2)
  • src/components/App.tsx
  • src/hooks/usePigMovement.ts

Comment thread src/components/App.tsx
Comment on lines +19 to +20
const pigs = usePigMovement(focuses, selectedId);
const { screenW, screenH } = useViewport();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use one viewport source for both pig bounds and PigDetail.

usePigMovement now clamps pigs against window.innerWidth/innerHeight, but useViewport() still feeds PigDetail with window.screen.width/height. Those values diverge in normal windowed desktop usage, so the detail card can be positioned outside the actual play area or drift away from a pig near the edges. Please switch both paths to the same viewport measurement, ideally via a shared helper/hook.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/App.tsx` around lines 19 - 20, The app currently mixes
viewport sources: usePigMovement clamps pigs to window.innerWidth/innerHeight
while PigDetail is fed screenW/screenH from useViewport (window.screen.*),
causing misaligned positioning; refactor so both use the same viewport values by
creating or reusing a single helper hook (e.g., useViewport or useWindowSize)
and pass its innerWidth/innerHeight output into usePigMovement and into the
PigDetail props (update the pigs creation call and the PigDetail render to
consume the same screenW/screenH), or change useViewport to return
window.innerWidth/innerHeight instead of window.screen values so usePigMovement
and PigDetail share identical measurements.

- Use CheckMenuItemBuilder for display toggles (native OS checkmark vs
  text prefix)
- Display items render flat for 1-3 monitors, nested submenu for 4+
- Section moves below focus list, above Quit
- usePigMovement: biome-formatted boundary blocks

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/hooks/usePigMovement.ts`:
- Around line 62-65: When frozen is true in usePigMovement, don’t just return
the original pig object; also freeze its timers by advancing nextTurnAt and
lastFrameAt by the elapsed time so they don’t “age” while selected. Concretely,
compute elapsed = now - currentTimestamp (use the same time reference used
elsewhere in usePigMovement, e.g. performance.now() or Date.now()), and return a
pig copy with nextTurnAt: pig.nextTurnAt + elapsed and lastFrameAt:
pig.lastFrameAt + elapsed (preserving other fields). This ensures nextTurnAt and
lastFrameAt remain relative and won’t trigger an immediate turn/frame when
unpaused.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 40a0f37c-6d24-4803-acf4-1a4c0e97fd34

📥 Commits

Reviewing files that changed from the base of the PR and between f11a67f and 679a153.

📒 Files selected for processing (2)
  • src-tauri/src/app/tray.rs
  • src/hooks/usePigMovement.ts

Comment thread src/hooks/usePigMovement.ts
overlay-0 was pre-defined in tauri.conf.json at 400x600. Tauri creates that
window before setup runs, so the webview loaded at 400px wide — pigs
initialized in a 400px space then became invisible after the spanning resize.

Remove the static entry; overlay_manager always creates the window fresh via
WebviewWindowBuilder with set_size(PhysicalSize) called synchronously on the
main thread before show(), so window.innerWidth in React reflects the actual
spanning dimensions from the first frame.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src-tauri/src/app/overlay_manager.rs (1)

63-84: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Spanning union still includes disabled displays for non-contiguous monitor selections.

At Line 63 through Line 84, computing a single min/max union rectangle can cover disabled monitors that sit between enabled ones (for example, enabled indices [0, 2]). That breaks the per-display toggle contract and can render pigs on displays the user explicitly disabled.

Please switch to explicit enabled-monitor regions (or keep a spanning window but enforce a strict region mask for rendering + hit-testing).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/app/overlay_manager.rs` around lines 63 - 84, The current
spanning MonitorInfo (variable spanning) built from min_x/min_y/max_x/max_y can
include disabled displays between enabled ones (e.g., enabled indices [0,2]);
replace the single-union approach with explicit enabled-monitor regions or add a
strict region mask: build a Vec of per-enabled MonitorInfo instances from
enabled.iter() (use the same .position/.size fields) and use that Vec for
rendering and hit-testing instead of the spanning variable, or if you must keep
spanning, compute enabled_rects and apply that mask in all rendering/hit-testing
paths so pixels on disabled monitors are never drawn or receive events.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src-tauri/src/app/overlay_manager.rs`:
- Around line 63-84: The current spanning MonitorInfo (variable spanning) built
from min_x/min_y/max_x/max_y can include disabled displays between enabled ones
(e.g., enabled indices [0,2]); replace the single-union approach with explicit
enabled-monitor regions or add a strict region mask: build a Vec of per-enabled
MonitorInfo instances from enabled.iter() (use the same .position/.size fields)
and use that Vec for rendering and hit-testing instead of the spanning variable,
or if you must keep spanning, compute enabled_rects and apply that mask in all
rendering/hit-testing paths so pixels on disabled monitors are never drawn or
receive events.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b23ef7c6-1311-4ece-ab3f-e720cd7b6386

📥 Commits

Reviewing files that changed from the base of the PR and between 679a153 and c37839e.

📒 Files selected for processing (2)
  • src-tauri/src/app/overlay_manager.rs
  • src-tauri/tauri.conf.json
💤 Files with no reviewable changes (1)
  • src-tauri/tauri.conf.json

…apability

Click-through issue: polling thread sets ignore_cursor_events=true whenever
cursor isn't on a pig rect. PigDetail backdrop and ESC keystrokes were going
to the desktop instead of the overlay. Fix: when selectedId is set, syncRects
sends a full-viewport sentinel rect so the thread always sees a hit and never
enables click-through.

Capability fix: add overlay-0 explicitly alongside overlay-* glob so
event listen and invoke reliably work for the programmatically created window,
ensuring focuses-changed events reach React and the pig/detail close on delete.
- usePigMovement: clamp spawn span with Math.max(0,...) so narrow viewports
  don't produce negative ranges and off-screen initial positions
- usePigMovement: advance nextTurnAt + lastFrameAt by dt while pig is frozen
  so timers don't expire during the pause, preventing immediate turn/frame
  jump on unfreeze
- useViewport: use innerWidth/innerHeight (same source as pig movement) so
  PigDetail positioning and pig bounds share identical viewport measurements
delete_focus bug: store.create_focus names directory by slug but writes
uuid to frontmatter id field. list() was returning focus.id = uuid, so
delete_focus(uuid) tried to rmdir ~/.adhd-ranch/focuses/<uuid>/ which
doesn't exist → NotFound, silently swallowed. Fix: list() overrides
focus.id with the directory entry name (slug) — the authoritative key
for all store path operations.

Bounds: switch window.innerWidth → document.documentElement.clientWidth
(same source used by browsers for layout viewport; avoids potential
physical-pixel return from innerWidth on multi-DPI spanning windows).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

016 — Real pig sprite sheet

1 participant