---
title: 课节卡 LessonCard
url: https://ui.zhaozimin.cn/components/lesson-card/
markdown: https://ui.zhaozimin.cn/components/lesson-card.md
group: 组件
origin: 设计系统源码（src/system），本仓库是唯一源头
source:
  - src/system/screens/v4/course/LessonCard.jsx  # https://ui.zhaozimin.cn/src/system/screens/v4/course/LessonCard.jsx
  - src/system/screens/v4/course/TierPill.jsx  # https://ui.zhaozimin.cn/src/system/screens/v4/course/TierPill.jsx
version: v0.2.0  # 设计系统版本，发布于 2026-09-23
scope: .zzm-v4   # 根节点必须带这个类，否则样式不生效
css: https://ui.zhaozimin.cn/v/0.2.0/zzm.css   # 锁版本地址，本仓库再改也不影响你
---

# 课节卡 LessonCard

> 一节课：16:9 暗底缩略图，四角是编号、档位角标、时长；未解锁时整卡压暗并居中一把锁；看完后整卡朱染。

### 样张 · 已完成

整卡朱染 + 标题行「✓ 已完成」

React：

```jsx
import { LessonCard } from './screens/v4/course/LessonCard.jsx'

// 课节位置数组：[标题, 时长, 简介, md, 免费, 源, uid, 课节ID, 行数据]
const lesson = (id, title, level) => [title, '15:32', '让想法第一次连起来。', '', level < 2 ? 1 : 0, '', '', id, { cover_url: null, required_level: level }]
const OPEN = { canWatchCourse: () => true, canWatchLesson: () => true }
const GUEST = { canWatchCourse: () => false, canWatchLesson: (a) => (a[8]?.required_level ?? 0) < 2 }

export function Done() {
  return (
    <div style={{ width: 280 }}>
      <LessonCard si={0} li={3} a={lesson('l-4', '双链与反向链接', 0)} plan={OPEN} done tierTag />
    </div>
  )
}
```

HTML（构建时由上面的 React 渲染得到，可直接粘贴）：

```html
<div style="width: 280px">
  <div class="zzm-cardlink zzm-cardlink--done">
    <div
      style="
        aspect-ratio: 16/9;
        border-radius: 12px;
        background: #131316;
        overflow: hidden;
        position: relative;
      "
    >
      <div
        style="
          position: absolute;
          inset: 0;
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
          gap: 9px;
        "
      >
        <svg
          viewBox="0 0 240 135"
          style="position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0.5"
        >
          <polyline
            points="18,110 62,38 120,84 178,30 224,96"
            fill="none"
            stroke="rgba(255,255,255,.09)"
            stroke-width="1"
          ></polyline>
          <circle cx="62" cy="38" r="2" fill="rgba(255,255,255,.18)"></circle>
          <circle cx="120" cy="84" r="2" fill="rgba(255,255,255,.18)"></circle>
          <circle cx="178" cy="30" r="2" fill="rgba(255,255,255,.18)"></circle>
        </svg>
        <div
          style="
            position: relative;
            font-size: 14px;
            font-weight: 600;
            color: #ffffff;
            letter-spacing: 0.4px;
            padding: 0 18px;
            text-align: center;
          "
        >
          双链与反向链接
        </div>
        <div
          style="
            position: relative;
            width: 18px;
            height: 2px;
            background: var(--zzm-accent, #a6402f);
          "
        ></div>
      </div>
      <div
        style="
          position: absolute;
          top: 8px;
          left: 9px;
          font-family: var(--zzm-mono);
          font-size: 9.5px;
          letter-spacing: 1px;
          padding: 3px 6px;
          border-radius: 4px;
          background: rgba(0, 0, 0, 0.55);
          color: rgba(255, 255, 255, 0.82);
        "
      >
        S1 · E04
      </div>
      <div
        style="
          position: absolute;
          bottom: 8px;
          right: 9px;
          font-family: var(--zzm-mono);
          font-size: 10.5px;
          padding: 3px 6px;
          border-radius: 4px;
          background: rgba(0, 0, 0, 0.62);
          color: rgba(255, 255, 255, 0.88);
        "
      >
        15:32
      </div>
      <div style="position: absolute; top: 8px; right: 9px; display: flex; gap: 5px">
        <span
          style="
            font-size: 9.5px;
            font-weight: 600;
            letter-spacing: 1px;
            padding: 3px 7px;
            border-radius: 4px;
            background: transparent;
            color: transparent;
            opacity: 0;
          "
        >
          试看
        </span>
      </div>
    </div>
    <div style="margin: 11px 2px 3px; display: flex; align-items: flex-start; gap: 7px">
      <span style="flex: 1; min-width: 0; font-size: 14px; font-weight: 600; line-height: 1.45">
        <span
          class="zzm-pill-shine"
          style="
            display: inline-block;
            vertical-align: middle;
            font-size: 10px;
            font-weight: 600;
            letter-spacing: 0.5px;
            padding: 2px 7px;
            border-radius: 4px;
            background: var(--zzm-tier-free-bg);
            color: var(--zzm-tier-free-ink);
            --zzm-shine-delay: 2.0999999999999996s;
            margin-right: 6px;
          "
        >
          免费
        </span>
        双链与反向链接
      </span>
      <span
        style="
          flex: none;
          margin-top: 2px;
          font-size: 9.5px;
          font-weight: 600;
          letter-spacing: 1px;
          padding: 3px 7px;
          border-radius: 4px;
          background: rgba(166, 64, 47, 0.08);
          color: var(--zzm-accent, #a6402f);
        "
      >
        ✓ 已完成
      </span>
    </div>
    <div
      style="
        margin: 0 2px;
        font-size: 12.5px;
        color: #8b8b85;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      "
    >
      让想法第一次连起来。
    </div>
  </div>
</div>
```

### 样张 · 试看

免费课节对未购者显朱色「试看」角标

React：

```jsx
import { LessonCard } from './screens/v4/course/LessonCard.jsx'

// 课节位置数组：[标题, 时长, 简介, md, 免费, 源, uid, 课节ID, 行数据]
const lesson = (id, title, level) => [title, '15:32', '让想法第一次连起来。', '', level < 2 ? 1 : 0, '', '', id, { cover_url: null, required_level: level }]
const OPEN = { canWatchCourse: () => true, canWatchLesson: () => true }
const GUEST = { canWatchCourse: () => false, canWatchLesson: (a) => (a[8]?.required_level ?? 0) < 2 }

export function Trial() {
  return (
    <div style={{ width: 280 }}>
      <LessonCard si={0} li={0} a={lesson('l-1', '为什么是 Obsidian', 0)} plan={GUEST} done={false} tierTag />
    </div>
  )
}
```

HTML（构建时由上面的 React 渲染得到，可直接粘贴）：

```html
<div style="width: 280px">
  <div class="zzm-cardlink">
    <div
      style="
        aspect-ratio: 16/9;
        border-radius: 12px;
        background: #131316;
        overflow: hidden;
        position: relative;
      "
    >
      <div
        style="
          position: absolute;
          inset: 0;
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
          gap: 9px;
        "
      >
        <svg
          viewBox="0 0 240 135"
          style="position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0.5"
        >
          <polyline
            points="18,110 62,38 120,84 178,30 224,96"
            fill="none"
            stroke="rgba(255,255,255,.09)"
            stroke-width="1"
          ></polyline>
          <circle cx="62" cy="38" r="2" fill="rgba(255,255,255,.18)"></circle>
          <circle cx="120" cy="84" r="2" fill="rgba(255,255,255,.18)"></circle>
          <circle cx="178" cy="30" r="2" fill="rgba(255,255,255,.18)"></circle>
        </svg>
        <div
          style="
            position: relative;
            font-size: 14px;
            font-weight: 600;
            color: #ffffff;
            letter-spacing: 0.4px;
            padding: 0 18px;
            text-align: center;
          "
        >
          为什么是 Obsidia…
        </div>
        <div
          style="
            position: relative;
            width: 18px;
            height: 2px;
            background: var(--zzm-accent, #a6402f);
          "
        ></div>
      </div>
      <div
        style="
          position: absolute;
          top: 8px;
          left: 9px;
          font-family: var(--zzm-mono);
          font-size: 9.5px;
          letter-spacing: 1px;
          padding: 3px 6px;
          border-radius: 4px;
          background: rgba(0, 0, 0, 0.55);
          color: rgba(255, 255, 255, 0.82);
        "
      >
        S1 · E01
      </div>
      <div
        style="
          position: absolute;
          bottom: 8px;
          right: 9px;
          font-family: var(--zzm-mono);
          font-size: 10.5px;
          padding: 3px 6px;
          border-radius: 4px;
          background: rgba(0, 0, 0, 0.62);
          color: rgba(255, 255, 255, 0.88);
        "
      >
        15:32
      </div>
      <div style="position: absolute; top: 8px; right: 9px; display: flex; gap: 5px">
        <span
          style="
            font-size: 9.5px;
            font-weight: 600;
            letter-spacing: 1px;
            padding: 3px 7px;
            border-radius: 4px;
            background: var(--zzm-accent, #a6402f);
            color: #ffffff;
            opacity: 1;
          "
        >
          试看
        </span>
      </div>
    </div>
    <div style="margin: 11px 2px 3px; display: flex; align-items: flex-start; gap: 7px">
      <span style="flex: 1; min-width: 0; font-size: 14px; font-weight: 600; line-height: 1.45">
        <span
          class="zzm-pill-shine"
          style="
            display: inline-block;
            vertical-align: middle;
            font-size: 10px;
            font-weight: 600;
            letter-spacing: 0.5px;
            padding: 2px 7px;
            border-radius: 4px;
            background: var(--zzm-tier-free-bg);
            color: var(--zzm-tier-free-ink);
            --zzm-shine-delay: 0s;
            margin-right: 6px;
          "
        >
          免费
        </span>
        为什么是 Obsidian
      </span>
    </div>
    <div
      style="
        margin: 0 2px;
        font-size: 12.5px;
        color: #8b8b85;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      "
    >
      让想法第一次连起来。
    </div>
  </div>
</div>
```

