Commit Graph

3479 Commits

Author SHA1 Message Date
Patrick PReis 482dca78ec Raise overfeed poop chance to 40% 2026-04-24 21:13:49 -03:00
Patrick PReis 10fc3bf0a7 Apply 20% overfeed chance to reactive feeding, not just page load
The probability check was only in generateInitialPoops (mount time).
The live feeding path in KitchenBar always spawned a poop. Now both
paths use the shared OVERFEED_CHANCE constant.
2026-04-24 21:12:12 -03:00
Patrick PReis d3462f42dc Lower overfeed poop chance from 60% to 20% 2026-04-24 20:50:27 -03:00
Patrick PReis 357d108c7e Show poops in every room, not just the room they spawned in
PoopOverlay (passive) now renders all poops regardless of poop.room.
The room field still controls where the shovel can clean them
(InteractivePoopOverlay filters by roomId).
2026-04-24 20:36:04 -03:00
Patrick PReis 755f3b9fb0 Make poop overlays room-aware and restore max 3 poop cap
The overlays now filter by poop.room so the data model is respected
end-to-end: poop-system assigns a room, overlays render only poops
matching their room. Currently all poops spawn in kitchen so the
behavior is identical, but enabling multi-room spawning later only
requires changing the room assignment in poop-system.ts — no UI
changes needed.

- PoopOverlay and InteractivePoopOverlay take a roomId prop and
  filter via getPoopsInRoom()
- Restore MAX_POOPS to 3 (was raised to 6 in previous commit)
- Fix indentation in generateInitialPoops
2026-04-24 20:30:56 -03:00
Patrick PReis 7aaf9f1cad Poops spawn in kitchen only, visible in all rooms, shovel only in kitchen
- All poops spawn with room='kitchen' (reverts random room assignment)
- PassivePoopOverlay renders poop emojis in Home, Care, and Rest rooms
  as static display-only elements (no interaction)
- KitchenPoopOverlay renders interactive poops with drag hit-test refs
- Shovel button remains exclusively in the kitchen bar
- useShovelDrag simplified: no longer takes roomId parameter
- Remove room-aware toast ('Try another room'); simple 'Nothing to
  clean!' message when tapping shovel with no poop
2026-04-24 20:26:04 -03:00
Patrick PReis 9be0c22b03 Render poops in all rooms and tune poop generation
- Poops now spawn in random rooms (care, kitchen, home, rest) and each
  room renders them with a draggable shovel via shared PoopOverlay +
  ShovelButton components
- Extract drag-to-clean logic into useShovelDrag hook (reused by all
  room bars, eliminating ~100 lines of duplicated code from KitchenBar)
- Overfeed poop is now 60% probability instead of deterministic
- Time-based poop cap raised from 3 to MAX_POOPS (6), so longer
  absences produce more poops proportionally
- Shovel replaces the left action button in each room when poop is
  present (e.g. replaces Photo in Home, Towel in Care); reverts
  when poop is cleaned
- Room-aware toast: 'Try another room' when poop exists elsewhere
2026-04-24 20:16:23 -03:00
Patrick PReis 97aacd96aa Rework blobbi shovel into drag-to-clean and fix poop system bugs
- Replace toggle-mode shovel with drag-and-drop: drag the shovel icon
  onto a poop to clean it (works on both desktop mouse and mobile touch)
- Shovel button is now always visible in the kitchen (no more disappearing)
- Always show 'Shovel' label (removed 'Done' toggle text)
- Show toast when tapping shovel with no poop present
- Fix invisible poops: all poops now spawn in kitchen only (previously
  time-based poops randomly spawned in rooms that never rendered them)
- Spawn poop immediately when feeding a blobbi with hunger >= 95
- Add addPoop() to PoopState for reactive poop creation
- Convert RoomActionButton to forwardRef with touch/mouse event props
- Cap total poops at 4 to prevent accumulation
2026-04-24 19:29:21 -03:00
Chad Curtis 8405d42902 Merge branch 'feat/status-guide' into 'main'
Add status guided UX with glow indicators and Guide me flow

Closes #254

See merge request soapbox-pub/ditto!203
2026-04-24 16:02:40 +00:00
Chad Curtis 6b9aeddb06 Clean up review findings from video/vine/pagination commits
- Remove orphaned JSDoc and dead isShort code path in VideoContent
- Unify vine mute state into shared vineGlobalMute module so mute
  preference carries between NoteCard feeds and VinesFeedPage
