fix: 升级了一些视觉
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
"@fingerprintjs/fingerprintjs": "^5.2.0",
|
||||
"@fontsource/cinzel": "^5.3.0",
|
||||
"@fontsource/fusion-pixel-12px-proportional-sc": "^5.3.0",
|
||||
"@fontsource/great-vibes": "^5.3.0",
|
||||
"@fontsource/liu-jian-mao-cao": "^5.3.0",
|
||||
"@fontsource/long-cang": "^5.3.0",
|
||||
@@ -34,6 +35,7 @@
|
||||
"qrcode": "^1.5.4",
|
||||
"swiper": "^11.0.5",
|
||||
"tailwindcss": "^4.1.4",
|
||||
"three": "^0.185.1",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.2.5"
|
||||
},
|
||||
|
||||
26
vite-tailwindcss/pnpm-lock.yaml
generated
26
vite-tailwindcss/pnpm-lock.yaml
generated
@@ -20,6 +20,9 @@ importers:
|
||||
'@fontsource/cinzel':
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
'@fontsource/fusion-pixel-12px-proportional-sc':
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
'@fontsource/great-vibes':
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
@@ -55,7 +58,7 @@ importers:
|
||||
version: 4.2.6(vue@3.5.40)
|
||||
axios:
|
||||
specifier: ^1.6.2
|
||||
version: 1.18.1(debug@4.4.3)
|
||||
version: 1.18.1
|
||||
echarts:
|
||||
specifier: ^6.1.0
|
||||
version: 6.1.0
|
||||
@@ -83,6 +86,9 @@ importers:
|
||||
tailwindcss:
|
||||
specifier: ^4.1.4
|
||||
version: 4.3.3
|
||||
three:
|
||||
specifier: ^0.185.1
|
||||
version: 0.185.1
|
||||
vue:
|
||||
specifier: ^3.5.13
|
||||
version: 3.5.40
|
||||
@@ -317,6 +323,9 @@ packages:
|
||||
'@fontsource/cinzel@5.3.0':
|
||||
resolution: {integrity: sha512-KEOYTsrRppW0uRIPWBDPrQgunCS6f8u4nzZxYRBfo9SCknKojLYjK7B0ZoVmJhyVLefz271ZQRF9EOpHZlfhRw==}
|
||||
|
||||
'@fontsource/fusion-pixel-12px-proportional-sc@5.3.0':
|
||||
resolution: {integrity: sha512-PDgfCunZbVgPM5/HSaP29KjQaxLUsVAng6urpSQXww87e9AOfA7j/PDzWYO/Q8yhPyaw6oUGh5s2eGln1HSgPw==}
|
||||
|
||||
'@fontsource/great-vibes@5.3.0':
|
||||
resolution: {integrity: sha512-1w60cXzXnC9V4y943po8044O1mqRQBiTTK75h7OpZE+1VQtx+KlAAzoIyEE5KNzu05gUpxaCQg7cbL7ITSwegQ==}
|
||||
|
||||
@@ -1274,6 +1283,9 @@ packages:
|
||||
text-segmentation@1.0.3:
|
||||
resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
|
||||
|
||||
three@0.185.1:
|
||||
resolution: {integrity: sha512-5aojFCXKwnjBRZvUnt3WFfEcvUJgkN5LlijRFN95hMy8WVkG4I0QNcJE+OuWvuJ0bOdStrbfXn0pkd6/QyiAlg==}
|
||||
|
||||
throttle-debounce@5.0.2:
|
||||
resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==}
|
||||
engines: {node: '>=12.22'}
|
||||
@@ -1521,6 +1533,8 @@ snapshots:
|
||||
|
||||
'@fontsource/cinzel@5.3.0': {}
|
||||
|
||||
'@fontsource/fusion-pixel-12px-proportional-sc@5.3.0': {}
|
||||
|
||||
'@fontsource/great-vibes@5.3.0': {}
|
||||
|
||||
'@fontsource/liu-jian-mao-cao@5.3.0': {}
|
||||
@@ -1919,9 +1933,9 @@ snapshots:
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
axios@1.18.1(debug@4.4.3):
|
||||
axios@1.18.1:
|
||||
dependencies:
|
||||
follow-redirects: 1.16.0(debug@4.4.3)
|
||||
follow-redirects: 1.16.0
|
||||
form-data: 4.0.6
|
||||
https-proxy-agent: 5.0.1
|
||||
proxy-from-env: 2.1.0
|
||||
@@ -2061,9 +2075,7 @@ snapshots:
|
||||
locate-path: 5.0.0
|
||||
path-exists: 4.0.0
|
||||
|
||||
follow-redirects@1.16.0(debug@4.4.3):
|
||||
optionalDependencies:
|
||||
debug: 4.4.3
|
||||
follow-redirects@1.16.0: {}
|
||||
|
||||
form-data@4.0.6:
|
||||
dependencies:
|
||||
@@ -2382,6 +2394,8 @@ snapshots:
|
||||
dependencies:
|
||||
utrie: 1.0.2
|
||||
|
||||
three@0.185.1: {}
|
||||
|
||||
throttle-debounce@5.0.2: {}
|
||||
|
||||
tinyglobby@0.2.17:
|
||||
|
||||
@@ -51,13 +51,13 @@ const { toasts, dismiss } = useToast()
|
||||
padding: 12px 14px;
|
||||
border-radius: 16px;
|
||||
background: rgba(253, 251, 247, 0.96);
|
||||
border: 0.5px solid rgba(168, 140, 107, 0.35);
|
||||
border: 0.5px solid rgba(var(--c-primary-rgb), 0.35);
|
||||
box-shadow: 0 12px 32px rgba(92, 74, 53, 0.16);
|
||||
backdrop-filter: blur(8px);
|
||||
color: #5c4a35;
|
||||
}
|
||||
.app-toast.is-success {
|
||||
border-color: rgba(168, 140, 107, 0.5);
|
||||
border-color: rgba(var(--c-primary-rgb), 0.5);
|
||||
}
|
||||
.app-toast.is-error {
|
||||
border-color: rgba(196, 120, 110, 0.55);
|
||||
@@ -73,8 +73,8 @@ const { toasts, dismiss } = useToast()
|
||||
justify-content: center;
|
||||
margin-top: 1px;
|
||||
font-size: 11px;
|
||||
background: rgba(168, 140, 107, 0.14);
|
||||
color: #a88c6b;
|
||||
background: rgba(var(--c-primary-rgb), 0.14);
|
||||
color: var(--c-primary);
|
||||
}
|
||||
.app-toast.is-error .app-toast-icon {
|
||||
background: rgba(196, 120, 110, 0.16);
|
||||
|
||||
143
vite-tailwindcss/src/components/BorderStylePicker.vue
Normal file
143
vite-tailwindcss/src/components/BorderStylePicker.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div class="bsp">
|
||||
<div v-for="group in BORDER_STYLE_GROUPS" :key="group.key" class="bsp-group">
|
||||
<div class="bsp-group-title">{{ group.label }}</div>
|
||||
<div class="bsp-grid">
|
||||
<button
|
||||
v-for="b in group.items"
|
||||
:key="b.value"
|
||||
type="button"
|
||||
class="bsp-cell"
|
||||
:class="{ active: modelValue === b.value }"
|
||||
:title="b.label"
|
||||
@click="$emit('update:modelValue', b.value)"
|
||||
>
|
||||
<span class="bsp-thumb-box" :style="{ height: thumbHeight(b) / 2 + 'px' }">
|
||||
<span
|
||||
class="bsp-thumb"
|
||||
:class="{ 'gf-shape-shadow': b.shaped }"
|
||||
:style="{ height: thumbHeight(b) + 'px' }"
|
||||
>
|
||||
<span class="bsp-frame" :class="borderClass(b.value)">
|
||||
<img :src="sample" alt="" loading="lazy" />
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="bsp-name">{{ b.label }}</span>
|
||||
<i v-if="modelValue === b.value" class="bsp-check fa-solid fa-check"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { BORDER_STYLE_GROUPS, borderClass } from '@/composables/borderStyles'
|
||||
|
||||
defineProps({
|
||||
modelValue: { type: String, default: 'mat' },
|
||||
/** 缩略示例图 */
|
||||
sample: {
|
||||
type: String,
|
||||
default: 'https://images.unsplash.com/photo-1519741497674-611481863552?auto=format&fit=crop&w=200&q=80',
|
||||
},
|
||||
})
|
||||
defineEmits(['update:modelValue'])
|
||||
|
||||
/** 缩略图按 2 倍渲染再 scale(0.5),保证固定像素边框细节比例正确;高度随样式推荐宽高比 */
|
||||
const THUMB_W = 120
|
||||
function thumbHeight(b) {
|
||||
const parts = String(b.ratio || '4 / 5').split('/')
|
||||
const w = parseFloat(parts[0]) || 4
|
||||
const h = parseFloat(parts[1]) || 5
|
||||
return Math.round((THUMB_W * h) / w)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.bsp {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.bsp-group-title {
|
||||
font-size: 11px;
|
||||
color: var(--c-primary);
|
||||
letter-spacing: 0.12em;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.bsp-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(78px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
.bsp-cell {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
padding: 10px 4px 7px;
|
||||
border: 1px solid rgba(var(--c-primary-rgb), 0.14);
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
|
||||
}
|
||||
.bsp-cell:hover {
|
||||
border-color: rgba(var(--c-primary-rgb), 0.4);
|
||||
background: #fbf9f4;
|
||||
}
|
||||
.bsp-cell.active {
|
||||
border-color: var(--c-primary);
|
||||
background: var(--c-bg);
|
||||
box-shadow: 0 0 0 2px rgba(var(--c-primary-rgb), 0.18);
|
||||
}
|
||||
.bsp-thumb-box {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 60px;
|
||||
}
|
||||
.bsp-thumb {
|
||||
flex: none;
|
||||
width: 120px;
|
||||
transform: scale(0.5);
|
||||
transform-origin: top center;
|
||||
}
|
||||
.bsp-frame {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
/* 缩略图太窄,邮戳 SVG 会压住 POST CARD 字样,仅保留字样 */
|
||||
.bsp-frame.gf-postcard::after {
|
||||
display: none;
|
||||
}
|
||||
.bsp-name {
|
||||
font-size: 10px;
|
||||
color: #6b6863;
|
||||
letter-spacing: 0.05em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.bsp-cell.active .bsp-name {
|
||||
color: var(--c-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
.bsp-check {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
z-index: 1;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
background: var(--c-primary);
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 6px rgba(120, 90, 60, 0.3);
|
||||
}
|
||||
</style>
|
||||
@@ -10,25 +10,22 @@
|
||||
v-for="row in rows"
|
||||
:key="row.id"
|
||||
class="danmaku-item"
|
||||
:class="{
|
||||
'danmaku-item--grad': row.isGradient,
|
||||
'danmaku-item--grad-light': row.isGradient && row.textLight,
|
||||
'danmaku-item--grad-dark': row.isGradient && !row.textLight,
|
||||
}"
|
||||
:class="{ 'danmaku-item--grad': row.isGradient }"
|
||||
:style="row.style"
|
||||
@animationend="onEnded(row.id)"
|
||||
>
|
||||
<span class="danmaku-name" :style="{ color: row.color }">{{ row.name }}</span>
|
||||
<span class="danmaku-sep" :style="{ color: row.color }">:</span>
|
||||
<span class="danmaku-text" :style="{ color: row.color }">{{ row.content }}</span>
|
||||
<!-- 渐变款:颜色落在文字上(clip 渐变字),底子是通透白纱 -->
|
||||
<span
|
||||
v-if="showTime && row.timeLabel"
|
||||
class="danmaku-time"
|
||||
:class="{
|
||||
'danmaku-time--on-light': row.isGradient && row.textLight,
|
||||
'danmaku-time--on-dark': row.isGradient && !row.textLight,
|
||||
}"
|
||||
>{{ row.timeLabel }}</span>
|
||||
v-if="row.textGradient"
|
||||
class="danmaku-body danmaku-body--grad"
|
||||
:style="{ backgroundImage: row.textGradient }"
|
||||
><span class="danmaku-name">{{ row.name }}</span><span class="danmaku-sep">:</span><span class="danmaku-text">{{ row.content }}</span></span>
|
||||
<template v-else>
|
||||
<span class="danmaku-name" :style="{ color: row.color }">{{ row.name }}</span>
|
||||
<span class="danmaku-sep" :style="{ color: row.color }">:</span>
|
||||
<span class="danmaku-text" :style="{ color: row.color }">{{ row.content }}</span>
|
||||
</template>
|
||||
<span v-if="showTime && row.timeLabel" class="danmaku-time">{{ row.timeLabel }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -173,7 +170,6 @@ const buildRow = (item, { force = false } = {}) => {
|
||||
const totalDelay = delay + slot.extraDelay
|
||||
// 轨道分布在当前弹幕层内部(由 area 控制层高度)
|
||||
const topPct = TRACKS <= 1 ? 50 : 12 + (slot.track / (TRACKS - 1)) * 70
|
||||
const isGradient = typeof tint.background === 'string' && tint.background.includes('gradient')
|
||||
|
||||
const timeLabel = force
|
||||
? '刚刚'
|
||||
@@ -184,8 +180,8 @@ const buildRow = (item, { force = false } = {}) => {
|
||||
name: item.name,
|
||||
content: item.content,
|
||||
color: resolveDanmakuColor(item.color),
|
||||
textLight: !!tint.textLight,
|
||||
isGradient,
|
||||
isGradient: !!tint.textGradient,
|
||||
textGradient: tint.textGradient || '',
|
||||
timeLabel,
|
||||
style: {
|
||||
top: `${topPct}%`,
|
||||
@@ -377,7 +373,7 @@ defineExpose({ refresh: fetchList, pushItem })
|
||||
white-space: nowrap;
|
||||
padding: 5px 14px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(168, 140, 107, 0.28);
|
||||
border: 1px solid rgba(var(--c-primary-rgb), 0.28);
|
||||
box-shadow: 0 4px 14px rgba(88, 68, 42, 0.08);
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.06em;
|
||||
@@ -389,14 +385,18 @@ defineExpose({ refresh: fetchList, pushItem })
|
||||
max-width: none;
|
||||
}
|
||||
.danmaku-item--grad {
|
||||
backdrop-filter: saturate(1.2) blur(8px);
|
||||
backdrop-filter: saturate(1.15) blur(7px);
|
||||
font-weight: 500;
|
||||
}
|
||||
.danmaku-item--grad-light {
|
||||
text-shadow: 0 1px 2px rgba(20, 10, 20, 0.45), 0 0 1px rgba(0, 0, 0, 0.2);
|
||||
/* 渐变字:颜色裁切进文字,底子保持通透 */
|
||||
.danmaku-body--grad {
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
.danmaku-item--grad-dark {
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.35);
|
||||
.danmaku-body--grad .danmaku-sep,
|
||||
.danmaku-body--grad .danmaku-text {
|
||||
opacity: 1;
|
||||
}
|
||||
.danmaku-name { font-weight: 600; }
|
||||
.danmaku-sep { opacity: 0.9; }
|
||||
@@ -407,12 +407,6 @@ defineExpose({ refresh: fetchList, pushItem })
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.danmaku-time--on-light {
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
}
|
||||
.danmaku-time--on-dark {
|
||||
color: rgba(74, 52, 32, 0.72);
|
||||
}
|
||||
@keyframes danmaku-fly {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(calc(-100vw - 100%)); }
|
||||
|
||||
1135
vite-tailwindcss/src/components/GiftEffect3D.vue
Normal file
1135
vite-tailwindcss/src/components/GiftEffect3D.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div
|
||||
class="relative w-[96px] h-[96px] rounded-xl border border-dashed border-[#a88c6b]/40 bg-[#Fdfbf7] overflow-hidden cursor-pointer hover:border-[#a88c6b] transition-colors group"
|
||||
class="relative w-[96px] h-[96px] rounded-xl border border-dashed border-[var(--c-primary)]/40 bg-[var(--c-paper)] overflow-hidden cursor-pointer hover:border-[var(--c-primary)] transition-colors group"
|
||||
@click="$emit('pick')"
|
||||
>
|
||||
<img v-if="modelValue" :src="modelValue" class="w-full h-full object-cover" />
|
||||
<div v-else class="w-full h-full flex flex-col items-center justify-center text-[#a88c6b]/70 gap-1">
|
||||
<div v-else class="w-full h-full flex flex-col items-center justify-center text-[var(--c-primary)]/70 gap-1">
|
||||
<i class="fa-solid fa-image text-lg"></i>
|
||||
<span class="text-[10px]">{{ placeholder }}</span>
|
||||
</div>
|
||||
<div class="absolute inset-0 bg-black/35 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center text-white text-[10px]">
|
||||
<i class="fa-solid fa-images mr-1"></i>{{ uploading ? '上传中' : '选择图片' }}
|
||||
</div>
|
||||
<div v-if="label" class="absolute left-1 bottom-1 bg-white/80 text-[#a88c6b] text-[9px] px-1 rounded">{{ label }}</div>
|
||||
<div v-if="label" class="absolute left-1 bottom-1 bg-white/80 text-[var(--c-primary)] text-[9px] px-1 rounded">{{ label }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
@cancel="$emit('update:open', false)"
|
||||
>
|
||||
<div class="mp-toolbar">
|
||||
<a-button type="primary" class="!bg-[#a88c6b]" :loading="uploading" @click="triggerUpload">
|
||||
<a-button type="primary" class="!bg-[var(--c-primary)]" :loading="uploading" @click="triggerUpload">
|
||||
<i class="fa-solid fa-cloud-arrow-up mr-1"></i>上传新图
|
||||
</a-button>
|
||||
<a-button :loading="loading" @click="reloadFirstPage">
|
||||
@@ -153,12 +153,12 @@ watch(
|
||||
}
|
||||
.mp-hint {
|
||||
font-size: 12px;
|
||||
color: #8a8680;
|
||||
color: var(--c-muted);
|
||||
margin-left: auto;
|
||||
}
|
||||
.mp-empty {
|
||||
text-align: center;
|
||||
color: #8a8680;
|
||||
color: var(--c-muted);
|
||||
padding: 48px 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -170,8 +170,8 @@ watch(
|
||||
}
|
||||
.mp-item {
|
||||
appearance: none;
|
||||
border: 1px solid rgba(168, 140, 107, 0.28);
|
||||
background: #fdfbf7;
|
||||
border: 1px solid rgba(var(--c-primary-rgb), 0.28);
|
||||
background: var(--c-paper);
|
||||
border-radius: 10px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
@@ -181,21 +181,21 @@ watch(
|
||||
}
|
||||
.mp-item:hover,
|
||||
.mp-item.active {
|
||||
border-color: #a88c6b;
|
||||
box-shadow: 0 0 0 2px rgba(168, 140, 107, 0.2);
|
||||
border-color: var(--c-primary);
|
||||
box-shadow: 0 0 0 2px rgba(var(--c-primary-rgb), 0.2);
|
||||
}
|
||||
.mp-item img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 88px;
|
||||
object-fit: cover;
|
||||
background: #f0ebe3;
|
||||
background: var(--c-bg-deep);
|
||||
}
|
||||
.mp-name {
|
||||
display: block;
|
||||
padding: 4px 6px;
|
||||
font-size: 10px;
|
||||
color: #8a8680;
|
||||
color: var(--c-muted);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
<template>
|
||||
<div class="gallery">
|
||||
<!-- 标题区 -->
|
||||
<div v-reveal="'up'" class="text-center mb-5 px-4">
|
||||
<span class="gallery-title calligraphy text-xl text-[#a88c6b] tracking-[0.3em] font-bold block mb-2">{{ page.title }}</span>
|
||||
<span class="gallery-subtitle text-[9px] text-[#8A8680] first-letter:uppercase lowercase tracking-[0.4em] block mb-4">{{ page.subtitle }}</span>
|
||||
<span class="gallery-desc text-[12px] text-[#6b6863] font-light leading-relaxed mx-auto max-w-[280px] tracking-widest block whitespace-pre-wrap">{{ page.desc }}</span>
|
||||
<!-- 标题区:完整模板主题用统一章节头,风格包保留经典排版 -->
|
||||
<SectionHeading
|
||||
v-if="layoutTheme"
|
||||
v-reveal="'up'"
|
||||
:index="pageIndex"
|
||||
:kicker="page.subtitle"
|
||||
:title="page.title"
|
||||
:note="page.desc"
|
||||
/>
|
||||
<div v-else v-reveal="'up'" class="text-center mb-5 px-4">
|
||||
<span class="gallery-title calligraphy text-xl text-[var(--c-primary)] tracking-[0.3em] font-bold block mb-2">{{ page.title }}</span>
|
||||
<span class="gallery-subtitle text-[9px] text-[var(--c-muted)] first-letter:uppercase lowercase tracking-[0.4em] block mb-4">{{ page.subtitle }}</span>
|
||||
<span class="gallery-desc text-[12px] text-[var(--c-muted)] font-light leading-relaxed mx-auto max-w-[280px] tracking-widest block whitespace-pre-wrap">{{ page.desc }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 单图(默认居中卡幅 / 可配置宽度铺满) -->
|
||||
<div v-if="page.type === 'single'" v-reveal="{ variant: 'scale', delay: 100 }"
|
||||
<div v-if="page.type === 'single'" v-reveal="{ variant: singleRevealVariant, delay: 100 }"
|
||||
class="w-full mx-auto mt-6" :class="singleWrapClass" :style="singleWrapStyle">
|
||||
<div :class="singleFrameClass">
|
||||
<img :src="displayImg1" class="preview-img" @click.stop="previewImage(originalImg1)" />
|
||||
<span v-if="showPostmark" class="gf-postmark" aria-hidden="true"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -197,8 +206,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 爱心拼接:绝对定位心形轮廓 + 串行挂载 -->
|
||||
<div v-else-if="page.type === 'heart-collage'" ref="multiRootRef" class="g-heart mt-6">
|
||||
<!-- 爱心拼接:绝对定位心形轮廓 + 串行挂载;拼合完成后心跳一次并飘散小心心 -->
|
||||
<div
|
||||
v-else-if="page.type === 'heart-collage'"
|
||||
ref="multiRootRef"
|
||||
class="g-heart mt-6"
|
||||
:class="{ 'g-heart-done': heartDone }"
|
||||
>
|
||||
<div
|
||||
v-for="(slot, imgIdx) in HEART_SLOTS"
|
||||
:key="imgIdx"
|
||||
@@ -211,6 +225,7 @@
|
||||
v-reveal="{ variant: heartReveal(imgIdx), immediate: true }"
|
||||
class="absolute inset-0 g-heart-cell"
|
||||
:class="frameClass"
|
||||
:style="heartCellShape(imgIdx)"
|
||||
>
|
||||
<img
|
||||
:src="displayAt(imgIdx)"
|
||||
@@ -221,17 +236,81 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="heartDone">
|
||||
<span class="g-heart-spark g-heart-spark-1" aria-hidden="true">♥</span>
|
||||
<span class="g-heart-spark g-heart-spark-2" aria-hidden="true">♥</span>
|
||||
<span class="g-heart-spark g-heart-spark-3" aria-hidden="true">♥</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-else class="text-center text-[#8A8680] text-[12px] mt-6">未识别的版式:{{ page.type }}</div>
|
||||
<!-- 三联屏:三竖条并排,中列上浮错落 -->
|
||||
<div v-else-if="page.type === 'triptych'" class="g-triptych mt-6">
|
||||
<div
|
||||
v-for="(_, imgIdx) in (page.images || []).slice(0, 3)"
|
||||
:key="imgIdx"
|
||||
v-reveal="{ variant: imgIdx === 0 ? 'left' : imgIdx === 2 ? 'right' : 'up', delay: imgIdx * 130 }"
|
||||
class="g-triptych-item"
|
||||
:class="[frameClass, { 'g-triptych-mid': imgIdx === 1 }]"
|
||||
>
|
||||
<img :src="displayAt(imgIdx)" class="preview-img g-cover-img" decoding="async" @click.stop="previewImage(originalAt(imgIdx))" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 胶片连拍:深色胶片条 + 横滑帧 -->
|
||||
<div v-else-if="page.type === 'filmstrip'" v-reveal="{ variant: 'up', delay: 100 }" class="g-filmstrip mt-6">
|
||||
<div class="g-filmstrip-track no-scrollbar">
|
||||
<div
|
||||
v-for="(_, imgIdx) in (page.images || [])"
|
||||
:key="imgIdx"
|
||||
class="g-filmstrip-frame"
|
||||
:class="frameClass"
|
||||
>
|
||||
<img :src="displayAt(imgIdx)" class="preview-img g-cover-img" decoding="async" @click.stop="previewImage(originalAt(imgIdx))" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 扇形叠影:五张卡入场后从底部枢轴展开成扇 -->
|
||||
<div v-else-if="page.type === 'fan'" v-reveal="{ variant: 'scale', delay: 120 }" class="g-fan mt-4">
|
||||
<div
|
||||
v-for="(_, imgIdx) in (page.images || []).slice(0, 5)"
|
||||
:key="imgIdx"
|
||||
class="g-fan-item"
|
||||
:class="frameClass"
|
||||
>
|
||||
<img :src="displayAt(imgIdx)" class="preview-img g-cover-img" decoding="async" @click.stop="previewImage(originalAt(imgIdx))" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 杂志拼贴:横竖交错三行 grid + 串行挂载 -->
|
||||
<div v-else-if="page.type === 'mosaic'" ref="multiRootRef" class="g-mosaic mt-6">
|
||||
<div
|
||||
v-for="(_, imgIdx) in (page.images || []).slice(0, 6)"
|
||||
:key="imgIdx"
|
||||
class="g-mosaic-slot"
|
||||
:class="[`g-mosaic-area-${imgIdx + 1}`, { 'g-mosaic-placeholder': imgIdx >= shownCount }]"
|
||||
>
|
||||
<div
|
||||
v-if="imgIdx < shownCount"
|
||||
v-reveal="{ variant: imgIdx % 2 === 0 ? 'left' : 'right', immediate: true }"
|
||||
class="absolute inset-0"
|
||||
:class="frameClass"
|
||||
>
|
||||
<img :src="displayAt(imgIdx)" class="preview-img g-cover-img" decoding="async" @click.stop="previewImage(originalAt(imgIdx))" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="text-center text-[var(--c-muted)] text-[12px] mt-6">未识别的版式:{{ page.type }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'
|
||||
import { borderClass } from '@/composables/borderStyles'
|
||||
import { borderClass, borderStyleMeta } from '@/composables/borderStyles'
|
||||
import { createSequentialMount } from '@/composables/useSequentialMount'
|
||||
import { HEART_SLOTS, heartSlotStyle, heartFocusCssAt } from '@/composables/heartCollage'
|
||||
import { HEART_SLOTS, heartSlotStyle, heartFocusCssAt, heartCellShape } from '@/composables/heartCollage'
|
||||
import SectionHeading from '@/components/themes/SectionHeading.vue'
|
||||
|
||||
const NINE_STEP_MS = 300
|
||||
const NINE_SETTLE_MS = 400
|
||||
@@ -264,12 +343,17 @@ const SEQUENTIAL_SELF_TYPES = new Set([
|
||||
'nine-grid',
|
||||
'one-large-five-small',
|
||||
'heart-collage',
|
||||
'mosaic',
|
||||
])
|
||||
|
||||
const props = defineProps({
|
||||
page: { type: Object, required: true },
|
||||
/** 九宫格:开启串行 v-reveal;关闭则一次展示 */
|
||||
nineGridAnimate: { type: Boolean, default: false },
|
||||
/** 完整模板主题(magazine/newspaper/pixel/zen):标题区换统一章节头 */
|
||||
layoutTheme: { type: Boolean, default: false },
|
||||
/** 章节序号(完整模板的编号章节头用) */
|
||||
pageIndex: { type: Number, default: 0 },
|
||||
})
|
||||
const emit = defineEmits(['preview-image', 'ready'])
|
||||
|
||||
@@ -475,7 +559,7 @@ const runSequentialByType = async () => {
|
||||
await runOlfsSequential()
|
||||
return
|
||||
}
|
||||
if (type === 'masonry' || type === 'polaroid' || type === 'heart-collage') {
|
||||
if (type === 'masonry' || type === 'polaroid' || type === 'heart-collage' || type === 'mosaic') {
|
||||
await runMultiSequential()
|
||||
}
|
||||
}
|
||||
@@ -592,15 +676,29 @@ function getRevealConfig(imgIdx) {
|
||||
}
|
||||
}
|
||||
|
||||
const singleWrapClass = computed(() =>
|
||||
props.page.singleWidth === 'full' ? 'max-w-none' : 'max-w-[300px]'
|
||||
/** 当前边框元数据(分组/是否异形裁切/推荐宽高比) */
|
||||
const singleStyleMeta = computed(() => borderStyleMeta(props.page.borderStyle))
|
||||
/** 明信片·异形/横幅风景分组用飞入入场,其余保持缩放入场 */
|
||||
const singleRevealVariant = computed(() =>
|
||||
['postcard', 'scenic'].includes(singleStyleMeta.value?.group) ? 'postcard' : 'scale'
|
||||
)
|
||||
/** 邮戳盖章:仅 明信片/邮票 两种边框叠加 */
|
||||
const showPostmark = computed(() => ['postcard', 'stamp'].includes(props.page.borderStyle))
|
||||
|
||||
const singleWrapClass = computed(() => [
|
||||
props.page.singleWidth === 'full'
|
||||
? 'max-w-none'
|
||||
: (singleStyleMeta.value?.group === 'scenic' ? 'max-w-[340px]' : 'max-w-[300px]'),
|
||||
// mask 裁切类的投影由外层 drop-shadow 提供(box-shadow 会被 mask 裁掉)
|
||||
singleStyleMeta.value?.shaped ? 'gf-shape-shadow' : '',
|
||||
].filter(Boolean).join(' '))
|
||||
const singleWrapStyle = computed(() => {
|
||||
const ratio = singleStyleMeta.value?.ratio || '4 / 5'
|
||||
if (props.page.singleWidth === 'full') {
|
||||
const bleed = { width: 'calc(100% + 48px)', marginLeft: '-24px', marginRight: '-24px' }
|
||||
return props.page.singleKeepRatio ? bleed : { ...bleed, aspectRatio: '4 / 5' }
|
||||
return props.page.singleKeepRatio ? bleed : { ...bleed, aspectRatio: ratio }
|
||||
}
|
||||
return { aspectRatio: '4 / 5' }
|
||||
return { aspectRatio: ratio }
|
||||
})
|
||||
const singleFrameClass = computed(() =>
|
||||
frameClass.value + (props.page.singleWidth === 'full' && props.page.singleKeepRatio ? ' gf-natural' : '')
|
||||
@@ -644,6 +742,11 @@ function heartReveal(i) {
|
||||
return 'up'
|
||||
}
|
||||
|
||||
/** 爱心拼合完成:触发一次心跳 + 小心心飘散 */
|
||||
const heartDone = computed(() =>
|
||||
props.page?.type === 'heart-collage' && shownCount.value >= HEART_SLOTS.length
|
||||
)
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -753,15 +856,15 @@ function heartReveal(i) {
|
||||
.g-arrow {
|
||||
position: absolute; top: 50%; transform: translateY(-50%);
|
||||
width: 34px; height: 34px; border-radius: 9999px; border: none;
|
||||
background: rgba(255,255,255,0.78); color: #a88c6b; font-size: 20px; line-height: 1;
|
||||
background: rgba(255,255,255,0.78); color: var(--c-primary); font-size: 20px; line-height: 1;
|
||||
display: flex; align-items: center; justify-content: center; cursor: pointer;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.12); backdrop-filter: blur(6px);
|
||||
}
|
||||
.g-prev { left: 8px; }
|
||||
.g-next { right: 8px; }
|
||||
.g-dots { display: flex; justify-content: center; gap: 6px; margin-top: 10px; }
|
||||
.g-dot { width: 7px; height: 7px; border-radius: 9999px; border: none; background: rgba(168,140,107,0.3); padding: 0; cursor: pointer; transition: all 0.3s; }
|
||||
.g-dot.active { background: #a88c6b; width: 18px; border-radius: 9999px; }
|
||||
.g-dot { width: 7px; height: 7px; border-radius: 9999px; border: none; background: rgba(var(--c-primary-rgb),0.3); padding: 0; cursor: pointer; transition: all 0.3s; }
|
||||
.g-dot.active { background: var(--c-primary); width: 18px; border-radius: 9999px; }
|
||||
|
||||
/* 爱心拼接:绝对定位块状心形 + 淡囍底纹 */
|
||||
.g-heart {
|
||||
@@ -816,6 +919,7 @@ function heartReveal(i) {
|
||||
}
|
||||
.g-heart-cell {
|
||||
overflow: hidden;
|
||||
border-radius: 5px;
|
||||
transition: transform 0.35s ease, box-shadow 0.35s ease;
|
||||
}
|
||||
@media (hover: hover) {
|
||||
@@ -832,6 +936,41 @@ function heartReveal(i) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 拼合完成:整颗心轻跳两下(transform 不影响 absolute 子块布局) */
|
||||
.g-heart-done {
|
||||
animation: gHeartBeat 1.15s ease-in-out 0.2s 1;
|
||||
}
|
||||
@keyframes gHeartBeat {
|
||||
0%, 100% { transform: scale(1); }
|
||||
18% { transform: scale(1.035); }
|
||||
36% { transform: scale(1); }
|
||||
54% { transform: scale(1.02); }
|
||||
70% { transform: scale(1); }
|
||||
}
|
||||
/* 完成后从心口飘散的小心心 */
|
||||
.g-heart-spark {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
color: #d9868d;
|
||||
line-height: 1;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
animation: gHeartSpark 1.6s ease-out forwards;
|
||||
}
|
||||
.g-heart-spark-1 { left: 20%; top: 24%; font-size: 15px; animation-delay: 0.35s; }
|
||||
.g-heart-spark-2 { right: 17%; top: 20%; font-size: 11px; animation-delay: 0.55s; }
|
||||
.g-heart-spark-3 { left: 47%; top: 7%; font-size: 13px; animation-delay: 0.75s; }
|
||||
@keyframes gHeartSpark {
|
||||
0% { opacity: 0; transform: translateY(8px) scale(0.5); }
|
||||
25% { opacity: 0.9; }
|
||||
100% { opacity: 0; transform: translateY(-36px) scale(1.2) rotate(14deg); }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.g-heart-done { animation: none; }
|
||||
.g-heart-spark { display: none; }
|
||||
}
|
||||
|
||||
.g-polaroid { display: flex; flex-wrap: wrap; justify-content: center; gap: 14px 10px; padding: 14px 4px 4px; }
|
||||
.g-polaroid-slot {
|
||||
width: 42%;
|
||||
@@ -849,5 +988,122 @@ function heartReveal(i) {
|
||||
.g-polaroid-item:hover { z-index: 10; }
|
||||
.g-polaroid-item img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; display: block; }
|
||||
|
||||
/* 通用:新版式内图片铺满裁切 */
|
||||
.g-cover-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 三联屏:三竖条,中列上浮(用 margin 错位,避免与 v-reveal 的 transform 冲突) */
|
||||
.g-triptych {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
padding: 14px 4px 0;
|
||||
}
|
||||
.g-triptych-item {
|
||||
aspect-ratio: 10 / 17;
|
||||
min-width: 0;
|
||||
}
|
||||
.g-triptych-mid {
|
||||
margin-top: -14px;
|
||||
}
|
||||
|
||||
/* 胶片连拍:深色胶片条 + 上下齿孔 + 横滑帧 */
|
||||
.g-filmstrip {
|
||||
position: relative;
|
||||
background: #26231f;
|
||||
border-radius: 10px;
|
||||
padding: 20px 10px;
|
||||
box-shadow: 0 14px 30px rgba(30, 24, 18, 0.28);
|
||||
}
|
||||
.g-filmstrip::before,
|
||||
.g-filmstrip::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
height: 8px;
|
||||
background-image: repeating-linear-gradient(90deg, rgba(253, 251, 247, 0.8) 0 10px, transparent 10px 20px);
|
||||
border-radius: 1px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.g-filmstrip::before { top: 6px; }
|
||||
.g-filmstrip::after { bottom: 6px; }
|
||||
.g-filmstrip-track {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
overflow-x: auto;
|
||||
scroll-snap-type: x mandatory;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.g-filmstrip-frame {
|
||||
flex: 0 0 76%;
|
||||
min-width: 76%;
|
||||
aspect-ratio: 4 / 5;
|
||||
scroll-snap-align: center;
|
||||
}
|
||||
|
||||
/* 扇形叠影:五张卡绝对定位于底部枢轴,入场后展开成扇 */
|
||||
.g-fan {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 340px;
|
||||
margin-inline: auto;
|
||||
aspect-ratio: 1 / 1.04;
|
||||
}
|
||||
.g-fan-item {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 3%;
|
||||
width: 56%;
|
||||
aspect-ratio: 3 / 4;
|
||||
transform-origin: 50% 94%;
|
||||
transform: translateX(-50%) rotate(0deg);
|
||||
transition: transform 0.9s cubic-bezier(0.34, 1.45, 0.5, 1);
|
||||
}
|
||||
/* 入场(容器 reveal--in)后按序展开 */
|
||||
.reveal--in .g-fan-item:nth-child(1) { transform: translateX(-50%) rotate(-26deg); transition-delay: 0.08s; }
|
||||
.reveal--in .g-fan-item:nth-child(2) { transform: translateX(-50%) rotate(-13deg); transition-delay: 0.16s; }
|
||||
.reveal--in .g-fan-item:nth-child(3) { transform: translateX(-50%) rotate(0deg); transition-delay: 0.24s; }
|
||||
.reveal--in .g-fan-item:nth-child(4) { transform: translateX(-50%) rotate(13deg); transition-delay: 0.32s; }
|
||||
.reveal--in .g-fan-item:nth-child(5) { transform: translateX(-50%) rotate(26deg); transition-delay: 0.4s; }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.g-fan-item { transition: none; }
|
||||
}
|
||||
|
||||
/* 杂志拼贴:横竖交错三行 grid */
|
||||
.g-mosaic {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
grid-template-areas: 'a b b' 'c c d' 'e f f';
|
||||
gap: 7px;
|
||||
aspect-ratio: 3 / 3.1;
|
||||
padding: 0 2px;
|
||||
}
|
||||
.g-mosaic-slot {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
contain: layout paint;
|
||||
}
|
||||
.g-mosaic-area-1 { grid-area: a; }
|
||||
.g-mosaic-area-2 { grid-area: b; }
|
||||
.g-mosaic-area-3 { grid-area: c; }
|
||||
.g-mosaic-area-4 { grid-area: d; }
|
||||
.g-mosaic-area-5 { grid-area: e; }
|
||||
.g-mosaic-area-6 { grid-area: f; }
|
||||
.g-mosaic-placeholder {
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.no-scrollbar::-webkit-scrollbar { display: none; }
|
||||
</style>
|
||||
|
||||
@@ -1,18 +1,54 @@
|
||||
// 相册边框样式定义:供前台 PhotoGallery 渲染与后台 AdminEditor 配置/预览共用
|
||||
// - group : base=常规矩形边框;postcard=明信片·异形(mask/clip 裁切类)
|
||||
// - shaped: mask/clip 裁切类(box-shadow 会随之被裁掉,需要父级加 .gf-shape-shadow 用 drop-shadow 补投影)
|
||||
// - ratio : 单图版式下推荐的宽高比(未设置时默认 4 / 5)
|
||||
export const BORDER_STYLES = [
|
||||
{ value: 'none', label: '无边框' },
|
||||
{ value: 'thin', label: '细金线' },
|
||||
{ value: 'mat', label: '白卡纸' },
|
||||
{ value: 'rounded', label: '柔圆角' },
|
||||
{ value: 'double', label: '双线框' },
|
||||
{ value: 'gold', label: '古典金框' },
|
||||
{ value: 'shadow', label: '阴影浮起' },
|
||||
{ value: 'ink', label: '墨黑描边' },
|
||||
{ value: 'card', label: '悬浮卡片' },
|
||||
{ value: 'lace', label: '虚线文艺' },
|
||||
{ value: 'glow', label: '柔光金晕' },
|
||||
{ value: 'none', label: '无边框', group: 'base' },
|
||||
{ value: 'thin', label: '细金线', group: 'base' },
|
||||
{ value: 'mat', label: '白卡纸', group: 'base' },
|
||||
{ value: 'rounded', label: '柔圆角', group: 'base' },
|
||||
{ value: 'double', label: '双线框', group: 'base' },
|
||||
{ value: 'gold', label: '古典金框', group: 'base' },
|
||||
{ value: 'shadow', label: '阴影浮起', group: 'base' },
|
||||
{ value: 'ink', label: '墨黑描边', group: 'base' },
|
||||
{ value: 'card', label: '悬浮卡片', group: 'base' },
|
||||
{ value: 'lace', label: '虚线文艺', group: 'base' },
|
||||
{ value: 'glow', label: '柔光金晕', group: 'base' },
|
||||
{ value: 'stamp', label: '邮票齿孔', group: 'postcard', shaped: true },
|
||||
{ value: 'postcard', label: '复古明信片', group: 'postcard' },
|
||||
{ value: 'arch', label: '拱门', group: 'postcard', ratio: '3 / 4' },
|
||||
{ value: 'oval', label: '椭圆相框', group: 'postcard', ratio: '3 / 4' },
|
||||
{ value: 'circle', label: '正圆相框', group: 'postcard', ratio: '1 / 1' },
|
||||
{ value: 'heart', label: '心形', group: 'postcard', shaped: true, ratio: '1 / 1' },
|
||||
{ value: 'flower', label: '八瓣花', group: 'postcard', shaped: true, ratio: '1 / 1' },
|
||||
{ value: 'leaf', label: '叶形花瓣', group: 'postcard', ratio: '1 / 1' },
|
||||
{ value: 'hexagon', label: '六边形', group: 'postcard', shaped: true, ratio: '1 / 1' },
|
||||
{ value: 'scallop', label: '扇贝花边', group: 'postcard', shaped: true },
|
||||
{ value: 'wave', label: '蕾丝波浪', group: 'postcard', shaped: true },
|
||||
{ value: 'zigzag', label: '锯齿花边', group: 'postcard', shaped: true },
|
||||
{ value: 'torn', label: '撕纸边', group: 'postcard', shaped: true },
|
||||
{ value: 'bevel', label: '切角相纸', group: 'postcard', shaped: true },
|
||||
{ value: 'ticket', label: '车票票根', group: 'postcard', shaped: true },
|
||||
{ value: 'film', label: '复古胶片', group: 'postcard' },
|
||||
{ value: 'cloud', label: '云朵相框', group: 'scenic', shaped: true, ratio: '4 / 3' },
|
||||
{ value: 'mist', label: '晨雾朦胧', group: 'scenic', ratio: '4 / 3' },
|
||||
{ value: 'bloom', label: '花语卡纸', group: 'scenic', ratio: '4 / 3' },
|
||||
{ value: 'panorama', label: '宽幅全景', group: 'scenic', ratio: '16 / 9' },
|
||||
{ value: 'ovalwide', label: '横椭圆框', group: 'scenic', ratio: '4 / 3' },
|
||||
]
|
||||
|
||||
// 供可视化选择器分组展示
|
||||
export const BORDER_STYLE_GROUPS = [
|
||||
{ key: 'base', label: '基础边框', items: BORDER_STYLES.filter((b) => b.group === 'base') },
|
||||
{ key: 'postcard', label: '明信片 · 异形', items: BORDER_STYLES.filter((b) => b.group === 'postcard') },
|
||||
{ key: 'scenic', label: '横幅 · 云雾花', items: BORDER_STYLES.filter((b) => b.group === 'scenic') },
|
||||
]
|
||||
|
||||
/** 取样式元数据(未知值回落白卡纸) */
|
||||
export function borderStyleMeta(style) {
|
||||
return BORDER_STYLES.find((b) => b.value === style) || BORDER_STYLES.find((b) => b.value === 'mat')
|
||||
}
|
||||
|
||||
export function borderClass(style) {
|
||||
const v = BORDER_STYLES.some((b) => b.value === style) ? style : 'mat'
|
||||
return 'gf-frame gf-' + v
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import { clampHeroFocus, heroFocusCss } from '@/composables/posterDefaults'
|
||||
|
||||
/** 爱心拼接槽位:百分比绝对定位(顶开叉、中最宽、底收尖) */
|
||||
/** 爱心拼接槽位:百分比绝对定位(顶开叉、中最宽、底收尖)
|
||||
* r : 格子圆角(外缘格用大弧度圆角把方块阶梯"磨"成心形弧线)
|
||||
* clip : 格子裁切(心尖用 V 形三角收出尖端)
|
||||
* 槽位索引与角色保持不变,存量配置的图片顺序不受影响 */
|
||||
export const HEART_SLOTS = [
|
||||
{ l: 10, t: 0, w: 32, h: 13 }, // 0 左上叶
|
||||
{ l: 58, t: 0, w: 32, h: 13 }, // 1 右上叶
|
||||
{ l: 0, t: 14, w: 14, h: 15.5 }, // 2 左外上
|
||||
{ l: 0, t: 30.5, w: 14, h: 15.5 }, // 3 左外下
|
||||
{ l: 15, t: 14, w: 26, h: 32 }, // 4 左大图
|
||||
{ l: 42, t: 14, w: 16, h: 32 }, // 5 中轴
|
||||
{ l: 59, t: 14, w: 26, h: 32 }, // 6 右大图
|
||||
{ l: 86, t: 14, w: 14, h: 15.5 }, // 7 右外上
|
||||
{ l: 86, t: 30.5, w: 14, h: 15.5 }, // 8 右外下
|
||||
{ l: 10, t: 47, w: 13, h: 16 }, // 9 左下收腰
|
||||
{ l: 24, t: 47, w: 52, h: 16 }, // 10 中下横
|
||||
{ l: 77, t: 47, w: 13, h: 16 }, // 11 右下收腰
|
||||
{ l: 38, t: 64, w: 24, h: 14 }, // 12 心尖
|
||||
{ l: 6, t: 0, w: 38, h: 14, r: '50% 50% 6px 6px' }, // 0 左上叶(半圆顶)
|
||||
{ l: 56, t: 0, w: 38, h: 14, r: '50% 50% 6px 6px' }, // 1 右上叶(半圆顶)
|
||||
{ l: 0, t: 14.8, w: 13, h: 15, r: '55% 6px 6px 6px' }, // 2 左外上(肩部外弧)
|
||||
{ l: 0, t: 30.6, w: 13, h: 14.6, r: '6px 6px 6px 55%' }, // 3 左外下(收腰起弧)
|
||||
{ l: 14, t: 14.8, w: 26, h: 30.4 }, // 4 左大图
|
||||
{ l: 41, t: 14.8, w: 18, h: 30.4 }, // 5 中轴
|
||||
{ l: 60, t: 14.8, w: 26, h: 30.4 }, // 6 右大图
|
||||
{ l: 87, t: 14.8, w: 13, h: 15, r: '6px 55% 6px 6px' }, // 7 右外上(肩部外弧)
|
||||
{ l: 87, t: 30.6, w: 13, h: 14.6, r: '6px 6px 55% 6px' }, // 8 右外下(收腰起弧)
|
||||
{ l: 8, t: 46.2, w: 16, h: 16, r: '6px 6px 6px 65%' }, // 9 左下收腰(底外弧)
|
||||
{ l: 25, t: 46.2, w: 50, h: 16 }, // 10 中下横
|
||||
{ l: 76, t: 46.2, w: 16, h: 16, r: '6px 6px 65% 6px' }, // 11 右下收腰(底外弧)
|
||||
{ l: 34, t: 63, w: 32, h: 16.5, clip: 'polygon(0 0, 100% 0, 50% 100%)' }, // 12 心尖(V 形)
|
||||
]
|
||||
|
||||
export const HEART_SLOT_COUNT = HEART_SLOTS.length
|
||||
@@ -37,6 +40,16 @@ export function heartSlotLabel(i) {
|
||||
return HEART_SLOT_LABELS[i] || `图${i + 1}`
|
||||
}
|
||||
|
||||
/** 格子形状样式(圆角/裁切),作用在展示格上让整体轮廓贴近心形 */
|
||||
export function heartCellShape(i) {
|
||||
const s = HEART_SLOTS[i]
|
||||
if (!s) return {}
|
||||
const style = {}
|
||||
if (s.r) style.borderRadius = s.r
|
||||
if (s.clip) style.clipPath = s.clip
|
||||
return style
|
||||
}
|
||||
|
||||
export function heartFocusAt(page, i, axis = 'x') {
|
||||
const arr = axis === 'y' ? page?.imagesFocusY : page?.imagesFocusX
|
||||
const v = Array.isArray(arr) ? arr[i] : 50
|
||||
|
||||
@@ -16,6 +16,48 @@ export const POSTER_TEMPLATES = [
|
||||
{ value: 'split', label: '左图右文', desc: '竖排书法 + 右侧宴席信息' },
|
||||
]
|
||||
|
||||
/**
|
||||
* 海报风格模板(5 套配色皮肤,竖版/横版通用)
|
||||
* - deep/mid/gold 供后台色卡预览
|
||||
* - sealColor/sealFill:canvas 囍字印章用色(canvas 不认 CSS 变量)
|
||||
* - shotBg:保存长图(html2canvas)的底色
|
||||
*/
|
||||
export const POSTER_THEMES = [
|
||||
{
|
||||
value: 'redgold', label: '朱红鎏金', desc: '经典中国红 + 鎏金描边',
|
||||
deep: '#7a0000', mid: '#9b0000', gold: '#f0d78c',
|
||||
sealColor: '#d4af37', sealFill: 'rgba(155, 0, 0, 0.5)', shotBg: '#7a0000',
|
||||
},
|
||||
{
|
||||
value: 'burgundy', label: '酒红丝绒', desc: '深酒红绒面 + 玫瑰金',
|
||||
deep: '#4a1220', mid: '#6b1d30', gold: '#e2b98f',
|
||||
sealColor: '#d9a05b', sealFill: 'rgba(90, 22, 40, 0.55)', shotBg: '#4a1220',
|
||||
},
|
||||
{
|
||||
value: 'navygold', label: '藏蓝烫金', desc: '午夜藏蓝 + 亮金烫印',
|
||||
deep: '#101f38', mid: '#1a2f52', gold: '#e8cf96',
|
||||
sealColor: '#d4b060', sealFill: 'rgba(16, 31, 56, 0.6)', shotBg: '#101f38',
|
||||
},
|
||||
{
|
||||
value: 'inkjade', label: '黛绿描金', desc: '墨玉黛绿 + 青金描线',
|
||||
deep: '#132a24', mid: '#1d3a32', gold: '#d8c493',
|
||||
sealColor: '#c9b37e', sealFill: 'rgba(19, 42, 36, 0.6)', shotBg: '#132a24',
|
||||
},
|
||||
{
|
||||
value: 'rosewood', label: '绯梅胭脂', desc: '绯梅粉绒 + 香槟金',
|
||||
deep: '#5c1f30', mid: '#7d2c42', gold: '#f0d3b2',
|
||||
sealColor: '#e0b487', sealFill: 'rgba(110, 38, 58, 0.55)', shotBg: '#5c1f30',
|
||||
},
|
||||
]
|
||||
|
||||
export function posterThemeMeta(value) {
|
||||
return POSTER_THEMES.find((t) => t.value === value) || POSTER_THEMES[0]
|
||||
}
|
||||
|
||||
export function normalizePosterTheme(value) {
|
||||
return POSTER_THEMES.some((t) => t.value === value) ? value : 'redgold'
|
||||
}
|
||||
|
||||
/** 手机端布局:仅影响窄屏;PC 仍按模板本身版式 */
|
||||
export const MOBILE_LAYOUTS = [
|
||||
{ value: 'auto', label: '自动布局', desc: '按屏幕宽度自动切换(当前默认)' },
|
||||
@@ -162,6 +204,7 @@ function baseFields(side) {
|
||||
side: resolvePosterSide(side) || 1,
|
||||
layout: 1,
|
||||
template: 'classic',
|
||||
posterTheme: 'redgold',
|
||||
mobileLayout: 'auto',
|
||||
enabled: true,
|
||||
heroImg: '',
|
||||
@@ -395,7 +438,7 @@ const SHARED_COPY_KEYS = [
|
||||
'musicUrl', 'invitePath', 'inviteButtonText', 'showInviteButton',
|
||||
'enabled', 'pageTitle', 'pageDescription', 'footer', 'title',
|
||||
'greeting', 'verticalSlogan', 'subSlogan1', 'subSlogan2', 'loveFooter',
|
||||
'loadingEffect', 'enterEffect',
|
||||
'loadingEffect', 'enterEffect', 'posterTheme',
|
||||
]
|
||||
|
||||
function copySharedFields(from, into) {
|
||||
@@ -432,6 +475,7 @@ export function normalizePosterConfig(raw, side = 1, layout = 1) {
|
||||
side: s,
|
||||
layout: l,
|
||||
template,
|
||||
posterTheme: normalizePosterTheme(loaded.posterTheme),
|
||||
mobileLayout,
|
||||
lines: lines.length ? lines : base.lines,
|
||||
groomName,
|
||||
|
||||
@@ -238,6 +238,18 @@ export function getSlotResizeSpecs(page) {
|
||||
boxForIndex = () => ({ maxWidth: 1200, maxHeight: 780 })
|
||||
} else if (page.type === 'polaroid') {
|
||||
boxForIndex = () => ({ maxWidth: 800, maxHeight: 1000 })
|
||||
} else if (page.type === 'triptych') {
|
||||
// 窄竖条(10:17)
|
||||
boxForIndex = () => ({ maxWidth: 560, maxHeight: 960 })
|
||||
} else if (page.type === 'filmstrip') {
|
||||
// 横滑大帧(4:5)
|
||||
boxForIndex = () => ({ maxWidth: 900, maxHeight: 1125 })
|
||||
} else if (page.type === 'fan') {
|
||||
// 扇形卡(3:4)
|
||||
boxForIndex = () => ({ maxWidth: 700, maxHeight: 940 })
|
||||
} else if (page.type === 'mosaic') {
|
||||
// 横竖混排格
|
||||
boxForIndex = () => ({ maxWidth: 900, maxHeight: 900 })
|
||||
} else if (page.type === 'heart-collage') {
|
||||
// 按槽位比例:大图 / 竖中轴 / 横图 / 小方图
|
||||
boxForIndex = (i) => {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* v-reveal="{ variant: 'scale', delay: 200 }"
|
||||
* v-reveal="{ variant: 'up', immediate: true }" -> 不建观察器,下一帧直接入场(串行挂载用)
|
||||
*
|
||||
* 支持的 variant: up | down | left | right | scale | blur
|
||||
* 支持的 variant: up | down | left | right | scale | blur | postcard
|
||||
*/
|
||||
const VARIANTS = {
|
||||
up: 'reveal-up',
|
||||
@@ -16,6 +16,7 @@ const VARIANTS = {
|
||||
right: 'reveal-right',
|
||||
scale: 'reveal-scale',
|
||||
blur: 'reveal-blur',
|
||||
postcard: 'reveal-postcard',
|
||||
}
|
||||
|
||||
function parseBinding(value) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import Antd from 'ant-design-vue'
|
||||
import './style.css'
|
||||
import './styles/animations.css'
|
||||
import './styles/frames.css'
|
||||
import './styles/theme-packs.css'
|
||||
import App from './App.vue'
|
||||
import '@fortawesome/fontawesome-free/css/all.css' // 引入所有样式
|
||||
// 注册路由
|
||||
|
||||
@@ -9,6 +9,264 @@
|
||||
@import "@fontsource/pinyon-script/400.css";
|
||||
@import "@fontsource/cinzel/400.css";
|
||||
@import "@fontsource/cinzel/700.css";
|
||||
@import "@fontsource/fusion-pixel-12px-proportional-sc/400.css";
|
||||
|
||||
/* ============================================================
|
||||
整站风格模板(前后台共用):html[data-theme] 切换整组变量
|
||||
色板:
|
||||
- --c-primary / --c-primary-deep / --c-primary-rgb 主品牌色
|
||||
- --c-line / --c-line-rgb 线条/描边
|
||||
- --c-muted / --c-ink 次要字 / 正文
|
||||
- --c-paper / --c-bg / --c-bg-deep 卡纸 / 页面底 / 深底
|
||||
- --c-card 浮层卡片底(暗色主题为深卡)
|
||||
- --c-on-primary 主色底上的文字色
|
||||
风格:
|
||||
- --t-font-title / --t-font-kicker / --t-font-body 标题 / 眉题 / 正文字体
|
||||
- --t-radius-card / --t-radius-btn 卡片 / 按钮圆角
|
||||
- --t-bg-texture 页面背景纹理(none 或 SVG)
|
||||
============================================================ */
|
||||
:root,
|
||||
html[data-theme='champagne'] {
|
||||
--c-primary: #a88c6b;
|
||||
--c-primary-deep: #967b5c;
|
||||
--c-primary-rgb: 168, 140, 107;
|
||||
--c-line: #b89a78;
|
||||
--c-line-rgb: 184, 154, 120;
|
||||
--c-muted: #8a8680;
|
||||
--c-ink: #2c2c2c;
|
||||
--c-ink-soft: #5c4a35;
|
||||
--c-paper: #fdfbf7;
|
||||
--c-bg: #fbf6ef;
|
||||
--c-bg-deep: #f3eee6;
|
||||
--c-card: #ffffff;
|
||||
--c-on-primary: #ffffff;
|
||||
--t-font-title: var(--font-calligraphy);
|
||||
--t-font-kicker: var(--font-calligraphy);
|
||||
--t-font-body: var(--font-sans);
|
||||
--t-radius-card: 12px;
|
||||
--t-radius-btn: 999px;
|
||||
--t-bg-texture: none;
|
||||
}
|
||||
/* 新中式:黛绿朱砂 · 宋体留白 · 竹枝细纹 */
|
||||
html[data-theme='guofeng-new'] {
|
||||
--c-primary: #486156;
|
||||
--c-primary-deep: #395046;
|
||||
--c-primary-rgb: 72, 97, 86;
|
||||
--c-line: #b0885a;
|
||||
--c-line-rgb: 176, 136, 90;
|
||||
--c-muted: #8b8a80;
|
||||
--c-ink: #2c302c;
|
||||
--c-ink-soft: #4a4f48;
|
||||
--c-paper: #faf8f2;
|
||||
--c-bg: #f6f3ec;
|
||||
--c-bg-deep: #ece7db;
|
||||
--c-card: #fffefa;
|
||||
--c-on-primary: #f6f3ec;
|
||||
--t-font-title: var(--font-serif);
|
||||
--t-font-kicker: var(--font-kai);
|
||||
--t-font-body: var(--font-serif);
|
||||
--t-radius-card: 2px;
|
||||
--t-radius-btn: 2px;
|
||||
--t-bg-texture: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cg fill='none' stroke='%23486156' stroke-opacity='0.04' stroke-width='1'%3E%3Cpath d='M24 96q2-26-6-44M24 74q10-4 14-14M24 60q-9-2-13-10'/%3E%3C/g%3E%3Cg fill='%23b5493f' fill-opacity='0.05'%3E%3Crect x='92' y='22' width='7' height='7'/%3E%3C/g%3E%3C/svg%3E");
|
||||
}
|
||||
/* 老中式:朱红鎏金 · 婚书囍字 · 宣纸噪点 */
|
||||
html[data-theme='guofeng-old'] {
|
||||
--c-primary: #a8323e;
|
||||
--c-primary-deep: #8c2833;
|
||||
--c-primary-rgb: 168, 50, 62;
|
||||
--c-line: #c9a24a;
|
||||
--c-line-rgb: 201, 162, 74;
|
||||
--c-muted: #9a8578;
|
||||
--c-ink: #3a2428;
|
||||
--c-ink-soft: #5c3a40;
|
||||
--c-paper: #fdf6f0;
|
||||
--c-bg: #f9efe6;
|
||||
--c-bg-deep: #f2e3d4;
|
||||
--c-card: #fffaf4;
|
||||
--c-on-primary: #f7e7c3;
|
||||
--t-font-title: var(--font-mashan);
|
||||
--t-font-kicker: var(--font-kai);
|
||||
--t-font-body: var(--font-serif);
|
||||
--t-radius-card: 4px;
|
||||
--t-radius-btn: 4px;
|
||||
--t-bg-texture: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='90' height='90'%3E%3Cg fill='%238c2833' fill-opacity='0.025'%3E%3Ccircle cx='12' cy='20' r='1'/%3E%3Ccircle cx='48' cy='8' r='1.4'/%3E%3Ccircle cx='76' cy='34' r='1'/%3E%3Ccircle cx='30' cy='52' r='1.2'/%3E%3Ccircle cx='64' cy='70' r='1'/%3E%3Ccircle cx='10' cy='80' r='1.3'/%3E%3Ccircle cx='86' cy='84' r='1'/%3E%3C/g%3E%3C/svg%3E");
|
||||
}
|
||||
/* 法式浪漫:奶油粉白 · 花体 · 细水彩点 */
|
||||
html[data-theme='french'] {
|
||||
--c-primary: #c78a96;
|
||||
--c-primary-deep: #b57683;
|
||||
--c-primary-rgb: 199, 138, 150;
|
||||
--c-line: #d9aab4;
|
||||
--c-line-rgb: 217, 170, 180;
|
||||
--c-muted: #9c8b90;
|
||||
--c-ink: #3d3136;
|
||||
--c-ink-soft: #5c454e;
|
||||
--c-paper: #fdfafa;
|
||||
--c-bg: #fdf6f4;
|
||||
--c-bg-deep: #f7e9e7;
|
||||
--c-card: #ffffff;
|
||||
--c-on-primary: #ffffff;
|
||||
--t-font-title: var(--font-calligraphy);
|
||||
--t-font-kicker: "Pinyon Script", cursive;
|
||||
--t-font-body: var(--font-sans);
|
||||
--t-radius-card: 18px;
|
||||
--t-radius-btn: 999px;
|
||||
--t-bg-texture: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cg fill='%23c78a96' fill-opacity='0.03'%3E%3Ccircle cx='24' cy='30' r='7'/%3E%3Ccircle cx='88' cy='16' r='5'/%3E%3Ccircle cx='104' cy='78' r='8'/%3E%3Ccircle cx='40' cy='96' r='6'/%3E%3C/g%3E%3C/svg%3E");
|
||||
}
|
||||
/* 现代简约:暖灰低饱和 · 无衬线 · 细几何 */
|
||||
html[data-theme='modern'] {
|
||||
--c-primary: #8a8578;
|
||||
--c-primary-deep: #757064;
|
||||
--c-primary-rgb: 138, 133, 120;
|
||||
--c-line: #a39e91;
|
||||
--c-line-rgb: 163, 158, 145;
|
||||
--c-muted: #8f8c85;
|
||||
--c-ink: #2e2d2a;
|
||||
--c-ink-soft: #4a4842;
|
||||
--c-paper: #fbfaf8;
|
||||
--c-bg: #f7f6f3;
|
||||
--c-bg-deep: #edebe6;
|
||||
--c-card: #ffffff;
|
||||
--c-on-primary: #ffffff;
|
||||
--t-font-title: var(--font-sans);
|
||||
--t-font-kicker: var(--font-sans);
|
||||
--t-font-body: var(--font-sans);
|
||||
--t-radius-card: 8px;
|
||||
--t-radius-btn: 10px;
|
||||
--t-bg-texture: none;
|
||||
}
|
||||
/* 森系自然:青绿 · 手写 · 纸纹 */
|
||||
html[data-theme='forest'] {
|
||||
--c-primary: #6f9678;
|
||||
--c-primary-deep: #5d8266;
|
||||
--c-primary-rgb: 111, 150, 120;
|
||||
--c-line: #92b399;
|
||||
--c-line-rgb: 146, 179, 153;
|
||||
--c-muted: #86928a;
|
||||
--c-ink: #29322c;
|
||||
--c-ink-soft: #3d4a40;
|
||||
--c-paper: #f9fcf9;
|
||||
--c-bg: #f3f8f2;
|
||||
--c-bg-deep: #e9f0e7;
|
||||
--c-card: #ffffff;
|
||||
--c-on-primary: #ffffff;
|
||||
--t-font-title: var(--font-longcang);
|
||||
--t-font-kicker: var(--font-liujian);
|
||||
--t-font-body: var(--font-sans);
|
||||
--t-radius-card: 14px;
|
||||
--t-radius-btn: 999px;
|
||||
--t-bg-texture: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='110' height='110'%3E%3Cg fill='none' stroke='%235d8266' stroke-opacity='0.035' stroke-width='1'%3E%3Cpath d='M20 30q8-12 18-6M74 80q10-10 20-4M90 24q6 10-4 16M34 92q-10 4-14-6'/%3E%3C/g%3E%3C/svg%3E");
|
||||
}
|
||||
/* 星空夜色:深蓝烫金 · 暗色主题 · 星点 */
|
||||
html[data-theme='night'] {
|
||||
--c-primary: #c9a96a;
|
||||
--c-primary-deep: #b3945a;
|
||||
--c-primary-rgb: 201, 169, 106;
|
||||
--c-line: #8fa3c0;
|
||||
--c-line-rgb: 143, 163, 192;
|
||||
--c-muted: #93a0b5;
|
||||
--c-ink: #e8e6df;
|
||||
--c-ink-soft: #cfd4de;
|
||||
--c-paper: #16233c;
|
||||
--c-bg: #101b2e;
|
||||
--c-bg-deep: #0a1322;
|
||||
--c-card: #1a2946;
|
||||
--c-on-primary: #14213a;
|
||||
--t-font-title: var(--font-display);
|
||||
--t-font-kicker: var(--font-cinzel);
|
||||
--t-font-body: var(--font-sans);
|
||||
--t-radius-card: 10px;
|
||||
--t-radius-btn: 999px;
|
||||
--t-bg-texture: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cg fill='%23e8e6df'%3E%3Ccircle cx='16' cy='24' r='1' fill-opacity='0.3'/%3E%3Ccircle cx='72' cy='10' r='0.8' fill-opacity='0.2'/%3E%3Ccircle cx='120' cy='40' r='1.2' fill-opacity='0.34'/%3E%3Ccircle cx='44' cy='66' r='0.7' fill-opacity='0.18'/%3E%3Ccircle cx='98' cy='92' r='1' fill-opacity='0.26'/%3E%3Ccircle cx='20' cy='112' r='0.9' fill-opacity='0.22'/%3E%3Ccircle cx='132' cy='126' r='0.8' fill-opacity='0.2'/%3E%3C/g%3E%3C/svg%3E");
|
||||
}
|
||||
/* 杂志封面:高对比黑棕 · Didot 大字 · 方角 */
|
||||
html[data-theme='magazine'] {
|
||||
--c-primary: #1f1c19;
|
||||
--c-primary-deep: #000000;
|
||||
--c-primary-rgb: 31, 28, 25;
|
||||
--c-line: #8a8378;
|
||||
--c-line-rgb: 138, 131, 120;
|
||||
--c-muted: #7d786f;
|
||||
--c-ink: #1f1c19;
|
||||
--c-ink-soft: #3a352e;
|
||||
--c-paper: #faf8f4;
|
||||
--c-bg: #f5f2ec;
|
||||
--c-bg-deep: #eae6dd;
|
||||
--c-card: #ffffff;
|
||||
--c-on-primary: #ffffff;
|
||||
--t-font-title: var(--font-display);
|
||||
--t-font-kicker: var(--font-cinzel);
|
||||
--t-font-body: var(--font-sans);
|
||||
--t-radius-card: 0px;
|
||||
--t-radius-btn: 0px;
|
||||
--t-bg-texture: none;
|
||||
}
|
||||
/* 日报编辑:报纸米黄 · 衬线排印 · 直角细框 */
|
||||
html[data-theme='newspaper'] {
|
||||
--c-primary: #2b2822;
|
||||
--c-primary-deep: #14120e;
|
||||
--c-primary-rgb: 43, 40, 34;
|
||||
--c-line: #6b675e;
|
||||
--c-line-rgb: 107, 103, 94;
|
||||
--c-muted: #6f6b62;
|
||||
--c-ink: #262420;
|
||||
--c-ink-soft: #423e35;
|
||||
--c-paper: #fbf9f2;
|
||||
--c-bg: #f6f3ea;
|
||||
--c-bg-deep: #ebe7da;
|
||||
--c-card: #fffdf6;
|
||||
--c-on-primary: #fbf9f2;
|
||||
--t-font-title: var(--font-serif);
|
||||
--t-font-kicker: var(--font-display);
|
||||
--t-font-body: var(--font-serif);
|
||||
--t-radius-card: 0px;
|
||||
--t-radius-btn: 2px;
|
||||
--t-bg-texture: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='6' height='6'%3E%3Crect x='0' y='0' width='1' height='1' fill='%232b2822' fill-opacity='0.016'/%3E%3C/svg%3E");
|
||||
}
|
||||
/* 复古像素:糖果撞色 · 像素字 · 棋盘点 */
|
||||
html[data-theme='pixel'] {
|
||||
--c-primary: #c94f7c;
|
||||
--c-primary-deep: #b03e69;
|
||||
--c-primary-rgb: 201, 79, 124;
|
||||
--c-line: #5f9ea0;
|
||||
--c-line-rgb: 95, 158, 160;
|
||||
--c-muted: #8a7f74;
|
||||
--c-ink: #33261f;
|
||||
--c-ink-soft: #54433a;
|
||||
--c-paper: #fdf9ee;
|
||||
--c-bg: #faf3e6;
|
||||
--c-bg-deep: #f0e6d2;
|
||||
--c-card: #fffdf4;
|
||||
--c-on-primary: #fffdf4;
|
||||
--t-font-title: var(--font-pixel);
|
||||
--t-font-kicker: var(--font-pixel);
|
||||
--t-font-body: var(--font-pixel);
|
||||
--t-radius-card: 0px;
|
||||
--t-radius-btn: 0px;
|
||||
--t-bg-texture: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Crect x='0' y='0' width='4' height='4' fill='%2333261f' fill-opacity='0.02'/%3E%3Crect x='4' y='4' width='4' height='4' fill='%2333261f' fill-opacity='0.02'/%3E%3C/svg%3E");
|
||||
}
|
||||
/* 极简主义:黑白留白 · 细线 · 轴线 */
|
||||
html[data-theme='zen'] {
|
||||
--c-primary: #2a2a28;
|
||||
--c-primary-deep: #111111;
|
||||
--c-primary-rgb: 42, 42, 40;
|
||||
--c-line: #8f8d88;
|
||||
--c-line-rgb: 143, 141, 136;
|
||||
--c-muted: #85837e;
|
||||
--c-ink: #262624;
|
||||
--c-ink-soft: #444442;
|
||||
--c-paper: #fdfdfc;
|
||||
--c-bg: #fafaf8;
|
||||
--c-bg-deep: #f0f0ed;
|
||||
--c-card: #ffffff;
|
||||
--c-on-primary: #ffffff;
|
||||
--t-font-title: var(--font-sans);
|
||||
--t-font-kicker: var(--font-sans);
|
||||
--t-font-body: var(--font-sans);
|
||||
--t-radius-card: 0px;
|
||||
--t-radius-btn: 0px;
|
||||
--t-bg-texture: none;
|
||||
}
|
||||
|
||||
:root {
|
||||
--font-sans: "Avenir Next", "Helvetica Neue", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
@@ -22,6 +280,8 @@
|
||||
--font-liujian: "Liu Jian Mao Cao", "Zhi Mang Xing", cursive, serif;
|
||||
--font-cinzel: "Cinzel", "Playfair Display", serif;
|
||||
--font-number: "Cochin", "Georgia", "Times New Roman", serif;
|
||||
/* CDN 全量中日韩像素字优先(按 unicode-range 分片下载),本地 latin 子集兜底 */
|
||||
--font-pixel: "Fusion Pixel 12px Mono latin", "Fusion Pixel 12px Proportional SC", "Courier New", monospace;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +292,19 @@ body {
|
||||
overflow-anchor: none;
|
||||
}
|
||||
|
||||
/* ---------- 后台实时预览(?preview=1 嵌入手机壳):隐藏一切滚动条,模拟真机 ---------- */
|
||||
html.preview-mode,
|
||||
html.preview-mode * {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
html.preview-mode::-webkit-scrollbar,
|
||||
html.preview-mode *::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.calligraphy {
|
||||
font-family: var(--font-calligraphy) !important;
|
||||
}
|
||||
@@ -44,3 +317,18 @@ body {
|
||||
font-family: var(--font-calligraphy) !important;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* ---------- 图片预览灯箱(Ant Design 全局覆写:遮罩柔化 + 操作栏磨砂) ---------- */
|
||||
.ant-image-preview-mask {
|
||||
background: rgba(30, 24, 17, 0.72) !important;
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
}
|
||||
.ant-image-preview-operations {
|
||||
background: rgba(24, 19, 14, 0.45) !important;
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
}
|
||||
.ant-image-preview-img {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@@ -18,11 +18,56 @@
|
||||
.reveal-right { transform: translateX(56px); }
|
||||
.reveal-scale { transform: scale(0.9); }
|
||||
.reveal-blur { transform: scale(1.04); filter: blur(14px); }
|
||||
/* 明信片飞入:带轻微旋转,弹性回正(单图明信片/异形边框用) */
|
||||
.reveal-postcard {
|
||||
transform: translateY(70px) rotate(-7deg) scale(0.94);
|
||||
transform-origin: 50% 80%;
|
||||
transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.reveal--in { opacity: 1; transform: none; filter: none; }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.reveal { opacity: 1 !important; transform: none !important; filter: none !important; transition: none; }
|
||||
/* filter 只清 blur 变体:同元素可能还有 .gf-shape-shadow 的 drop-shadow */
|
||||
.reveal { opacity: 1 !important; transform: none !important; transition: none; }
|
||||
.reveal-blur { filter: none !important; }
|
||||
}
|
||||
|
||||
/* ---------- 邮戳盖章(单图 明信片/邮票 边框的入场收尾) ---------- */
|
||||
.gf-postmark {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 14px;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(187, 72, 62, 0.85);
|
||||
outline: 1px solid rgba(187, 72, 62, 0.5);
|
||||
outline-offset: -7px;
|
||||
color: rgba(187, 72, 62, 0.9);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: var(--font-calligraphy, serif);
|
||||
font-size: 22px;
|
||||
line-height: 1;
|
||||
transform: rotate(-14deg);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
.gf-postmark::before { content: '囍'; }
|
||||
.reveal--in .gf-postmark {
|
||||
animation: postmarkStamp 0.55s cubic-bezier(0.2, 1.4, 0.45, 1) 0.65s both;
|
||||
}
|
||||
@keyframes postmarkStamp {
|
||||
0% { opacity: 0; transform: rotate(-14deg) scale(1.9); }
|
||||
55% { opacity: 0.95; transform: rotate(-14deg) scale(0.92); }
|
||||
75% { transform: rotate(-14deg) scale(1.05); }
|
||||
100% { opacity: 0.9; transform: rotate(-14deg) scale(1); }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.reveal--in .gf-postmark { animation: none; opacity: 0.9; }
|
||||
}
|
||||
|
||||
/* ---------- 飘落花瓣(优雅:柔和渐变 + 轻微摇曳) ---------- */
|
||||
@@ -132,7 +177,7 @@
|
||||
/* ---------- 渐变流光文字 ---------- */
|
||||
@keyframes gradientShift { to { background-position: 200% center; } }
|
||||
.gradient-text {
|
||||
background: linear-gradient(90deg, #a88c6b, #e8a5b2, #d4af37, #a88c6b);
|
||||
background: linear-gradient(90deg, var(--c-primary), #e8a5b2, #d4af37, var(--c-primary));
|
||||
background-size: 200% auto;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
@@ -160,7 +205,7 @@
|
||||
100% { background-position: 200% 0; }
|
||||
}
|
||||
.shimmer-line {
|
||||
background: linear-gradient(90deg, transparent, rgba(168,140,107,0.6), transparent);
|
||||
background: linear-gradient(90deg, transparent, rgba(var(--c-primary-rgb),0.6), transparent);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 3s linear infinite;
|
||||
}
|
||||
|
||||
@@ -117,6 +117,586 @@
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
明信片 · 异形边框(group: postcard)
|
||||
- mask/clip 裁切类(stamp/scallop/torn/ticket/heart)box-shadow 会被
|
||||
mask 一并裁掉,投影统一由父级 .gf-shape-shadow 的 drop-shadow 提供
|
||||
- 装饰伪元素需要定位上下文:用零优先级 :where() 设 relative,
|
||||
模板里 Tailwind 的 .absolute(0,1,0)永远能赢,不违背头部约定
|
||||
============================================================ */
|
||||
:where(.gf-postcard, .gf-film, .gf-stamp, .gf-arch, .gf-oval, .gf-circle, .gf-leaf,
|
||||
.gf-heart, .gf-flower, .gf-hexagon, .gf-scallop, .gf-wave, .gf-zigzag,
|
||||
.gf-torn, .gf-ticket, .gf-bevel,
|
||||
.gf-cloud, .gf-mist, .gf-bloom, .gf-panorama, .gf-ovalwide) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 边缘渐隐内晕:照片边缘向纸色柔和过渡,与边框/裁切形状融为一体
|
||||
(mask/clip 类的伪元素会随形状一并裁切,晕自动贴合轮廓) */
|
||||
.gf-heart::after, .gf-flower::after, .gf-scallop::after, .gf-wave::after,
|
||||
.gf-zigzag::after, .gf-torn::after, .gf-stamp::after, .gf-ticket::after,
|
||||
.gf-hexagon::after, .gf-bevel::after, .gf-arch::after, .gf-oval::after,
|
||||
.gf-circle::after, .gf-leaf::after, .gf-cloud::after, .gf-ovalwide::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(ellipse 62% 62% at 50% 50%, rgba(255, 252, 246, 0) 55%, rgba(255, 252, 246, 0.48) 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 邮票齿孔(四边半圆打孔,经典邮票纸边) */
|
||||
.gf-stamp {
|
||||
--gf-r: 5px;
|
||||
padding: 12px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
background: #fff;
|
||||
-webkit-mask:
|
||||
radial-gradient(var(--gf-r), #0000 98%, #000) calc(-1.5 * var(--gf-r)) calc(-1.5 * var(--gf-r)) / calc(3 * var(--gf-r)) calc(3 * var(--gf-r)) round,
|
||||
linear-gradient(#000 0 0) 50% / calc(100% - 3 * var(--gf-r)) calc(100% - 3 * var(--gf-r)) no-repeat;
|
||||
mask:
|
||||
radial-gradient(var(--gf-r), #0000 98%, #000) calc(-1.5 * var(--gf-r)) calc(-1.5 * var(--gf-r)) / calc(3 * var(--gf-r)) calc(3 * var(--gf-r)) round,
|
||||
linear-gradient(#000 0 0) 50% / calc(100% - 3 * var(--gf-r)) calc(100% - 3 * var(--gf-r)) no-repeat;
|
||||
}
|
||||
/* 邮票内框线(贴照片边缘内侧,经典邮票图框) */
|
||||
.gf-stamp::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 15px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.65);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 复古明信片(暖白卡 + POST CARD 字样 + 邮戳/邮票装饰) */
|
||||
.gf-postcard {
|
||||
padding: 10px 10px 40px;
|
||||
border: 1px solid rgba(184, 154, 120, 0.28);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 10px 26px rgba(120, 90, 60, 0.16);
|
||||
background: #fffdf6;
|
||||
}
|
||||
.gf-postcard::before {
|
||||
content: 'POST CARD';
|
||||
position: absolute;
|
||||
left: 14px;
|
||||
bottom: 14px;
|
||||
/* 窄格(九宫格/爱心拼贴)里给右侧邮戳让位,calc 为负时按 0 处理自动隐藏 */
|
||||
max-width: calc(100% - 118px);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
font-family: Georgia, 'Times New Roman', serif;
|
||||
font-size: 9px;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.35em;
|
||||
color: #b89a78;
|
||||
pointer-events: none;
|
||||
}
|
||||
.gf-postcard::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
bottom: 3px;
|
||||
width: 96px;
|
||||
height: 36px;
|
||||
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 48'%3E%3Cg fill='none' stroke='%23b89a78' stroke-width='1.5'%3E%3Ccircle cx='24' cy='24' r='15'/%3E%3Ccircle cx='24' cy='24' r='10.5'/%3E%3Cpath d='M48 14c5-3 9 3 14 0M48 22c5-3 9 3 14 0M48 30c5-3 9 3 14 0' stroke-width='1.2'/%3E%3C/g%3E%3Cg transform='translate(76,6)'%3E%3Crect width='30' height='36' fill='none' stroke='%23c9a978' stroke-width='1.5' stroke-dasharray='3 2.4'/%3E%3Cpath d='M15 26c-5-3.6-9-7-9-11a5 5 0 0 1 9-2.6a5 5 0 0 1 9 2.6c0 4-4 7.4-9 11z' fill='%23d9a0a5'/%3E%3C/g%3E%3C/svg%3E") no-repeat right center / contain;
|
||||
opacity: 0.9;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 拱门(顶部全圆,婚礼穹顶造型) */
|
||||
.gf-arch {
|
||||
padding: 8px;
|
||||
border: 1px solid rgba(201, 169, 120, 0.5);
|
||||
border-radius: 999px 999px 14px 14px;
|
||||
box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.65), 0 14px 32px rgba(120, 90, 60, 0.16);
|
||||
background: #fff;
|
||||
}
|
||||
.gf-arch > img {
|
||||
border-radius: 992px 992px 8px 8px;
|
||||
}
|
||||
/* 拱顶花饰圆章(呼应尾页门框 keystone) */
|
||||
.gf-arch::before {
|
||||
content: '❀';
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
color: #b08e63;
|
||||
background: rgba(253, 251, 247, 0.92);
|
||||
box-shadow: 0 0 0 1px rgba(201, 169, 120, 0.45), 0 2px 6px rgba(120, 90, 60, 0.18);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 椭圆相框 */
|
||||
.gf-oval {
|
||||
padding: 7px;
|
||||
border: 1px solid rgba(201, 169, 120, 0.55);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.7), 0 12px 30px rgba(120, 90, 60, 0.16);
|
||||
background: #fff;
|
||||
}
|
||||
.gf-oval > img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* 正圆相框(推荐 1:1) */
|
||||
.gf-circle {
|
||||
padding: 8px;
|
||||
border: 1px solid rgba(201, 169, 120, 0.55);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.7), 0 0 0 5px rgba(201, 169, 120, 0.35), 0 12px 30px rgba(120, 90, 60, 0.16);
|
||||
background: #fff;
|
||||
}
|
||||
.gf-circle > img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
/* 椭圆/正圆:底部花饰小章 */
|
||||
.gf-oval::before,
|
||||
.gf-circle::before {
|
||||
content: '❀';
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
color: #b08e63;
|
||||
background: rgba(253, 251, 247, 0.92);
|
||||
box-shadow: 0 0 0 1px rgba(201, 169, 120, 0.4), 0 2px 5px rgba(120, 90, 60, 0.15);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 叶形花瓣(两对角大圆两对角小圆,推荐 1:1) */
|
||||
.gf-leaf {
|
||||
padding: 7px;
|
||||
border: 1px solid rgba(201, 169, 120, 0.5);
|
||||
border-radius: 60% 12% 60% 12%;
|
||||
box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.6), 0 12px 30px rgba(120, 90, 60, 0.15);
|
||||
background: #fff;
|
||||
}
|
||||
.gf-leaf > img {
|
||||
border-radius: 58% 9% 58% 9%;
|
||||
}
|
||||
|
||||
/* 六边形(clip 直裁,推荐 1:1) */
|
||||
.gf-hexagon {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
|
||||
-webkit-clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
|
||||
}
|
||||
|
||||
/* 八瓣花(多层 radial mask 合成花形,推荐 1:1) */
|
||||
.gf-flower {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
-webkit-mask:
|
||||
radial-gradient(19% 19% at 50% 19%, #000 98%, #0000 100%),
|
||||
radial-gradient(19% 19% at 71.9% 28.1%, #000 98%, #0000 100%),
|
||||
radial-gradient(19% 19% at 81% 50%, #000 98%, #0000 100%),
|
||||
radial-gradient(19% 19% at 71.9% 71.9%, #000 98%, #0000 100%),
|
||||
radial-gradient(19% 19% at 50% 81%, #000 98%, #0000 100%),
|
||||
radial-gradient(19% 19% at 28.1% 71.9%, #000 98%, #0000 100%),
|
||||
radial-gradient(19% 19% at 19% 50%, #000 98%, #0000 100%),
|
||||
radial-gradient(19% 19% at 28.1% 28.1%, #000 98%, #0000 100%),
|
||||
radial-gradient(34% 34% at 50% 50%, #000 98%, #0000 100%);
|
||||
mask:
|
||||
radial-gradient(19% 19% at 50% 19%, #000 98%, #0000 100%),
|
||||
radial-gradient(19% 19% at 71.9% 28.1%, #000 98%, #0000 100%),
|
||||
radial-gradient(19% 19% at 81% 50%, #000 98%, #0000 100%),
|
||||
radial-gradient(19% 19% at 71.9% 71.9%, #000 98%, #0000 100%),
|
||||
radial-gradient(19% 19% at 50% 81%, #000 98%, #0000 100%),
|
||||
radial-gradient(19% 19% at 28.1% 71.9%, #000 98%, #0000 100%),
|
||||
radial-gradient(19% 19% at 19% 50%, #000 98%, #0000 100%),
|
||||
radial-gradient(19% 19% at 28.1% 28.1%, #000 98%, #0000 100%),
|
||||
radial-gradient(34% 34% at 50% 50%, #000 98%, #0000 100%);
|
||||
}
|
||||
|
||||
/* 心形(SVG mask 裁切,推荐 1:1) */
|
||||
.gf-heart {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath fill='%23000' d='M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z'/%3E%3C/svg%3E") no-repeat 50% 50% / 100% 100%;
|
||||
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath fill='%23000' d='M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z'/%3E%3C/svg%3E") no-repeat 50% 50% / 100% 100%;
|
||||
}
|
||||
|
||||
/* 扇贝花边(四边外凸圆弧波浪) */
|
||||
.gf-scallop {
|
||||
--gf-r: 7px;
|
||||
padding: 14px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
background: #fff;
|
||||
-webkit-mask:
|
||||
radial-gradient(var(--gf-r), #000 98%, #0000) 0 0 / calc(2 * var(--gf-r)) calc(2 * var(--gf-r)) round,
|
||||
linear-gradient(#000 0 0) 50% / calc(100% - 2 * var(--gf-r)) calc(100% - 2 * var(--gf-r)) no-repeat;
|
||||
mask:
|
||||
radial-gradient(var(--gf-r), #000 98%, #0000) 0 0 / calc(2 * var(--gf-r)) calc(2 * var(--gf-r)) round,
|
||||
linear-gradient(#000 0 0) 50% / calc(100% - 2 * var(--gf-r)) calc(100% - 2 * var(--gf-r)) no-repeat;
|
||||
}
|
||||
/* 蕾丝缝线(内圈虚线) */
|
||||
.gf-scallop::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 17px;
|
||||
border: 1px dashed rgba(255, 255, 255, 0.7);
|
||||
border-radius: 2px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 蕾丝波浪(四边相切凹圆弧,剪纸蕾丝垫感) */
|
||||
.gf-wave {
|
||||
--gf-r: 9px;
|
||||
padding: 15px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
background: #fff;
|
||||
-webkit-mask:
|
||||
radial-gradient(var(--gf-r), #0000 98%, #000) calc(-1 * var(--gf-r)) calc(-1 * var(--gf-r)) / calc(2 * var(--gf-r)) calc(2 * var(--gf-r)) round,
|
||||
linear-gradient(#000 0 0) 50% / calc(100% - 2 * var(--gf-r)) calc(100% - 2 * var(--gf-r)) no-repeat;
|
||||
mask:
|
||||
radial-gradient(var(--gf-r), #0000 98%, #000) calc(-1 * var(--gf-r)) calc(-1 * var(--gf-r)) / calc(2 * var(--gf-r)) calc(2 * var(--gf-r)) round,
|
||||
linear-gradient(#000 0 0) 50% / calc(100% - 2 * var(--gf-r)) calc(100% - 2 * var(--gf-r)) no-repeat;
|
||||
}
|
||||
/* 蕾丝点状缝线 */
|
||||
.gf-wave::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 18px;
|
||||
border: 1px dotted rgba(255, 255, 255, 0.75);
|
||||
border-radius: 3px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 锯齿花边(四边三角齿,花边剪刀裁切的相纸;齿深须为齿宽一半) */
|
||||
.gf-zigzag {
|
||||
--gf-z: 16px;
|
||||
--gf-zd: 8px;
|
||||
padding: 14px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
background: #fff;
|
||||
-webkit-mask:
|
||||
conic-gradient(from 135deg at 50% 0, #000 90deg, #0000 0) 50% 0 / var(--gf-z) var(--gf-zd) repeat-x,
|
||||
conic-gradient(from -45deg at 50% 100%, #000 90deg, #0000 0) 50% 100% / var(--gf-z) var(--gf-zd) repeat-x,
|
||||
conic-gradient(from 45deg at 0 50%, #000 90deg, #0000 0) 0 50% / var(--gf-zd) var(--gf-z) repeat-y,
|
||||
conic-gradient(from 225deg at 100% 50%, #000 90deg, #0000 0) 100% 50% / var(--gf-zd) var(--gf-z) repeat-y,
|
||||
linear-gradient(#000 0 0) 50% / calc(100% - 2 * var(--gf-zd) + 2px) calc(100% - 2 * var(--gf-zd) + 2px) no-repeat;
|
||||
mask:
|
||||
conic-gradient(from 135deg at 50% 0, #000 90deg, #0000 0) 50% 0 / var(--gf-z) var(--gf-zd) repeat-x,
|
||||
conic-gradient(from -45deg at 50% 100%, #000 90deg, #0000 0) 50% 100% / var(--gf-z) var(--gf-zd) repeat-x,
|
||||
conic-gradient(from 45deg at 0 50%, #000 90deg, #0000 0) 0 50% / var(--gf-zd) var(--gf-z) repeat-y,
|
||||
conic-gradient(from 225deg at 100% 50%, #000 90deg, #0000 0) 100% 50% / var(--gf-zd) var(--gf-z) repeat-y,
|
||||
linear-gradient(#000 0 0) 50% / calc(100% - 2 * var(--gf-zd) + 2px) calc(100% - 2 * var(--gf-zd) + 2px) no-repeat;
|
||||
}
|
||||
/* 剪纸内线 */
|
||||
.gf-zigzag::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 17px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.55);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 切角相纸(八边形切角,复古照片角) */
|
||||
.gf-bevel {
|
||||
padding: 10px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
background: #fff;
|
||||
clip-path: polygon(18px 0, calc(100% - 18px) 0, 100% 18px, 100% calc(100% - 18px), calc(100% - 18px) 100%, 18px 100%, 0 calc(100% - 18px), 0 18px);
|
||||
-webkit-clip-path: polygon(18px 0, calc(100% - 18px) 0, 100% 18px, 100% calc(100% - 18px), calc(100% - 18px) 100%, 18px 100%, 0 calc(100% - 18px), 0 18px);
|
||||
}
|
||||
|
||||
/* 撕纸边(不规则毛边,像从相纸上撕下来) */
|
||||
.gf-torn {
|
||||
padding: 12px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
background: #fff;
|
||||
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none'%3E%3Cpath fill='%23000' d='M0 3L6 1.4L13 3.4L19 1.2L26 3L33 1.6L40 3.2L47 1.3L54 2.8L61 1.5L68 3.3L75 1.4L82 2.9L89 1.6L96 3.1L98.6 8L96.9 14L98.4 20L96.8 27L98.5 34L97 41L98.6 48L96.9 55L98.3 62L97 69L98.5 76L96.8 83L98.4 90L97.1 96L94 98.6L87 97L80 98.7L73 96.9L66 98.5L59 97.1L52 98.6L45 96.8L38 98.4L31 97L24 98.6L17 96.9L10 98.5L4 97.2L1.5 92L3.2 85L1.4 78L3 71L1.6 64L3.3 57L1.3 50L2.9 43L1.5 36L3.2 29L1.4 22L3 15L1.7 8Z'/%3E%3C/svg%3E") no-repeat 50% 50% / 100% 100%;
|
||||
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none'%3E%3Cpath fill='%23000' d='M0 3L6 1.4L13 3.4L19 1.2L26 3L33 1.6L40 3.2L47 1.3L54 2.8L61 1.5L68 3.3L75 1.4L82 2.9L89 1.6L96 3.1L98.6 8L96.9 14L98.4 20L96.8 27L98.5 34L97 41L98.6 48L96.9 55L98.3 62L97 69L98.5 76L96.8 83L98.4 90L97.1 96L94 98.6L87 97L80 98.7L73 96.9L66 98.5L59 97.1L52 98.6L45 96.8L38 98.4L31 97L24 98.6L17 96.9L10 98.5L4 97.2L1.5 92L3.2 85L1.4 78L3 71L1.6 64L3.3 57L1.3 50L2.9 43L1.5 36L3.2 29L1.4 22L3 15L1.7 8Z'/%3E%3C/svg%3E") no-repeat 50% 50% / 100% 100%;
|
||||
}
|
||||
/* 撕纸:顶部半透明胶带贴纸 */
|
||||
.gf-torn::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 50%;
|
||||
width: 72px;
|
||||
height: 20px;
|
||||
transform: translateX(-50%) rotate(-2.5deg);
|
||||
background: linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.36));
|
||||
box-shadow: 0 1px 4px rgba(96, 72, 50, 0.18);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 车票票根(两侧半圆缺口,缺口自带撕票线意象;
|
||||
不画横穿虚线——会压在照片主体/人脸上) */
|
||||
.gf-ticket {
|
||||
--gf-n: 11px;
|
||||
padding: 10px 16px 26px;
|
||||
border: 0;
|
||||
border-radius: 14px;
|
||||
box-shadow: none;
|
||||
background: #fff;
|
||||
-webkit-mask:
|
||||
radial-gradient(var(--gf-n) at 0 50%, #0000 98%, #000) 0 0 / 51% 100% no-repeat,
|
||||
radial-gradient(var(--gf-n) at 100% 50%, #0000 98%, #000) 100% 0 / 51% 100% no-repeat;
|
||||
mask:
|
||||
radial-gradient(var(--gf-n) at 0 50%, #0000 98%, #000) 0 0 / 51% 100% no-repeat,
|
||||
radial-gradient(var(--gf-n) at 100% 50%, #0000 98%, #000) 100% 0 / 51% 100% no-repeat;
|
||||
}
|
||||
/* 票面字样(位于底部票边区,窄格自动截断居中) */
|
||||
.gf-ticket::before {
|
||||
content: 'ADMIT ONE';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 8px;
|
||||
text-align: center;
|
||||
font-family: Georgia, 'Times New Roman', serif;
|
||||
font-size: 9px;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.4em;
|
||||
text-indent: 0.4em;
|
||||
color: #b89a78;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 复古胶片(深色框 + 上下齿孔) */
|
||||
.gf-film {
|
||||
padding: 15px 6px;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 12px 28px rgba(30, 24, 18, 0.3);
|
||||
background: #26231f;
|
||||
}
|
||||
.gf-film > img {
|
||||
border-radius: 2px;
|
||||
}
|
||||
.gf-film::before,
|
||||
.gf-film::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
height: 7px;
|
||||
background-image: repeating-linear-gradient(90deg, rgba(253, 251, 247, 0.85) 0 9px, transparent 9px 18px);
|
||||
pointer-events: none;
|
||||
}
|
||||
.gf-film::before {
|
||||
top: 4px;
|
||||
}
|
||||
.gf-film::after {
|
||||
bottom: 4px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
横幅 · 云雾花(group: scenic)—— 横向比例的风景明信片
|
||||
装饰主题:云朵 / 晨雾 / 花卉;推荐 4:3 或 16:9
|
||||
============================================================ */
|
||||
|
||||
/* 云朵相框(大小圆交替蓬松云边,推荐 4:3) */
|
||||
.gf-cloud {
|
||||
--gf-c: 14px;
|
||||
padding: 16px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
background: #fff;
|
||||
-webkit-mask:
|
||||
radial-gradient(var(--gf-c), #000 98%, #0000) 0 0 / calc(3.4 * var(--gf-c)) calc(3.4 * var(--gf-c)) round,
|
||||
radial-gradient(calc(0.68 * var(--gf-c)), #000 98%, #0000) calc(1.7 * var(--gf-c)) calc(1.7 * var(--gf-c)) / calc(3.4 * var(--gf-c)) calc(3.4 * var(--gf-c)) round,
|
||||
linear-gradient(#000 0 0) 50% / calc(100% - 2.4 * var(--gf-c)) calc(100% - 2.4 * var(--gf-c)) no-repeat;
|
||||
mask:
|
||||
radial-gradient(var(--gf-c), #000 98%, #0000) 0 0 / calc(3.4 * var(--gf-c)) calc(3.4 * var(--gf-c)) round,
|
||||
radial-gradient(calc(0.68 * var(--gf-c)), #000 98%, #0000) calc(1.7 * var(--gf-c)) calc(1.7 * var(--gf-c)) / calc(3.4 * var(--gf-c)) calc(3.4 * var(--gf-c)) round,
|
||||
linear-gradient(#000 0 0) 50% / calc(100% - 2.4 * var(--gf-c)) calc(100% - 2.4 * var(--gf-c)) no-repeat;
|
||||
}
|
||||
/* 右上飘着的小云 */
|
||||
.gf-cloud::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 22px;
|
||||
right: 24px;
|
||||
width: 52px;
|
||||
height: 22px;
|
||||
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 26'%3E%3Cpath d='M13 22a7 7 0 1 1 1.6-13.8A10 10 0 0 1 34 5a8 8 0 0 1 8.6 8A6.5 6.5 0 0 1 41 22z' fill='%23ffffff' fill-opacity='.92'/%3E%3Cellipse cx='54' cy='19' rx='8' ry='4.6' fill='%23ffffff' fill-opacity='.72'/%3E%3C/svg%3E") no-repeat center / contain;
|
||||
filter: drop-shadow(0 2px 3px rgba(120, 90, 60, 0.14));
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 晨雾朦胧(四周羽化渐隐 + 底部飘雾,推荐 4:3;无边框无投影更空灵) */
|
||||
.gf-mist {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
-webkit-mask: radial-gradient(112% 112% at 50% 48%, #000 56%, #0000 90%);
|
||||
mask: radial-gradient(112% 112% at 50% 48%, #000 56%, #0000 90%);
|
||||
}
|
||||
/* 底部两团晨雾 */
|
||||
.gf-mist::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 6%;
|
||||
right: 6%;
|
||||
bottom: 4%;
|
||||
height: 30%;
|
||||
background:
|
||||
radial-gradient(58% 100% at 30% 100%, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0) 70%),
|
||||
radial-gradient(48% 88% at 72% 100%, rgba(255, 255, 255, 0.38), rgba(255, 255, 255, 0) 72%);
|
||||
filter: blur(6px);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 花语卡纸(暖白卡 + 对角花枝,推荐 4:3) */
|
||||
.gf-bloom {
|
||||
padding: 14px;
|
||||
border: 1px solid rgba(201, 169, 120, 0.4);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 26px rgba(120, 90, 60, 0.14);
|
||||
background: linear-gradient(160deg, #fffdf8, #fdf7ec);
|
||||
}
|
||||
.gf-bloom > img {
|
||||
border-radius: 5px;
|
||||
}
|
||||
/* 左上 / 右下花枝(同一 SVG,右下旋转 180°) */
|
||||
.gf-bloom::before,
|
||||
.gf-bloom::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cg fill='none' stroke='%23b8a078' stroke-width='1.4' stroke-linecap='round'%3E%3Cpath d='M6 58C16 46 26 36 46 24'/%3E%3Cpath d='M22 42c7 1 12-2 14-8'/%3E%3Cpath d='M14 50c5 2 10 1 13-3'/%3E%3C/g%3E%3Cg fill='%23a8bfa0'%3E%3Cpath d='M30 33c-1-5 1-8 5-9 1 4-1 7-5 9z'/%3E%3Cpath d='M20 44c-4-2-5-6-3-9 3 2 4 5 3 9z'/%3E%3C/g%3E%3Cg transform='translate(48,20)'%3E%3Cg fill='%23e4aeb6'%3E%3Ccircle cx='0' cy='-5.2' r='3.6'/%3E%3Ccircle cx='4.9' cy='-1.6' r='3.6'/%3E%3Ccircle cx='3.1' cy='4.2' r='3.6'/%3E%3Ccircle cx='-3.1' cy='4.2' r='3.6'/%3E%3Ccircle cx='-4.9' cy='-1.6' r='3.6'/%3E%3C/g%3E%3Ccircle r='2.4' fill='%23c9a24a'/%3E%3C/g%3E%3Cg transform='translate(30,52) scale(.62)'%3E%3Cg fill='%23edc3ca'%3E%3Ccircle cx='0' cy='-5.2' r='3.6'/%3E%3Ccircle cx='4.9' cy='-1.6' r='3.6'/%3E%3Ccircle cx='3.1' cy='4.2' r='3.6'/%3E%3Ccircle cx='-3.1' cy='4.2' r='3.6'/%3E%3Ccircle cx='-4.9' cy='-1.6' r='3.6'/%3E%3C/g%3E%3Ccircle r='2.4' fill='%23c9a24a'/%3E%3C/g%3E%3C/svg%3E") no-repeat center / contain;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
.gf-bloom::before {
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
}
|
||||
.gf-bloom::after {
|
||||
bottom: 2px;
|
||||
right: 2px;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* 宽幅全景(16:9 电影画幅白卡 + 云与飞鸟 + 手写落款) */
|
||||
.gf-panorama {
|
||||
padding: 16px 14px 34px;
|
||||
border: 1px solid rgba(184, 154, 120, 0.3);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 12px 30px rgba(120, 90, 60, 0.16);
|
||||
background: #fffdf6;
|
||||
}
|
||||
/* 顶部白边区:淡云 + 三只飞鸟 */
|
||||
.gf-panorama::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 14px;
|
||||
right: 12px;
|
||||
height: 12px;
|
||||
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 124 14'%3E%3Cpath d='M8 11a4 4 0 1 1 1-7.9A5.5 5.5 0 0 1 19 2a4.5 4.5 0 0 1 5 4.5A3.8 3.8 0 0 1 22 11z' fill='%23d8c8b0' fill-opacity='.55'/%3E%3Cg fill='none' stroke='%23a08a6a' stroke-width='1.3' stroke-linecap='round'%3E%3Cpath d='M78 8c2.5-2.6 4.5-2.6 7 0M85 8c2.5-2.6 4.5-2.6 7 0'/%3E%3Cpath d='M96 5c2-2.2 3.8-2.2 5.8 0M101.8 5c2-2.2 3.8-2.2 5.8 0'/%3E%3Cpath d='M110 10c1.6-1.8 3-1.8 4.6 0M114.6 10c1.6-1.8 3-1.8 4.6 0'/%3E%3C/g%3E%3C/svg%3E") no-repeat center / contain;
|
||||
opacity: 0.9;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
/* 底部白边区:手写体落款 */
|
||||
.gf-panorama::after {
|
||||
content: 'with love';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 9px;
|
||||
text-align: center;
|
||||
font-family: 'Great Vibes', 'Segoe Script', cursive;
|
||||
font-size: 15px;
|
||||
line-height: 1;
|
||||
color: #b89a78;
|
||||
letter-spacing: 0.06em;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 横椭圆框(椭圆随 4:3 容器自然拉宽 + 底部花饰小章) */
|
||||
.gf-ovalwide {
|
||||
padding: 7px;
|
||||
border: 1px solid rgba(201, 169, 120, 0.55);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.7), 0 12px 30px rgba(120, 90, 60, 0.16);
|
||||
background: #fff;
|
||||
}
|
||||
.gf-ovalwide > img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.gf-ovalwide::before {
|
||||
content: '❀';
|
||||
position: absolute;
|
||||
bottom: 6px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
color: #b08e63;
|
||||
background: rgba(253, 251, 247, 0.92);
|
||||
box-shadow: 0 0 0 1px rgba(201, 169, 120, 0.4), 0 2px 5px rgba(120, 90, 60, 0.15);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 异形裁切类的投影:mask 会裁掉 box-shadow,由父级 drop-shadow 跟随轮廓补投影 */
|
||||
.gf-shape-shadow {
|
||||
filter: drop-shadow(0 10px 18px rgba(96, 72, 50, 0.22));
|
||||
}
|
||||
|
||||
/* 辅助:图片保持原始宽高比(单图铺满+保比例时使用,覆盖 gf-frame 的 height:100%) */
|
||||
.gf-natural {
|
||||
height: auto !important;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const DEFAULT_DANMAKU_COLOR = 'champagne'
|
||||
|
||||
/** @type {{ key: string, hex: string, label: string, gradient?: string, gradientBg?: string, glow?: string, textLight?: boolean }[]} */
|
||||
/** @type {{ key: string, hex: string, label: string, gradient?: string, textGradient?: string, edge?: string }[]} */
|
||||
export const DANMAKU_COLORS = [
|
||||
{ key: 'champagne', hex: '#a88c6b', label: '香槟金' },
|
||||
{ key: 'blush', hex: '#e8b4b8', label: '浅粉' },
|
||||
@@ -14,64 +14,55 @@ export const DANMAKU_COLORS = [
|
||||
]
|
||||
|
||||
/**
|
||||
* 渐变配色:
|
||||
* - textLight=true → 浅色字(需整体偏深,保证对比度)
|
||||
* - textLight=false → 深色字(浅色/金色底)
|
||||
* 渐变配色:渐变用于「文字」(background-clip: text),
|
||||
* 气泡底统一为通透白纱,避免大色块遮挡照片内容。
|
||||
* - gradient : 色点预览 / 文字渐变
|
||||
* - textGradient : 文字专用渐变(个别浅色渐变需加深保证白纱底上可读)
|
||||
* - edge : 气泡描边色(取渐变主色调、低透明度)
|
||||
*/
|
||||
export const DANMAKU_GRADIENT_COLORS = [
|
||||
{
|
||||
key: 'gradSunset',
|
||||
hex: '#fff6f2',
|
||||
hex: '#e11d48',
|
||||
label: '晚霞',
|
||||
textLight: true,
|
||||
gradient: 'linear-gradient(125deg, #f97316 0%, #e11d48 42%, #c026d3 78%, #7c3aed 100%)',
|
||||
gradientBg: 'linear-gradient(125deg, rgba(234,88,12,0.94) 0%, rgba(225,29,72,0.93) 42%, rgba(192,38,211,0.93) 78%, rgba(124,58,237,0.94) 100%)',
|
||||
glow: '0 6px 22px rgba(225,29,72,0.42), 0 2px 8px rgba(124,58,237,0.28)',
|
||||
edge: 'rgba(225, 29, 72, 0.32)',
|
||||
},
|
||||
{
|
||||
key: 'gradChampagne',
|
||||
hex: '#4a3420',
|
||||
hex: '#a86a24',
|
||||
label: '流金',
|
||||
textLight: false,
|
||||
gradient: 'linear-gradient(120deg, #fff1c9 0%, #f0c27a 28%, #e8a54b 55%, #c9832f 78%, #8b5a2b 100%)',
|
||||
gradientBg: 'linear-gradient(120deg, rgba(255,241,201,0.96) 0%, rgba(240,194,122,0.94) 28%, rgba(232,165,75,0.92) 55%, rgba(201,131,47,0.9) 78%, rgba(180,140,80,0.88) 100%)',
|
||||
glow: '0 6px 20px rgba(201,131,47,0.35), 0 2px 8px rgba(139,90,43,0.22)',
|
||||
textGradient: 'linear-gradient(120deg, #d9a54a 0%, #c9832f 45%, #a86a24 75%, #8b5a2b 100%)',
|
||||
edge: 'rgba(201, 131, 47, 0.34)',
|
||||
},
|
||||
{
|
||||
key: 'gradBlush',
|
||||
hex: '#fff5fa',
|
||||
hex: '#e11d74',
|
||||
label: '桃雾',
|
||||
textLight: true,
|
||||
gradient: 'linear-gradient(130deg, #fb7185 0%, #e11d74 35%, #c026d3 68%, #7c3aed 100%)',
|
||||
gradientBg: 'linear-gradient(130deg, rgba(244,63,94,0.93) 0%, rgba(219,39,119,0.92) 35%, rgba(192,38,211,0.92) 68%, rgba(124,58,237,0.93) 100%)',
|
||||
glow: '0 6px 22px rgba(219,39,119,0.4), 0 2px 8px rgba(124,58,237,0.28)',
|
||||
edge: 'rgba(219, 39, 119, 0.3)',
|
||||
},
|
||||
{
|
||||
key: 'gradOcean',
|
||||
hex: '#f0f9ff',
|
||||
hex: '#0284c7',
|
||||
label: '海暮',
|
||||
textLight: true,
|
||||
gradient: 'linear-gradient(125deg, #0284c7 0%, #0d9488 32%, #4f46e5 68%, #9333ea 100%)',
|
||||
gradientBg: 'linear-gradient(125deg, rgba(2,132,199,0.93) 0%, rgba(13,148,136,0.92) 32%, rgba(79,70,229,0.93) 68%, rgba(147,51,234,0.93) 100%)',
|
||||
glow: '0 6px 22px rgba(2,132,199,0.38), 0 2px 8px rgba(147,51,234,0.26)',
|
||||
edge: 'rgba(2, 132, 199, 0.3)',
|
||||
},
|
||||
{
|
||||
key: 'gradAurora',
|
||||
hex: '#f4fffb',
|
||||
hex: '#0891b2',
|
||||
label: '极光',
|
||||
textLight: true,
|
||||
gradient: 'linear-gradient(120deg, #059669 0%, #0891b2 28%, #6366f1 58%, #db2777 100%)',
|
||||
gradientBg: 'linear-gradient(120deg, rgba(5,150,105,0.93) 0%, rgba(8,145,178,0.92) 28%, rgba(99,102,241,0.93) 58%, rgba(219,39,119,0.93) 100%)',
|
||||
glow: '0 6px 22px rgba(8,145,178,0.36), 0 2px 10px rgba(219,39,119,0.28)',
|
||||
edge: 'rgba(8, 145, 178, 0.3)',
|
||||
},
|
||||
{
|
||||
key: 'gradEmber',
|
||||
hex: '#fff7ed',
|
||||
hex: '#dc2626',
|
||||
label: '暖焰',
|
||||
textLight: true,
|
||||
gradient: 'linear-gradient(125deg, #ea580c 0%, #dc2626 40%, #be123c 70%, #9f1239 100%)',
|
||||
gradientBg: 'linear-gradient(125deg, rgba(234,88,12,0.94) 0%, rgba(220,38,38,0.93) 40%, rgba(190,18,60,0.93) 70%, rgba(159,18,57,0.94) 100%)',
|
||||
glow: '0 6px 22px rgba(220,38,38,0.42), 0 2px 8px rgba(190,18,60,0.3)',
|
||||
edge: 'rgba(220, 38, 38, 0.3)',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -98,20 +89,21 @@ export function resolveDanmakuSwatch(key) {
|
||||
|
||||
export function resolveDanmakuTint(key) {
|
||||
const entry = BY_KEY[key] || BY_KEY[DEFAULT_DANMAKU_COLOR]
|
||||
if (entry.gradientBg) {
|
||||
if (entry.gradient) {
|
||||
// 渐变字 + 白纱底:彩色只落在文字上,底子通透不挡内容
|
||||
return {
|
||||
background: entry.gradientBg,
|
||||
borderColor: entry.textLight ? 'rgba(255, 255, 255, 0.5)' : 'rgba(74, 52, 32, 0.22)',
|
||||
boxShadow: entry.glow || '0 6px 20px rgba(0,0,0,0.12)',
|
||||
textLight: !!entry.textLight,
|
||||
background: 'rgba(255, 253, 248, 0.4)',
|
||||
borderColor: entry.edge || 'rgba(168, 140, 107, 0.3)',
|
||||
boxShadow: '0 2px 10px rgba(88, 68, 42, 0.1)',
|
||||
textGradient: entry.textGradient || entry.gradient,
|
||||
}
|
||||
}
|
||||
const { r, g, b } = hexToRgb(entry.hex)
|
||||
return {
|
||||
background: `rgba(${r}, ${g}, ${b}, 0.18)`,
|
||||
borderColor: `rgba(${r}, ${g}, ${b}, 0.42)`,
|
||||
boxShadow: '0 4px 14px rgba(88, 68, 42, 0.08)',
|
||||
textLight: false,
|
||||
background: `rgba(${r}, ${g}, ${b}, 0.15)`,
|
||||
borderColor: `rgba(${r}, ${g}, ${b}, 0.38)`,
|
||||
boxShadow: '0 2px 8px rgba(88, 68, 42, 0.07)',
|
||||
textGradient: '',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
125
vite-tailwindcss/src/utils/themes.js
Normal file
125
vite-tailwindcss/src/utils/themes.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 整站风格模板(前后台共用):与 style.css 中 html[data-theme] 变量一一对应
|
||||
* - kind: 'pack' 风格包 —— 布局不变,成套更换配色/字体/纹样/装饰
|
||||
* - kind: 'layout' 完整模板 —— 首屏封面、章节头、尾页的排版结构也不同
|
||||
* - dark: true 暗色主题(night)
|
||||
*/
|
||||
export const DEFAULT_SITE_THEME = 'champagne'
|
||||
|
||||
export const SITE_THEMES = [
|
||||
// ———— 风格包 ————
|
||||
{
|
||||
key: 'champagne', kind: 'pack', label: '典雅香槟',
|
||||
desc: '金棕象牙 · 花体英文 · 细金线',
|
||||
primary: '#a88c6b', line: '#b89a78', bg: '#fbf6ef',
|
||||
},
|
||||
{
|
||||
key: 'french', kind: 'pack', label: '法式浪漫',
|
||||
desc: '奶油粉白 · 花体字 · 花卉线描',
|
||||
primary: '#c78a96', line: '#d9aab4', bg: '#fdf6f4',
|
||||
},
|
||||
{
|
||||
key: 'modern', kind: 'pack', label: '现代简约',
|
||||
desc: '暖灰低饱和 · 无衬线 · 细几何线',
|
||||
primary: '#8a8578', line: '#a39e91', bg: '#f7f6f3',
|
||||
},
|
||||
{
|
||||
key: 'forest', kind: 'pack', label: '森系自然',
|
||||
desc: '青绿手写 · 枝叶元素 · 纸纹底',
|
||||
primary: '#6f9678', line: '#92b399', bg: '#f3f8f2',
|
||||
},
|
||||
{
|
||||
key: 'night', kind: 'pack', label: '星空夜色', dark: true,
|
||||
desc: '深蓝烫金 · 暗色主题 · 星月点缀',
|
||||
primary: '#c9a96a', line: '#8fa3c0', bg: '#101b2e',
|
||||
},
|
||||
// ———— 完整模板 ————
|
||||
{
|
||||
key: 'guofeng-new', kind: 'layout', label: '新中式',
|
||||
desc: '黛绿朱砂 · 月洞门 · 宋体留白',
|
||||
primary: '#486156', line: '#b0885a', bg: '#f6f3ec',
|
||||
},
|
||||
{
|
||||
key: 'guofeng-old', kind: 'layout', label: '老中式',
|
||||
desc: '朱红鎏金 · 婚书囍字 · 回纹灯彩',
|
||||
primary: '#a8323e', line: '#c9a24a', bg: '#f9efe6',
|
||||
},
|
||||
{
|
||||
key: 'magazine', kind: 'layout', label: '杂志封面',
|
||||
desc: '全幅大图 · 贴边大字 · 编号章节',
|
||||
primary: '#1f1c19', line: '#8a8378', bg: '#f5f2ec',
|
||||
},
|
||||
{
|
||||
key: 'newspaper', kind: 'layout', label: '日报编辑',
|
||||
desc: '报头栏线 · 衬线排印 · 日期栏',
|
||||
primary: '#2b2822', line: '#6b675e', bg: '#f6f3ea',
|
||||
},
|
||||
{
|
||||
key: 'pixel', kind: 'layout', label: '复古像素',
|
||||
desc: '8-bit 像素字 · 游戏界面 · 马赛克心',
|
||||
primary: '#c94f7c', line: '#5f9ea0', bg: '#faf3e6',
|
||||
},
|
||||
{
|
||||
key: 'zen', kind: 'layout', label: '极简主义',
|
||||
desc: '黑白留白 · 细线 · 轴线排版',
|
||||
primary: '#2a2a28', line: '#8f8d88', bg: '#fafaf8',
|
||||
},
|
||||
]
|
||||
|
||||
export const SITE_THEME_GROUPS = [
|
||||
{ key: 'pack', label: '风格包 · 换装不换版', items: SITE_THEMES.filter((t) => t.kind === 'pack') },
|
||||
{ key: 'layout', label: '完整模板 · 排版重构', items: SITE_THEMES.filter((t) => t.kind === 'layout') },
|
||||
]
|
||||
|
||||
/** 旧主题值映射(v1 纯色主题 / v2 中式风格包 → 现行风格模板) */
|
||||
const LEGACY_THEME_MAP = {
|
||||
crimson: 'guofeng-old',
|
||||
chinese: 'guofeng-old',
|
||||
sage: 'forest',
|
||||
ocean: 'night',
|
||||
lilac: 'french',
|
||||
sunset: 'champagne',
|
||||
}
|
||||
|
||||
export function normalizeSiteTheme(key) {
|
||||
if (SITE_THEMES.some((t) => t.key === key)) return key
|
||||
if (LEGACY_THEME_MAP[key]) return LEGACY_THEME_MAP[key]
|
||||
return DEFAULT_SITE_THEME
|
||||
}
|
||||
|
||||
export function siteThemeMeta(key) {
|
||||
const k = normalizeSiteTheme(key)
|
||||
return SITE_THEMES.find((t) => t.key === k)
|
||||
}
|
||||
|
||||
/** 是否完整模板(前台首屏/章节头/尾页走布局分支) */
|
||||
export function themeLayoutKind(key) {
|
||||
return siteThemeMeta(key).kind
|
||||
}
|
||||
|
||||
/** 是否暗色主题 */
|
||||
export function isDarkTheme(key) {
|
||||
return !!siteThemeMeta(key).dark
|
||||
}
|
||||
|
||||
/** 应用主题到整页(html[data-theme]),前台按配置、后台跟随编辑值 */
|
||||
export function applySiteTheme(key) {
|
||||
const k = normalizeSiteTheme(key)
|
||||
document.documentElement.dataset.theme = k
|
||||
if (k === 'pixel') ensurePixelCjkFont()
|
||||
}
|
||||
|
||||
/**
|
||||
* 中文像素字形按需加载:
|
||||
* 本地 @fontsource 包只含 latin 子集(英文/数字像素),中日韩全量字形
|
||||
* 走 unicode-range 分片 CDN(cn-font-split 产物,只下载用到的分片)。
|
||||
* 仅在选中 pixel 主题时注入,其他主题零开销。
|
||||
*/
|
||||
function ensurePixelCjkFont() {
|
||||
if (typeof document === 'undefined' || document.getElementById('pixel-cjk-font')) return
|
||||
const link = document.createElement('link')
|
||||
link.id = 'pixel-cjk-font'
|
||||
link.rel = 'stylesheet'
|
||||
link.href = 'https://fontsapi.zeoseven.com/570/main/result.css'
|
||||
document.head.appendChild(link)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,37 +1,37 @@
|
||||
<template>
|
||||
<div class="min-h-screen w-full flex items-center justify-center bg-[#Fdfbf7] relative overflow-hidden">
|
||||
<div class="min-h-screen w-full flex items-center justify-center bg-[var(--c-paper)] relative overflow-hidden">
|
||||
<!-- 背景装饰 -->
|
||||
<div class="absolute -top-24 -right-24 w-80 h-80 rounded-full bg-[#a88c6b]/10 blur-3xl"></div>
|
||||
<div class="absolute -top-24 -right-24 w-80 h-80 rounded-full bg-[var(--c-primary)]/10 blur-3xl"></div>
|
||||
<div class="absolute -bottom-24 -left-24 w-80 h-80 rounded-full bg-[#e8a5b2]/10 blur-3xl"></div>
|
||||
<div class="absolute inset-0 pointer-events-none opacity-[0.04]"
|
||||
style="background-image: radial-gradient(#a88c6b 1px, transparent 1px); background-size: 22px 22px;"></div>
|
||||
style="background-image: radial-gradient(var(--c-primary) 1px, transparent 1px); background-size: 22px 22px;"></div>
|
||||
|
||||
<div class="relative z-10 w-[360px] bg-white/80 backdrop-blur-xl border border-[#a88c6b]/15 rounded-3xl shadow-2xl p-9 animate-fade-up">
|
||||
<div class="relative z-10 w-[360px] bg-white/80 backdrop-blur-xl border border-[var(--c-primary)]/15 rounded-3xl shadow-2xl p-9 animate-fade-up">
|
||||
<div class="text-center mb-8">
|
||||
<div class="text-3xl mb-3 text-[#a88c6b] font-serif">❀</div>
|
||||
<h1 class="text-xl tracking-[0.3em] text-[#2c2c2c] font-light">婚礼邀请函 · 后台</h1>
|
||||
<p class="text-[11px] text-[#8A8680] tracking-[0.25em] mt-2">制作者 · 年糕崽崽</p>
|
||||
<div class="text-3xl mb-3 text-[var(--c-primary)] font-serif">❀</div>
|
||||
<h1 class="text-xl tracking-[0.3em] text-[var(--c-ink)] font-light">婚礼邀请函 · 后台</h1>
|
||||
<p class="text-[11px] text-[var(--c-muted)] tracking-[0.25em] mt-2">制作者 · 年糕崽崽</p>
|
||||
</div>
|
||||
|
||||
<a-form :model="form" layout="vertical" @finish="onSubmit">
|
||||
<a-form-item name="account" :rules="[{ required: true, message: '请输入账号' }]">
|
||||
<a-input v-model:value="form.account" size="large" placeholder="管理员账号">
|
||||
<template #prefix><i class="fa-solid fa-user text-[#a88c6b]"></i></template>
|
||||
<template #prefix><i class="fa-solid fa-user text-[var(--c-primary)]"></i></template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="password" :rules="[{ required: true, message: '请输入密码' }]">
|
||||
<a-input-password v-model:value="form.password" size="large" placeholder="密码">
|
||||
<template #prefix><i class="fa-solid fa-lock text-[#a88c6b]"></i></template>
|
||||
<template #prefix><i class="fa-solid fa-lock text-[var(--c-primary)]"></i></template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-button type="primary" html-type="submit" size="large" block :loading="loading"
|
||||
class="!bg-[#a88c6b] hover:!bg-[#967b5c] !border-[#a88c6b] !rounded-xl !tracking-[0.2em]">
|
||||
class="!bg-[var(--c-primary)] hover:!bg-[var(--c-primary-deep)] !border-[var(--c-primary)] !rounded-xl !tracking-[0.2em]">
|
||||
登 录
|
||||
</a-button>
|
||||
</a-form>
|
||||
|
||||
<div class="mt-6 text-center">
|
||||
<router-link to="/" class="text-[12px] text-[#a88c6b] hover:underline">返回请柬预览</router-link>
|
||||
<router-link to="/" class="text-[12px] text-[var(--c-primary)] hover:underline">返回请柬预览</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
<div v-if="view === 'list'" class="admin-panel">
|
||||
<div class="flex justify-between items-center mb-4 gap-3 flex-wrap">
|
||||
<div>
|
||||
<div class="text-sm text-[#2c2c2c]">婚礼迎宾易拉宝</div>
|
||||
<div class="text-[11px] text-[#8A8680] mt-0.5">
|
||||
<div class="text-sm text-[var(--c-ink)]">婚礼迎宾易拉宝</div>
|
||||
<div class="text-[11px] text-[var(--c-muted)] mt-0.5">
|
||||
共 {{ list.length }} 张 · 酒店门口欢迎海报(新人姓名 / 日期 / WEDDING),非按贵宾制
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<a-button size="small" type="primary" class="!bg-[#a88c6b] !border-[#a88c6b]" @click="openCreate">
|
||||
<a-button size="small" type="primary" class="!bg-[var(--c-primary)] !border-[var(--c-primary)]" @click="openCreate">
|
||||
<i class="fa-solid fa-plus mr-1"></i>新建海报
|
||||
</a-button>
|
||||
<a-button size="small" :loading="listLoading" @click="loadList">
|
||||
@@ -33,11 +33,11 @@
|
||||
<span class="admin-pill admin-pill--gold">{{ templateLabel(record.template) }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'updated_at'">
|
||||
<span class="text-[12px] text-[#8A8680]">{{ formatTime(record.updated_at) }}</span>
|
||||
<span class="text-[12px] text-[var(--c-muted)]">{{ formatTime(record.updated_at) }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<a-space>
|
||||
<a-button size="small" type="link" class="!text-[#a88c6b] !px-1" @click="openDetail(record)">编辑</a-button>
|
||||
<a-button size="small" type="link" class="!text-[var(--c-primary)] !px-1" @click="openDetail(record)">编辑</a-button>
|
||||
<a-button size="small" danger type="text" @click="onDelete(record)">删除</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
@@ -54,8 +54,8 @@
|
||||
<i class="fa-solid fa-arrow-left mr-1"></i>返回列表
|
||||
</a-button>
|
||||
<div class="min-w-0">
|
||||
<div class="text-sm text-[#2c2c2c] truncate">{{ detail?.name || '迎宾易拉宝' }}</div>
|
||||
<div class="text-[11px] text-[#8A8680] mt-0.5">改新人姓名与日期 · 拖拽排版 · 导出酒店门口立牌 PNG</div>
|
||||
<div class="text-sm text-[var(--c-ink)] truncate">{{ detail?.name || '迎宾易拉宝' }}</div>
|
||||
<div class="text-[11px] text-[var(--c-muted)] mt-0.5">改新人姓名与日期 · 拖拽排版 · 导出酒店门口立牌 PNG</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
@@ -65,7 +65,7 @@
|
||||
<a-button
|
||||
size="small"
|
||||
type="primary"
|
||||
class="!bg-[#a88c6b] !border-[#a88c6b]"
|
||||
class="!bg-[var(--c-primary)] !border-[var(--c-primary)]"
|
||||
:loading="saving"
|
||||
@click="saveDetail"
|
||||
>
|
||||
@@ -75,7 +75,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="detailLoading" class="admin-panel text-center text-[#8A8680] py-16">加载中…</div>
|
||||
<div v-if="detailLoading" class="admin-panel text-center text-[var(--c-muted)] py-16">加载中…</div>
|
||||
|
||||
<div v-else-if="detail" class="hw-editor-grid">
|
||||
<div class="admin-panel hw-canvas-panel">
|
||||
@@ -111,7 +111,7 @@
|
||||
{{ t.content || ' ' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-[10px] text-[#8A8680] mt-2 text-center">点击文字选中并拖拽;点击空白取消选中</div>
|
||||
<div class="text-[10px] text-[var(--c-muted)] mt-2 text-center">点击文字选中并拖拽;点击空白取消选中</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-panel hw-side-panel">
|
||||
@@ -156,7 +156,7 @@
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div v-if="!texts.length" class="text-[11px] text-[#8A8680] mb-3">暂无文字,可切换模板或手动添加</div>
|
||||
<div v-if="!texts.length" class="text-[11px] text-[var(--c-muted)] mb-3">暂无文字,可切换模板或手动添加</div>
|
||||
|
||||
<div
|
||||
v-for="t in texts"
|
||||
@@ -166,7 +166,7 @@
|
||||
@click="selectedId = t.id"
|
||||
>
|
||||
<div class="flex justify-between gap-2 mb-2">
|
||||
<span class="text-[11px] text-[#8A8680] truncate">{{ t.content || '(空)' }}</span>
|
||||
<span class="text-[11px] text-[var(--c-muted)] truncate">{{ t.content || '(空)' }}</span>
|
||||
<a-button size="small" danger type="text" class="!px-1" @click.stop="removeText(t.id)">删除</a-button>
|
||||
</div>
|
||||
<a-input v-model:value="t.content" placeholder="文字内容" class="mb-2" :maxlength="80" />
|
||||
@@ -205,7 +205,7 @@
|
||||
<a-checkbox v-model:checked="t.shadow">阴影</a-checkbox>
|
||||
<a-checkbox v-model:checked="t.stroke">描边</a-checkbox>
|
||||
</div>
|
||||
<div class="text-[10px] text-[#8A8680]">位置 X {{ Math.round(t.x) }}% · Y {{ Math.round(t.y) }}%</div>
|
||||
<div class="text-[10px] text-[var(--c-muted)]">位置 X {{ Math.round(t.x) }}% · Y {{ Math.round(t.y) }}%</div>
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
@@ -251,7 +251,7 @@
|
||||
:uploading="uploading"
|
||||
@pick="pickCreateImage"
|
||||
/>
|
||||
<div class="text-[10px] text-[#8A8680] mt-1">创建后改新人姓名、日期,并拖拽排版后导出</div>
|
||||
<div class="text-[10px] text-[var(--c-muted)] mt-1">创建后改新人姓名、日期,并拖拽排版后导出</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
@@ -267,7 +267,7 @@
|
||||
:body-style="{ padding: '12px 16px 8px', maxHeight: 'min(70vh, 640px)', overflow: 'hidden' }"
|
||||
>
|
||||
<div class="hw-tpl-modal-body">
|
||||
<div class="text-[11px] text-[#8A8680] mb-3 sticky top-0 bg-white z-[1] pb-1">
|
||||
<div class="text-[11px] text-[var(--c-muted)] mb-3 sticky top-0 bg-white z-[1] pb-1">
|
||||
每个卡片展示文字落点区域,点击即可选用;之后仍可在画布上拖拽微调
|
||||
</div>
|
||||
<div v-for="group in templateGroupList" :key="group.label" class="hw-tpl-group">
|
||||
@@ -885,7 +885,7 @@ defineExpose({ loadList, refresh: loadList })
|
||||
<style scoped>
|
||||
.admin-panel {
|
||||
background: #fff;
|
||||
border: 1px solid rgba(168, 140, 107, 0.14);
|
||||
border: 1px solid rgba(var(--c-primary-rgb), 0.14);
|
||||
border-radius: 16px;
|
||||
padding: 20px 20px 16px;
|
||||
box-shadow: 0 8px 28px rgba(88, 68, 42, 0.05);
|
||||
@@ -901,8 +901,8 @@ defineExpose({ loadList, refresh: loadList })
|
||||
}
|
||||
.admin-pill--gold {
|
||||
color: #8a7048;
|
||||
background: rgba(168, 140, 107, 0.14);
|
||||
border-color: rgba(168, 140, 107, 0.28);
|
||||
background: rgba(var(--c-primary-rgb), 0.14);
|
||||
border-color: rgba(var(--c-primary-rgb), 0.28);
|
||||
}
|
||||
.admin-table :deep(.ant-table-thead > tr > th) {
|
||||
background: #f7f3ec !important;
|
||||
@@ -954,7 +954,7 @@ defineExpose({ loadList, refresh: loadList })
|
||||
pointer-events: none;
|
||||
}
|
||||
.hw-stage-empty {
|
||||
color: #8a8680;
|
||||
color: var(--c-muted);
|
||||
font-size: 13px;
|
||||
line-height: normal;
|
||||
}
|
||||
@@ -991,21 +991,21 @@ defineExpose({ loadList, refresh: loadList })
|
||||
overflow: auto;
|
||||
}
|
||||
.hw-text-row {
|
||||
border: 1px solid rgba(168, 140, 107, 0.2);
|
||||
border: 1px solid rgba(var(--c-primary-rgb), 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
background: #fdfbf7;
|
||||
background: var(--c-paper);
|
||||
cursor: pointer;
|
||||
}
|
||||
.hw-text-row.active {
|
||||
border-color: #a88c6b;
|
||||
box-shadow: 0 0 0 1px rgba(168, 140, 107, 0.25);
|
||||
border-color: var(--c-primary);
|
||||
box-shadow: 0 0 0 1px rgba(var(--c-primary-rgb), 0.25);
|
||||
}
|
||||
.hw-color {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
border: 1px solid rgba(168, 140, 107, 0.35);
|
||||
border: 1px solid rgba(var(--c-primary-rgb), 0.35);
|
||||
border-radius: 6px;
|
||||
padding: 2px;
|
||||
background: #fff;
|
||||
@@ -1018,16 +1018,16 @@ defineExpose({ loadList, refresh: loadList })
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid rgba(168, 140, 107, 0.28);
|
||||
border: 1px solid rgba(var(--c-primary-rgb), 0.28);
|
||||
border-radius: 10px;
|
||||
background: #fdfbf7;
|
||||
background: var(--c-paper);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s, box-shadow 0.15s;
|
||||
}
|
||||
.hw-tpl-trigger:hover {
|
||||
border-color: #a88c6b;
|
||||
box-shadow: 0 0 0 1px rgba(168, 140, 107, 0.2);
|
||||
border-color: var(--c-primary);
|
||||
box-shadow: 0 0 0 1px rgba(var(--c-primary-rgb), 0.2);
|
||||
}
|
||||
.hw-tpl-mini {
|
||||
position: relative;
|
||||
@@ -1059,12 +1059,12 @@ defineExpose({ loadList, refresh: loadList })
|
||||
}
|
||||
.hw-tpl-trigger-title {
|
||||
font-size: 13px;
|
||||
color: #2c2c2c;
|
||||
color: var(--c-ink);
|
||||
}
|
||||
.hw-tpl-trigger-desc {
|
||||
margin-top: 2px;
|
||||
font-size: 10px;
|
||||
color: #8a8680;
|
||||
color: var(--c-muted);
|
||||
line-height: 1.35;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
@@ -1072,7 +1072,7 @@ defineExpose({ loadList, refresh: loadList })
|
||||
overflow: hidden;
|
||||
}
|
||||
.hw-tpl-trigger-icon {
|
||||
color: #a88c6b;
|
||||
color: var(--c-primary);
|
||||
font-size: 14px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -1102,7 +1102,7 @@ defineExpose({ loadList, refresh: loadList })
|
||||
}
|
||||
}
|
||||
.hw-tpl-card {
|
||||
border: 1px solid rgba(168, 140, 107, 0.2);
|
||||
border: 1px solid rgba(var(--c-primary-rgb), 0.2);
|
||||
border-radius: 12px;
|
||||
padding: 10px;
|
||||
background: #fff;
|
||||
@@ -1111,12 +1111,12 @@ defineExpose({ loadList, refresh: loadList })
|
||||
transition: border-color 0.15s, box-shadow 0.15s, transform 0.12s;
|
||||
}
|
||||
.hw-tpl-card:hover {
|
||||
border-color: #a88c6b;
|
||||
border-color: var(--c-primary);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.hw-tpl-card.active {
|
||||
border-color: #a88c6b;
|
||||
box-shadow: 0 0 0 2px rgba(168, 140, 107, 0.22);
|
||||
border-color: var(--c-primary);
|
||||
box-shadow: 0 0 0 2px rgba(var(--c-primary-rgb), 0.22);
|
||||
}
|
||||
.hw-tpl-preview {
|
||||
position: relative;
|
||||
@@ -1161,7 +1161,7 @@ defineExpose({ loadList, refresh: loadList })
|
||||
}
|
||||
.hw-tpl-card-label {
|
||||
font-size: 12px;
|
||||
color: #2c2c2c;
|
||||
color: var(--c-ink);
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1186,7 +1186,7 @@ defineExpose({ loadList, refresh: loadList })
|
||||
.hw-tpl-card-desc {
|
||||
margin-top: 2px;
|
||||
font-size: 10px;
|
||||
color: #8a8680;
|
||||
color: var(--c-muted);
|
||||
line-height: 1.35;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="hb-root">
|
||||
<div class="hb-root" :data-hb-theme="cfg.posterTheme || 'redgold'">
|
||||
<div class="hb-bg-decor" aria-hidden="true">
|
||||
<span class="hb-bg-xi hb-bg-xi-1">囍</span>
|
||||
<span class="hb-bg-xi hb-bg-xi-2">囍</span>
|
||||
@@ -168,7 +168,7 @@ import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import html2canvas from 'html2canvas'
|
||||
import { getConfig, getPosterConfig } from '@/api/wedding'
|
||||
import { normalizePosterConfig, posterLayoutMeta, posterSideMeta, resolvePosterLayout, resolvePosterSide } from '@/composables/posterDefaults'
|
||||
import { normalizePosterConfig, posterLayoutMeta, posterSideMeta, posterThemeMeta, resolvePosterLayout, resolvePosterSide } from '@/composables/posterDefaults'
|
||||
import { renderFestiveQr } from '@/composables/renderFestiveQr'
|
||||
import { drawXiSeal } from '@/composables/drawXiSeal'
|
||||
import { useAudio } from '@/composables/useAudio'
|
||||
@@ -457,12 +457,13 @@ async function savePosterImage() {
|
||||
try { await document.fonts?.ready } catch { /* ignore */ }
|
||||
el.querySelectorAll('canvas.sp-xi-mid, canvas.hb-xi-circle').forEach((c) => {
|
||||
const isClassic = c.classList.contains('hb-xi-circle')
|
||||
const theme = posterThemeMeta(cfg.posterTheme)
|
||||
drawXiSeal(c, {
|
||||
size: isClassic ? 22 : 28,
|
||||
fontSize: isClassic ? 11 : 13,
|
||||
color: '#d4af37',
|
||||
borderColor: '#d4af37',
|
||||
fill: isClassic ? 'rgba(155, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.18)',
|
||||
color: theme.sealColor,
|
||||
borderColor: theme.sealColor,
|
||||
fill: isClassic ? theme.sealFill : 'rgba(0, 0, 0, 0.18)',
|
||||
})
|
||||
})
|
||||
// 按元素实际宽度抬升倍率:目标约 1600px 宽,最高 3x,避免手机导出糊成一片
|
||||
@@ -471,7 +472,7 @@ async function savePosterImage() {
|
||||
const canvas = await html2canvas(el, {
|
||||
useCORS: true,
|
||||
allowTaint: true,
|
||||
backgroundColor: '#7a0000',
|
||||
backgroundColor: posterThemeMeta(cfg.posterTheme).shotBg,
|
||||
scale,
|
||||
logging: false,
|
||||
imageTimeout: 20000,
|
||||
@@ -601,20 +602,135 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hb-root {
|
||||
/* ============================================================
|
||||
海报风格模板(5 套皮肤):data-hb-theme 切换整组变量
|
||||
- 面板红分三档 rgb(hi/mid/lo)保留原有明暗层次
|
||||
- 所有模板(竖版/横版)共用这一组变量
|
||||
============================================================ */
|
||||
.hb-root,
|
||||
.hb-root[data-hb-theme='redgold'] {
|
||||
--hb-red: #9b0000;
|
||||
--hb-red-deep: #7a0000;
|
||||
--hb-red-bright: #c41e3a;
|
||||
--hb-gold: #d4af37;
|
||||
--hb-gold-deep: #a8892e;
|
||||
--hb-gold-soft: #f0d78c;
|
||||
--hb-cream: #f7e7c3;
|
||||
--hb-ivory: #f7efe0;
|
||||
--hb-gold-rgb: 212, 175, 55;
|
||||
--hb-gold-soft-rgb: 240, 215, 140;
|
||||
--hb-accent-rgb: 196, 30, 58;
|
||||
--hb-cream-rgb: 243, 230, 200;
|
||||
--hb-ivory-rgb: 247, 239, 224;
|
||||
--hb-panel: #8b0000;
|
||||
--hb-panel-deep: #5c0000;
|
||||
--hb-photo-bg: #c23a2b;
|
||||
--hb-panel-hi-rgb: 145, 5, 5;
|
||||
--hb-panel-rgb: 116, 0, 0;
|
||||
--hb-panel-lo-rgb: 85, 0, 0;
|
||||
--hb-page-hi: #c20808;
|
||||
--hb-page-mid: #b30000;
|
||||
}
|
||||
.hb-root[data-hb-theme='burgundy'] {
|
||||
--hb-red: #6b1d30;
|
||||
--hb-red-deep: #4a1220;
|
||||
--hb-red-bright: #a8465c;
|
||||
--hb-gold: #d9a05b;
|
||||
--hb-gold-deep: #a87840;
|
||||
--hb-gold-soft: #e2b98f;
|
||||
--hb-cream: #ecd9c2;
|
||||
--hb-ivory: #f2e8dc;
|
||||
--hb-gold-rgb: 217, 160, 91;
|
||||
--hb-gold-soft-rgb: 226, 185, 143;
|
||||
--hb-accent-rgb: 168, 70, 92;
|
||||
--hb-cream-rgb: 236, 217, 194;
|
||||
--hb-ivory-rgb: 242, 232, 220;
|
||||
--hb-panel: #5a1728;
|
||||
--hb-panel-deep: #380d18;
|
||||
--hb-photo-bg: #7d3040;
|
||||
--hb-panel-hi-rgb: 108, 32, 50;
|
||||
--hb-panel-rgb: 84, 22, 38;
|
||||
--hb-panel-lo-rgb: 60, 14, 26;
|
||||
--hb-page-hi: #7d2438;
|
||||
--hb-page-mid: #5f1a2a;
|
||||
}
|
||||
.hb-root[data-hb-theme='navygold'] {
|
||||
--hb-red: #1a2f52;
|
||||
--hb-red-deep: #101f38;
|
||||
--hb-red-bright: #4a6a9e;
|
||||
--hb-gold: #d4b060;
|
||||
--hb-gold-deep: #a3833c;
|
||||
--hb-gold-soft: #e8cf96;
|
||||
--hb-cream: #ecdfc0;
|
||||
--hb-ivory: #f0ead8;
|
||||
--hb-gold-rgb: 212, 176, 96;
|
||||
--hb-gold-soft-rgb: 232, 207, 150;
|
||||
--hb-accent-rgb: 74, 106, 158;
|
||||
--hb-cream-rgb: 236, 223, 192;
|
||||
--hb-ivory-rgb: 240, 234, 216;
|
||||
--hb-panel: #16294a;
|
||||
--hb-panel-deep: #0b1526;
|
||||
--hb-photo-bg: #27436d;
|
||||
--hb-panel-hi-rgb: 30, 52, 88;
|
||||
--hb-panel-rgb: 20, 38, 66;
|
||||
--hb-panel-lo-rgb: 12, 24, 44;
|
||||
--hb-page-hi: #24406b;
|
||||
--hb-page-mid: #1a3054;
|
||||
}
|
||||
.hb-root[data-hb-theme='inkjade'] {
|
||||
--hb-red: #1d3a32;
|
||||
--hb-red-deep: #132a24;
|
||||
--hb-red-bright: #4a8a74;
|
||||
--hb-gold: #c9b37e;
|
||||
--hb-gold-deep: #97824f;
|
||||
--hb-gold-soft: #d8c493;
|
||||
--hb-cream: #e6dcc0;
|
||||
--hb-ivory: #eee9d8;
|
||||
--hb-gold-rgb: 201, 179, 126;
|
||||
--hb-gold-soft-rgb: 216, 196, 147;
|
||||
--hb-accent-rgb: 74, 138, 116;
|
||||
--hb-cream-rgb: 230, 220, 192;
|
||||
--hb-ivory-rgb: 238, 233, 216;
|
||||
--hb-panel: #1a342d;
|
||||
--hb-panel-deep: #0e1e19;
|
||||
--hb-photo-bg: #2c4f44;
|
||||
--hb-panel-hi-rgb: 34, 62, 53;
|
||||
--hb-panel-rgb: 24, 46, 39;
|
||||
--hb-panel-lo-rgb: 15, 30, 25;
|
||||
--hb-page-hi: #2a4c40;
|
||||
--hb-page-mid: #203b32;
|
||||
}
|
||||
.hb-root[data-hb-theme='rosewood'] {
|
||||
--hb-red: #7d2c42;
|
||||
--hb-red-deep: #5c1f30;
|
||||
--hb-red-bright: #c05a74;
|
||||
--hb-gold: #e0b487;
|
||||
--hb-gold-deep: #b08a5c;
|
||||
--hb-gold-soft: #f0d3b2;
|
||||
--hb-cream: #f5e4d0;
|
||||
--hb-ivory: #f8eee2;
|
||||
--hb-gold-rgb: 224, 180, 135;
|
||||
--hb-gold-soft-rgb: 240, 211, 178;
|
||||
--hb-accent-rgb: 192, 90, 116;
|
||||
--hb-cream-rgb: 245, 228, 208;
|
||||
--hb-ivory-rgb: 248, 238, 226;
|
||||
--hb-panel: #6e2538;
|
||||
--hb-panel-deep: #451522;
|
||||
--hb-photo-bg: #94445a;
|
||||
--hb-panel-hi-rgb: 130, 46, 68;
|
||||
--hb-panel-rgb: 100, 32, 50;
|
||||
--hb-panel-lo-rgb: 72, 22, 35;
|
||||
--hb-page-hi: #963c56;
|
||||
--hb-page-mid: #762c42;
|
||||
}
|
||||
.hb-root {
|
||||
position: relative;
|
||||
min-height: 100dvh;
|
||||
background:
|
||||
radial-gradient(ellipse at 50% 18%, #c20808 0%, transparent 42%),
|
||||
radial-gradient(circle at 12% 80%, rgba(212, 175, 55, 0.12), transparent 28%),
|
||||
radial-gradient(circle at 88% 75%, rgba(212, 175, 55, 0.1), transparent 26%),
|
||||
radial-gradient(ellipse at 50% 20%, #b30000 0%, var(--hb-red) 45%, var(--hb-red-deep) 100%);
|
||||
radial-gradient(ellipse at 50% 18%, var(--hb-page-hi) 0%, transparent 42%),
|
||||
radial-gradient(circle at 12% 80%, rgba(var(--hb-gold-rgb), 0.12), transparent 28%),
|
||||
radial-gradient(circle at 88% 75%, rgba(var(--hb-gold-rgb), 0.1), transparent 26%),
|
||||
radial-gradient(ellipse at 50% 20%, var(--hb-page-mid) 0%, var(--hb-red) 45%, var(--hb-red-deep) 100%);
|
||||
color: var(--hb-gold);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -667,7 +783,7 @@ onUnmounted(() => {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #f0d78c;
|
||||
color: var(--hb-gold-soft);
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.26em;
|
||||
animation: hb-hint-pulse 1.6s ease-in-out infinite;
|
||||
@@ -676,8 +792,8 @@ onUnmounted(() => {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 999px;
|
||||
background: #f0d78c;
|
||||
box-shadow: 0 0 8px rgba(240, 215, 140, 0.7);
|
||||
background: var(--hb-gold-soft);
|
||||
box-shadow: 0 0 8px rgba(var(--hb-gold-soft-rgb), 0.7);
|
||||
}
|
||||
@keyframes hb-hint-pulse {
|
||||
0%, 100% { opacity: 0.75; }
|
||||
@@ -691,9 +807,9 @@ onUnmounted(() => {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 999px;
|
||||
border: 1.5px solid rgba(212, 175, 55, 0.85);
|
||||
background: linear-gradient(160deg, rgba(212, 175, 55, 0.42), rgba(122, 0, 0, 0.82));
|
||||
color: #f0d78c;
|
||||
border: 1.5px solid rgba(var(--hb-gold-rgb), 0.85);
|
||||
background: linear-gradient(160deg, rgba(var(--hb-gold-rgb), 0.42), rgba(var(--hb-panel-rgb), 0.82));
|
||||
color: var(--hb-gold-soft);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -715,9 +831,9 @@ onUnmounted(() => {
|
||||
}
|
||||
.hb-save-btn {
|
||||
appearance: none;
|
||||
border: 1.5px solid #d4af37;
|
||||
background: linear-gradient(180deg, rgba(212, 175, 55, 0.45), rgba(155, 0, 0, 0.72));
|
||||
color: #f0d78c;
|
||||
border: 1.5px solid var(--hb-gold);
|
||||
background: linear-gradient(180deg, rgba(var(--hb-gold-rgb), 0.45), rgba(var(--hb-panel-hi-rgb), 0.72));
|
||||
color: var(--hb-gold-soft);
|
||||
padding: 10px 16px;
|
||||
border-radius: 999px;
|
||||
letter-spacing: 0.2em;
|
||||
@@ -737,27 +853,27 @@ onUnmounted(() => {
|
||||
.hb-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
|
||||
.hb-bg-decor { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
|
||||
.hb-bg-xi {
|
||||
position: absolute; color: rgba(240, 215, 140, 0.14); font-weight: 700; line-height: 1;
|
||||
position: absolute; color: rgba(var(--hb-gold-soft-rgb), 0.14); font-weight: 700; line-height: 1;
|
||||
user-select: none; animation: hb-pulse-soft 7s ease-in-out infinite;
|
||||
}
|
||||
.hb-bg-xi-1 { top: 6%; left: 4%; font-size: 56px; transform: rotate(-12deg); }
|
||||
.hb-bg-xi-2 { top: 14%; right: 6%; font-size: 42px; transform: rotate(10deg); color: rgba(196, 30, 58, 0.22); animation-delay: 1.2s; }
|
||||
.hb-bg-xi-2 { top: 14%; right: 6%; font-size: 42px; transform: rotate(10deg); color: rgba(var(--hb-accent-rgb), 0.22); animation-delay: 1.2s; }
|
||||
.hb-bg-xi-3 { bottom: 18%; left: 8%; font-size: 48px; transform: rotate(8deg); animation-delay: 2s; }
|
||||
.hb-bg-xi-4 { bottom: 10%; right: 10%; font-size: 64px; transform: rotate(-8deg); color: rgba(196, 30, 58, 0.18); animation-delay: 0.6s; }
|
||||
.hb-bg-xi-4 { bottom: 10%; right: 10%; font-size: 64px; transform: rotate(-8deg); color: rgba(var(--hb-accent-rgb), 0.18); animation-delay: 0.6s; }
|
||||
.hb-bg-xi-5 { top: 42%; left: 2%; font-size: 36px; opacity: 0.8; animation-delay: 3s; }
|
||||
.hb-bg-xi-6 { top: 48%; right: 3%; font-size: 40px; opacity: 0.75; animation-delay: 2.4s; }
|
||||
.hb-bg-knot { position: absolute; color: rgba(212, 175, 55, 0.38); width: 56px; height: 70px; animation: hb-float 10s ease-in-out infinite; }
|
||||
.hb-bg-knot { position: absolute; color: rgba(var(--hb-gold-rgb), 0.38); width: 56px; height: 70px; animation: hb-float 10s ease-in-out infinite; }
|
||||
.hb-bg-knot-1 { top: 7%; left: 12%; width: 52px; height: 66px; }
|
||||
.hb-bg-knot-2 { top: 20%; right: 10%; width: 46px; height: 58px; color: rgba(240, 215, 140, 0.34); animation-delay: 1.8s; }
|
||||
.hb-bg-knot-2 { top: 20%; right: 10%; width: 46px; height: 58px; color: rgba(var(--hb-gold-soft-rgb), 0.34); animation-delay: 1.8s; }
|
||||
.hb-bg-knot-3 { bottom: 18%; left: 8%; width: 50px; height: 64px; animation-delay: 2.6s; }
|
||||
.hb-bg-knot-4 { bottom: 10%; right: 12%; width: 58px; height: 72px; color: rgba(240, 215, 140, 0.3); animation-delay: 1s; }
|
||||
.hb-bg-lantern { position: absolute; color: rgba(240, 215, 140, 0.48); width: 42px; height: 64px; animation: hb-swing 5.5s ease-in-out infinite; }
|
||||
.hb-bg-knot-4 { bottom: 10%; right: 12%; width: 58px; height: 72px; color: rgba(var(--hb-gold-soft-rgb), 0.3); animation-delay: 1s; }
|
||||
.hb-bg-lantern { position: absolute; color: rgba(var(--hb-gold-soft-rgb), 0.48); width: 42px; height: 64px; animation: hb-swing 5.5s ease-in-out infinite; }
|
||||
.hb-bg-lantern-1 { top: 3%; left: 20%; }
|
||||
.hb-bg-lantern-2 { top: 4%; right: 18%; animation-delay: 1.4s; }
|
||||
.hb-bg-spark {
|
||||
position: absolute; width: 6px; height: 6px; border-radius: 50%;
|
||||
background: radial-gradient(circle, var(--hb-gold-soft), transparent 70%);
|
||||
box-shadow: 0 0 10px rgba(240, 215, 140, 0.55); animation: hb-twinkle 2.8s ease-in-out infinite;
|
||||
box-shadow: 0 0 10px rgba(var(--hb-gold-soft-rgb), 0.55); animation: hb-twinkle 2.8s ease-in-out infinite;
|
||||
}
|
||||
.hb-bg-spark-1 { top: 18%; left: 30%; }
|
||||
.hb-bg-spark-2 { top: 28%; right: 28%; animation-delay: 0.7s; }
|
||||
@@ -770,11 +886,11 @@ onUnmounted(() => {
|
||||
.hb-seal {
|
||||
width: 88px; height: 88px; border-radius: 50%; border: 3px solid var(--hb-gold); color: var(--hb-gold);
|
||||
display: flex; align-items: center; justify-content: center; font-size: 42px; font-weight: 700;
|
||||
animation: hb-seal-pop 1s ease both; box-shadow: 0 0 0 6px rgba(212, 175, 55, 0.15);
|
||||
animation: hb-seal-pop 1s ease both; box-shadow: 0 0 0 6px rgba(var(--hb-gold-rgb), 0.15);
|
||||
}
|
||||
.hb-shimmer {
|
||||
font-size: 28px; letter-spacing: 0.35em;
|
||||
background: linear-gradient(90deg, #8a6a1a, var(--hb-gold-soft), #8a6a1a); background-size: 200% 100%;
|
||||
background: linear-gradient(90deg, var(--hb-gold-deep), var(--hb-gold-soft), var(--hb-gold-deep)); background-size: 200% 100%;
|
||||
-webkit-background-clip: text; background-clip: text; color: transparent; animation: hb-shimmer 1.2s linear infinite;
|
||||
}
|
||||
.hb-fade-dot {
|
||||
@@ -784,11 +900,11 @@ onUnmounted(() => {
|
||||
.hb-enter { position: fixed; inset: 0; z-index: 30; pointer-events: none; }
|
||||
.hb-enter--curtain .hb-enter-panel {
|
||||
position: absolute; top: 0; bottom: 0; width: 50%;
|
||||
background: linear-gradient(180deg, #8b0000, #5c0000);
|
||||
background: linear-gradient(180deg, var(--hb-panel), var(--hb-panel-deep));
|
||||
transition: transform 0.95s cubic-bezier(0.65, 0, 0.35, 1);
|
||||
}
|
||||
.hb-enter--curtain .hb-enter-left { left: 0; border-right: 1px solid rgba(212, 175, 55, 0.35); }
|
||||
.hb-enter--curtain .hb-enter-right { right: 0; border-left: 1px solid rgba(212, 175, 55, 0.35); }
|
||||
.hb-enter--curtain .hb-enter-left { left: 0; border-right: 1px solid rgba(var(--hb-gold-rgb), 0.35); }
|
||||
.hb-enter--curtain .hb-enter-right { right: 0; border-left: 1px solid rgba(var(--hb-gold-rgb), 0.35); }
|
||||
.hb-enter--curtain.is-done .hb-enter-left { transform: translateX(-105%); }
|
||||
.hb-enter--curtain.is-done .hb-enter-right { transform: translateX(105%); }
|
||||
.hb-enter--fadeUp .hb-enter-veil,
|
||||
@@ -801,11 +917,11 @@ onUnmounted(() => {
|
||||
.hb-disabled {
|
||||
position: fixed; inset: 0; z-index: 50; display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center; gap: 16px;
|
||||
background: rgba(80, 0, 0, 0.92); color: var(--hb-cream);
|
||||
background: rgba(var(--hb-panel-lo-rgb), 0.92); color: var(--hb-cream);
|
||||
}
|
||||
.hb-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));
|
||||
background: linear-gradient(180deg, rgba(var(--hb-gold-rgb), 0.22), rgba(var(--hb-gold-rgb), 0.08));
|
||||
color: var(--hb-gold-soft); padding: 8px 22px; border-radius: 999px;
|
||||
letter-spacing: 0.28em; font-size: 13px; cursor: pointer;
|
||||
}
|
||||
@@ -832,12 +948,12 @@ onUnmounted(() => {
|
||||
gap: 0;
|
||||
padding: 8px 10px;
|
||||
background:
|
||||
radial-gradient(circle at 50% 0%, rgba(240, 215, 140, 0.28), transparent 55%),
|
||||
linear-gradient(180deg, #9b0000, #6e0000);
|
||||
border: 1.5px solid #d4af37;
|
||||
radial-gradient(circle at 50% 0%, rgba(var(--hb-gold-soft-rgb), 0.28), transparent 55%),
|
||||
linear-gradient(180deg, var(--hb-red), var(--hb-red-deep));
|
||||
border: 1.5px solid var(--hb-gold);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35);
|
||||
color: #f0d78c;
|
||||
color: var(--hb-gold-soft);
|
||||
pointer-events: none;
|
||||
font-family: "Noto Serif SC", "Source Han Serif SC", "Songti SC", "STSong", serif;
|
||||
}
|
||||
@@ -886,15 +1002,15 @@ onUnmounted(() => {
|
||||
[data-hb-capture-qr] .hb-cq-frame {
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
background: linear-gradient(145deg, #fff8e7, #f7e7c3);
|
||||
border: 2px solid #d4af37;
|
||||
background: linear-gradient(145deg, #fff8e7, var(--hb-cream));
|
||||
border: 2px solid var(--hb-gold);
|
||||
border-radius: 8px;
|
||||
}
|
||||
[data-hb-capture-qr] .hb-cq-c {
|
||||
position: absolute;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: #c41e3a;
|
||||
color: var(--hb-red-bright);
|
||||
line-height: 1;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
@@ -913,7 +1029,7 @@ onUnmounted(() => {
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.12em;
|
||||
line-height: 1.35;
|
||||
color: rgba(240, 215, 140, 0.92);
|
||||
color: rgba(var(--hb-gold-soft-rgb), 0.92);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
|
||||
@@ -144,7 +144,7 @@
|
||||
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 { heroFocusCss, posterThemeMeta } from '@/composables/posterDefaults'
|
||||
import { drawXiSeal } from '@/composables/drawXiSeal'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -170,15 +170,19 @@ const nameRight = computed(() => `${props.cfg.daughterInLawLabel || ''}${props.c
|
||||
|
||||
async function paintXiSeal() {
|
||||
try { await document.fonts?.ready } catch { /* ignore */ }
|
||||
// canvas 不解析 CSS 变量,从海报主题元数据取印章色
|
||||
const theme = posterThemeMeta(props.cfg.posterTheme)
|
||||
drawXiSeal(xiSealRef.value, {
|
||||
size: 22,
|
||||
fontSize: 11,
|
||||
color: '#d4af37',
|
||||
borderColor: '#d4af37',
|
||||
fill: 'rgba(155, 0, 0, 0.5)',
|
||||
color: theme.sealColor,
|
||||
borderColor: theme.sealColor,
|
||||
fill: theme.sealFill,
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => props.cfg.posterTheme, () => nextTick(paintXiSeal))
|
||||
|
||||
function later(fn, ms) {
|
||||
const id = setTimeout(fn, ms)
|
||||
timers.push(id)
|
||||
@@ -255,9 +259,9 @@ onBeforeUnmount(() => {
|
||||
border-radius: 10px;
|
||||
padding: 8px;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(212, 175, 55, 0.18), transparent 40%),
|
||||
linear-gradient(180deg, rgba(155, 0, 0, 0.4), rgba(90, 0, 0, 0.25));
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35), inset 0 0 0 1px rgba(240, 215, 140, 0.2);
|
||||
linear-gradient(135deg, rgba(var(--hb-gold-rgb), 0.18), transparent 40%),
|
||||
linear-gradient(180deg, rgba(var(--hb-panel-hi-rgb), 0.4), rgba(var(--hb-panel-lo-rgb), 0.25));
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35), inset 0 0 0 1px rgba(var(--hb-gold-soft-rgb), 0.2);
|
||||
}
|
||||
.hb-frame-ornament {
|
||||
position: absolute;
|
||||
@@ -275,13 +279,13 @@ onBeforeUnmount(() => {
|
||||
.hb-frame-ornament-br { bottom: 2px; right: 2px; transform: scale(-1); }
|
||||
.hb-inner {
|
||||
position: relative;
|
||||
border: 1.5px solid rgba(212, 175, 55, 0.85);
|
||||
border: 1.5px solid rgba(var(--hb-gold-rgb), 0.85);
|
||||
border-radius: 6px;
|
||||
padding: 14px 14px 20px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(130, 0, 0, 0.58), rgba(110, 0, 0, 0.38)),
|
||||
linear-gradient(180deg, rgba(var(--hb-panel-hi-rgb), 0.58), rgba(var(--hb-panel-rgb), 0.38)),
|
||||
radial-gradient(circle at 90% 8%, rgba(255, 255, 255, 0.08), transparent 28%),
|
||||
radial-gradient(circle at 10% 92%, rgba(196, 30, 58, 0.2), transparent 30%);
|
||||
radial-gradient(circle at 10% 92%, rgba(var(--hb-accent-rgb), 0.2), transparent 30%);
|
||||
min-height: min(780px, calc(100dvh - 40px));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -292,7 +296,7 @@ onBeforeUnmount(() => {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 5px;
|
||||
border: 1px solid rgba(240, 215, 140, 0.35);
|
||||
border: 1px solid rgba(var(--hb-gold-soft-rgb), 0.35);
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
@@ -304,28 +308,28 @@ onBeforeUnmount(() => {
|
||||
inset: 10px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
border: 1px dashed rgba(212, 175, 55, 0.18);
|
||||
border: 1px dashed rgba(var(--hb-gold-rgb), 0.18);
|
||||
border-radius: 2px;
|
||||
}
|
||||
.hb-paper-decor { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
|
||||
.hb-paper-watermark {
|
||||
position: absolute; left: 50%; top: 52%; transform: translate(-50%, -50%);
|
||||
font-size: min(220px, 58vw); font-weight: 700; color: rgba(196, 30, 58, 0.1); line-height: 1;
|
||||
font-size: min(220px, 58vw); font-weight: 700; color: rgba(var(--hb-accent-rgb), 0.1); line-height: 1;
|
||||
}
|
||||
.hb-paper-knot { position: absolute; width: 34px; height: 44px; color: rgba(212, 175, 55, 0.55); }
|
||||
.hb-paper-knot { position: absolute; width: 34px; height: 44px; color: rgba(var(--hb-gold-rgb), 0.55); }
|
||||
.hb-paper-knot-tl { top: 10px; left: 10px; }
|
||||
.hb-paper-knot-tr { top: 10px; right: 10px; transform: scaleX(-1); }
|
||||
.hb-paper-knot-bl { bottom: 12px; left: 10px; transform: scaleY(-1); }
|
||||
.hb-paper-knot-br { bottom: 12px; right: 10px; transform: scale(-1); }
|
||||
.hb-paper-cloud { position: absolute; width: 96px; height: 42px; color: rgba(212, 175, 55, 0.32); }
|
||||
.hb-paper-cloud { position: absolute; width: 96px; height: 42px; color: rgba(var(--hb-gold-rgb), 0.32); }
|
||||
.hb-paper-cloud-t { top: 8px; left: 50%; transform: translateX(-50%); }
|
||||
.hb-paper-cloud-b { bottom: 8px; left: 50%; transform: translateX(-50%) scaleY(-1); color: rgba(196, 30, 58, 0.28); }
|
||||
.hb-paper-xi { position: absolute; top: 46%; font-size: 22px; font-weight: 700; color: rgba(196, 30, 58, 0.28); }
|
||||
.hb-paper-cloud-b { bottom: 8px; left: 50%; transform: translateX(-50%) scaleY(-1); color: rgba(var(--hb-accent-rgb), 0.28); }
|
||||
.hb-paper-xi { position: absolute; top: 46%; font-size: 22px; font-weight: 700; color: rgba(var(--hb-accent-rgb), 0.28); }
|
||||
.hb-paper-xi-l { left: 8px; transform: rotate(-18deg); }
|
||||
.hb-paper-xi-r { right: 8px; transform: rotate(18deg); }
|
||||
.hb-paper-dot {
|
||||
position: absolute; width: 5px; height: 5px; border-radius: 50%;
|
||||
background: rgba(212, 175, 55, 0.45); box-shadow: 0 0 0 3px rgba(196, 30, 58, 0.12);
|
||||
background: rgba(var(--hb-gold-rgb), 0.45); box-shadow: 0 0 0 3px rgba(var(--hb-accent-rgb), 0.12);
|
||||
}
|
||||
.hb-paper-dot-1 { top: 22%; left: 14px; }
|
||||
.hb-paper-dot-2 { top: 22%; right: 14px; }
|
||||
@@ -333,11 +337,11 @@ onBeforeUnmount(() => {
|
||||
.hb-paper-dot-4 { bottom: 26%; right: 16px; }
|
||||
.hb-paper-side {
|
||||
position: absolute; top: 28%; bottom: 28%; width: 10px;
|
||||
background: repeating-linear-gradient(180deg, transparent 0 10px, rgba(212, 175, 55, 0.22) 10px 11px, transparent 11px 20px);
|
||||
background: repeating-linear-gradient(180deg, transparent 0 10px, rgba(var(--hb-gold-rgb), 0.22) 10px 11px, transparent 11px 20px);
|
||||
opacity: 0.7;
|
||||
}
|
||||
.hb-paper-side-l { left: 4px; border-left: 1px solid rgba(212, 175, 55, 0.25); }
|
||||
.hb-paper-side-r { right: 4px; border-right: 1px solid rgba(212, 175, 55, 0.25); }
|
||||
.hb-paper-side-l { left: 4px; border-left: 1px solid rgba(var(--hb-gold-rgb), 0.25); }
|
||||
.hb-paper-side-r { right: 4px; border-right: 1px solid rgba(var(--hb-gold-rgb), 0.25); }
|
||||
.hb-hero-wrap, .hb-names, .hb-title-wrap, .hb-body, .hb-footer, .hb-cta { position: relative; z-index: 2; }
|
||||
.hb-hero-wrap {
|
||||
width: 100%; opacity: 0; transform: translateY(12px);
|
||||
@@ -351,10 +355,10 @@ onBeforeUnmount(() => {
|
||||
.hb-hero-img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||
.hb-hero-placeholder {
|
||||
width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;
|
||||
color: rgba(240, 215, 140, 0.55); letter-spacing: 0.3em; font-size: 13px; background: rgba(0, 0, 0, 0.2);
|
||||
color: rgba(var(--hb-gold-soft-rgb), 0.55); letter-spacing: 0.3em; font-size: 13px; background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.hb-xi {
|
||||
position: absolute; top: 10%; font-size: 28px; color: #c41e3a; font-weight: 700;
|
||||
position: absolute; top: 10%; font-size: 28px; color: var(--hb-red-bright); font-weight: 700;
|
||||
opacity: 0.92; pointer-events: none;
|
||||
}
|
||||
.hb-xi-l { left: 8%; }
|
||||
@@ -374,7 +378,7 @@ onBeforeUnmount(() => {
|
||||
.hb-name {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
color: #f7efe0 !important;
|
||||
color: var(--hb-ivory) !important;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.12em;
|
||||
justify-content: center;
|
||||
@@ -409,11 +413,11 @@ onBeforeUnmount(() => {
|
||||
.hb-cursor { color: var(--hb-gold); }
|
||||
.hb-footer { margin-top: 8px; min-height: 24px; opacity: 0; transition: opacity 0.4s ease; }
|
||||
.hb-footer.is-show { opacity: 1; }
|
||||
.hb-footer-text { color: #f7efe0 !important; font-size: 12px; letter-spacing: 0.2em; justify-content: center; }
|
||||
.hb-footer-text { color: var(--hb-ivory) !important; font-size: 12px; letter-spacing: 0.2em; justify-content: center; }
|
||||
.hb-cta { margin-top: 16px; opacity: 0; transform: translateY(8px); animation: hb-cta-in 0.5s ease forwards; }
|
||||
.hb-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));
|
||||
background: linear-gradient(180deg, rgba(var(--hb-gold-rgb), 0.22), rgba(var(--hb-gold-rgb), 0.08));
|
||||
color: var(--hb-gold-soft); padding: 8px 22px; border-radius: 999px;
|
||||
letter-spacing: 0.28em; font-size: 13px; cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@
|
||||
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 { heroFocusCss, posterThemeMeta } from '@/composables/posterDefaults'
|
||||
import { drawXiSeal } from '@/composables/drawXiSeal'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -203,15 +203,19 @@ const roleRight = computed(() => {
|
||||
|
||||
async function paintXiSeal() {
|
||||
try { await document.fonts?.ready } catch { /* ignore */ }
|
||||
// canvas 不解析 CSS 变量,从海报主题元数据取印章色
|
||||
const theme = posterThemeMeta(props.cfg.posterTheme)
|
||||
drawXiSeal(xiSealRef.value, {
|
||||
size: 28,
|
||||
fontSize: 13,
|
||||
color: '#d4af37',
|
||||
borderColor: '#d4af37',
|
||||
color: theme.sealColor,
|
||||
borderColor: theme.sealColor,
|
||||
fill: 'rgba(0, 0, 0, 0.18)',
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => props.cfg.posterTheme, () => nextTick(paintXiSeal))
|
||||
|
||||
function later(fn, ms) {
|
||||
timers.push(setTimeout(fn, ms))
|
||||
}
|
||||
@@ -280,29 +284,29 @@ onBeforeUnmount(() => {
|
||||
.sp-shell-outer {
|
||||
padding: 1px;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(145deg, #f0d78c, #d4af37 45%, #a8892e);
|
||||
background: linear-gradient(145deg, var(--hb-gold-soft), var(--hb-gold) 45%, var(--hb-gold-deep));
|
||||
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);
|
||||
background: var(--hb-red-deep);
|
||||
border: 1px solid rgba(var(--hb-gold-soft-rgb), 0.28);
|
||||
}
|
||||
.sp-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 2px solid #d4af37;
|
||||
border: 2px solid var(--hb-gold);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
min-height: min(720px, calc(100dvh - 48px));
|
||||
background: #8b0000;
|
||||
background: var(--hb-panel);
|
||||
}
|
||||
.sp-card-rim {
|
||||
position: absolute;
|
||||
inset: 5px;
|
||||
border: 1px solid rgba(240, 215, 140, 0.45);
|
||||
border: 1px solid rgba(var(--hb-gold-soft-rgb), 0.45);
|
||||
border-radius: 7px;
|
||||
pointer-events: none;
|
||||
z-index: 6;
|
||||
@@ -311,7 +315,7 @@ onBeforeUnmount(() => {
|
||||
position: absolute;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
color: rgba(240, 215, 140, 0.95);
|
||||
color: rgba(var(--hb-gold-soft-rgb), 0.95);
|
||||
z-index: 7;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -353,7 +357,7 @@ onBeforeUnmount(() => {
|
||||
.sp-left {
|
||||
position: relative;
|
||||
min-height: 260px;
|
||||
background: #c23a2b;
|
||||
background: var(--hb-photo-bg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -369,11 +373,11 @@ onBeforeUnmount(() => {
|
||||
.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;
|
||||
color: rgba(var(--hb-ivory-rgb), 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%);
|
||||
background: linear-gradient(180deg, transparent 40%, rgba(var(--hb-panel-hi-rgb), 0.55) 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -405,7 +409,7 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.sp-slogan {
|
||||
color: #f3e6c8 !important;
|
||||
color: var(--hb-cream) !important;
|
||||
font-size: clamp(36px, 8vw, 52px);
|
||||
letter-spacing: 0.2em;
|
||||
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
|
||||
@@ -413,7 +417,7 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
.sp-subs { display: flex; flex-direction: column; gap: 18px; margin-top: 8px; }
|
||||
.sp-sub {
|
||||
color: rgba(243, 230, 200, 0.88) !important;
|
||||
color: rgba(var(--hb-cream-rgb), 0.88) !important;
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.35em;
|
||||
}
|
||||
@@ -423,7 +427,7 @@ onBeforeUnmount(() => {
|
||||
padding: 22px 20px 18px;
|
||||
background:
|
||||
radial-gradient(circle at 80% 10%, rgba(255, 255, 255, 0.06), transparent 30%),
|
||||
linear-gradient(180deg, #9b0000, #7a0000);
|
||||
linear-gradient(180deg, var(--hb-red), var(--hb-red-deep));
|
||||
color: var(--hb-gold-soft);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -434,7 +438,7 @@ onBeforeUnmount(() => {
|
||||
.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;
|
||||
font-size: 180px; font-weight: 700; color: rgba(var(--hb-accent-rgb), 0.12); line-height: 1;
|
||||
}
|
||||
|
||||
.sp-xi-top {
|
||||
@@ -445,7 +449,7 @@ onBeforeUnmount(() => {
|
||||
.sp-xi-top.is-show { opacity: 1; transform: scale(1); }
|
||||
|
||||
.sp-greeting {
|
||||
color: #f7efe0 !important; font-size: 14px; letter-spacing: 0.2em;
|
||||
color: var(--hb-ivory) !important; font-size: 14px; letter-spacing: 0.2em;
|
||||
margin-bottom: 12px; justify-content: center; position: relative; z-index: 1;
|
||||
}
|
||||
|
||||
@@ -481,7 +485,7 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
.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-role { font-size: 11px; color: rgba(var(--hb-ivory-rgb), 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 {
|
||||
@@ -503,11 +507,11 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
.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;
|
||||
font-size: 12px; letter-spacing: 0.35em; color: rgba(var(--hb-ivory-rgb), 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-meta-sub { font-size: 12px; margin-top: 2px; color: rgba(var(--hb-gold-soft-rgb), 0.75); letter-spacing: 0.15em; }
|
||||
|
||||
.sp-love {
|
||||
position: relative; z-index: 1;
|
||||
@@ -516,13 +520,13 @@ onBeforeUnmount(() => {
|
||||
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-love-text { font-size: 22px; color: var(--hb-cream); }
|
||||
.sp-heart { color: var(--hb-red-bright); 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));
|
||||
background: linear-gradient(180deg, rgba(var(--hb-gold-rgb), 0.22), rgba(var(--hb-gold-rgb), 0.08));
|
||||
color: var(--hb-gold-soft); padding: 7px 18px; border-radius: 999px;
|
||||
letter-spacing: 0.28em; font-size: 12px; cursor: pointer;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user