### 样张 · 会员 · 未解锁

暗底锁罩；锁孔是这张卡唯一的朱色

React：

```jsx
import { LessonCard } from './screens/v4/course/LessonCard.jsx'

// 课节位置数组：[标题, 时长, 简介, md, 免费, 源, uid, 课节ID, 行数据]
const lesson = (id, title, level) => [title, '15:32', '让想法第一次连起来。', '', level < 2 ? 1 : 0, '', '', id, { cover_url: null, required_level: level }]
const OPEN = { canWatchCourse: () => true, canWatchLesson: () => true }
const GUEST = { canWatchCourse: () => false, canWatchLesson: (a) => (a[8]?.required_level ?? 0) < 2 }

export function Locked() {
  return (
    <div style={{ width: 280 }}>
      <LessonCard si={1} li={1} a={lesson('l-7', 'Dataview：笔记变数据库', 2)} plan={GUEST} done={false} tierTag />
    </div>
  )
}
```

HTML（构建时由上面的 React 渲染得到，可直接粘贴）：

```html
<div style="width: 280px">
  <div class="zzm-cardlink">
    <div
      style="
        aspect-ratio: 16/9;
        border-radius: 12px;
        background: #131316;
        overflow: hidden;
        position: relative;
      "
    >
      <div
        style="
          position: absolute;
          inset: 0;
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
          gap: 9px;
        "
      >
        <svg
          viewBox="0 0 240 135"
          style="position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0.5"
        >
          <polyline
            points="18,110 62,38 120,84 178,30 224,96"
            fill="none"
            stroke="rgba(255,255,255,.09)"
            stroke-width="1"
          ></polyline>
          <circle cx="62" cy="38" r="2" fill="rgba(255,255,255,.18)"></circle>
          <circle cx="120" cy="84" r="2" fill="rgba(255,255,255,.18)"></circle>
          <circle cx="178" cy="30" r="2" fill="rgba(255,255,255,.18)"></circle>
        </svg>
        <div
          style="
            position: relative;
            font-size: 14px;
            font-weight: 600;
            color: #ffffff;
            letter-spacing: 0.4px;
            padding: 0 18px;
            text-align: center;
          "
        >
          Dataview
        </div>
        <div
          style="
            position: relative;
            width: 18px;
            height: 2px;
            background: var(--zzm-accent, #a6402f);
          "
        ></div>
      </div>
      <div
        style="
          position: absolute;
          top: 8px;
          left: 9px;
          font-family: var(--zzm-mono);
          font-size: 9.5px;
          letter-spacing: 1px;
          padding: 3px 6px;
          border-radius: 4px;
          background: rgba(0, 0, 0, 0.55);
          color: rgba(255, 255, 255, 0.82);
        "
      >
        S2 · E02
      </div>
      <div
        style="
          position: absolute;
          bottom: 8px;
          right: 9px;
          font-family: var(--zzm-mono);
          font-size: 10.5px;
          padding: 3px 6px;
          border-radius: 4px;
          background: rgba(0, 0, 0, 0.62);
          color: rgba(255, 255, 255, 0.88);
        "
      >
        15:32
      </div>
      <div style="position: absolute; top: 8px; right: 9px; display: flex; gap: 5px">
        <span
          style="
            font-size: 9.5px;
            font-weight: 600;
            letter-spacing: 1px;
            padding: 3px 7px;
            border-radius: 4px;
            background: rgba(0, 0, 0, 0.55);
            color: rgba(255, 255, 255, 0.92);
            opacity: 1;
          "
        >
          会员
        </span>
      </div>
      <div
        style="
          position: absolute;
          inset: 0;
          z-index: 2;
          background: rgba(8, 8, 10, 0.7);
          display: flex;
          align-items: center;
          justify-content: center;
        "
      >
        <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
          <rect
            x="5"
            y="10.5"
            width="14"
            height="9"
            rx="2.5"
            stroke="rgba(255,255,255,.9)"
            stroke-width="1.5"
          ></rect>
          <path
            d="M8 10 L8 7.5 a4 4 0 0 1 8 0 L16 10"
            stroke="rgba(255,255,255,.9)"
            stroke-width="1.5"
            fill="none"
          ></path>
          <circle cx="12" cy="15" r="1.6" fill="var(--zzm-accent,#A6402F)"></circle>
        </svg>
      </div>
    </div>
    <div style="margin: 11px 2px 3px; display: flex; align-items: flex-start; gap: 7px">
      <span style="flex: 1; min-width: 0; font-size: 14px; font-weight: 600; line-height: 1.45">
        <span
          class="zzm-pill-shine"
          style="
            display: inline-block;
            vertical-align: middle;
            font-size: 10px;
            font-weight: 600;
            letter-spacing: 0.5px;
            padding: 2px 7px;
            border-radius: 4px;
            background: var(--zzm-tier-member-bg);
            color: var(--zzm-tier-member-ink);
            --zzm-shine-delay: 0.7s;
            margin-right: 6px;
          "
        >
          会员
        </span>
        Dataview：笔记变数据库
      </span>
    </div>
    <div
      style="
        margin: 0 2px;
        font-size: 12.5px;
        color: #8b8b85;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      "
    >
      让想法第一次连起来。
    </div>
  </div>
</div>
```

### 样张 · 拓展 · 未解锁

传 catName 时脚注带系列名

React：

```jsx
import { LessonCard } from './screens/v4/course/LessonCard.jsx'

// 课节位置数组：[标题, 时长, 简介, md, 免费, 源, uid, 课节ID, 行数据]
const lesson = (id, title, level) => [title, '15:32', '让想法第一次连起来。', '', level < 2 ? 1 : 0, '', '', id, { cover_url: null, required_level: level }]
const OPEN = { canWatchCourse: () => true, canWatchLesson: () => true }
const GUEST = { canWatchCourse: () => false, canWatchLesson: (a) => (a[8]?.required_level ?? 0) < 2 }

export function Extension() {
  return (
    <div style={{ width: 280 }}>
      <LessonCard si={4} li={0} a={lesson('l-x', '用笔记管理人脉', 3)} plan={GUEST} done={false} catName="人脉管理" />
    </div>
  )
}
```

HTML（构建时由上面的 React 渲染得到，可直接粘贴）：

```html
<div style="width: 280px">
  <div class="zzm-cardlink">
    <div
      style="
        aspect-ratio: 16/9;
        border-radius: 12px;
        background: #131316;
        overflow: hidden;
        position: relative;
      "
    >
      <div
        style="
          position: absolute;
          inset: 0;
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
          gap: 9px;
        "
      >
        <svg
          viewBox="0 0 240 135"
          style="position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0.5"
        >
          <polyline
            points="18,110 62,38 120,84 178,30 224,96"
            fill="none"
            stroke="rgba(255,255,255,.09)"
            stroke-width="1"
          ></polyline>
          <circle cx="62" cy="38" r="2" fill="rgba(255,255,255,.18)"></circle>
          <circle cx="120" cy="84" r="2" fill="rgba(255,255,255,.18)"></circle>
          <circle cx="178" cy="30" r="2" fill="rgba(255,255,255,.18)"></circle>
        </svg>
        <div
          style="
            position: relative;
            font-size: 14px;
            font-weight: 600;
            color: #ffffff;
            letter-spacing: 0.4px;
            padding: 0 18px;
            text-align: center;
          "
        >
          用笔记管理人脉
        </div>
        <div
          style="
            position: relative;
            width: 18px;
            height: 2px;
            background: var(--zzm-accent, #a6402f);
          "
        ></div>
      </div>
      <div
        style="
          position: absolute;
          top: 8px;
          left: 9px;
          font-family: var(--zzm-mono);
          font-size: 9.5px;
          letter-spacing: 1px;
          padding: 3px 6px;
          border-radius: 4px;
          background: rgba(0, 0, 0, 0.55);
          color: rgba(255, 255, 255, 0.82);
        "
      >
        S5 · E01
      </div>
      <div
        style="
          position: absolute;
          bottom: 8px;
          right: 9px;
          font-family: var(--zzm-mono);
          font-size: 10.5px;
          padding: 3px 6px;
          border-radius: 4px;
          background: rgba(0, 0, 0, 0.62);
          color: rgba(255, 255, 255, 0.88);
        "
      >
        15:32
      </div>
      <div style="position: absolute; top: 8px; right: 9px; display: flex; gap: 5px">
        <span
          style="
            font-size: 9.5px;
            font-weight: 600;
            letter-spacing: 1px;
            padding: 3px 7px;
            border-radius: 4px;
            background: rgba(0, 0, 0, 0.55);
            color: rgba(255, 255, 255, 0.92);
            opacity: 1;
          "
        >
          拓展
        </span>
      </div>
      <div
        style="
          position: absolute;
          inset: 0;
          z-index: 2;
          background: rgba(8, 8, 10, 0.7);
          display: flex;
          align-items: center;
          justify-content: center;
        "
      >
        <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
          <rect
            x="5"
            y="10.5"
            width="14"
            height="9"
            rx="2.5"
            stroke="rgba(255,255,255,.9)"
            stroke-width="1.5"
          ></rect>
          <path
            d="M8 10 L8 7.5 a4 4 0 0 1 8 0 L16 10"
            stroke="rgba(255,255,255,.9)"
            stroke-width="1.5"
            fill="none"
          ></path>
          <circle cx="12" cy="15" r="1.6" fill="var(--zzm-accent,#A6402F)"></circle>
        </svg>
      </div>
    </div>
    <div style="margin: 11px 2px 3px; display: flex; align-items: flex-start; gap: 7px">
      <span style="flex: 1; min-width: 0; font-size: 14px; font-weight: 600; line-height: 1.45">
        用笔记管理人脉
      </span>
    </div>
    <div
      style="
        margin: 0 2px;
        font-size: 12.5px;
        color: #8b8b85;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      "
    >
      人脉管理 · 让想法第一次连起来。
    </div>
  </div>
</div>
```

