<template>
	<view class="boost">
		<view class="nav" :style="{height: navHeight + 'px', paddingTop: statusBarHeight + 'px'}">
			<view class="nav-back" @tap="goBack"><text>‹</text></view>
			<view class="nav-title">{{isDailyMode ? '学院排位赛' : 'PK局'}}</view>
		</view>

		<scroll-view scroll-y class="page" :style="{paddingTop: (navHeight + 10) + 'px'}">
			<view class="hero" v-if="isDailyMode">
				<view class="hero-kicker">COLLEGE ROCKET</view>
				<view class="hero-title">{{pageTitle}}</view>
				<view class="college-picker" @tap="openCollegePicker">
					<text>{{college ? '已选择学院' : '先选择学院'}}</text>
					<text>{{collegeName}} ›</text>
				</view>
				<view class="rocket">
					<view class="rocket-body">🚀</view>
					<view class="rocket-line">
						<view class="rocket-progress" :style="{width: rocketPercent + '%'}"></view>
					</view>
				</view>
				<view class="hero-reward-tip">
					<view class="hero-reward-icon">🏆</view>
					<view class="hero-reward-copy">
						<text>{{heroSub}}</text>
					</view>
				</view>
			</view>

			<view class="rank-overview" v-if="isDailyMode">
				<view class="section-head">
					<text>今日榜单速览</text>
					<text class="reward-record-btn" @tap="openYesterdayRewards">昨日获奖榜单</text>
				</view>
				<view class="rank-mini-grid">
					<view class="rank-mini-panel">
						<view class="rank-mini-title">
							<text>个人排位榜</text>
							<text>最快通关</text>
						</view>
						<view class="glory-mini-list" v-if="compactGloryList.length">
							<view class="glory-mini-item" v-for="(item,i) in compactGloryList" :key="item._key" :class="'rank-' + i">
								<view class="mini-medal">{{gloryMedals[i] || (i + 1)}}</view>
								<view class="mini-main">
									<text>{{item.nickname || '匿名同学'}}</text>
									<text>{{item.college || '神秘学院'}}</text>
								</view>
								<view class="mini-score">{{item.durationSeconds || 0}}s</view>
							</view>
						</view>
						<view class="empty mini-empty" v-else>第一枚金牌等你来拿</view>
					</view>
					<view class="rank-mini-panel">
						<view class="rank-mini-title">
							<text>学院排位榜</text>
							<text>推进值</text>
						</view>
						<view class="college-mini-list" v-if="compactCollegeRanks.length">
							<view class="college-mini-item" v-for="(item,i) in compactCollegeRanks" :key="i">
								<view class="mini-medal">{{i + 1}}</view>
								<view class="mini-main">
									<text>{{item.college || item.nickname || '未知学院'}}</text>
									<view class="mini-bar"><view :style="{width: (item.percent || 8) + '%'}"></view></view>
								</view>
								<view class="mini-score college-score">{{item.score || 0}}</view>
							</view>
						</view>
						<view class="empty mini-empty" v-else>快来抢第一棒</view>
					</view>
				</view>
			</view>

			<view class="game-card">
				<view class="section-head">
					<text>{{gameTitle}}</text>
					<view class="timer-pill" :class="timerClass">
						<text>{{cleared ? clearedPillText : timerText}}</text>
					</view>
				</view>
				<button class="arena-share" v-if="isPkMode" open-type="share">邀请同学来擂台</button>
				<view class="start-strip" :class="{disabled: actionLocked && isDailyMode}" v-if="!playing && !cleared" @tap="startLevel">
					<text>{{isDailyMode ? '开始游戏' : '开始PK'}}</text>
					<text>先点这里，再开始消除卡牌</text>
				</view>
				<view class="start-strip done" v-if="cleared && isDailyMode">
					<text>今日已通关</text>
					<text>{{dailyResultText || '明天再来为学院继续推进'}}</text>
				</view>

				<view class="tips">
					<text>规则：点选未被压住的卡牌，底部槽内3张相同自动消除，宇宙垃圾2张会触发清理。</text>
					<text>陷阱难度 · 剩余 {{remainingCount}} 张 · 当前可点 {{availableCount}} 张 · {{riskText}}</text>
				</view>

				<view class="countdown-card" :class="timerClass">
					<view class="countdown-main">
						<text>⏱</text>
						<view>
							<text>{{timeLeft}}秒</text>
							<text>{{timerHint}}</text>
						</view>
						<view class="restart-left" v-if="isDailyMode">
							<text>{{dailyRestartLeft}}</text>
							<text>今日剩余</text>
						</view>
					</view>
					<view class="countdown-track">
						<view class="countdown-progress" :style="{width: timerPercent + '%'}"></view>
					</view>
				</view>

				<view class="debug-panel">
					<view><text>{{availableCount}}</text><text>可点</text></view>
					<view><text>{{slots.length}}/{{slotLimit}}</text><text>槽位</text></view>
					<view><text>{{directComboCount}}</text><text>可消</text></view>
					<view><text>{{trapGroupCount}}</text><text>陷阱组</text></view>
				</view>

				<view class="board">
					<view
						class="tile"
						v-for="card in visibleCards"
						:key="card.id"
						:class="card.className"
						:style="card.style"
						@tap="pickCard(card.id)">
						<text>{{card.renderIcon}}</text>
						<text class="debug-layer">{{card.layer}}</text>
					</view>
				</view>

				<view class="slot-wrap">
					<view class="slot-title">消除槽 {{slots.length}}/{{slotLimit}}</view>
					<view class="slots">
						<view class="slot" v-for="cell in slotCells" :key="cell.key">
							<text v-if="cell.icon">{{cell.icon}}</text>
						</view>
					</view>
				</view>

				<view class="actions" v-if="isDailyMode">
					<view class="main-btn full-btn" :class="{disabled: actionLocked}" @tap="playing ? resetLevel() : startLevel()">{{playing ? '重开本关' : '开始排位'}}</view>
				</view>
				<view class="actions" v-else>
					<view class="sub-btn" @tap="resetLevel">查看同局</view>
					<view class="main-btn" :class="{disabled: cleared || playing}" @tap="startLevel">
						{{startButtonText}}
					</view>
				</view>
			</view>
		</scroll-view>

		<view v-if="modal.show" class="modal">
			<view class="modal-card">
				<view class="modal-title">{{modal.title}}</view>
				<view class="modal-sub">{{modal.sub}}</view>
				<button class="invite-share" v-if="modal.invite" open-type="share">邀请好友为学院助力</button>
				<view class="main-btn" @tap="closeModal">知道了</view>
			</view>
		</view>

		<view v-if="collegeModal" class="modal">
			<view class="modal-card college-modal">
				<view class="modal-title">选择你的学院</view>
				<view class="modal-sub">学院来自当前校区后台配置，可随时修改。当天通关后会为所选学院贡献推进值。</view>
				<scroll-view scroll-y class="college-list">
					<view
						class="college-option"
						v-for="(item,i) in colleges"
						:key="i"
						:class="{active: college === item.collegeName}"
						@tap="selectCollege(item)">
						<text>{{item.icon || '🎓'}}</text>
						<view>
							<text>{{item.collegeName}}</text>
							<text>{{item.shortName || '学院战队'}}</text>
						</view>
					</view>
				</scroll-view>
				<view class="empty" v-if="!colleges.length">当前校区还没有配置学院，请联系后台先维护学院配置</view>
				<view class="main-btn" @tap="closeCollegePicker">确定</view>
			</view>
		</view>

		<view v-if="rewardModal.show" class="modal">
			<view class="modal-card reward-modal">
				<view class="modal-title">昨日奖励记录</view>
				<view class="modal-sub">个人前五显示通关秒数，学院前三显示学院分数。</view>
				<view class="reward-modal-grid">
					<view class="reward-modal-panel">
						<view class="reward-modal-head">
							<text>个人前五</text>
							<text>秒数</text>
						</view>
						<view v-if="personalRewardList.length" class="reward-record-list">
							<view class="reward-record-row" v-for="(item,i) in personalRewardList" :key="item._key">
								<view class="mini-medal">{{gloryMedals[i] || item.rankNo || (i + 1)}}</view>
								<view class="mini-main">
									<text>{{item.nickname || '匿名同学'}}</text>
									<text class="reward-content personal">{{item.rewardContent || '现金奖励待发放'}}</text>
								</view>
								<view class="mini-score">{{item.durationSeconds || 0}}s</view>
							</view>
						</view>
						<view class="empty mini-empty" v-else>昨日暂无个人奖励</view>
					</view>
					<view class="reward-modal-panel">
						<view class="reward-modal-head">
							<text>学院前三</text>
							<text>分数</text>
						</view>
						<view v-if="collegeRewardList.length" class="reward-record-list">
							<view class="reward-record-row" v-for="(item,i) in collegeRewardList" :key="item._key">
								<view class="mini-medal">{{item.rankNo || (i + 1)}}</view>
								<view class="mini-main">
									<text>{{item.college || item.nickname || '未知学院'}}</text>
									<text class="reward-content college">{{item.rewardContent || '学院奖励待发放'}}</text>
								</view>
								<view class="mini-score college-score">{{item.score || 0}}</view>
							</view>
						</view>
						<view class="empty mini-empty" v-else>昨日暂无学院奖励</view>
					</view>
				</view>
				<view class="main-btn" @tap="closeRewardModal">知道了</view>
			</view>
		</view>
	</view>
</template>

