546 lines
18 KiB
Vue
546 lines
18 KiB
Vue
<template>
|
||
<div
|
||
class="sp"
|
||
:class="[
|
||
{ 'is-visible': ready },
|
||
`sp-m-${cfg.mobileLayout || 'auto'}`,
|
||
]"
|
||
>
|
||
<div class="sp-shell" data-poster-capture>
|
||
<div class="sp-shell-outer">
|
||
<div class="sp-shell-gap">
|
||
<div class="sp-card">
|
||
<div class="sp-frame-ornament sp-frame-ornament-tl" aria-hidden="true">
|
||
<svg viewBox="0 0 48 48" fill="none">
|
||
<path d="M4 44V18c0-8 6-14 14-14h26" stroke="currentColor" stroke-width="2"/>
|
||
<path d="M10 44V24c0-6 4-10 10-10h24" stroke="currentColor" stroke-width="1.2" opacity="0.55"/>
|
||
<circle cx="18" cy="18" r="3" fill="currentColor"/>
|
||
<path d="M18 8c6 2 10 6 12 12" stroke="currentColor" stroke-width="1.2" opacity="0.7"/>
|
||
</svg>
|
||
</div>
|
||
<div class="sp-frame-ornament sp-frame-ornament-tr" aria-hidden="true">
|
||
<svg viewBox="0 0 48 48" fill="none">
|
||
<path d="M4 44V18c0-8 6-14 14-14h26" stroke="currentColor" stroke-width="2"/>
|
||
<path d="M10 44V24c0-6 4-10 10-10h24" stroke="currentColor" stroke-width="1.2" opacity="0.55"/>
|
||
<circle cx="18" cy="18" r="3" fill="currentColor"/>
|
||
<path d="M18 8c6 2 10 6 12 12" stroke="currentColor" stroke-width="1.2" opacity="0.7"/>
|
||
</svg>
|
||
</div>
|
||
<div class="sp-frame-ornament sp-frame-ornament-bl" aria-hidden="true">
|
||
<svg viewBox="0 0 48 48" fill="none">
|
||
<path d="M4 44V18c0-8 6-14 14-14h26" stroke="currentColor" stroke-width="2"/>
|
||
<path d="M10 44V24c0-6 4-10 10-10h24" stroke="currentColor" stroke-width="1.2" opacity="0.55"/>
|
||
<circle cx="18" cy="18" r="3" fill="currentColor"/>
|
||
<path d="M18 8c6 2 10 6 12 12" stroke="currentColor" stroke-width="1.2" opacity="0.7"/>
|
||
</svg>
|
||
</div>
|
||
<div class="sp-frame-ornament sp-frame-ornament-br" aria-hidden="true">
|
||
<svg viewBox="0 0 48 48" fill="none">
|
||
<path d="M4 44V18c0-8 6-14 14-14h26" stroke="currentColor" stroke-width="2"/>
|
||
<path d="M10 44V24c0-6 4-10 10-10h24" stroke="currentColor" stroke-width="1.2" opacity="0.55"/>
|
||
<circle cx="18" cy="18" r="3" fill="currentColor"/>
|
||
<path d="M18 8c6 2 10 6 12 12" stroke="currentColor" stroke-width="1.2" opacity="0.7"/>
|
||
</svg>
|
||
</div>
|
||
<div class="sp-card-rim" aria-hidden="true"></div>
|
||
<section class="sp-left">
|
||
<div class="sp-photo" :class="{ 'is-show': stage >= 1 }">
|
||
<img
|
||
v-if="cfg.heroImg"
|
||
:src="cfg.heroImg"
|
||
alt=""
|
||
crossorigin="anonymous"
|
||
:style="{ objectPosition: heroObjectPosition }"
|
||
/>
|
||
<div v-else class="sp-photo-ph">婚礼照片</div>
|
||
<div class="sp-photo-veil"></div>
|
||
</div>
|
||
<div class="sp-slogan-block" :class="{ 'is-show': stage >= 2 }">
|
||
<BlurText
|
||
v-if="stage >= 2"
|
||
:text="cfg.verticalSlogan || '吾家有喜'"
|
||
:active="stage >= 2"
|
||
animate-by="letters"
|
||
:delay="90"
|
||
class-name="sp-slogan calligraphy-strong"
|
||
@animation-complete="bumpStage(3)"
|
||
/>
|
||
<div v-if="stage >= 3" class="sp-subs">
|
||
<BlurText :text="cfg.subSlogan1 || ''" :active="true" animate-by="letters" :delay="50" class-name="sp-sub calligraphy" />
|
||
<BlurText :text="cfg.subSlogan2 || ''" :active="true" animate-by="letters" :delay="50" class-name="sp-sub calligraphy" />
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="sp-right">
|
||
<div class="sp-right-decor" aria-hidden="true">
|
||
<span class="sp-wm">囍</span>
|
||
</div>
|
||
<div class="sp-xi-top" :class="{ 'is-show': stage >= 3 }">囍</div>
|
||
|
||
<BlurText
|
||
v-if="stage >= 3"
|
||
:text="cfg.greeting || ''"
|
||
:active="stage >= 3"
|
||
animate-by="letters"
|
||
:delay="40"
|
||
class-name="sp-greeting calligraphy"
|
||
@animation-complete="bumpStage(4)"
|
||
/>
|
||
|
||
<div class="sp-body">
|
||
<template v-for="(line, i) in bodyLines" :key="`sp-${i}`">
|
||
<TextType
|
||
v-if="lineStage > i && i % 2 === 0"
|
||
:text="line"
|
||
:active="lineStage > i"
|
||
:loop="false"
|
||
:show-cursor="lineStage === i + 1"
|
||
cursor-character="|"
|
||
cursor-class-name="sp-cursor"
|
||
:typing-speed="42"
|
||
class-name="sp-line calligraphy"
|
||
@animation-complete="onLineDone(i)"
|
||
/>
|
||
<BlurText
|
||
v-else-if="lineStage > i"
|
||
:text="line"
|
||
:active="lineStage > i"
|
||
animate-by="words"
|
||
:delay="70"
|
||
class-name="sp-line calligraphy"
|
||
@animation-complete="onLineDone(i)"
|
||
/>
|
||
</template>
|
||
</div>
|
||
|
||
<div class="sp-couple" :class="{ 'is-show': stage >= 5 }">
|
||
<div class="sp-person sp-person-l">
|
||
<span class="sp-role calligraphy">新郎</span>
|
||
<BlurText v-if="stage >= 5" :text="groom" :active="true" animate-by="letters" :delay="40" class-name="sp-name calligraphy" />
|
||
</div>
|
||
<canvas ref="xiSealRef" class="sp-xi-mid" width="56" height="56" aria-hidden="true"></canvas>
|
||
<div class="sp-person sp-person-r">
|
||
<span class="sp-role calligraphy">新娘</span>
|
||
<BlurText v-if="stage >= 5" :text="bride" :active="true" animate-by="letters" :delay="40" class-name="sp-name calligraphy" @animation-complete="bumpStage(6)" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sp-meta" :class="{ 'is-show': stage >= 6 }">
|
||
<div class="sp-meta-title calligraphy">新婚喜宴时间</div>
|
||
<div class="sp-meta-val calligraphy">{{ cfg.dateText }}</div>
|
||
<div class="sp-meta-sub calligraphy">{{ cfg.lunarText }}</div>
|
||
<div class="sp-meta-title mt calligraphy">地点</div>
|
||
<div class="sp-meta-val calligraphy">{{ cfg.venueText }}</div>
|
||
</div>
|
||
|
||
<div class="sp-love" :class="{ 'is-show': stage >= 6 }">
|
||
<span class="sp-love-text calligraphy">{{ cfg.loveFooter || 'Love you' }}</span>
|
||
<span class="sp-heart">♥</span>
|
||
</div>
|
||
|
||
<div
|
||
v-if="cfg.showInviteButton && stage >= 6"
|
||
class="sp-cta"
|
||
data-html2canvas-ignore
|
||
>
|
||
<button type="button" class="sp-cta-btn" @click="$emit('invite')">
|
||
{{ cfg.inviteButtonText }}
|
||
</button>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||
import BlurText from '@/components/vue-bits/BlurText.vue'
|
||
import TextType from '@/components/vue-bits/TextType.vue'
|
||
import { heroFocusCss } from '@/composables/posterDefaults'
|
||
import { drawXiSeal } from '@/composables/drawXiSeal'
|
||
|
||
const props = defineProps({
|
||
cfg: { type: Object, required: true },
|
||
ready: { type: Boolean, default: false },
|
||
})
|
||
defineEmits(['invite'])
|
||
|
||
const stage = ref(0)
|
||
const lineStage = ref(0)
|
||
const xiSealRef = ref(null)
|
||
let timers = []
|
||
|
||
const isNarrow = ref(typeof window !== 'undefined' && window.matchMedia('(max-width: 719px)').matches)
|
||
let narrowMq = null
|
||
function onNarrowChange(e) {
|
||
isNarrow.value = !!e.matches
|
||
}
|
||
const heroObjectPosition = computed(() => heroFocusCss(props.cfg, isNarrow.value ? 'mobile' : undefined))
|
||
|
||
const bodyLines = computed(() => {
|
||
const lines = Array.isArray(props.cfg.lines) ? props.cfg.lines.filter(Boolean) : []
|
||
if (lines.length) return lines.slice(0, 4)
|
||
return [
|
||
'良辰已定,吉日待访,吾有薄酒',
|
||
'以谢良友,敬备喜宴,随候尊驾',
|
||
]
|
||
})
|
||
|
||
const groom = computed(() => props.cfg.groomName || props.cfg.sonName || '')
|
||
const bride = computed(() => props.cfg.brideName || props.cfg.daughterInLawName || '')
|
||
|
||
async function paintXiSeal() {
|
||
try { await document.fonts?.ready } catch { /* ignore */ }
|
||
drawXiSeal(xiSealRef.value, {
|
||
size: 28,
|
||
fontSize: 13,
|
||
color: '#d4af37',
|
||
borderColor: '#d4af37',
|
||
fill: 'rgba(0, 0, 0, 0.18)',
|
||
})
|
||
}
|
||
|
||
function later(fn, ms) {
|
||
timers.push(setTimeout(fn, ms))
|
||
}
|
||
|
||
function bumpStage(n) {
|
||
if (stage.value < n) stage.value = n
|
||
if (n === 4) {
|
||
if (!bodyLines.value.length) {
|
||
later(() => bumpStage(5), 200)
|
||
return
|
||
}
|
||
lineStage.value = 1
|
||
}
|
||
}
|
||
|
||
function onLineDone(i) {
|
||
if (i + 1 >= bodyLines.value.length) {
|
||
bumpStage(5)
|
||
return
|
||
}
|
||
if (lineStage.value === i + 1) lineStage.value = i + 2
|
||
}
|
||
|
||
watch(
|
||
() => props.ready,
|
||
(v) => {
|
||
if (!v) return
|
||
later(() => { stage.value = 1 }, 180)
|
||
later(() => { stage.value = 2 }, 550)
|
||
},
|
||
{ immediate: true },
|
||
)
|
||
|
||
onMounted(() => {
|
||
nextTick(paintXiSeal)
|
||
window.addEventListener('resize', paintXiSeal, { passive: true })
|
||
narrowMq = window.matchMedia('(max-width: 719px)')
|
||
isNarrow.value = !!narrowMq.matches
|
||
narrowMq.addEventListener?.('change', onNarrowChange)
|
||
})
|
||
|
||
onBeforeUnmount(() => {
|
||
window.removeEventListener('resize', paintXiSeal)
|
||
narrowMq?.removeEventListener?.('change', onNarrowChange)
|
||
narrowMq = null
|
||
timers.forEach(clearTimeout)
|
||
timers = []
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.sp {
|
||
position: relative;
|
||
z-index: 1;
|
||
width: min(720px, 100%);
|
||
opacity: 0;
|
||
transform: translateY(16px) scale(0.98);
|
||
transition: opacity 0.55s ease, transform 0.65s ease;
|
||
}
|
||
.sp.is-visible { opacity: 1; transform: none; }
|
||
|
||
.sp-shell {
|
||
position: relative;
|
||
width: 100%;
|
||
}
|
||
.sp-shell-outer {
|
||
padding: 1px;
|
||
border-radius: 14px;
|
||
background: linear-gradient(145deg, #f0d78c, #d4af37 45%, #a8892e);
|
||
box-shadow: 0 16px 42px rgba(0, 0, 0, 0.4);
|
||
}
|
||
.sp-shell-gap {
|
||
padding: 4px;
|
||
border-radius: 13px;
|
||
background: #7a0000;
|
||
border: 1px solid rgba(240, 215, 140, 0.28);
|
||
}
|
||
.sp-card {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
border: 2px solid #d4af37;
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
min-height: min(720px, calc(100dvh - 48px));
|
||
background: #8b0000;
|
||
}
|
||
.sp-card-rim {
|
||
position: absolute;
|
||
inset: 5px;
|
||
border: 1px solid rgba(240, 215, 140, 0.45);
|
||
border-radius: 7px;
|
||
pointer-events: none;
|
||
z-index: 6;
|
||
}
|
||
.sp-frame-ornament {
|
||
position: absolute;
|
||
width: 28px;
|
||
height: 28px;
|
||
color: rgba(240, 215, 140, 0.95);
|
||
z-index: 7;
|
||
pointer-events: none;
|
||
}
|
||
.sp-frame-ornament svg { width: 100%; height: 100%; display: block; }
|
||
.sp-frame-ornament-tl { top: 7px; left: 7px; }
|
||
.sp-frame-ornament-tr { top: 7px; right: 7px; transform: scaleX(-1); }
|
||
.sp-frame-ornament-bl { bottom: 7px; left: 7px; transform: scaleY(-1); }
|
||
.sp-frame-ornament-br { bottom: 7px; right: 7px; transform: scale(-1); }
|
||
|
||
/* 手机端:自动 = 上下;竖向强制上下;横屏强制左右 */
|
||
@media (max-width: 719px) {
|
||
.sp-m-auto .sp-card,
|
||
.sp-m-portrait .sp-card {
|
||
flex-direction: column;
|
||
}
|
||
.sp-m-auto .sp-left,
|
||
.sp-m-portrait .sp-left {
|
||
width: 100%;
|
||
}
|
||
.sp-m-landscape .sp-card {
|
||
flex-direction: row;
|
||
min-height: min(420px, 70dvh);
|
||
}
|
||
.sp-m-landscape .sp-left { width: 54%; min-height: 0; }
|
||
.sp-m-landscape .sp-right { width: 46%; padding: 10px 8px; }
|
||
.sp-m-landscape .sp-slogan { font-size: clamp(22px, 6vw, 32px); }
|
||
.sp-m-landscape .sp-photo { min-height: 100%; }
|
||
.sp-frame-ornament { width: 22px; height: 22px; }
|
||
.sp-card-rim { inset: 4px; }
|
||
.sp-shell-gap { padding: 3px; }
|
||
}
|
||
|
||
@media (min-width: 720px) {
|
||
.sp-card { flex-direction: row; min-height: min(560px, calc(100dvh - 56px)); }
|
||
.sp-left { width: 54%; }
|
||
.sp-right { width: 46%; }
|
||
}
|
||
|
||
.sp-left {
|
||
position: relative;
|
||
min-height: 260px;
|
||
background: #c23a2b;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.sp-photo {
|
||
position: relative;
|
||
flex: 1;
|
||
min-height: 220px;
|
||
opacity: 0;
|
||
transition: opacity 0.6s ease;
|
||
}
|
||
.sp-photo.is-show { opacity: 1; }
|
||
.sp-photo img { width: 100%; height: 100%; object-fit: cover; display: block; position: absolute; inset: 0; }
|
||
.sp-photo-ph {
|
||
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
|
||
color: rgba(255, 240, 220, 0.55); letter-spacing: 0.3em;
|
||
}
|
||
.sp-photo-veil {
|
||
position: absolute; inset: 0;
|
||
background: linear-gradient(180deg, transparent 40%, rgba(140, 20, 20, 0.55) 100%);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.sp-slogan-block {
|
||
position: absolute;
|
||
left: 10%;
|
||
top: 12%;
|
||
bottom: 12%;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: flex-start;
|
||
gap: 12px;
|
||
opacity: 0;
|
||
transition: opacity 0.45s ease;
|
||
z-index: 2;
|
||
writing-mode: vertical-rl;
|
||
text-orientation: mixed;
|
||
}
|
||
.sp-slogan-block.is-show { opacity: 1; }
|
||
|
||
/* 须写在基础规则之后,否则 top/left/bottom 会被后面的基础样式盖掉 */
|
||
@media (max-width: 719px) {
|
||
.sp-slogan-block {
|
||
left: auto;
|
||
right: 12%;
|
||
top: 46%;
|
||
bottom: auto;
|
||
}
|
||
}
|
||
|
||
.sp-slogan {
|
||
color: #f3e6c8 !important;
|
||
font-size: clamp(36px, 8vw, 52px);
|
||
letter-spacing: 0.2em;
|
||
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
|
||
justify-content: flex-start;
|
||
}
|
||
.sp-subs { display: flex; flex-direction: column; gap: 18px; margin-top: 8px; }
|
||
.sp-sub {
|
||
color: rgba(243, 230, 200, 0.88) !important;
|
||
font-size: 13px;
|
||
letter-spacing: 0.35em;
|
||
}
|
||
|
||
.sp-right {
|
||
position: relative;
|
||
padding: 22px 20px 18px;
|
||
background:
|
||
radial-gradient(circle at 80% 10%, rgba(255, 255, 255, 0.06), transparent 30%),
|
||
linear-gradient(180deg, #9b0000, #7a0000);
|
||
color: var(--hb-gold-soft);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
text-align: center;
|
||
overflow: hidden;
|
||
}
|
||
.sp-right-decor { position: absolute; inset: 0; pointer-events: none; }
|
||
.sp-wm {
|
||
position: absolute; left: 50%; top: 48%; transform: translate(-50%, -50%);
|
||
font-size: 180px; font-weight: 700; color: rgba(196, 30, 58, 0.12); line-height: 1;
|
||
}
|
||
|
||
.sp-xi-top {
|
||
font-size: 48px; font-weight: 700; color: var(--hb-gold-soft);
|
||
opacity: 0; transform: scale(0.8); transition: opacity 0.4s ease, transform 0.4s ease;
|
||
margin-bottom: 8px; text-shadow: 0 2px 8px rgba(0, 0, 0, 0.25); position: relative; z-index: 1;
|
||
}
|
||
.sp-xi-top.is-show { opacity: 1; transform: scale(1); }
|
||
|
||
.sp-greeting {
|
||
color: #f7efe0 !important; font-size: 14px; letter-spacing: 0.2em;
|
||
margin-bottom: 12px; justify-content: center; position: relative; z-index: 1;
|
||
}
|
||
|
||
.sp-body {
|
||
position: relative; z-index: 1;
|
||
display: flex; flex-direction: column; align-items: center; gap: 8px;
|
||
min-height: 72px; margin-bottom: 14px; width: 100%;
|
||
}
|
||
.sp-line {
|
||
color: var(--hb-gold-soft) !important; font-size: 13px; letter-spacing: 0.12em;
|
||
line-height: 1.55; justify-content: center; text-align: center;
|
||
}
|
||
.sp-cursor { color: var(--hb-gold); }
|
||
|
||
.sp-couple {
|
||
position: relative;
|
||
z-index: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
width: 100%;
|
||
margin: 6px 0 14px;
|
||
opacity: 0;
|
||
transition: opacity 0.4s ease;
|
||
}
|
||
.sp-couple.is-show { opacity: 1; }
|
||
.sp-person {
|
||
flex: 1 1 0;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
.sp-person-l { padding-right: 22px; }
|
||
.sp-person-r { padding-left: 22px; }
|
||
.sp-role { font-size: 11px; color: rgba(247, 239, 224, 0.7); letter-spacing: 0.3em; }
|
||
.sp-name { color: var(--hb-gold-soft) !important; font-size: 18px; letter-spacing: 0.2em; justify-content: center; }
|
||
/* canvas 位图印章:不用 CSS transform,避免 html2canvas 错位 */
|
||
.sp-xi-mid {
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 50%;
|
||
width: 28px;
|
||
height: 28px;
|
||
margin-left: -14px;
|
||
margin-top: -14px;
|
||
display: block;
|
||
pointer-events: none;
|
||
z-index: 2;
|
||
}
|
||
|
||
.sp-meta {
|
||
position: relative; z-index: 1; width: 100%;
|
||
opacity: 0; transition: opacity 0.45s ease; margin-bottom: 10px;
|
||
}
|
||
.sp-meta.is-show { opacity: 1; }
|
||
.sp-meta-title {
|
||
font-size: 12px; letter-spacing: 0.35em; color: rgba(247, 239, 224, 0.75); margin-bottom: 4px;
|
||
}
|
||
.sp-meta-title.mt { margin-top: 12px; }
|
||
.sp-meta-val { font-size: 14px; letter-spacing: 0.12em; color: var(--hb-gold-soft); }
|
||
.sp-meta-sub { font-size: 12px; margin-top: 2px; color: rgba(240, 215, 140, 0.75); letter-spacing: 0.15em; }
|
||
|
||
.sp-love {
|
||
position: relative; z-index: 1;
|
||
margin-top: auto; padding-top: 10px;
|
||
opacity: 0; transition: opacity 0.4s ease;
|
||
display: flex; align-items: center; gap: 6px;
|
||
}
|
||
.sp-love.is-show { opacity: 1; }
|
||
.sp-love-text { font-size: 22px; color: #f3e6c8; }
|
||
.sp-heart { color: #e85d5d; font-size: 14px; }
|
||
|
||
.sp-cta { position: relative; z-index: 1; margin-top: 12px; }
|
||
.sp-cta-btn {
|
||
appearance: none; border: 1px solid var(--hb-gold);
|
||
background: linear-gradient(180deg, rgba(212, 175, 55, 0.22), rgba(212, 175, 55, 0.08));
|
||
color: var(--hb-gold-soft); padding: 7px 18px; border-radius: 999px;
|
||
letter-spacing: 0.28em; font-size: 12px; cursor: pointer;
|
||
}
|
||
|
||
/* 移动端右侧文案缩小(须在基础字号之后,避免被覆盖) */
|
||
@media (max-width: 719px) {
|
||
.sp-right { padding: 14px 12px 12px; }
|
||
.sp-wm { font-size: 120px; }
|
||
.sp-xi-top { font-size: 34px; margin-bottom: 4px; }
|
||
.sp-greeting { font-size: 11px; letter-spacing: 0.14em; margin-bottom: 8px; }
|
||
.sp-body { gap: 5px; min-height: 56px; margin-bottom: 10px; }
|
||
.sp-line { font-size: 11px; letter-spacing: 0.08em; line-height: 1.45; }
|
||
.sp-couple { margin: 4px 0 10px; }
|
||
.sp-person-l { padding-right: 16px; }
|
||
.sp-person-r { padding-left: 16px; }
|
||
.sp-role { font-size: 9px; letter-spacing: 0.2em; }
|
||
.sp-name { font-size: 14px; letter-spacing: 0.14em; }
|
||
.sp-xi-mid { width: 24px; height: 24px; margin-left: -12px; margin-top: -12px; }
|
||
.sp-meta-title { font-size: 10px; letter-spacing: 0.22em; margin-bottom: 2px; }
|
||
.sp-meta-title.mt { margin-top: 8px; }
|
||
.sp-meta-val { font-size: 12px; letter-spacing: 0.08em; }
|
||
.sp-meta-sub { font-size: 10px; }
|
||
.sp-love-text { font-size: 16px; }
|
||
.sp-heart { font-size: 12px; }
|
||
.sp-cta { margin-top: 8px; }
|
||
.sp-cta-btn { padding: 6px 14px; font-size: 11px; letter-spacing: 0.2em; }
|
||
}
|
||
</style>
|