5 changed files with 344 additions and 0 deletions
-
22src/components/SongDisclaimer.vue
-
105src/components/SongLoading.vue
-
30src/components/SongLogo.vue
-
105src/components/SongScrim.vue
-
82src/components/SongSeal.vue
@ -0,0 +1,22 @@ |
|||||
|
<script setup> |
||||
|
/* SongDisclaimer — 瘦金体版权免责声明 §3.5 */ |
||||
|
defineProps({ |
||||
|
text: { type: String, default: '' } |
||||
|
}) |
||||
|
defineOptions({ name: 'SongDisclaimer' }) |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<p class="song-disclaimer caption"> |
||||
|
{{ text || '本页面中的瘦金体风格标识为艺术字图形,参考宋徽宗瘦金体书风自行绘制的 SVG 作品;未被授权的瘦金体字库文件不得用于任何商业用途。如需商业使用宋徽宗瘦金体风格字体,请联系相应字库权利人获取授权。' }} |
||||
|
</p> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped> |
||||
|
.song-disclaimer { |
||||
|
margin: var(--song-space-4) 0 0; |
||||
|
color: var(--song-text-disabled); |
||||
|
line-height: 1.7; |
||||
|
opacity: 0.9; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,105 @@ |
|||||
|
<script setup> |
||||
|
/* SongLoading — 水墨晕染加载 §7 |
||||
|
* 墨点如滴入宣纸般扩散、敛去,合成层动效(transform/opacity only) |
||||
|
* mask: 全屏遮罩模式(弹层加载) */ |
||||
|
defineProps({ |
||||
|
visible: { type: Boolean, default: true }, |
||||
|
mask: { type: Boolean, default: false }, |
||||
|
text: { type: String, default: '墨迹未干' } |
||||
|
}) |
||||
|
defineOptions({ name: 'SongLoading' }) |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div v-if="visible" class="song-loading" :class="{ 'song-loading--mask': mask }"> |
||||
|
<div class="song-loading__inner"> |
||||
|
<div class="song-loading__inks" aria-hidden="true"> |
||||
|
<i class="song-loading__ink" /> |
||||
|
<i class="song-loading__ink" /> |
||||
|
<i class="song-loading__ink" /> |
||||
|
</div> |
||||
|
<div class="song-loading__abs" aria-hidden="true" /> |
||||
|
<span v-if="text" class="song-loading__text caption">{{ text }}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped> |
||||
|
.song-loading { |
||||
|
display: grid; |
||||
|
place-items: center; |
||||
|
padding: 40px 0; |
||||
|
} |
||||
|
.song-loading--mask { |
||||
|
position: fixed; |
||||
|
inset: 0; |
||||
|
z-index: 950; |
||||
|
background: var(--song-bg-elevated); |
||||
|
opacity: 0.96; |
||||
|
} |
||||
|
.song-loading__inner { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
gap: 18px; |
||||
|
} |
||||
|
.song-loading__inks { |
||||
|
position: relative; |
||||
|
width: 64px; |
||||
|
height: 64px; |
||||
|
} |
||||
|
.song-loading__ink { |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
top: 50%; |
||||
|
width: 40px; |
||||
|
height: 40px; |
||||
|
border-radius: 50%; |
||||
|
background: radial-gradient(circle, var(--song-primary) 0%, rgba(79, 120, 184, 0) 68%); |
||||
|
opacity: 0; |
||||
|
transform: translate(-50%, -50%) scale(0.3); |
||||
|
animation: song-ink-soak 2.4s var(--song-ease) infinite; |
||||
|
} |
||||
|
.song-loading__ink:nth-child(2) { |
||||
|
animation-delay: 0.8s; |
||||
|
} |
||||
|
.song-loading__ink:nth-child(3) { |
||||
|
animation-delay: 1.6s; |
||||
|
} |
||||
|
.song-loading__abs { |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
top: 50%; |
||||
|
width: 52px; |
||||
|
height: 52px; |
||||
|
margin: -26px 0 0 -26px; |
||||
|
border: 1px solid var(--song-border-strong); |
||||
|
border-radius: 50%; |
||||
|
} |
||||
|
.song-loading__text { |
||||
|
letter-spacing: 0.4em; |
||||
|
color: var(--song-text-secondary); |
||||
|
} |
||||
|
|
||||
|
@keyframes song-ink-soak { |
||||
|
0% { |
||||
|
transform: translate(-50%, -50%) scale(0.3); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
35% { |
||||
|
opacity: 0.85; |
||||
|
} |
||||
|
80% { |
||||
|
opacity: 0; |
||||
|
} |
||||
|
100% { |
||||
|
transform: translate(-50%, -50%) scale(1.15); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 合成层约束 §7 */ |
||||
|
.song-loading__ink { |
||||
|
will-change: transform, opacity; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<script setup> |
||||
|
/* SongLogo — 品牌字为自行绘制的 SVG 图形作品,瘦金体风格,非字库文件。 |
||||
|
* 契约 §3.5:仅作图形装饰,禁止作为段落排版字体。 */ |
||||
|
defineProps({ |
||||
|
size: { type: Number, default: 40 } |
||||
|
}) |
||||
|
defineOptions({ name: 'SongLogo' }) |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<svg class="song-logo" :width="size" :height="size" viewBox="0 0 48 48" role="img" aria-label="檐下"> |
||||
|
<!-- 印章外框:细线方印 --> |
||||
|
<rect x="2" y="2" width="44" height="44" fill="none" stroke="currentColor" stroke-width="1.6" /> |
||||
|
<!-- 檐(屋檐起翘意象):长短撇掠,瘦劲屈铁 --> |
||||
|
<path d="M12 16 L36 10 L22 14 L12 16 M14 17 L16 8 M28 18 L31 9" fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="2.2" /> |
||||
|
<!-- 下(石柱/直棂意象):>_ 结构感 --> |
||||
|
<path d="M16 28 L18 40 M32 28 L30 40 M12 30 L36 30 M18 37 L30 37 M21 34 L27 34" fill="none" stroke="currentColor" stroke-width="2" /> |
||||
|
<path d="M20 26 L24 22 L28 26" fill="none" stroke="currentColor" stroke-width="1.8" /> |
||||
|
</svg> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped> |
||||
|
.song-logo { |
||||
|
color: var(--song-primary); |
||||
|
display: block; |
||||
|
} |
||||
|
:root[data-theme='dark'] .song-logo { |
||||
|
color: var(--song-primary); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,105 @@ |
|||||
|
<script setup> |
||||
|
/* SongScrim — 纹样底纹合成器 §5 |
||||
|
* 纹样均以 SVG 几何化矢量、低占比,置于内容之下。 |
||||
|
* pattern: ice(冰裂纹) | blind(直棂窗) | tortoise(龟背纹) | vine(缠枝纹) |
||||
|
*/ |
||||
|
defineProps({ |
||||
|
pattern: { type: String, default: 'ice' }, |
||||
|
opacity: { type: Number, default: 0.55 }, |
||||
|
box: { type: Boolean, default: false } |
||||
|
}) |
||||
|
defineOptions({ name: 'SongScrim' }) |
||||
|
|
||||
|
const PATTERNS = { |
||||
|
ice: ` |
||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="220" height="220" viewBox="0 0 220 220"> |
||||
|
<g fill="none" stroke="#6B7A86" stroke-width="1"> |
||||
|
<path d="M0 40 40 0 M60 0 0 60 M40 40 90 90 M90 90 110 70 M0 90 90 0"/> |
||||
|
<path d="M120 0 90 30 M0 120 30 90 M110 110 60 160 M160 60 110 110 M90 90 160 160"/> |
||||
|
<path d="M220 40 180 0 M180 180 220 220 M40 220 0 180 M180 180 160 200 M200 160 180 180"/> |
||||
|
</g> |
||||
|
</svg>`, |
||||
|
blind: ` |
||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 120 120"> |
||||
|
<g fill="none" stroke="#6B7A86" stroke-width="1"> |
||||
|
<path d="M0 0v120 M20 0v120 M40 0v120 M60 0v120 M80 0v120 M100 0v120 M120 0v120"/> |
||||
|
</g> |
||||
|
</svg>`, |
||||
|
tortoise: ` |
||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 120 120"> |
||||
|
<g fill="none" stroke="#6B7A86" stroke-width="1"> |
||||
|
<path d="M30 8 60 21 30 34Z M60 21 90 8 90 34Z"/> |
||||
|
<path d="M30 34 60 60 30 86Z M60 60 90 34 90 86Z"/> |
||||
|
<path d="M30 8 30 60 0 38Z M90 8 90 60 120 38Z"/> |
||||
|
<path d="M30 86 0 64 0 92Z M90 86 120 64 120 92Z"/> |
||||
|
</g> |
||||
|
</svg>`, |
||||
|
vine: ` |
||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="60" viewBox="0 0 120 60"> |
||||
|
<g fill="none" stroke="#6B7A86" stroke-width="1"> |
||||
|
<path d="M0 30 Q15 10 30 30 T60 30 T90 30 T120 30"/> |
||||
|
<path d="M10 45 q0 -12 0 -24 M30 45 30 15 M50 45 50 15 M70 45 70 15 M90 45 90 15 M110 45 110 15"/> |
||||
|
</g> |
||||
|
</svg>`, |
||||
|
water: ` |
||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 120 120"> |
||||
|
<g fill="none" stroke="#6B7A86" stroke-width="1"> |
||||
|
<path d="M20 40 q10 -8 20 0 q10 8 20 0 q10 -8 20 0 q10 8 20 0"/> |
||||
|
<path d="M10 64 q10 -8 20 0 q10 8 20 0 q10 -8 20 0 q10 8 20 0 q10 -8 20 0"/> |
||||
|
<path d="M30 88 q10 -8 20 0 q10 8 20 0 q10 -8 20 0"/> |
||||
|
<path d="M5 16 q10 -8 20 0 q10 8 20 0 q10 -8 20 0 q10 8 20 0 q10 -8 20 0 q10 8 20 0"/> |
||||
|
</g> |
||||
|
</svg>`, |
||||
|
cloud: ` |
||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 120 120"> |
||||
|
<g fill="none" stroke="#6B7A86" stroke-width="1"> |
||||
|
<path d="M10 30 C10 12 30 8 34 24 C38 6 62 10 58 28 C78 18 90 34 76 46 C96 46 96 66 72 62 C82 78 60 88 54 74 C52 92 30 92 28 74 C12 84 2 68 18 60 C0 54 2 38 22 44 C14 34 16 18 30 24 C22 26 14 26 10 30 Z"/> |
||||
|
</g> |
||||
|
</svg>` |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div class="song-scrim" :class="{ 'song-scrim--box': box }" aria-hidden="true"> |
||||
|
<div |
||||
|
v-for="i in 4" |
||||
|
:key="i" |
||||
|
class="song-scrim__tile" |
||||
|
v-html="PATTERNS[pattern]" |
||||
|
:style="{ opacity }" |
||||
|
/> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped> |
||||
|
:global(.song-scrim) { |
||||
|
position: fixed; |
||||
|
inset: 0; |
||||
|
z-index: 0; |
||||
|
pointer-events: none; |
||||
|
overflow: hidden; |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(2, 1fr); |
||||
|
grid-template-rows: repeat(2, 1fr); |
||||
|
opacity: 0.9; |
||||
|
} |
||||
|
.song-scrim--box { |
||||
|
position: absolute !important; |
||||
|
border-radius: var(--song-radius-l); |
||||
|
} |
||||
|
.song-scrim--box :deep(svg) { |
||||
|
filter: opacity(0.55); |
||||
|
} |
||||
|
.song-scrim__tile { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
mix-blend-mode: multiply; |
||||
|
} |
||||
|
.song-scrim__tile :deep(svg) { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
:global([data-theme='dark']) .song-scrim__tile { |
||||
|
mix-blend-mode: screen; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,82 @@ |
|||||
|
<script setup> |
||||
|
/* SongSeal — 章印(钤印意象),SVG 自绘图形。 |
||||
|
* 方印 ≤4 字(田字排布),圆印单字(连珠双圈)。 |
||||
|
* color 默认 --song-seal(印泥砂红),rotate 模拟手钤微妙倾斜。 */ |
||||
|
import { computed } from 'vue' |
||||
|
const props = defineProps({ |
||||
|
text: { type: String, default: '檐下' }, |
||||
|
round: { type: Boolean, default: false }, |
||||
|
size: { type: Number, default: 76 }, |
||||
|
rotate: { type: Number, default: -2 } |
||||
|
}) |
||||
|
defineOptions({ name: 'SongSeal' }) |
||||
|
|
||||
|
const chars = computed(() => props.text.slice(0, props.round ? 1 : 4).split('')) |
||||
|
const fd = computed(() => { |
||||
|
const n = chars.value.length |
||||
|
return n <= 1 ? 1 : n === 2 ? [0, 1] : [0, 1, 2, 3] |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<svg |
||||
|
class="song-seal" |
||||
|
:width="size" |
||||
|
:height="size" |
||||
|
:viewBox="round ? '0 0 64 64' : '0 0 72 72'" |
||||
|
role="img" |
||||
|
:aria-label="`印章:${text}`" |
||||
|
:style="{ transform: `rotate(${rotate}deg)` }" |
||||
|
> |
||||
|
<!-- 方印:田字界格 + 2×2 文字 --> |
||||
|
<g v-if="!round" fill="none" stroke="currentColor"> |
||||
|
<rect x="2" y="2" width="68" height="68" rx="3" /> |
||||
|
<line v-if="fd.length > 1" x1="36" y1="2" x2="36" y2="70" /> |
||||
|
<line v-if="fd.length > 2" x1="2" y1="36" x2="70" y2="36" /> |
||||
|
</g> |
||||
|
<!-- 圆印:连珠双圈 + 单字 --> |
||||
|
<g v-else fill="none" stroke="currentColor"> |
||||
|
<circle cx="32" cy="32" r="28" /> |
||||
|
<circle cx="32" cy="32" r="21" /> |
||||
|
</g> |
||||
|
<g |
||||
|
v-if="!round" |
||||
|
fill="currentColor" |
||||
|
fill-opacity="0.88" |
||||
|
font-family="var(--song-font-serif)" |
||||
|
font-weight="600" |
||||
|
text-anchor="middle" |
||||
|
dominant-baseline="central" |
||||
|
> |
||||
|
<text v-for="(c, i) in chars" :key="i" |
||||
|
:x="36 + (i % 2 === 0 ? -18 : 18)" |
||||
|
:y="36 + (i < 2 ? -18 : 18)" |
||||
|
opacity="0.72" |
||||
|
>{{ c }}</text> |
||||
|
</g> |
||||
|
<text |
||||
|
v-else |
||||
|
x="32" y="32" |
||||
|
fill="currentColor" |
||||
|
fill-opacity="0.88" |
||||
|
font-family="var(--song-font-serif)" |
||||
|
font-weight="600" |
||||
|
font-size="22" |
||||
|
text-anchor="middle" |
||||
|
dominant-baseline="central" |
||||
|
>{{ chars[0] }}</text> |
||||
|
</svg> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped> |
||||
|
.song-seal { |
||||
|
color: var(--song-seal, #a84635); |
||||
|
display: block; |
||||
|
opacity: 0.9; |
||||
|
transition: transform var(--song-dur-2) var(--song-ease); |
||||
|
} |
||||
|
.song-seal:hover { |
||||
|
opacity: 1; |
||||
|
transform: rotate(-1deg) !important; |
||||
|
} |
||||
|
</style> |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue