qaunji
This commit is contained in:
157
frontend/src/components/LaborDayPage.vue
Normal file
157
frontend/src/components/LaborDayPage.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<script setup lang="ts">
|
||||
import { usePointerSwipe, usePreferredReducedMotion } from '@vueuse/core'
|
||||
import { computed, ref } from 'vue'
|
||||
import type { Memory } from '../api/client'
|
||||
import { getStandardNepalTheme } from '../constants/nepalThemes'
|
||||
import PolaroidCard from './PolaroidCard.vue'
|
||||
import { renderMarkdown } from '../utils/renderMarkdown'
|
||||
|
||||
const props = defineProps<{
|
||||
memory: Memory
|
||||
}>()
|
||||
|
||||
const stageRef = ref<HTMLElement | null>(null)
|
||||
const openAmount = ref(0)
|
||||
const reduceMotion = usePreferredReducedMotion()
|
||||
const theme = computed(() => getStandardNepalTheme(props.memory.nepal_art_style))
|
||||
const bodyHtml = computed(() => renderMarkdown(props.memory.main_text))
|
||||
|
||||
usePointerSwipe(stageRef, {
|
||||
disableTextSelect: true,
|
||||
onSwipeEnd(_e, direction) {
|
||||
if (reduceMotion.value === 'reduce') return
|
||||
if (direction === 'left') openAmount.value = Math.min(1, openAmount.value + 0.34)
|
||||
if (direction === 'right') openAmount.value = Math.max(0, openAmount.value - 0.34)
|
||||
},
|
||||
})
|
||||
|
||||
const isOpen = computed(() => openAmount.value > 0.45)
|
||||
const curtainShift = computed(() => `${openAmount.value * 86}%`)
|
||||
const intimacyTags = computed(() =>
|
||||
isOpen.value ? ['缠绵', '很累', '靠近得更彻底'] : ['暖灯', '床褶', '呼吸感'],
|
||||
)
|
||||
const stageCopy = computed(() => {
|
||||
if (isOpen.value) return '幕布拉开以后,呼吸、灯影、褶皱全都开始说话。'
|
||||
return '把幕布拉开一点,这一页才会真正进入劳动节的“劳动”。'
|
||||
})
|
||||
|
||||
function toggleCurtain() {
|
||||
openAmount.value = isOpen.value ? 0 : 1
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="relative mx-auto max-w-lg overflow-hidden rounded-3xl bg-gradient-to-br px-3 pb-8 pt-6 sm:px-5 sm:pb-10 sm:pt-8"
|
||||
:class="theme.gradient"
|
||||
>
|
||||
<div class="pointer-events-none absolute -left-10 top-10 h-48 w-48 rounded-full bg-rose-300/28 blur-[3rem]" aria-hidden="true" />
|
||||
<div class="pointer-events-none absolute -right-10 bottom-8 h-56 w-56 rounded-full bg-amber-300/20 blur-[3.8rem]" aria-hidden="true" />
|
||||
|
||||
<div
|
||||
class="relative z-[1] mx-auto mb-7 max-w-[min(100%,400px)] overflow-hidden rounded-[2rem] border border-rose-200/65 bg-[linear-gradient(180deg,#fff6f4_0%,#ffece2_100%)] shadow-[0_24px_60px_-18px_rgba(190,80,120,0.24)]"
|
||||
>
|
||||
<div class="pointer-events-none absolute inset-0 nepal-fiber opacity-[0.12] mix-blend-multiply" />
|
||||
|
||||
<div class="relative px-5 pb-5 pt-5 sm:px-6 sm:pb-6">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-[10px] font-semibold uppercase tracking-[0.3em] text-rose-700/66">
|
||||
Labor Day / 05.01
|
||||
</p>
|
||||
<h3 class="mt-2 font-display-cn text-[1.9rem] leading-none text-rose-950">
|
||||
劳动节“劳动”
|
||||
</h3>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-rose-200/70 bg-white/88 px-3 py-2 text-right shadow-[0_8px_18px_rgba(190,80,120,0.12)]">
|
||||
<p class="text-[9px] font-semibold tracking-[0.22em] text-rose-700/70">
|
||||
Intimacy Meter
|
||||
</p>
|
||||
<p class="mt-1 font-brush text-xl text-rose-950">{{ isOpen ? '很累嘛' : '快拉开' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 rounded-[1.8rem] border border-rose-200/60 bg-white/74 p-4 shadow-inner">
|
||||
<div
|
||||
ref="stageRef"
|
||||
class="relative overflow-hidden rounded-[1.6rem] bg-gradient-to-b from-[#7f3b4f] via-[#b46878] to-[#f6cdb0] px-4 pb-5 pt-5"
|
||||
style="touch-action: none"
|
||||
>
|
||||
<div class="absolute inset-x-0 top-0 h-12 bg-gradient-to-b from-white/15 to-transparent" aria-hidden="true" />
|
||||
<div class="absolute right-7 top-5 h-12 w-12 rounded-full bg-[#ffd89c] shadow-[0_0_30px_rgba(255,216,156,0.35)]" aria-hidden="true" />
|
||||
|
||||
<div class="relative h-[230px] overflow-hidden rounded-[1.35rem] border border-white/18 bg-[#4a2730]">
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-[#6d3244] via-[#8f4d62] to-[#352028]" />
|
||||
<div class="absolute left-1/2 top-4 h-3 w-24 -translate-x-1/2 rounded-full bg-white/10 blur-md" aria-hidden="true" />
|
||||
|
||||
<div class="absolute bottom-0 left-0 right-0 h-24 rounded-t-[2rem] bg-gradient-to-b from-[#f8e6d8] to-[#ebc7b0]" />
|
||||
<div class="absolute bottom-7 left-[18%] h-16 w-[64%] rounded-[2rem] bg-[#fff3ea]/70 shadow-[0_18px_24px_rgba(80,35,45,0.16)]" />
|
||||
<div class="absolute bottom-11 left-[28%] h-12 w-16 rounded-full bg-[#d89a87]/70 blur-[1px]" />
|
||||
<div class="absolute bottom-10 right-[28%] h-12 w-16 rounded-full bg-[#d89a87]/70 blur-[1px]" />
|
||||
<div class="absolute bottom-[5.2rem] left-[36%] h-10 w-7 rounded-full bg-[#f4ccb7]/85" />
|
||||
<div class="absolute bottom-[5.2rem] right-[36%] h-10 w-7 rounded-full bg-[#f4ccb7]/85" />
|
||||
<div
|
||||
class="absolute left-1/2 top-[5.6rem] z-[2] -translate-x-1/2 text-3xl transition-all duration-500"
|
||||
:class="isOpen ? 'scale-100 opacity-100' : 'scale-75 opacity-0'"
|
||||
>
|
||||
♡
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="absolute inset-y-0 left-0 z-[3] w-[52%] bg-[linear-gradient(180deg,rgba(255,245,244,0.97)_0%,rgba(244,196,203,0.92)_100%)] shadow-[14px_0_30px_rgba(127,59,79,0.18)] transition-transform duration-700 ease-[cubic-bezier(0.22,1,0.36,1)]"
|
||||
:style="{ transform: `translateX(-${curtainShift})` }"
|
||||
/>
|
||||
<div
|
||||
class="absolute inset-y-0 right-0 z-[3] w-[52%] bg-[linear-gradient(180deg,rgba(255,245,244,0.97)_0%,rgba(244,196,203,0.92)_100%)] shadow-[-14px_0_30px_rgba(127,59,79,0.18)] transition-transform duration-700 ease-[cubic-bezier(0.22,1,0.36,1)]"
|
||||
:style="{ transform: `translateX(${curtainShift})` }"
|
||||
/>
|
||||
<div class="absolute left-[calc(50%-2px)] top-0 z-[4] h-full w-1 rounded-full bg-white/22" aria-hidden="true" />
|
||||
</div>
|
||||
|
||||
<div class="mt-4 rounded-[1.25rem] border border-white/55 bg-white/78 px-4 py-3 text-center shadow-[0_10px_18px_rgba(120,53,15,0.08)]">
|
||||
<p class="text-[10px] font-semibold uppercase tracking-[0.24em] text-rose-700/65">
|
||||
Pull The Curtain
|
||||
</p>
|
||||
<p class="mt-2 text-sm leading-relaxed text-rose-950/88">
|
||||
{{ stageCopy }}
|
||||
</p>
|
||||
<div class="mt-3 flex flex-wrap justify-center gap-2">
|
||||
<span
|
||||
v-for="tag in intimacyTags"
|
||||
:key="tag"
|
||||
class="rounded-full border border-rose-200 bg-rose-50/90 px-2.5 py-1 text-[10px] font-medium text-rose-900/85"
|
||||
>
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full border border-rose-300/70 bg-white/88 px-4 py-2 text-xs font-semibold tracking-[0.18em] text-rose-950 transition hover:-translate-y-0.5 hover:bg-white"
|
||||
@click="toggleCurtain"
|
||||
>
|
||||
{{ isOpen ? '把幕布合回去' : '拉开被单 / 幕布' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative z-[1] flex flex-col gap-6 sm:gap-8">
|
||||
<PolaroidCard :memory="memory" :index="memory.story_order" compact />
|
||||
|
||||
<div
|
||||
class="nepal-torn-panel mx-auto max-w-md border border-rose-200/60 bg-gradient-to-b from-[#fffdfa] to-rose-50/35 px-5 py-6 text-left shadow-[0_12px_30px_-15px_rgba(190,80,120,0.18)] sm:px-6 sm:py-7"
|
||||
>
|
||||
<div class="mb-4 h-1 w-12 rounded-full bg-gradient-to-r from-rose-400 to-amber-400/80" aria-hidden="true" />
|
||||
<div
|
||||
class="text-[15px] leading-[1.8] tracking-wide text-rose-950/88 [&_a]:font-bold [&_a]:text-rose-700 [&_a]:underline [&_a]:decoration-rose-300 [&_a]:underline-offset-4 [&_blockquote]:border-l-4 [&_blockquote]:border-rose-300/60 [&_blockquote]:bg-rose-50/50 [&_blockquote]:py-1 [&_blockquote]:pl-3 [&_blockquote]:text-rose-900/72 [&_code]:rounded [&_code]:bg-rose-100/55 [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:font-mono [&_strong]:text-rose-900 [&_p+p]:mt-4"
|
||||
v-html="bodyHtml"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
168
frontend/src/components/QuanjiPage.vue
Normal file
168
frontend/src/components/QuanjiPage.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<script setup lang="ts">
|
||||
import { usePreferredReducedMotion } from '@vueuse/core'
|
||||
import { computed, onBeforeUnmount, ref } from 'vue'
|
||||
import type { Memory } from '../api/client'
|
||||
import { getStandardNepalTheme } from '../constants/nepalThemes'
|
||||
import PolaroidCard from './PolaroidCard.vue'
|
||||
import { renderMarkdown } from '../utils/renderMarkdown'
|
||||
|
||||
const props = defineProps<{
|
||||
memory: Memory
|
||||
}>()
|
||||
|
||||
const bodyHtml = computed(() => renderMarkdown(props.memory.main_text))
|
||||
const theme = computed(() => getStandardNepalTheme(props.memory.nepal_art_style))
|
||||
const reduceMotion = usePreferredReducedMotion()
|
||||
|
||||
const phase = ref<'idle' | 'opening' | 'opened'>('idle')
|
||||
let doorTimer: ReturnType<typeof setTimeout> | undefined
|
||||
|
||||
const roomText = computed(() => {
|
||||
if (phase.value === 'opened') return '门打开以后,故事就从“见面”走向了“爱的开始”。'
|
||||
if (phase.value === 'opening') return '房卡刷过的一瞬间,灯光和情绪一起变暖。'
|
||||
return '点一下房卡,把门打开,把这一页真正带进去。'
|
||||
})
|
||||
|
||||
function openRoom() {
|
||||
if (phase.value === 'opened') {
|
||||
phase.value = 'idle'
|
||||
return
|
||||
}
|
||||
if (doorTimer) clearTimeout(doorTimer)
|
||||
if (reduceMotion.value === 'reduce') {
|
||||
phase.value = 'opened'
|
||||
return
|
||||
}
|
||||
phase.value = 'opening'
|
||||
doorTimer = setTimeout(() => {
|
||||
phase.value = 'opened'
|
||||
doorTimer = undefined
|
||||
}, 950)
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (doorTimer) clearTimeout(doorTimer)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="relative mx-auto max-w-lg overflow-hidden rounded-3xl bg-gradient-to-br px-3 pb-8 pt-6 sm:px-5 sm:pb-10 sm:pt-8"
|
||||
:class="theme.gradient"
|
||||
>
|
||||
<div class="pointer-events-none absolute -left-10 top-10 h-44 w-44 rounded-full bg-amber-300/24 blur-[3rem]" aria-hidden="true" />
|
||||
<div class="pointer-events-none absolute -right-12 bottom-8 h-52 w-52 rounded-full bg-rose-300/18 blur-[3.4rem]" aria-hidden="true" />
|
||||
|
||||
<div
|
||||
class="relative z-[1] mx-auto mb-7 max-w-[min(100%,398px)] overflow-hidden rounded-[2rem] border border-amber-200/65 bg-[linear-gradient(180deg,#fff8f2_0%,#ffefdf_100%)] shadow-[0_24px_56px_-18px_rgba(180,83,9,0.24)]"
|
||||
>
|
||||
<div class="pointer-events-none absolute inset-0 nepal-fiber opacity-[0.12] mix-blend-multiply" />
|
||||
|
||||
<div class="relative px-5 pb-5 pt-5 sm:px-6 sm:pb-6">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-[10px] font-semibold uppercase tracking-[0.3em] text-amber-700/68">
|
||||
Hotel Night / 04.30
|
||||
</p>
|
||||
<h3 class="mt-2 font-display-cn text-[1.9rem] leading-none text-amber-950">
|
||||
全季M爱
|
||||
</h3>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-amber-200/70 bg-white/88 px-3 py-2 text-right shadow-[0_8px_18px_rgba(245,158,11,0.14)]">
|
||||
<p class="text-[9px] font-semibold tracking-[0.22em] text-amber-700/70">
|
||||
Room Mood
|
||||
</p>
|
||||
<p class="mt-1 font-brush text-xl text-amber-950">爱的开始</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 rounded-[1.8rem] border border-amber-200/60 bg-white/74 p-4 shadow-inner">
|
||||
<div class="relative overflow-hidden rounded-[1.55rem] bg-gradient-to-b from-[#efe7dd] via-[#e4d3c4] to-[#d1b8a1] px-4 pb-5 pt-5">
|
||||
<div class="absolute inset-y-0 left-[51%] z-[1] w-[44%] bg-gradient-to-l from-amber-200/38 to-transparent opacity-0 transition-opacity duration-700" :class="phase !== 'idle' ? 'opacity-100' : ''" aria-hidden="true" />
|
||||
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="rounded-2xl border border-white/70 bg-white/78 px-3 py-2 shadow-[0_10px_18px_rgba(120,53,15,0.08)]">
|
||||
<p class="text-[10px] font-semibold uppercase tracking-[0.24em] text-amber-700/65">
|
||||
季 Hotel
|
||||
</p>
|
||||
<p class="mt-1 font-mono text-sm font-bold text-amber-950">04 · 30 / 2117</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-[1.2rem] border border-amber-200/80 bg-white/90 px-4 py-3 text-left shadow-[0_10px_20px_rgba(120,53,15,0.1)] transition hover:-translate-y-0.5 hover:bg-white"
|
||||
@click="openRoom"
|
||||
>
|
||||
<p class="text-[10px] font-semibold uppercase tracking-[0.24em] text-amber-700/65">
|
||||
房卡
|
||||
</p>
|
||||
<p class="mt-1 font-brush text-xl text-amber-950">刷开门</p>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="relative mt-5 h-[220px] overflow-hidden rounded-[1.45rem] border border-[#9b765e]/30 bg-[#3c2f2c] shadow-[inset_0_2px_4px_rgba(255,255,255,0.12)]">
|
||||
<div
|
||||
class="absolute inset-y-0 left-0 w-1/2 border-r border-[#6f5547]/45 bg-[linear-gradient(180deg,#6c5347_0%,#4e3a31_100%)] transition-transform duration-700 ease-[cubic-bezier(0.22,1,0.36,1)]"
|
||||
:class="phase === 'idle' ? '' : 'translate-x-[-76%]'"
|
||||
/>
|
||||
<div
|
||||
class="absolute inset-y-0 right-0 w-1/2 border-l border-[#6f5547]/45 bg-[linear-gradient(180deg,#6c5347_0%,#4e3a31_100%)] transition-transform duration-700 ease-[cubic-bezier(0.22,1,0.36,1)]"
|
||||
:class="phase === 'idle' ? '' : 'translate-x-[76%]'"
|
||||
/>
|
||||
<div class="absolute inset-x-[19%] top-4 h-3 rounded-full bg-white/12 blur-sm" aria-hidden="true" />
|
||||
|
||||
<div class="absolute inset-0 z-0 bg-gradient-to-b from-[#fff0d2]/18 via-[#f7dba7]/16 to-[#5a3c2d]/0 transition-opacity duration-700" :class="phase === 'idle' ? 'opacity-0' : 'opacity-100'" />
|
||||
|
||||
<div class="absolute inset-0 z-0 px-6 pb-5 pt-6">
|
||||
<div class="mx-auto h-16 w-10 rounded-t-full bg-[#f4cb73] shadow-[0_0_28px_rgba(251,191,36,0.3)]" />
|
||||
<div class="mx-auto h-12 w-2 bg-[#6b4f3a]" />
|
||||
|
||||
<div class="mx-auto mt-4 flex max-w-[11rem] items-end justify-center gap-3">
|
||||
<div class="h-16 w-10 rounded-t-[1rem] bg-[#fff4ea]/78 shadow-sm" />
|
||||
<div class="h-20 w-24 rounded-[1.4rem] bg-[#f3d6c3]/72 shadow-[0_10px_20px_rgba(80,40,20,0.14)]" />
|
||||
<div class="h-12 w-12 rounded-t-[1rem] bg-[#fff4ea]/68 shadow-sm" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mx-auto mt-5 max-w-[12rem] rounded-[1.2rem] border border-white/45 bg-white/72 px-4 py-3 text-center shadow-[0_10px_22px_rgba(120,53,15,0.1)] transition-all duration-500"
|
||||
:class="phase === 'opened' ? 'translate-y-0 opacity-100' : 'translate-y-3 opacity-70'"
|
||||
>
|
||||
<p class="text-[10px] font-semibold uppercase tracking-[0.24em] text-amber-700/65">
|
||||
Door Status
|
||||
</p>
|
||||
<p class="mt-2 text-sm leading-relaxed text-amber-950/88">
|
||||
{{ roomText }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full border border-amber-300/70 bg-white/88 px-4 py-2 text-xs font-semibold tracking-[0.18em] text-amber-950 transition hover:-translate-y-0.5 hover:bg-white"
|
||||
@click="openRoom"
|
||||
>
|
||||
{{ phase === 'opened' ? '把门轻轻合上' : phase === 'opening' ? '门正在打开…' : '用房卡开门' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative z-[1] flex flex-col gap-6 sm:gap-8">
|
||||
<PolaroidCard :memory="memory" :index="memory.story_order" compact />
|
||||
|
||||
<div
|
||||
class="nepal-torn-panel mx-auto max-w-md border border-amber-200/60 bg-gradient-to-b from-[#fffdfa] to-amber-50/40 px-5 py-6 text-left shadow-[0_12px_30px_-15px_rgba(180,83,9,0.2)] sm:px-6 sm:py-7"
|
||||
>
|
||||
<div class="mb-4 h-1 w-12 rounded-full bg-gradient-to-r from-amber-400 to-rose-400/80" aria-hidden="true" />
|
||||
<div
|
||||
class="text-[15px] leading-[1.8] tracking-wide text-amber-950/90 [&_a]:font-bold [&_a]:text-rose-600 [&_a]:underline [&_a]:decoration-rose-300 [&_a]:underline-offset-4 [&_blockquote]:border-l-4 [&_blockquote]:border-rose-300/60 [&_blockquote]:bg-rose-50/30 [&_blockquote]:py-1 [&_blockquote]:pl-3 [&_blockquote]:text-rose-900/70 [&_code]:rounded [&_code]:bg-amber-100/50 [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:font-mono [&_strong]:text-rose-900 [&_p+p]:mt-4"
|
||||
v-html="bodyHtml"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
200
frontend/src/components/TaxiHandHoldPage.vue
Normal file
200
frontend/src/components/TaxiHandHoldPage.vue
Normal file
@@ -0,0 +1,200 @@
|
||||
<script setup lang="ts">
|
||||
import { usePreferredReducedMotion } from '@vueuse/core'
|
||||
import { computed, onBeforeUnmount, ref } from 'vue'
|
||||
import type { Memory } from '../api/client'
|
||||
import { getStandardNepalTheme } from '../constants/nepalThemes'
|
||||
import PolaroidCard from './PolaroidCard.vue'
|
||||
import { renderMarkdown } from '../utils/renderMarkdown'
|
||||
|
||||
const props = defineProps<{
|
||||
memory: Memory
|
||||
}>()
|
||||
|
||||
const bodyHtml = computed(() => renderMarkdown(props.memory.main_text))
|
||||
const theme = computed(() => getStandardNepalTheme(props.memory.nepal_art_style))
|
||||
const reduceMotion = usePreferredReducedMotion()
|
||||
|
||||
const phase = ref<'idle' | 'approaching' | 'held'>('idle')
|
||||
let holdTimer: ReturnType<typeof setTimeout> | undefined
|
||||
|
||||
const subtitle = computed(() => {
|
||||
if (phase.value === 'held') return '牵住了,连空气都跟着变暖。'
|
||||
if (phase.value === 'approaching') return '那一秒,心跳比出租车拐弯还快。'
|
||||
return '先让两只手慢慢靠近,再真正牵住。'
|
||||
})
|
||||
|
||||
function bringHandsTogether() {
|
||||
if (phase.value === 'held') {
|
||||
phase.value = 'idle'
|
||||
return
|
||||
}
|
||||
if (holdTimer) clearTimeout(holdTimer)
|
||||
if (reduceMotion.value === 'reduce') {
|
||||
phase.value = 'held'
|
||||
return
|
||||
}
|
||||
phase.value = 'approaching'
|
||||
holdTimer = setTimeout(() => {
|
||||
phase.value = 'held'
|
||||
holdTimer = undefined
|
||||
}, 900)
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (holdTimer) clearTimeout(holdTimer)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="relative mx-auto max-w-lg overflow-hidden rounded-3xl bg-gradient-to-br px-3 pb-8 pt-6 sm:px-5 sm:pb-10 sm:pt-8"
|
||||
:class="theme.gradient"
|
||||
>
|
||||
<div
|
||||
class="pointer-events-none absolute -left-10 top-8 h-48 w-48 rounded-full bg-amber-300/30 blur-[3rem]"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div
|
||||
class="pointer-events-none absolute -right-10 bottom-6 h-52 w-52 rounded-full bg-rose-300/22 blur-[3.4rem]"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="relative z-[1] mx-auto mb-7 max-w-[min(100%,398px)] overflow-hidden rounded-[2rem] border border-amber-200/65 bg-[linear-gradient(180deg,#fff7ef_0%,#fff0df_100%)] shadow-[0_24px_56px_-18px_rgba(180,83,9,0.24)]"
|
||||
>
|
||||
<div class="pointer-events-none absolute inset-0 nepal-fiber opacity-[0.12] mix-blend-multiply" />
|
||||
|
||||
<div class="relative px-5 pb-5 pt-5 sm:px-6 sm:pb-6">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-[10px] font-semibold uppercase tracking-[0.3em] text-amber-700/68">
|
||||
Taxi Ride / 04.30
|
||||
</p>
|
||||
<h3 class="mt-2 font-display-cn text-[1.9rem] leading-none text-amber-950">
|
||||
出租牵手
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
class="rounded-2xl border border-amber-200/70 bg-white/88 px-3 py-2 text-right shadow-[0_8px_18px_rgba(245,158,11,0.14)]"
|
||||
>
|
||||
<p class="text-[9px] font-semibold tracking-[0.22em] text-amber-700/70">
|
||||
After Meeting
|
||||
</p>
|
||||
<p class="mt-1 font-brush text-xl text-amber-950">心跳加速</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 rounded-[1.8rem] border border-amber-200/60 bg-white/74 p-4 shadow-inner">
|
||||
<div class="relative overflow-hidden rounded-[1.55rem] bg-gradient-to-b from-[#ffe8cf] via-[#fed7aa] to-[#f5b27f] px-4 pb-5 pt-4">
|
||||
<div class="absolute inset-x-0 top-0 h-12 bg-gradient-to-b from-white/20 to-transparent" aria-hidden="true" />
|
||||
<div class="absolute inset-y-4 left-3 w-5 rounded-full bg-[#2f2f35]/65" aria-hidden="true" />
|
||||
<div class="absolute inset-y-4 right-3 w-5 rounded-full bg-[#2f2f35]/65" aria-hidden="true" />
|
||||
|
||||
<div class="absolute left-8 top-8 h-24 w-[38%] overflow-hidden rounded-[1rem] border border-white/50 bg-[#12263a]" aria-hidden="true">
|
||||
<div class="taxi-window-lights h-full w-[180%]" :class="phase === 'held' && !reduceMotion ? 'taxi-window-fast' : ''" />
|
||||
</div>
|
||||
<div class="absolute right-8 top-8 h-24 w-[38%] overflow-hidden rounded-[1rem] border border-white/50 bg-[#12263a]" aria-hidden="true">
|
||||
<div class="taxi-window-lights h-full w-[180%]" :class="phase === 'held' && !reduceMotion ? 'taxi-window-fast' : ''" />
|
||||
</div>
|
||||
|
||||
<div class="absolute left-1/2 top-[5.7rem] z-[2] -translate-x-1/2 rotate-[-12deg]" aria-hidden="true">
|
||||
<div class="rounded-full border border-rose-200/70 bg-white/86 px-3 py-1 text-[11px] font-semibold text-rose-900 shadow-[0_10px_18px_rgba(190,24,93,0.12)]">
|
||||
花束卡住了一点点
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative mt-32 h-32">
|
||||
<div
|
||||
class="absolute bottom-3 left-5 h-20 w-24 rounded-[2rem] border border-[#734231]/55 bg-gradient-to-br from-[#f5c5b3] to-[#d9937b] shadow-[0_10px_20px_rgba(115,66,49,0.18)] transition-transform duration-700 ease-out"
|
||||
:class="phase === 'idle' ? 'rotate-[8deg]' : phase === 'approaching' ? 'translate-x-10 rotate-[4deg]' : 'translate-x-15 rotate-[2deg]'"
|
||||
>
|
||||
<div class="absolute right-2 top-4 h-5 w-5 rounded-full bg-[#f8e0d6]" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="absolute bottom-3 right-5 h-20 w-24 rounded-[2rem] border border-[#734231]/55 bg-gradient-to-bl from-[#f5c5b3] to-[#d9937b] shadow-[0_10px_20px_rgba(115,66,49,0.18)] transition-transform duration-700 ease-out"
|
||||
:class="phase === 'idle' ? 'rotate-[-8deg]' : phase === 'approaching' ? '-translate-x-10 rotate-[-4deg]' : '-translate-x-15 rotate-[-2deg]'"
|
||||
>
|
||||
<div class="absolute left-2 top-4 h-5 w-5 rounded-full bg-[#f8e0d6]" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="pointer-events-none absolute left-1/2 top-7 z-[3] -translate-x-1/2 text-3xl drop-shadow-[0_4px_10px_rgba(225,29,72,0.25)] transition-all duration-500"
|
||||
:class="phase === 'held' ? 'scale-100 opacity-100' : 'scale-75 opacity-0'"
|
||||
>
|
||||
♡
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 rounded-[1.2rem] border border-white/65 bg-white/74 px-4 py-3 text-center shadow-[0_10px_18px_rgba(180,83,9,0.08)]">
|
||||
<p class="text-[10px] font-semibold uppercase tracking-[0.24em] text-amber-700/65">
|
||||
Hold Hands
|
||||
</p>
|
||||
<p class="mt-2 text-sm leading-relaxed text-amber-950/88">
|
||||
{{ subtitle }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full border border-amber-300/70 bg-white/88 px-4 py-2 text-xs font-semibold tracking-[0.18em] text-amber-950 transition hover:-translate-y-0.5 hover:bg-white"
|
||||
@click="bringHandsTogether"
|
||||
>
|
||||
{{ phase === 'held' ? '再心动一次' : phase === 'approaching' ? '快要牵住了…' : '让双手靠近' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative z-[1] flex flex-col gap-6 sm:gap-8">
|
||||
<PolaroidCard :memory="memory" :index="memory.story_order" compact />
|
||||
|
||||
<div
|
||||
class="nepal-torn-panel mx-auto max-w-md border border-amber-200/60 bg-gradient-to-b from-[#fffdfa] to-amber-50/40 px-5 py-6 text-left shadow-[0_12px_30px_-15px_rgba(180,83,9,0.2)] sm:px-6 sm:py-7"
|
||||
>
|
||||
<div class="mb-4 h-1 w-12 rounded-full bg-gradient-to-r from-amber-400 to-rose-400/80" aria-hidden="true" />
|
||||
<div
|
||||
class="text-[15px] leading-[1.8] tracking-wide text-amber-950/90 [&_a]:font-bold [&_a]:text-rose-600 [&_a]:underline [&_a]:decoration-rose-300 [&_a]:underline-offset-4 [&_blockquote]:border-l-4 [&_blockquote]:border-rose-300/60 [&_blockquote]:bg-rose-50/30 [&_blockquote]:py-1 [&_blockquote]:pl-3 [&_blockquote]:text-rose-900/70 [&_code]:rounded [&_code]:bg-amber-100/50 [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:font-mono [&_strong]:text-rose-900 [&_p+p]:mt-4"
|
||||
v-html="bodyHtml"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.taxi-window-lights {
|
||||
background:
|
||||
linear-gradient(
|
||||
90deg,
|
||||
transparent 0 4%,
|
||||
rgb(252 211 77 / 0.85) 4% 9%,
|
||||
transparent 9% 16%,
|
||||
rgb(244 114 182 / 0.6) 16% 20%,
|
||||
transparent 20% 28%,
|
||||
rgb(96 165 250 / 0.75) 28% 34%,
|
||||
transparent 34% 42%,
|
||||
rgb(251 146 60 / 0.8) 42% 47%,
|
||||
transparent 47% 56%,
|
||||
rgb(253 224 71 / 0.82) 56% 60%,
|
||||
transparent 60% 100%
|
||||
);
|
||||
animation: taxi-window-lights 7s linear infinite;
|
||||
}
|
||||
|
||||
.taxi-window-fast {
|
||||
animation-duration: 2.4s;
|
||||
}
|
||||
|
||||
@keyframes taxi-window-lights {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-40%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user