- Fix fetchNextPage race cascade by using a ref guard instead of
  putting isFetchingNextPage in the useCallback dep array
- Batch event ingestion in useStreamPosts so the event map is sorted
  and flushed to state once per page instead of per-event
- Don't permanently kill pagination on transient network errors
- Move sort modifiers into searchParts before the join for consistency
- Wrap full VideoGridCard/ShortThumb content in ContentWarningGuard
  so metadata doesn't leak when policy is blur
2026-04-24 10:11:32 -05:00
Chad Curtis 23e845ebc1 Render kind 22 shorts as vines in feeds with volume control
Kind 22 (Short-form Portrait Video) was rendering through VideoContent
with a shrunken max-width and a redundant "Short" badge, duplicating
the vine experience poorly. Now kind 22 shares the VineMedia component
with kind 34236, rendering full-width with play/pause and mute toggle.

- Unified isVine to match both kind 22 and 34236
- Removed isShortVideo flag and the small/badged VideoContent path
- Added mute/unmute button to VineMedia with shared module-level state
- Mute preference persists across shorts as you scroll through a feed
- Removed dead parseImeta function (replaced by parseVideoImeta)
2026-04-24 10:02:49 -05:00
Chad Curtis 5a80df05f5 Add infinite scroll pagination to search page
useStreamPosts previously fetched a single batch of 40 events with no
way to load more. Refactored to track the oldest event timestamp and
expose fetchNextPage/hasNextPage/isFetchingNextPage for cursor-based
pagination using the same NIP-50 search filter with an `until` param.

SearchPage now renders an IntersectionObserver sentinel below the post
list that triggers loading the next page when scrolled into view.
2026-04-24 09:51:50 -05:00
Chad Curtis cc3a5b3415 Fix vines always autoplay regardless of autoplayVideos setting
The autoplayVideos config controls inline video players in normal
feeds. Vines use snap-scroll where the active slide should always
autoplay — gating on this setting made vines not play at all when
the user had autoplay disabled.
2026-04-24 09:44:33 -05:00
filemon 9a48d039db Fix guide not resuming room arrow when leaving target room
The guide step effect only handled the forward transition (room →
item/action on entering the target room). Add the reverse: when the
step is item or action but the user has navigated away, revert to
the room step so the directional arrow resumes blinking.
2026-04-24 11:28:23 -03:00
Chad Curtis fdacb2029a Fix content warnings not applying to video and vine feed pages
VideosFeedPage and VinesFeedPage rendered their own card components
(VideoGridCard, ShortThumb, VineCard) without any content warning
checks, bypassing the ContentWarningGuard used elsewhere. Videos
with NIP-36 content-warning tags displayed without blur or filtering.

- Wrap VideoGridCard and ShortThumb thumbnails in ContentWarningGuard
- Add full-screen dark CW overlay to VineCard matching vine aesthetic
- Filter out CW events when contentWarningPolicy is "hide" in both pages
- Prevent video autoplay while CW overlay is shown in VineCard
2026-04-24 09:25:25 -05:00
filemon 1eb126bdf8 Make all stat icons trigger the guided-care flow
Previously only low-status (warning/critical) stat icons were clickable
and the guide auto-cleared when the stat recovered to normal. Now every
stat icon fires onGuide on tap regardless of status, and the guide is
only dismissed by completion events (item used, sleep started, or a
different stat clicked).

Changes:
- StatsCrown: remove the status-gate from onClick and cursor-pointer
- StatsCrown: add z-10 so stat icons layer above the Blobbi visual
- Blobbi animation wrapper: pointer-events-none so it cannot intercept
  taps on overlapping stat icons
- BlobbiDashboard: remove the effect that cleared the guide when the
  stat was normal; drop unused getStatStatus import
- Update comments and prop docs to reflect the new behaviour
2026-04-24 11:16:10 -03:00
filemon ca260497cc Merge branch 'main' into feat/status-guide 2026-04-24 10:24:54 -03:00
Chad Curtis 846c4f794a Merge branch 'audit/blobbi-adult-color-application-and-generation' into 'main'
Make Blobbi visual identity seed-driven and sync mirror tags

See merge request soapbox-pub/ditto!200
2026-04-24 04:04:48 +00:00
Chad Curtis 3a9f41892f Harden seed derivation: extract readSeedUint32, guard edge cases
- Extract readSeedUint32() for raw 32-bit reads instead of misusing
  deriveIndexFromSeed with max=0x100000000 (which was a no-op modulus).
  deriveIndexFromSeed is now only used for bounded array indexing.
