6.2 KiB
Content Balance Implementation
Transform Sauci from binary explicit/clean filtering to graduated intensity-based control.
Overview
Problem
The current binary "Spicy Content" toggle is too coarse. Users either see ALL explicit content or NONE, which:
- Makes the app appear like "smut" to App Store reviewers if explicit is on
- Provides no middle ground for users who want flirty but not explicit content
Solution
Replace the binary toggle with a 5-level intensity slider:
| Level | Label | Content Type |
|---|---|---|
| 1 | Light | Conversations, compliments, emotional connection |
| 2 | Mild | Cuddling, gentle kisses, flirty texts |
| 3 | Moderate | Making out, massage, suggestive content |
| 4 | Spicy | Intimate activities, explicit content |
| 5 | Intense | Advanced intimacy, kinks, fantasies |
Key Changes
- Default: New users see max intensity 2 (Mild)
- Control: Graduated 1-5 slider instead of on/off
- Filtering: Filter by question intensity, not just pack explicit flag
- Display: Show intensity range on pack cards
Tasks
Phase 1: Database Migration
File: apps/supabase/migrations/YYYYMMDDHHMMSS_add_intensity_levels.sql
- Add
max_intensitycolumn toprofilestable (INTEGER, default 2, CHECK 1-5) - Migrate existing users:
show_explicit_content=true→ 5, else → 2 - Add
min_intensity,max_intensity,avg_intensitycolumns toquestion_packs - Create trigger to auto-update pack intensity stats when questions change
- Backfill intensity stats for existing packs
-- Example migration
ALTER TABLE profiles
ADD COLUMN max_intensity INTEGER DEFAULT 2
CHECK (max_intensity >= 1 AND max_intensity <= 5);
UPDATE profiles SET max_intensity = CASE
WHEN show_explicit_content = true THEN 5
ELSE 2
END;
Phase 2: Mobile Types
File: apps/mobile/src/types/index.ts
- Add
max_intensity: 1 | 2 | 3 | 4 | 5toProfileinterface - Add
min_intensity?: number,max_intensity?: number,avg_intensity?: numbertoQuestionPackinterface
Phase 3: IntensitySlider Component
File: apps/mobile/src/components/ui/IntensitySlider.tsx (NEW)
- Create slider component with 5 levels
- Add flame icons that fill based on selected level
- Display label and description for current level
- Apply glass-morphism styling matching app theme
- Add haptic feedback on level change
interface IntensitySliderProps {
value: 1 | 2 | 3 | 4 | 5;
onValueChange: (value: 1 | 2 | 3 | 4 | 5) => void;
disabled?: boolean;
}
Phase 4: Onboarding Update
File: apps/mobile/app/(app)/onboarding.tsx
- Replace Stage 4 binary choice with IntensitySlider
- Update heading: "Set Your Comfort Level"
- Change default state from
showExplicit: falsetomaxIntensity: 2 - Update profile save to set both fields for backwards compatibility
Current Stage 4:
"Show me everything" vs "Keep it clean"
New Stage 4:
"Set Your Comfort Level"
[Slider: 1 ----*---- 5]
Light Intense
Current: "Mild - Cuddling, gentle kisses, flirty texts"
Note: "Start gentle - you can turn up the heat anytime"
Phase 5: Settings Update
File: apps/mobile/src/features/profile/components/PrivacySettings.tsx
- Replace
SwitchItemwithIntensitySlider
File: apps/mobile/src/features/profile/hooks/useProfileSettings.ts
- Change state from
showExplicit: booleantomaxIntensity: number - Update handler to save both
max_intensityandshow_explicit_content - Call
fetchPacks()after intensity change to refresh filtered packs
Phase 6: Pack Store Filtering
File: apps/mobile/src/store/packsStore.ts
- Update
fetchPacks()to filter by intensity instead of explicit flag
// Current
if (!showExplicit) {
query = query.eq("is_explicit", false);
}
// New
const maxIntensity = useAuthStore.getState().user?.max_intensity ?? 2;
query = query.or(`max_intensity.is.null,max_intensity.lte.${maxIntensity}`);
Phase 7: Pack Display Enhancement
File: apps/mobile/app/(app)/packs/questions.tsx
- Add intensity badge to pack cards showing flame icons
- Display intensity label: "Light" or "Light - Moderate" range
Phase 8: Default Packs for New Couples
File: apps/supabase/functions/manage-couple/index.ts
- When couple forms, auto-enable packs where
avg_intensity <= 2
const { data: safePacks } = await supabase
.from('question_packs')
.select('id')
.lte('avg_intensity', 2)
.eq('is_public', true);
await supabase.from('couple_packs').insert(
safePacks.map(p => ({ couple_id, pack_id: p.id, enabled: true }))
);
Files Summary
| File | Action |
|---|---|
apps/supabase/migrations/XXX_add_intensity_levels.sql |
CREATE |
apps/mobile/src/types/index.ts |
MODIFY |
apps/mobile/src/components/ui/IntensitySlider.tsx |
CREATE |
apps/mobile/app/(app)/onboarding.tsx |
MODIFY |
apps/mobile/src/features/profile/components/PrivacySettings.tsx |
MODIFY |
apps/mobile/src/features/profile/hooks/useProfileSettings.ts |
MODIFY |
apps/mobile/src/store/packsStore.ts |
MODIFY |
apps/mobile/app/(app)/packs/questions.tsx |
MODIFY |
apps/supabase/functions/manage-couple/index.ts |
MODIFY |
Backwards Compatibility
- Keep
show_explicit_contentcolumn during transition period - Sync both fields when user changes intensity:
max_intensity >= 3→show_explicit_content = truemax_intensity < 3→show_explicit_content = false
- Deprecation: Remove
show_explicit_contentafter 2-3 app update cycles
App Store Benefits
After implementation:
- Default experience shows only Light/Mild content (levels 1-2)
- App Store screenshots will feature connection-focused questions
- Users must consciously opt-in to see spicier content
- Categories already ordered with connection-focused content first