Commit Graph

4447 Commits

Author SHA1 Message Date
Alex Gleason 9010dba583 Add privacy policy page with route and sidebar link 2026-03-18 01:13:04 -05:00
Dirk Rost 9309308a4e Merge branch 'fix/iOS-accessibility' into 'main'
fix: prevent iOS Safari auto-zoom on input focus

See merge request soapbox-pub/ditto!84
2026-03-17 19:55:18 +00:00
Dirk Rost cd1620c76e Merge branch 'feat/poll-compose' into 'main'
Add poll creation to compose toolbar and polls page FAB

See merge request soapbox-pub/ditto!85
2026-03-17 19:55:18 +00:00
Lemon f91a37914c fix: prevent iOS Safari auto-zoom on input focus 2026-03-17 19:55:18 +00:00
Chad Curtis f1c15b64a2 Add poll creation to compose toolbar and polls page FAB 2026-03-17 19:55:18 +00:00
Mary Kate c3a4d8be57 Fix sticky tabs not scrolling to top on click or switch 2026-03-17 19:46:44 +00:00
sam 8267962bdf weather station profile field 2026-03-17 23:56:57 +07:00
filemon a7c29c4a85 feat: add Blobbi shapes as avatar masks
New feature allowing users to select Blobbi character silhouettes as
avatar masks, in addition to existing emoji shapes.

New files:
- src/lib/blobbiShapes.ts: Shape definitions with SVG paths for all
  Blobbi forms (egg, baby, 16 adults)
- src/components/BlobbiShapePicker.tsx: Grid picker with category tabs

Changes:
- Extended avatarShape.ts to support 'blobbi:' prefixed shape values
- Added getAvatarMaskUrl() unified function for both shape types
- Updated Avatar component to render Blobbi masks
- Added tabbed UI in ProfileCard (Emoji/Blobbi tabs)
- Renamed emojiAvatarBorderStyle to shapedAvatarBorderStyle

Shape format: 'blobbi:<id>' (e.g., 'blobbi:baby', 'blobbi:catti')
Stored in kind-0 metadata as 'shape' property, same as emojis.

Available shapes:
- Egg (simple egg silhouette)
- Baby Blobbi (water droplet)
- Adults: catti, owli, froggi, droppi, flammi, crysti, cloudi,
  mushie, starri, pandi, cacti, breezy, leafy, rocky, rosey, bloomi
2026-03-17 12:32:46 -03:00
filemon 91364385c3 fix: use data attributes for blink center instead of CSS parsing
Changes:
- Store blink center as data-cx/data-cy attributes on .blobbi-blink groups
- Use eye white center (cx, cy) as blink anchor when available
- Fallback to pupil center if no eye white found
- Read center from data attributes in animation loop (more reliable than CSS)

Structure per eye:
<g class="blobbi-blink" data-cx="38" data-cy="45">  <!-- blink group -->
  <ellipse ... />                                        <!-- eye white -->
  <g class="blobbi-eye">                               <!-- tracking group -->
    <circle ... />                                       <!-- pupil -->
    <circle ... />                                       <!-- highlight -->
  </g>
</g>

Blink transform: translate(cx,cy) scale(1,blinkY) translate(-cx,-cy)
- Scales around the actual eye center from SVG element data
- No CSS transform-origin parsing needed
- Eye closes in place without shifting
2026-03-17 12:21:02 -03:00
filemon d958722e63 fix: scale blink around eye center to prevent downward shift
Problem: scale(1 blinkY) was scaling from top-left origin, causing
the eye to move down during blink.

Solution: Use translate-scale-translate pattern to scale around the
eye's center point:
  translate(cx cy) scale(1 blinkY) translate(-cx -cy)

The center coordinates are extracted from the transform-origin style
that was already set during SVG processing.

Tracking behavior remains completely unchanged - only the blink
transform application was modified.
2026-03-17 12:11:10 -03:00
filemon d3a19ebfaa fix: separate tracking and blink into distinct transform groups
Problem: Blink was only affecting pupil, not whole eye.
Previous attempt: Wrapping entire eye in one group caused eye white to
move with mouse tracking.

Solution: Two separate nested groups per eye:
- .blobbi-blink (outer): wraps entire eye for scaleY blink animation
- .blobbi-eye (inner): wraps only pupil+highlight for translate tracking