- Guard deriveAdultFormFromSeed against NaN from short/invalid seeds.
- Guard adjustSeedForAdultType against indexOf returning -1 for
  unknown form values.
2026-04-23 22:54:12 -05:00
Chad Curtis b6b5a46f4f Merge branch 'feat/blobbi-click-overstimulation-reaction' into 'main'
Add Blobbi overstimulation reaction for repeated clicks

Closes #236

See merge request soapbox-pub/ditto!190
2026-04-24 03:47:04 +00:00
Chad Curtis 8b0eb97abb Fix redundant layout reflows and sanitize SVG color interpolation
Cache getBoundingClientRect() result in OverstimulationBlockOverlay
to avoid four forced reflows on the same element per activation.

Add sanitizeSvgColor() guard in generateAngerRiseEffect() so that
config.color is validated before interpolation into SVG stop-color
attributes, preventing injection if a future caller passes untrusted
color strings.
2026-04-23 22:39:27 -05:00
Chad Curtis 5463206d84 Fix mobile nav bar rendering during overstimulation zoom
Scroll to top before applying the zoom transform and restore the
saved scroll position after zoom-out completes. The transform on
#root creates a new containing block that breaks sticky positioning
of the mobile top/bottom nav bars when the user is scrolled down.
2026-04-23 22:31:32 -05:00
Chad Curtis ed637bc9df Compact reaction system: inline drain logic, trim verbosity
Delete useReactionDrain (120 lines) — the abstraction added more
complexity than it saved. Each hook now inlines a ~15-line rAF drain.

Rewrite all major files for density:
- useOverstimulationReaction: 375 → 175 lines
- useShakeReaction: 407 → 188 lines (was 205 with shared hook)
- shakeDetection: 222 → 93 lines
- OverstimulationBlockOverlay: 216 → 112 lines

Total branch diff: 1297 → 900 insertions.
2026-04-23 22:27:49 -05:00
Chad Curtis f465cb7347 Extract shared hooks to deduplicate reaction and SVG renderer code
- useFillLevelUpdate: shared recipe fingerprint + imperative gradient
  stop updates, extracted from BlobbiAdultSvgRenderer and
  BlobbiBabySvgRenderer (removed ~60 duplicated lines from each)

- useReactionDrain: shared rAF-based level drain loop with throttled
  React state push, extracted from useOverstimulationReaction and
  useShakeReaction (removed ~170 duplicated lines from each)

Net reduction: ~285 lines across the 4 consumer files.
2026-04-23 22:21:54 -05:00
Chad Curtis 49a5461fbe Fix zoom origin: query companion DOM element directly via data attribute
The previous approach put a ref on the pointer-events-auto wrapper div,
which is a full-width block element -- getBoundingClientRect returned
the page width, not Blobbi's position. Now we query the companion's
actual fixed-position container via [data-blobbi-companion] to get
the true visual bounding rect.
2026-04-23 22:14:06 -05:00
Chad Curtis 75f6283d9b Lower zoom origin to bottom edge of Blobbi's bounding box 2026-04-23 22:09:38 -05:00
Chad Curtis a144193cb4 Lower zoom origin to 80% of Blobbi's height 2026-04-23 22:09:18 -05:00
Chad Curtis 2ec57ad027 Lower zoom origin to 65% of Blobbi's height 2026-04-23 22:08:56 -05:00
Chad Curtis ff412bbb29 Crank overstimulation zoom to 5x for screen-hogging close-up 2026-04-23 22:07:43 -05:00
Chad Curtis 12d299a7ec Replace crumble effect with UI zoom + radial shockwave on overstimulation
When Blobbi hits max overstimulation, the entire UI now zooms toward
Blobbi's face (transform on #root) while a radial shockwave expands
from the companion and a red vignette dims the screen edges. On
recovery the zoom eases back out and the vignette fades.

The overlay is portaled onto document.body so it stays at viewport
scale while #root is scaled. Body overflow is hidden during the zoom
to prevent scrollbar flash.

Removes the canvas-based crumble/debris system (crumbleEngine.ts) in
favor of this simpler CSS-driven approach.
2026-04-23 22:05:30 -05:00
Chad Curtis 8fe0751a67 Add overstimulation visual feedback: shockwave, UI crumble, and debris
Replace the invisible click shield with a dramatic visual sequence when
Blobbi reaches max overstimulation: a radial shockwave expands from
Blobbi, a dark backdrop covers the UI (Blobbi stands alone in the void),
and canvas debris particles rain down like rubble. On recovery the
backdrop fades and debris converges back.