### 样张 · 骨架态

React：

```jsx
import { LessonCard } from './screens/v4/course/LessonCard.jsx'

export function Loading() {
  return (
    <div style={{ width: 280 }}>
      <LessonCard loading />
    </div>
  )
}
```

HTML（构建时由上面的 React 渲染得到，可直接粘贴）：

```html
<div style="width: 280px">
  <div class="zzm-cardlink">
    <div
      style="aspect-ratio: 16/9; border-radius: 12px; background: #131316; overflow: hidden"
    ></div>
    <div style="margin: 11px 2px 3px">
      <div class="zzm-skel" style="width: 72%; height: 14px; border-radius: 8px"></div>
    </div>
    <div style="margin: 0 2px">
      <div class="zzm-skel" style="width: 48%; height: 12.5px; border-radius: 8px"></div>
    </div>
  </div>
</div>
```

## 属性

| 属性 | 取值 | 默认 | 说明 |
|---|---|---|---|
| `si / li` | number | — | 系列下标 / 课节下标，生成左上 S1 · E04 编号 |
| `a` | 课节位置数组 | — | 见下表 |
| `plan` | { canWatchCourse(ci), canWatchLesson(a) } | — | 权限视图：决定角标显隐与是否上锁 |
| `done` | boolean | — | 已完成 → 整卡朱染 |
| `tierTag` | boolean | false | 标题前显档位胶囊（带反光） |
| `catName` | string | — | 脚注前缀系列名 |
| `ci` | number | 0 | 课程下标（传给 canWatchCourse） |
| `loading` | boolean | false | 骨架态 |