Structure:
<g class="blobbi-blink">     <!-- blink: scale(1 blinkY) -->
  <ellipse ... />             <!-- eye white - stays fixed -->
  <g class="blobbi-eye">    <!-- tracking: translate(x y) -->
    <circle ... />            <!-- pupil - moves with mouse -->
    <circle ... />            <!-- highlight - moves with mouse -->
  </g>
</g>

Result:
- Mouse tracking: only pupil+highlight translate (unchanged behavior)
- Blinking: entire eye scales vertically (natural cartoon blink)
- Eye white: never moves, only scales during blink
2026-03-17 12:01:08 -03:00
Chad Curtis f17bc899c8 Fix tall images cropped in lightbox
The wrapRef div had no dimensions, so max-w-full/max-h-full on the img
resolved against the image's own natural size rather than the available
space. Give wrapRef full width/height so percentage constraints work
correctly against the padded slot area.
2026-03-17 09:59:04 -05:00
filemon c17883bdb8 feat: add natural blinking system for Blobbi eyes
- Random blink intervals between 2-5 seconds for organic feel
- Blink animation: fast close (~80ms), pause (~100ms), slower open (~120ms)
- 20% chance for double blinks (extra polish)
- Uses scaleY transform combined with mouse tracking translate
- Easing functions: ease-in for close, ease-out for open
- Disabled when Blobbi is sleeping
- No CSS transitions - all animation via RAF for instant response
2026-03-17 11:42:40 -03:00
filemon 0b90b0206b fix: eliminate eye tracking lag with direct SVG transforms
- Remove CSS transitions from .blobbi-eye class (root cause of delay)
- Use SVG transform attribute instead of style.transform for reliable repaints
- Cache eye element references after mount with automatic refresh on SVG changes
- Hook now manages DOM directly without onUpdate callback
- Simplified visual components to just pass containerRef
2026-03-17 11:34:58 -03:00
Chad Curtis d955473c3c Fix long tag/bech32 string overflow in feed tabs, empty states, and note content 2026-03-17 09:29:50 -05:00
Chad Curtis 0fbc562ee0 Fix long hashtag overflow in page title header 2026-03-17 09:26:19 -05:00
Chad Curtis e55b90202b Enable all fun content kinds by default in feed settings 2026-03-17 09:15:59 -05:00
Chad Curtis 230b4820bf fix: pre-fetch VAPID key on mount to avoid insecure-operation error
Browsers (especially Safari/Firefox) require pushManager.subscribe() to be
synchronously reachable from a user gesture. The previous code awaited a
getVapidKey() network call inside enable(), which broke the activation chain
and threw 'DOMException: The operation is insecure'.

Fix: fetch and cache the VAPID key during SW registration on mount so it is
ready in vapidKeyRef before the user ever clicks Enable, making
pushManager.subscribe() the first async call after the user gesture.
2026-03-17 08:48:45 -05:00
Chad Curtis ae60146390 Merge branch 'feat/pwa-notifications' into 'main'
Add Web Push notifications via "nostr-push"

See merge request soapbox-pub/ditto!83
2026-03-17 13:39:05 +00:00
filemon 8f958ef6c7 refactor: simplify eye system to always track mouse globally
Removed from previous system:
- Idle random movement logic
- Energy-based behavior (timing, smoothing, micro-movements)
- Tracking radius (200px distance check)
- Idle/tracking state switching
- lerp interpolation for tracking
- isTracking state and callback parameter
- Per-instance mouse listeners

New behavior:
- Eyes ALWAYS follow the mouse cursor
- Works across the entire screen (no distance limit)
- Instant response (no interpolation, no lag)
- Simple angle calculation every frame

How the new tracking loop works:
1. Global mouse listener updates globalMouseX/globalMouseY
2. RAF loop runs every frame
3. Calculate angle: atan2(mouseY - centerY, mouseX - centerX)
4. Calculate position: cos(angle) * max, sin(angle) * max * 0.7
5. Call onUpdate callback with position
6. DOM updated directly (no React state)

Performance optimizations:
- Single global mouse listener shared by all Blobbi instances
- Instance count tracking for cleanup
- No React state in animation loop
- Direct DOM manipulation via callback
- Minimal computation per frame (just trig)

