diff --git a/src/screens/cover/SongDownload.js b/src/screens/cover/SongDownload.js
index 6133c4e..21f9c7c 100644
--- a/src/screens/cover/SongDownload.js
+++ b/src/screens/cover/SongDownload.js
@@ -32,6 +32,9 @@ import { isWeb } from '../../hooks/useLayoutType'
import { getArtistDisplayName } from '../../utils/artistName'
import { toDate } from '../../utils/dateFormatting'
import SubscriptionConfirmModal from '../../components/SubscriptionConfirmModal'
+import FullscreenIntroVideo from '../../components/FullscreenIntroVideo'
+import AdventureChoiceModal from './components/AdventureChoiceModal'
+import ShareAndCreditsModal from './components/ShareAndCreditsModal'
const SongDownload = ({ route }) => {
const {
@@ -45,6 +48,11 @@ const SongDownload = ({ route }) => {
const [isDownloading, setIsDownloading] = useState(false)
const [showConfirmModal, setShowConfirmModal] = useState(false)
const [hasAcceptedPublication, setHasAcceptedPublication] = useState(true)
+ const { videos } = useUser()
+ const part1Url = isWeb ? videos?.benaiPart1 : videos?.benaiPart1
+ const [showIntro, setShowIntro] = useState(null)
+ const [showAdventureModal, setShowAdventureModal] = useState(false)
+ const [showShareModal, setShowShareModal] = useState(false)
const projectForStage = useMemo(
() => routeProject || selectedProject || null,
@@ -145,11 +153,30 @@ const SongDownload = ({ route }) => {
return
}
if (hasActiveSubscription) {
- continueFlow()
+ if (part1Url) {
+ setShowIntro(part1Url)
+ } else {
+ continueFlow()
+ }
return
}
setShowConfirmModal(true)
- }, [continueFlow, hasAcceptedPublication, hasActiveSubscription, setTooltip])
+ }, [continueFlow, hasAcceptedPublication, hasActiveSubscription, part1Url, setTooltip])
+
+ const handleCloseIntro = useCallback(() => {
+ setShowIntro(null)
+ setShowAdventureModal(true)
+ }, [])
+
+ const handleContinueAdventure = useCallback(() => {
+ setShowAdventureModal(false)
+ continueFlow()
+ }, [continueFlow])
+
+ const handleStopAdventure = useCallback(() => {
+ setShowAdventureModal(false)
+ setShowShareModal(true)
+ }, [])
const handleDownload = useCallback(async () => {
const downloadUrl =
@@ -410,7 +437,30 @@ const SongDownload = ({ route }) => {
isVisible={showConfirmModal}
setIsVisible={setShowConfirmModal}
onJoinClub={() => navigate(Routes.Payments)}
- onContinue={continueFlow}
+ onContinue={() => {
+ if (part1Url) {
+ setShowIntro(part1Url)
+ } else {
+ continueFlow()
+ }
+ }}
+ />
+
+
+ {
+ setShowShareModal(false)
+ navigate(Routes.DownloadPrices, { action: 'song' })
+ }}
+ project={projectForStage}
+ selectedOption={selectedOption}
/>
)
diff --git a/src/screens/cover/ValidateCover.js b/src/screens/cover/ValidateCover.js
index a62269a..75fde56 100644
--- a/src/screens/cover/ValidateCover.js
+++ b/src/screens/cover/ValidateCover.js
@@ -13,7 +13,6 @@ import { goBack, navigate } from '../../navigation/NavigationService'
import { useUser, useUserData } from '../../providers/UserDataProvider'
import { gutters, Palette, Style } from '../../styles'
import CreateLyricsHeader from '../Writing/components/CreateLyricsHeader'
-
const ValidateCover = () => {
const { selectedProject, updateProjectData } = useUserData()
const { videos } = useUser()
@@ -102,7 +101,7 @@ const ValidateCover = () => {
selectedOption,
coverOptions,
})
- }, [selectedProject, selectedOption, coverOptions])
+ }, [coverOptions, selectedOption, selectedProject])
return (
diff --git a/src/screens/cover/components/AdventureChoiceModal.js b/src/screens/cover/components/AdventureChoiceModal.js
new file mode 100644
index 0000000..9b59da6
--- /dev/null
+++ b/src/screens/cover/components/AdventureChoiceModal.js
@@ -0,0 +1,59 @@
+import React from 'react'
+import { StyleSheet, Text, View } from 'react-native'
+import { gutters, Palette } from '../../../styles'
+import { FONT_FAMILY } from '../../../styles/Fonts'
+import BorderGradientButton from '../../../components/BorderGradientButton'
+import GradientButton from '../../../components/GradientButton'
+import Overlay from '../../../components/Overlay'
+
+const AdventureChoiceModal = ({ isVisible, setIsVisible, onContinue, onStop }) => {
+ return (
+
+
+ Quelle est la suite ?
+
+
+
+
+
+
+ )
+}
+
+export default AdventureChoiceModal
+
+const styles = StyleSheet.create({
+ modalCard: {
+ width: '90%',
+ maxWidth: 420,
+ alignSelf: 'center',
+ padding: gutters * 1.4,
+ borderRadius: 20,
+ backgroundColor: 'rgba(37, 36, 56, 0.96)',
+ borderWidth: 1,
+ borderColor: 'rgba(255, 255, 255, 0.12)',
+ gap: gutters * 0.8,
+ },
+ modalTitle: {
+ fontFamily: FONT_FAMILY.InterSemiBold,
+ fontSize: 18,
+ color: Palette.white,
+ textAlign: 'center',
+ marginBottom: 8,
+ },
+ modalActions: {
+ gap: gutters * 0.6,
+ marginTop: gutters * 0.6,
+ },
+ modalAction: {
+ width: '100%',
+ },
+})
diff --git a/src/screens/cover/components/ShareAndCreditsModal.js b/src/screens/cover/components/ShareAndCreditsModal.js
new file mode 100644
index 0000000..d8c6d2f
--- /dev/null
+++ b/src/screens/cover/components/ShareAndCreditsModal.js
@@ -0,0 +1,897 @@
+import React, { useMemo, useState, useCallback } from 'react'
+import {
+ StyleSheet,
+ Text,
+ View,
+ Pressable,
+ Linking,
+ Alert,
+ ScrollView,
+ ActivityIndicator,
+} from 'react-native'
+import { MaterialCommunityIcons } from '@expo/vector-icons'
+import * as Sharing from 'expo-sharing'
+import { BlurView } from 'expo-blur'
+import { Image as ExpoImage } from 'expo-image'
+import { gutters, Palette } from '../../../styles'
+import { FONT_FAMILY } from '../../../styles/Fonts'
+import Overlay from '../../../components/Overlay'
+import { useStripe } from '../../../providers/StripeProvider'
+import CreditAmount from '../../../components/CreditAmount'
+import GradientButton from '../../../components/GradientButton'
+import AppCheckbox from '../../../components/AppCheckbox'
+import { subBadges, icons } from '../../../assets'
+import { isWeb } from '../../../hooks/useLayoutType'
+
+// --- HELPER FUNCTIONS & CONSTANTS (Copied from Subscriptions.js) ---
+
+const formatCurrency = (amount, currency = 'eur') => {
+ if (typeof amount !== 'number') {
+ return null
+ }
+ const normalized = amount / 100
+ const upperCurrency =
+ typeof currency === 'string' && currency.trim() ? currency.trim().toUpperCase() : 'EUR'
+
+ if (typeof Intl !== 'undefined' && Intl.NumberFormat) {
+ try {
+ return new Intl.NumberFormat('fr-FR', {
+ style: 'currency',
+ currency: upperCurrency,
+ minimumFractionDigits: 2,
+ }).format(normalized)
+ } catch (_error) {
+ // Ignore
+ }
+ }
+ return `${normalized.toFixed(2)} ${upperCurrency}`
+}
+
+const getIntervalLabel = (recurring) => {
+ if (!recurring || typeof recurring !== 'object') {
+ return null
+ }
+ const interval = recurring.interval || 'month'
+ const count = recurring.interval_count || 1
+ const vocabulary = {
+ day: { singular: 'jour', plural: 'jours' },
+ week: { singular: 'semaine', plural: 'semaines' },
+ month: { singular: 'mois', plural: 'mois' },
+ year: { singular: 'an', plural: 'ans' },
+ }
+ const terms = vocabulary[interval] || vocabulary.month
+ if (count <= 1) {
+ return `par ${terms.singular}`
+ }
+ return `tous les ${count} ${count > 1 ? terms.plural : terms.singular}`
+}
+
+const PLAN_SYNONYMS = {
+ starter: ['starter'],
+ pro: ['pro'],
+ premium: ['premium'],
+}
+
+const PLAN_DISPLAY_NAME_BY_KEY = {
+ starter: 'Play Backer Blue',
+ pro: 'Play Backer Silver',
+ premium: 'Play Backer Gold',
+}
+
+const PACK_PRICE_ID_BY_PERIOD = {
+ monthly: {
+ starter: 'price_1SPgitCzf2o5bDRdbnhLFx6f',
+ pro: 'price_1SPgjCCzf2o5bDRdr08Xzp8u',
+ premium: 'price_1SPgjaCzf2o5bDRdd9Xo2u26',
+ },
+ annual: {
+ starter: 'price_1SPgkDCzf2o5bDRdNGLVNeQ3',
+ pro: 'price_1SPgkXCzf2o5bDRdejBVxEBY',
+ premium: 'price_1SPgkqCzf2o5bDRdIcUwTDrm',
+ },
+}
+
+const PRICE_ID_TO_PLAN_KEY = {}
+Object.values(PACK_PRICE_ID_BY_PERIOD).forEach((mapping) => {
+ Object.entries(mapping).forEach(([planKey, priceId]) => {
+ if (!priceId) return
+ PRICE_ID_TO_PLAN_KEY[priceId] = planKey
+ })
+})
+
+const PLAN_DETAILS = {
+ starter: {
+ creditsLabel: '50 Crédits / Mois',
+ popular: false,
+ privileges: [
+ { text: 'Eligible au concours mensuel chanson/Vidéo', bold: true },
+ ],
+ },
+ pro: {
+ creditsLabel: '100 crédits + 50 offerts / Mois',
+ popular: true,
+ privileges: [
+ { text: 'Eligible au concours mensuel chanson/Vidéo', bold: true },
+ ],
+ },
+ premium: {
+ creditsLabel: '150 Crédits + 100 offerts / Mois',
+ popular: false,
+ privileges: [
+ { text: 'Eligible au concours mensuel chanson/Vidéo', bold: true },
+ ],
+ },
+}
+
+const COIN_PACK_DETAILS = {
+ starter: {
+ label: 'Pack starter',
+ creditsDisplay: '50 Crédits',
+ disclaimer: '*Non-Eligible au concours Chanson/Vidéo',
+ },
+ medium: {
+ label: 'Pack Medium',
+ creditsDisplay: '100 Crédits + 50 offerts',
+ disclaimer: '*Non-Eligible au concours Chanson/Vidéo',
+ },
+ premium: {
+ label: 'Pack Premium',
+ creditsDisplay: '150 Crédits + 100 offerts',
+ disclaimer: '*Non-Eligible au concours Chanson/Vidéo',
+ },
+}
+
+const getCoinPackKey = (pack) => {
+ const name = (pack?.name || '').toLowerCase()
+ if (name.includes('starter')) return 'starter'
+ if (name.includes('medium')) return 'medium'
+ if (name.includes('premium') || name.includes('prenium') || name.includes('creator'))
+ return 'premium'
+ return null
+}
+
+const getPlanKeyForBadge = (plan) => {
+ if (!plan || typeof plan !== 'object') return null
+ const priceId = typeof plan?.priceId === 'string' ? plan.priceId : plan?.id || null
+ if (priceId && PRICE_ID_TO_PLAN_KEY[priceId]) {
+ return PRICE_ID_TO_PLAN_KEY[priceId]
+ }
+ const label = (plan?.product?.name || plan?.nickname || '').toString().toLowerCase()
+ if (!label) return null
+ const match = Object.entries(PLAN_SYNONYMS).find(([, variants]) =>
+ variants.some((variant) => label.includes(variant))
+ )
+ return match ? match[0] : null
+}
+
+// --- SUBSCRIPTION CARD COMPONENT (Copied) ---
+
+function SubscriptionCard({ plan, selected, onSelect, isAnnual }) {
+ const planKey = getPlanKeyForBadge(plan)
+ const planName =
+ (planKey && PLAN_DISPLAY_NAME_BY_KEY[planKey]) ||
+ plan?.product?.name ||
+ plan?.nickname ||
+ plan?.priceId ||
+ 'Abonnement'
+ const formattedPrice = formatCurrency(plan?.unitAmount, plan?.currency)
+ const intervalLabel = getIntervalLabel(plan?.recurring)
+ const coinsPerMonth =
+ typeof plan?.coinsPerMonth === 'number' && Number.isFinite(plan.coinsPerMonth)
+ ? Math.round(plan.coinsPerMonth)
+ : null
+ const details = planKey ? PLAN_DETAILS[planKey] : null
+ const isPopular = details?.popular
+
+ const planBadgeKey = planKey
+ const planBadgeSource = planBadgeKey && subBadges[planBadgeKey] ? subBadges[planBadgeKey] : null
+
+ const handleSelect = React.useCallback(() => {
+ if (typeof onSelect === 'function' && plan?.priceId) {
+ onSelect(plan.priceId)
+ }
+ }, [onSelect, plan?.priceId])
+
+ return (
+ [
+ styles.cardWrapper,
+ selected && styles.cardWrapperSelected,
+ pressed && styles.cardWrapperPressed,
+ plan?.active === false && styles.cardWrapperInactive,
+ isPopular && styles.cardWrapperPopular,
+ ]}
+ disabled={plan?.active === false}
+ >
+
+
+ {plan?.badge ? (
+
+ {plan.badge}
+
+ ) : null}
+
+
+
+ {planName}
+ {planBadgeSource ? (
+
+ ) : null}
+
+ {plan?.nickname ? {plan.nickname} : null}
+
+
+ {formattedPrice || coinsPerMonth !== null ? (
+
+ {formattedPrice ? (
+
+ {formattedPrice}
+ {intervalLabel ? {intervalLabel} : null}
+
+ ) : null}
+
+ {details?.creditsLabel ? (
+ {details.creditsLabel}
+ ) : coinsPerMonth !== null ? (
+
+
+ / mois
+
+ ) : null}
+
+ ) : null}
+
+
+ Privilège Membre :
+ {details?.privileges?.map((item, index) => (
+
+
+ {item.bold ? `• ${item.text}` : item.text}
+
+
+ )) || (
+ • Crédits gratuits tous les mois
+ )}
+
+
+ {isPopular ? (
+
+ Le plus populaire !
+
+ ) : null}
+
+
+ {isAnnual ? (
+
+ 2 mois offert
+
+ ) : null}
+
+ )
+}
+
+// --- MAIN WRAPPER COMPONENT ---
+
+const ShareAndCreditsModal = ({
+ isVisible,
+ setIsVisible,
+ project,
+ selectedOption,
+}) => {
+ const trackTitle = project?.title || 'Musicland Track'
+
+ const {
+ subscriptions,
+ coinPacks,
+ createSubscriptionCheckout,
+ createCoinPackCheckout,
+ isCatalogLoading,
+ } = useStripe()
+
+ const [selectedPeriodKey, setSelectedPeriodKey] = useState('annual')
+ const [selectedPriceId, setSelectedPriceId] = useState(null)
+ const [processingPriceId, setProcessingPriceId] = useState(null)
+ const [errorMessage, setErrorMessage] = useState(null)
+ const [hasAcceptedPublication, setHasAcceptedPublication] = useState(true)
+
+ const normalizedPlansByPeriod = useMemo(() => {
+ // Basic normalization/filter logic similar to Subscriptions.js
+ const normalize = (plans) => {
+ if (!Array.isArray(plans)) return []
+ return plans.filter((p) => p && p.priceId)
+ }
+ return {
+ monthly: normalize(subscriptions?.monthly),
+ annual: normalize(subscriptions?.annual),
+ }
+ }, [subscriptions])
+
+ // Set default selection
+ React.useEffect(() => {
+ const plans = normalizedPlansByPeriod[selectedPeriodKey] || []
+ if (plans.length > 0 && !selectedPriceId) {
+ // Optional: Auto-select popular/first plan
+ // But for now let's leave it null to force user choice or pick first
+ }
+ }, [normalizedPlansByPeriod, selectedPeriodKey, selectedPriceId])
+
+
+ const handleShare = async (platform) => {
+ const shareUrl =
+ project?.songUrl ||
+ project?.playbackUrl ||
+ selectedOption?.finalUrl ||
+ 'https://musicland.ai'
+ const shareText = `Check out my new song "${trackTitle}" created with MusicLand! ${shareUrl}`
+
+ if (platform === 'whatsapp') {
+ Linking.openURL(`whatsapp://send?text=${encodeURIComponent(shareText)}`).catch(() => {
+ Alert.alert('Erreur', 'WhatsApp is not installed')
+ })
+ } else if (platform === 'facebook') {
+ Linking.openURL(
+ `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`
+ ).catch(() => {
+ Alert.alert('Erreur', 'Unable to open Facebook')
+ })
+ } else if (platform === 'instagram' || platform === 'tiktok') {
+ if (await Sharing.isAvailableAsync()) {
+ await Sharing.shareAsync(shareUrl, { dialogTitle: 'Partager ma musique' })
+ } else {
+ Linking.openURL(shareUrl)
+ }
+ }
+ }
+
+ const handleBuyCredits = async (pack) => {
+ const targetId = pack?.productId
+ if (!targetId) return
+ setProcessingPriceId(targetId)
+ setErrorMessage(null)
+ try {
+ await createCoinPackCheckout(targetId)
+ } catch (error) {
+ setErrorMessage(error?.message || 'Erreur lors de l\'achat de crédits.')
+ } finally {
+ setProcessingPriceId(null)
+ }
+ }
+
+ const handleSubscribe = async () => {
+ if (!selectedPriceId) return
+ setProcessingPriceId(selectedPriceId)
+ try {
+ await createSubscriptionCheckout(selectedPriceId)
+ } catch (error) {
+ setErrorMessage(error?.message || 'Erreur lors de l\'abonnement.')
+ } finally {
+ setProcessingPriceId(null)
+ }
+ }
+
+ const currentPlans = normalizedPlansByPeriod[selectedPeriodKey] || []
+
+ return (
+
+
+
+ setIsVisible(false)} style={styles.closeButton}>
+
+
+
+
+
+ {/* SHARE SECTION */}
+ Partage ton succès !
+
+ Partage mon morceau
+
+ handleShare('whatsapp')}>
+
+
+ handleShare('facebook')}>
+
+
+ handleShare('instagram')}>
+
+
+ handleShare('tiktok')}>
+
+
+
+
+
+ {/* CREDITS SECTION */}
+ {coinPacks && coinPacks.length > 0 ? (
+
+ Recharger vos crédits
+
+ {coinPacks.map((pack) => {
+ const rawAmount = pack.unitAmount ?? pack.amount ?? pack.price ?? 0
+ const price = formatCurrency(rawAmount, pack.currency)
+ const coins = Number(pack.metadata?.coins || 0)
+ const packKey = getCoinPackKey(pack)
+ const details = packKey ? COIN_PACK_DETAILS[packKey] : null
+ const displayName = details?.label || pack.name
+ const creditsText = details?.creditsDisplay
+
+ return (
+
+
+ {displayName}
+ {creditsText ? (
+ {creditsText}
+ ) : (
+
+ )}
+ {details?.disclaimer ? (
+ {details.disclaimer}
+ ) : null}
+
+ handleBuyCredits(pack)}
+ disabled={Boolean(processingPriceId)}
+ style={styles.creditButton}
+ textStyle={styles.creditButtonText}
+ gradientStyle={{ paddingVertical: 8, paddingHorizontal: 16 }}
+ />
+
+ )
+ })}
+
+
+ ) : null}
+
+ {/* SUBSCRIPTIONS SECTION */}
+
+ Rejoignez le club MusicLand
+
+ {/* Period Toggle */}
+
+ {['monthly', 'annual'].map((key) => {
+ const isActive = selectedPeriodKey === key
+ const label = key === 'monthly' ? 'Mensuel' : 'Annuel'
+ const showAnnualPromo = key === 'annual'
+ return (
+ {
+ setSelectedPeriodKey(key)
+ setSelectedPriceId(null)
+ }}
+ style={[styles.segmentButton, isActive && styles.segmentButtonActive]}
+ >
+
+ {label}
+
+ {showAnnualPromo && (
+
+ -16%
+
+ )}
+
+ )
+ })}
+
+
+ {/* Plans Grid */}
+
+ {isCatalogLoading && !currentPlans.length ? (
+
+ ) : (
+ currentPlans.map((plan) => (
+
+
+
+ ))
+ )}
+
+
+
+ p.productId === processingPriceId) ? 'Traitement...' : 'Choisir cet abonnement'}
+ onPress={handleSubscribe}
+ disabled={!selectedPriceId || Boolean(processingPriceId)}
+ style={{ width: isWeb ? 320 : '100%' }}
+ />
+
+ {errorMessage ? {errorMessage} : null}
+
+
+ {/* BROADCAST SECTION */}
+
+
+ setHasAcceptedPublication((prev) => !prev)}
+ label="J'accepte de diffuser mon contenu sur la plateforme de streaming de musicland et sur youtube"
+ />
+
+ {
+ // No-op for now
+ console.log('Diffuser pressed', { hasAcceptedPublication })
+ }}
+ disabled={!hasAcceptedPublication}
+ containerStyle={styles.broadcastButton}
+ />
+
+
+
+
+ )
+}
+
+export default ShareAndCreditsModal
+
+const styles = StyleSheet.create({
+ modalCard: {
+ width: isWeb ? '90%' : '95%',
+ maxWidth: 1000,
+ height: isWeb ? '90%' : '85%',
+ alignSelf: 'center',
+ borderRadius: 24,
+ backgroundColor: 'rgba(23, 22, 33, 0.98)',
+ borderWidth: 1,
+ borderColor: 'rgba(255, 255, 255, 0.12)',
+ overflow: 'hidden',
+ padding: 0,
+ },
+ scrollContent: {
+ padding: gutters,
+ gap: gutters * 1.5,
+ paddingBottom: gutters * 2,
+ },
+ closeButtonContainer: {
+ position: 'absolute',
+ top: 12,
+ right: 12,
+ zIndex: 100,
+ },
+ closeButton: {
+ padding: 8,
+ backgroundColor: 'rgba(255,255,255,0.1)',
+ borderRadius: 20,
+ },
+ modalTitle: {
+ fontFamily: FONT_FAMILY.InterBold,
+ fontSize: 24,
+ color: Palette.white,
+ textAlign: 'center',
+ marginTop: 10,
+ },
+ shareSection: {
+ gap: 12,
+ alignItems: 'center',
+ },
+ shareSubtitle: {
+ fontSize: 16,
+ fontFamily: FONT_FAMILY.InterSemiBold,
+ color: Palette.white,
+ opacity: 0.9,
+ },
+ shareButtons: {
+ flexDirection: 'row',
+ gap: 20,
+ justifyContent: 'center',
+ },
+ shareBtn: {
+ padding: 8,
+ borderRadius: 50,
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
+ },
+ sectionContainer: {
+ gap: 16,
+ width: '100%',
+ alignItems: 'center',
+ borderTopWidth: 1,
+ borderTopColor: 'rgba(255, 255, 255, 0.08)',
+ paddingTop: gutters,
+ },
+ sectionTitle: {
+ fontFamily: FONT_FAMILY.InterBold,
+ fontSize: 20,
+ color: Palette.white,
+ textAlign: 'center',
+ },
+ creditsGrid: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ gap: 16,
+ justifyContent: 'center',
+ width: '100%',
+ },
+ creditCard: {
+ backgroundColor: 'rgba(255, 255, 255, 0.05)',
+ borderRadius: 16,
+ padding: 16,
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ width: isWeb ? 220 : '100%',
+ minHeight: 160,
+ gap: 12,
+ borderWidth: 1,
+ borderColor: 'rgba(255, 255, 255, 0.1)',
+ },
+ creditInfoContainer: {
+ alignItems: 'center',
+ gap: 4,
+ },
+ creditName: {
+ fontFamily: FONT_FAMILY.InterSemiBold,
+ fontSize: 14,
+ color: Palette.white,
+ textAlign: 'center',
+ marginBottom: 4,
+ },
+ creditAmountCustom: {
+ fontFamily: FONT_FAMILY.InterBold,
+ fontSize: 16,
+ color: Palette.white,
+ textAlign: 'center',
+ },
+ creditAmountText: {
+ fontSize: 20,
+ },
+ packDisclaimer: {
+ fontFamily: FONT_FAMILY.InterRegular,
+ fontSize: 10,
+ color: 'rgba(255, 255, 255, 0.6)',
+ fontStyle: 'italic',
+ textAlign: 'center',
+ marginTop: 4,
+ },
+ creditButton: {
+ width: '100%',
+ height: 36,
+ minHeight: 36,
+ },
+ creditButtonText: {
+ fontSize: 13,
+ },
+ segmentedControl: {
+ flexDirection: 'row',
+ alignSelf: 'center',
+ justifyContent: 'center',
+ padding: 4,
+ borderRadius: 999,
+ backgroundColor: 'rgba(255, 255, 255, 0.08)',
+ },
+ segmentButton: {
+ paddingVertical: 8,
+ paddingHorizontal: 18,
+ borderRadius: 999,
+ position: 'relative',
+ },
+ segmentButtonActive: {
+ backgroundColor: 'rgba(255, 255, 255, 0.18)',
+ },
+ segmentLabel: {
+ fontFamily: FONT_FAMILY.InterMedium,
+ fontSize: 14,
+ color: 'rgba(255, 255, 255, 0.7)',
+ },
+ segmentLabelActive: {
+ color: Palette.white,
+ },
+ segmentBadge: {
+ position: 'absolute',
+ top: -6,
+ right: -8,
+ paddingHorizontal: 8,
+ paddingVertical: 3,
+ borderRadius: 999,
+ backgroundColor: Palette.red,
+ },
+ segmentBadgeText: {
+ fontFamily: FONT_FAMILY.InterSemiBold,
+ fontSize: 11,
+ color: Palette.white,
+ },
+ plansGrid: {
+ width: '100%',
+ flexDirection: isWeb ? 'row' : 'column',
+ flexWrap: 'wrap',
+ justifyContent: 'center',
+ gap: 16,
+ },
+ // Subscription Card Styles
+ cardWrapper: {
+ width: '100%',
+ minHeight: 280,
+ borderRadius: 24,
+ overflow: 'hidden',
+ borderWidth: 1,
+ borderColor: 'rgba(255, 255, 255, 0.12)',
+ backgroundColor: 'rgba(12, 14, 18, 0.45)',
+ },
+ cardWrapperSelected: {
+ borderColor: Palette.primary,
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 12 },
+ shadowOpacity: 0.35,
+ shadowRadius: 20,
+ elevation: 8,
+ },
+ cardWrapperPopular: {
+ borderColor: Palette.primary,
+ backgroundColor: 'rgba(112, 35, 247, 0.1)',
+ },
+ cardWrapperPressed: {
+ transform: [{ scale: 0.98 }],
+ },
+ cardWrapperInactive: {
+ opacity: 0.6,
+ },
+ cardBlur: {
+ flex: 1,
+ padding: 16,
+ gap: 16,
+ },
+ badge: {
+ alignSelf: 'flex-start',
+ backgroundColor: 'rgba(112, 35, 247, 0.25)',
+ borderRadius: 12,
+ paddingHorizontal: 8,
+ paddingVertical: 4,
+ marginBottom: 8,
+ },
+ badgeText: {
+ color: Palette.primary,
+ fontSize: 11,
+ fontFamily: FONT_FAMILY.InterSemiBold,
+ },
+ cardHeader: {
+ gap: 4,
+ },
+ titleRow: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ },
+ planName: {
+ color: Palette.white,
+ fontSize: 18,
+ fontFamily: FONT_FAMILY.InterBold,
+ },
+ planBadgeImage: {
+ width: 40,
+ height: 40,
+ },
+ cardSubtitle: {
+ color: 'rgba(255,255,255,0.7)',
+ fontSize: 13,
+ },
+ priceBlock: {
+ marginBottom: 8,
+ },
+ priceRow: {
+ flexDirection: 'row',
+ alignItems: 'baseline',
+ gap: 6,
+ },
+ priceValue: {
+ color: Palette.white,
+ fontSize: 20,
+ fontFamily: FONT_FAMILY.InterBold,
+ },
+ period: {
+ color: 'rgba(255,255,255,0.6)',
+ fontSize: 13,
+ },
+ coinsPromoText: {
+ color: Palette.primary,
+ fontSize: 14,
+ fontFamily: FONT_FAMILY.InterBold,
+ },
+ coinsPerMonthRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 4,
+ },
+ coinsPerMonth: {
+ fontSize: 14,
+ color: Palette.primary,
+ },
+ coinsPerMonthSuffix: {
+ fontSize: 13,
+ color: Palette.primary,
+ },
+ cardBenefits: {
+ gap: 6,
+ },
+ benefitsTitle: {
+ color: Palette.white,
+ fontSize: 13,
+ fontFamily: FONT_FAMILY.InterSemiBold,
+ },
+ benefitBlock: {
+ flexDirection: 'row',
+ },
+ benefitText: {
+ color: 'rgba(255,255,255,0.8)',
+ fontSize: 12,
+ },
+ benefitTextBold: {
+ fontFamily: FONT_FAMILY.InterSemiBold,
+ color: Palette.white,
+ },
+ cardBenefitsItem: {
+ color: 'rgba(255,255,255,0.8)',
+ fontSize: 12,
+ },
+ popularBadge: {
+ alignSelf: 'center',
+ backgroundColor: Palette.primary,
+ paddingHorizontal: 12,
+ paddingVertical: 4,
+ borderRadius: 12,
+ marginTop: 12,
+ },
+ popularBadgeText: {
+ color: Palette.white,
+ fontSize: 10,
+ fontFamily: FONT_FAMILY.InterBold,
+ textTransform: 'uppercase',
+ },
+ annualBadge: {
+ position: 'absolute',
+ top: 10,
+ right: 10,
+ backgroundColor: Palette.red,
+ paddingHorizontal: 10,
+ paddingVertical: 4,
+ borderRadius: 12,
+ },
+ annualBadgeText: {
+ color: Palette.white,
+ fontSize: 11,
+ fontFamily: FONT_FAMILY.InterBold,
+ },
+ errorText: {
+ color: Palette.red,
+ textAlign: 'center',
+ marginTop: 10,
+ },
+ broadcastSection: {
+ marginTop: gutters,
+ paddingTop: gutters,
+ borderTopWidth: 1,
+ borderTopColor: 'rgba(255, 255, 255, 0.08)',
+ gap: gutters,
+ width: '100%',
+ alignItems: 'center',
+ },
+ consentContainer: {
+ width: '100%',
+ paddingHorizontal: gutters,
+ },
+ broadcastButton: {
+ width: isWeb ? 320 : '100%',
+ },
+})