## 课节位置数组 a

| 下标 | 含义 |
|---|---|
| 0 | 标题；无封面时截首段（「：」前，≤ 12 字）作卡面短标题 |
| 1 | 时长文字，右下角显示 |
| 2 | 简介，作脚注 |
| 3 | Markdown 笔记（卡片不用） |
| 4 | 是否免费 |
| 5 / 6 | 视频源标签 / 播放器 uid（卡片不用） |
| 7 | 课节 ID |
| 8 | 行数据：`cover_url`、`required_level`、`source`、`duration_minutes` |

在 edu 里，悬停可看的课节会预取播放凭证（`hoverPrefetchProps`）。设计系统站用 `src/stubs/playUrlCache.js` 的空实现顶替，源文件一字未改。

## 铁律

- ✓ 要：四角元素（编号、时长、角标）各自带深色小底
- ✗ 不要：给整张封面加暗色蒙层
- ✓ 要：有封面就不叠短标题——封面图自己写着课名
- ✗ 不要：锁定用 backdrop 模糊（同屏几十张卡，GPU 吃不消）

## 本页用到的 CSS

按样张里出现的类名，从源文件原样裁出（完整版见 zzm.css）：

```css
/* tokens.css · 用到的令牌 */
:root {
  --zzm-accent: #A6402F;
  --zzm-mono: ui-monospace, "SF Mono", Menlo, monospace;
  --zzm-tier-free-bg: rgba(22, 163, 74, 0.12);
  --zzm-tier-free-ink: #157F3C;
  --zzm-tier-member-bg: rgba(224, 146, 10, 0.18);
  --zzm-tier-member-ink: #8A5606;
}

/* 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;
}

/* controls.css */

.zzm-v4 .zzm-cardlink {
  background: #FFFFFF;
  border: 1px solid rgba(0, 0, 0, .09);
  border-radius: 16px;
  padding: 12px;
  cursor: pointer;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .04);
  transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease;
}

.zzm-v4 .zzm-cardlink:hover {
  transform: translateY(-3px);
  border-color: rgba(0, 0, 0, .3);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .09), 0 1px 2px rgba(0, 0, 0, .04);
}

.zzm-v4 .zzm-cardlink--done {
  background: rgba(166, 64, 47, .04);
  border-color: rgba(166, 64, 47, .35);
}

.zzm-v4 .zzm-cardlink--done:hover {
  border-color: rgba(166, 64, 47, .55);
}

.zzm-v4 .zzm-pill-shine {
  position: relative;
  overflow: hidden;
}

.zzm-v4 .zzm-pill-shine::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  transform: translateX(-180%);
  background: linear-gradient(105deg, transparent 34%, rgba(255, 255, 255, .85) 50%, transparent 66%);
  animation: zzmPillShine 4.6s ease-in-out infinite;
  animation-delay: var(--zzm-shine-delay, 0s);
}

/* keyframes */
@keyframes zzmShimmer { 100% { transform: translateX(100%) } }
@keyframes zzmPillShine { 0% { transform: translateX(-180%) } 14% { transform: translateX(180%) } 100% { transform: translateX(180%) } }
```

---

上一页：[系列卡](https://ui.zhaozimin.cn/components/series-card.md) · 下一页：[封面图](https://ui.zhaozimin.cn/components/cover-image.md) · 全站目录：https://ui.zhaozimin.cn/llms.txt
