Commit Graph

3447 Commits

Author SHA1 Message Date
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
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 f0f54d76c5 Merge branch 'main' into fix/blobbi-deterministic-legacy-migration 2026-04-21 19:54:07 -03:00
filemon 819d0a88f1 Make legacy Blobbi migration deterministic
Derive the migration petId from sha256(pubkey + legacyD) instead of
crypto.getRandomValues(). The same legacy Blobbi now always produces the
same canonical d-tag, seed, and visual traits regardless of which device
or session triggers the migration.

The equivalence guard (findCanonicalEquivalent) still runs first, so
pre-existing canonicals from the random-petId era are reused and no
duplicate is created.
2026-04-21 19:45:19 -03:00
Chad Curtis 08e61eea89 Merge branch 'fix/blobbi-adult-polish' into 'main'
Fix adult Blobbi colors, reactions, and eye/eyebrow alignment

Closes #241, #242, #243, #244, #245, and #246

See merge request soapbox-pub/ditto!196
2026-04-21 22:29:35 +00:00
Chad Curtis 273469eda8 Merge branch 'fix/blobbi-progression-task-persistence' into 'main'
Move Blobbi progression missions from kind 11125 to kind 31124

Closes #239

See merge request soapbox-pub/ditto!193
2026-04-21 22:29:05 +00:00
Chad Curtis 97a219aa8c Merge branch 'fix/blobbi-sleep-eye-overlay-initial-render' into 'main'
Fix open-eye flash on first sleep transition

Closes #240

See merge request soapbox-pub/ditto!195
2026-04-21 22:28:31 +00:00
Chad Curtis 5dafdf85f7 Merge branch 'fix/blobbi-legacy-new-deduplication' into 'main'
Fix Blobbi legacy/new-format deduplication to prevent infinite duplicates

Closes #247

See merge request soapbox-pub/ditto!198
2026-04-21 22:28:00 +00:00
filemon 7830269ea1 Collapse canonical Blobbi duplicates created by migration races
filterMigratedLegacyCompanions now runs a second pass that groups
canonical companions by their migrated_from tag. Within each group
only the newest event (highest created_at) is kept; the rest are
hidden from the collection UI. Canonical companions without the tag
are never grouped — they pass through untouched.

This closes the remaining duplicate-in-UI gap left intentionally by
the earlier legacy→canonical dedup work.
2026-04-21 18:37:38 -03:00
filemon 118b0c11ab Strengthen legacy->canonical equivalence with migrated_from and base_color
Replace the name-only equivalence rule with a tiered priority:

1. migrated_from exact match (canonical event's migrated_from tag equals
   the legacy d-tag) — strongest signal, written during migration and
   preserved across all subsequent Blobbi updates.

2. Same normalized name + same raw base_color tag — covers older canonical
   copies created before migrated_from existed, where both events have an
   explicit base_color tag that matches.

3. Same normalized name when the legacy event has no base_color tag —
   weakest fallback for genuinely old bare legacy events with no visual
   tags to compare.

All tiers still require the legacy d-tag to be absent from profile.has
(the migration-completion guard).

Audited that migrated_from survives all Blobbi lifecycle operations:
mergeBlobbiStateTagsForRepublish preserves it as an unknown tag,
validateAndRepairBlobbiTags passes it through (not in schema, not
deprecated), and stage transition cleanup does not touch it.
2026-04-21 17:52:24 -03:00
filemon 4ad0a9cfb4 Fix Blobbi legacy/new-format deduplication to prevent infinite duplicates
Legacy Blobbi events (d=blobbi-{name}) persisted on relays after migration
to canonical format (d=blobbi-{hex}-{hex}), causing them to appear alongside
their canonical copies in the UI. Interacting with a still-visible legacy
Blobbi triggered another migration each time, creating unbounded duplicates.

Three changes:

1. Filter migrated legacy Blobbies from the rendered collection. A legacy
   Blobbi is hidden only when a canonical Blobbi with the same normalized
   name exists AND the legacy d-tag is no longer in profile.has (confirming
   migration already occurred).

2. Guard ensureCanonicalBlobbiBeforeAction against re-migration. Before
   creating a new canonical event, query all companions and look for an
   existing canonical equivalent by normalized name. If found, reuse it
   and fix up profile.has/current_companion instead of migrating again.

3. Store a migrated_from tag on newly migrated events for future stronger
   equivalence lookups (additive, not depended on by current dedup logic).
2026-04-21 17:32:50 -03:00
Chad Curtis c23af72da7 Fix lightbox swipe-to-dismiss flicker and locked controls race
The dismiss animation only translated the image strip, leaving the top
bar, nav buttons, dot indicators, and bottom bar stationary — visible
as a jarring flicker of controls. The backdrop also flashed back to full
opacity for one frame before the portal unmounted.

Wrap all visible content (everything except the backdrop) in a single
container that receives the translateY transform so the entire UI sweeps
away as one unit. Reorder the setTimeout callback so onClose fires
before clearing the animating lock, and add an unmount-cleanup effect as
a safety net against stuck controls.

Regression-of: cc655891
2026-04-20 20:00:50 -05:00
Chad Curtis bfee3dfdf1 release: v2.10.3 2026-04-20 19:40:48 -05:00
Chad Curtis b29f7ec4d5 Fix white status bar text on light theme (iOS)
App.tsx had a useEffect that unconditionally set SystemBarsStyle.Dark
(white icons) on mount, overriding the theme-aware logic in main.tsx
that had already set the correct style. On light themes this produced
white-on-white status bar text.