Also fix the debug bypass in useShakeReaction (true || threshold) that
shipped nausea on every shake regardless of hunger stat, and remove the
toast notification replaced by the visual overlay.
2026-04-23 21:55:28 -05:00
Chad Curtis 1b940b262c Merge branch 'main' of gitlab.com:soapbox-pub/ditto into feat/blobbi-click-overstimulation-reaction 2026-04-23 21:21:44 -05:00
Chad Curtis 6ea1d0da2b Merge branch 'fix/blobbi-deterministic-legacy-migration' into 'main'
Make legacy Blobbi migration deterministic

Closes #248

See merge request soapbox-pub/ditto!199
2026-04-24 02:18:58 +00:00
Chad Curtis 4dd487e0b2 Merge branch 'feat/improve-blobbi-companion-transitions' into 'main'
Add generic route-transition reaction for Blobbi companion

Closes #225

See merge request soapbox-pub/ditto!182
2026-04-24 02:09:45 +00:00
Chad Curtis 82f97aa1e2 Merge branch 'feat/blobbi-eyes-feed' into 'main'
Enable Blobbi eye tracking on feed cards (feed-only, with touch support)

Closes #249

See merge request soapbox-pub/ditto!201
2026-04-24 01:56:35 +00:00
Chad Curtis 1be0b3f101 Enable Blobbi eye tracking on detail page
Pass lookMode="follow-pointer" to BlobbiStateCard in PostDetailPage,
matching the feed card behavior so Blobbi eyes follow the cursor on the
detail view as well.
2026-04-23 20:53:35 -05:00
filemon 1afafb7abd Simplify guided-care UX: click low-status icon to start guide directly
Remove the intermediate popover + "Guide me" button step. Tapping a
low-status stat icon now calls onGuide immediately, starting the room
navigation or item/action highlight with zero friction.

Deletes the StatIndicatorWithHelp component (~120 lines) and its
associated imports (Popover, STAT_HELP_TEXT, Navigation, React state/
ref/callback/effect hooks). All stat icons now render through the
same StatIndicator; low-status icons get cursor-pointer and onClick
on their positioning wrapper.
2026-04-23 22:27:03 -03:00
filemon afce15d2d4 Add low-status guided UX with glow indicators and Guide me flow
Low-stat indicators now glow/pulse at warning and critical levels across
    BlobbiPage and widgets via a shared StatIndicator. On BlobbiPage, hovering
    (desktop) or tapping (mobile) a low stat shows contextual help with a
    Guide me button that visually leads the user to the correct room and
    item/action through a sequential glow chain: stat popover → room nav
    arrow → carousel arrow → target item/action.

    Implementation details:
    - Synchronized low-status icon animations via a shared CSS @property
      clock on the StatsCrown parent, so all icons pulse in phase regardless
      of mount timing
    - Split warning (stat-glow, 2s) and critical (stat-glow-critical, 2s)
      into separate keyframes with distinct visual weight
    - Guide glow uses a quick-blink-and-pause rhythm (1.1s cycle) distinct
      from the status pulse
    - Stable popover hover zone shared between trigger and content with
      paired open/close timers; onOpenAutoFocus prevented to avoid flicker
    - Guide cleanup on item use, action execution, new guide start, or stat
      recovery
    - ItemCarousel index only clamped when actually out of bounds, preventing
      guide visual instability from unrelated re-renders
    - Energy modeled as a first-class action target (sleep) rather than a
      special case
    - companionNeedsCare() uses calculateProjectedDecay() instead of raw
      persisted stats, consistent with all other low-status UI
2026-04-23 22:21:52 -03:00
Chad Curtis 360a8c88e3 Merge branch 'fix-crysti' into 'main'
Fix crysti blobbi: add missing sparkle animations and fix pink facet path

Closes #252