<script>
	import { requireLoginToCurrentPage } from '@/utils/authRedirect.js'
	import { buildHomeSharePath } from '@/utils/sharePath.js'
	export default {
		data() {
			return {
				statusBarHeight: 20,
				navHeight: 64,
				userId: '',
				regionId: '',
				nickname: '',
				avatar: '',
				college: '',
				colleges: [],
				gloryMedals: ['🥇','🥈','🥉','4','5'],
				collegeModal: false,
				home: {},
				session: null,
				playing: false,
				cleared: false,
				startTs: 0,
				cards: [],
				slots: [],
				slotLimit: 7,
				timeLimit: 300,
				timeLeft: 300,
				timer: null,
				roundSeed: '',
				mode: 'daily',
				roomId: '',
				challengeId: '',
				fixedSeed: '',
				inviteCollege: '',
				pkInfo: null,
				challengeInfo: null,
				gloryList: [],
				moveCount: 0,
				buildingLevel: false,
				prebuildTimer: null,
				needRebuildOnStart: false,
				actionLocked: false,
				dailyRestartCount: 0,
				modal: { show: false, title: '', sub: '' },
				rewardModal: {
					show: false,
					loading: false,
					personalRewardList: [],
					collegeRewardList: []
				}
			}
		},
		computed: {
			collegeName() {
				return this.college || '我的学院'
			},
			isDailyMode() {
				return this.mode === 'daily'
			},
			isPkMode() {
				return this.mode === 'pk'
			},
			isChallengeMode() {
				return this.mode === 'challenge'
			},
			pageTitle() {
				if (this.isPkMode) return '星球擂台开战'
				if (this.isChallengeMode) return '好友挑战赛'
				return `今天为 ${this.collegeName} 推一把`
			},
			heroSub() {
				if (this.isPkMode) return '同一房间使用完全一致的卡牌布局。每人只有一次机会，退出或失败都会记录成绩。'
				if (this.isChallengeMode) return '复刻好友通关布局，挑战同一组卡牌，结束后会显示领先或落后秒数。'
				return '21:30结算学院榜券奖励，前三名学院参与者每人得3/2/1张星球券；个人最快通关前五名可获得现金奖励5/4/3/2/1元。'
			},
			gameTitle() {
				if (this.isPkMode) return '擂台关卡'
				if (this.isChallengeMode) return '挑战关卡'
				return '今日关卡'
			},
			startButtonText() {
				if (this.playing) return '排位中'
				if (this.cleared) return this.isDailyMode ? '今日已排位' : '成绩已提交'
				if (this.isPkMode) return '开始擂台'
				if (this.isChallengeMode) return '开始挑战'
				return '开始排位'
			},
			dailyRestartLeft() {
				return Math.max(0, 3 - this.dailyRestartCount)
			},
			collegeRanks() {
				const list = this.home.rankList || []
				let max = 1
				list.forEach(item => {
					if ((item.score || 0) > max) max = item.score || 0
				})
				return list.map(item => {
					item.percent = Math.max(8, Math.round((item.score || 0) * 100 / max))
					return item
				})
			},
			compactGloryList() {
				return (this.gloryList || []).slice(0, 5)
			},
			personalRewardList() {
				return this.withRewardKeys(this.rewardModal.personalRewardList || [], 'personal_reward')
			},
			collegeRewardList() {
				return this.withRewardKeys(this.rewardModal.collegeRewardList || [], 'college_reward')
			},
			compactCollegeRanks() {
				return (this.collegeRanks || []).slice(0, 3)
			},
			myDailyDuration() {
				const home = this.home || {}
				const fields = [
					home.myDurationSeconds,
					home.durationSeconds,
					home.myBestDurationSeconds,
					home.myRecord && home.myRecord.durationSeconds,
					home.todayRecord && home.todayRecord.durationSeconds,
					home.adventureRecord && home.adventureRecord.durationSeconds
				]
				const value = fields.find(item => Number(item) > 0)
				return value ? Number(value) : 0
			},
			myDailyScore() {
				const home = this.home || {}
				const fields = [
					home.myScore,
					home.score,
					home.myTodayScore,
					home.myRecord && home.myRecord.score,
					home.todayRecord && home.todayRecord.score,
					home.adventureRecord && home.adventureRecord.score
				]
				const value = fields.find(item => Number(item) > 0)
				return value ? Number(value) : 0
			},
			dailyResultText() {
				const parts = []
				if (this.myDailyDuration) parts.push(`用时 ${this.myDailyDuration}s`)
				if (this.myDailyScore) parts.push(`推进值 ${this.myDailyScore}`)
				return parts.join(' · ')
			},
			clearedPillText() {
				if (this.myDailyDuration) return `已通关 ${this.myDailyDuration}s`
				return '已通关'
			},
			rocketPercent() {
				let mine = null
				for (let i = 0; i < this.collegeRanks.length; i++) {
					const item = this.collegeRanks[i]
					if ((item.college || item.nickname) === this.college) {
						mine = item
						break
					}
				}
				const top = this.collegeRanks[0] || {}
				if (!mine || !top.score) return 16
				return Math.max(16, Math.min(100, Math.round((mine.score || 0) * 100 / top.score)))
			},
			visibleCards() {
				return this.cards
				        .filter(item => !item.removed && !item.selected)
				        .sort((a,b)=>this.cardZIndex(a)-this.cardZIndex(b))
			},
			remainingCount() {
				return this.cards.filter(item => !item.removed && !item.selected).length
			},
			availableCount() {
				return this.cards.filter(item => !item.removed && !item.selected && item.coverDepth === 0 && !item.locked).length
			},
			directComboCount() {
				return this.collectDifficultyStats(this.cards.filter(item => !item.removed && !item.selected)).directComboCount
			},
			trapGroupCount() {
				return this.collectDifficultyStats(this.cards).trapGroupCount
			},
			slotCells() {
				const cells = []
				for (let i = 0; i < this.slotLimit; i++) {
					const card = this.slots[i]
					cells.push({
						key: 'slot_' + i,
						icon: card ? (card.displayIcon || this.displayIcon(card.icon)) : ''
					})
				}
				return cells
			},
			timerText() {
				return this.playing ? `${this.timeLeft}s` : `${this.timeLimit}s限时`
			},
			timerPercent() {
				return Math.max(0, Math.min(100, Math.round(this.timeLeft * 100 / this.timeLimit)))
			},
			timerClass() {
				if (!this.playing) return ''
				if (this.timeLeft <= 30) return 'danger'
				if (this.timeLeft <= 60) return 'warning'
				return ''
			},
			timerHint() {
				if (!this.playing) return '开始后进入倒计时'
				if (this.timeLeft <= 30) return '最后冲刺，槽位别满'
				if (this.timeLeft <= 60) return '时间紧张，加快决策'
				return `${this.timeLimit}秒内完成排位`
			},
			riskText() {
				if (this.slots.length >= this.slotLimit - 1) return '危险：差1格就满'
				if (this.slots.length >= this.slotLimit - 2) return '注意槽位'
				return '先看上层再下手'
			}
		},
		onLoad(options) {
			const sys = uni.getSystemInfoSync()
			this.statusBarHeight = sys.statusBarHeight || 20
			this.initNavHeight()
			options = options || {}
			this.mode = options.mode || 'daily'
			this.roomId = options.roomId || ''
			this.challengeId = options.challengeId || ''
			this.fixedSeed = options.seed ? decodeURIComponent(options.seed) : ''
			this.inviteCollege = options.college ? decodeURIComponent(options.college) : ''
			if (!this.ensureLoginForShare()) return
			this.userId = uni.getStorageSync('id') || ''
			this.nickname = uni.getStorageSync('nickName') || uni.getStorageSync('nickname') || ''
			this.avatar = uni.getStorageSync('avatarUrl') || uni.getStorageSync('avatar') || ''
			try {
				const area = uni.getStorageSync('area')
				if (area) this.regionId = JSON.parse(area).id || ''
			} catch (e) {}
			this.college = uni.getStorageSync('planetCollege_' + this.regionId) || ''
			if (this.isDailyMode && this.inviteCollege) this.college = this.inviteCollege
			this.dailyRestartCount = this.getDailyRestartCount()
			this.loadColleges()
			this.loadHome()
			this.loadGlory()
			this.prepareSharedMode()
			this.schedulePrebuildLevel()
		},
		onUnload() {
			this.clearTimer()
			this.clearPrebuildTimer()
		},
		onHide() {
			if (this.playing && this.isPkMode) {
				this.submitLeave()
			} else {
				this.clearTimer()
			}
		},
		onShow() {
			if (!this.playing || !this.startTs) return
			const elapsed = Math.floor((Date.now() - this.startTs) / 1000)
			this.timeLeft = Math.max(0, this.timeLimit - elapsed)
			if (this.timeLeft <= 0) {
				this.failLevel('时间到了', `超过${this.timeLimit}秒，本次挑战结束。`)
			} else {
				this.startTimer(false)
			}
		},
		onShareAppMessage() {
			if (this.isPkMode && this.roomId) {
				const roomName = (this.pkInfo && this.pkInfo.roomName) || '星球擂台'
				return {
					title: `星球擂台开战：${roomName}`,
					path: buildHomeSharePath('/package1/planet/adventure?mode=pk&roomId=' + this.roomId)
				}
			}
			const collegeParam = this.college ? ('?college=' + encodeURIComponent(this.college)) : ''
			return {
				title: this.college ? `来为${this.college}助力，冲学院排位榜！` : '学院排位赛，来帮学院冲榜',
				path: buildHomeSharePath('/package1/planet/adventure' + collegeParam)
			}
		},
		methods: {
			initNavHeight() {
				try {
					const menu = uni.getMenuButtonBoundingClientRect && uni.getMenuButtonBoundingClientRect()
					if (menu && menu.top && menu.height) {
						this.navHeight = menu.top + menu.height + 8
						return
					}
				} catch (e) {}
				this.navHeight = this.statusBarHeight + 44
			},
			currentSharePath() {
				if (this.isPkMode && this.roomId) return '/package1/planet/adventure?mode=pk&roomId=' + this.roomId
				if (this.isChallengeMode && this.challengeId) return '/package1/planet/adventure?mode=challenge&challengeId=' + this.challengeId
				return '/package1/planet/adventure'
			},
			ensureLoginForShare() {
				if (this.userId || uni.getStorageSync('id')) return true
				if (this.isPkMode || this.isChallengeMode) {
					uni.setStorageSync('planetPendingSharePath', this.currentSharePath())
					uni.redirectTo({
						url: '/package2/login/login?redirect=planetPendingSharePath'
					})
					return false
				}
				return true
			},
			loadColleges() {
				if (!this.regionId && !this.isDailyMode) return
				this.tui.request('/app/planet/college/list', 'POST', {
					regionId: this.regionId
				}, false, false, true).then(res => {
					if (res.code == 200) {
						this.colleges = res.result || []
						const exists = this.colleges.some(item => item.collegeName === this.college)
						if (!exists) {
							this.college = ''
							uni.removeStorageSync('planetCollege_' + this.regionId)
						}
						if (!this.college && this.colleges.length && this.isDailyMode) {
							this.collegeModal = true
						}
					}
				})
			},
			loadHome() {
				if (!this.isDailyMode) {
					this.cleared = false
					return
				}
				this.tui.request('/app/planet/adventure/home', 'POST', {
					userId: this.userId,
					regionId: this.regionId,
					college: this.college
				}, false, false, true).then(res => {
					if (res.code == 200 && res.result) {
						this.home = res.result
						this.cleared = !res.result.freeAvailable
					}
				})
			},
			loadGlory() {
				if (!this.regionId) return
				this.tui.request('/app/planet/adventure/glory', 'POST', {
					regionId: this.regionId
				}, false, false, true).then(res => {
					if (res.code == 200) {
						this.gloryList = (res.result || []).slice(0, 5).map((item, index) => {
							item._key = item.id || ('glory_' + index)
							return item
						})
					}
				})
			},
			openYesterdayRewards() {
				if (!this.regionId || this.rewardModal.loading) return
				this.rewardModal.show = true
				this.rewardModal.loading = true
				this.tui.request('/app/planet/adventure/yesterdayRewards', 'POST', {
					regionId: this.regionId
				}, false, false, true).then(res => {
					this.rewardModal.loading = false
					if (res.code == 200 && res.result) {
						this.rewardModal.personalRewardList = res.result.personalRewardList || []
						this.rewardModal.collegeRewardList = res.result.collegeRewardList || []
					} else {
						this.tui.toast(res.message)
					}
				}).catch(() => {
					this.rewardModal.loading = false
				})
			},
			closeRewardModal() {
				this.rewardModal.show = false
			},
			withRewardKeys(list, prefix) {
				return (list || []).map((item, index) => {
					item._key = item.id || item.userId || item.college || (prefix + '_' + index)
					return item
				})
			},
			prepareSharedMode() {
				if (this.isPkMode && this.roomId && !this.fixedSeed) {
					this.checkPkRoomAvailable(() => {
						this.joinPkRoom(res => {
							this.fixedSeed = res.result.seed
							this.pkInfo = res.result.room
							if (!this.regionId && this.pkInfo) this.regionId = this.pkInfo.regionId || ''
							if (!this.college && res.result.player) this.college = res.result.player.college || ''
							this.roundSeed = this.fixedSeed
							this.buildLevel()
						})
					})
				}
				if (this.isChallengeMode && this.challengeId && !this.fixedSeed) {
					this.tui.request('/app/planet/adventure/challenge/start', 'POST', {
						userId: this.userId,
						regionId: this.regionId,
						challengeId: this.challengeId
					}).then(res => {
						if (res.code != 200 || !res.result) {
							this.tui.toast(res.message)
							return
						}
						this.fixedSeed = res.result.seed
						this.challengeInfo = res.result.challenge
						if (!this.regionId && this.challengeInfo) this.regionId = this.challengeInfo.regionId || ''
						if (!this.college && this.challengeInfo) this.college = this.challengeInfo.college || ''
						this.roundSeed = this.fixedSeed
						this.buildLevel()
					})
				}
			},
			checkPkRoomAvailable(next) {
				this.tui.request('/app/planet/adventure/pk/roomInfo', 'POST', {
					userId: this.userId,
					regionId: this.regionId,
					roomId: this.roomId
				}, false, false, true).then(res => {
					if (res.code != 200 || !res.result || !res.result.room) {
						this.tui.toast(res.message)
						return
					}
					const room = res.result.room
					const joined = !!res.result.player
					this.pkInfo = room
					if (!this.regionId && room) this.regionId = room.regionId || ''
					if (!this.college && res.result.player) this.college = res.result.player.college || ''
					if (room.status === 2 || room.status === 3) {
						this.tui.toast('房间已结束')
						return
					}
					if (!joined && (room.playerCount || 0) >= (room.maxPlayers || 2)) {
						this.tui.toast('房间人数已满，无法进入')
						return
					}
					if (typeof next === 'function') next()
				})
			},
			joinPkRoom(done) {
				this.tui.request('/app/planet/adventure/pk/join', 'POST', {
					userId: this.userId,
					regionId: this.regionId,
					nickname: this.nickname,
					avatar: this.avatar,
					college: this.college,
					roomId: this.roomId
				}).then(res => {
					if (res.code != 200 || !res.result) {
						this.tui.toast(res.message)
						return
					}
					if (typeof done === 'function') done(res)
				})
			},
			schedulePrebuildLevel() {
				if (!this.isDailyMode && !this.fixedSeed) return
				this.clearPrebuildTimer()
				this.prebuildTimer = setTimeout(() => {
					if (this.cards.length || this.playing || this.cleared) return
					this.roundSeed = this.fixedSeed || this.newRoundSeed()
					this.buildLevel()
					if (!this.cards.length) this.buildLevel()
				}, 200)
			},
			clearPrebuildTimer() {
				if (!this.prebuildTimer) return
				clearTimeout(this.prebuildTimer)
				this.prebuildTimer = null
			},
			startLevel() {
				if (!requireLoginToCurrentPage()) return
				if (this.cleared || this.playing) return
				if (this.isDailyMode && this.actionLocked) return
				if (this.isDailyMode) this.actionLocked = true
				this.clearPrebuildTimer()
				if (!this.college && this.isDailyMode) {
					this.actionLocked = false
					this.openCollegePicker()
					return
				}
				if (this.isDailyMode && !this.canUseDailyRestart()) {
					this.actionLocked = false
					this.tui.toast('今日重开次数已用完')
					return
				}
				if (!this.isDailyMode) {
					this.startFixedMode()
					return
				}
				this.tui.request('/app/planet/adventure/start', 'POST', {
					userId: this.userId,
					regionId: this.regionId,
					nickname: this.nickname,
					avatar: this.avatar,
					college: this.college
				}).then(res => {
					if (res.code != 200 || !res.result) {
						this.actionLocked = false
						this.tui.toast(res.message)
						return
					}
					this.session = res.result
					this.applyServerGameSeconds(res.result.gameSeconds)
					this.playing = true
					this.startTs = Date.now()
					this.recordDailyRestartUse()
					if (this.needRebuildOnStart || !this.cards.length) {
						this.roundSeed = this.newRoundSeed()
						this.buildLevel()
						if (!this.cards.length) this.buildLevel()
						this.needRebuildOnStart = false
					}
					this.startTimer()
					this.actionLocked = false
				}).catch(() => {
					this.actionLocked = false
				})
			},
			applyServerGameSeconds(gameSeconds) {
				const seconds = Number(gameSeconds)
				if (!seconds || seconds <= 0) return
				this.timeLimit = seconds
				this.timeLeft = seconds
			},
			startFixedMode() {
				if (!requireLoginToCurrentPage()) return
				if (!this.fixedSeed && this.isPkMode) {
					this.checkPkRoomAvailable(() => {
						this.joinPkRoom(res => {
							this.fixedSeed = res.result.seed
							this.pkInfo = res.result.room
							this.roomId = res.result.roomId || this.roomId
							if (!this.regionId && this.pkInfo) this.regionId = this.pkInfo.regionId || ''
							if (!this.college && res.result.player) this.college = res.result.player.college || ''
							this.startFixedMode()
						})
					})
					return
				}
				if (this.isChallengeMode && !this.fixedSeed) {
					this.tui.request('/app/planet/adventure/challenge/start', 'POST', {
						userId: this.userId,
						regionId: this.regionId,
						challengeId: this.challengeId
					}).then(res => {
						if (res.code != 200 || !res.result) {
							this.tui.toast(res.message)
							return
						}
						this.fixedSeed = res.result.seed
						this.challengeInfo = res.result.challenge
						this.challengeId = res.result.challengeId || this.challengeId
						if (!this.regionId && this.challengeInfo) this.regionId = this.challengeInfo.regionId || ''
						if (!this.college && this.challengeInfo) this.college = this.challengeInfo.college || ''
						this.startFixedMode()
					})
					return
				}
				this.playing = true
				this.startTs = Date.now()
				this.roundSeed = this.fixedSeed
				if (!this.cards.length) {
					this.buildLevel()
					if (!this.cards.length) this.buildLevel()
				}
				this.startTimer()
			},
			buildLevel() {
				const icons = ['🍔', '🥤', '🥟', '🏀', '🎧', '🚲', '🍜', '🔥', '☕', '🍢']
				const garbageIcons = ['🚀', '🍱', '🍗']
				if (!this.roundSeed) this.roundSeed = this.newRoundSeed()
				let cards = []
				this.buildingLevel = true
				try {
					for (let attempt = 0; attempt < 100; attempt++) {
						const seed = this.roundSeed + '_try_' + attempt
						const positions = this.buildCardPositions(seed)
						cards = this.assignTrapLayoutIcons(positions, icons, garbageIcons, seed)
						if (cards.length && this.checkDifficultyMetrics(cards)) break
						cards = []
					}
					if (!cards.length) {
						cards = this.assignTrapLayoutIcons(this.buildCardPositions(this.roundSeed + '_fallback'), icons, garbageIcons, this.roundSeed + '_fallback', true)
					}
					if (!cards.length) {
						cards = this.assignEmergencyIcons(this.buildCardPositions(this.roundSeed + '_emergency'), icons, garbageIcons)
					}
					if (this.shouldBuildTrapLayout()) {
						this.applyTrapLayout(cards)
					}
					if (!this.validateTileCounts(cards)) {
						this.repairTileCounts(cards, icons, garbageIcons)
					}
					this.cards = cards
					this.slots = []
					this.moveCount = 0
					this.normalizeCards(icons)
				} finally {
					this.buildingLevel = false
				}
				this.refreshCardState()
			},
			newRoundSeed() {
				return `${this.levelSeed()}_${Date.now()}_${Math.floor(Math.random() * 100000)}`
			},
			buildCardPositions(seed) {
				const cards = []
				const template = this.levelTemplate('hell')
				const layerCounts = template.layerCounts
				const rand = this.seededRandom(seed + '_pos')
				let idx = 0
				layerCounts.forEach((count, layer) => {
					for (let i = 0; i < count; i++) {
						const zone = template.zones[layer]
						const col = i % zone.cols
						const row = Math.floor(i / zone.cols)
						const dx = Math.floor(rand() * 6)
						const dy = Math.floor(rand() * 6)
						const card = {
							id: 'c' + idx,
							order: idx,
							icon: '',
							layer,
							x: zone.x + col * zone.gapX + (layer * zone.offsetX) + ((row % 2) * zone.stagger) + dx,
							y: zone.y + row * zone.gapY + (layer * zone.offsetY) + dy,
							style: '',
							removed: false,
							selected: false,
							locked: false,
							className: '',
							trap: false,
							keyCard: false
						}
						card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};`
						cards.push(card)
						idx++
					}
				})
				return cards
			},
			levelTemplate(type) {
				const templates = {
					simple: {
						layerCounts: [36, 30, 24],
						zones: [
							{ x: 20, y: 18, cols: 6, gapX: 82, gapY: 72, offsetX: 8, offsetY: 18, stagger: 8 },
							{ x: 70, y: 72, cols: 5, gapX: 78, gapY: 70, offsetX: 10, offsetY: 22, stagger: 10 },
							{ x: 96, y: 110, cols: 5, gapX: 74, gapY: 66, offsetX: 12, offsetY: 24, stagger: 10 }
						]
					},
					normal: {
						layerCounts: [36, 30, 24],
						zones: [
							{ x: 18, y: 18, cols: 6, gapX: 80, gapY: 70, offsetX: 12, offsetY: 24, stagger: 10 },
							{ x: 78, y: 76, cols: 5, gapX: 76, gapY: 66, offsetX: 14, offsetY: 28, stagger: 12 },
							{ x: 110, y: 116, cols: 4, gapX: 74, gapY: 62, offsetX: 16, offsetY: 30, stagger: 12 }
						]
					},
					difficult: {
						layerCounts: [36, 30, 24],
						zones: [
							{ x: 14, y: 16, cols: 6, gapX: 80, gapY: 70, offsetX: 14, offsetY: 28, stagger: 12 },
							{ x: 92, y: 76, cols: 5, gapX: 70, gapY: 62, offsetX: 18, offsetY: 32, stagger: 14 },
							{ x: 132, y: 122, cols: 4, gapX: 66, gapY: 58, offsetX: 20, offsetY: 34, stagger: 14 }
						]
					},
					hell: {
						layerCounts: [52, 40, 28],
						zones: [
							{ x: 44, y: 14, cols: 7, gapX: 76, gapY: 72, offsetX: 0, offsetY: 0, stagger: 4 },
							{ x: 72, y: 42, cols: 6, gapX: 76, gapY: 72, offsetX: 0, offsetY: 0, stagger: 4 },
							{ x: 34, y: 18, cols: 7, gapX: 76, gapY: 88, offsetX: 0, offsetY: 0, stagger: 0 }
						]
					}
				}
				return templates[type] || templates.hell
			},
			assignTrapLayoutIcons(positions, icons, garbageIcons, seed, forceBuild) {
				const cards = positions.slice()
				cards.forEach(card => {
					card.icon = ''
					card.trap = false
					card.keyCard = false
					card.garbage = false
					card.openingSafe = false
				})
				const bottom = this.seededShuffle(cards.filter(card => card.layer === 0), seed + '_bottom')
				const middle = this.seededShuffle(cards.filter(card => card.layer === 1), seed + '_middle')
				const top = this.seededShuffle(cards.filter(card => card.layer === 2), seed + '_top')
				const bottomCenter = this.seededShuffle(bottom.filter(card => this.isCenterCard(card)), seed + '_bottom_center')
				const topOuter = this.seededShuffle(top.filter(card => !this.isCenterCard(card)), seed + '_top_outer')
				const midOuter = this.seededShuffle(middle.filter(card => !this.isCenterCard(card)), seed + '_mid_outer')
				const assigned = {}
				const groupCount = Math.floor((cards.length - 6) / 3)
				const trapCount = Math.max(20,Math.ceil(groupCount*0.62))
				for (let group = 0; group < groupCount; group++) {
					const icon = icons[group % icons.length]
					let chosen = []
					if (group < trapCount) {
						chosen = [
							this.takeNext(topOuter, assigned) || this.takeNext(top, assigned) || this.takeAny(cards, assigned),
							this.takeNext(midOuter, assigned) || this.takeNext(middle, assigned) || this.takeAny(cards, assigned),
							this.takeNext(bottomCenter, assigned) || this.takeNext(bottom, assigned) || this.takeAny(cards, assigned)
						]
					} else {
						chosen = [
							this.takeNext(top, assigned) || this.takeAny(cards, assigned),
							this.takeNext(middle, assigned) || this.takeAny(cards, assigned),
							this.takeNext(bottom, assigned) || this.takeAny(cards, assigned)
						]
					}
					if (chosen.some(card => !card)) {
						if (!forceBuild) return []
						break
					}
					chosen.forEach((card, index) => {
						card.icon = icon
						card.trap = group < trapCount
						card.keyCard = group < trapCount && index === 2
						assigned[card.id] = true
					})
				}
				this.cards = cards
				this.repositionKeyCovers(cards, seed)
				const freeCards = cards.filter(card => !assigned[card.id])
				const garbageCards = this.seededShuffle(freeCards.slice(), seed + '_garbage').slice(0, 6)
				garbageCards.forEach((card, index) => {
					card.icon = garbageIcons[Math.floor(index / 2) % garbageIcons.length]
					card.garbage = true
					assigned[card.id] = true
				})
				let fillIndex = 0
				cards.forEach((card, index) => {
					if (!card.icon) {
						card.icon = icons[Math.floor(fillIndex / 3) % icons.length]
						fillIndex++
					}
					card.displayIcon = this.displayIcon(card.icon)
					card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};`
				})
				return cards
			},
			assignEmergencyIcons(positions, icons, garbageIcons) {
				positions.forEach((card, index) => {
					card.icon = index < 6 ? garbageIcons[Math.floor(index / 2) % garbageIcons.length] : icons[Math.floor((index - 6) / 3) % icons.length]
					card.displayIcon = this.displayIcon(card.icon)
					card.trap = index >= 6 && index < 42
					card.keyCard = card.trap && card.layer === 0 && this.isCenterCard(card)
					card.garbage = index < 6
					card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};`
				})
				return positions
			},
			validateTileCounts(cards) {
				const counts = {}
				cards.forEach(card => {
					if (!card.icon) return
					counts[card.icon] = (counts[card.icon] || 0) + 1
				})
				return Object.keys(counts).every(icon => {
					const unit = this.isGarbageIcon(icon) ? 2 : 3
					return counts[icon] % unit === 0
				})
			},
			repairTileCounts(cards, icons, garbageIcons) {
				const sorted = cards.slice().sort((a, b) => this.cardZIndex(a) - this.cardZIndex(b))
				sorted.forEach((card, index) => {
					if (index < 6) {
						card.icon = garbageIcons[Math.floor(index / 2) % garbageIcons.length]
						card.garbage = true
					} else {
						card.icon = icons[Math.floor((index - 6) / 3) % icons.length]
						card.garbage = false
					}
					card.displayIcon = this.displayIcon(card.icon)
				})
			},
			shouldBuildTrapLayout() {
				return true
			},
			applyTrapLayout(cards) {
				this.cards = cards
				const iconMap = {}
				cards.forEach(card => {
					if (this.isGarbageIcon(card.icon)) return
					if (!iconMap[card.icon]) iconMap[card.icon] = []
					iconMap[card.icon].push(card)
				})
				const candidates = Object.keys(iconMap).filter(icon => iconMap[icon].length >= 3)
				if (!candidates.length) return
				const rand = this.seededRandom((this.roundSeed || this.levelSeed()) + '_trap_layout')
				this.shuffleWithRandom(candidates, rand)
				const trapIcons = candidates.slice(0, Math.min(candidates.length, 8 + Math.floor(rand() * 2)))
				trapIcons.forEach((targetIcon, trapIndex) => {
					const group = this.seededShuffle(iconMap[targetIcon].slice(), (this.roundSeed || this.levelSeed()) + '_trap_icon_' + trapIndex).slice(0, 3)
					if (group.length < 3) return
					const key = group[0]
					const pair = group.slice(1, 3)
					this.exposePairEarly(pair, trapIndex)
					this.placeDeepKeyCard(key, trapIndex, rand)
					this.buildCoverChain(key, cards, trapIndex, rand)
				})
				this.exposeOpeningCombos(cards, candidates, rand)
				this.tightenOpeningPressure(cards, rand)
			},
			placeDeepKeyCard(card, index, rand) {
				card.layer = 0
				card.keyCard = true
				card.trap = true
				card.order = 20 + index
				card.x = 188 + Math.floor(rand() * 150)
				card.y = 150 + Math.floor(rand() * 250)
				card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};`
				this.refreshCardState()
			},
			exposePairEarly(pair, index) {
				const slots = [
					{ x: 34, y: 16 }, { x: 138, y: 16 }, { x: 242, y: 16 }, { x: 346, y: 16 }, { x: 450, y: 16 },
					{ x: 34, y: 114 }, { x: 138, y: 114 }, { x: 242, y: 114 }, { x: 346, y: 114 }, { x: 450, y: 114 },
					{ x: 34, y: 314 }, { x: 138, y: 314 }, { x: 242, y: 314 }, { x: 346, y: 314 }, { x: 450, y: 314 },
					{ x: 34, y: 412 }, { x: 138, y: 412 }, { x: 242, y: 412 }, { x: 346, y: 412 }, { x: 450, y: 412 }
				]
				const first = slots[index % 10]
				const second = slots[index % 10 + 10]
				pair.forEach((card, i) => {
					card.layer = 2
					card.order = 900 + index * 4 + i
					card.trap = true
					card.x = i === 0 ? first.x : second.x
					card.y = i === 0 ? first.y : second.y
					card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};`
				})
				this.refreshCardState()
			},
			exposeOpeningCombos(cards, candidates, rand) {
				const slots = [
					{ x: 38, y: 16 }, { x: 122, y: 16 }, { x: 206, y: 16 },
					{ x: 310, y: 16 }, { x: 394, y: 16 }, { x: 478, y: 16 },
					{ x: 38, y: 118 }, { x: 122, y: 118 }, { x: 206, y: 118 },
					{ x: 310, y: 118 }, { x: 394, y: 118 }, { x: 478, y: 118 },
					{ x: 38, y: 408 }, { x: 122, y: 408 }, { x: 206, y: 408 }
				]
				const safeIcons = candidates.slice(-4)
				let slotIndex = 0
				safeIcons.forEach((icon, groupIndex) => {
					const group = cards.filter(card => card.icon === icon && !card.garbage && !card.keyCard && !card.trap).slice(0, 3)
					if (group.length < 3 || slotIndex + 2 >= slots.length) return
					group.forEach(card => {
						const slot = slots[slotIndex++]
						card.layer = 2
						card.trap = false
						card.keyCard = false
						card.openingSafe = true
						card.order = 1100 + groupIndex * 8 + slotIndex
						card.x = slot.x + Math.floor(rand() * 4)
						card.y = slot.y + Math.floor(rand() * 4)
						card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};`
					})
				})
				this.refreshCardState()
			},
			buildCoverChain(key, cards, index, rand) {
				const covers = cards.filter(card => {
					return card.id !== key.id && !card.garbage && !card.keyCard && !card.trap && !card.openingSafe && card.layer > 0
				})
				this.shuffleWithRandom(covers, rand)
				const count = 7 + (index % 3)
				const offsets = [
					{ x: 0, y: 0 },
					{ x: 8, y: 8 },
					{ x: -8, y: 10 },
					{ x: 10, y: -8 },
					{ x: -10, y: -6 },
					{ x: 2, y: 14 },
					{ x: 14, y: 2 },
					{ x: -14, y: 4 },
					{ x: 4, y: -14 },
					{ x: -4, y: 16 },
					{ x: 16, y: -4 },
					{ x: -16, y: -2 }
				]
				for (let i = 0; i < count && i < covers.length; i++) {
					const cover = covers[i]
					const offset = offsets[i % offsets.length]
					cover.layer = i < 5 ? 1 : 2
					cover.order = 520 + index * 28 + i
					cover.x = key.x + offset.x + Math.floor(rand() * 5)
					cover.y = key.y + offset.y + Math.floor(rand() * 5)
					cover.style = `left:${cover.x}rpx;top:${cover.y}rpx;z-index:${this.cardZIndex(cover)};`
				}
				this.refreshCardState()
			},
			tightenOpeningPressure(cards, rand) {
				let buryIndex = 0
				for (let round = 0; round < 3; round++) {
					this.forceRefreshCardState()
					const visibleMap = {}
					cards.forEach(card => {
						if (card.removed || card.selected || card.coverDepth > 0 || this.isGarbageIcon(card.icon)) return
						if (!visibleMap[card.icon]) visibleMap[card.icon] = []
						visibleMap[card.icon].push(card)
					})
					let changed = false
					Object.keys(visibleMap).forEach(icon => {
						const list = visibleMap[icon].sort((a, b) => {
							if (a.openingSafe !== b.openingSafe) return a.openingSafe ? -1 : 1
							if (a.trap !== b.trap) return a.trap ? -1 : 1
							return this.getRealZ(b) - this.getRealZ(a)
						})
						const keepCount = list.some(card => card.openingSafe) ? 3 : (list.some(card => !card.trap) ? 3 : 2)
						list.slice(keepCount).forEach(card => {
							changed = true
							this.buryOpeningCard(card, cards, buryIndex++, rand)
						})
					})
					if (!changed) break
				}
				this.forceRefreshCardState()
				const visible = cards.filter(card => !card.removed && !card.selected && card.coverDepth === 0 && !this.isGarbageIcon(card.icon) && !card.openingSafe)
				visible
					.sort((a, b) => {
						if (a.trap !== b.trap) return a.trap ? -1 : 1
						return this.getRealZ(b) - this.getRealZ(a)
					})
					.slice(30)
					.forEach(card => {
						this.buryOpeningCard(card, cards, buryIndex++, rand)
					})
				this.forceRefreshCardState()
			},
			buryOpeningCard(card, cards, index, rand) {
				card.layer = 0
				card.trap = true
				card.keyCard = true
				card.order = 40 + index
				card.x = 170 + (index % 5) * 44 + Math.floor(rand() * 10)
				card.y = 150 + Math.floor(index / 5) * 54 + Math.floor(rand() * 12)
				card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};`
				this.buildCoverChain(card, cards, 20 + index, rand)
				if (this.coverCount(card, cards) < 3) {
					this.forceCoverBuriedCard(card, cards, index, rand)
				}
			},
			forceCoverBuriedCard(card, cards, index, rand) {
				const covers = cards.filter(item => {
					return item.id !== card.id && !item.garbage && !item.keyCard && !item.trap && !item.openingSafe && item.layer > 0
				})
				this.shuffleWithRandom(covers, rand)
				for (let i = 0; i < 3 && i < covers.length; i++) {
					const cover = covers[i]
					cover.layer = i === 0 ? 1 : 2
					cover.order = 980 + index * 8 + i
					cover.x = card.x + (i === 0 ? 0 : (i === 1 ? 12 : -12)) + Math.floor(rand() * 4)
					cover.y = card.y + (i === 0 ? 0 : (i === 1 ? 10 : -10)) + Math.floor(rand() * 4)
					cover.style = `left:${cover.x}rpx;top:${cover.y}rpx;z-index:${this.cardZIndex(cover)};`
				}
				this.forceRefreshCardState()
			},
			repositionKeyCovers(cards, seed) {
				const rand = this.seededRandom(seed + '_cover')
				const keys = cards.filter(card => card.keyCard && card.layer === 0 && this.isCenterCard(card)).slice(0, 10)
				const covers = this.seededShuffle(cards.filter(card => !card.keyCard && card.layer > 0), seed + '_cover_cards')
				keys.forEach(key => {
					for (let i = 0; i < 2; i++) {
						const cover = covers.shift()
						if (!cover) return
						cover.x = key.x + (i === 0 ? 18 : -4) + Math.floor(rand() * 8)
						cover.y = key.y + (i === 0 ? 18 : -4) + Math.floor(rand() * 8)
						cover.style = `left:${cover.x}rpx;top:${cover.y}rpx;z-index:${this.cardZIndex(cover)};`
					}
				})
				this.refreshCardState()
			},
			takeNext(pool, assigned) {
				while (pool.length) {
					const card = pool.shift()
					if (!assigned[card.id]) return card
				}
				return null
			},
			takeAny(cards, assigned) {
				for (let i = 0; i < cards.length; i++) {
					if (!assigned[cards[i].id]) return cards[i]
				}
				return null
			},
			checkDifficultyMetrics(cards) {
				const stats = this.collectDifficultyStats(cards)
				return stats.visibleTypeCount >= 8 &&
					stats.directComboCount <= 2 &&
					stats.trapGroupCount >= 10 &&
					stats.deepKeyCount >= 10
			},
			collectDifficultyStats(cards) {
				const visible = cards.filter(card => !this.isBlockedIn(card, cards))
				const typeMap = {}
				const countMap = {}
				visible.forEach(card => {
					typeMap[card.icon] = true
					countMap[card.icon] = (countMap[card.icon] || 0) + 1
				})
				const trapMap = {}
				let deepKeyCount = 0
				cards.forEach(card => {
					if (card.trap && card.icon) trapMap[card.icon] = true
					if (card.keyCard && card.layer === 0 && this.coverCount(card, cards) >= 2) deepKeyCount++
				})
				return {
					visibleTypeCount: Object.keys(typeMap).length,
					directComboCount: Object.keys(countMap).filter(icon => countMap[icon] >= 3 && !this.isGarbageIcon(icon)).length,
					trapGroupCount: Object.keys(trapMap).length,
					deepKeyCount
				}
			},
			coverCount(card, cards) {
				return cards.filter(other => {
					if (other.id === card.id || this.getRealZ(other) <= this.getRealZ(card)) return false
					return this.covers(other, card)
				}).length
			},
			isGarbageIcon(icon) {
				return icon === '🚀' || icon === '🍱' || icon === '🍗'
			},
			isCenterCard(card) {
				return card.x >= 160 && card.x <= 400 && card.y >= 120 && card.y <= 440
			},
			isBlockedIn(card, list) {
				return list.some(other => {
					if (other.id === card.id || this.getRealZ(other) <= this.getRealZ(card)) return false
					return this.covers(other, card)
				})
			},
			isOverlap(a, b) {
				const size = 76
				const overlapX = Math.min(a.x + size, b.x + size) - Math.max(a.x, b.x)
				const overlapY = Math.min(a.y + size, b.y + size) - Math.max(a.y, b.y)
				if (overlapX <= 0 || overlapY <= 0) return false
				const minAxis = 12
				const minAreaRatio = 0.08
				return overlapX >= minAxis &&
					overlapY >= minAxis &&
					(overlapX * overlapY) >= size * size * minAreaRatio
			},
			// upper 是否真正压住 lower：同层不会重叠，故只要两轴都有实质重叠即判定被压
			covers(upper, lower) {
				const size = 76
				const overlapX = Math.min(upper.x + size, lower.x + size) - Math.max(upper.x, lower.x)
				const overlapY = Math.min(upper.y + size, lower.y + size) - Math.max(upper.y, lower.y)
				return overlapX >= 10 && overlapY >= 10
			},
			getRealZ(card) {
				return (card.layer || 0) * 1000 + (card.order || 0)
			},
			cardZIndex(card) {
				return this.getRealZ(card) + 1
			},
			normalizeCards(icons) {
				this.cards.forEach((card, index) => {
					if (!card.icon) card.icon = icons[index % icons.length]
					card.displayIcon = this.displayIcon(card.icon)
					card.locked = false
					card.coverDepth = 0
					card.displayState = 'top'
					card.renderIcon = card.displayIcon
					card.className = 'active'
					card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};`
				})
			},
			displayIcon(icon) {
				if (!icon) return '？'
				if (this.isGarbageIcon(icon)) return icon
				return icon
			},
			refreshCardState() {
				if (this.buildingLevel) return
				this.forceRefreshCardState()
			},
			forceRefreshCardState() {
				this.cards.forEach(card => {
					const depth = this.coverDepth(card)
					card.locked = !card.removed && !card.selected && depth > 0
					card.coverDepth = depth
					card.displayState = depth === 0 ? 'top' : (depth === 1 ? 'middle' : 'deep')
					card.renderIcon = card.displayState === 'deep' ? '?' : (card.displayIcon || this.displayIcon(card.icon))
					card.className = [
						depth === 0 ? 'active' : '',
						depth === 1 ? 'locked' : '',
						depth >= 2 ? 'hidden-card' : '',
						card.selected ? 'selected' : ''
					].filter(Boolean).join(' ')
				})
			},
			coverDepth(card) {
				if (card.removed || card.selected) return 0
				return this.cards.filter(other => {
					if (other.id === card.id) return false
					if (other.removed || other.selected) return false
					return this.getRealZ(other) > this.getRealZ(card) && this.covers(other, card)
				}).length
			},
			pickCard(id) {
				const card = this.cards.find(item => item.id === id)
				if (!card) return
				if (this.cleared) {
					this.tui.toast('今日已排位，明天再来')
					return
				}
				if (!this.playing) {
					this.tui.toast('先点击开始排位')
					return
				}
				this.refreshCardState()
				if (card.coverDepth > 0) {
					this.tui.toast('这张卡被压住了',300)
					return
				}
				if (card.removed || card.selected) return
				if (this.slots.length >= this.slotLimit) return
				card.displayIcon = this.displayIcon(card.icon)
				card.selected = true
				this.slots.push(card)
				this.moveCount++
				this.tryClear(card.icon)
				this.refreshCardState()
				if (this.cards.every(item => item.removed) && this.slots.length === 0) {
					this.finishClear()
				} else if (this.cards.every(item => item.removed || item.selected) && this.slots.length > 0) {
					this.failLevel('没有可消组合', '剩余卡牌无法凑成消除，本次挑战结束。')
				} else if (this.slots.length >= this.slotLimit) {
					this.failLevel()
				}
			},
			startTimer(reset) {
				this.clearTimer()
				if (reset !== false) this.timeLeft = this.timeLimit
				this.timer = setInterval(() => {
					if (!this.playing) {
						this.clearTimer()
						return
					}
					const elapsed = Math.floor((Date.now() - this.startTs) / 1000)
					this.timeLeft = Math.max(0, this.timeLimit - elapsed)
					if (this.timeLeft <= 0) {
						this.failLevel('时间到了', `超过${this.timeLimit}秒，本次挑战结束。`)
					}
				}, 1000)
			},
			clearTimer() {
				if (this.timer) {
					clearInterval(this.timer)
					this.timer = null
				}
			},
			tryClear(icon) {
				if (this.isGarbageIcon(icon)) {
					const garbage = this.slots.filter(item => item.icon === icon)
					if (garbage.length < 2) return
					let removedGarbage = 0
					this.slots = this.slots.filter(item => {
						if (item.icon === icon && removedGarbage < 2) {
							item.removed = true
							removedGarbage++
							return false
						}
						return true
					})
					return
				}
				const same = this.slots.filter(item => item.icon === icon)
				if (same.length < 3) return
				let removed = 0
				this.slots = this.slots.filter(item => {
					if (item.icon === icon && removed < 3) {
						item.removed = true
						removed++
						return false
					}
					return true
				})
			},
			isLocked(card) {
				return this.coverDepth(card) > 0
			},
			finishClear() {
				const duration = Math.max(20, Math.floor((Date.now() - this.startTs) / 1000))
				const progress = Math.max(100, 260 - duration + Math.max(0, 80 - this.moveCount))
				this.playing = false
				this.clearTimer()
				if (!this.isDailyMode) {
					this.submitCompetitive(true, duration, 0)
					return
				}
				this.tui.request('/app/planet/adventure/submit', 'POST', {
					userId: this.userId,
					regionId: this.regionId,
					sessionId: this.session.sessionId,
					normalCount: this.cards.length,
					goldenCount: 1,
					diamondCount: 0,
					rainbowCount: 0,
					ticketBagCount: 0,
					propUseCount: 0,
					hitCount: 0,
					score: progress,
					durationSeconds: duration
				}).then(res => {
					if (res.code == 200) {
						this.cleared = true
						this.home = Object.assign({}, this.home, {
							myDurationSeconds: duration,
							myScore: progress
						})
						this.clearDailyRestartCount()
						this.modal = {
							show: true,
							title: '排位成功',
							sub: `${this.collegeName} 获得 ${progress} 点推进值。分享给好友继续为学院助力，每个人都会随机生成自己的关卡。`,
							invite: true,
							duration
						}
						this.loadHome()
						this.loadGlory()
					} else {
						this.showSubmitFailed(res.message)
					}
				}).catch(err => {
					this.showSubmitFailed(err && err.message)
				})
			},
			showSubmitFailed(message) {
				this.needRebuildOnStart = true
				this.modal = {
					show: true,
					title: '成绩未计入',
					sub: message || '成绩提交失败，请重新开始本关后再试。'
				}
			},
			failLevel(title, sub) {
				this.playing = false
				this.clearTimer()
				if (!this.isDailyMode) {
					this.submitCompetitive(false, Math.floor((Date.now() - this.startTs) / 1000), this.remainingCount)
					return
				}
				this.needRebuildOnStart = true
				this.modal = {
					show: true,
					title: title || '槽位满了',
					sub: `${sub || '本次挑战结束。'}${this.dailyRestartLeft <= 0 ? ' 今日次数已用完。' : ' 今日还可开始 ' + this.dailyRestartLeft + ' 次。'}`
				}
			},
			submitCompetitive(cleared, duration, remainingCards) {
				const url = this.isPkMode ? '/app/planet/adventure/pk/submit' : '/app/planet/adventure/challenge/submit'
				const data = {
					userId: this.userId,
					regionId: this.regionId,
					nickname: this.nickname,
					avatar: this.avatar,
					college: this.college,
					roomId: this.roomId,
					challengeId: this.challengeId,
					cleared: cleared ? 1 : 0,
					durationSeconds: Math.max(0, duration || 0),
					remainingSeconds: this.timeLeft,
					remainingCards: remainingCards || this.remainingCount
				}
				this.tui.request(url, 'POST', data).then(res => {
					if (res.code != 200) {
						this.tui.toast(res.message)
						return
					}
					if (this.isPkMode) {
						this.cleared = true
						this.modal = {
							show: true,
							title: cleared ? '擂台成绩已提交' : '擂台挑战失败',
							sub: cleared ? `用时 ${duration} 秒，等待房间结算。` : '本房间只有一次机会，成绩已记录，等待最终排名。'
						}
					} else {
						this.cleared = true
						const result = res.result || {}
						this.modal = {
							show: true,
							title: cleared ? '挑战完成' : '挑战失败',
							sub: result.resultText || (cleared ? `用时 ${duration} 秒` : '这次没追上，换个顺序还有机会。')
						}
					}
				})
			},
			submitLeave() {
				this.playing = false
				this.clearTimer()
				this.tui.request('/app/planet/adventure/pk/leave', 'POST', {
					userId: this.userId,
					regionId: this.regionId,
					roomId: this.roomId,
					remainingCards: this.remainingCount
				}, false, false, true)
			},
			resetLevel() {
				if (this.cleared) return
				if (this.isDailyMode && this.actionLocked) return
				if (this.isDailyMode) this.actionLocked = true
				if (!this.isDailyMode && this.playing) {
					this.tui.toast('擂台/挑战只有一次机会，不能重开')
					return
				}
				if (this.isDailyMode && !this.canUseDailyRestart()) {
					this.actionLocked = false
					this.tui.toast('今日重开次数已用完')
					return
				}
				/* if (this.isDailyMode) this.recordDailyRestartUse() */
				this.playing = false
				this.clearTimer()
				this.timeLeft = this.timeLimit
				this.roundSeed = this.fixedSeed || this.newRoundSeed()
				this.buildLevel()
				if (!this.cards.length) this.buildLevel()
				this.needRebuildOnStart = false
				this.actionLocked = false
			},
			dailyRestartStorageKey() {
				const uid = this.userId || uni.getStorageSync('id') || 'guest'
				return `planetAdventureRestart_${uid}_${this.regionId || 'global'}_${this.todayKey()}`
			},
			todayKey() {
				const d = new Date()
				const p = n => (n < 10 ? '0' + n : '' + n)
				return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}`
			},
			getDailyRestartCount() {
				const count = parseInt(uni.getStorageSync(this.dailyRestartStorageKey()) || '0', 10)
				return Number.isNaN(count) ? 0 : count
			},
			canUseDailyRestart() {
				return this.dailyRestartCount < 3
			},
			recordDailyRestartUse() {
				const count = Math.min(3, this.getDailyRestartCount() + 1)
				uni.setStorageSync(this.dailyRestartStorageKey(), count)
				this.dailyRestartCount = count
				return count
			},
			clearDailyRestartCount() {
				uni.removeStorageSync(this.dailyRestartStorageKey())
				this.dailyRestartCount = 0
			},
			closeModal() {
				this.modal.show = false
			},
			openCollegePicker() {
				this.collegeModal = true
			},
			closeCollegePicker() {
				if (!this.college) {
					this.tui.toast('请先选择学院')
					return
				}
				this.collegeModal = false
				this.loadHome()
			},
			selectCollege(item) {
				this.college = item.collegeName
				uni.setStorageSync('planetCollege_' + this.regionId, this.college)
			},
			levelSeed() {
				const d = new Date()
				const p = n => (n < 10 ? '0' + n : n)
				return `${this.regionId}_${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}_college_boost_v2`
			},
			seededRandom(seed) {
				let h = 2166136261
				for (let i = 0; i < seed.length; i++) {
					h ^= seed.charCodeAt(i)
					h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)
				}
				return function() {
					h += 0x6D2B79F5
					let t = h
					t = Math.imul(t ^ (t >>> 15), t | 1)
					t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
					return ((t ^ (t >>> 14)) >>> 0) / 4294967296
				}
			},
			seededShuffle(arr, seed) {
				const rand = this.seededRandom(seed)
				return this.shuffleWithRandom(arr, rand)
			},
			shuffleWithRandom(arr, rand) {
				for (let i = arr.length - 1; i > 0; i--) {
					const j = Math.floor(rand() * (i + 1))
					const t = arr[i]
					arr[i] = arr[j]
					arr[j] = t
				}
				return arr
			},
			buildFallbackPositions() {
				const cards = []
				let idx = 0
				for (let layer = 0; layer < 3; layer++) {
					for (let i = 0; i < 27; i++) {
						const col = i % 9
						const row = Math.floor(i / 9)
						const card = {
							id: 'f' + idx,
							order: idx,
							icon: '',
							layer,
							x: 10 + col * 68 + (layer * 6),
							y: 28 + row * 94 + (layer * 34),
							style: '',
							removed: false,
							selected: false,
							locked: false,
							className: '',
							trap: false,
							keyCard: false
						}
						card.style = `left:${card.x}rpx;top:${card.y}rpx;z-index:${this.cardZIndex(card)};`
						cards.push(card)
						idx++
					}
				}
				return cards
			},
			goBack() {
				const pages = getCurrentPages ? getCurrentPages() : []
				if (pages.length > 1) {
					uni.navigateBack({ delta: 1 })
					return
				}
				uni.redirectTo({ url: '/package1/planet/index' })
			}
		}
	}
</script>

<style lang="scss" scoped>
	.boost { min-height: 100vh; background: linear-gradient(155deg, #F3FFF4 0%, #EAF8FF 46%, #FFF7DE 100%); color: #12342F; }
	.nav { position: fixed; top: 0; left: 0; right: 0; z-index: 200; box-sizing: border-box; display: flex; align-items: center; justify-content: center; background: rgba(243,255,244,0.9); }
	.nav-back { position: absolute; left: 24rpx; bottom: 12rpx; width: 56rpx; height: 56rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: rgba(255,255,255,.72); color: #12342F; box-shadow: 0 8rpx 20rpx rgba(18,52,47,.08); }
	.nav-back text { padding-bottom: 14rpx;padding-right: 4rpx;font-size: 62rpx; line-height: 1;font-weight: 500; transform: translateY(-2rpx); }
	.nav-title { padding-bottom: 14rpx; color: #12342F; font-size: 32rpx; font-weight: 900; }
	.page { height: 100vh; box-sizing: border-box; padding-left: 24rpx; padding-right: 24rpx; }
	.hero, .rank-overview, .game-card { margin-bottom: 24rpx; border-radius: 42rpx; background: rgba(255,255,255,0.82); padding: 30rpx; box-shadow: 0 16rpx 42rpx rgba(79,183,255,0.10); }
	.hero-kicker { color: #FF9C42; font-size: 22rpx; font-weight: 900; letter-spacing: 2rpx; }
	.hero-title { margin-top: 10rpx; font-size: 42rpx; font-weight: 900; }
	.hero-sub, .tips { margin-top: 16rpx; color: #6B817D; font-size: 24rpx; line-height: 1.55; }
	.hero-reward-tip { position: relative; margin-top: 20rpx; padding: 20rpx 22rpx 20rpx 86rpx; border-radius: 24rpx; background: linear-gradient(135deg, rgba(255,247,221,0.96), rgba(255,255,255,0.9) 48%, rgba(229,255,246,0.92)); border: 3rpx solid rgba(255,184,77,0.58); box-shadow: 0 16rpx 34rpx rgba(255,184,77,0.18), inset 0 0 0 2rpx rgba(255,255,255,0.72); overflow: hidden; }
	.hero-reward-tip:before { content: ''; position: absolute; right: -48rpx; top: -62rpx; width: 180rpx; height: 180rpx; border-radius: 50%; background: radial-gradient(circle, rgba(255,184,77,0.26), transparent 66%); }
	.hero-reward-tip:after { content: '现金奖励'; position: absolute; right: 10rpx; top: 14rpx; padding: 4rpx 14rpx; border-radius: 999rpx; background: linear-gradient(135deg, #FFB84D, #35D6A6); color: #FFFFFF; font-size: 19rpx; font-weight: 900; }
	.hero-reward-icon { position: absolute; left: 20rpx; top: 50%; transform: translateY(-50%); width: 48rpx; height: 48rpx; line-height: 48rpx; border-radius: 50%; text-align: center; background: rgba(255,255,255,0.82); box-shadow: 0 8rpx 18rpx rgba(255,184,77,0.18); font-size: 28rpx; }
	.hero-reward-copy { position: relative; z-index: 1; padding-right: 92rpx; color: #6A4A1D; font-size: 24rpx; line-height: 1.55; font-weight: 800; }
	.tips { display: flex; flex-direction: column; gap: 6rpx; }
	.countdown-card { margin-top: 16rpx; padding: 18rpx 20rpx; border-radius: 28rpx; background: linear-gradient(135deg, rgba(53,214,166,0.14), rgba(79,183,255,0.16)); border: 2rpx solid rgba(53,214,166,0.22); }
	.countdown-card.warning { background: linear-gradient(135deg, rgba(255,184,77,0.18), rgba(255,244,215,0.9)); border-color: rgba(255,184,77,0.42); }
	.countdown-card.danger { background: linear-gradient(135deg, rgba(255,76,76,0.18), rgba(255,232,232,0.96)); border-color: rgba(255,76,76,0.42); }
	.countdown-main { display: flex; align-items: center; gap: 16rpx; }
	.countdown-main > text { width: 58rpx; height: 58rpx; line-height: 58rpx; border-radius: 50%; text-align: center; background: #fff; font-size: 32rpx; box-shadow: 0 8rpx 18rpx rgba(18,52,47,0.08); }
	.countdown-main view { display: flex; flex-direction: column; }
	.countdown-main view:not(.restart-left) { flex: 1; min-width: 0; }
	.countdown-main view text:first-child { color: #12342F; font-size: 42rpx; font-weight: 900; line-height: 1; }
	.countdown-main view text:last-child { margin-top: 8rpx; color: #6B817D; font-size: 22rpx; }
	.restart-left { width: 132rpx; height: 72rpx; border-radius: 22rpx; align-items: center; justify-content: center; background: rgba(255,255,255,0.78); border: 2rpx solid rgba(53,214,166,0.18); box-shadow: 0 8rpx 18rpx rgba(18,52,47,0.06); }
	.countdown-main .restart-left text:first-child { color: #22B889; font-size: 30rpx; font-weight: 900; line-height: 1; }
	.countdown-main .restart-left text:last-child { margin-top: 6rpx; color: #6B817D; font-size: 20rpx; font-weight: 800; }
	.countdown-card.warning .countdown-main view text:first-child { color: #D88400; }
	.countdown-card.danger .countdown-main view text:first-child { color: #F04444; }
	.countdown-track { margin-top: 16rpx; height: 14rpx; border-radius: 999rpx; background: rgba(255,255,255,0.72); overflow: hidden; }
	.countdown-progress { height: 100%; border-radius: 999rpx; background: linear-gradient(90deg, #35D6A6, #4FB7FF); transition: width .25s linear; }
	.countdown-card.warning .countdown-progress { background: linear-gradient(90deg, #FFB84D, #FF8A3D); }
	.countdown-card.danger .countdown-progress { background: linear-gradient(90deg, #FF6B6B, #F04444); }
	.debug-panel { margin-top: 16rpx; display: flex; gap: 10rpx; }
	.debug-panel view { flex: 1; border-radius: 20rpx; padding: 12rpx 6rpx; background: rgba(255,255,255,0.76); text-align: center; border: 1rpx solid rgba(53,214,166,0.16); }
	.debug-panel text:first-child { display: block; color: #22B889; font-size: 28rpx; font-weight: 900; }
	.debug-panel text:last-child { display: block; margin-top: 4rpx; color: #8AA09C; font-size: 20rpx; }
	.college-picker { margin-top: 18rpx; padding: 18rpx 22rpx; border-radius: 26rpx; background: rgba(255,255,255,0.72); display: flex; align-items: center; justify-content: space-between; color: #42635E; font-size: 24rpx; }
	.college-picker text:last-child { color: #22B889; font-weight: 900; }
	.rocket { margin-top: 22rpx; display: flex; align-items: center; gap: 16rpx; }
	.rocket-body { font-size: 56rpx; }
	.rocket-line { flex: 1; height: 18rpx; border-radius: 999rpx; background: #E6F2EF; overflow: hidden; }
	.rocket-progress { height: 100%; border-radius: 999rpx; background: linear-gradient(90deg, #35D6A6, #4FB7FF, #FFB84D); }
	.section-head { display: flex; justify-content: space-between; align-items: center; font-size: 24rpx; color: #6B817D; }
	.section-head text:first-child { color: #12342F; font-size: 31rpx; font-weight: 900; }
	.reward-record-btn { padding: 8rpx 18rpx; border-radius: 999rpx; background: rgba(255,184,77,0.18); color: #D88400; font-size: 22rpx; font-weight: 900; border: 2rpx solid rgba(255,184,77,0.36); }
	.rank-overview { padding: 24rpx; background: radial-gradient(circle at 90% 8%, rgba(255,184,77,.22), transparent 34%), linear-gradient(135deg, rgba(255,255,255,.9), rgba(242,250,255,.82)); }
	.rank-mini-grid { margin-top: 18rpx; display: flex; gap: 16rpx; }
	.rank-mini-panel { flex: 1; min-width: 0; padding: 16rpx; border-radius: 28rpx; background: rgba(255,255,255,.72); border: 2rpx solid rgba(255,255,255,.68); }
	.rank-mini-title { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12rpx; }
	.rank-mini-title text:first-child { color: #12342F; font-size: 24rpx; font-weight: 900; }
	.rank-mini-title text:last-child { color: #8AA09C; font-size: 20rpx; font-weight: 800; }
	.glory-mini-list, .college-mini-list { display: flex; flex-direction: column; gap: 8rpx; }
	.glory-mini-item, .college-mini-item { min-height: 50rpx; display: flex; align-items: center; gap: 10rpx; }
	.glory-mini-item.rank-0 .mini-score { color: #FF9C42; }
	.mini-medal { width: 40rpx; height: 40rpx; line-height: 40rpx; border-radius: 50%; background: #FFF1C7; color: #B87932; text-align: center; font-size: 22rpx; font-weight: 900; flex-shrink: 0; }
	.mini-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
	.mini-main text:first-child { color: #12342F; font-size: 22rpx; font-weight: 900; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
	.mini-main text:last-child { margin-top: 2rpx; color: #8AA09C; font-size: 18rpx; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
	.mini-score { min-width: 58rpx; text-align: right; color: #FF9C42; font-size: 23rpx; font-weight: 900; }
	.college-score { color: #22B889; }
	.mini-bar { margin-top: 8rpx; height: 8rpx; background: #EEF7F4; border-radius: 999rpx; overflow: hidden; }
	.mini-bar view { height: 100%; border-radius: 999rpx; background: linear-gradient(90deg, #35D6A6, #4FB7FF); }
	.mini-empty { margin-top: 28rpx; font-size: 21rpx; text-align: center; }
	.timer-pill { min-width: 126rpx; height: 48rpx; padding: 0 18rpx; border-radius: 999rpx; background: #EFFFF8; color: #20B486; display: flex; align-items: center; justify-content: center; font-size: 26rpx; font-weight: 900; box-shadow: 0 8rpx 20rpx rgba(32,180,134,0.12); }
	.timer-pill.warning { background: #FFF4D7; color: #D88400; box-shadow: 0 8rpx 22rpx rgba(216,132,0,0.16); }
	.timer-pill.danger { background: #FFE8E8; color: #F04444; box-shadow: 0 8rpx 24rpx rgba(240,68,68,0.2); }
	.arena-share { margin: 18rpx 0 0; height: 64rpx; line-height: 64rpx; border-radius: 999rpx; background: linear-gradient(135deg, #7C3AED, #38BDF8); color: #fff; font-size: 24rpx; font-weight: 900; box-shadow: 0 12rpx 26rpx rgba(124,58,237,.16); }
	.arena-share:after, .invite-share:after { border: 0; }
	.start-strip { margin-top: 20rpx; padding: 20rpx 24rpx; border-radius: 28rpx; background: linear-gradient(135deg, #35D6A6, #4FB7FF); color: #fff; display: flex; align-items: center; justify-content: space-between; box-shadow: 0 14rpx 34rpx rgba(53,214,166,0.2); }
	.start-strip text:first-child { font-size: 30rpx; font-weight: 900; }
	.start-strip text:last-child { font-size: 22rpx; opacity: .88; }
	.start-strip.done { background: rgba(255,255,255,0.8); color: #6B817D; box-shadow: none; }
	.start-strip.disabled { opacity: .45; }
	.empty { margin-top: 18rpx; color: #8AA09C; font-size: 24rpx; }
	.board { margin-top: 22rpx; height: 620rpx; border-radius: 36rpx; background: linear-gradient(155deg, rgba(234,248,255,0.9), rgba(255,248,222,0.72)); position: relative; overflow: hidden; z-index: 1; }
	.tile { position: absolute; width: 76rpx; height: 76rpx; border-radius: 22rpx; background: #fff; display: flex; align-items: center; justify-content: center; box-shadow: 0 10rpx 22rpx rgba(66,99,94,0.12); border: 2rpx solid rgba(255,255,255,0.9); transition: transform .12s ease, opacity .12s ease; }
	.tile text { font-size: 42rpx; }
	.tile .debug-layer { position: absolute; right: 6rpx; bottom: 4rpx; font-size: 16rpx; color: #8AA09C; line-height: 1; }
	.tile.active { background: #fff; box-shadow: 0 12rpx 24rpx rgba(66,99,94,0.14); }
	.tile.locked { opacity: .45; filter: grayscale(100%); background: #F3F6F5; box-shadow: 0 6rpx 14rpx rgba(66,99,94,0.08); }
	.tile.hidden-card { opacity: .32; background: #fff; border-radius: 22rpx; box-shadow: none; color: #BFC9D4; }
	.tile.hidden-card text:first-child { display: block; font-size: 36rpx; color: #BFC9D4; }
	.tile.selected { opacity: 0; transform: scale(.5); pointer-events: none; }
	.slot-wrap { margin-top: 24rpx; }
	.slot-title { color: #6B817D; font-size: 24rpx; margin-bottom: 14rpx; }
	.slots { display: flex; gap: 10rpx; }
	.slot { flex: 1; height: 66rpx; border-radius: 18rpx; background: rgba(255,255,255,0.8); border: 2rpx dashed rgba(53,214,166,0.28); display: flex; align-items: center; justify-content: center; }
	.slot text { font-size: 34rpx; }
	.actions { margin-top: 28rpx; display: flex; gap: 18rpx; }
	.sub-btn, .main-btn { flex: 1; height: 82rpx; line-height: 82rpx; text-align: center; border-radius: 999rpx; font-size: 28rpx; font-weight: 900; }
	.sub-btn { background: rgba(255,255,255,0.78); color: #42635E; }
	.main-btn { background: linear-gradient(135deg, #35D6A6, #4FB7FF); color: #fff; box-shadow: 0 14rpx 34rpx rgba(53,214,166,0.2); }
	.full-btn { width: 100%; }
	.main-btn.disabled { opacity: .45; }
	.modal { position: fixed; inset: 0; z-index: 9999; background: rgba(18,52,47,0.2); display: flex; align-items: center; justify-content: center; padding: 46rpx; }
	.modal-card { width: 100%; border-radius: 42rpx; background: #fff; padding: 42rpx; text-align: center; }
	.modal-title { font-size: 40rpx; font-weight: 900; }
	.modal-sub { margin: 18rpx 0 30rpx; color: #6B817D; font-size: 26rpx; line-height: 1.55; }
	.invite-share { margin: 0 0 18rpx; height: 76rpx; line-height: 76rpx; border-radius: 999rpx; background: linear-gradient(135deg, #35D6A6, #FFB84D); color: #fff; font-size: 27rpx; font-weight: 900; box-shadow: 0 14rpx 30rpx rgba(53,214,166,.18); }
	.reward-modal { text-align: left; }
	.reward-modal .modal-title, .reward-modal .modal-sub { text-align: center; }
	.reward-modal-grid { display: flex; flex-direction: column; gap: 18rpx; margin-bottom: 26rpx; }
	.reward-modal-panel { padding: 18rpx; border-radius: 26rpx; background: #F5FBFA; border: 2rpx solid rgba(53,214,166,0.12); }
	.reward-modal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12rpx; }
	.reward-modal-head text:first-child { color: #12342F; font-size: 26rpx; font-weight: 900; }
	.reward-modal-head text:last-child { color: #8AA09C; font-size: 21rpx; font-weight: 800; }
	.reward-record-list { display: flex; flex-direction: column; gap: 10rpx; }
	.reward-record-row { min-height: 58rpx; display: flex; align-items: center; gap: 12rpx; }
	.reward-record-row .reward-content { align-self: flex-start; display: inline-flex; width: fit-content; max-width: 100%; margin-top: 6rpx; padding: 4rpx 12rpx; border-radius: 999rpx; font-size: 20rpx; font-weight: 900; line-height: 1.25; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
	.reward-record-row .reward-content.personal { color: #D88400; background: linear-gradient(135deg, rgba(255,184,77,0.22), rgba(255,245,214,0.96)); border: 2rpx solid rgba(255,184,77,0.46); box-shadow: 0 6rpx 14rpx rgba(255,184,77,0.12); }
	.reward-record-row .reward-content.college { color: #16A779; background: linear-gradient(135deg, rgba(53,214,166,0.18), rgba(235,255,249,0.96)); border: 2rpx solid rgba(53,214,166,0.36); box-shadow: 0 6rpx 14rpx rgba(53,214,166,0.10); }
	.college-modal { text-align: left; }
	.college-modal .modal-title, .college-modal .modal-sub { text-align: center; }
	.college-list { max-height: 560rpx; margin-bottom: 24rpx; }
	.college-option { margin-bottom: 14rpx; padding: 20rpx; border-radius: 26rpx; background: #F5FBFA; display: flex; align-items: center; gap: 18rpx; border: 2rpx solid transparent; }
	.college-option.active { border-color: #35D6A6; background: #EEFFF9; }
	.college-option > text { font-size: 42rpx; width: 56rpx; text-align: center; }
	.college-option view { display: flex; flex-direction: column; }
	.college-option view text:first-child { color: #12342F; font-size: 28rpx; font-weight: 900; }
	.college-option view text:last-child { color: #6B817D; font-size: 22rpx; margin-top: 6rpx; }
</style>