Hook reduced from ~390 lines to ~140 lines.
2026-03-17 10:35:44 -03:00
filemon 2118fa483b fix: remove React state from animation loop for real-time eye tracking
Problems fixed:

1. React state caused tracking delay
   - setState() batches updates and triggers re-renders
   - Even with refs, calling setState inside RAF caused 1-2 frame lag
   - Eyes only followed mouse properly when it stopped moving

2. Tracking intensity was inverted
   - Old: farther mouse = stronger movement (wrong)
   - New: closer mouse = stronger movement (correct)
   - Formula: intensity = 1 - Math.pow(normalizedDistance, 0.5)

3. Idle froze after mouse left
   - Old: scheduled idle change 300-800ms later
   - New: immediately triggers new idle target when tracking stops
   - Formula: nextIdleChangeRef.current = currentTime (force immediate)

Solution - callback-based architecture:

- REMOVED all setState calls from animation loop
- Added onUpdate(left, right, isTracking) callback option
- Callback is called every RAF frame with current positions
- Components apply transforms directly to DOM via querySelectorAll
- Zero React re-renders during animation = zero lag

Data flow now:
  RAF loop → compute position → onUpdate callback → direct DOM update

Before:
  RAF loop → setState → React re-render → useEffect → DOM update

The onUpdate callback receives positions every frame and applies
transforms immediately, bypassing React's batching entirely.
2026-03-17 10:29:25 -03:00
filemon 2bb9c7738a feat: instant mouse tracking and energy-based idle behavior for Blobbi eyes
1. Mouse tracking now INSTANT (no lag):
   - When tracking, eyes lock directly onto cursor position
   - No interpolation/lerp during tracking mode
   - Feels like 'locked on target' instead of floating/chasing

2. Energy-based idle behavior:
   - High energy (100): frequent movement, shorter pauses, quicker smoothing
   - Low energy (0): lazy movement, longer pauses, slower drift
   - Energy affects: idle duration, smoothing speed, micro-movement chance

3. Micro-movements for aliveness:
   - Small movements (0.2-0.5px) happen randomly
   - High energy = 50% chance, Low energy = 10% chance
   - Makes Blobbi feel alert and curious

4. Pause behavior scaled by energy:
   - Low energy: 50% chance to rest at center
   - High energy: 10% chance to rest at center

Values chosen:
- SMOOTHING_MIN = 0.02 (low energy - dreamy drift)
- SMOOTHING_MAX = 0.06 (high energy - alert movement)
- IDLE_DURATION_MIN = 1000ms (high energy)
- IDLE_DURATION_MAX = 6000ms (low energy)
- MICRO_MOVEMENT_MAX = 0.5px (subtle but visible)

Behavior summary:
- Mouse near -> eyes LOCK instantly on cursor
- High energy -> curious, active, moving often
- Low energy -> slower, lazy, but still alive
- Sleeping -> no movement
2026-03-17 10:17:37 -03:00
filemon 7ed55b00a6 fix: smooth eye animation with proper interpolation and mouse tracking
Root causes of previous issues:

1. Jumping behavior: The old implementation used setState() directly to
   random positions instead of interpolating toward them. Each idle
   movement instantly teleported the eyes.

2. Mouse tracking failure: The updateMouseTracking callback had isTracking
   in its dependencies, causing it to be recreated on every state change.
   This restarted the animation frame loop constantly, breaking the
   continuous tracking.

3. State conflict: Idle timeouts and tracking animation frames ran
   independently and fought each other, causing erratic behavior.

Solution - Single animation loop architecture:

- ONE requestAnimationFrame loop handles ALL animation
- Maintains separate 'current' and 'target' positions
- Always interpolates: current = lerp(current, target, smoothing)
- Idle behavior only sets new targets (doesn't move directly)
- Mouse tracking overrides targets when cursor is nearby
- Clean state machine: tracking active = idle paused

Smoothing values used:
- IDLE_SMOOTHING = 0.03 (very smooth drift)
- TRACKING_SMOOTHING = 0.08 (responsive but not snappy)
- RETURN_SMOOTHING = 0.04 (gentle return to idle)

Timing improvements:
- Idle duration: 3-6 seconds between movements
- 40% chance to pause at center (natural resting)
- Time-scaled smoothing for consistent feel across frame rates

Movement constraints:
- Baby: 2px max, Adult: 2.5px max
- Vertical movement reduced to 70% of horizontal
- State updates throttled (only when position changes > 0.001px)
2026-03-17 10:08:58 -03:00
filemon 4feb051177 fix: rewrite eye animation system for proper SVG transforms and mouse tracking
Root cause: The original implementation had two critical issues:
1. Grouping algorithm assumed highlights immediately followed pupils in SVG,
   but SVGs have all pupils first, then all highlights (proximity-based fix)
2. CSS transforms weren't working on SVG <g> elements without transform-box

Fixes:
- Rewrite pupil/highlight detection to use proximity-based grouping (15px radius)
- Add transform-box: fill-box and transform-origin: center inline styles
- Replace CSS keyframe animation with JavaScript-controlled transforms

New features:
- Natural idle behavior with random movement and pauses
- Mouse tracking when cursor is within 200px radius
- Smooth transitions between idle and tracking states
- Different delays for left/right eyes for organic feel

Implementation:
- useBlobbiEyes hook manages animation state and mouse tracking
- addEyeAnimation wraps pupil+highlight elements in <g class="blobbi-eye">
- Visual components apply transforms via DOM refs in useEffect
- CSS provides transition timing (.3s idle, .1s tracking)
2026-03-17 09:47:39 -03:00
Alex Gleason 33fcae9bd5 Fix right sidebar not hiding when rightSidebar is null
The nullish coalescing operator (??) treats null as nullish, so
`null ?? <RightSidebar />` was falling through to the default.
Use an explicit null check so pages can hide the sidebar by
passing rightSidebar: null.
2026-03-16 23:26:19 -05:00
Alex Gleason 44e5c1719f Expand Messages page to full width by hiding right sidebar 2026-03-16 23:24:42 -05:00
Alex Gleason 5f9f23bcf8 Enable direct messaging with NIP-04 and NIP-17 support 2026-03-16 23:21:27 -05:00
Lemon 547fcd91fe Refactor: clean up notification code for clarity
- useNativeNotifications: remove web push concerns entirely, this hook
  is now Capacitor-only. Web push lifecycle is fully managed by
  usePushNotifications + NotificationSettings.
- usePushNotifications: strip debug logging, tighten doc comments.
- nostrPush: strip debug logging.
- notificationTemplates: trim verbose block comment.
- NotificationSettings: reduce comment noise.
2026-03-16 19:05:45 -07:00
Alex Gleason 5bdd679643 Fix profile tabs spacing: stop stretching tabs, add padding and horizontal scroll 2026-03-16 20:45:25 -05:00
Dirk Rost 79965f80ca Merge branch 'fix/divine-video-media-feed' into 'main'
Fix diVine videos not displaying in profile media feed

Closes #116

See merge request soapbox-pub/ditto!79
2026-03-17 00:29:02 +00:00
Mary Kate ad1361e15b Fix diVine videos not displaying in profile media feed 2026-03-17 00:29:02 +00:00
Dirk Rost 16946b5ec1 Merge branch 'fix/follow-button-page-refresh' into 'main'
Fix follow button on Themes page causing feed to reset

Closes #98

See merge request soapbox-pub/ditto!77
2026-03-17 00:29:01 +00:00
Mary Kate 7da03761ab Fix follow button on Themes page causing feed to reset 2026-03-17 00:29:01 +00:00
Dirk Rost 2fadb12183 Merge branch 'fix/kind-feed-global-tab' into 'main'
Fix kind-specific feeds showing Ditto tab content instead of actual kind data

Closes #122

See merge request soapbox-pub/ditto!75
2026-03-17 00:28:48 +00:00
Mary Kate d2c57e9287 Fix kind-specific feeds showing Ditto tab content instead of actual kind data 2026-03-17 00:28:48 +00:00
Dirk Rost 52eead2423 Merge branch 'remove-sidebar-theme-picker' into 'main'
Remove theme picker from sidebar profile actions

Closes #85

See merge request soapbox-pub/ditto!78
2026-03-17 00:28:47 +00:00
Mary Kate c353e8886a Remove theme picker from sidebar profile actions 2026-03-17 00:28:47 +00:00
Dirk Rost 3e73276845 Merge branch 'fix/status-bubble-italic-clipping' into 'main'
Fix italic status text clipping in NIP-38 status bubbles

Closes #112

See merge request soapbox-pub/ditto!76
2026-03-17 00:28:45 +00:00
Mary Kate b177b3fbd5 Fix italic status text clipping in NIP-38 status bubbles 2026-03-17 00:28:44 +00:00
filemon 27bce0d334 feat: add subtle eye movement animation for Blobbi baby and adult visuals
Add eye animation utility that:
- Detects pupil and highlight elements via gradient patterns and dark fills
- Wraps pupil+highlight elements in animated <g> groups
- Applies CSS keyframe animation for gentle wandering eye movement
- Uses different delays for left/right eyes for natural feel
- Only animates when awake (skips sleeping state)

Integrates animation into both BlobbiBabyVisual and BlobbiAdultVisual
components for a more lifelike appearance.
2026-03-16 21:23:24 -03:00
Lemon 587f0ff442 Fix enable() double-requesting permission and add push debug logging
- Remove redundant Notification.requestPermission() from enable() since
  the caller (NotificationSettings) already handles it from the user gesture.
  On iOS this second call could behave unexpectedly.
- Add console.debug logging throughout the enable flow and RPC send for
  diagnosing push registration issues in production.
2026-03-16 17:17:56 -07:00
Lemon 023f440054 Fix web push notifications: derive toggle from browser state, not NIP-78
- NotificationSettings: on web, drive the push toggle from the hook's
  enabled state (actual browser subscription) instead of the NIP-78
  notificationsEnabled setting which can be stale across devices.
  NIP-78 is still used for the Capacitor/Android path.

- useNativeNotifications: remove the web push auto-disable effect that
  raced against encrypted settings loading, causing disablePush() on
  every page load before settings resolved.

- usePushNotifications: remove REGISTERED_KEY localStorage check from
  the mount restore logic; derive enabled purely from
  pushManager.getSubscription() like the working Ghastly reference.
  Remove dead REGISTERED_KEY code entirely.
2026-03-16 16:57:31 -07:00
Dirk Rost f02f883e2e Fix iOS theme glitch on notification settings page 2026-03-16 23:09:05 +00:00
filemon ddf50724f0 fix: adult form resolution now uses adult_type tag as primary source
- Add adultType field to BlobbiCompanion interface
- Parse adult_type tag in parseBlobbiEvent from kind 31124
- Pass adult.evolutionForm in toBlobbiForVisual adapter
- Seed-derived form is now only used as fallback when no adult_type tag exists
2026-03-16 20:07:36 -03:00
filemon d07bd75d07 feat: implement adult Blobbi visual system with 16 evolution forms
- Add adult-blobbi module with types, SVG resolver, and customizer
- Support 16 adult forms: bloomi, breezy, cacti, catti, cloudi, crysti,
  droppi, flammi, froggi, leafy, mushie, owli, pandi, rocky, rosey, starri
- Each form has base and sleeping SVG variants
- Adult form resolved from blobbi.adult.evolutionForm or derived from seed
- Color customization applies to body and pupil gradients via pattern matching
- BlobbiAdultVisual component with reaction animations support
- Replace adult placeholder in BlobbiStageVisual with real visuals
2026-03-16 19:48:43 -03:00
Alex Gleason 6570d7d901 Fix trends count mismatch between TrendsPage and sidebar (ditto#45) 2026-03-16 17:39:39 -05:00
filemon d59ba03cc6 fix: sing reaction only starts when recording begins, adjust animation timing
- Add onRecordingStart/onRecordingStop callbacks to InlineSingCard
- Move singing reaction trigger from card open to actual recording start
- Reduce sing bounce animation movement (6px → 3px for baby, 4px → 2px for egg)
- Slow down sing bounce animation (0.4s → 0.5s for baby, 0.5s → 0.6s for egg)
- Change Record button label to Sing
2026-03-16 19:29:50 -03:00
filemon 04112110f7 polish: faster music animation timing and fix egg centering with music notes 2026-03-16 19:22:40 -03:00
filemon d835cb5e6a feat: add floating music notes and tune animation timing for Blobbi reactions 2026-03-16 19:10:20 -03:00
Alex Gleason d7c55aa7d4 Merge branch 'profile-field-presets' into 'main'
feat: add field preset templates and live sidebar preview to profile settings

Closes #105

See merge request soapbox-pub/ditto!73
2026-03-16 21:54:34 +00:00