组件 · COMPONENT
课程卡 CourseCard
综合首页与落地页共用的课程卡:16:9 暗底头图(封面或星座线稿)+ 名称、简介、适合人群、元数据与脚注动作。
登录后的综合首页卡就是基准形态;落地页不再维护变体,只换脚注文案与颜色。
import { CourseCard } from './components/v4/CourseCard.jsx'
const DEMO = {
num: '01',
name: 'Obsidian 知识管理',
intro: '从零搭建一套以你为中心、能用十年的知识管理系统。',
aud: '刚开始整理数字笔记的知识工作者',
cover: null,
art: {
pl: [{ pts: '28,78 84,30 132,62 186,22 214,52', c: 'rgba(255,255,255,.28)', w: '1.2' }],
dt: [
{ x: 28, y: 78, r: 2.4, f: '#FFFFFF' }, { x: 84, y: 30, r: 2.4, f: '#FFFFFF' },
{ x: 132, y: 62, r: 3.2, f: '@' }, { x: 186, y: 22, r: 2.4, f: '#FFFFFF' }, { x: 214, y: 52, r: 2.4, f: '#FFFFFF' },
],
},
}
export function Hub() {
return (
<div style={{ width: 300 }}>
<CourseCard course={DEMO} meta="6 系列 · 67 节" />
</div>
)
}<div style="width: 300px">
<div
class="zzm-hovercard"
style="
border: 1px solid rgba(0, 0, 0, 0.09);
border-radius: 16px;
background: #ffffff;
overflow: hidden;
cursor: pointer;
"
>
<div style="aspect-ratio: 16/9; background: #131316; position: relative; overflow: hidden">
<svg
viewBox="0 0 240 135"
preserveAspectRatio="xMidYMid slice"
style="position: absolute; inset: 0; width: 100%; height: 100%"
>
<polyline
points="28,78 84,30 132,62 186,22 214,52"
fill="none"
stroke="rgba(255,255,255,.28)"
stroke-width="1.2"
stroke-linecap="round"
stroke-linejoin="round"
></polyline>
<circle cx="28" cy="78" r="2.4" fill="#FFFFFF"></circle>
<circle cx="84" cy="30" r="2.4" fill="#FFFFFF"></circle>
<circle cx="132" cy="62" r="3.2" fill="var(--zzm-accent,#A6402F)"></circle>
<circle cx="186" cy="22" r="2.4" fill="#FFFFFF"></circle>
<circle cx="214" cy="52" r="2.4" fill="#FFFFFF"></circle>
</svg>
<div
style="
position: absolute;
top: 10px;
left: 11px;
font-family: var(--zzm-mono);
font-size: 10px;
letter-spacing: 2px;
padding: 3px 7px;
border-radius: 4px;
background: rgba(0, 0, 0, 0.55);
color: rgba(255, 255, 255, 0.82);
"
>
01
</div>
</div>
<div style="padding: 16px 18px 17px">
<div style="font-size: 16px; font-weight: 650">Obsidian 知识管理</div>
<div style="margin-top: 6px; font-size: 13px; line-height: 1.65; color: #72726e">
从零搭建一套以你为中心、能用十年的知识管理系统。
</div>
<div style="margin-top: 10px; font-size: 11.5px; color: #a2a29c">
适合 · 刚开始整理数字笔记的知识工作者
</div>
<div style="margin-top: 13px; display: flex; align-items: center; gap: 8px">
<span style="font-family: var(--zzm-mono); font-size: 10.5px; color: #8b8b85">
6 系列 · 67 节
</span>
<span style="flex: 1"></span>
<span style="font-size: 12px; font-weight: 600; color: #161616; white-space: nowrap">
进入课程 →
</span>
</div>
</div>
</div>
</div>/* tokens.css · 用到的令牌 */
:root {
--zzm-accent: #A6402F;
--zzm-mono: ui-monospace, "SF Mono", Menlo, monospace;
}
/* base.css */
.zzm-v4 .zzm-hovercard { transition: all .16s ease; }
.zzm-v4 .zzm-hovercard:hover {
transform: translateY(-3px);
border-color: rgba(0, 0, 0, .3) !important;
box-shadow: 0 10px 30px rgba(0, 0, 0, .07);
}import { CourseCard } from './components/v4/CourseCard.jsx'
const DEMO = {
num: '01',
name: 'Obsidian 知识管理',
intro: '从零搭建一套以你为中心、能用十年的知识管理系统。',
aud: '刚开始整理数字笔记的知识工作者',
cover: null,
art: {
pl: [{ pts: '28,78 84,30 132,62 186,22 214,52', c: 'rgba(255,255,255,.28)', w: '1.2' }],
dt: [
{ x: 28, y: 78, r: 2.4, f: '#FFFFFF' }, { x: 84, y: 30, r: 2.4, f: '#FFFFFF' },
{ x: 132, y: 62, r: 3.2, f: '@' }, { x: 186, y: 22, r: 2.4, f: '#FFFFFF' }, { x: 214, y: 52, r: 2.4, f: '#FFFFFF' },
],
},
}
export function Landing() {
return (
<div style={{ width: 300 }}>
<CourseCard course={DEMO} meta="6 系列 · 67 节" cta="免费试 3 节 →" ctaColor="var(--zzm-accent,#A6402F)" />
</div>
)
}<div style="width: 300px">
<div
class="zzm-hovercard"
style="
border: 1px solid rgba(0, 0, 0, 0.09);
border-radius: 16px;
background: #ffffff;
overflow: hidden;
cursor: pointer;
"
>
<div style="aspect-ratio: 16/9; background: #131316; position: relative; overflow: hidden">
<svg
viewBox="0 0 240 135"
preserveAspectRatio="xMidYMid slice"
style="position: absolute; inset: 0; width: 100%; height: 100%"
>
<polyline
points="28,78 84,30 132,62 186,22 214,52"
fill="none"
stroke="rgba(255,255,255,.28)"
stroke-width="1.2"
stroke-linecap="round"
stroke-linejoin="round"
></polyline>
<circle cx="28" cy="78" r="2.4" fill="#FFFFFF"></circle>
<circle cx="84" cy="30" r="2.4" fill="#FFFFFF"></circle>
<circle cx="132" cy="62" r="3.2" fill="var(--zzm-accent,#A6402F)"></circle>
<circle cx="186" cy="22" r="2.4" fill="#FFFFFF"></circle>
<circle cx="214" cy="52" r="2.4" fill="#FFFFFF"></circle>
</svg>
<div
style="
position: absolute;
top: 10px;
left: 11px;
font-family: var(--zzm-mono);
font-size: 10px;
letter-spacing: 2px;
padding: 3px 7px;
border-radius: 4px;
background: rgba(0, 0, 0, 0.55);
color: rgba(255, 255, 255, 0.82);
"
>
01
</div>
</div>
<div style="padding: 16px 18px 17px">
<div style="font-size: 16px; font-weight: 650">Obsidian 知识管理</div>
<div style="margin-top: 6px; font-size: 13px; line-height: 1.65; color: #72726e">
从零搭建一套以你为中心、能用十年的知识管理系统。
</div>
<div style="margin-top: 10px; font-size: 11.5px; color: #a2a29c">
适合 · 刚开始整理数字笔记的知识工作者
</div>
<div style="margin-top: 13px; display: flex; align-items: center; gap: 8px">
<span style="font-family: var(--zzm-mono); font-size: 10.5px; color: #8b8b85">
6 系列 · 67 节
</span>
<span style="flex: 1"></span>
<span
style="
font-size: 12px;
font-weight: 600;
color: var(--zzm-accent, #a6402f);
white-space: nowrap;
"
>
免费试 3 节 →
</span>
</div>
</div>
</div>
</div>/* tokens.css · 用到的令牌 */
:root {
--zzm-accent: #A6402F;
--zzm-mono: ui-monospace, "SF Mono", Menlo, monospace;
}
/* base.css */
.zzm-v4 .zzm-hovercard { transition: all .16s ease; }
.zzm-v4 .zzm-hovercard:hover {
transform: translateY(-3px);
border-color: rgba(0, 0, 0, .3) !important;
box-shadow: 0 10px 30px rgba(0, 0, 0, .07);
}import { CourseCard } from './components/v4/CourseCard.jsx'
export function Loading() {
return (
<div style={{ width: 300 }}>
<CourseCard loading />
</div>
)
}<div style="width: 300px">
<div
style="
border: 1px solid rgba(0, 0, 0, 0.09);
border-radius: 16px;
background: #ffffff;
overflow: hidden;
"
>
<div
style="aspect-ratio: 16/9; background: #131316; position: relative; overflow: hidden"
></div>
<div style="padding: 16px 18px 17px">
<div class="zzm-skel" style="width: 62%; height: 16px; border-radius: 8px"></div>
<div
class="zzm-skel"
style="width: 92%; height: 13px; border-radius: 8px; margin-top: 6px"
></div>
<div
class="zzm-skel"
style="width: 40%; height: 11.5px; border-radius: 8px; margin-top: 10px"
></div>
<div style="margin-top: 13px; display: flex; align-items: center; gap: 8px">
<div class="zzm-skel" style="width: 86px; height: 10.5px; border-radius: 8px"></div>
<span style="flex: 1"></span>
<div class="zzm-skel" style="width: 58px; height: 12px; border-radius: 8px"></div>
</div>
</div>
</div>
</div>/* base.css */
.zzm-v4 .zzm-skel {
position: relative;
overflow: hidden;
background: #EFEFEC;
border-radius: 8px;
}
.zzm-v4 .zzm-skel::after {
content: '';
position: absolute;
inset: 0;
transform: translateX(-100%);
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .65), transparent);
animation: zzmShimmer 1.4s ease infinite;
}
/* keyframes */
@keyframes zzmShimmer { 100% { transform: translateX(100%) } }属性
| 属性 | 取值 | 默认 | 说明 |
|---|---|---|---|
| course | { num, name, intro, aud, cover?, art: { pl, dt } } | — | art 是星座线稿:pl 折线、dt 圆点;颜色 "@" 表示品牌朱 |
| meta | string | — | 左下 mono 元数据,如「6 系列 · 67 节」 |
| cta | string | '进入课程 →' | 右下动作文案 |
| ctaColor | string | '#161616' | 动作文案颜色 |
| onClick | () => void | — | 整卡点击 |
| loading | boolean | false | 骨架态(同一段布局) |
铁律
✓ 要封面按上传原图呈现,不叠蒙层
✗ 不要为了一行小字压暗整幅封面
✓ 要编号用深色小底自持可读性
✗ 不要用 textShadow 救浅字——白底海报上一样糊