mwy quanji
This commit is contained in:
@@ -75,6 +75,42 @@ class Command(BaseCommand):
|
||||
nepal_art_style=Memory.NepalArtStyle.DAWN_MEET,
|
||||
is_finale=False,
|
||||
)
|
||||
Memory.objects.create(
|
||||
story_order=20260431,
|
||||
date=date(2026, 4, 30),
|
||||
event_title="出租牵手",
|
||||
main_text=(
|
||||
"**2026 年 4 月 30 日**,我们在车上开始牵手。\n\n"
|
||||
"心跳开始加速、见面的花倒成了我们之间的障碍。"
|
||||
),
|
||||
unique_feature_type=Memory.FeatureType.FIRST_MEET_TAIX,
|
||||
nepal_art_style=Memory.NepalArtStyle.DAWN_MEET,
|
||||
is_finale=False,
|
||||
)
|
||||
Memory.objects.create(
|
||||
story_order=20260432,
|
||||
date=date(2026, 4, 30),
|
||||
event_title="全季M爱",
|
||||
main_text=(
|
||||
"**2026 年 4 月 30 日**,下次见面还是全季。\n\n"
|
||||
"所以爱的开始就在全季,那么我们下次可以去四季嘛。"
|
||||
),
|
||||
unique_feature_type=Memory.FeatureType.FIRST_MEET_QUANJI,
|
||||
nepal_art_style=Memory.NepalArtStyle.DAWN_MEET,
|
||||
is_finale=False,
|
||||
)
|
||||
Memory.objects.create(
|
||||
story_order=20260501,
|
||||
date=date(2026, 5, 1),
|
||||
event_title="劳动节“劳动”",
|
||||
main_text=(
|
||||
"**2026 年 5 月 1 日**,劳动节我们倒是很累嘛,所以我身体真的很好吧。\n\n"
|
||||
"从那天开始,我们就开始了劳动节的“劳动”,各种姿势,我们缠绵。"
|
||||
),
|
||||
unique_feature_type=Memory.FeatureType.MAKE_LOVE,
|
||||
nepal_art_style=Memory.NepalArtStyle.DAWN_MEET,
|
||||
is_finale=False,
|
||||
)
|
||||
Memory.objects.create(
|
||||
story_order=20260503,
|
||||
date=date(2026, 5, 3),
|
||||
@@ -121,7 +157,7 @@ class Command(BaseCommand):
|
||||
date=date(2026, 5, 10),
|
||||
event_title="逛宜家的小日子",
|
||||
main_text=(
|
||||
"推车里的台灯、地毯和一杯热狗,"
|
||||
"推车里的热狗、桌子、盘子和杯子,"
|
||||
"都是以后会说「还记得吗」的碎片。"
|
||||
),
|
||||
unique_feature_type=Memory.FeatureType.STANDARD_NEPAL,
|
||||
|
||||
@@ -12,6 +12,10 @@ class Memory(models.Model):
|
||||
FISSURE_PAGE = "fissure_page", "裂缝对开页"
|
||||
STANDARD_NEPAL = "standard_nepal", "标准尼泊尔页"
|
||||
FIRST_MEET_TRAIN = "first_meet_train", "初见·高铁遇见"
|
||||
FIRST_MEET_TAIX = "first_meet_taix", "初见·出租牵手"
|
||||
FIRST_MEET_QUANJI = "first_meet_quanji", "初见·全季M爱"
|
||||
MAKE_LOVE = "make_love", "爱爱页"
|
||||
|
||||
|
||||
class NepalArtStyle(models.TextChoices):
|
||||
"""每页可选不同尼泊尔视觉皮肤(备用可继续加枚举)。"""
|
||||
|
||||
@@ -15,6 +15,9 @@ export type MemoryFeatureType =
|
||||
| 'first_meet_ju'
|
||||
| 'first_meet_rui'
|
||||
| 'first_meet_train'
|
||||
| 'first_meet_taix'
|
||||
| 'first_meet_quanji'
|
||||
| 'make_love'
|
||||
|
||||
export type NepalArtStyle =
|
||||
| 'none'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { usePreferredReducedMotion } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
import { computed, onBeforeUnmount, ref } from 'vue'
|
||||
import type { Memory } from '../api/client'
|
||||
import { getStandardNepalTheme } from '../constants/nepalThemes'
|
||||
import PolaroidCard from './PolaroidCard.vue'
|
||||
@@ -13,8 +13,37 @@ const props = defineProps<{
|
||||
const bodyHtml = computed(() => renderMarkdown(props.memory.main_text))
|
||||
const theme = computed(() => getStandardNepalTheme(props.memory.nepal_art_style))
|
||||
const reduceMotion = usePreferredReducedMotion()
|
||||
const callPhase = ref<'idle' | 'dialing' | 'connected'>('idle')
|
||||
let connectTimer: ReturnType<typeof setTimeout> | undefined
|
||||
|
||||
const waveHeights = [18, 34, 24, 42, 28, 36, 20, 31]
|
||||
|
||||
const callTimeLabel = computed(() => {
|
||||
if (callPhase.value === 'idle') return '00:00'
|
||||
if (callPhase.value === 'dialing') return '00:07'
|
||||
return '20:00'
|
||||
})
|
||||
|
||||
function startCall() {
|
||||
if (callPhase.value === 'connected') {
|
||||
callPhase.value = 'idle'
|
||||
return
|
||||
}
|
||||
if (connectTimer) clearTimeout(connectTimer)
|
||||
if (reduceMotion.value === 'reduce') {
|
||||
callPhase.value = 'connected'
|
||||
return
|
||||
}
|
||||
callPhase.value = 'dialing'
|
||||
connectTimer = setTimeout(() => {
|
||||
callPhase.value = 'connected'
|
||||
connectTimer = undefined
|
||||
}, 900)
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (connectTimer) clearTimeout(connectTimer)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -60,7 +89,7 @@ const waveHeights = [18, 34, 24, 42, 28, 36, 20, 31]
|
||||
<p class="text-[10px] font-semibold tracking-[0.2em] text-slate-300/78">
|
||||
通话时长
|
||||
</p>
|
||||
<p class="mt-1 font-mono text-2xl font-bold text-cyan-100">20:00</p>
|
||||
<p class="mt-1 font-mono text-2xl font-bold text-cyan-100">{{ callTimeLabel }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -88,12 +117,22 @@ const waveHeights = [18, 34, 24, 42, 28, 36, 20, 31]
|
||||
v-for="(h, i) in waveHeights"
|
||||
:key="`${h}-${i}`"
|
||||
class="call-wave block w-2 rounded-full bg-gradient-to-t from-cyan-500 via-sky-300 to-cyan-100"
|
||||
:class="reduceMotion === 'reduce' ? '' : `call-wave--${(i % 4) + 1}`"
|
||||
:class="[
|
||||
reduceMotion === 'reduce' ? '' : `call-wave--${(i % 4) + 1}`,
|
||||
callPhase === 'idle' ? 'opacity-45' : '',
|
||||
callPhase === 'connected' ? 'brightness-110' : '',
|
||||
]"
|
||||
:style="{ height: `${h}px` }"
|
||||
/>
|
||||
</div>
|
||||
<p class="mt-3 text-center text-xs leading-relaxed text-slate-300/80">
|
||||
第一次听见你的声音,原来文字之外,还有更柔软的那一层。
|
||||
{{
|
||||
callPhase === 'idle'
|
||||
? '点一下拨通,第一次电话会从文字走进声音里。'
|
||||
: callPhase === 'dialing'
|
||||
? '正在接通,心跳也跟着一起变快。'
|
||||
: '第一次听见你的声音,原来文字之外,还有更柔软的那一层。'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -106,6 +145,22 @@ const waveHeights = [18, 34, 24, 42, 28, 36, 20, 31]
|
||||
{{ tag }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full border border-cyan-300/24 bg-cyan-400/10 px-4 py-2 text-xs font-semibold tracking-[0.18em] text-cyan-100 transition hover:bg-cyan-400/18"
|
||||
@click="startCall"
|
||||
>
|
||||
{{
|
||||
callPhase === 'idle'
|
||||
? '拨通这通电话'
|
||||
: callPhase === 'dialing'
|
||||
? '接通中…'
|
||||
: '再听一遍'
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { usePreferredReducedMotion } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import type { Memory } from '../api/client'
|
||||
import { getStandardNepalTheme } from '../constants/nepalThemes'
|
||||
import PolaroidCard from './PolaroidCard.vue'
|
||||
@@ -14,7 +14,12 @@ const bodyHtml = computed(() => renderMarkdown(props.memory.main_text))
|
||||
const theme = computed(() => getStandardNepalTheme(props.memory.nepal_art_style))
|
||||
const reduceMotion = usePreferredReducedMotion()
|
||||
|
||||
const morseGroups = ['-- .- -.--', '.. / .... . .- .-. -..', '.- .-.. .-.. / -. .. --. .... -']
|
||||
const morseEntries = [
|
||||
{ code: '-- .- -.--', text: '别怕,我在听' },
|
||||
{ code: '.. / .... . .- .-. -..', text: '我听见你的害怕了' },
|
||||
{ code: '.- .-.. .-.. / -. .. --. .... -', text: '聊到很晚也没关系' },
|
||||
]
|
||||
const activeMorse = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -124,14 +129,28 @@ const morseGroups = ['-- .- -.--', '.. / .... . .- .-. -..', '.- .-.. .-.. / -.
|
||||
</div>
|
||||
|
||||
<div class="mt-4 grid gap-2 sm:grid-cols-3">
|
||||
<div
|
||||
v-for="line in morseGroups"
|
||||
:key="line"
|
||||
class="rounded-2xl border border-white/10 bg-white/8 px-3 py-2 font-mono text-[10px] tracking-[0.22em] text-slate-300/85"
|
||||
:class="reduceMotion === 'reduce' ? '' : 'campus-morse-glow'"
|
||||
<button
|
||||
v-for="(entry, index) in morseEntries"
|
||||
:key="entry.code"
|
||||
type="button"
|
||||
class="rounded-2xl border px-3 py-2 text-left font-mono text-[10px] tracking-[0.22em] transition"
|
||||
:class="[
|
||||
activeMorse === index
|
||||
? 'border-cyan-300/60 bg-cyan-300/16 text-cyan-100 shadow-[0_10px_22px_rgba(34,211,238,0.14)]'
|
||||
: 'border-white/10 bg-white/8 text-slate-300/85 hover:bg-white/12',
|
||||
reduceMotion === 'reduce' ? '' : 'campus-morse-glow',
|
||||
]"
|
||||
@click="activeMorse = index"
|
||||
>
|
||||
{{ line }}
|
||||
{{ entry.code }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 rounded-2xl border border-cyan-300/18 bg-cyan-300/10 px-4 py-3 text-sm leading-relaxed text-cyan-50 shadow-[0_10px_24px_rgba(34,211,238,0.1)]">
|
||||
<p class="text-[10px] font-semibold uppercase tracking-[0.24em] text-cyan-200/70">
|
||||
点一下,解码一下
|
||||
</p>
|
||||
<p class="mt-2">{{ morseEntries[activeMorse].text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,7 @@ const props = defineProps<{
|
||||
const bodyHtml = computed(() => renderMarkdown(props.memory.main_text))
|
||||
const theme = computed(() => getStandardNepalTheme(props.memory.nepal_art_style))
|
||||
const reduceMotion = usePreferredReducedMotion()
|
||||
const replySent = ref(false)
|
||||
|
||||
/** typing → shown */
|
||||
const revealPhase = ref<'typing' | 'shown'>('typing')
|
||||
@@ -64,6 +65,11 @@ const dmDisplayParts = computed(() => {
|
||||
{ type: 'text' as const, text: s.slice(idx + '[流泪]'.length) },
|
||||
]
|
||||
})
|
||||
|
||||
function toggleReply() {
|
||||
if (revealPhase.value === 'typing') return
|
||||
replySent.value = !replySent.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -215,6 +221,32 @@ const dmDisplayParts = computed(() => {
|
||||
<p class="mt-2.5 pl-1 text-[10px] text-slate-500/80 font-medium tracking-wide">
|
||||
对方已通过「视频推荐」发起会话
|
||||
</p>
|
||||
|
||||
<div class="mt-3 flex items-start justify-between gap-3 pl-1">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full border border-cyan-400/25 bg-cyan-400/10 px-3 py-1.5 text-[10px] font-semibold tracking-[0.18em] text-cyan-200 transition hover:bg-cyan-400/18 disabled:cursor-default disabled:opacity-45"
|
||||
:disabled="revealPhase === 'typing'"
|
||||
@click="toggleReply"
|
||||
>
|
||||
{{ replySent ? '撤回心动回复' : '回一句试试' }}
|
||||
</button>
|
||||
<span class="text-[10px] text-slate-500/78">
|
||||
{{ revealPhase === 'typing' ? '正在输入中…' : '轻点按钮,故事就会继续一点。' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="replySent"
|
||||
class="mt-3 flex justify-end"
|
||||
>
|
||||
<div
|
||||
class="max-w-[84%] rounded-2xl rounded-tr-sm border border-cyan-300/25 bg-gradient-to-br from-cyan-500/18 to-blue-500/16 px-4 py-3 text-right text-[13px] leading-[1.72] text-cyan-50 shadow-[0_10px_22px_rgba(6,182,212,0.12)]"
|
||||
:class="reduceMotion === 'reduce' ? '' : 'fm-reply-pop'"
|
||||
>
|
||||
读博会辛苦,但你这样认真来问的人,也会被认真回答。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -335,6 +367,22 @@ const dmDisplayParts = computed(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.fm-reply-pop {
|
||||
animation: fm-reply-pop 0.42s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
transform-origin: top right;
|
||||
}
|
||||
|
||||
@keyframes fm-reply-pop {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(12px) scale(0.92);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* 撕纸边缘容器补充 */
|
||||
.nepal-torn-panel {
|
||||
border-radius: 4px 4px 16px 16px;
|
||||
|
||||
@@ -13,6 +13,7 @@ const stageRef = ref<HTMLElement | null>(null)
|
||||
const split = ref(0)
|
||||
const reduceMotion = usePreferredReducedMotion()
|
||||
const bodyHtml = computed(() => renderMarkdown(props.memory.main_text))
|
||||
const panelShift = computed(() => `${split.value * 72}%`)
|
||||
|
||||
const hurtFacts = [
|
||||
'我把你和前任之间的事情告诉了别人',
|
||||
@@ -43,7 +44,7 @@ usePointerSwipe(stageRef, {
|
||||
const isOpen = computed(() => split.value > 0.35)
|
||||
|
||||
function toggleSplit() {
|
||||
split.value = isOpen.value ? 0 : 0.85
|
||||
split.value = isOpen.value ? 0 : 1
|
||||
}
|
||||
|
||||
const splitHint = computed(() =>
|
||||
@@ -156,7 +157,7 @@ const fracturePulseClass = computed(() => {
|
||||
<div
|
||||
class="relative z-[3] flex-1 overflow-hidden border-r border-white/6 bg-[linear-gradient(180deg,#4b1522_0%,#241017_100%)]"
|
||||
:class="reduceMotion === 'reduce' ? '' : 'transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)]'"
|
||||
:style="{ transform: `translateX(-${split * 30}%)` }"
|
||||
:style="{ transform: `translateX(-${panelShift})` }"
|
||||
>
|
||||
<div
|
||||
class="pointer-events-none absolute inset-0 opacity-[0.16]"
|
||||
@@ -221,7 +222,7 @@ const fracturePulseClass = computed(() => {
|
||||
<div
|
||||
class="relative z-[3] flex-1 overflow-hidden border-l border-white/6 bg-[linear-gradient(180deg,#2a1317_0%,#120d10_100%)]"
|
||||
:class="reduceMotion === 'reduce' ? '' : 'transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)]'"
|
||||
:style="{ transform: `translateX(${split * 30}%)` }"
|
||||
:style="{ transform: `translateX(${panelShift})` }"
|
||||
>
|
||||
<div
|
||||
class="pointer-events-none absolute inset-0 opacity-[0.16]"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { usePreferredReducedMotion } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import type { Memory } from '../api/client'
|
||||
import { getStandardNepalTheme } from '../constants/nepalThemes'
|
||||
import PolaroidCard from './PolaroidCard.vue'
|
||||
@@ -13,6 +13,7 @@ const props = defineProps<{
|
||||
const bodyHtml = computed(() => renderMarkdown(props.memory.main_text))
|
||||
const theme = computed(() => getStandardNepalTheme(props.memory.nepal_art_style))
|
||||
const reduceMotion = usePreferredReducedMotion()
|
||||
const homeUnlocked = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -59,7 +60,10 @@ const reduceMotion = usePreferredReducedMotion()
|
||||
<div class="absolute inset-y-0 right-0 w-[42%] bg-gradient-to-l from-amber-100/85 to-transparent" aria-hidden="true" />
|
||||
<div
|
||||
class="window-beam absolute right-[18%] top-0 h-full w-20 -skew-x-[18deg] bg-gradient-to-b from-white/70 via-amber-100/45 to-transparent blur-sm"
|
||||
:class="reduceMotion === 'reduce' ? '' : 'window-beam--move'"
|
||||
:class="[
|
||||
reduceMotion === 'reduce' ? '' : 'window-beam--move',
|
||||
homeUnlocked ? 'opacity-100' : '',
|
||||
]"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
@@ -80,9 +84,13 @@ const reduceMotion = usePreferredReducedMotion()
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="relative flex shrink-0 flex-col items-center">
|
||||
<button
|
||||
type="button"
|
||||
class="relative flex shrink-0 flex-col items-center rounded-[1.4rem] px-1 py-1 transition hover:-translate-y-0.5"
|
||||
@click="homeUnlocked = !homeUnlocked"
|
||||
>
|
||||
<div class="h-9 w-1 rounded-full bg-amber-700/55" aria-hidden="true" />
|
||||
<div class="relative flex h-20 w-20 items-center justify-center rounded-full bg-[#2d8cff] shadow-[0_12px_22px_rgba(45,140,255,0.25)] ring-4 ring-white/65">
|
||||
<div class="relative flex h-20 w-20 items-center justify-center rounded-full bg-[#2d8cff] shadow-[0_12px_22px_rgba(45,140,255,0.25)] ring-4 ring-white/65" :class="homeUnlocked ? 'scale-[1.03]' : ''">
|
||||
<div class="absolute bottom-4 h-7 w-7 rounded-full bg-[#f7d34b] shadow-inner ring-2 ring-[#c99113]" />
|
||||
<div class="absolute top-5 h-7 w-11 rounded-full bg-white" />
|
||||
<div class="absolute top-8 h-7 w-12 rounded-b-[1rem] rounded-t-[0.5rem] bg-white" />
|
||||
@@ -90,9 +98,9 @@ const reduceMotion = usePreferredReducedMotion()
|
||||
<div class="absolute right-5 top-[2.15rem] h-1.5 w-1.5 rounded-full bg-slate-900" />
|
||||
</div>
|
||||
<p class="mt-2 text-[10px] font-medium tracking-[0.2em] text-blue-700/75">
|
||||
钥匙圈
|
||||
{{ homeUnlocked ? '门开啦' : '钥匙圈' }}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,6 +114,10 @@ const reduceMotion = usePreferredReducedMotion()
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 rounded-[1.25rem] border border-amber-200/70 bg-white/85 px-4 py-3 text-center text-sm leading-relaxed text-amber-950/82">
|
||||
{{ homeUnlocked ? '钥匙轻轻一转,这个“共同生活”的按钮就真的按下去了。' : '点一下钥匙圈,试着把这间小屋先打开。' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { usePreferredReducedMotion } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
import { computed, onBeforeUnmount, ref } from 'vue'
|
||||
import type { Memory } from '../api/client'
|
||||
import { getStandardNepalTheme } from '../constants/nepalThemes'
|
||||
import PolaroidCard from './PolaroidCard.vue'
|
||||
@@ -13,6 +13,61 @@ const props = defineProps<{
|
||||
const bodyHtml = computed(() => renderMarkdown(props.memory.main_text))
|
||||
const theme = computed(() => getStandardNepalTheme(props.memory.nepal_art_style))
|
||||
const reduceMotion = usePreferredReducedMotion()
|
||||
const ikeaItems = ['热狗', '桌子', '盘子', '杯子']
|
||||
const cartStage = ref(0)
|
||||
const flyingItem = ref<string | null>(null)
|
||||
const flightKey = ref(0)
|
||||
const newestItem = ref<string | null>(null)
|
||||
let flightTimer: ReturnType<typeof setTimeout> | undefined
|
||||
|
||||
const activeItems = computed(() => ikeaItems.slice(0, cartStage.value))
|
||||
const cartVisualItems = computed(() =>
|
||||
activeItems.value.map((item, index) => ({
|
||||
item,
|
||||
style: {
|
||||
left: `${14 + index * 14}%`,
|
||||
bottom: `${40 + (index % 2) * 12}px`,
|
||||
transform: `rotate(${index % 2 === 0 ? -8 : 7}deg)`,
|
||||
},
|
||||
})),
|
||||
)
|
||||
|
||||
function triggerFlight(item: string) {
|
||||
newestItem.value = item
|
||||
if (reduceMotion.value === 'reduce') return
|
||||
if (flightTimer) clearTimeout(flightTimer)
|
||||
flightKey.value += 1
|
||||
flyingItem.value = item
|
||||
flightTimer = setTimeout(() => {
|
||||
flyingItem.value = null
|
||||
flightTimer = undefined
|
||||
}, 820)
|
||||
}
|
||||
|
||||
function setCartStage(nextStage: number, item?: string) {
|
||||
cartStage.value = nextStage
|
||||
if (item) triggerFlight(item)
|
||||
else if (nextStage === 0) newestItem.value = null
|
||||
}
|
||||
|
||||
function advanceCart() {
|
||||
if (cartStage.value >= ikeaItems.length) {
|
||||
setCartStage(0)
|
||||
return
|
||||
}
|
||||
const item = ikeaItems[cartStage.value]
|
||||
setCartStage(cartStage.value + 1, item)
|
||||
}
|
||||
|
||||
function chooseItem(index: number) {
|
||||
const nextStage = index + 1
|
||||
const item = ikeaItems[index]
|
||||
setCartStage(nextStage, item)
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (flightTimer) clearTimeout(flightTimer)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -60,6 +115,15 @@ const reduceMotion = usePreferredReducedMotion()
|
||||
<div class="absolute right-5 top-4 rounded-full bg-[#f8d548] px-3 py-1 text-[10px] font-black tracking-[0.24em] text-[#0c4ca9]">
|
||||
SALE
|
||||
</div>
|
||||
<Transition name="ikea-fly" mode="out-in">
|
||||
<div
|
||||
v-if="flyingItem"
|
||||
:key="`${flyingItem}-${flightKey}`"
|
||||
class="pointer-events-none absolute left-8 top-[5.8rem] z-30 rounded-full border border-indigo-200/80 bg-white/96 px-3 py-1.5 text-[11px] font-semibold text-indigo-950 shadow-[0_12px_22px_rgba(79,70,229,0.18)]"
|
||||
>
|
||||
{{ flyingItem }}
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<div class="mt-10 grid gap-4 sm:grid-cols-[1fr_0.95fr] sm:items-end">
|
||||
<div class="rounded-[1.4rem] border border-white/70 bg-white/80 p-4 shadow-[0_12px_24px_rgba(12,90,219,0.12)]">
|
||||
@@ -74,9 +138,16 @@ const reduceMotion = usePreferredReducedMotion()
|
||||
Cart List
|
||||
</p>
|
||||
<div class="mt-2 flex flex-wrap gap-2 text-[11px] text-indigo-950/88">
|
||||
<span class="rounded-full bg-[#dbeafe] px-2.5 py-1">台灯</span>
|
||||
<span class="rounded-full bg-[#dbeafe] px-2.5 py-1">地毯</span>
|
||||
<span class="rounded-full bg-[#dbeafe] px-2.5 py-1">热狗</span>
|
||||
<button
|
||||
v-for="item in ikeaItems"
|
||||
:key="item"
|
||||
type="button"
|
||||
class="rounded-full px-2.5 py-1 transition"
|
||||
:class="activeItems.includes(item) ? 'bg-[#93c5fd] text-indigo-950' : 'bg-[#dbeafe] hover:bg-[#c5dcff]'"
|
||||
@click="chooseItem(ikeaItems.indexOf(item))"
|
||||
>
|
||||
{{ item }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -86,6 +157,9 @@ const reduceMotion = usePreferredReducedMotion()
|
||||
class="relative mx-auto h-32 w-[180px]"
|
||||
:class="reduceMotion === 'reduce' ? '' : 'ikea-cart-bob'"
|
||||
>
|
||||
<div class="absolute right-0 top-0 rounded-full bg-[#0c5adb] px-2.5 py-1 text-[10px] font-bold text-white shadow-lg">
|
||||
{{ activeItems.length }}
|
||||
</div>
|
||||
<div class="absolute bottom-3 left-4 h-14 w-24 rounded-[1.2rem] border-[3px] border-[#0c4ca9] bg-white/90" />
|
||||
<div class="absolute bottom-11 left-10 h-8 w-14 rotate-[-12deg] rounded-lg bg-[#f8d548] shadow-[0_8px_16px_rgba(248,213,72,0.3)]" />
|
||||
<div class="absolute bottom-12 left-20 h-6 w-10 rounded-full bg-[#ff7a59] shadow-[0_8px_16px_rgba(255,122,89,0.22)]" />
|
||||
@@ -95,19 +169,49 @@ const reduceMotion = usePreferredReducedMotion()
|
||||
<div class="absolute bottom-3 left-24 h-4 w-4 rounded-full border-[3px] border-[#0c4ca9] bg-white" />
|
||||
<div class="absolute bottom-[4.35rem] left-[6.2rem] h-2 w-12 rounded-full bg-[#0c4ca9]" />
|
||||
<div class="absolute bottom-[5.1rem] left-[7rem] h-8 w-1.5 rounded-full bg-[#0c4ca9]" />
|
||||
<div
|
||||
v-for="entry in cartVisualItems"
|
||||
:key="entry.item"
|
||||
class="absolute rounded-full border border-white/75 bg-[#fff7cf] px-2.5 py-1 text-[10px] font-semibold text-[#0c4ca9] shadow-[0_8px_16px_rgba(12,90,219,0.16)] transition-all"
|
||||
:class="newestItem === entry.item ? 'ring-2 ring-[#f8d548]/80' : ''"
|
||||
:style="entry.style"
|
||||
>
|
||||
{{ entry.item }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex flex-wrap justify-center gap-2">
|
||||
<span
|
||||
v-for="tag in ['推车里的碎片', '未来会再提起', '一起逛生活']"
|
||||
v-for="tag in ['热狗和餐具', '一起挑桌子', '一起逛生活']"
|
||||
:key="tag"
|
||||
class="rounded-full border border-indigo-200 bg-white/88 px-3 py-1.5 text-[11px] font-medium text-indigo-900/82"
|
||||
>
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex flex-col items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full border border-indigo-200 bg-white/92 px-4 py-2 text-xs font-semibold tracking-[0.18em] text-indigo-950 transition hover:bg-indigo-50"
|
||||
@click="advanceCart"
|
||||
>
|
||||
{{
|
||||
cartStage >= ikeaItems.length
|
||||
? '再逛一圈'
|
||||
: `把${ikeaItems[cartStage]}放进推车`
|
||||
}}
|
||||
</button>
|
||||
<p class="text-center text-[11px] leading-relaxed text-indigo-900/70">
|
||||
{{
|
||||
activeItems.length
|
||||
? `推车里现在有:${activeItems.join('、')}`
|
||||
: '轻点一下,把今天想带走的小碎片先放进去。'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -133,6 +237,32 @@ const reduceMotion = usePreferredReducedMotion()
|
||||
animation: ikea-cart-bob 4.6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.ikea-fly-enter-active {
|
||||
animation: ikea-fly-into-cart 0.8s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
.ikea-fly-leave-active {
|
||||
transition: opacity 0.14s linear;
|
||||
}
|
||||
|
||||
.ikea-fly-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@keyframes ikea-fly-into-cart {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translate(0, 0) scale(0.88) rotate(-6deg);
|
||||
}
|
||||
16% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translate(168px, 92px) scale(0.74) rotate(11deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ikea-cart-bob {
|
||||
0%,
|
||||
100% {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { usePreferredReducedMotion } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import type { Memory } from '../api/client'
|
||||
import { getStandardNepalTheme } from '../constants/nepalThemes'
|
||||
import PolaroidCard from './PolaroidCard.vue'
|
||||
@@ -13,6 +13,7 @@ const props = defineProps<{
|
||||
const bodyHtml = computed(() => renderMarkdown(props.memory.main_text))
|
||||
const theme = computed(() => getStandardNepalTheme(props.memory.nepal_art_style))
|
||||
const reduceMotion = usePreferredReducedMotion()
|
||||
const focusDetail = ref<'orange' | 'figure'>('orange')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -93,9 +94,23 @@ const reduceMotion = usePreferredReducedMotion()
|
||||
class="absolute bottom-5 left-1/2 w-[85%] -translate-x-1/2 rounded-[1.2rem] border border-white/70 bg-white/72 px-3 py-2 shadow-[0_10px_25px_rgba(255,255,255,0.45)] backdrop-blur-sm"
|
||||
>
|
||||
<div class="flex flex-wrap justify-center gap-2 text-[10px] font-semibold text-orange-800/80">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full px-2.5 py-1 transition"
|
||||
:class="focusDetail === 'orange' ? 'bg-orange-200 text-orange-950' : 'bg-orange-100 hover:bg-orange-150'"
|
||||
@click="focusDetail = 'orange'"
|
||||
>
|
||||
橙C
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full px-2.5 py-1 transition"
|
||||
:class="focusDetail === 'figure' ? 'bg-sky-200 text-sky-950' : 'bg-orange-100 hover:bg-orange-150'"
|
||||
@click="focusDetail = 'figure'"
|
||||
>
|
||||
灵笼
|
||||
</button>
|
||||
<span class="rounded-full bg-orange-100 px-2.5 py-1">咖啡</span>
|
||||
<span class="rounded-full bg-orange-100 px-2.5 py-1">橙C</span>
|
||||
<span class="rounded-full bg-orange-100 px-2.5 py-1">灵笼</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,14 +123,24 @@ const reduceMotion = usePreferredReducedMotion()
|
||||
Receipt
|
||||
</p>
|
||||
<div class="mt-3 space-y-2.5 text-sm text-slate-700">
|
||||
<div class="flex items-center justify-between border-b border-dashed border-orange-200 pb-2">
|
||||
<span>生椰拿铁</span>
|
||||
<span class="font-mono text-slate-500">x 1</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between border-b border-dashed border-orange-200 pb-2">
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center justify-between rounded-xl border-b border-dashed pb-2 text-left transition"
|
||||
:class="focusDetail === 'figure' ? 'border-sky-200 bg-sky-50/70 px-2 pt-1 text-sky-900' : 'border-orange-200'"
|
||||
@click="focusDetail = 'figure'"
|
||||
>
|
||||
<span>桌角灵笼</span>
|
||||
<span class="font-mono text-slate-500">noticed</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center justify-between rounded-xl border-b border-dashed pb-2 text-left transition"
|
||||
:class="focusDetail === 'orange' ? 'border-orange-200 bg-orange-50/70 px-2 pt-1 text-orange-950' : 'border-orange-200'"
|
||||
@click="focusDetail = 'orange'"
|
||||
>
|
||||
<span>橙 C 美式</span>
|
||||
<span class="font-mono text-slate-500">x 1</span>
|
||||
</div>
|
||||
<span class="font-mono text-slate-500">remembered</span>
|
||||
</button>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>记住你的喜好</span>
|
||||
<span class="font-brush text-xl text-orange-950">1 次</span>
|
||||
@@ -124,14 +149,19 @@ const reduceMotion = usePreferredReducedMotion()
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative overflow-hidden rounded-[1.6rem] border border-slate-200/70 bg-slate-900 px-4 py-4 text-slate-100 shadow-[0_15px_30px_rgba(15,23,42,0.18)]"
|
||||
class="relative overflow-hidden rounded-[1.6rem] border border-slate-200/70 px-4 py-4 shadow-[0_15px_30px_rgba(15,23,42,0.18)]"
|
||||
:class="focusDetail === 'orange' ? 'bg-slate-900 text-slate-100' : 'bg-sky-950 text-slate-100'"
|
||||
>
|
||||
<div class="absolute -right-8 -top-8 h-24 w-24 rounded-full bg-blue-400/20 blur-2xl" aria-hidden="true" />
|
||||
<p class="text-[10px] font-semibold uppercase tracking-[0.28em] text-blue-200/65">
|
||||
Desk Corner
|
||||
<div class="absolute -right-8 -top-8 h-24 w-24 rounded-full blur-2xl" :class="focusDetail === 'orange' ? 'bg-blue-400/20' : 'bg-sky-300/20'" aria-hidden="true" />
|
||||
<p class="text-[10px] font-semibold uppercase tracking-[0.28em]" :class="focusDetail === 'orange' ? 'text-blue-200/65' : 'text-sky-200/70'">
|
||||
{{ focusDetail === 'orange' ? 'Orange C' : 'Desk Corner' }}
|
||||
</p>
|
||||
<p class="mt-2 text-sm leading-relaxed text-slate-200/88">
|
||||
你连桌角的小小“灵笼”都看见了。喜欢有时候不是一句话,是被认真注意到。
|
||||
{{
|
||||
focusDetail === 'orange'
|
||||
? '你记住了我会点橙C。喜欢有时候不是轰轰烈烈,是被记住这一点点。'
|
||||
: '你连桌角的小小“灵笼”都看见了。喜欢有时候不是一句话,是被认真注意到。'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,9 +15,12 @@ import FissurePage from './FissurePage.vue'
|
||||
import HeartWindowPage from './HeartWindowPage.vue'
|
||||
import HomeRentalPage from './HomeRentalPage.vue'
|
||||
import IkeaDatePage from './IkeaDatePage.vue'
|
||||
import LaborDayPage from './LaborDayPage.vue'
|
||||
import LuckinCoffeePage from './LuckinCoffeePage.vue'
|
||||
import QuanjiPage from './QuanjiPage.vue'
|
||||
import ReconcilePage from './ReconcilePage.vue'
|
||||
import StandardNepalPage from './StandardNepalPage.vue'
|
||||
import TaxiHandHoldPage from './TaxiHandHoldPage.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
memories: Memory[]
|
||||
@@ -49,6 +52,9 @@ const pageComponent = computed(() => {
|
||||
20260423: LuckinCoffeePage,
|
||||
20260424: FirstCallPage,
|
||||
20260430: FirstMeetTrainPage,
|
||||
20260431: TaxiHandHoldPage,
|
||||
20260432: QuanjiPage,
|
||||
20260501: LaborDayPage,
|
||||
20260503: FissurePage,
|
||||
20260504: ReconcilePage,
|
||||
20260508: HomeRentalPage,
|
||||
@@ -68,6 +74,9 @@ const pageComponent = computed(() => {
|
||||
return memory.event_title.includes('电话') ? FirstCallPage : LuckinCoffeePage
|
||||
}
|
||||
if (t === 'first_meet_train') return FirstMeetTrainPage
|
||||
if (t === 'first_meet_taix') return TaxiHandHoldPage
|
||||
if (t === 'first_meet_quanji') return QuanjiPage
|
||||
if (t === 'make_love') return LaborDayPage
|
||||
return StandardNepalPage
|
||||
})
|
||||
|
||||
@@ -180,6 +189,8 @@ function next() {
|
||||
.story-fissure-leave-active,
|
||||
.story-dawn-enter-active,
|
||||
.story-dawn-leave-active,
|
||||
.story-silk-enter-active,
|
||||
.story-silk-leave-active,
|
||||
.story-blush-enter-active,
|
||||
.story-blush-leave-active,
|
||||
.story-mint-enter-active,
|
||||
@@ -245,6 +256,18 @@ function next() {
|
||||
filter: brightness(0.92);
|
||||
}
|
||||
|
||||
/* 亲密页 · 被单与呼吸感 */
|
||||
.story-silk-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px) scale(0.96);
|
||||
filter: blur(2px) saturate(1.08);
|
||||
}
|
||||
.story-silk-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(1.03);
|
||||
filter: blur(1px) brightness(1.04);
|
||||
}
|
||||
|
||||
/* 别扭 · 粉雾 */
|
||||
.story-blush-enter-from {
|
||||
opacity: 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { usePreferredReducedMotion } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import type { Memory } from '../api/client'
|
||||
import { getStandardNepalTheme } from '../constants/nepalThemes'
|
||||
import PolaroidCard from './PolaroidCard.vue'
|
||||
@@ -13,6 +13,7 @@ const props = defineProps<{
|
||||
const bodyHtml = computed(() => renderMarkdown(props.memory.main_text))
|
||||
const theme = computed(() => getStandardNepalTheme(props.memory.nepal_art_style))
|
||||
const reduceMotion = usePreferredReducedMotion()
|
||||
const talkedItOut = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -58,7 +59,10 @@ const reduceMotion = usePreferredReducedMotion()
|
||||
<div class="grid gap-3 sm:grid-cols-[0.92fr_0.16fr_0.92fr] sm:items-center">
|
||||
<div
|
||||
class="rounded-[1.5rem] border border-rose-200/65 bg-rose-50/88 px-4 py-4 shadow-[0_10px_20px_rgba(244,114,182,0.08)]"
|
||||
:class="reduceMotion === 'reduce' ? '' : 'reconcile-card-left'"
|
||||
:class="[
|
||||
reduceMotion === 'reduce' ? '' : 'reconcile-card-left',
|
||||
talkedItOut ? 'sm:translate-x-2' : '',
|
||||
]"
|
||||
>
|
||||
<p class="text-[10px] font-semibold uppercase tracking-[0.25em] text-rose-700/65">
|
||||
Before
|
||||
@@ -69,7 +73,13 @@ const reduceMotion = usePreferredReducedMotion()
|
||||
<div class="relative flex items-center justify-center py-2">
|
||||
<div
|
||||
class="flex h-16 w-16 items-center justify-center rounded-full bg-gradient-to-br from-emerald-300 to-cyan-300 text-2xl text-white shadow-[0_14px_28px_rgba(16,185,129,0.22)]"
|
||||
:class="reduceMotion === 'reduce' ? '' : 'reconcile-heartbeat'"
|
||||
:class="[
|
||||
reduceMotion === 'reduce' ? '' : 'reconcile-heartbeat',
|
||||
talkedItOut ? 'scale-110' : '',
|
||||
]"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="talkedItOut = !talkedItOut"
|
||||
>
|
||||
♡
|
||||
</div>
|
||||
@@ -78,7 +88,10 @@ const reduceMotion = usePreferredReducedMotion()
|
||||
|
||||
<div
|
||||
class="rounded-[1.5rem] border border-cyan-200/70 bg-cyan-50/88 px-4 py-4 shadow-[0_10px_20px_rgba(34,211,238,0.08)]"
|
||||
:class="reduceMotion === 'reduce' ? '' : 'reconcile-card-right'"
|
||||
:class="[
|
||||
reduceMotion === 'reduce' ? '' : 'reconcile-card-right',
|
||||
talkedItOut ? 'sm:-translate-x-2' : '',
|
||||
]"
|
||||
>
|
||||
<p class="text-[10px] font-semibold uppercase tracking-[0.25em] text-cyan-700/65">
|
||||
After
|
||||
@@ -98,6 +111,19 @@ const reduceMotion = usePreferredReducedMotion()
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex flex-col items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full border border-emerald-200 bg-white/92 px-4 py-2 text-xs font-semibold tracking-[0.18em] text-emerald-900 transition hover:bg-emerald-50"
|
||||
@click="talkedItOut = !talkedItOut"
|
||||
>
|
||||
{{ talkedItOut ? '先把话收回一点' : '把话说开' }}
|
||||
</button>
|
||||
<p class="text-center text-[11px] leading-relaxed text-emerald-900/70">
|
||||
{{ talkedItOut ? '说开以后,卡在中间的那口气终于顺了。' : '轻点一下,让“Before”和“After”靠近一点。' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ export type StoryTransitionName =
|
||||
| 'story-heart'
|
||||
| 'story-fissure'
|
||||
| 'story-dawn'
|
||||
| 'story-silk'
|
||||
| 'story-blush'
|
||||
| 'story-mint'
|
||||
| 'story-saffron'
|
||||
@@ -43,7 +44,10 @@ export function getStoryTransitionName(m: Memory | null): StoryTransitionName {
|
||||
if (t === 'heart_window') return 'story-heart'
|
||||
if (t === 'fissure_page') return 'story-fissure'
|
||||
if (t === 'first_meet' || t === 'first_meet_ju' || t === 'first_meet_rui') return 'story-night'
|
||||
if (t === 'first_meet_train') return 'story-dawn'
|
||||
if (t === 'first_meet_train' || t === 'first_meet_taix' || t === 'first_meet_quanji') {
|
||||
return 'story-dawn'
|
||||
}
|
||||
if (t === 'make_love') return 'story-silk'
|
||||
return transitionForStandardStyle(m.nepal_art_style)
|
||||
}
|
||||
|
||||
@@ -62,9 +66,12 @@ export function getStoryShellClasses(m: Memory | null): string {
|
||||
if (t === 'first_meet' || t === 'first_meet_ju' || t === 'first_meet_rui') {
|
||||
return 'border-slate-400/35 shadow-[0_24px_70px_-22px_rgba(30,58,138,0.28)] ring-cyan-900/15'
|
||||
}
|
||||
if (t === 'first_meet_train') {
|
||||
if (t === 'first_meet_train' || t === 'first_meet_taix' || t === 'first_meet_quanji') {
|
||||
return 'border-amber-200/50 shadow-[0_24px_72px_-22px_rgba(234,88,12,0.24)] ring-amber-100/35'
|
||||
}
|
||||
if (t === 'make_love') {
|
||||
return 'border-rose-200/45 shadow-[0_24px_72px_-22px_rgba(190,80,120,0.28)] ring-rose-100/25'
|
||||
}
|
||||
const shells: Partial<Record<NepalArtStyle, string>> = {
|
||||
dawn_meet:
|
||||
'border-amber-200/45 shadow-[0_22px_68px_-22px_rgba(217,119,6,0.22)] ring-amber-100/30',
|
||||
@@ -98,7 +105,10 @@ export function getStoryNavAccentClass(m: Memory | null): string {
|
||||
if (t === 'first_meet' || t === 'first_meet_ju' || t === 'first_meet_rui') {
|
||||
return 'hover:border-slate-400/45 hover:bg-slate-100/85'
|
||||
}
|
||||
if (t === 'first_meet_train') return 'hover:border-amber-300/50 hover:bg-amber-50/80'
|
||||
if (t === 'first_meet_train' || t === 'first_meet_taix' || t === 'first_meet_quanji') {
|
||||
return 'hover:border-amber-300/50 hover:bg-amber-50/80'
|
||||
}
|
||||
if (t === 'make_love') return 'hover:border-rose-300/50 hover:bg-rose-50/80'
|
||||
const accents: Partial<Record<NepalArtStyle, string>> = {
|
||||
dawn_meet: 'hover:border-amber-300/50 hover:bg-amber-50/80',
|
||||
blush_argue: 'hover:border-rose-300/45 hover:bg-rose-50/75',
|
||||
|
||||
Reference in New Issue
Block a user