Remove the hardcoded override entirely — main.tsx handles initial
detection and MutationObservers cover all subsequent theme changes.
2026-04-20 19:12:06 -05:00
Chad Curtis a42e5f085e Fix envelope cards hard to tap on mobile
The hover wobble animation was triggering on touch devices via the
sticky :hover pseudo-class, rotating the envelope while the user
was trying to tap it. Restrict the wobble to true pointer-hover
devices with @media (hover: hover) and (pointer: fine).

Also tighten the entrance animation: remove rotation so tap targets
stay stable, reduce duration from 0.4s to 0.3s, start closer to
final size (0.85 vs 0.6), and cap stagger delay at 300ms so later
envelopes settle before the user can scroll to them.
2026-04-20 19:05:31 -05:00
Chad Curtis cc655891d5 Add swipe-to-dismiss gesture to lightbox overlays
Users can now swipe up or down to dismiss the full-screen image
lightbox, matching the native mobile pattern of flicking an image
away instead of reaching for the X button. The image follows the
finger with opacity fade, and commits the dismiss after 15% of
viewport height. When zoomed in the gesture is disabled so it
doesn't conflict with panning.

Applies to both the main Lightbox (feeds, galleries, media collage)
and the ProfileImageLightbox (avatar/banner taps).
2026-04-20 19:01:03 -05:00
Chad Curtis 708c25d938 Clear inline wall compose box after posting from modal
When a user typed in the inline ComposeBox then tapped the FAB (which
covers the Post button), the modal opened with the same draft text.
After posting from the modal, the inline ComposeBox still showed the
old text because it was a separate React instance with its own state,
leading to accidental double-posts.

Bump a key on the inline ComposeBox after a successful modal post so
React remounts it, picking up the already-cleared localStorage draft.
2026-04-20 18:54:21 -05:00
Chad Curtis a7cd13228b Add autoplay videos setting (default off)
Adds a new autoplayVideos config field and a toggle in Settings > Content >
Video Playback. When enabled, videos auto-play muted in feeds, collage
thumbnails, profile sidebar tiles, the Vines feed, and the VideoPlayer
component. The preference syncs across devices via encrypted settings.

usePlayerControls now listens for the volumechange event to keep the
volume UI in sync when the video is programmatically muted for autoplay.
2026-04-20 12:26:58 -05:00
filemon 618655e921 Merge branch 'main' into fix/blobbi-adult-polish 2026-04-20 01:16:43 -03:00
filemon 436324fe8f Fix adult eyebrow alignment by anchoring to eye top instead of eye center
Eyebrow Y position was calculated as eye.cy + offsetY, which broke on
forms with large eye whites (catti ry=16, froggi/owli r=22, droppi/
pandi/rocky r=12) because the fixed offset did not account for the
distance between eye center and eye top.

Propagate eye white vertical radius through the pipeline: write
data-eye-rx/ry on blink groups in addEyeAnimation, read data-eye-ry
in detectFromProcessedSvg, add eyeWhiteRy to EyePosition type. The
eyebrow formula now rebases recipe offsets from center-relative to
top-relative using the actual eye white radius, producing a consistent
gap above the eye top regardless of eye size.

Remove FORM_EYEBROW_OFFSETS (owli, froggi) — the radius-aware formula
handles all forms correctly without per-form overrides. Baby eyebrow
formula is unchanged.
2026-04-19 21:15:58 -03:00
filemon d0a11e266f Fix body detection for adult forms using circle, ellipse, and rect bodies
detectBodyPath() only matched <path> elements, so 10 of 16 adult forms
failed body detection — anger-rise was silently skipped and dirt/dust
fell back to hardcoded positions.

Add data-blobbi-body="true" marker to the primary body element of all
16 forms (base + sleeping = 32 elements). Extend detectBodyPath() with
a marker-first strategy that supports <circle>, <ellipse>, and <rect>
via path synthesis. Update the anger-rise overlay insertion regex in
apply.ts to find the marked element instead of only matching <path>.

Existing gradient-name and comment-based fallbacks remain for backwards
compatibility.
2026-04-19 20:55:06 -03:00
filemon 5bf99176bb Fix eye tracking lost on flat-fill adults after eyeColor replacement
The eyeColor fallback replaced known pupil fills with arbitrary colors,
causing isPupilElement() to no longer recognize them via PUPIL_COLORS.
Inject a data-blobbi-pupil marker during the fill replacement and check
for it first in both isPupilElement() copies so detection is independent
of the actual fill color value.

Regression-of: 9c20102d
2026-04-19 20:34:51 -03:00
filemon 9c20102dad Apply eyeColor and secondaryColor consistently to all adult Blobbi forms
eyeColor was silently dropped for 12 of 16 adult forms because they use
hardcoded fill attributes on pupil circles instead of gradient references.
Add a scoped flat-fill fallback in applyPupilGradient() that replaces the
known default pupil color within the <!-- Pupils --> comment block only.

secondaryColor was threaded through the type system but never read by any
adult customizer. Following the baby two-tone pattern (secondaryColor at
center, baseColor at edge), add an optional innerColor parameter to the
gradient builders and pass secondaryColor to each form's main body
gradient. Pandi remains excluded from body color changes by design.
2026-04-19 20:06:25 -03:00
Chad Curtis 8b311bde81 Merge branch 'ditto-music-feed' into 'main'
feat: redesign Music page with curated discovery experience

Closes #233

See merge request soapbox-pub/ditto!189
2026-04-19 22:27:56 +00:00
Chad Curtis b4e42778fa Merge remote-tracking branch 'origin/main' into ditto-music-feed
# Conflicts:
#	src/components/NoteCard.tsx
2026-04-19 17:27:12 -05:00
Chad Curtis 986adeb901 Reset package-lock.json to origin/main 2026-04-19 17:25:26 -05:00