See merge request soapbox-pub/ditto!202
2026-04-23 22:21:02 +00:00
Patrick PReis feca8bc357 Revert unintended package-lock.json changes
The test script ran npm install which modified package-lock.json with
version bumps and dev flag additions unrelated to the crysti fixes.
2026-04-23 17:58:43 -03:00
Chad Curtis 5080970366 release: v2.10.4 2026-04-23 15:35:54 -05:00
Chad Curtis be4a741a73 Add RTL support for Arabic, Hebrew, and other RTL languages
Add dir="auto" to NoteContent, BioContent, and DM message bubbles
so the browser's Unicode Bidirectional Algorithm automatically detects
text direction from the first strong directional character.
2026-04-23 15:29:52 -05:00
Patrick PReis 589fb8ebba Fix crysti facet5 degenerate path (bottom-right purple section)
The path started and ended at the same point (100,105), making it a
triangle instead of a quadrilateral. The bottom-right hexagon vertex
(140,130) was missing entirely. Changed to trace center → right-mid →
bottom-right vertex → inner bottom-right point.

Fixed in all four locations: both .svg source files and both inlined
constants in adult-svg-data.ts.
2026-04-23 17:27:48 -03:00
Chad Curtis 0156a82629 Fix overflow menu rendering inside compose modal
The Poll/Spoiler popover opened side="bottom" from the toolbar, which
is near the bottom edge of the dialog.  Because the dialog container
uses overflow-hidden (needed for flex layout containment) and the
PortalContainerProvider portals content inside the dialog DOM, the
popover was clipped.

Switch to side="top" so the menu opens upward into the visible area
of the modal.
2026-04-23 15:25:44 -05:00
Patrick PReis 8497d87238 Sync crysti fixes into inlined SVG data constants
The app renders Blobbi SVGs from inlined string constants in
adult-svg-data.ts, not from the .svg source files. The previous commits
only fixed the source files. This syncs all three fixes into the
inlined CRYSTI_BASE and CRYSTI_SLEEPING constants:

- Add animateTransform groups to CRYSTI_BASE sparkle circles
- Fix self-intersecting pink facet path in both constants
- Match sleeping facet opacities to base values
2026-04-23 17:00:50 -03:00
Patrick PReis 787e0f6902 Match crysti sleeping facet opacities to base variant
The sleeping SVG had all six facet opacities reduced by 0.2 compared to
the base, making it look washed out. Only the eyes/mouth should differ
between states — the body colors should stay vibrant, consistent with
how bloomi handles its sleeping variant.
2026-04-23 15:27:39 -03:00
Patrick PReis 6ac7bdf826 Fix crysti blobbi: add missing sparkle animations and fix pink facet path
The base SVG sparkle circles were missing animateTransform tags, causing
them to render static instead of orbiting like the sleeping variant.
Wrapped them in two animated groups matching crysti-sleeping.

The top-left pink facet (crystiFacet2) had a self-intersecting path
(bowtie shape) that caused inconsistent fill rendering. Reordered the
vertices to trace a proper convex quadrilateral in both base and sleeping
SVGs.
2026-04-23 15:02:40 -03:00
filemon 2a2ebd6a46 Enable Blobbi eye tracking on feed cards (feed-only, with touch support)
BlobbiStateCard gains an optional lookMode prop (default: 'forward'),
threaded through to BlobbiStageVisual. Only the NoteCard feed call site
passes 'follow-pointer'; post detail, embedded notes, and embedded naddr
keep the default forward gaze.

The global pointer listener in useBlobbiEyes now also tracks touchstart
and touchmove so the effect works on mobile.
2026-04-22 18:58:36 -03:00
filemon 4d00ba9542 Fix duplicate egg creation when adopting another Blobbi
The module-level setupInFlightFor guard had a race condition: the
effect cleanup unconditionally deleted the pubkey from the guard set
even when setup() was already mid-flight. If a parent re-render caused
the component to unmount/remount during the async publish window, the
new instance passed all guards and created a second egg.

Fix: track whether setup() has started in a ref. Cleanup only releases
the guard when the timer was cancelled before setup began; otherwise
setup() releases it in its own finally block.

Also stabilize the eggOnly completion timer by reading onComplete
through a ref, preventing the 1500ms timer from resetting on every
parent re-render that creates a new inline callback reference.
2026-04-22 13:14:42 -03:00
filemon ea99fdf288 Clean up self-review findings: deduplicate hexToHsl, add abort handling, fix comments
- Remove hexToHslLocal from blobbi.ts; reuse shared hexToHsl from
  color-guardrails.ts (eliminates duplicate implementation)
- Add abort flag to useSeedIdentitySync useEffect so the async sync
  loop stops on unmount and never calls updateCompanionEvent after
  teardown
- Replace relative date wording in compat cutoff comment with
  absolute date only
- Fix stale STEP numbering in BlobbiPage.tsx (5/6 → 4/5)
2026-04-22 10:31:31 -03:00