You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

2744 lines
73 KiB

<template>
<view class="ie-page" :class="{ 'low-performance': lowPowerMode, 'android-home': isAndroid }">
<view class="aurora aurora-a"></view>
<view class="aurora aurora-b"></view>
<view class="aurora aurora-c"></view>
<view class="top-safe" :style="{ height: menuButtonInfo.top + 'px' }"></view>
<view class="float-bar">
<view class="home-back" @tap="backHome">
<text class="home-back-icon"></text>
<text>返回首页</text>
</view>
</view>
<view class="hero">
<view class="hero-top">
<view class="hero-eyebrow">{{ timeLabel }} · {{ greeting }}</view>
<view class="perf-toggle" :class="{ active: lowPowerMode }" @tap="toggleLowPowerMode">
{{ lowPowerMode ? ' 流畅' : ' 氛围' }}
</view>
</view>
<view class="hero-statement">
此刻有 <text class="hero-num">{{ displayAwakeCount }}</text> 个灵魂正在这片星系漂流
</view>
<!-- <view class="hero-statement second"></view> -->
<view class="hero-live daily-live" v-if="dailyQuestion.exists" @tap="goDailyQuestion">
<text class="live-dot"></text>
<text class="daily-live-tag">同频一题</text>
<text class="daily-live-text">{{ dailyQuestion.content }}</text>
<text class="daily-live-go">{{ dailyQuestion.answered ? '看答案' : '去回答' }} ›</text>
</view>
<view class="hero-profile-line">
<view class="hero-profile" @tap="goUniverse">
<image class="profile-avatar-img" v-if="profile.avatarUrl" :src="profile.avatarUrl" mode="aspectFill"></image>
<view class="profile-avatar" v-else>{{ profile.avatarText || '我' }}</view>
<view class="profile-meta">
<view class="profile-name">{{ profile.anonymousName || '半匿名漂流者' }}</view>
<view class="profile-tags">{{ profileTagsText }}</view>
</view>
<view class="profile-arrow">›</view>
</view>
<view class="say-intent-btn" @tap.stop="openCompanionEditor">我也说一句</view>
</view>
</view>
<view class="lower-stack">
<view class="drift-field">
<view class="spark spark-a">✦</view>
<view class="spark spark-b">✧</view>
<view class="spark spark-c">✦</view>
<view class="spark spark-d">✧</view>
<view class="danmu-lane lane-0">
<view class="lane-track">
<view class="danmu-item" v-for="(item,idx) in danmuLanes[0]" :key="idx"
:class="['tint-' + (idx % 4), item.type]" @tap="selectDrift(item)">
<text class="danmu-emoji">{{ moodEmoji(item) }}</text>
<text class="danmu-text">{{ item.text }}</text>
</view>
</view>
</view>
<view class="danmu-lane lane-1">
<view class="lane-track">
<view class="danmu-item" v-for="(item,idx) in danmuLanes[1]" :key="idx"
:class="['tint-' + ((idx + 1) % 4), item.type]" @tap="selectDrift(item)">
<text class="danmu-emoji">{{ moodEmoji(item) }}</text>
<text class="danmu-text">{{ item.text }}</text>
</view>
</view>
</view>
<view class="danmu-lane lane-2">
<view class="lane-track">
<view class="danmu-item" v-for="(item,idx) in danmuLanes[2]" :key="idx"
:class="['tint-' + ((idx + 2) % 4), item.type]" @tap="selectDrift(item)">
<text class="danmu-emoji">{{ moodEmoji(item) }}</text>
<text class="danmu-text">{{ item.text }}</text>
</view>
</view>
</view>
<view class="signal-planet" :style="galaxyStyle" @touchstart.stop="startGalaxyDrag" @touchmove.stop="moveGalaxyDrag"
@touchend="endGalaxyDrag" @touchcancel="endGalaxyDrag">
<view class="galaxy-breath">
<view class="galaxy-ring ring-main"></view>
<view class="galaxy-ring ring-second"></view>
<view class="galaxy-ring ring-third"></view>
<view class="orbit-planet" v-for="(planet,index) in orbitPlanets" :key="planet.name"
:class="'orbit-planet-' + index" :style="orbitPlanetStyles[index]"></view>
<view class="galaxy-body">
<view class="liquid liquid-a"></view>
<view class="liquid liquid-b"></view>
<view class="soul-particle soul-a"></view>
<view class="soul-particle soul-b"></view>
<view class="soul-particle soul-c"></view>
<view class="planet-core">
<view class="planet-text">有人在等你</view>
<view class="planet-sub">{{ currentMode === 'i' ? '安静靠近' : '轻轻热闹' }}</view>
<view class="planet-cta" @tap.stop="goMatch" @click.stop="goMatch" @touchstart.stop @touchend.stop="goMatch">戳我 开始漂流 →</view>
</view>
</view>
</view>
</view>
</view>
<view class="home-action-panel">
<view class="play-deck">
<view class="ticket" @tap="showTicketTip">
<view class="ticket-left">
<text class="ticket-icon">🎫</text>
<text class="ticket-label">今日漂流票</text>
</view>
<view class="ticket-divider"></view>
<view class="ticket-right">
<text class="ticket-num">{{ chancesLeft }}</text>
<text class="ticket-unit">张</text>
</view>
</view>
<view class="target-btn" @tap="openTargetPanel">
<text class="target-star">✦</text>
<text class="target-text">想遇见谁</text>
<text class="target-sub">偏好设置</text>
</view>
</view>
<view class="daily-q-bar mood-copy-bar">
<text>“{{ displayMoodCopy }}”</text><text class="typing-cursor" v-if="displayMoodCopy.length < activeMood.copy.length"></text>
</view>
<view class="mood-strip">
<view class="mood-orbs">
<view class="mood-orb" v-for="item in moods" :key="item.key"
:class="{ active: currentMood === item.key }" @tap="changeMood(item.key)">
<view class="orb-face"><text class="orb-emoji">{{ item.icon }}</text></view>
<text class="orb-label">{{ item.label }}</text>
</view>
</view>
</view>
</view>
</view>
<view class="target-mask" v-if="showTargetPanel" @tap="closeTargetPanel">
<view class="target-popup" @tap.stop>
<view class="target-popup-head">
<view>
<view class="target-popup-title">随机陪伴设置</view>
<view class="target-popup-sub">选择此刻想遇见的人,再开始漂流。</view>
</view>
<view class="target-close" @tap="closeTargetPanel">×</view>
</view>
<view class="target-title">找什么搭子(可选)</view>
<view class="target-row intent-row">
<view class="target-chip intent-chip" v-for="item in intents" :key="item.key"
:class="{ active: matchIntent === item.key }" @tap="toggleIntent(item.key)">
{{ item.icon }} {{ item.label }}
</view>
</view>
<input class="target-input search-input" v-model="fuzzyKeyword" maxlength="24" placeholder="模糊搜索昵称/签名/找搭子内容" />
<view class="target-title gender-title">今天想遇见谁</view>
<view class="target-row">
<view class="target-chip" v-for="item in targetModes" :key="item.key"
:class="{ active: targetMode === item.key }" @tap="setTargetMode(item.key)">
{{ item.label }}
</view>
</view>
<view class="target-title gender-title">匹配范围</view>
<view class="target-row">
<view class="target-chip scope-chip" v-for="item in matchScopes" :key="item.key"
:class="{ active: matchScope === item.key }" @tap="matchScope = item.key">
{{ item.label }}
</view>
</view>
<view class="target-title gender-title">想匹配的性别</view>
<view class="target-row">
<view class="target-chip gender-chip" v-for="item in targetGenders" :key="item.key"
:class="{ active: targetGender === item.key }" @tap="setTargetGender(item.key)">
{{ item.label }}
</view>
</view>
<view class="start-match-btn" @tap="openMatch">开始随机陪伴</view>
</view>
</view>
<ie-bottom-tab active="index" :unread-count="unreadCount"></ie-bottom-tab>
<view class="companion-mask" v-if="showCompanionEditor" :style="companionMaskStyle" @tap="closeCompanionEditor">
<view class="companion-editor" :style="companionEditorStyle" @tap.stop>
<view class="companion-editor-head">
<view>
<view class="companion-editor-title">我也说一句</view>
<view class="companion-editor-sub">写下你此刻想找什么搭子,会展示在星系漂流里。</view>
</view>
<view class="target-close" @tap="closeCompanionEditor">×</view>
</view>
<input class="target-input companion-input" v-model="editCompanionIntent" maxlength="24" :adjust-position="false"
placeholder="例如:想找散步搭子" @focus="onCompanionInputFocus" @blur="onCompanionInputBlur" />
<view class="companion-save" :class="{ disabled: savingCompanionIntent }" @tap="saveCompanionIntent">
{{ savingCompanionIntent ? '保存中...' : '发布一句' }}
</view>
</view>
</view>
<view class="match-mask" v-if="showMatch" @tap="closeMatch">
<view class="match-panel" @tap.stop>
<scroll-view scroll-y class="match-scroll">
<view class="match-banner">
<view class="match-banner-deco">✦ ✧ ✦</view>
<view class="match-label">缘分签 · 半匿名漂流者</view>
<view class="close" @tap="closeMatch">×</view>
</view>
<view class="match-body">
<view class="match-avatar-wrap">
<image class="companion-orb-img" v-if="matchedPerson.avatarUrl" :src="matchedPerson.avatarUrl" mode="aspectFill"></image>
<view class="companion-orb" v-else :class="matchedPerson.mode || currentMode">{{ matchedPerson.avatar }}</view>
</view>
<view class="match-name">{{ matchedPerson.name }}</view>
<view class="match-online" v-if="matchedPerson.lastActiveText">
<view class="online-dot" :class="{ on: matchedOnline }"></view>
<text>{{ matchedPerson.lastActiveText }}</text>
</view>
<view class="match-intent" v-if="currentMatch && currentMatch.intentMatched">
⚡ TA 也在找{{ intentLabel(currentMatch.intent) }}
</view>
<view class="match-mood" v-if="matchedPerson.moodText">
<text class="match-mood-icon">{{ matchedPerson.moodIcon }}</text>
<text>{{ matchedPerson.moodText }}</text>
</view>
<view class="match-state">{{ modeText(matchedPerson.mode) }} · {{ genderText(matchedPerson.gender) }}</view>
<view class="match-region" v-if="matchedPerson.regionName">📍 {{ matchedPerson.regionName }}</view>
<view class="match-tags" v-if="matchedPerson.tags && matchedPerson.tags.length">
<text v-for="tag in matchedPerson.tags" :key="tag"># {{ tag }}</text>
</view>
<view class="match-state-text">{{ matchedPerson.state }}</view>
<view class="match-quote">{{ matchedPerson.quote }}</view>
<view class="match-persona" v-if="matchedPerson.personaImages && matchedPerson.personaImages.length">
<view class="match-section-title">人格卡片</view>
<scroll-view scroll-x class="match-persona-scroll">
<image class="match-persona-image" v-for="(img,index) in matchedPerson.personaImages" :key="img"
:src="img" mode="aspectFill" @tap="previewMatchedPersona(index)"></image>
</scroll-view>
</view>
<view class="match-actions">
<view class="ghost-btn" @tap="skipMatch">轻轻划过</view>
<view class="solid-btn" @tap="goChat">进入聊天 →</view>
</view>
<view class="match-groupbuy" v-if="showGroupBuyEntry" @tap="goGroupBuy">
🛍 搭子福利:一起拼一单更划算 →
</view>
</view>
</scroll-view>
</view>
</view>
<view class="profile-mask" v-if="showProfile" @tap="closeProfile">
<view class="profile-mini" @tap.stop>
<view class="mini-orb">◌</view>
<view class="mini-title">半匿名漂流者</view>
<view class="mini-sub">今天更适合:{{ currentMode === 'i' ? '慢一点靠近' : '轻轻热闹一下' }}</view>
<view class="mini-action" @tap="goUniverse">进入我的宇宙</view>
</view>
</view>
<view class="creating-mask" v-if="matching">
<view class="creating-card">
<view class="creating-orbit">
<view class="creating-dot dot-a"></view>
<view class="creating-dot dot-b"></view>
<view class="creating-core">{{ currentMode }}</view>
</view>
<view class="creating-title">寻找同频中</view>
<view class="creating-sub">正在捕捉附近的轻轻回应,请稍等一下</view>
<view class="creating-progress"><view></view></view>
</view>
</view>
</view>
</template>
<script>
import { ieHome, getIeUnreadCount, updateIeStatus, startIeMatch, matchIeProfile, getIeDailyQuestion, saveIeProfile } from '@/common/ieApi.js'
import IeBottomTab from '@/components/ie-bottom-tab/ie-bottom-tab.vue'
export default {
components: { IeBottomTab },
data() {
return {
menuButtonInfo: { top: 44 },
hasShownOnce: false,
currentMood: 'quiet',
currentMode: 'i',
chancesLeft: 20,
unreadCount: 0,
showMatch: false,
isAndroid: false,
ieBannedRedirecting: false,
now: new Date(),
awakeCount: 127,
onlineCountBase: 0,
displayAwakeCount: 0,
waitingCount: 49,
liveTimer: null,
unreadTimer: null,
countTimer: null,
showProfile: false,
showTargetPanel: false,
showCompanionEditor: false,
matching: false,
savingCompanionIntent: false,
keyboardHeight: 0,
profileReady: false,
profile: {},
lowPowerMode: false,
targetMode: 'any',
targetGender: 'any',
matchScope: 'school',
matchIntent: '',
customCompanionIntent: '',
editCompanionIntent: '',
fuzzyKeyword: '',
intents: [
{ key: 'milktea', icon: '🧋', label: '奶茶搭子' },
{ key: 'meal', icon: '🍚', label: '吃饭搭子' },
{ key: 'study', icon: '📚', label: '自习搭子' },
{ key: 'walk', icon: '🚶', label: '散步搭子' },
{ key: 'chat', icon: '💬', label: '只想聊聊' }
],
dailyQuestion: { exists: false },
activeDriftText: '',
displayMoodCopy: '对方没有催我讲话,沉默变得没那么尴尬',
moodTypeTimer: null,
galaxyRotateX: -8,
galaxyRotateY: 18,
galaxyDragging: false,
galaxyMoved: false,
galaxyStartX: 0,
galaxyStartY: 0,
galaxyLastMoveTime: 0,
galaxyAutoAngle: 0,
galaxyTimer: null,
orbitPlanets: [
{ name: 'mint', angle: 12, radiusX: 269, radiusY: 70, size: 22, speed: 1.05, color: 'rgba(169,255,231,.92)' },
{ name: 'violet', angle: 86, radiusX: 228, radiusY: 110, size: 31, speed: .72, color: 'rgba(162,155,254,.86)' },
{ name: 'peach', angle: 156, radiusX: 295, radiusY: 89, size: 18, speed: .9, color: 'rgba(255,184,209,.9)' },
{ name: 'lemon', angle: 218, radiusX: 242, radiusY: 132, size: 14, speed: 1.22, color: 'rgba(255,226,139,.88)' },
{ name: 'aqua', angle: 288, radiusX: 324, radiusY: 101, size: 26, speed: .62, color: 'rgba(132,233,255,.82)' },
{ name: 'snow', angle: 332, radiusX: 199, radiusY: 58, size: 12, speed: 1.38, color: 'rgba(255,255,255,.95)' }
],
doneText: "今天先到这里,平台下单可增加匹配次数",
moods: [{"key":"quiet","label":"想安静","icon":"🌙","copy":"对方没有催我讲话,沉默变得没那么尴尬。"},{"key":"talk","label":"想说话","icon":"💭","copy":"聊了一个很小的日常话题,心情被拉亮了一点。"},{"key":"listen","label":"听着呢","icon":"🎧","copy":"像有人坐在旁边,不需要解释为什么低落。"},{"key":"drift","label":"轻了一点","icon":"🫧","copy":"只是给今天留一点柔软的痕迹。"}],
targetModes: [{ key: 'i', label: '匹配 i 人' }, { key: 'e', label: '匹配 e 人' }, { key: 'any', label: '都可以' }],
targetGenders: [{ key: 'male', label: '男生' }, { key: 'female', label: '女生' }, { key: 'any', label: '不限' }],
matchScopes: [{ key: 'school', label: '本校区' }, { key: 'world', label: '世界用户' }],
companions: {"i":[{"name":"树荫下的风","avatar":"风","state":"在校园里发呆","quote":"可以安静待 15 分钟,不用急着找话题。"},{"name":"耳机里的云","avatar":"云","state":"刚从教室出来","quote":"今天只想慢慢说两句。"}],"e":[{"name":"便利店灯光","avatar":"光","state":"想聊点不重要的","quote":"要不要交换一句今天最荒唐的小事?"},{"name":"操场散步员","avatar":"跑","state":"刚从操场回来","quote":"我可以负责开场,你负责随便接。"}]},
matchedPerson: {},
currentMatch: null,
presetDriftMessages: [
{ type: 'i', mood: 'quiet', text: '操场现在风很舒服' },
{ type: 'i', mood: 'listen', text: '有人在图书馆假装努力' },
{ type: 'e', mood: 'talk', text: '谁愿意陪我散步 10 分钟' },
{ type: 'i', mood: 'quiet', text: '刚刚有人有点低落' },
{ type: 'e', mood: 'talk', text: '想听一个不重要的故事' },
{ type: 'i', mood: 'drift', text: '走在路上突然有点空' }
],
driftMessages: []
}
},
computed: {
activeMood() {
return this.moods.find(item => item.key === this.currentMood) || this.moods[0]
},
matchedOnline() {
const text = this.matchedPerson.lastActiveText || ''
return text === '当前在线' || text === '刚刚在线'
},
showGroupBuyEntry() {
// 吃喝类搭子才挂拼团入口,意图越具体转化越自然
const intent = this.currentMatch && this.currentMatch.intent
return intent === 'milktea' || intent === 'meal'
},
profileTagsText() {
const tags = this.profile.interestTags || []
return tags.length ? tags.slice(0, 3).join(' · ') : (this.currentMode === 'i' ? '安静陪伴' : '轻轻热闹')
},
timeLabel() {
const hours = String(this.now.getHours()).padStart(2, '0')
const minutes = String(this.now.getMinutes()).padStart(2, '0')
return `${hours}:${minutes}`
},
greeting() {
const hour = this.now.getHours()
if (hour < 5) return '夜深了,漂流者'
if (hour < 11) return '早安,漂流者'
if (hour < 14) return '午安,漂流者'
if (hour < 18) return '下午好,漂流者'
if (hour < 23) return '晚上好,漂流者'
return '夜深了,漂流者'
},
danmuLanes() {
const lanes = [[], [], []]
const source = this.driftMessages.length ? this.driftMessages : this.presetDriftMessages
source.forEach((item, index) => {
lanes[index % 3].push(item)
})
// Duplicate each lane so the marquee loops seamlessly.
return lanes.map(lane => lane.concat(lane))
},
galaxyStyle() {
return 'transform: translate(-50%, -50%);'
},
orbitPlanetStyles() {
return this.orbitPlanets.map((planet) => {
const angle = (planet.angle + this.galaxyRotateY + this.galaxyAutoAngle * planet.speed) * Math.PI / 180
const tilt = this.galaxyRotateX * Math.PI / 180
const depth = Math.sin(angle)
const x = Math.cos(angle) * planet.radiusX
const y = Math.sin(angle) * planet.radiusY * Math.cos(tilt) + Math.sin(tilt) * 20
const scale = 0.72 + (depth + 1) * 0.18
const opacity = 0.42 + (depth + 1) * 0.25
const zIndex = Math.round(8 + (depth + 1) * 7)
return [
`width: ${planet.size}rpx`,
`height: ${planet.size}rpx`,
`background: ${planet.color}`,
`color: ${planet.color}`,
`opacity: ${opacity}`,
`z-index: ${zIndex}`,
`transform: translate(-50%, -50%) translate(${x}rpx, ${y}rpx) scale(${scale})`
].join(';')
})
},
companionMaskStyle() {
const lift = this.keyboardHeight ? Math.max(this.keyboardHeight - 24, 0) : 0
return lift ? `padding-bottom: ${lift}px` : ''
},
companionEditorStyle() {
return this.keyboardHeight ? 'margin-bottom: 24rpx' : ''
}
},
onLoad() {
if (uni.getMenuButtonBoundingClientRect) {
this.menuButtonInfo = uni.getMenuButtonBoundingClientRect()
}
this.lowPowerMode = uni.getStorageSync('ieLowPowerMode') === '1'
this.detectPlatform()
this.pickCompanion()
this.loadHome()
this.loadDailyQuestion()
this.animateAwakeCount(this.awakeCount)
this.startPageTimers()
this.displayMoodCopy = this.activeMood.copy
},
onShow() {
this.startPageTimers()
if (this.hasShownOnce) {
this.loadHome()
this.loadDailyQuestion()
} else {
this.hasShownOnce = true
}
uni.authorize({
scope: 'scope.record',
success() {
},
fail() {
this.tui.toast("您未授权,语音功能可能会出现错误")
}
})
},
onHide() {
this.stopPageTimers()
},
onUnload() {
this.stopPageTimers()
},
methods: {
startPageTimers() {
if (!this.liveTimer) {
this.liveTimer = setInterval(() => {
this.now = new Date()
this.awakeCount = this.randomAwakeCount(this.onlineCountBase)
this.waitingCount = 39 + Math.floor(Math.random() * 18)
this.animateAwakeCount(this.awakeCount)
}, this.lowPowerMode ? 9000 : 4200)
}
if (!this.lowPowerMode && !this.galaxyTimer) {
this.galaxyTimer = setInterval(() => {
this.galaxyAutoAngle = (this.galaxyAutoAngle + 3.8) % 360
}, 160)
}
if (!this.unreadTimer) {
this.unreadTimer = setInterval(() => {
this.refreshUnreadCount()
}, 15000)
}
},
detectPlatform() {
try {
const info = uni.getSystemInfoSync ? uni.getSystemInfoSync() : {}
this.isAndroid = String(info.platform || '').toLowerCase() === 'android'
} catch (e) {
this.isAndroid = false
}
},
stopPageTimers() {
if (this.liveTimer) {
clearInterval(this.liveTimer)
this.liveTimer = null
}
if (this.unreadTimer) {
clearInterval(this.unreadTimer)
this.unreadTimer = null
}
if (this.countTimer) {
clearInterval(this.countTimer)
this.countTimer = null
}
if (this.galaxyTimer) {
clearInterval(this.galaxyTimer)
this.galaxyTimer = null
}
if (this.moodTypeTimer) {
clearInterval(this.moodTypeTimer)
this.moodTypeTimer = null
}
},
async refreshUnreadCount() {
const count = await getIeUnreadCount()
if (count === null || count === undefined) return
this.unreadCount = Number(count) || 0
},
randomBetween(min, max) {
return min + Math.floor(Math.random() * (max - min + 1))
},
wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
},
randomAwakeCount(onlineCount) {
const count = Number(onlineCount) || 0
if (count > 1000) {
const min = Math.floor(count / 1000) * 1000
return this.randomBetween(min, min + 999)
}
return this.randomBetween(1000, 2000)
},
animateAwakeCount(target) {
if (this.countTimer) {
clearInterval(this.countTimer)
}
if (this.lowPowerMode) {
this.displayAwakeCount = target
return
}
const start = this.displayAwakeCount
const diff = target - start
let step = 0
this.countTimer = setInterval(() => {
step += 1
const progress = Math.min(step / 10, 1)
const eased = 1 - Math.pow(1 - progress, 3)
this.displayAwakeCount = Math.round(start + diff * eased)
if (progress >= 1) {
clearInterval(this.countTimer)
this.countTimer = null
}
}, 42)
},
pickCompanion() {
const list = this.companions[this.currentMode]
this.matchedPerson = list[Math.floor(Math.random() * list.length)]
},
toggleLowPowerMode() {
this.lowPowerMode = !this.lowPowerMode
uni.setStorageSync('ieLowPowerMode', this.lowPowerMode ? '1' : '0')
this.stopPageTimers()
this.startPageTimers()
uni.showToast({ title: this.lowPowerMode ? '已开启流畅模式' : '已开启氛围动效', icon: 'none' })
},
loadDailyQuestion() {
getIeDailyQuestion().then(res => {
if (res) this.dailyQuestion = res
}).catch(() => {})
},
goDailyQuestion() {
uni.navigateTo({ url: '/package1/ieBrowser/dailyQuestion' })
},
toggleIntent(key) {
this.matchIntent = this.matchIntent === key ? '' : key
},
setTargetMode(key) {
this.targetMode = key
this.syncStatus()
},
setTargetGender(key) {
this.targetGender = key
this.syncStatus()
},
intentLabel(key) {
const item = this.intents.find(intent => intent.key === key)
return item ? item.label : '搭子'
},
goGroupBuy() {
this.showMatch = false
uni.navigateTo({ url: '/package2/group/groupBuyList' })
},
async loadHome() {
const home = await ieHome()
if (home && home.banned) {
this.redirectBannedIe(home.message)
return
}
if (!home) return
if (home.profileCompleted !== 1) {
uni.redirectTo({ url: '/package1/ieBrowser/profileSetup' })
return
}
this.profile = home.profile || {}
this.customCompanionIntent = this.profile.companionIntent || this.customCompanionIntent
this.editCompanionIntent = this.customCompanionIntent
this.profileReady = true
this.onlineCountBase = Number(home.onlineCount) || 0
this.awakeCount = this.randomAwakeCount(this.onlineCountBase)
this.waitingCount = home.waitingCount || this.waitingCount
this.chancesLeft = Math.max((home.dailyQuota || 20) - (home.usedQuota || 0), 0)
this.unreadCount = home.unreadCount || 0
this.currentMode = home.currentMode || this.currentMode
this.currentMood = home.currentMood || this.currentMood
this.targetMode = home.targetModePreference || this.targetMode
this.targetGender = home.targetGenderPreference || this.targetGender
this.driftMessages = this.buildDriftMessages(home)
this.animateAwakeCount(this.awakeCount)
this.syncStatus()
},
buildDriftMessages(home = {}) {
const realItems = (home.companionIntents || []).filter(item => item && item.text).slice(0, 50).map((item, index) => ({
type: item.type || (index % 2 === 0 ? 'i' : 'e'),
mood: item.mood || this.moods[index % this.moods.length].key,
text: item.text,
userId: item.userId,
anonymousName: item.anonymousName || '',
avatarText: item.avatarText || '',
avatarUrl: item.avatarUrl || '',
isSelf: !!item.isSelf,
isUserIntent: true
}))
const fillTexts = (home.hotStatuses && home.hotStatuses.length ? home.hotStatuses : this.presetDriftMessages.map(item => item.text))
const fillItems = fillTexts.map((text, index) => ({
type: index % 2 === 0 ? 'i' : 'e',
mood: this.moods[index % this.moods.length].key,
text,
isUserIntent: false
}))
return realItems.concat(fillItems).slice(0, 50)
},
redirectBannedIe(message) {
if (this.ieBannedRedirecting) return
this.ieBannedRedirecting = true
this.stopPageTimers()
this.matching = false
this.showMatch = false
uni.showToast({
title: message || '你的 i/e 身份已被封禁',
icon: 'none',
duration: 1800
})
setTimeout(() => {
uni.switchTab({
url: '/pages/index/index',
fail: () => {
uni.reLaunch({ url: '/pages/index/index' })
}
})
}, 1800)
},
syncStatus() {
if (!this.profileReady) return
const areaInfo = this.getAreaInfo()
updateIeStatus({
mode: this.currentMode,
targetMode: this.targetMode,
targetGender: this.targetGender,
mood: this.currentMood,
statusText: this.activeMood.copy,
...areaInfo,
interestTags: [this.activeMood.label]
})
},
getAreaInfo() {
try {
const area = JSON.parse(uni.getStorageSync('area') || '{}')
return {
regionId: area.id || '',
regionName: area.title || ''
}
} catch (e) {
return { regionId: '', regionName: '' }
}
},
async openMatch() {
if (this.matching) return
this.showTargetPanel = false
if (this.chancesLeft === 0) {
uni.showToast({ title: this.doneText, icon: 'none' })
return
}
const areaInfo = this.getAreaInfo()
if (this.matchScope === 'school' && !areaInfo.regionId) {
uni.showToast({ title: '请先选择校区', icon: 'none' })
return
}
this.matching = true
try {
const matchAnimation = this.wait(2000)
const match = await startIeMatch({
mode: this.currentMode,
targetMode: this.targetMode,
targetGender: this.targetGender,
matchScope: this.matchScope,
intent: this.matchIntent || '',
fuzzyKeyword: this.fuzzyKeyword.trim(),
mood: this.currentMood,
...areaInfo,
interestTags: [this.activeMood.label]
})
await matchAnimation
if (!match || !match.roomId) {
uni.showToast({ title: (match && match.failReason) || '暂时没有同频的人', icon: 'none' })
return
}
this.applyMatchResult(match)
// 匹配成功即消耗一次机会,与服务端 usedQuota 保持一致
this.chancesLeft = Math.max(this.chancesLeft - 1, 0)
this.showMatch = true
} finally {
this.matching = false
}
},
applyMatchResult(match) {
this.currentMatch = match
this.matchedPerson = {
avatar: match.avatarText || '◌',
avatarUrl: match.avatarUrl || '',
name: match.anonymousName || '半匿名漂流者',
mode: match.mode || this.currentMode,
mood: match.mood || 'quiet',
moodIcon: this.moodIcon(match.mood || 'quiet'),
moodText: this.moodText(match.mood || 'quiet'),
gender: match.gender || '',
regionName: match.regionName || '',
tags: match.interestTags || [],
personaImages: match.personaImages || [],
state: match.stateText || '也在等一句轻轻的回应',
quote: match.companionIntent || match.quoteText || '可以先安静待一会,不用急着找话题。',
lastActiveText: match.lastActiveText || ''
}
},
modeText(mode) {
return mode === 'e' ? 'e 人' : 'i 人'
},
moodIcon(key) {
const item = this.moods.find(mood => mood.key === key)
return item ? item.icon : '🌙'
},
moodText(key) {
const item = this.moods.find(mood => mood.key === key)
return item ? `此刻${item.label}` : '此刻想安静'
},
moodEmoji(item) {
const map = { quiet: '🌙', talk: '💭', listen: '🎧', drift: '🫧' }
return map[item.mood] || (item.type === 'e' ? '⚡' : '🌱')
},
genderText(gender) {
if (gender === 'male') return '男生'
if (gender === 'female') return '女生'
return '性别未设置'
},
previewMatchedPersona(index) {
const images = this.matchedPerson.personaImages || []
if (!images.length) return
uni.previewImage({ urls: images, current: images[index] })
},
closeMatch() { this.showMatch = false },
showTicketTip() {
uni.showToast({ title: '平台下单,订单完成可获得更多漂流票哦', icon: 'none' })
},
toggleMode() {
this.currentMode = this.currentMode === 'i' ? 'e' : 'i'
this.syncStatus()
},
changeMood(key) {
this.currentMood = key
this.playMoodCopy(this.activeMood.copy)
this.syncStatus()
},
async selectDrift(item) {
if (item && item.isUserIntent && item.userId) {
if (item.isSelf) {
uni.showToast({ title: '这是你发布的内容,不能匹配自己', icon: 'none' })
return
}
await this.matchDriftUser(item)
}
},
async matchDriftUser(item) {
if (this.matching) return
this.matching = true
try {
const match = await matchIeProfile(item.userId)
if (!match || !match.roomId) {
uni.showToast({ title: (match && match.failReason) || '暂时不能发起聊天', icon: 'none' })
return
}
this.applyMatchResult(match)
this.showMatch = true
} finally {
this.matching = false
}
},
playMoodCopy(text) {
if (this.moodTypeTimer) {
clearInterval(this.moodTypeTimer)
this.moodTypeTimer = null
}
const chars = Array.from(text)
let index = 0
this.displayMoodCopy = ''
this.moodTypeTimer = setInterval(() => {
index += 1
this.displayMoodCopy = chars.slice(0, index).join('')
if (index >= chars.length) {
clearInterval(this.moodTypeTimer)
this.moodTypeTimer = null
}
}, 42)
},
replyDrift() {
uni.showToast({ title: '可以从一句话开始', icon: 'none' })
},
likeDrift() {
uni.showToast({ title: '已轻轻回应', icon: 'none' })
},
openProfile() {
this.goUniverse()
},
openTargetPanel() {
this.showTargetPanel = true
},
closeTargetPanel() {
this.showTargetPanel = false
},
openCompanionEditor() {
this.editCompanionIntent = this.customCompanionIntent || ''
this.showCompanionEditor = true
},
closeCompanionEditor() {
if (this.savingCompanionIntent) return
this.keyboardHeight = 0
this.showCompanionEditor = false
},
onCompanionInputFocus(event) {
const height = event && event.detail ? Number(event.detail.height) || 0 : 0
this.keyboardHeight = height
},
onCompanionInputBlur() {
this.keyboardHeight = 0
},
async saveCompanionIntent() {
if (this.savingCompanionIntent) return
const text = this.editCompanionIntent.trim()
if (!text) {
uni.showToast({ title: '先写一句想找什么搭子', icon: 'none' })
return
}
this.savingCompanionIntent = true
uni.showLoading({ title: '保存中...', mask: true })
try {
const saved = await saveIeProfile({
...this.profile,
companionIntent: text
})
if (!saved) return
this.profile = saved
this.customCompanionIntent = saved.companionIntent || text
this.editCompanionIntent = this.customCompanionIntent
this.showCompanionEditor = false
uni.showToast({ title: '已发布', icon: 'none' })
this.loadHome()
} finally {
this.savingCompanionIntent = false
uni.hideLoading()
}
},
closeProfile() {
this.showProfile = false
},
getGalaxyPoint(event) {
const touch = (event.touches && event.touches[0]) || (event.changedTouches && event.changedTouches[0])
if (!touch) return null
const x = touch.pageX !== undefined ? touch.pageX : touch.clientX
const y = touch.pageY !== undefined ? touch.pageY : touch.clientY
if (x === undefined || y === undefined) return null
return { x, y }
},
startGalaxyDrag(event) {
const point = this.getGalaxyPoint(event)
if (!point) return
this.galaxyDragging = true
this.galaxyMoved = false
this.galaxyStartX = point.x
this.galaxyStartY = point.y
},
moveGalaxyDrag(event) {
if (!this.galaxyDragging) return
const now = Date.now()
if (now - this.galaxyLastMoveTime < 32) return
this.galaxyLastMoveTime = now
const point = this.getGalaxyPoint(event)
if (!point) return
const deltaX = point.x - this.galaxyStartX
const deltaY = point.y - this.galaxyStartY
if (Math.abs(deltaX) + Math.abs(deltaY) > 4) {
this.galaxyMoved = true
}
this.galaxyRotateY = (this.galaxyRotateY + deltaX * 0.82) % 360
this.galaxyRotateX = Math.max(-48, Math.min(48, this.galaxyRotateX - deltaY * 0.28))
this.galaxyStartX = point.x
this.galaxyStartY = point.y
},
endGalaxyDrag() {
if (!this.galaxyDragging) return
this.galaxyDragging = false
},
skipMatch() {
this.showMatch = false
uni.showToast({ title: "这次轻轻划过了", icon: 'none' })
},
goChat() {
this.showMatch = false
const room = this.currentMatch ? '&roomId=' + this.currentMatch.roomId + '&targetUserId=' + this.currentMatch.targetUserId +
'&name=' + encodeURIComponent(this.matchedPerson.name || '') +
'&avatar=' + encodeURIComponent(this.matchedPerson.avatar || '') +
'&avatarUrl=' + encodeURIComponent(this.matchedPerson.avatarUrl || '') +
'&quote=' + encodeURIComponent(this.matchedPerson.quote || '') : ''
uni.navigateTo({ url: '/package1/ieBrowser/chat?mode=' + this.currentMode + '&mood=' + this.currentMood + room })
},
async goMatch() {
await this.openMatch()
},
goMatchPage() {
uni.navigateTo({ url: '/package1/ieBrowser/match?mode=' + this.currentMode + '&mood=' + this.currentMood + '&targetMode=' + this.targetMode + '&targetGender=' + this.targetGender + '&matchScope=' + this.matchScope + '&intent=' + (this.matchIntent || '') })
},
goRecords() { uni.navigateTo({ url: '/package1/ieBrowser/chatList' }) },
goArchive() { uni.navigateTo({ url: '/package1/ieBrowser/universe' }) },
goFate() { uni.navigateTo({ url: '/package1/ieBrowser/fate' }) },
goMessages() { uni.navigateTo({ url: '/package1/ieBrowser/messages?unreadCount=' + encodeURIComponent(String(this.unreadCount || 0)) }) },
goUniverse() { uni.navigateTo({ url: '/package1/ieBrowser/universe?unreadCount=' + encodeURIComponent(String(this.unreadCount || 0)) }) },
backHome() { uni.switchTab({ url: '/pages/index/index' }) }
}
}
</script>
<style lang="scss" scoped>
page {
background: #f7f9ff;
}
/* ============ 页面底色:通透极光星系 ============ */
.ie-page {
position: relative;
min-height: 100vh;
padding: 0 32rpx 230rpx;
box-sizing: border-box;
color: #161b2e;
overflow: hidden;
display: flex;
flex-direction: column;
background:
radial-gradient(circle at 12% 8%, rgba(221, 231, 255, .9), transparent 320rpx),
radial-gradient(circle at 90% 16%, rgba(169, 255, 231, .5), transparent 340rpx),
radial-gradient(circle at 16% 66%, rgba(255, 214, 165, .42), transparent 360rpx),
radial-gradient(circle at 88% 76%, rgba(255, 184, 209, .3), transparent 400rpx),
linear-gradient(180deg, #fbfdff 0%, #eef4ff 56%, #fff5ea 100%);
}
.android-home {
height: 100vh;
padding-bottom: 232rpx;
}
/* 星点纹理 */
.ie-page::before {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
opacity: .1;
background-image:
radial-gradient(circle, rgba(22, 27, 46, .4) 0 1rpx, transparent 1rpx),
radial-gradient(circle, rgba(108, 92, 231, .3) 0 1rpx, transparent 1rpx);
background-size: 46rpx 46rpx, 74rpx 74rpx;
}
.aurora {
position: absolute;
border-radius: 50%;
filter: blur(60rpx);
pointer-events: none;
animation: auroraDrift 10s ease-in-out infinite;
}
.aurora-a {
top: 140rpx;
right: -120rpx;
width: 380rpx;
height: 380rpx;
background: rgba(162, 155, 254, .3);
}
.aurora-b {
left: -160rpx;
top: 560rpx;
width: 420rpx;
height: 420rpx;
background: rgba(169, 255, 231, .4);
animation-delay: 2s;
}
.aurora-c {
right: -100rpx;
bottom: 240rpx;
width: 340rpx;
height: 340rpx;
background: rgba(255, 184, 209, .3);
animation-delay: 4s;
}
/* ============ 顶部悬浮条 ============ */
.float-bar {
position: relative;
z-index: 5;
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 14rpx;
}
.home-back {
display: flex;
align-items: center;
height: 58rpx;
padding: 0 22rpx 0 12rpx;
border: 1rpx solid rgba(255, 255, 255, .88);
border-radius: 999rpx;
color: rgba(22, 27, 46, .66);
background: rgba(255, 255, 255, .66);
backdrop-filter: blur(20rpx);
box-shadow: 0 14rpx 36rpx rgba(96, 112, 160, .12), inset 0 1rpx 0 rgba(255, 255, 255, .95);
font-size: 23rpx;
font-weight: 800;
}
.home-back:active {
transform: scale(.95);
background: rgba(169, 255, 231, .7);
}
.home-back-icon {
margin-right: 6rpx;
padding-bottom: 6rpx;
font-size: 40rpx;
line-height: 50rpx;
font-weight: 400;
}
.perf-toggle {
flex-shrink: 0;
height: 52rpx;
line-height: 52rpx;
padding: 0 20rpx;
border-radius: 999rpx;
color: rgba(22, 27, 46, .58);
background: rgba(255, 255, 255, .66);
border: 1rpx solid rgba(255, 255, 255, .88);
box-shadow: 0 10rpx 26rpx rgba(96, 112, 160, .1);
font-size: 21rpx;
font-weight: 800;
}
.perf-toggle.active {
color: #11162a;
background: #a9ffe7;
}
/* ============ Hero 海报大字区 ============ */
.hero {
position: relative;
z-index: 4;
margin-top: 20rpx;
animation: riseIn .5s ease both;
}
.hero-top {
display: flex;
align-items: center;
gap: 16rpx;
}
.hero-eyebrow {
display: inline-block;
padding: 8rpx 22rpx;
border-radius: 999rpx;
color: #5b4fd6;
background: rgba(139, 124, 255, .12);
font-size: 22rpx;
font-weight: 800;
letter-spacing: 2rpx;
}
.hero-statement {
margin-top: 14rpx;
font-size: 38rpx;
line-height: 54rpx;
font-weight: 900;
letter-spacing: -1rpx;
color: #161b2e;
}
.hero-statement.second {
margin-top: 0;
}
.hero-num {
display: inline-block;
margin: 0 6rpx;
padding-right: 4rpx;
font-size: 54rpx;
font-weight: 900;
color: #6c5ce7;
background: linear-gradient(115deg, #6c5ce7 10%, #00cba9 55%, #ff7eb3 95%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
animation: countPop .42s ease;
}
.hero-live {
display: inline-flex;
align-items: center;
max-width: 100%;
margin-top: 12rpx;
padding: 8rpx 22rpx;
border-radius: 999rpx;
color: rgba(22, 27, 46, .62);
background: rgba(255, 255, 255, .6);
border: 1rpx solid rgba(255, 255, 255, .88);
backdrop-filter: blur(16rpx);
box-shadow: inset 0 1rpx 0 rgba(255, 255, 255, .92), 0 10rpx 28rpx rgba(96, 112, 160, .08);
font-size: 23rpx;
font-weight: 600;
}
.daily-live {
box-sizing: border-box;
gap: 10rpx;
padding-right: 18rpx;
}
.live-dot {
width: 14rpx;
height: 14rpx;
border-radius: 50%;
background: #2bd9a4;
animation: liveDotPulse 1.8s ease-out infinite;
}
.daily-live-tag,
.daily-live-go {
flex-shrink: 0;
font-weight: 900;
}
.daily-live-tag {
color: #6c5ce7;
}
.daily-live-text {
flex: 1;
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: rgba(22, 27, 46, .66);
}
.daily-live-go {
color: #8b7cff;
font-size: 21rpx;
}
.hero-profile-line {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14rpx;
margin-top: 14rpx;
}
.hero-profile {
display: flex;
align-items: center;
width: fit-content;
max-width: 600rpx;
min-width: 0;
padding: 10rpx 22rpx 10rpx 10rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, .56);
border: 1rpx solid rgba(255, 255, 255, .82);
box-shadow: 0 14rpx 40rpx rgba(96, 112, 160, .1);
transition: transform .16s ease;
}
.say-intent-btn {
flex-shrink: 0;
height: 58rpx;
line-height: 58rpx;
padding: 0 22rpx;
border-radius: 999rpx;
color: #5b4fd6;
background: rgba(255, 255, 255, .62);
border: 1rpx solid rgba(255, 255, 255, .9);
box-shadow: 0 12rpx 30rpx rgba(96, 112, 160, .1);
font-size: 22rpx;
font-weight: 900;
}
.say-intent-btn:active {
transform: scale(.96);
background: rgba(169, 255, 231, .76);
}
.hero-profile:active {
transform: scale(.97);
}
.profile-avatar,
.profile-avatar-img {
width: 64rpx;
height: 64rpx;
margin-right: 16rpx;
border-radius: 50%;
flex-shrink: 0;
}
.profile-avatar {
text-align: center;
line-height: 64rpx;
color: #11162a;
background: linear-gradient(145deg, #effffb, #a9ffe7);
font-size: 26rpx;
font-weight: 800;
}
.profile-name {
font-size: 26rpx;
font-weight: 800;
}
.profile-tags {
margin-top: 4rpx;
color: rgba(22, 27, 46, .44);
font-size: 20rpx;
}
.profile-arrow {
margin-left: 18rpx;
color: rgba(22, 27, 46, .35);
font-size: 36rpx;
font-weight: 300;
}
/* ============ 漂流星域:弹幕 + 星球 ============ */
.lower-stack {
position: relative;
z-index: 3;
}
.android-home .lower-stack {
transform: translateY(28rpx);
}
.drift-field {
position: relative;
z-index: 3;
height: 470rpx;
margin: 36rpx -32rpx 0;
}
.spark {
position: absolute;
z-index: 1;
color: rgba(108, 92, 231, .55);
font-size: 24rpx;
animation: sparkTwinkle 2.6s ease-in-out infinite;
pointer-events: none;
}
.spark-a { left: 92rpx; top: 168rpx; }
.spark-b { right: 70rpx; top: 120rpx; color: rgba(255, 158, 194, .7); font-size: 30rpx; animation-delay: .8s; }
.spark-c { right: 160rpx; bottom: 70rpx; color: rgba(43, 217, 164, .7); animation-delay: 1.6s; }
.spark-d { left: 60rpx; bottom: 150rpx; color: rgba(255, 214, 165, .9); font-size: 28rpx; animation-delay: 2.2s; }
/* 弹幕轨道:上下留出余量,避免倾斜的气泡被裁切 */
.danmu-lane {
position: absolute;
left: 0;
right: 0;
z-index: 2;
padding: 14rpx 0;
overflow: hidden;
}
.lane-0 { top: 0; }
.lane-1 { top: 188rpx; z-index: 1; }
.lane-2 { bottom: 0; }
.lane-track {
display: flex;
align-items: center;
width: max-content;
animation: laneScroll 26s linear infinite;
}
.lane-1 .lane-track {
animation-duration: 34s;
animation-direction: reverse;
opacity: .85;
}
.lane-2 .lane-track {
animation-duration: 22s;
}
.danmu-item {
display: flex;
align-items: center;
flex-shrink: 0;
margin-right: 56rpx;
padding: 14rpx 26rpx;
border-radius: 999rpx;
border: 1rpx solid rgba(255, 255, 255, .9);
transition: transform .16s ease;
}
.danmu-item:active {
transform: scale(1.06);
}
.danmu-item.tint-0 { background: linear-gradient(135deg, rgba(217, 250, 240, .96), rgba(255, 255, 255, .9)); transform: rotate(-1.6deg); }
.danmu-item.tint-1 { background: linear-gradient(135deg, rgba(228, 235, 255, .96), rgba(255, 255, 255, .9)); transform: rotate(1.4deg); }
.danmu-item.tint-2 { background: linear-gradient(135deg, rgba(255, 235, 211, .96), rgba(255, 255, 255, .9)); transform: rotate(-1.2deg); }
.danmu-item.tint-3 { background: linear-gradient(135deg, rgba(255, 226, 237, .96), rgba(255, 255, 255, .9)); transform: rotate(1.8deg); }
.danmu-emoji {
margin-right: 12rpx;
font-size: 28rpx;
line-height: 1;
}
.danmu-text {
color: rgba(22, 27, 46, .76);
font-size: 24rpx;
font-weight: 600;
white-space: nowrap;
}
/* 中央星球 */
.signal-planet {
position: absolute;
left: 50%;
top: 50%;
z-index: 5;
width: 410rpx;
height: 410rpx;
perspective: 1180rpx;
}
.galaxy-breath {
position: absolute;
inset: 0;
animation: galaxyBreath 4.2s ease-in-out infinite;
}
.galaxy-body {
position: absolute;
left: 50%;
top: 50%;
width: 260rpx;
height: 260rpx;
transform: translate(-50%, -50%) translateZ(34rpx);
border-radius: 47% 53% 44% 56% / 56% 42% 58% 44%;
background:
radial-gradient(circle at 28% 24%, rgba(255, 255, 255, .94), rgba(255, 255, 255, 0) 38%),
radial-gradient(circle at 70% 74%, rgba(162, 155, 254, .66), rgba(162, 155, 254, 0) 46%),
linear-gradient(135deg, rgba(169, 255, 231, .78), rgba(221, 231, 255, .76) 54%, rgba(255, 184, 209, .52));
box-shadow:
0 36rpx 96rpx rgba(108, 92, 231, .22),
inset 10rpx 12rpx 34rpx rgba(255, 255, 255, .64),
inset -20rpx -22rpx 42rpx rgba(108, 92, 231, .18);
overflow: hidden;
z-index: 12;
animation: blobMorph 9s ease-in-out infinite;
}
.galaxy-body::before {
content: '';
position: absolute;
left: 28rpx;
top: 22rpx;
width: 108rpx;
height: 86rpx;
border-radius: 50%;
background: radial-gradient(circle, rgba(255, 255, 255, .85), rgba(255, 255, 255, 0) 68%);
filter: blur(2rpx);
z-index: 1;
}
.galaxy-ring {
position: absolute;
left: 50%;
top: 50%;
border-radius: 50%;
pointer-events: none;
}
.ring-main {
width: 480rpx;
height: 148rpx;
margin-left: -240rpx;
margin-top: -74rpx;
border: 12rpx solid rgba(111, 231, 224, .36);
box-shadow: 0 0 34rpx rgba(169, 255, 231, .32), inset 0 0 22rpx rgba(255, 255, 255, .46);
transform: rotateX(66deg) rotateZ(-14deg);
animation: ringDrift 18s linear infinite;
}
.ring-second {
width: 396rpx;
height: 120rpx;
margin-left: -198rpx;
margin-top: -60rpx;
border: 4rpx solid rgba(162, 155, 254, .38);
transform: rotateX(72deg) rotateZ(24deg) translateZ(24rpx);
animation: ringDrift 24s linear infinite reverse;
}
.ring-third {
width: 318rpx;
height: 318rpx;
margin-left: -159rpx;
margin-top: -159rpx;
border: 3rpx solid rgba(255, 214, 165, .36);
transform: rotateX(18deg) rotateY(64deg);
animation: ringDrift 30s linear infinite;
}
.orbit-planet {
position: absolute;
left: 50%;
top: 50%;
border-radius: 50%;
pointer-events: none;
box-shadow:
0 0 18rpx rgba(255, 255, 255, .72),
0 0 34rpx currentColor,
inset 3rpx 4rpx 8rpx rgba(255, 255, 255, .68),
inset -4rpx -5rpx 10rpx rgba(108, 92, 231, .18);
transition: transform .16s linear, opacity .16s linear;
}
.orbit-planet::after {
content: '';
position: absolute;
left: 18%;
top: 16%;
width: 34%;
height: 28%;
border-radius: 50%;
background: rgba(255, 255, 255, .86);
filter: blur(1rpx);
}
.liquid {
position: absolute;
border-radius: 45%;
filter: blur(2rpx);
}
.liquid-a {
left: -46rpx;
top: 16rpx;
width: 250rpx;
height: 226rpx;
background: rgba(169, 255, 231, .58);
animation: liquidMove 7s ease-in-out infinite;
}
.liquid-b {
right: -60rpx;
bottom: -20rpx;
width: 238rpx;
height: 232rpx;
background: rgba(139, 124, 255, .28);
animation: liquidMove 8s ease-in-out infinite reverse;
}
.soul-particle {
position: absolute;
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: rgba(255, 255, 255, .86);
box-shadow: 0 0 18rpx rgba(255, 255, 255, .8);
z-index: 2;
animation: soulFloat 5s ease-in-out infinite;
}
.soul-a { left: 78rpx; top: 66rpx; }
.soul-b { right: 70rpx; top: 116rpx; animation-delay: 1.2s; }
.soul-c { left: 142rpx; bottom: 70rpx; animation-delay: 2s; }
.planet-core {
position: relative;
z-index: 2;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
.planet-text {
font-size: 32rpx;
line-height: 44rpx;
font-weight: 800;
color: #161b2e;
white-space: nowrap;
}
.planet-sub {
margin-top: 8rpx;
color: rgba(22, 27, 46, .5);
font-size: 23rpx;
animation: subBreath 3s ease-in-out infinite;
}
.planet-cta {
margin-top: 16rpx;
padding: 8rpx 20rpx;
border-radius: 999rpx;
color: #5b4fd6;
background: rgba(255, 255, 255, .78);
box-shadow: 0 8rpx 22rpx rgba(108, 92, 231, .2), inset 0 1rpx 0 rgba(255, 255, 255, .95);
font-size: 20rpx;
font-weight: 800;
letter-spacing: 1rpx;
animation: ctaBreath 2.2s ease-in-out infinite;
}
/* ============ 底部功能面板 ============ */
.home-action-panel {
position: relative;
z-index: 4;
margin-top: 26rpx;
padding: 14rpx 14rpx 18rpx;
border-radius: 32rpx;
background:
linear-gradient(180deg, rgba(255, 255, 255, .82), rgba(255, 255, 255, .68)),
radial-gradient(circle at 16% 0%, rgba(169, 255, 231, .28), transparent 220rpx),
radial-gradient(circle at 88% 22%, rgba(162, 155, 254, .2), transparent 240rpx);
border: 1rpx solid rgba(255, 255, 255, .9);
backdrop-filter: blur(22rpx);
box-shadow: 0 22rpx 70rpx rgba(96, 112, 160, .14), inset 0 1rpx 0 rgba(255, 255, 255, .92);
}
.android-home .drift-field {
margin-top: 22rpx;
}
.android-home .home-action-panel {
margin-top: 18rpx;
}
.play-deck {
display: flex;
align-items: stretch;
top: 12rpx;
gap: 12rpx;
}
.ticket {
flex: 1.3;
display: flex;
align-items: stretch;
min-height: 94rpx;
border-radius: 24rpx;
overflow: hidden;
background: #ffffff;
box-shadow: 0 12rpx 30rpx rgba(96, 112, 160, .12);
transition: transform .16s ease;
}
.ticket:active {
transform: scale(.95);
}
.ticket-left {
flex: 1;
display: flex;
align-items: center;
gap: 10rpx;
padding: 0 12rpx 0 18rpx;
background: linear-gradient(135deg, #dffef4, #aef5dd);
}
.ticket-icon {
font-size: 34rpx;
}
.ticket-label {
color: #0e4a35;
font-size: 22rpx;
font-weight: 900;
letter-spacing: 1rpx;
white-space: nowrap;
}
.ticket-divider {
position: relative;
width: 0;
margin: 14rpx 0;
border-left: 3rpx dashed rgba(22, 27, 46, .18);
}
.ticket-divider::before,
.ticket-divider::after {
content: '';
position: absolute;
left: -11rpx;
width: 20rpx;
height: 20rpx;
border-radius: 50%;
background: #f1f5fd;
}
.ticket-divider::before { top: -24rpx; }
.ticket-divider::after { bottom: -24rpx; }
.ticket-right {
display: flex;
align-items: center;
justify-content: center;
gap: 6rpx;
min-width: 106rpx;
padding: 0 16rpx;
}
.ticket-num {
font-size: 46rpx;
font-weight: 900;
color: #6c5ce7;
background: linear-gradient(120deg, #6c5ce7, #00b894);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.ticket-unit {
color: rgba(22, 27, 46, .45);
font-size: 20rpx;
font-weight: 700;
}
.target-btn {
flex: 1;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4rpx;
min-height: 94rpx;
border-radius: 24rpx;
overflow: hidden;
color: #fff;
background: linear-gradient(135deg, #6c5ce7, #a29bfe);
box-shadow: 0 14rpx 34rpx rgba(108, 92, 231, .28);
transition: transform .16s ease;
animation: entryWiggle 5s ease-in-out infinite;
}
.target-btn:active {
transform: scale(.95);
}
.target-star {
position: absolute;
right: 16rpx;
top: 10rpx;
color: #ffe28b;
font-size: 26rpx;
animation: sparkTwinkle 1.6s ease-in-out infinite;
}
.target-text {
font-size: 27rpx;
font-weight: 900;
letter-spacing: 1rpx;
}
.target-sub {
color: rgba(255, 255, 255, .72);
font-size: 18rpx;
font-weight: 700;
}
/* ============ 心情星球带 ============ */
.daily-q-bar {
position: relative;
z-index: 4;
display: flex;
align-items: center;
gap: 12rpx;
margin-top: 28rpx;
padding: 22rpx 18rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, .76);
border: 1rpx solid rgba(255, 255, 255, .8);
box-shadow: 0 10rpx 26rpx rgba(96, 112, 160, .1);
}
.daily-q-bar:active {
transform: scale(.98);
}
.mood-copy-bar {
justify-content: center;
min-height: 58rpx;
color: rgba(22, 27, 46, .62);
font-size: 23rpx;
line-height: 32rpx;
text-align: center;
font-weight: 800;
}
.daily-q-tag {
flex-shrink: 0;
padding: 6rpx 16rpx;
border-radius: 999rpx;
font-size: 20rpx;
font-weight: 800;
color: #fff;
background: linear-gradient(135deg, #7a4dff, #ff7eb3);
}
.daily-q-text {
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 24rpx;
font-weight: 700;
color: rgba(22, 27, 46, .76);
}
.daily-q-go {
flex-shrink: 0;
font-size: 22rpx;
font-weight: 800;
color: #7a4dff;
}
.mood-strip {
position: relative;
z-index: 4;
margin-top: 22rpx;
padding: 2rpx 4rpx 0;
}
.strip-tag {
display: inline-block;
padding: 8rpx 22rpx;
border-radius: 999rpx;
color: #7a4b12;
background: linear-gradient(135deg, #ffe9c9, #ffd6a5);
box-shadow: 0 10rpx 24rpx rgba(255, 195, 130, .3);
font-size: 22rpx;
font-weight: 900;
letter-spacing: 1rpx;
transform: rotate(-2deg);
}
.mood-orbs {
display: flex;
justify-content: space-between;
padding: 0 8rpx;
}
.mood-orb {
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
}
.orb-face {
position: relative;
width: 94rpx;
height: 94rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: #ffffff;
border: 1rpx solid rgba(255, 255, 255, .92);
box-shadow: 0 12rpx 28rpx rgba(96, 112, 160, .14), inset 0 -8rpx 14rpx rgba(139, 124, 255, .07);
transition: transform .2s cubic-bezier(.34, 1.56, .64, 1), background .24s ease, box-shadow .24s ease;
}
.mood-orb:active .orb-face {
transform: scale(.86);
}
.mood-orb.active .orb-face {
background: linear-gradient(145deg, #eafff8, #a9ffe7);
box-shadow: 0 16rpx 36rpx rgba(43, 217, 164, .34);
transform: translateY(-8rpx) scale(1.1);
}
.mood-orb.active .orb-face::after {
content: '';
position: absolute;
inset: -12rpx;
border-radius: 50%;
border: 3rpx dashed rgba(43, 217, 164, .6);
animation: orbSpin 9s linear infinite;
}
.orb-emoji {
font-size: 44rpx;
line-height: 1;
}
.mood-orb.active .orb-emoji {
animation: emojiBounce .42s cubic-bezier(.34, 1.56, .64, 1);
}
.orb-label {
color: rgba(22, 27, 46, .5);
font-size: 22rpx;
font-weight: 700;
transition: color .2s ease;
}
.mood-orb.active .orb-label {
color: #0e4a35;
font-weight: 900;
}
.say-bubble {
position: relative;
width: fit-content;
max-width: 620rpx;
margin: 24rpx auto 0;
display: flex;
align-items: center;
justify-content: center;
padding: 12rpx 24rpx;
border-radius: 999rpx;
color: rgba(22, 27, 46, .58);
background: rgba(255, 255, 255, .76);
border: 1rpx solid rgba(255, 255, 255, .94);
box-shadow: 0 12rpx 34rpx rgba(139, 124, 255, .1), inset 0 1rpx 0 rgba(255, 255, 255, .95);
font-size: 23rpx;
line-height: 34rpx;
text-align: center;
font-weight: 700;
min-height: 34rpx;
}
.typing-cursor {
display: inline-block;
width: 3rpx;
height: 28rpx;
margin-left: 6rpx;
border-radius: 999rpx;
background: rgba(108, 92, 231, .58);
vertical-align: -4rpx;
animation: typingCursor .86s steps(1) infinite;
}
/* ============ 随机陪伴设置弹层 ============ */
.target-mask {
position: fixed;
inset: 0;
z-index: 99;
display: flex;
align-items: flex-end;
padding: 32rpx 30rpx 158rpx;
box-sizing: border-box;
background: rgba(22, 27, 46, .42);
backdrop-filter: blur(24rpx);
}
.target-popup {
width: 100%;
padding: 34rpx 30rpx 30rpx;
border-radius: 42rpx;
border: 1rpx solid rgba(255, 255, 255, .9);
background:
linear-gradient(135deg, #ffffff, #f9fbff),
radial-gradient(circle at 82% 0%, rgba(169, 255, 231, .28), transparent 220rpx);
box-shadow: 0 34rpx 100rpx rgba(96, 112, 160, .22);
box-sizing: border-box;
animation: targetUp .18s ease-out both;
}
.target-popup-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 28rpx;
}
.target-popup-title {
color: #161b2e;
font-size: 34rpx;
font-weight: 900;
}
.target-popup-sub {
margin-top: 8rpx;
color: rgba(22, 27, 46, .46);
font-size: 23rpx;
}
.target-close {
width: 58rpx;
height: 58rpx;
border-radius: 50%;
text-align: center;
line-height: 54rpx;
color: rgba(22, 27, 46, .5);
background: rgba(238, 244, 255, .78);
font-size: 36rpx;
font-weight: 700;
}
.target-title {
color: rgba(22, 27, 46, .54);
font-size: 23rpx;
font-weight: 800;
}
.target-row {
display: flex;
margin-top: 18rpx;
}
.intent-row {
flex-wrap: wrap;
gap: 12rpx 0;
}
.target-input {
height: 68rpx;
margin-top: 16rpx;
padding: 0 24rpx;
border-radius: 999rpx;
color: #161b2e;
background: rgba(255, 255, 255, .72);
border: 1rpx solid rgba(255, 255, 255, .9);
box-shadow: inset 0 1rpx 0 rgba(255, 255, 255, .96);
font-size: 23rpx;
font-weight: 700;
}
.search-input {
margin-top: 12rpx;
background: rgba(239, 244, 255, .74);
}
.companion-mask {
position: fixed;
inset: 0;
z-index: 99;
display: flex;
align-items: flex-end;
padding: 0 32rpx 190rpx;
box-sizing: border-box;
background: rgba(22, 27, 46, .2);
backdrop-filter: blur(10rpx);
transition: padding-bottom .18s ease;
}
.companion-editor {
width: 100%;
padding: 34rpx 30rpx 30rpx;
border-radius: 42rpx;
border: 1rpx solid rgba(255, 255, 255, .9);
background: linear-gradient(135deg, rgba(255, 255, 255, .96), rgba(255, 255, 255, .84));
box-shadow: 0 34rpx 100rpx rgba(96, 112, 160, .22);
box-sizing: border-box;
animation: targetUp .18s ease-out both;
}
.companion-editor-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 24rpx;
margin-bottom: 12rpx;
}
.companion-editor-title {
color: #161b2e;
font-size: 34rpx;
font-weight: 900;
}
.companion-editor-sub {
margin-top: 8rpx;
color: rgba(22, 27, 46, .46);
font-size: 23rpx;
line-height: 34rpx;
}
.companion-input {
margin-top: 22rpx;
}
.companion-save {
height: 86rpx;
line-height: 86rpx;
margin-top: 22rpx;
border-radius: 999rpx;
text-align: center;
color: #11162a;
background: linear-gradient(135deg, #effffb, #a9ffe7 62%, #ffd6a5);
box-shadow: 0 18rpx 46rpx rgba(169, 255, 231, .3);
font-size: 28rpx;
font-weight: 900;
}
.companion-save.disabled {
opacity: .62;
}
.intent-chip.active {
color: #fff;
background: linear-gradient(135deg, #ff8a54, #ff5f8f);
box-shadow: 0 10rpx 24rpx rgba(255, 110, 110, .3);
}
.target-chip {
height: 62rpx;
line-height: 62rpx;
padding: 0 24rpx;
margin-right: 14rpx;
border-radius: 999rpx;
color: rgba(22, 27, 46, .52);
background: rgba(238, 244, 255, .78);
font-size: 23rpx;
font-weight: 700;
transition: transform .14s ease;
}
.target-chip:active {
transform: scale(.94);
}
.target-chip.active {
color: #11162a;
background: #a9ffe7;
}
.gender-title {
margin-top: 24rpx;
}
.start-match-btn {
height: 92rpx;
line-height: 92rpx;
margin-top: 26rpx;
border-radius: 999rpx;
text-align: center;
color: #11162a;
background: linear-gradient(135deg, #effffb, #a9ffe7 62%, #ffd6a5);
box-shadow: 0 18rpx 46rpx rgba(169, 255, 231, .3);
font-size: 29rpx;
font-weight: 900;
animation: breathButton 2.8s ease-in-out infinite;
}
.start-match-btn:active {
transform: scale(.97);
}
/* ============ 缘分签弹层 ============ */
.match-mask {
position: fixed;
inset: 0;
z-index: 99;
display: flex;
align-items: flex-end;
padding: 30rpx;
background: rgba(22, 27, 46, .58);
backdrop-filter: blur(24rpx);
box-sizing: border-box;
}
.match-panel {
width: 100%;
max-height: 80vh;
border: 1rpx solid rgba(255, 255, 255, .9);
border-radius: 46rpx;
background: #ffffff;
box-sizing: border-box;
box-shadow: 0 34rpx 110rpx rgba(22, 27, 46, .34);
overflow: hidden;
animation: targetUp .22s ease-out both;
}
.match-scroll {
max-height: 80vh;
box-sizing: border-box;
background: linear-gradient(180deg, #ffffff 0%, #f7f9ff 100%);
}
.match-banner {
position: relative;
padding: 30rpx 34rpx 64rpx;
background:
radial-gradient(circle at 80% 20%, rgba(255, 226, 139, .3), transparent 200rpx),
linear-gradient(135deg, rgba(108, 92, 231, .92), rgba(162, 155, 254, .88));
}
.match-banner-deco {
position: absolute;
right: 90rpx;
top: 26rpx;
color: rgba(255, 255, 255, .55);
font-size: 22rpx;
letter-spacing: 8rpx;
}
.match-label {
color: rgba(255, 255, 255, .92);
font-size: 24rpx;
font-weight: 800;
letter-spacing: 2rpx;
}
.close {
position: absolute;
right: 30rpx;
top: 24rpx;
width: 52rpx;
height: 52rpx;
border-radius: 50%;
text-align: center;
line-height: 48rpx;
color: #fff;
background: rgba(255, 255, 255, .2);
font-size: 36rpx;
}
.match-body {
position: relative;
z-index: 2;
padding: 0 34rpx 34rpx;
background: linear-gradient(180deg, #ffffff 0%, #f7f9ff 100%);
}
.match-avatar-wrap {
position: relative;
z-index: 3;
display: flex;
justify-content: center;
margin-top: -52rpx;
}
.companion-orb,
.companion-orb-img {
width: 148rpx;
height: 148rpx;
border-radius: 50%;
border: 8rpx solid rgba(255, 255, 255, .96);
background: #ffffff;
box-shadow: 0 18rpx 46rpx rgba(108, 92, 231, .24);
box-sizing: border-box;
}
.companion-orb {
text-align: center;
line-height: 132rpx;
font-size: 48rpx;
font-weight: 800;
}
.companion-orb.i {
color: #f7f4ff;
background: linear-gradient(145deg, #8b7cff, #dde7ff);
}
.companion-orb.e {
color: #11162a;
background: linear-gradient(145deg, #a9ffe7, #ffd6a5);
}
.match-name {
margin-top: 18rpx;
text-align: center;
font-size: 36rpx;
font-weight: 900;
}
.match-intent {
margin: 12rpx auto 0;
width: fit-content;
padding: 8rpx 24rpx;
border-radius: 999rpx;
font-size: 23rpx;
font-weight: 800;
color: #ff5f3c;
background: rgba(255, 138, 84, .12);
border: 1rpx solid rgba(255, 138, 84, .32);
}
.match-mood {
margin: 12rpx auto 0;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
width: fit-content;
max-width: 520rpx;
padding: 8rpx 22rpx;
border-radius: 999rpx;
color: #5a55c8;
background: rgba(139, 124, 255, .1);
border: 1rpx solid rgba(139, 124, 255, .18);
font-size: 22rpx;
font-weight: 800;
}
.match-mood-icon {
font-size: 24rpx;
line-height: 1;
}
.match-groupbuy {
margin: -10rpx 34rpx 34rpx;
padding: 18rpx;
border-radius: 22rpx;
text-align: center;
font-size: 24rpx;
font-weight: 800;
color: #7a4b12;
background: linear-gradient(135deg, #ffe9c9, #ffd6a5);
}
.match-online {
margin: 10rpx auto 0;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
width: fit-content;
padding: 6rpx 20rpx;
border-radius: 999rpx;
background: rgba(22, 27, 46, .05);
font-size: 22rpx;
color: rgba(22, 27, 46, .55);
}
.online-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: #c2c8d6;
}
.online-dot.on {
background: #3ddc97;
box-shadow: 0 0 8rpx rgba(61, 220, 151, .8);
}
.match-state {
margin-top: 8rpx;
text-align: center;
color: rgba(22, 27, 46, .48);
font-size: 23rpx;
}
.match-region {
width: fit-content;
max-width: 520rpx;
margin: 14rpx auto 0;
padding: 8rpx 20rpx;
border-radius: 999rpx;
color: #6c69d8;
background: rgba(139, 124, 255, .1);
font-size: 22rpx;
font-weight: 700;
}
.match-tags {
margin-top: 16rpx;
padding: 0 34rpx;
text-align: center;
}
.match-tags text {
display: inline-block;
height: 46rpx;
line-height: 46rpx;
padding: 0 18rpx;
margin: 0 8rpx 10rpx;
border-radius: 999rpx;
color: #6c69d8;
background: rgba(139, 124, 255, .1);
font-size: 21rpx;
font-weight: 700;
}
.match-state-text {
margin-top: 8rpx;
text-align: center;
color: rgba(22, 27, 46, .54);
font-size: 23rpx;
}
.match-quote {
margin: 26rpx 34rpx 0;
padding: 26rpx;
border-radius: 8rpx 28rpx 28rpx 28rpx;
color: rgba(22, 27, 46, .72);
background: rgba(139, 124, 255, .08);
font-size: 26rpx;
line-height: 42rpx;
}
.match-persona {
margin: 22rpx 34rpx 0;
}
.match-section-title {
margin-bottom: 14rpx;
color: rgba(22, 27, 46, .62);
font-size: 24rpx;
font-weight: 800;
}
.match-persona-scroll {
white-space: nowrap;
}
.match-persona-image {
display: inline-block;
width: 168rpx;
height: 208rpx;
margin-right: 16rpx;
border-radius: 26rpx;
background: rgba(22, 27, 46, .06);
box-shadow: 0 14rpx 34rpx rgba(96, 112, 160, .12);
}
.match-actions {
display: flex;
gap: 18rpx;
margin: 30rpx 34rpx 34rpx;
}
.ghost-btn,
.solid-btn {
flex: 1;
height: 90rpx;
border-radius: 999rpx;
text-align: center;
line-height: 90rpx;
font-size: 26rpx;
font-weight: 800;
transition: transform .14s ease;
}
.ghost-btn:active,
.solid-btn:active {
transform: scale(.96);
}
.ghost-btn {
color: rgba(22, 27, 46, .55);
background: rgba(238, 244, 255, .85);
}
.solid-btn {
color: #11162a;
background: linear-gradient(135deg, #a9ffe7, #8ef0ff 60%, #c9c2ff);
box-shadow: 0 16rpx 38rpx rgba(120, 200, 230, .32);
}
/* ============ 个人浮层 ============ */
.profile-mask {
position: fixed;
inset: 0;
z-index: 30;
display: flex;
align-items: flex-end;
padding: 34rpx;
background: rgba(22, 27, 46, .12);
backdrop-filter: blur(18rpx);
box-sizing: border-box;
}
.profile-mini {
width: 100%;
padding: 40rpx 34rpx;
border-radius: 44rpx;
background: rgba(255, 255, 255, .82);
border: 1rpx solid rgba(255, 255, 255, .9);
box-shadow: 0 30rpx 90rpx rgba(96, 112, 160, .18), inset 0 1rpx 0 rgba(255, 255, 255, .96);
text-align: center;
}
.mini-orb {
width: 104rpx;
height: 104rpx;
margin: 0 auto;
border-radius: 50%;
line-height: 104rpx;
color: #fff;
background: linear-gradient(135deg, #6c5ce7, #a29bfe);
font-size: 44rpx;
}
.mini-title {
margin-top: 22rpx;
font-size: 34rpx;
font-weight: 700;
}
.mini-sub {
margin-top: 10rpx;
color: rgba(22, 27, 46, .5);
font-size: 24rpx;
}
.mini-action {
margin-top: 28rpx;
height: 82rpx;
line-height: 82rpx;
border-radius: 999rpx;
color: #161b2e;
background: linear-gradient(135deg, #eafff8, #a9ffe7);
font-size: 26rpx;
font-weight: 700;
}
.creating-mask {
position: fixed;
inset: 0;
z-index: 80;
display: flex;
align-items: center;
justify-content: center;
padding: 48rpx;
box-sizing: border-box;
background: rgba(20, 26, 46, .24);
backdrop-filter: blur(18rpx);
}
.creating-card {
width: 100%;
padding: 52rpx 40rpx 42rpx;
border-radius: 44rpx;
text-align: center;
background: linear-gradient(180deg, rgba(255, 255, 255, .96), rgba(247, 249, 255, .9));
border: 1rpx solid rgba(255, 255, 255, .92);
box-shadow: 0 34rpx 100rpx rgba(42, 50, 86, .22);
box-sizing: border-box;
}
.creating-orbit {
position: relative;
width: 132rpx;
height: 132rpx;
margin: 0 auto;
border-radius: 50%;
background: radial-gradient(circle, #effffb 0%, #a9ffe7 58%, rgba(169, 255, 231, .2) 100%);
box-shadow: 0 20rpx 54rpx rgba(96, 200, 170, .26), inset 0 2rpx 0 rgba(255, 255, 255, .9);
animation: creatingPulse 1.8s ease-in-out infinite;
}
.creating-orbit::before {
content: '';
position: absolute;
inset: -18rpx;
border-radius: 50%;
border: 3rpx dashed rgba(108, 105, 216, .24);
animation: creatingSpin 5s linear infinite;
}
.creating-core {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
color: #151a2d;
font-size: 48rpx;
font-weight: 900;
}
.creating-dot {
position: absolute;
width: 18rpx;
height: 18rpx;
border-radius: 50%;
background: #8b7cff;
box-shadow: 0 0 24rpx rgba(139, 124, 255, .45);
}
.dot-a {
right: 2rpx;
top: 22rpx;
animation: dotFloatA 1.7s ease-in-out infinite;
}
.dot-b {
left: 8rpx;
bottom: 26rpx;
background: #ff9fc1;
animation: dotFloatB 1.9s ease-in-out infinite;
}
.creating-title {
margin-top: 34rpx;
font-size: 36rpx;
font-weight: 900;
color: #151a2d;
}
.creating-sub {
margin-top: 12rpx;
color: rgba(21, 26, 45, .52);
font-size: 24rpx;
line-height: 38rpx;
}
.creating-progress {
height: 10rpx;
margin-top: 34rpx;
border-radius: 999rpx;
overflow: hidden;
background: rgba(21, 26, 45, .06);
}
.creating-progress view {
width: 44%;
height: 100%;
border-radius: 999rpx;
background: linear-gradient(90deg, #a9ffe7, #8b7cff, #ff9fc1);
animation: loadingSlide 1.2s ease-in-out infinite;
}
/* ============ 动画 ============ */
@keyframes riseIn {
from { opacity: 0; transform: translateY(20rpx); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes auroraDrift {
0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
50% { transform: translate3d(24rpx, -30rpx, 0) scale(1.1); }
}
@keyframes laneScroll {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
@keyframes liveDotPulse {
0% { box-shadow: 0 0 0 0 rgba(43, 217, 164, .5); }
70% { box-shadow: 0 0 0 14rpx rgba(43, 217, 164, 0); }
100% { box-shadow: 0 0 0 0 rgba(43, 217, 164, 0); }
}
@keyframes entryWiggle {
0%, 86%, 100% { transform: rotate(0); }
90% { transform: rotate(-2deg); }
94% { transform: rotate(2deg); }
98% { transform: rotate(-1deg); }
}
@keyframes sparkTwinkle {
0%, 100% { opacity: .25; transform: scale(.8) rotate(0deg); }
50% { opacity: 1; transform: scale(1.15) rotate(20deg); }
}
@keyframes ctaBreath {
0%, 100% { transform: scale(1); opacity: .85; }
50% { transform: scale(1.06); opacity: 1; }
}
@keyframes countPop {
0% { transform: translateY(8rpx) scale(.96); opacity: .55; }
70% { transform: translateY(-3rpx) scale(1.04); opacity: 1; }
100% { transform: translateY(0) scale(1); }
}
@keyframes orbSpin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes emojiBounce {
0% { transform: scale(.6); }
60% { transform: scale(1.25); }
100% { transform: scale(1); }
}
@keyframes blobMorph {
0%, 100% { border-radius: 47% 53% 44% 56% / 56% 42% 58% 44%; }
50% { border-radius: 58% 42% 55% 45% / 44% 58% 42% 56%; }
}
@keyframes soulFloat {
0%, 100% { transform: translate3d(0, 0, 0); opacity: .36; }
50% { transform: translate3d(12rpx, -18rpx, 0); opacity: .95; }
}
@keyframes subBreath {
0%, 100% { opacity: .75; }
50% { opacity: 1; }
}
@keyframes typingCursor {
0%, 48% { opacity: 1; }
49%, 100% { opacity: 0; }
}
@keyframes galaxyBreath {
0%, 100% { transform: scale(.98); }
50% { transform: scale(1.035); }
}
@keyframes ringDrift {
from { filter: hue-rotate(0deg); }
to { filter: hue-rotate(18deg); }
}
@keyframes breathButton {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.015); }
}
@keyframes liquidMove {
0%, 100% { transform: translate(0, 0) rotate(0deg) scale(1); }
50% { transform: translate(22rpx, 18rpx) rotate(18deg) scale(1.12); }
}
@keyframes targetUp {
from { transform: translateY(42rpx); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
@keyframes creatingSpin {
from { transform: rotate(0); }
to { transform: rotate(360deg); }
}
@keyframes creatingPulse {
0%, 100% { transform: scale(.98); }
50% { transform: scale(1.04); }
}
@keyframes dotFloatA {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(-10rpx, 8rpx); }
}
@keyframes dotFloatB {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(12rpx, -8rpx); }
}
@keyframes loadingSlide {
0% { transform: translateX(-120%); }
100% { transform: translateX(230%); }
}
/* ============ 流畅模式降级 ============ */
.low-performance::before,
.low-performance .aurora,
.low-performance .spark,
.low-performance .liquid,
.low-performance .soul-particle,
.low-performance .galaxy-ring,
.low-performance .orbit-planet::after {
display: none !important;
}
.low-performance .lane-track {
animation-duration: 60s;
}
.low-performance .galaxy-breath,
.low-performance .galaxy-body,
.low-performance .planet-sub,
.low-performance .planet-cta,
.low-performance .target-btn,
.low-performance .target-star,
.low-performance .mood-orb.active .orb-face::after,
.low-performance .live-dot,
.low-performance .hero-num,
.low-performance .start-match-btn,
.low-performance .typing-cursor {
animation: none !important;
}
.low-performance .galaxy-body {
box-shadow:
0 18rpx 48rpx rgba(108, 92, 231, .14),
inset 8rpx 10rpx 24rpx rgba(255, 255, 255, .52);
}
.low-performance .danmu-item,
.low-performance .hero-live,
.low-performance .home-back,
.low-performance .perf-toggle,
.low-performance .target-mask,
.low-performance .match-mask,
.low-performance .profile-mask {
backdrop-filter: none !important;
}
</style>