Celestia 主题收藏卡
一个「AI 生成收藏集」的展示画廊:每条数据自带整套主题(全屏背景渐变 + 卡底 + 文字/强调色), 点顶部导航切换条目,氛围随之整体变奏 —— 深绿田园、宣纸米白、鎏金玄青、星空蓝、烈日红。 玻璃拟态大卡 + 衬线大字 + 宽字距全大写小标签,杂志内页气质。
要点
- 把整套主题当数据:每条数据自带
theme对象(bgMain/cardBg/textMain/textSecondary/accent),组件只负责注入 CSS 变量,换条目 = 换全套配色 - 三层字阶拉开主次:衬线 8rem 缩写大字是唯一主角,标题衬线 2.6rem,其余一律 9–12px 全大写 +
.2em以上宽字距压低存在感 - 玻璃拟态卡用半透明卡底 +
backdrop-filter: blur(24px)+ 1px 微弱边框,同一个边框色复用为 tag 描边 / 分隔线 / 角落装饰 - 切换做成「调色」而非翻页:
body背景 / 文字给.8s过渡、卡内各处.7s,渐变本身不可过渡也不碍事 - 大字背后垫一张细线 SVG 星图(星点 + 连线),配 10s 缓浮动画
源码(折叠)
celestia-collection-demo.html(自包含单页)
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Celestia 主题收藏卡 demo</title>
<!--
celestia-collection-demo.html —— 「前端风格收集」的 Celestia 主题收藏卡演示真身。
吸纳自一段 AI 生成的收藏展示单页(2026-07-09 收录)。原版走 unpkg React +
Babel standalone(JSX)+ Tailwind CDN + Google Fonts + lucide,按本 wiki
自包含规矩(skills/writing/mkdocs-wiki/index.md 第 9 节)改写:
- React 18 UMD + htm 从全站 /vendor/ 引,免 JSX 构建,不走任何 CDN
- Tailwind 工具类翻成下面的语义化 CSS;每条数据的 theme 注入为 CSS 变量
- Playfair Display / Inter 降级为本地字体链(Didot / Georgia、system-ui 兜底)
- 修复原稿贴出时的损坏处:header 左侧品牌区按残留结构重建,底部太阳图标 path 补全
-->
<style>
/* 三层字阶是这个风格的骨架:
大 —— 衬线 8rem 缩写字(唯一视觉主角)
中 —— 衬线 2.6rem 标题
微 —— 9~12px 全大写 + .2em 以上字距(导航/标签/说明,统一压低存在感) */
:root {
--serif: 'Playfair Display', Didot, 'Bodoni MT', Georgia, 'Times New Roman', serif;
--sans: Inter, system-ui, 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
* { box-sizing: border-box; margin: 0; }
body {
font-family: var(--sans);
-webkit-font-smoothing: antialiased;
/* 背景渐变本身不可过渡,但 background-color / color 的 .8s 过渡
足以让主题切换读作"调色"而非"翻页" */
transition: background .8s ease-in-out, color .8s ease-in-out;
}
::selection { background: rgba(255,255,255,.3); }
button { font: inherit; background: none; border: none; color: inherit; cursor: pointer; }
.app { min-height: 100vh; display: flex; flex-direction: column; }
/* ── 顶部:品牌 | 系列信息+导航 | 占位,三栏对称 ── */
.header {
display: grid; grid-template-columns: 12rem 1fr 12rem;
align-items: start; gap: 16px; padding: 24px; z-index: 10;
}
.brand {
display: flex; align-items: center; gap: 10px;
font-size: 12px; letter-spacing: .25em;
}
.series { display: flex; flex-direction: column; align-items: center; text-align: center; }
.series-no { font-size: 10px; letter-spacing: .25em; text-transform: uppercase; opacity: .6; margin-bottom: 4px; }
.series-cat { font-size: 12px; letter-spacing: .2em; text-transform: uppercase; margin-bottom: 22px; }
.nav-tabs {
display: flex; justify-content: center; gap: 8px;
max-width: 100%; overflow-x: auto; padding-bottom: 8px;
scrollbar-width: none;
}
.nav-tabs::-webkit-scrollbar { display: none; }
.nav-btn {
font-size: 10px; letter-spacing: .25em; text-transform: uppercase;
padding: 6px 12px; border-radius: 4px; border: 1px solid transparent;
opacity: .5; white-space: nowrap; transition: all .3s;
}
.nav-btn:hover { opacity: .8; }
.nav-btn.active {
opacity: 1; background: var(--card-border);
border-color: var(--card-border); backdrop-filter: blur(4px);
}
/* ── 中央玻璃卡 ── */
.stage { flex: 1; display: flex; align-items: center; justify-content: center; padding: 12px 24px 24px; z-index: 1; }
.card {
width: 100%; max-width: 1100px; height: 620px;
display: flex; position: relative; overflow: hidden; border-radius: 4px;
background: var(--card-bg); border: 1px solid var(--card-border);
backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px);
box-shadow: 0 25px 50px -12px rgba(0,0,0,.25);
transition: background-color .7s ease-in-out;
}
.corner {
position: absolute; top: 24px; left: 24px; width: 16px; height: 16px;
border-top: 1px solid var(--card-border); border-left: 1px solid var(--card-border); opacity: .5;
}
/* 左半:大字展示区 */
.card-left {
width: 50%; padding: 80px; position: relative;
display: flex; flex-direction: column; align-items: center; justify-content: center;
border-right: 1px solid var(--card-border); transition: border-color .7s;
}
.starmap {
position: absolute; inset: 0; width: 100%; height: 100%;
opacity: .4; pointer-events: none;
animation: subtle-float 10s ease-in-out infinite;
}
@keyframes subtle-float {
0%, 100% { transform: translateY(0) scale(1); }
50% { transform: translateY(-5px) scale(1.02); }
}
.abbr-wrap { z-index: 1; text-align: center; }
.abbr {
font-family: var(--serif); font-weight: 500;
font-size: clamp(6rem, 12vw, 8rem); line-height: 1;
margin-bottom: 24px; filter: drop-shadow(0 4px 3px rgba(0,0,0,.15));
transition: color .7s;
}
.date { font-family: var(--serif); font-style: italic; font-size: 15px; letter-spacing: .08em; color: var(--accent); }
.modify-btn {
position: absolute; bottom: 40px; z-index: 1;
display: flex; align-items: center; gap: 8px;
font-size: 10px; letter-spacing: .2em; text-transform: uppercase;
opacity: .5; transition: opacity .3s;
}
.modify-btn:hover { opacity: 1; }
/* 右半:文案区 */
.card-right { width: 50%; padding: 56px 64px; display: flex; flex-direction: column; justify-content: center; }
.eyebrow {
display: flex; align-items: center; gap: 16px;
font-size: 10px; letter-spacing: .25em; text-transform: uppercase;
color: var(--text-secondary); margin-bottom: 16px;
}
.rule { width: 24px; height: 1px; background: currentColor; opacity: .3; }
.title { font-family: var(--serif); font-weight: 500; font-size: clamp(2.2rem, 4vw, 3rem); line-height: 1.15; margin-bottom: 8px; transition: color .7s; }
.subtitle { font-size: 14px; font-weight: 400; letter-spacing: .25em; color: var(--text-secondary); transition: color .7s; }
.desc { display: grid; gap: 20px; margin: 30px 0 36px; font-size: 13px; line-height: 1.8; color: var(--text-secondary); transition: color .7s; }
.desc p:first-child { opacity: .8; }
.desc p:last-child { opacity: .7; }
.tags { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 40px; }
.tag {
font-size: 9px; letter-spacing: .25em; text-transform: uppercase;
padding: 6px 12px; border-radius: 2px;
border: 1px solid var(--card-border); color: var(--text-secondary);
}
.card-footer { margin-top: auto; border-top: 1px solid var(--card-border); padding-top: 28px; display: grid; gap: 28px; }
.explore-btn {
display: flex; justify-content: space-between; width: 100%; text-align: left;
font-size: 12px; letter-spacing: .2em; text-transform: uppercase;
color: var(--text-main); transition: opacity .3s;
}
.explore-btn:hover { opacity: .7; }
.palette-row { display: flex; align-items: center; justify-content: space-between; }
.micro-label { font-size: 10px; letter-spacing: .25em; text-transform: uppercase; color: var(--text-secondary); }
.palette { display: flex; gap: 12px; }
.swatch {
width: 20px; height: 20px; border-radius: 50%;
box-shadow: 0 1px 2px rgba(0,0,0,.25); cursor: pointer; transition: transform .2s;
}
.swatch:hover { transform: scale(1.15); }
/* 左下角系统图标 */
.sys-icon {
position: fixed; bottom: 24px; left: 24px;
width: 32px; height: 32px; border-radius: 50%; border: 1px solid currentColor;
display: flex; align-items: center; justify-content: center;
color: var(--text-main); opacity: .4; cursor: pointer; transition: opacity .3s;
}
.sys-icon:hover { opacity: 1; }
@media (max-width: 860px) {
.header { grid-template-columns: 1fr; }
.brand, .brand-balance { display: none; }
.card { flex-direction: column; height: auto; }
.card-left { width: 100%; min-height: 380px; padding: 48px; border-right: none; border-bottom: 1px solid var(--card-border); }
.card-right { width: 100%; padding: 40px 32px; }
}
</style>
<script src="/vendor/react.production.min.js"></script>
<script src="/vendor/react-dom.production.min.js"></script>
<script src="/vendor/htm.umd.js"></script>
</head>
<body>
<div id="root"></div>
<script>
const html = htm.bind(React.createElement);
const { useState, useEffect } = React;
/* 风格的核心资产:每条数据自带整套 theme(全屏背景渐变、卡底、文字/强调色),
组件只负责把 theme 注入 CSS 变量 —— 换条目 = 换全套配色。 */
const collectionData = [
{
id: 'taurus',
series: 'SERIES 1/1',
category: 'ZODIAC CLASSICS',
name: 'Taurus',
abbr: 'TA',
date: 'April 20 - May 20',
title: 'Taurus: The Steadfast Earth',
subtitle: '金牛座: 坚韧大地',
descEn: "Grounded in verdant fields, Taurus embodies unwavering strength and a deep appreciation for life's enduring beauty. With a spirit both resilient and sensual, it finds solace in stability and the rich bounty of the earth.",
descZh: '金牛座扎根于翠绿大地,展现坚定不移的力量和对生命永恒之美的深刻欣赏。其精神既坚韧又感性,在稳定和大地丰饶中寻得慰藉。',
tags: ['GROUNDED', 'LUXURIOUS', 'RESILIENT'],
colors: ['#F5EEDB', '#D4B886', '#9E7453', '#7C9385', '#D1C0B1'],
theme: {
bgMain: 'radial-gradient(circle at 30% 30%, #3a5446 0%, #1e2c24 100%)',
cardBg: 'rgba(42, 60, 50, 0.4)',
cardBorder: 'rgba(255, 255, 255, 0.05)',
textMain: '#E8E5D7',
textSecondary: '#8B9B90',
accent: '#9A8C73'
}
},
{
id: 'ram',
series: 'SERIES 2/2',
category: '瑞兽呈祥:十二生肖古风系列',
name: '吉羊如意',
abbr: '吉羊',
date: 'Year of the Ram: Pastoral Whispers',
title: 'Serenity of the Auspicious Ram',
subtitle: '吉羊如意',
descEn: "Embrace the gentle spirit of the auspicious ram amidst serene pastoral landscapes. A design reflecting warmth, contentment, and nature's tranquil embrace.",
descZh: '群山环绕,翠草茵茵,吉羊温顺,寓意万事顺遂。色泽米棕,点缀翠绿,营造祥和宁静之境,满载美好与满足。',
tags: ['GENTLE', 'SERENE', 'CONTENT'],
colors: ['#423930', '#63846C', '#CDB891', '#A69785', '#84796D'],
theme: {
bgMain: 'radial-gradient(circle at top left, #F9F7F1 0%, #EAE2D1 100%)',
cardBg: 'rgba(247, 243, 232, 0.6)',
cardBorder: 'rgba(0, 0, 0, 0.03)',
textMain: '#36312B',
textSecondary: '#8C857B',
accent: '#B0A28E',
abbrColor: '#5A5348' /* 唯一的亮底主题:大字比正文再压一档,避免糊成一团 */
}
},
{
id: 'dragon',
series: 'SERIES 2/2',
category: '瑞兽呈祥:十二生肖古风系列',
name: '祥龙御天',
abbr: '祥龙',
date: 'Epoch of Celestial Majesty',
title: "The Imperial Dragon's Ascent",
subtitle: '祥龙御天',
descEn: "Witness the divine power of the dragon, guardian of the heavens and bringer of prosperity. Its majestic form commands respect, embodying strength and auspicious fortune.",
descZh: '见证神龙之威,乃九天守护与繁荣赐予者。其雄伟之姿尽显尊崇,寓意力量与祥瑞。',
tags: ['REGAL', 'CELESTIAL', 'POWERFUL'],
colors: ['#DEB548', '#408A5F', '#A32929', '#FFFFFF', '#A9A9A9'],
theme: {
bgMain: 'radial-gradient(circle at 70% 30%, #2A2E45 0%, #11111A 100%)',
cardBg: 'rgba(30, 32, 50, 0.5)',
cardBorder: 'rgba(255, 255, 255, 0.05)',
textMain: '#D8B762',
textSecondary: '#7A7E9A',
accent: '#823742'
}
},
{
id: 'aquarius',
series: 'SERIES 1/1',
category: 'ZODIAC CLASSICS',
name: 'Aquarius',
abbr: 'AQ',
date: 'January 20 - February 18',
title: 'Aquarius',
subtitle: '水瓶座',
descEn: "The visionary Water Bearer pours forth knowledge, innovation, and humanitarian spirit. Embracing individuality, Aquarians illuminate paths to a brighter future with their unique brilliance.",
descZh: '宝瓶座,承载智慧与革新的水瓶,倾注人道主义精神。拥抱个性,水瓶座以其独特的才华照亮通往更美好未来的道路。',
tags: ['INNOVATIVE', 'VISIONARY', 'INDEPENDENT'],
colors: ['#E2E8F0', '#60A5FA', '#93C5FD', '#64748B', '#CBD5E1'],
theme: {
bgMain: 'radial-gradient(circle at 20% 80%, #2C3A5A 0%, #15192B 100%)',
cardBg: 'rgba(35, 43, 65, 0.4)',
cardBorder: 'rgba(255, 255, 255, 0.05)',
textMain: '#F1F5F9',
textSecondary: '#8B9DBB',
accent: '#5776A5'
}
},
{
id: 'leo',
series: 'SERIES 1/1',
category: 'ZODIAC CLASSICS',
name: 'Leo',
abbr: 'LE',
date: 'July 23 - August 22',
title: 'Leo',
subtitle: '狮子座',
descEn: "Regal and confident, Leo embodies the sun's radiant energy and generous spirit. Their leadership shines brightly, captivating all with warmth and courage.",
descZh: '狮子座高贵自信,承载着太阳的璀璨能量和慷慨精神。他们的领导力熠熠生辉,以热情和勇气征服一切。',
tags: ['REGAL', 'CONFIDENT', 'PASSIONATE'],
colors: ['#FDE047', '#EA580C', '#450A0A', '#FEF08A', '#D97706'],
theme: {
bgMain: 'radial-gradient(circle at 50% 50%, #8A1A1A 0%, #4A0B0B 100%)',
cardBg: 'rgba(100, 15, 15, 0.3)',
cardBorder: 'rgba(255, 255, 255, 0.1)',
textMain: '#FCD34D',
textSecondary: '#DFA896',
accent: '#B04739'
}
}
];
const StarIcon = () => html`
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
</svg>`;
const AdjustIcon = () => html`
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" />
<path d="M12 2a10 10 0 0 0 0 20z" />
</svg>`;
const SunIcon = () => html`
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="4" />
<path d="M12 2v2" /><path d="M12 20v2" />
<path d="m4.93 4.93 1.41 1.41" /><path d="m17.66 17.66 1.41 1.41" />
<path d="M2 12h2" /><path d="M20 12h2" />
<path d="m6.34 17.66-1.41 1.41" /><path d="m19.07 4.93-1.41 1.41" />
</svg>`;
/* 大字背后的细线星图装饰 */
const StarMap = ({ strokeColor }) => html`
<svg className="starmap" viewBox="0 0 400 400" fill="none">
<path d="M100 150 L300 120" stroke=${strokeColor} strokeWidth="1" />
<path d="M120 280 L280 180" stroke=${strokeColor} strokeWidth="1" />
<path d="M180 100 L180 120" stroke=${strokeColor} strokeWidth="1" />
<path d="M160 110 C 200 80, 250 80, 260 150" stroke=${strokeColor} strokeWidth="0.5" fill="none" />
<circle cx="120" cy="110" r="1.5" fill=${strokeColor} />
<circle cx="310" cy="240" r="2" fill=${strokeColor} opacity="0.5" />
</svg>`;
const App = () => {
const [activeId, setActiveId] = useState(collectionData[0].id);
const data = collectionData.find(d => d.id === activeId);
const t = data.theme;
/* 全屏背景渐变挂在 body 上,卡片外的一切随主题变奏 */
useEffect(() => {
document.body.style.background = t.bgMain;
document.body.style.color = t.textMain;
}, [activeId]);
const themeVars = {
'--text-main': t.textMain,
'--text-secondary': t.textSecondary,
'--accent': t.accent,
'--card-bg': t.cardBg,
'--card-border': t.cardBorder
};
return html`
<div className="app" style=${themeVars}>
<header className="header">
<div className="brand"><${StarIcon} /><span>CELESTIA</span></div>
<div className="series">
<div className="series-no">${data.series}</div>
<div className="series-cat">${data.category}</div>
<nav className="nav-tabs">
${collectionData.map(item => html`
<button key=${item.id}
className=${'nav-btn' + (item.id === activeId ? ' active' : '')}
onClick=${() => setActiveId(item.id)}>${item.name}</button>`)}
</nav>
</div>
<div className="brand-balance"></div>
</header>
<main className="stage">
<div className="card">
<div className="corner"></div>
<div className="card-left">
<${StarMap} strokeColor=${t.accent} />
<div className="abbr-wrap">
<h1 className="abbr" style=${{ color: t.abbrColor || t.textMain }}>${data.abbr}</h1>
<p className="date">${data.date}</p>
</div>
<button className="modify-btn"><${AdjustIcon} /><span>MODIFY DESIGN</span></button>
</div>
<div className="card-right">
<div>
<p className="eyebrow"><span className="rule"></span>AI GENERATED COLLECTION</p>
<h2 className="title">${data.title}</h2>
<h3 className="subtitle">${data.subtitle}</h3>
</div>
<div className="desc">
<p>${data.descEn}</p>
<p>${data.descZh}</p>
</div>
<div className="tags">
${data.tags.map(tag => html`<span key=${tag} className="tag">${tag}</span>`)}
</div>
<div className="card-footer">
<button className="explore-btn"><span>EXPLORE ATTRIBUTES</span><span>→</span></button>
<div className="palette-row">
<span className="micro-label">COLOR SCHEME</span>
<div className="palette">
${data.colors.map((c, i) => html`
<div key=${i} className="swatch" style=${{ backgroundColor: c }} title=${c}></div>`)}
</div>
<span className="micro-label">配色方案</span>
</div>
</div>
</div>
</div>
</main>
<div className="sys-icon"><${SunIcon} /></div>
</div>`;
};
ReactDOM.createRoot(document.getElementById('root')).render(html`<${App} />`);
</script>
</body>
</html>