From 45058fdd5fb8763ef6906237e076218d650f029d Mon Sep 17 00:00:00 2001 From: null Date: Wed, 17 Jun 2026 21:57:40 -0500 Subject: [PATCH] feat: couple intimacy seed data, "How Well" screen, ThisOrThat + wheel polish --- .../closer/core/navigation/AppNavigation.kt | 5 + .../app/closer/core/navigation/AppRoute.kt | 4 +- .../java/app/closer/data/local/QuestionDao.kt | 3 + .../data/repository/FakeQuestionRepository.kt | 2 + .../data/repository/RoomQuestionRepository.kt | 4 + .../domain/repository/QuestionRepository.kt | 1 + .../app/closer/ui/howwell/HowWellScreen.kt | 918 ++++ .../java/app/closer/ui/play/PlayHubScreen.kt | 84 + .../closer/ui/thisorthat/ThisOrThatScreen.kt | 179 +- .../app/closer/ui/wheel/SpinWheelScreen.kt | 40 +- seed/questions/couple_intimacy.json | 3828 +++++++++++++++++ 11 files changed, 5052 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/app/closer/ui/howwell/HowWellScreen.kt create mode 100644 seed/questions/couple_intimacy.json diff --git a/app/src/main/java/app/closer/core/navigation/AppNavigation.kt b/app/src/main/java/app/closer/core/navigation/AppNavigation.kt index 5c68037c..ae6500dd 100644 --- a/app/src/main/java/app/closer/core/navigation/AppNavigation.kt +++ b/app/src/main/java/app/closer/core/navigation/AppNavigation.kt @@ -48,6 +48,7 @@ import app.closer.ui.dates.DateBuilderScreen import app.closer.ui.dates.BucketListScreen import app.closer.ui.paywall.PaywallScreen import app.closer.ui.play.PlayHubScreen +import app.closer.ui.howwell.HowWellScreen import app.closer.ui.thisorthat.ThisOrThatScreen import app.closer.ui.questions.DailyQuestionScreen import app.closer.ui.questions.QuestionCategoryScreen @@ -302,6 +303,9 @@ fun AppNavigation( composable(route = AppRoute.THIS_OR_THAT) { ThisOrThatScreen(onNavigate = navigateRoute) } + composable(route = AppRoute.HOW_WELL) { + HowWellScreen(onNavigate = navigateRoute) + } // Dates composable(route = AppRoute.DATE_MATCH) { @@ -378,6 +382,7 @@ private val shellBackRoutes = setOf( AppRoute.DATE_BUILDER, AppRoute.BUCKET_LIST, AppRoute.THIS_OR_THAT, + AppRoute.HOW_WELL, AppRoute.ACCOUNT, AppRoute.SUBSCRIPTION, AppRoute.PAYWALL diff --git a/app/src/main/java/app/closer/core/navigation/AppRoute.kt b/app/src/main/java/app/closer/core/navigation/AppRoute.kt index 144101a3..860a7ae4 100644 --- a/app/src/main/java/app/closer/core/navigation/AppRoute.kt +++ b/app/src/main/java/app/closer/core/navigation/AppRoute.kt @@ -39,6 +39,7 @@ object AppRoute { const val DATE_BUILDER = "date_builder" const val BUCKET_LIST = "bucket_list" const val THIS_OR_THAT = "this_or_that" + const val HOW_WELL = "how_well" // Question thread: coupleId and questionId are required; prevId and nextId are optional. const val QUESTION_THREAD = @@ -87,7 +88,8 @@ object AppRoute { Definition(DATE_MATCHES, "Matches", "dates"), Definition(DATE_BUILDER, "Plan a Date", "dates"), Definition(BUCKET_LIST, "Our Bucket List", "dates"), - Definition(THIS_OR_THAT, "This or That", "play") + Definition(THIS_OR_THAT, "This or That", "play"), + Definition(HOW_WELL, "How Well Do You Know Me", "play") ) val topLevelRoutes = setOf( diff --git a/app/src/main/java/app/closer/data/local/QuestionDao.kt b/app/src/main/java/app/closer/data/local/QuestionDao.kt index 4b4398e7..408f0e17 100644 --- a/app/src/main/java/app/closer/data/local/QuestionDao.kt +++ b/app/src/main/java/app/closer/data/local/QuestionDao.kt @@ -24,6 +24,9 @@ interface QuestionDao { @Query("SELECT * FROM question WHERE type = :type AND status = 'active'") suspend fun getQuestionsByType(type: String): List + @Query("SELECT * FROM question WHERE type IN ('single_choice', 'this_or_that', 'scale') AND status = 'active'") + suspend fun getQuestionsForPrediction(): List + @Query("SELECT * FROM question WHERE is_premium = 0 AND status = 'active'") suspend fun getFreeQuestions(): List diff --git a/app/src/main/java/app/closer/data/repository/FakeQuestionRepository.kt b/app/src/main/java/app/closer/data/repository/FakeQuestionRepository.kt index 5c7c989c..f1ffa03c 100644 --- a/app/src/main/java/app/closer/data/repository/FakeQuestionRepository.kt +++ b/app/src/main/java/app/closer/data/repository/FakeQuestionRepository.kt @@ -18,4 +18,6 @@ class FakeQuestionRepository : QuestionRepository { override suspend fun getQuestionCountByCategory(categoryId: String): Int = 0 override suspend fun getQuestionsByType(type: String): List = emptyList() + + override suspend fun getQuestionsForPrediction(): List = emptyList() } diff --git a/app/src/main/java/app/closer/data/repository/RoomQuestionRepository.kt b/app/src/main/java/app/closer/data/repository/RoomQuestionRepository.kt index eecfe68f..7f63928a 100644 --- a/app/src/main/java/app/closer/data/repository/RoomQuestionRepository.kt +++ b/app/src/main/java/app/closer/data/repository/RoomQuestionRepository.kt @@ -42,4 +42,8 @@ class RoomQuestionRepository @Inject constructor( override suspend fun getQuestionsByType(type: String): List { return questionDao.getQuestionsByType(type).map { it.toQuestion() } } + + override suspend fun getQuestionsForPrediction(): List { + return questionDao.getQuestionsForPrediction().map { it.toQuestion() } + } } diff --git a/app/src/main/java/app/closer/domain/repository/QuestionRepository.kt b/app/src/main/java/app/closer/domain/repository/QuestionRepository.kt index 4c068f05..f673cd80 100644 --- a/app/src/main/java/app/closer/domain/repository/QuestionRepository.kt +++ b/app/src/main/java/app/closer/domain/repository/QuestionRepository.kt @@ -11,4 +11,5 @@ interface QuestionRepository { suspend fun getCategoryById(id: String): QuestionCategory? suspend fun getQuestionCountByCategory(categoryId: String): Int suspend fun getQuestionsByType(type: String): List + suspend fun getQuestionsForPrediction(): List } diff --git a/app/src/main/java/app/closer/ui/howwell/HowWellScreen.kt b/app/src/main/java/app/closer/ui/howwell/HowWellScreen.kt new file mode 100644 index 00000000..390deb8e --- /dev/null +++ b/app/src/main/java/app/closer/ui/howwell/HowWellScreen.kt @@ -0,0 +1,918 @@ +package app.closer.ui.howwell + +import android.util.Log +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawingPadding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import app.closer.core.navigation.AppRoute +import app.closer.domain.model.AnswerConfig +import app.closer.domain.model.ChoiceAnswerConfigImpl +import app.closer.domain.model.ChoiceOption +import app.closer.domain.model.Question +import app.closer.domain.model.ScaleAnswerConfigImpl +import app.closer.domain.model.ThisOrThatAnswerConfigImpl +import app.closer.domain.repository.QuestionRepository +import app.closer.ui.theme.CloserPalette +import app.closer.ui.theme.closerBackgroundBrush +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlin.math.abs +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +// ── Domain ──────────────────────────────────────────────────────────────────── + +data class HowWellAnswer( + val selectedOptionId: String? = null, + val scaleValue: Int? = null +) { + val isEmpty get() = selectedOptionId == null && scaleValue == null +} + +fun HowWellAnswer.isMatch(other: HowWellAnswer): Boolean = when { + selectedOptionId != null -> selectedOptionId == other.selectedOptionId + scaleValue != null && other.scaleValue != null -> scaleValue == other.scaleValue + else -> false +} + +fun HowWellAnswer.isClose(other: HowWellAnswer): Boolean = + scaleValue != null && other.scaleValue != null && + !isMatch(other) && abs(scaleValue - other.scaleValue) == 1 + +fun HowWellAnswer.displayText(config: AnswerConfig?): String = when { + selectedOptionId != null -> when (config) { + is ChoiceAnswerConfigImpl -> config.config.options.find { it.id == selectedOptionId }?.text ?: selectedOptionId + is ThisOrThatAnswerConfigImpl -> when (selectedOptionId) { + config.config.optionA.id -> config.config.optionA.text + config.config.optionB.id -> config.config.optionB.text + else -> selectedOptionId + } + else -> selectedOptionId + } + scaleValue != null -> "$scaleValue" + else -> "—" +} + +data class HowWellResult( + val question: Question, + val playerAAnswer: HowWellAnswer, + val playerBAnswer: HowWellAnswer, + val isMatch: Boolean, + val isClose: Boolean +) + +enum class HowWellPhase { + LOADING, PLAYER_A_INTRO, PLAYER_A_TURN, HANDOFF, + PLAYER_B_TURN, REVEALING, COMPLETE +} + +data class HowWellUiState( + val phase: HowWellPhase = HowWellPhase.LOADING, + val questions: List = emptyList(), + val currentIndex: Int = 0, + val playerAAnswers: List = emptyList(), + val results: List = emptyList(), + val selectedOptionId: String? = null, + val selectedScale: Int? = null, + val score: Int = 0, + val error: String? = null +) + +// ── ViewModel ───────────────────────────────────────────────────────────────── + +@HiltViewModel +class HowWellViewModel @Inject constructor( + private val repository: QuestionRepository +) : ViewModel() { + + private val _uiState = MutableStateFlow(HowWellUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + init { load() } + + private fun load() { + viewModelScope.launch { + val questions = runCatching { + repository.getQuestionsForPrediction().shuffled().take(SESSION_SIZE) + } + .onFailure { Log.w(TAG, "Failed to load prediction questions", it) } + .getOrElse { emptyList() } + _uiState.update { + it.copy( + phase = if (questions.isEmpty()) HowWellPhase.LOADING else HowWellPhase.PLAYER_A_INTRO, + questions = questions, + error = if (questions.isEmpty()) "No questions available." else null + ) + } + } + } + + fun selectOption(id: String) = _uiState.update { it.copy(selectedOptionId = id, selectedScale = null) } + fun selectScale(v: Int) = _uiState.update { it.copy(selectedScale = v, selectedOptionId = null) } + + fun startPlayerA() = _uiState.update { it.copy(phase = HowWellPhase.PLAYER_A_TURN, currentIndex = 0) } + + fun confirmAnswer() { + val s = _uiState.value + val answer = HowWellAnswer(s.selectedOptionId, s.selectedScale) + if (answer.isEmpty) return + val newAnswers = s.playerAAnswers + answer + val next = s.currentIndex + 1 + _uiState.update { + it.copy( + playerAAnswers = newAnswers, + selectedOptionId = null, + selectedScale = null, + currentIndex = if (next < it.questions.size) next else it.currentIndex, + phase = if (next >= it.questions.size) HowWellPhase.HANDOFF else HowWellPhase.PLAYER_A_TURN + ) + } + } + + fun readyForPlayerB() = _uiState.update { + it.copy(phase = HowWellPhase.PLAYER_B_TURN, currentIndex = 0, selectedOptionId = null, selectedScale = null) + } + + fun confirmPrediction() { + val s = _uiState.value + val prediction = HowWellAnswer(s.selectedOptionId, s.selectedScale) + if (prediction.isEmpty) return + val actual = s.playerAAnswers.getOrNull(s.currentIndex) ?: return + val question = s.questions.getOrNull(s.currentIndex) ?: return + val match = prediction.isMatch(actual) + val close = prediction.isClose(actual) + _uiState.update { + it.copy( + results = it.results + HowWellResult(question, actual, prediction, match, close), + selectedOptionId = null, + selectedScale = null, + score = if (match) it.score + 1 else it.score, + phase = HowWellPhase.REVEALING + ) + } + } + + fun nextQuestion() { + val next = _uiState.value.currentIndex + 1 + val total = _uiState.value.questions.size + _uiState.update { + it.copy( + currentIndex = if (next < total) next else it.currentIndex, + phase = if (next >= total) HowWellPhase.COMPLETE else HowWellPhase.PLAYER_B_TURN + ) + } + } + + fun restart() { + _uiState.value = HowWellUiState() + load() + } + + companion object { + const val SESSION_SIZE = 10 + private const val TAG = "HowWellViewModel" + } +} + +// ── Root screen ─────────────────────────────────────────────────────────────── + +@Composable +fun HowWellScreen( + onNavigate: (String) -> Unit = {}, + viewModel: HowWellViewModel = hiltViewModel() +) { + val state by viewModel.uiState.collectAsState() + + Box( + modifier = Modifier + .fillMaxSize() + .background(closerBackgroundBrush()) + ) { + when (state.phase) { + HowWellPhase.LOADING -> CircularProgressIndicator( + modifier = Modifier.align(Alignment.Center), + color = CloserPalette.PurpleDeep + ) + HowWellPhase.PLAYER_A_INTRO -> PlayerIntroScreen( + playerNumber = 1, + total = state.questions.size, + onReady = viewModel::startPlayerA + ) + HowWellPhase.PLAYER_A_TURN -> { + val q = state.questions.getOrNull(state.currentIndex) ?: return@Box + AnswerScreen( + question = q, + index = state.currentIndex, + total = state.questions.size, + isPlayerB = false, + selectedOptionId = state.selectedOptionId, + selectedScale = state.selectedScale, + onSelectOption = viewModel::selectOption, + onSelectScale = viewModel::selectScale, + onConfirm = viewModel::confirmAnswer, + onQuit = { onNavigate(AppRoute.PLAY) } + ) + } + HowWellPhase.HANDOFF -> HandoffScreen(onReady = viewModel::readyForPlayerB) + HowWellPhase.PLAYER_B_TURN -> { + val q = state.questions.getOrNull(state.currentIndex) ?: return@Box + AnswerScreen( + question = q, + index = state.currentIndex, + total = state.questions.size, + isPlayerB = true, + selectedOptionId = state.selectedOptionId, + selectedScale = state.selectedScale, + onSelectOption = viewModel::selectOption, + onSelectScale = viewModel::selectScale, + onConfirm = viewModel::confirmPrediction, + onQuit = { onNavigate(AppRoute.PLAY) } + ) + } + HowWellPhase.REVEALING -> { + val result = state.results.lastOrNull() ?: return@Box + RevealScreen( + result = result, + questionNumber = state.currentIndex + 1, + total = state.questions.size, + score = state.score, + onNext = viewModel::nextQuestion + ) + } + HowWellPhase.COMPLETE -> CompleteScreen( + score = state.score, + total = state.questions.size, + results = state.results, + onPlayAgain = viewModel::restart, + onHome = { onNavigate(AppRoute.PLAY) } + ) + } + } +} + +// ── Phase screens ───────────────────────────────────────────────────────────── + +@Composable +private fun PlayerIntroScreen(playerNumber: Int, total: Int, onReady: () -> Unit) { + Column( + modifier = Modifier + .fillMaxSize() + .safeDrawingPadding() + .navigationBarsPadding() + .padding(horizontal = 28.dp, vertical = 40.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text(text = if (playerNumber == 1) "👤" else "🔮", fontSize = 56.sp, textAlign = TextAlign.Center) + Spacer(Modifier.height(20.dp)) + Surface(shape = RoundedCornerShape(999.dp), color = CloserPalette.PurpleMist) { + Text( + text = "Player $playerNumber", + modifier = Modifier.padding(horizontal = 14.dp, vertical = 6.dp), + style = MaterialTheme.typography.labelLarge, + color = CloserPalette.PurpleDeep, + fontWeight = FontWeight.SemiBold + ) + } + Spacer(Modifier.height(16.dp)) + Text( + text = if (playerNumber == 1) + "Answer $total questions honestly.\nYour partner will try to predict what you said." + else + "For each question, guess what your partner answered.\nNo peeking!", + style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.SemiBold), + color = Color(0xFF261D2E), + textAlign = TextAlign.Center, + lineHeight = MaterialTheme.typography.headlineSmall.lineHeight + ) + if (playerNumber == 1) { + Spacer(Modifier.height(10.dp)) + Text( + text = "Ask your partner to look away.", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center + ) + } + Spacer(Modifier.height(36.dp)) + Button( + onClick = onReady, + modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp), + shape = RoundedCornerShape(18.dp), + colors = ButtonDefaults.buttonColors(containerColor = CloserPalette.PurpleDeep) + ) { Text("I'm ready", color = Color.White) } + } +} + +@Composable +private fun HandoffScreen(onReady: () -> Unit) { + Column( + modifier = Modifier + .fillMaxSize() + .safeDrawingPadding() + .navigationBarsPadding() + .padding(horizontal = 28.dp, vertical = 40.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("🎉", fontSize = 56.sp, textAlign = TextAlign.Center) + Spacer(Modifier.height(20.dp)) + Text( + text = "Pass the phone!", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.SemiBold), + color = Color(0xFF261D2E), + textAlign = TextAlign.Center + ) + Spacer(Modifier.height(10.dp)) + Text( + text = "Player 1 is done. Hand the phone to Player 2 — keep your answers secret!", + style = MaterialTheme.typography.bodyLarge, + color = Color(0xFF5A5060), + textAlign = TextAlign.Center + ) + Spacer(Modifier.height(36.dp)) + Button( + onClick = onReady, + modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp), + shape = RoundedCornerShape(18.dp), + colors = ButtonDefaults.buttonColors(containerColor = CloserPalette.PurpleDeep) + ) { Text("I'm Player 2, let's go!", color = Color.White) } + } +} + +@Composable +private fun AnswerScreen( + question: Question, + index: Int, + total: Int, + isPlayerB: Boolean, + selectedOptionId: String?, + selectedScale: Int?, + onSelectOption: (String) -> Unit, + onSelectScale: (Int) -> Unit, + onConfirm: () -> Unit, + onQuit: () -> Unit +) { + val hasSelection = selectedOptionId != null || selectedScale != null + + Column( + modifier = Modifier + .fillMaxSize() + .safeDrawingPadding() + .navigationBarsPadding() + .padding(horizontal = 20.dp, vertical = 16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Surface(shape = RoundedCornerShape(999.dp), color = CloserPalette.PurpleMist) { + Text( + text = "Player ${if (isPlayerB) 2 else 1} · ${index + 1} / $total", + modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp), + style = MaterialTheme.typography.labelMedium, + color = CloserPalette.PurpleDeep, + fontWeight = FontWeight.SemiBold + ) + } + TextButton(onClick = onQuit) { + Text("Quit", color = MaterialTheme.colorScheme.onSurfaceVariant) + } + } + + LinearProgressIndicator( + progress = { index.toFloat() / total }, + modifier = Modifier.fillMaxWidth(), + color = CloserPalette.PurpleDeep, + trackColor = CloserPalette.PurpleMist + ) + + Card( + modifier = Modifier.fillMaxWidth().weight(1f), + shape = RoundedCornerShape(28.dp), + colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.9f)), + elevation = CardDefaults.cardElevation(8.dp) + ) { + Box(modifier = Modifier.fillMaxSize().padding(24.dp), contentAlignment = Alignment.Center) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + if (isPlayerB) { + Surface(shape = RoundedCornerShape(999.dp), color = CloserPalette.PurpleMist) { + Text( + text = "What did Player 1 say?", + modifier = Modifier.padding(horizontal = 12.dp, vertical = 5.dp), + style = MaterialTheme.typography.labelSmall, + color = CloserPalette.PurpleDeep, + fontWeight = FontWeight.SemiBold + ) + } + } + Text( + text = question.text, + style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.SemiBold), + color = Color(0xFF261D2E), + textAlign = TextAlign.Center, + maxLines = 5, + overflow = TextOverflow.Ellipsis + ) + } + } + } + + when (val config = question.answerConfig) { + is ChoiceAnswerConfigImpl -> SingleChoiceInput( + options = config.config.options, + selectedId = selectedOptionId, + onSelect = onSelectOption + ) + is ThisOrThatAnswerConfigImpl -> BinaryChoiceInput( + optionA = config.config.optionA, + optionB = config.config.optionB, + selectedId = selectedOptionId, + onSelect = onSelectOption + ) + is ScaleAnswerConfigImpl -> ScaleInput( + min = config.config.minScale, + max = config.config.maxScale, + minLabel = config.config.minLabel, + maxLabel = config.config.maxLabel, + selected = selectedScale, + onSelect = onSelectScale + ) + else -> {} + } + + Button( + onClick = onConfirm, + enabled = hasSelection, + modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp), + shape = RoundedCornerShape(18.dp), + colors = ButtonDefaults.buttonColors(containerColor = CloserPalette.PurpleDeep) + ) { + Text( + text = if (index + 1 >= total && !isPlayerB) "Done →" else "Confirm →", + color = Color.White + ) + } + } +} + +@Composable +private fun RevealScreen( + result: HowWellResult, + questionNumber: Int, + total: Int, + score: Int, + onNext: () -> Unit +) { + val matchColor = Color(0xFF2E7D32) + val closeColor = Color(0xFFF57F17) + val missColor = Color(0xFFC62828) + val accentColor = when { + result.isMatch -> matchColor + result.isClose -> closeColor + else -> missColor + } + val bgColor = when { + result.isMatch -> Color(0xFFE8F5E9) + result.isClose -> Color(0xFFFFF8E1) + else -> Color(0xFFFCE4EC) + } + + Column( + modifier = Modifier + .fillMaxSize() + .safeDrawingPadding() + .navigationBarsPadding() + .padding(horizontal = 20.dp, vertical = 16.dp), + verticalArrangement = Arrangement.spacedBy(14.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "$questionNumber / $total", + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Surface(shape = RoundedCornerShape(999.dp), color = CloserPalette.PurpleMist) { + Text( + text = "Score: $score / $questionNumber", + modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp), + style = MaterialTheme.typography.labelMedium, + color = CloserPalette.PurpleDeep, + fontWeight = FontWeight.SemiBold + ) + } + } + + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(18.dp), + colors = CardDefaults.cardColors(containerColor = bgColor), + elevation = CardDefaults.cardElevation(0.dp) + ) { + Row( + modifier = Modifier.padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = if (result.isMatch) "✓" else if (result.isClose) "≈" else "✗", + fontSize = 28.sp, + color = accentColor + ) + Text( + text = if (result.isMatch) "Match!" else if (result.isClose) "So close!" else "Not quite", + style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.SemiBold), + color = accentColor + ) + } + } + + Text( + text = result.question.text, + style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.SemiBold), + color = Color(0xFF261D2E), + maxLines = 3, + overflow = TextOverflow.Ellipsis + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + AnswerRevealCard( + label = "Player 1 said", + text = result.playerAAnswer.displayText(result.question.answerConfig), + isMatch = result.isMatch, + modifier = Modifier.weight(1f) + ) + AnswerRevealCard( + label = "Player 2 guessed", + text = result.playerBAnswer.displayText(result.question.answerConfig), + isMatch = result.isMatch, + modifier = Modifier.weight(1f) + ) + } + + Spacer(Modifier.weight(1f)) + + Button( + onClick = onNext, + modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp), + shape = RoundedCornerShape(18.dp), + colors = ButtonDefaults.buttonColors(containerColor = CloserPalette.PurpleDeep) + ) { + Text( + text = if (questionNumber >= total) "See results" else "Next →", + color = Color.White + ) + } + } +} + +@Composable +private fun AnswerRevealCard( + label: String, + text: String, + isMatch: Boolean, + modifier: Modifier = Modifier +) { + Card( + modifier = modifier.heightIn(min = 90.dp), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors( + containerColor = if (isMatch) Color(0xFFE8F5E9) else MaterialTheme.colorScheme.surface + ), + elevation = CardDefaults.cardElevation(4.dp) + ) { + Column( + modifier = Modifier.padding(12.dp), + verticalArrangement = Arrangement.spacedBy(6.dp) + ) { + Text( + text = label, + style = MaterialTheme.typography.labelSmall, + color = if (isMatch) Color(0xFF2E7D32) else MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( + text = text, + style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.SemiBold), + color = if (isMatch) Color(0xFF1B5E20) else MaterialTheme.colorScheme.onSurface, + maxLines = 3, + overflow = TextOverflow.Ellipsis + ) + } + } +} + +@Composable +private fun CompleteScreen( + score: Int, + total: Int, + results: List, + onPlayAgain: () -> Unit, + onHome: () -> Unit +) { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .safeDrawingPadding() + .navigationBarsPadding() + .padding(horizontal = 20.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + item { + Column( + modifier = Modifier.fillMaxWidth().padding(top = 32.dp, bottom = 8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + Text("🎯", fontSize = 56.sp, textAlign = TextAlign.Center) + Text( + text = "$score / $total", + style = MaterialTheme.typography.displaySmall.copy(fontWeight = FontWeight.Bold), + color = CloserPalette.PurpleDeep, + textAlign = TextAlign.Center + ) + Text( + text = scoreLabel(score, total), + style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.SemiBold), + color = Color(0xFF261D2E), + textAlign = TextAlign.Center + ) + } + } + + item { + Text( + text = "Breakdown", + style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.SemiBold), + color = MaterialTheme.colorScheme.onBackground + ) + } + + items(results) { result -> BreakdownRow(result) } + + item { + Column( + modifier = Modifier.padding(top = 8.dp, bottom = 20.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + Button( + onClick = onPlayAgain, + modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp), + shape = RoundedCornerShape(18.dp), + colors = ButtonDefaults.buttonColors(containerColor = CloserPalette.PurpleDeep) + ) { Text("Play again", color = Color.White) } + OutlinedButton( + onClick = onHome, + modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp), + shape = RoundedCornerShape(18.dp) + ) { Text("Back to Play") } + } + } + } +} + +@Composable +private fun BreakdownRow(result: HowWellResult) { + val matchColor = Color(0xFF2E7D32) + val closeColor = Color(0xFFF57F17) + val missColor = Color(0xFFC62828) + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(14.dp), + colors = CardDefaults.cardColors( + containerColor = if (result.isMatch) Color(0xFFE8F5E9) else MaterialTheme.colorScheme.surface + ), + elevation = CardDefaults.cardElevation(2.dp) + ) { + Row( + modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp), + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = if (result.isMatch) "✓" else if (result.isClose) "≈" else "✗", + fontSize = 18.sp, + color = if (result.isMatch) matchColor else if (result.isClose) closeColor else missColor + ) + Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = result.question.text, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + val config = result.question.answerConfig + if (result.isMatch) { + Text( + text = result.playerAAnswer.displayText(config), + style = MaterialTheme.typography.labelMedium.copy(fontWeight = FontWeight.SemiBold), + color = matchColor, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } else { + Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { + Text( + text = result.playerAAnswer.displayText(config), + style = MaterialTheme.typography.labelMedium.copy(fontWeight = FontWeight.SemiBold), + color = if (result.isClose) closeColor else missColor, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f, fill = false) + ) + Text( + text = "→ ${result.playerBAnswer.displayText(config)}", + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f, fill = false) + ) + } + } + } + } + } +} + +// ── Answer input widgets ────────────────────────────────────────────────────── + +@Composable +private fun SingleChoiceInput( + options: List, + selectedId: String?, + onSelect: (String) -> Unit +) { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + options.forEach { option -> + val selected = selectedId == option.id + Card( + onClick = { onSelect(option.id) }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(14.dp), + colors = CardDefaults.cardColors( + containerColor = if (selected) CloserPalette.PurpleDeep else MaterialTheme.colorScheme.surface + ), + elevation = CardDefaults.cardElevation(if (selected) 6.dp else 2.dp) + ) { + Text( + text = option.text, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 14.dp), + style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Medium), + color = if (selected) Color.White else MaterialTheme.colorScheme.onSurface, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + } + } +} + +@Composable +private fun BinaryChoiceInput( + optionA: ChoiceOption, + optionB: ChoiceOption, + selectedId: String?, + onSelect: (String) -> Unit +) { + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { + listOf(optionA to "A", optionB to "B").forEach { (option, label) -> + val selected = selectedId == option.id + Card( + onClick = { onSelect(option.id) }, + modifier = Modifier.weight(1f).heightIn(min = 80.dp), + shape = RoundedCornerShape(14.dp), + colors = CardDefaults.cardColors( + containerColor = if (selected) CloserPalette.PurpleDeep else MaterialTheme.colorScheme.surface + ), + elevation = CardDefaults.cardElevation(if (selected) 6.dp else 2.dp) + ) { + Column( + modifier = Modifier.fillMaxWidth().padding(12.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = label, + style = MaterialTheme.typography.labelSmall, + fontWeight = FontWeight.Bold, + color = if (selected) Color.White.copy(alpha = 0.7f) else MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( + text = option.text, + style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold), + color = if (selected) Color.White else MaterialTheme.colorScheme.onSurface, + textAlign = TextAlign.Center, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + } + } + } +} + +@Composable +private fun ScaleInput( + min: Int, + max: Int, + minLabel: String, + maxLabel: String, + selected: Int?, + onSelect: (Int) -> Unit +) { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + (min..max).forEach { value -> + val isSelected = selected == value + Surface( + onClick = { onSelect(value) }, + shape = CircleShape, + color = if (isSelected) CloserPalette.PurpleDeep else MaterialTheme.colorScheme.surface, + modifier = Modifier.size(52.dp), + shadowElevation = if (isSelected) 6.dp else 2.dp + ) { + Box(contentAlignment = Alignment.Center) { + Text( + text = "$value", + style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.SemiBold), + color = if (isSelected) Color.White else MaterialTheme.colorScheme.onSurface + ) + } + } + } + } + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text(minLabel, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant) + Text(maxLabel, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant) + } + } +} + +// ── Helpers ─────────────────────────────────────────────────────────────────── + +private fun scoreLabel(score: Int, total: Int): String = when { + score == total -> "Mind reader! 🤯" + score >= total * 0.8 -> "You really know each other 💜" + score >= total * 0.6 -> "Pretty good!" + score >= total * 0.4 -> "Getting there!" + else -> "Room to grow 🌱" +} diff --git a/app/src/main/java/app/closer/ui/play/PlayHubScreen.kt b/app/src/main/java/app/closer/ui/play/PlayHubScreen.kt index 85e5f6c3..c3a21a37 100644 --- a/app/src/main/java/app/closer/ui/play/PlayHubScreen.kt +++ b/app/src/main/java/app/closer/ui/play/PlayHubScreen.kt @@ -102,6 +102,12 @@ private fun PlayHubContent( ) } + item { + HowWellCard( + onClick = { onNavigate(AppRoute.HOW_WELL) } + ) + } + item { Row( modifier = Modifier.fillMaxWidth(), @@ -231,6 +237,84 @@ private fun ThisOrThatCard( } } +@Composable +private fun HowWellCard( + onClick: () -> Unit +) { + Card( + onClick = onClick, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(24.dp), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), + elevation = CardDefaults.cardElevation(defaultElevation = 6.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(18.dp), + horizontalArrangement = Arrangement.spacedBy(14.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Surface( + shape = RoundedCornerShape(18.dp), + color = CloserPalette.Romantic.copy(alpha = 0.12f), + modifier = Modifier.size(52.dp) + ) { + Box(contentAlignment = Alignment.Center) { + Text( + text = "💜", + style = MaterialTheme.typography.titleMedium + ) + } + } + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "How Well Do You Know Me", + style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.SemiBold), + color = MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f) + ) + Surface( + shape = RoundedCornerShape(999.dp), + color = CloserPalette.Romantic.copy(alpha = 0.12f) + ) { + Text( + text = "10 rounds", + modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp), + style = MaterialTheme.typography.labelSmall, + color = CloserPalette.Romantic, + fontWeight = FontWeight.SemiBold + ) + } + } + Text( + text = "One answers, the other predicts. Find out how well you really know each other.", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowForward, + contentDescription = null, + tint = CloserPalette.Romantic, + modifier = Modifier.size(18.dp) + ) + } + } +} + @Composable private fun FeaturedPlayCard( onClick: () -> Unit diff --git a/app/src/main/java/app/closer/ui/thisorthat/ThisOrThatScreen.kt b/app/src/main/java/app/closer/ui/thisorthat/ThisOrThatScreen.kt index b484e188..2ed0efbe 100644 --- a/app/src/main/java/app/closer/ui/thisorthat/ThisOrThatScreen.kt +++ b/app/src/main/java/app/closer/ui/thisorthat/ThisOrThatScreen.kt @@ -2,11 +2,18 @@ package app.closer.ui.thisorthat import android.util.Log import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.foundation.background +import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -18,30 +25,34 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Divider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.scale +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -196,6 +207,12 @@ private fun ThisOrThatContent( onSelect: (String) -> Unit, onBack: () -> Unit ) { + val animatedProgress by animateFloatAsState( + targetValue = ((currentIndex + 1).toFloat() / total.coerceAtLeast(1)).coerceIn(0f, 1f), + animationSpec = tween(durationMillis = 260), + label = "this_or_that_progress" + ) + Column( modifier = Modifier .fillMaxSize() @@ -226,6 +243,8 @@ private fun ThisOrThatContent( } } + ThisOrThatProgress(progress = animatedProgress) + Card( modifier = Modifier .fillMaxWidth() @@ -240,6 +259,9 @@ private fun ThisOrThatContent( .padding(24.dp), contentAlignment = Alignment.Center ) { + ChoicePromptBackdrop( + modifier = Modifier.matchParentSize() + ) Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(10.dp) @@ -278,12 +300,8 @@ private fun ThisOrThatContent( onSelect = onSelect ) - Text( - text = "or", - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant + VersusBadge( + modifier = Modifier.align(Alignment.CenterHorizontally) ) OptionCard( @@ -298,6 +316,69 @@ private fun ThisOrThatContent( } } +@Composable +private fun ThisOrThatProgress( + progress: Float +) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(8.dp) + .clip(RoundedCornerShape(999.dp)) + .background(CloserPalette.PinkMist) + ) { + Box( + modifier = Modifier + .fillMaxHeight() + .fillMaxWidth(progress.coerceIn(0f, 1f)) + .clip(RoundedCornerShape(999.dp)) + .background(CloserPalette.PurpleDeep) + ) + } +} + +@Composable +private fun ChoicePromptBackdrop( + modifier: Modifier = Modifier +) { + Canvas(modifier = modifier) { + val minDimension = size.minDimension + val radius = minDimension * 0.16f + val left = Offset(size.width * 0.18f, size.height * 0.18f) + val right = Offset(size.width * 0.82f, size.height * 0.82f) + val strokeWidth = 3.dp.toPx() + + drawLine( + color = CloserPalette.PurpleMist.copy(alpha = 0.9f), + start = left, + end = right, + strokeWidth = strokeWidth + ) + drawCircle( + color = CloserPalette.PurpleGlow.copy(alpha = 0.58f), + radius = radius, + center = left + ) + drawCircle( + color = CloserPalette.PinkSoft.copy(alpha = 0.76f), + radius = radius * 0.94f, + center = right + ) + drawCircle( + color = CloserPalette.PurpleDeep.copy(alpha = 0.12f), + radius = radius * 0.56f, + center = left, + style = Stroke(width = strokeWidth) + ) + drawCircle( + color = CloserPalette.PinkAccentDeep.copy(alpha = 0.1f), + radius = radius * 0.54f, + center = right, + style = Stroke(width = strokeWidth) + ) + } +} + @Composable private fun OptionCard( text: String, @@ -310,6 +391,15 @@ private fun OptionCard( val isSelected = pendingSelection == optionId val isOtherSelected = pendingSelection != null && !isSelected + val cardScale by animateFloatAsState( + targetValue = when { + isSelected -> 1.02f + isOtherSelected -> 0.98f + else -> 1f + }, + animationSpec = tween(durationMillis = 180), + label = "scale_$optionId" + ) val background by animateColorAsState( targetValue = when { isSelected -> accentColor @@ -333,7 +423,8 @@ private fun OptionCard( onClick = { if (pendingSelection == null) onSelect(optionId) }, modifier = Modifier .fillMaxWidth() - .heightIn(min = 72.dp), + .heightIn(min = 72.dp) + .scale(cardScale), shape = RoundedCornerShape(18.dp), colors = CardDefaults.cardColors(containerColor = background), elevation = CardDefaults.cardElevation(defaultElevation = if (isSelected) 10.dp else 4.dp) @@ -370,6 +461,37 @@ private fun OptionCard( } } +@Composable +private fun VersusBadge( + modifier: Modifier = Modifier +) { + val infiniteTransition = rememberInfiniteTransition(label = "versus_badge") + val pulse by infiniteTransition.animateFloat( + initialValue = 0.96f, + targetValue = 1.04f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 1100), + repeatMode = RepeatMode.Reverse + ), + label = "versus_badge_pulse" + ) + + Surface( + modifier = modifier.scale(pulse), + shape = RoundedCornerShape(999.dp), + color = CloserPalette.PurpleMist, + shadowElevation = 4.dp + ) { + Text( + text = "OR", + modifier = Modifier.padding(horizontal = 18.dp, vertical = 8.dp), + style = MaterialTheme.typography.labelLarge, + color = CloserPalette.PurpleDeep, + fontWeight = FontWeight.Bold + ) + } +} + @Composable private fun ThisOrThatComplete( aCount: Int, @@ -389,7 +511,7 @@ private fun ThisOrThatComplete( ) { Spacer(Modifier.weight(1f)) - Text("✓", fontSize = 64.sp, color = CloserPalette.PurpleDeep) + ChoiceCompleteBadge() Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -424,7 +546,7 @@ private fun ThisOrThatComplete( verticalAlignment = Alignment.CenterVertically ) { TallyItem(label = "A", count = aCount, color = CloserPalette.PurpleDeep) - Divider( + VerticalDivider( modifier = Modifier .height(48.dp) .width(1.dp), @@ -459,6 +581,41 @@ private fun ThisOrThatComplete( } } +@Composable +private fun ChoiceCompleteBadge() { + val infiniteTransition = rememberInfiniteTransition(label = "choice_complete") + val pulse by infiniteTransition.animateFloat( + initialValue = 0.96f, + targetValue = 1.04f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 1300), + repeatMode = RepeatMode.Reverse + ), + label = "choice_complete_pulse" + ) + + Surface( + modifier = Modifier + .size(104.dp) + .scale(pulse), + shape = CircleShape, + color = CloserPalette.PinkMist, + shadowElevation = 10.dp + ) { + Box( + modifier = Modifier.background(CloserPalette.PurpleMist.copy(alpha = 0.42f)), + contentAlignment = Alignment.Center + ) { + Text( + text = "A/B", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.Bold), + color = CloserPalette.PurpleDeep, + textAlign = TextAlign.Center + ) + } + } +} + @Composable private fun TallyItem(label: String, count: Int, color: Color) { Column( diff --git a/app/src/main/java/app/closer/ui/wheel/SpinWheelScreen.kt b/app/src/main/java/app/closer/ui/wheel/SpinWheelScreen.kt index bc3d117b..fc01388e 100644 --- a/app/src/main/java/app/closer/ui/wheel/SpinWheelScreen.kt +++ b/app/src/main/java/app/closer/ui/wheel/SpinWheelScreen.kt @@ -3,9 +3,11 @@ package app.closer.ui.wheel import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -34,6 +36,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate +import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.drawscope.Stroke @@ -176,7 +179,11 @@ private fun SpinWheelContent( modifier = Modifier .fillMaxWidth() .heightIn(min = 56.dp), - shape = RoundedCornerShape(18.dp) + shape = RoundedCornerShape(18.dp), + border = BorderStroke(1.dp, CloserPalette.PurpleDeep.copy(alpha = 0.44f)), + colors = ButtonDefaults.outlinedButtonColors( + contentColor = CloserPalette.PurpleDeep + ) ) { Text("Spin again") } @@ -214,9 +221,31 @@ private fun WheelSpinner( rotation: Float ) { val segmentColors = closerWheelSegmentColors() + val idleTransition = rememberInfiniteTransition(label = "wheel_idle") + val idlePulse by idleTransition.animateFloat( + initialValue = 0.98f, + targetValue = 1.02f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 1400), + repeatMode = RepeatMode.Reverse + ), + label = "wheel_idle_pulse" + ) + val centerScale by animateFloatAsState( + targetValue = if (spunAndReady) 1.08f else 1f, + animationSpec = tween(durationMillis = 220), + label = "wheel_center_scale" + ) + val pointerScale by animateFloatAsState( + targetValue = if (isSpinning) 1.12f else 1f, + animationSpec = tween(durationMillis = 160), + label = "wheel_pointer_scale" + ) Box( - modifier = Modifier.size(270.dp), + modifier = Modifier + .size(270.dp) + .scale(if (isSpinning) 1f else idlePulse), contentAlignment = Alignment.Center ) { Canvas( @@ -247,6 +276,7 @@ private fun WheelSpinner( modifier = Modifier .align(Alignment.TopCenter) .size(width = 34.dp, height = 28.dp) + .scale(pointerScale) ) { val path = Path().apply { moveTo(size.width / 2f, size.height) @@ -258,9 +288,11 @@ private fun WheelSpinner( } Surface( - modifier = Modifier.size(94.dp), + modifier = Modifier + .size(94.dp) + .scale(centerScale), shape = CircleShape, - color = MaterialTheme.colorScheme.surface, + color = if (spunAndReady) CloserPalette.PinkMist else MaterialTheme.colorScheme.surface, shadowElevation = 10.dp ) { Box(contentAlignment = Alignment.Center) { diff --git a/seed/questions/couple_intimacy.json b/seed/questions/couple_intimacy.json new file mode 100644 index 00000000..d9f8ff8f --- /dev/null +++ b/seed/questions/couple_intimacy.json @@ -0,0 +1,3828 @@ +{ + "metadata": { + "title": "Adult Couple Intimacy Questions", + "version": "1.0", + "intended_use": "Consenting adult couples only.", + "total_questions": 300, + "tiers": { + "free": 60, + "premium": 240 + }, + "audiences": { + "female": 150, + "male": 150 + }, + "formats": [ + "yes_no", + "true_false", + "multiple_choice" + ], + "notes": [ + "Questions are original and grouped for couple intimacy, teasing, boundaries, consent, preferences, sexual health, and aftercare.", + "Female questions are written for a woman answering about a male partner.", + "Male questions are written for a man answering about a female partner." + ] + }, + "questions": [ + { + "id": "female_free_001", + "audience": "female", + "tier": "free", + "format": "yes_no", + "category": "initiation", + "question": "Do you want him to initiate sex more often?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_free_002", + "audience": "female", + "tier": "free", + "format": "true_false", + "category": "foreplay", + "question": "I get turned on faster when he touches me before trying to have sex.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_free_003", + "audience": "female", + "tier": "free", + "format": "multiple_choice", + "category": "touch", + "question": "Which first touch gets you most interested?", + "options": [ + "Neck kisses", + "Back rub", + "Hand on thigh", + "Direct sexual touch" + ] + }, + { + "id": "female_free_004", + "audience": "female", + "tier": "free", + "format": "yes_no", + "category": "desire", + "question": "Do you want more kissing before sex starts?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_free_005", + "audience": "female", + "tier": "free", + "format": "true_false", + "category": "communication", + "question": "I want him to ask what feels good instead of guessing.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_free_006", + "audience": "female", + "tier": "free", + "format": "multiple_choice", + "category": "mood", + "question": "What mood turns you on most?", + "options": [ + "Soft and romantic", + "Playful and teasing", + "Bold and dirty", + "Quiet and slow" + ] + }, + { + "id": "female_free_007", + "audience": "female", + "tier": "free", + "format": "yes_no", + "category": "teasing", + "question": "Do you like being teased before he touches you directly?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_free_008", + "audience": "female", + "tier": "free", + "format": "true_false", + "category": "confidence", + "question": "I like when he tells me exactly what he wants to do to me.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_free_009", + "audience": "female", + "tier": "free", + "format": "multiple_choice", + "category": "pace", + "question": "What pace do you usually prefer at the start?", + "options": [ + "Slow", + "Medium", + "Fast", + "Let it change" + ] + }, + { + "id": "female_free_010", + "audience": "female", + "tier": "free", + "format": "yes_no", + "category": "compliments", + "question": "Do compliments about your body turn you on?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_free_011", + "audience": "female", + "tier": "free", + "format": "true_false", + "category": "boundaries", + "question": "I want a clear pause button if anything feels wrong.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_free_012", + "audience": "female", + "tier": "free", + "format": "multiple_choice", + "category": "aftercare", + "question": "What do you want most after sex?", + "options": [ + "Cuddling", + "Talking", + "Space", + "Round two" + ] + }, + { + "id": "female_free_013", + "audience": "female", + "tier": "free", + "format": "yes_no", + "category": "spontaneity", + "question": "Do you like surprise sex attempts when you are already relaxed?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_free_014", + "audience": "female", + "tier": "free", + "format": "true_false", + "category": "privacy", + "question": "I need privacy and low stress to fully enjoy sex.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_free_015", + "audience": "female", + "tier": "free", + "format": "multiple_choice", + "category": "initiation", + "question": "How should he initiate more often?", + "options": [ + "Kiss me first", + "Say what he wants", + "Touch me slowly", + "Ask directly" + ] + }, + { + "id": "female_free_016", + "audience": "female", + "tier": "free", + "format": "yes_no", + "category": "dirty_talk", + "question": "Do you like dirty talk from him?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_free_017", + "audience": "female", + "tier": "free", + "format": "true_false", + "category": "emotional", + "question": "Feeling desired matters as much as the physical part.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_free_018", + "audience": "female", + "tier": "free", + "format": "multiple_choice", + "category": "turn_offs", + "question": "What kills the mood fastest?", + "options": [ + "Rushing", + "Bad timing", + "No affection", + "Poor hygiene" + ] + }, + { + "id": "female_free_019", + "audience": "female", + "tier": "free", + "format": "yes_no", + "category": "oral", + "question": "Do you want more oral sex from him?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_free_020", + "audience": "female", + "tier": "free", + "format": "true_false", + "category": "guidance", + "question": "I want to guide his hands or mouth without him getting offended.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_free_021", + "audience": "female", + "tier": "free", + "format": "multiple_choice", + "category": "foreplay", + "question": "Which foreplay do you want more of?", + "options": [ + "Kissing", + "Hands", + "Oral", + "Massage" + ] + }, + { + "id": "female_free_022", + "audience": "female", + "tier": "free", + "format": "yes_no", + "category": "eye_contact", + "question": "Does eye contact during sex turn you on?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_free_023", + "audience": "female", + "tier": "free", + "format": "true_false", + "category": "energy", + "question": "I like when he makes me feel chased and wanted.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_free_024", + "audience": "female", + "tier": "free", + "format": "multiple_choice", + "category": "clothing", + "question": "What is sexiest for him to wear?", + "options": [ + "Nothing", + "Boxers", + "Jeans", + "A nice shirt" + ] + }, + { + "id": "female_free_025", + "audience": "female", + "tier": "free", + "format": "yes_no", + "category": "fantasy", + "question": "Do you have a fantasy you want to tell him?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_free_026", + "audience": "female", + "tier": "free", + "format": "true_false", + "category": "consent", + "question": "A clear yes from me should matter more than his assumptions.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_free_027", + "audience": "female", + "tier": "free", + "format": "multiple_choice", + "category": "location", + "question": "Where would you most like him to start teasing you?", + "options": [ + "Couch", + "Bed", + "Shower", + "Kitchen" + ] + }, + { + "id": "female_free_028", + "audience": "female", + "tier": "free", + "format": "yes_no", + "category": "frequency", + "question": "Do you want sex more often than you currently have it?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_free_029", + "audience": "female", + "tier": "free", + "format": "true_false", + "category": "playfulness", + "question": "I like playful flirting before things get sexual.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_free_030", + "audience": "female", + "tier": "free", + "format": "multiple_choice", + "category": "free_choice", + "question": "What do you want him to improve first?", + "options": [ + "Foreplay", + "Confidence", + "Listening", + "Timing" + ] + }, + { + "id": "male_free_001", + "audience": "male", + "tier": "free", + "format": "yes_no", + "category": "initiation", + "question": "Do you want her to initiate sex more often?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_free_002", + "audience": "male", + "tier": "free", + "format": "true_false", + "category": "foreplay", + "question": "I get turned on faster when she shows clear desire.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_free_003", + "audience": "male", + "tier": "free", + "format": "multiple_choice", + "category": "touch", + "question": "Which first touch gets you most interested?", + "options": [ + "Kissing", + "Hand on chest", + "Hand on thigh", + "Direct sexual touch" + ] + }, + { + "id": "male_free_004", + "audience": "male", + "tier": "free", + "format": "yes_no", + "category": "desire", + "question": "Do you want more kissing before sex starts?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_free_005", + "audience": "male", + "tier": "free", + "format": "true_false", + "category": "communication", + "question": "I want her to tell me what feels good instead of making me guess.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_free_006", + "audience": "male", + "tier": "free", + "format": "multiple_choice", + "category": "mood", + "question": "What mood turns you on most?", + "options": [ + "Soft and romantic", + "Playful and teasing", + "Bold and dirty", + "Quiet and slow" + ] + }, + { + "id": "male_free_007", + "audience": "male", + "tier": "free", + "format": "yes_no", + "category": "teasing", + "question": "Do you like when she teases you before touching you directly?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_free_008", + "audience": "male", + "tier": "free", + "format": "true_false", + "category": "confidence", + "question": "I like when she tells me exactly what she wants from me.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_free_009", + "audience": "male", + "tier": "free", + "format": "multiple_choice", + "category": "pace", + "question": "What pace do you usually prefer at the start?", + "options": [ + "Slow", + "Medium", + "Fast", + "Let it change" + ] + }, + { + "id": "male_free_010", + "audience": "male", + "tier": "free", + "format": "yes_no", + "category": "compliments", + "question": "Do compliments about your body turn you on?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_free_011", + "audience": "male", + "tier": "free", + "format": "true_false", + "category": "boundaries", + "question": "I want a clear pause button if anything feels wrong.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_free_012", + "audience": "male", + "tier": "free", + "format": "multiple_choice", + "category": "aftercare", + "question": "What do you want most after sex?", + "options": [ + "Cuddling", + "Talking", + "Space", + "Round two" + ] + }, + { + "id": "male_free_013", + "audience": "male", + "tier": "free", + "format": "yes_no", + "category": "spontaneity", + "question": "Do you like surprise sex attempts when you are already relaxed?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_free_014", + "audience": "male", + "tier": "free", + "format": "true_false", + "category": "privacy", + "question": "I enjoy sex more when there is privacy and no distractions.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_free_015", + "audience": "male", + "tier": "free", + "format": "multiple_choice", + "category": "initiation", + "question": "How should she initiate more often?", + "options": [ + "Kiss me first", + "Say what she wants", + "Touch me slowly", + "Ask directly" + ] + }, + { + "id": "male_free_016", + "audience": "male", + "tier": "free", + "format": "yes_no", + "category": "dirty_talk", + "question": "Do you like dirty talk from her?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_free_017", + "audience": "male", + "tier": "free", + "format": "true_false", + "category": "emotional", + "question": "Feeling wanted matters as much as the physical part.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_free_018", + "audience": "male", + "tier": "free", + "format": "multiple_choice", + "category": "turn_offs", + "question": "What kills the mood fastest?", + "options": [ + "Rushing", + "Bad timing", + "No affection", + "Poor hygiene" + ] + }, + { + "id": "male_free_019", + "audience": "male", + "tier": "free", + "format": "yes_no", + "category": "oral", + "question": "Do you want more oral sex from her?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_free_020", + "audience": "male", + "tier": "free", + "format": "true_false", + "category": "guidance", + "question": "I want her to guide me without worrying I will take it personally.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_free_021", + "audience": "male", + "tier": "free", + "format": "multiple_choice", + "category": "foreplay", + "question": "Which foreplay do you want more of?", + "options": [ + "Kissing", + "Hands", + "Oral", + "Massage" + ] + }, + { + "id": "male_free_022", + "audience": "male", + "tier": "free", + "format": "yes_no", + "category": "eye_contact", + "question": "Does eye contact during sex turn you on?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_free_023", + "audience": "male", + "tier": "free", + "format": "true_false", + "category": "energy", + "question": "I like when she makes me feel wanted and chosen.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_free_024", + "audience": "male", + "tier": "free", + "format": "multiple_choice", + "category": "clothing", + "question": "What is sexiest for her to wear?", + "options": [ + "Nothing", + "Lingerie", + "My shirt", + "A dress" + ] + }, + { + "id": "male_free_025", + "audience": "male", + "tier": "free", + "format": "yes_no", + "category": "fantasy", + "question": "Do you have a fantasy you want to tell her?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_free_026", + "audience": "male", + "tier": "free", + "format": "true_false", + "category": "consent", + "question": "A clear yes from her should matter more than my assumptions.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_free_027", + "audience": "male", + "tier": "free", + "format": "multiple_choice", + "category": "location", + "question": "Where would you most like her to start teasing you?", + "options": [ + "Couch", + "Bed", + "Shower", + "Kitchen" + ] + }, + { + "id": "male_free_028", + "audience": "male", + "tier": "free", + "format": "yes_no", + "category": "frequency", + "question": "Do you want sex more often than you currently have it?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_free_029", + "audience": "male", + "tier": "free", + "format": "true_false", + "category": "playfulness", + "question": "I like playful flirting before things get sexual.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_free_030", + "audience": "male", + "tier": "free", + "format": "multiple_choice", + "category": "free_choice", + "question": "What do you want her to improve first?", + "options": [ + "Initiation", + "Confidence", + "Guidance", + "Timing" + ] + }, + { + "id": "female_premium_001", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "oral", + "question": "Do you want him to go down on you more often?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_002", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "oral", + "question": "I want him to focus on my clitoris more directly.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_003", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "oral", + "question": "Which oral style do you prefer most?", + "options": [ + "Slow and gentle", + "Firm and focused", + "Teasing with pauses", + "I do not want oral" + ] + }, + { + "id": "female_premium_004", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "manual_touch", + "question": "Do you like him using his fingers inside you during foreplay?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_005", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "manual_touch", + "question": "I want him to ask before changing speed or pressure.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_006", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "manual_touch", + "question": "What hand technique do you prefer?", + "options": [ + "Outside only", + "Inside only", + "Both together", + "No hand play" + ] + }, + { + "id": "female_premium_007", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "penetration", + "question": "Do you enjoy deep penetration?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_008", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "penetration", + "question": "I like when he starts shallow before going deeper.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_009", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "penetration", + "question": "Which penetration pace turns you on most?", + "options": [ + "Slow and deep", + "Fast and hard", + "Rhythm changes", + "I prefer no penetration" + ] + }, + { + "id": "female_premium_010", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "positions", + "question": "Do you want to be on top more often?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_011", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "positions", + "question": "I enjoy positions where I control the speed.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_012", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "positions", + "question": "Which position do you want more of?", + "options": [ + "On top", + "Missionary", + "From behind", + "Side by side" + ] + }, + { + "id": "female_premium_013", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "dominance", + "question": "Do you want him to be more dominant in bed?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_014", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "dominance", + "question": "I want dominance only when I clearly agree to it first.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_015", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "power", + "question": "Which power dynamic interests you most?", + "options": [ + "He leads", + "I lead", + "We switch", + "No power play" + ] + }, + { + "id": "female_premium_016", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "submission", + "question": "Do you like being told what to do sexually?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_017", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "submission", + "question": "I want commands to stop immediately if I say stop.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_018", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "dirty_talk", + "question": "What dirty talk style do you prefer?", + "options": [ + "Romantic", + "Filthy", + "Commanding", + "None" + ] + }, + { + "id": "female_premium_019", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "praise", + "question": "Do you like being praised during sex?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_020", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "praise", + "question": "I get turned on when he says I am doing a good job.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_021", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "praise", + "question": "What praise sounds best?", + "options": [ + "You are so sexy", + "You feel amazing", + "I want you badly", + "Keep doing that" + ] + }, + { + "id": "female_premium_022", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "teasing", + "question": "Do you like him making you wait before giving you what you want?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_023", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "teasing", + "question": "Teasing is hotter when he still checks that I am enjoying it.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_024", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "teasing", + "question": "What teasing do you like most?", + "options": [ + "Slow undressing", + "Almost touching", + "Dirty whispers", + "Kissing everywhere first" + ] + }, + { + "id": "female_premium_025", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "orgasm", + "question": "Do you want him to care more about your orgasm?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_026", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "orgasm", + "question": "I would rather he ask what helps me finish than pretend he knows.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_027", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "orgasm", + "question": "What helps you orgasm most often?", + "options": [ + "Clitoral touch", + "Oral sex", + "Penetration", + "Mental build up" + ] + }, + { + "id": "female_premium_028", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "toys", + "question": "Do you want to use sex toys with him?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_029", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "toys", + "question": "Toys feel like teamwork, not competition.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_030", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "toys", + "question": "Which toy idea interests you most?", + "options": [ + "Vibrator", + "Couples toy", + "Restraints", + "No toys" + ] + }, + { + "id": "female_premium_031", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "restraints", + "question": "Do you want to try light restraints with clear consent?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_032", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "restraints", + "question": "I need a safe word before any restraint play.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_033", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "restraints", + "question": "What restraint level feels acceptable?", + "options": [ + "Hands only", + "Soft cuffs", + "Blindfold only", + "None" + ] + }, + { + "id": "female_premium_034", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "spanking", + "question": "Do you enjoy light spanking?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_035", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "impact", + "question": "I want impact play to stay light unless I clearly ask for more.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_036", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "impact", + "question": "What level of impact sounds best?", + "options": [ + "None", + "Playful taps", + "Light spanking", + "Firm spanking" + ] + }, + { + "id": "female_premium_037", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "roleplay", + "question": "Do you want to try roleplay with him?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_038", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "roleplay", + "question": "Roleplay works better when we agree on limits first.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_039", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "roleplay", + "question": "Which roleplay vibe interests you most?", + "options": [ + "Strangers flirting", + "Bossy partner", + "Romantic fantasy", + "No roleplay" + ] + }, + { + "id": "female_premium_040", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "lingerie", + "question": "Do you like wearing lingerie to tease him?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_041", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "confidence", + "question": "I want him to make me feel sexy before expecting me to perform.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_042", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "clothing", + "question": "What clothing tease works best for you?", + "options": [ + "Lingerie", + "His shirt", + "Nothing under clothes", + "No clothing tease" + ] + }, + { + "id": "female_premium_043", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "sexting", + "question": "Do you like receiving dirty texts from him?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_044", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "sexting", + "question": "I want dirty texts only when I am in the mood and available.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_045", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "sexting", + "question": "What sexting style do you like?", + "options": [ + "Sweet and suggestive", + "Graphic and direct", + "Commanding", + "No sexting" + ] + }, + { + "id": "female_premium_046", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "photos", + "question": "Do you like sending sexy photos if trust is strong?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_047", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "photos", + "question": "Sexy photos require clear privacy rules first.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_048", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "photos", + "question": "What photo boundary matters most?", + "options": [ + "No face", + "No saving", + "No sharing ever", + "No photos" + ] + }, + { + "id": "female_premium_049", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "public_tease", + "question": "Do you like discreet teasing in public?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_050", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "public_tease", + "question": "Public teasing should stay private enough that no one else is involved.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_051", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "public_tease", + "question": "What public tease is acceptable?", + "options": [ + "A dirty text", + "A hand on my thigh", + "A whispered comment", + "None" + ] + }, + { + "id": "female_premium_052", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "shower", + "question": "Do you like shower sex or shower foreplay?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_053", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "location", + "question": "New locations turn me on when they still feel safe and private.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_054", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "location", + "question": "Which private location sounds hottest?", + "options": [ + "Shower", + "Couch", + "Kitchen counter", + "Car parked privately" + ] + }, + { + "id": "female_premium_055", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "fantasy", + "question": "Do you want him to ask about your fantasies directly?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_056", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "fantasy", + "question": "I have at least one fantasy I have not fully explained to him.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_057", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "fantasy", + "question": "Which fantasy category interests you most?", + "options": [ + "Power play", + "Voyeur tease", + "Romantic scenario", + "Trying something new" + ] + }, + { + "id": "female_premium_058", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "watching", + "question": "Do you like him watching you touch yourself?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_059", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "watching", + "question": "Being watched turns me on only when I feel confident and safe.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_060", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "watching", + "question": "What watching dynamic sounds best?", + "options": [ + "He watches only", + "We touch ourselves together", + "He tells me what to do", + "No watching" + ] + }, + { + "id": "female_premium_061", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "mutual_masturbation", + "question": "Do you want to try mutual masturbation?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_062", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "learning", + "question": "Watching each other can teach what each person actually likes.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_063", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "learning", + "question": "What would help him learn your body?", + "options": [ + "Show him", + "Guide his hand", + "Tell him after", + "Use a yes no maybe list" + ] + }, + { + "id": "female_premium_064", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "anal", + "question": "Do you want to discuss anal play as a yes, no, or maybe?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_065", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "anal", + "question": "Anal play is off limits unless I clearly choose it.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_066", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "anal", + "question": "Where are you on anal play?", + "options": [ + "Yes", + "Maybe with preparation", + "Curious but not now", + "No" + ] + }, + { + "id": "female_premium_067", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "cum", + "question": "Do you want to talk openly about where he finishes?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_068", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "cum", + "question": "Where he finishes should be discussed before the moment.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_069", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "cum", + "question": "What finish boundary fits you best?", + "options": [ + "Ask every time", + "Pre-agreed choice", + "Condom only", + "Do not care" + ] + }, + { + "id": "female_premium_070", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "protection", + "question": "Do you want stricter rules about condoms or birth control?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_071", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "sexual_health", + "question": "I need STI testing talks to be normal, not awkward.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_072", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "protection", + "question": "What protection rule feels best?", + "options": [ + "Condoms always", + "Condoms sometimes", + "Testing first", + "Discuss each time" + ] + }, + { + "id": "female_premium_073", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "period", + "question": "Are you comfortable talking about period sex directly?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_074", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "period", + "question": "Period sex is a practical preference topic, not a shame topic.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_075", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "period", + "question": "What is your period sex preference?", + "options": [ + "Yes", + "Maybe with towels", + "Only nonpenetrative", + "No" + ] + }, + { + "id": "female_premium_076", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "pain", + "question": "Do you want him to stop immediately if sex hurts, even a little?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_077", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "pain", + "question": "Pain should be treated as information, not as something to push through.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_078", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "comfort", + "question": "What should he do if you seem uncomfortable?", + "options": [ + "Stop and ask", + "Slow down", + "Switch activity", + "Give space" + ] + }, + { + "id": "female_premium_079", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "aftercare", + "question": "Do you want more cuddling after sex?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_080", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "aftercare", + "question": "Aftercare affects whether I want sex again later.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_081", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "aftercare", + "question": "Which aftercare feels best?", + "options": [ + "Hold me", + "Talk to me", + "Bring water", + "Let me rest" + ] + }, + { + "id": "female_premium_082", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "feedback", + "question": "Do you want a post sex check in sometimes?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_083", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "feedback", + "question": "Talking after sex can make the next time better.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_084", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "feedback", + "question": "When should feedback happen?", + "options": [ + "Right after", + "Later that day", + "Next day", + "Only when needed" + ] + }, + { + "id": "female_premium_085", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "kissing", + "question": "Do you want more kissing during sex itself?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_086", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "kissing", + "question": "Kissing keeps me emotionally connected during sex.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_087", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "kissing", + "question": "Where do you want more kisses?", + "options": [ + "Mouth", + "Neck", + "Breasts", + "Inner thighs" + ] + }, + { + "id": "female_premium_088", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "breasts", + "question": "Do you like breast or nipple play?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_089", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "breasts", + "question": "I want him to ask how sensitive my breasts or nipples are that day.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_090", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "breasts", + "question": "What breast play do you prefer?", + "options": [ + "Soft touch", + "Mouth", + "Firm touch", + "None" + ] + }, + { + "id": "female_premium_091", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "clit", + "question": "Do you want him to spend more time on clitoral stimulation?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_092", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "clit", + "question": "Direct clitoral touch can be too much unless the pressure is right.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_093", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "clit", + "question": "What clitoral pressure usually works best?", + "options": [ + "Barely there", + "Light", + "Firm", + "Indirect only" + ] + }, + { + "id": "female_premium_094", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "roughness", + "question": "Do you enjoy rougher sex sometimes?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_095", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "roughness", + "question": "Rough sex needs clearer consent than gentle sex.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_096", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "roughness", + "question": "What rough element interests you most?", + "options": [ + "Hair pulling", + "Firm grip", + "Harder thrusting", + "None" + ] + }, + { + "id": "female_premium_097", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "slow_sex", + "question": "Do you want slower sex more often?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_098", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "slow_sex", + "question": "Slow sex can feel more intense than fast sex.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_099", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "slow_sex", + "question": "What slow sex focus sounds best?", + "options": [ + "Deep kissing", + "Grinding", + "Slow penetration", + "Full body touch" + ] + }, + { + "id": "female_premium_100", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "quickies", + "question": "Do you enjoy quickies?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_101", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "quickies", + "question": "Quickies are better when they do not replace longer sex every time.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_102", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "quickies", + "question": "When would a quickie work best?", + "options": [ + "Morning", + "Before going out", + "After work", + "Rarely" + ] + }, + { + "id": "female_premium_103", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "body_confidence", + "question": "Do you want him to compliment specific body parts more?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_104", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "body_confidence", + "question": "I enjoy sex more when I feel wanted exactly as I am.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_105", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "body_confidence", + "question": "What compliment do you want most?", + "options": [ + "My body", + "My face", + "My voice", + "My sexual confidence" + ] + }, + { + "id": "female_premium_106", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "initiation_style", + "question": "Do you want him to be more direct when he wants sex?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_107", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "initiation_style", + "question": "Vague hints are less sexy than clear desire.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_108", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "initiation_style", + "question": "What direct line would work best?", + "options": [ + "I want you tonight", + "Come kiss me", + "I need you close", + "Tell me what you want" + ] + }, + { + "id": "female_premium_109", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "rejection", + "question": "Do you want him to handle rejection without sulking?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_110", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "rejection", + "question": "I am more likely to say yes later if no is respected now.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_111", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "rejection", + "question": "What should happen after a no?", + "options": [ + "Cuddle anyway", + "Give space", + "Try later", + "Do something nonsexual" + ] + }, + { + "id": "female_premium_112", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "rules", + "question": "Do you want a written yes no maybe list together?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_113", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "rules", + "question": "Clear rules can make sex feel freer, not colder.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_114", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "rules", + "question": "What list section matters most?", + "options": [ + "Hard no", + "Maybe", + "Want more", + "Need to discuss" + ] + }, + { + "id": "female_premium_115", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "trust", + "question": "Do you trust him with your sexual boundaries?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_116", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "trust", + "question": "Trust is sexy because it lets me relax.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_117", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "trust", + "question": "What builds the most sexual trust?", + "options": [ + "Listening", + "Patience", + "Privacy", + "Keeping promises" + ] + }, + { + "id": "female_premium_118", + "audience": "female", + "tier": "premium", + "format": "yes_no", + "category": "final_blunt", + "question": "Do you want him to ask you exactly what you want tonight?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "female_premium_119", + "audience": "female", + "tier": "premium", + "format": "true_false", + "category": "final_blunt", + "question": "I would rather be asked bluntly than have him guess badly.", + "options": [ + "True", + "False" + ] + }, + { + "id": "female_premium_120", + "audience": "female", + "tier": "premium", + "format": "multiple_choice", + "category": "final_blunt", + "question": "What question should he ask first?", + "options": [ + "What do you want tonight?", + "What is off limits?", + "How rough do you want it?", + "Do you want me to lead?" + ] + }, + { + "id": "male_premium_001", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "oral", + "question": "Do you want her to go down on you more often?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_002", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "oral", + "question": "I want oral sex to feel wanted, not like a chore.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_003", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "oral", + "question": "Which oral style do you prefer most?", + "options": [ + "Slow and teasing", + "Firm and focused", + "Eye contact", + "I do not want oral" + ] + }, + { + "id": "male_premium_004", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "manual_touch", + "question": "Do you like her stroking your penis during foreplay?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_005", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "manual_touch", + "question": "I want her to ask what pressure feels best.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_006", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "manual_touch", + "question": "What hand technique do you prefer?", + "options": [ + "Slow strokes", + "Firm strokes", + "Teasing pauses", + "No hand play" + ] + }, + { + "id": "male_premium_007", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "balls", + "question": "Do you enjoy having your balls touched during sex?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_008", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "balls", + "question": "I want ball play to be gentle unless I clearly say otherwise.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_009", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "balls", + "question": "What ball touch do you prefer?", + "options": [ + "None", + "Light touch", + "Mouth", + "Ask each time" + ] + }, + { + "id": "male_premium_010", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "penetration", + "question": "Do you enjoy deep penetration?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_011", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "penetration", + "question": "I like when she helps set the rhythm.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_012", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "penetration", + "question": "Which penetration pace turns you on most?", + "options": [ + "Slow and deep", + "Fast and hard", + "Rhythm changes", + "Depends on her" + ] + }, + { + "id": "male_premium_013", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "positions", + "question": "Do you want her on top more often?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_014", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "positions", + "question": "I enjoy positions where I can see her face.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_015", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "positions", + "question": "Which position do you want more of?", + "options": [ + "Her on top", + "Missionary", + "From behind", + "Side by side" + ] + }, + { + "id": "male_premium_016", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "dominance", + "question": "Do you want her to be more dominant in bed?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_017", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "dominance", + "question": "I like dominance only when both of us clearly agree to it.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_018", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "power", + "question": "Which power dynamic interests you most?", + "options": [ + "She leads", + "I lead", + "We switch", + "No power play" + ] + }, + { + "id": "male_premium_019", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "submission", + "question": "Do you like being told what to do sexually?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_020", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "submission", + "question": "I can enjoy taking orders without it meaning I am weak.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_021", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "dirty_talk", + "question": "What dirty talk style do you prefer?", + "options": [ + "Romantic", + "Filthy", + "Commanding", + "None" + ] + }, + { + "id": "male_premium_022", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "praise", + "question": "Do you like being praised during sex?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_023", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "praise", + "question": "I get turned on when she tells me I make her feel good.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_024", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "praise", + "question": "What praise sounds best?", + "options": [ + "You feel amazing", + "I want you", + "Do not stop", + "You are so sexy" + ] + }, + { + "id": "male_premium_025", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "teasing", + "question": "Do you like her making you wait before touching you directly?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_026", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "teasing", + "question": "Teasing is hotter when she acts confident.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_027", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "teasing", + "question": "What teasing do you like most?", + "options": [ + "Slow undressing", + "Almost touching", + "Dirty whispers", + "Kissing everywhere first" + ] + }, + { + "id": "male_premium_028", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "orgasm", + "question": "Do you want her to talk openly about your orgasm timing?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_029", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "orgasm", + "question": "I would rather talk about finishing too fast or too slow than pretend it never happens.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_030", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "orgasm", + "question": "What helps you control orgasm timing?", + "options": [ + "Slowing down", + "Changing position", + "Taking breaks", + "I do not need control" + ] + }, + { + "id": "male_premium_031", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "toys", + "question": "Do you want to use sex toys with her?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_032", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "toys", + "question": "Toys feel like teamwork, not competition.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_033", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "toys", + "question": "Which toy idea interests you most?", + "options": [ + "Vibrator for her", + "Couples toy", + "Restraints", + "No toys" + ] + }, + { + "id": "male_premium_034", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "restraints", + "question": "Do you want to try light restraints with clear consent?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_035", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "restraints", + "question": "I need a safe word before any restraint play.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_036", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "restraints", + "question": "What restraint level feels acceptable?", + "options": [ + "Hands only", + "Soft cuffs", + "Blindfold only", + "None" + ] + }, + { + "id": "male_premium_037", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "spanking", + "question": "Do you enjoy light spanking as giver or receiver?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_038", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "impact", + "question": "I want impact play to stay light unless we both clearly agree.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_039", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "impact", + "question": "What level of impact sounds best?", + "options": [ + "None", + "Playful taps", + "Light spanking", + "Firm spanking" + ] + }, + { + "id": "male_premium_040", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "roleplay", + "question": "Do you want to try roleplay with her?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_041", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "roleplay", + "question": "Roleplay works better when we agree on limits first.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_042", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "roleplay", + "question": "Which roleplay vibe interests you most?", + "options": [ + "Strangers flirting", + "Bossy partner", + "Romantic fantasy", + "No roleplay" + ] + }, + { + "id": "male_premium_043", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "lingerie", + "question": "Do you like when she wears lingerie to tease you?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_044", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "confidence", + "question": "Her confidence turns me on more than any specific outfit.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_045", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "clothing", + "question": "What clothing tease works best for you?", + "options": [ + "Lingerie", + "My shirt", + "Nothing under clothes", + "No clothing tease" + ] + }, + { + "id": "male_premium_046", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "sexting", + "question": "Do you like receiving dirty texts from her?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_047", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "sexting", + "question": "I want dirty texts only when privacy is safe.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_048", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "sexting", + "question": "What sexting style do you like?", + "options": [ + "Sweet and suggestive", + "Graphic and direct", + "Commanding", + "No sexting" + ] + }, + { + "id": "male_premium_049", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "photos", + "question": "Do you like receiving sexy photos if trust is strong?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_050", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "photos", + "question": "Sexy photos require clear privacy rules first.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_051", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "photos", + "question": "What photo boundary matters most?", + "options": [ + "No face", + "No saving", + "No sharing ever", + "No photos" + ] + }, + { + "id": "male_premium_052", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "public_tease", + "question": "Do you like discreet teasing in public?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_053", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "public_tease", + "question": "Public teasing should stay private enough that no one else is involved.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_054", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "public_tease", + "question": "What public tease is acceptable?", + "options": [ + "A dirty text", + "A hand on my thigh", + "A whispered comment", + "None" + ] + }, + { + "id": "male_premium_055", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "shower", + "question": "Do you like shower sex or shower foreplay?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_056", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "location", + "question": "New locations turn me on when they still feel safe and private.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_057", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "location", + "question": "Which private location sounds hottest?", + "options": [ + "Shower", + "Couch", + "Kitchen counter", + "Car parked privately" + ] + }, + { + "id": "male_premium_058", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "fantasy", + "question": "Do you want her to ask about your fantasies directly?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_059", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "fantasy", + "question": "I have at least one fantasy I have not fully explained to her.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_060", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "fantasy", + "question": "Which fantasy category interests you most?", + "options": [ + "Power play", + "Voyeur tease", + "Romantic scenario", + "Trying something new" + ] + }, + { + "id": "male_premium_061", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "watching", + "question": "Do you like watching her touch herself?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_062", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "watching", + "question": "Watching her pleasure herself can be hotter than rushing into sex.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_063", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "watching", + "question": "What watching dynamic sounds best?", + "options": [ + "I watch only", + "We touch ourselves together", + "She tells me what to do", + "No watching" + ] + }, + { + "id": "male_premium_064", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "mutual_masturbation", + "question": "Do you want to try mutual masturbation?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_065", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "learning", + "question": "Watching each other can teach what each person actually likes.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_066", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "learning", + "question": "What would help you learn her body?", + "options": [ + "She shows me", + "She guides my hand", + "She tells me after", + "Use a yes no maybe list" + ] + }, + { + "id": "male_premium_067", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "anal", + "question": "Do you want to discuss anal play as a yes, no, or maybe?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_068", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "anal", + "question": "Anal play is off limits unless she clearly chooses it.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_069", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "anal", + "question": "Where are you on anal play?", + "options": [ + "Yes", + "Maybe with preparation", + "Curious but not now", + "No" + ] + }, + { + "id": "male_premium_070", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "cum", + "question": "Do you want to talk openly about where you finish?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_071", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "cum", + "question": "Where I finish should be discussed before the moment.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_072", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "cum", + "question": "What finish boundary fits you best?", + "options": [ + "Ask every time", + "Pre-agreed choice", + "Condom only", + "Do not care" + ] + }, + { + "id": "male_premium_073", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "protection", + "question": "Do you want clearer rules about condoms or birth control?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_074", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "sexual_health", + "question": "I need STI testing talks to be normal, not awkward.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_075", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "protection", + "question": "What protection rule feels best?", + "options": [ + "Condoms always", + "Condoms sometimes", + "Testing first", + "Discuss each time" + ] + }, + { + "id": "male_premium_076", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "period", + "question": "Are you comfortable talking about period sex directly?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_077", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "period", + "question": "Period sex is a practical preference topic, not a shame topic.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_078", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "period", + "question": "What is your period sex preference?", + "options": [ + "Yes", + "Maybe with towels", + "Only nonpenetrative", + "No" + ] + }, + { + "id": "male_premium_079", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "pain", + "question": "Do you stop immediately if she says sex hurts, even a little?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_080", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "pain", + "question": "Pain should be treated as information, not as something to push through.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_081", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "comfort", + "question": "What should you do if she seems uncomfortable?", + "options": [ + "Stop and ask", + "Slow down", + "Switch activity", + "Give space" + ] + }, + { + "id": "male_premium_082", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "aftercare", + "question": "Do you want more cuddling after sex?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_083", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "aftercare", + "question": "Aftercare affects whether both of us want sex again later.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_084", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "aftercare", + "question": "Which aftercare feels best?", + "options": [ + "Hold each other", + "Talk", + "Bring water", + "Rest separately" + ] + }, + { + "id": "male_premium_085", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "feedback", + "question": "Do you want a post sex check in sometimes?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_086", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "feedback", + "question": "Talking after sex can make the next time better.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_087", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "feedback", + "question": "When should feedback happen?", + "options": [ + "Right after", + "Later that day", + "Next day", + "Only when needed" + ] + }, + { + "id": "male_premium_088", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "kissing", + "question": "Do you want more kissing during sex itself?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_089", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "kissing", + "question": "Kissing keeps sex from feeling mechanical.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_090", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "kissing", + "question": "Where do you want to kiss her more?", + "options": [ + "Mouth", + "Neck", + "Breasts", + "Inner thighs" + ] + }, + { + "id": "male_premium_091", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "breasts", + "question": "Do you like breast or nipple play with her?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_092", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "breasts", + "question": "I should ask how sensitive her breasts or nipples are that day.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_093", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "breasts", + "question": "What breast play interests you most?", + "options": [ + "Soft touch", + "Mouth", + "Firm touch", + "None" + ] + }, + { + "id": "male_premium_094", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "clit", + "question": "Do you want to get better at clitoral stimulation?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_095", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "clit", + "question": "Direct clitoral touch can be too much unless the pressure is right.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_096", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "clit", + "question": "What is the smartest way to improve?", + "options": [ + "Ask her", + "Watch her show me", + "Go slower", + "Use a toy together" + ] + }, + { + "id": "male_premium_097", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "roughness", + "question": "Do you enjoy rougher sex sometimes?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_098", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "roughness", + "question": "Rough sex needs clearer consent than gentle sex.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_099", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "roughness", + "question": "What rough element interests you most?", + "options": [ + "Hair pulling", + "Firm grip", + "Harder thrusting", + "None" + ] + }, + { + "id": "male_premium_100", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "slow_sex", + "question": "Do you want slower sex more often?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_101", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "slow_sex", + "question": "Slow sex can feel more intense than fast sex.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_102", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "slow_sex", + "question": "What slow sex focus sounds best?", + "options": [ + "Deep kissing", + "Grinding", + "Slow penetration", + "Full body touch" + ] + }, + { + "id": "male_premium_103", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "quickies", + "question": "Do you enjoy quickies?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_104", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "quickies", + "question": "Quickies are better when they do not replace longer sex every time.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_105", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "quickies", + "question": "When would a quickie work best?", + "options": [ + "Morning", + "Before going out", + "After work", + "Rarely" + ] + }, + { + "id": "male_premium_106", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "body_confidence", + "question": "Do you want her to compliment your body more?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_107", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "body_confidence", + "question": "I enjoy sex more when I feel wanted exactly as I am.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_108", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "body_confidence", + "question": "What compliment do you want most?", + "options": [ + "My body", + "My face", + "My voice", + "My sexual confidence" + ] + }, + { + "id": "male_premium_109", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "initiation_style", + "question": "Do you want her to be more direct when she wants sex?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_110", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "initiation_style", + "question": "Vague hints are less sexy than clear desire.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_111", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "initiation_style", + "question": "What direct line would work best?", + "options": [ + "I want you tonight", + "Come kiss me", + "I need you close", + "Tell me what you want" + ] + }, + { + "id": "male_premium_112", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "rejection", + "question": "Can you handle rejection without sulking?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_113", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "rejection", + "question": "She is more likely to say yes later if no is respected now.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_114", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "rejection", + "question": "What should happen after a no?", + "options": [ + "Cuddle anyway", + "Give space", + "Try later", + "Do something nonsexual" + ] + }, + { + "id": "male_premium_115", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "rules", + "question": "Do you want a written yes no maybe list together?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_116", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "rules", + "question": "Clear rules can make sex feel freer, not colder.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_117", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "rules", + "question": "What list section matters most?", + "options": [ + "Hard no", + "Maybe", + "Want more", + "Need to discuss" + ] + }, + { + "id": "male_premium_118", + "audience": "male", + "tier": "premium", + "format": "yes_no", + "category": "final_blunt", + "question": "Do you want her to ask you exactly what you want tonight?", + "options": [ + "Yes", + "No" + ] + }, + { + "id": "male_premium_119", + "audience": "male", + "tier": "premium", + "format": "true_false", + "category": "final_blunt", + "question": "I would rather be asked bluntly than have her guess badly.", + "options": [ + "True", + "False" + ] + }, + { + "id": "male_premium_120", + "audience": "male", + "tier": "premium", + "format": "multiple_choice", + "category": "final_blunt", + "question": "What question should she ask first?", + "options": [ + "What do you want tonight?", + "What is off limits?", + "How rough do you want it?", + "Do you want me to lead?" + ] + } + ] +}