web-design について
web-design は、Web ページの設計からコード生成までを支援するスキルです。参考資料や要件を分析し、確認済みの DESIGN.md を作成した後、その仕様に沿ってコードを生成・監査します。
主な機能
- 01URL、スクリーンショット、キーワード、PRD の分析
- 02DESIGN.md 仕様書の作成と確認
- 03設計仕様に基づくプロジェクトコード生成
- 04L1、L2、L3 のインタラクション段階に対応
- 05設計準拠、レスポンシブ性、コード品質の監査
活用シーン
- 01PRD に基づく製品ページの設計仕様作成
- 02既存サイトを参考にした新規ページの制作
- 03DESIGN.md に沿ったプロジェクトページコードの生成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ xiaopu-ai/ web-design --skill web-designインストール後、Claude Code で/web-designを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSE.gitignoreREADME.mddocs/index.htmlstyles.cssapp.jsDESIGN.mdreferences/design-md-template.mdreferences/design-systems/INDEX.mdairbnb.mdairtable.mdbmw.mdcohere.mdapple.mdcoinbase.mdclaude.mdclay.mdcal.mdfigma.mdelevenlabs.mdclickhouse.mdhashicorp.mdcomposio.mdframer.mdcursor.mdexpo.mdibm.mdferrari.mdintercom.mdkraken.md詳細
Web Design
Web 端视觉设计 SKILL。两阶段工作流:先出规范(DESIGN.md),再出代码。
DESIGN.md 是显式文件产物,保存到项目目录,可跨项目复用、可手动修改、可给其他工具消费。
核心流程
Phase A 理解需求(灵活输入)──→ Phase B 输出 DESIGN.md(用户确认)──→ Phase C 生成项目代码
启动时先检测(按优先级):
- 项目里已经有 DESIGN.md 了吗?→ 有则沿用/修改/重建
- 项目里有 PRD.md 吗?→ 有则进入 A4(PRD 驱动流程)
- 都没有 → 正常走 Phase A
Phase A: 理解需求
输入是灵活的,以下任意组合都行,不要强制用户走固定路径:
| 输入 | 处理方式 |
|---|---|
| 参考 URL | 抓取 HTML+CSS,提取 Token,motion audit |
| 截图 / 设计稿 | 从视觉提取气质、色温、密度、字体风格 |
| 关键词 / 描述 | "暗色克制衬线风格" → 从 style-seeds 匹配 Token |
| 品牌名 | "做成类似 Linear 的" → 先查 references/design-systems/,有则直接读取;无则抓取该网站 |
| 混合 | "参考这个 URL 但配色要暖一些" → 提取 + 覆盖 |
| PRD 文档 | 读取 PRD.md 的产品定位、页面结构、设计交接区 → 分析竞品风格 → 推导设计方向 |
A1. URL 分析(有参考 URL 时)
意图判断:参考(提取设计语言,做独立新页面)vs 复刻(用户明确说"复刻/clone")。
抓取流程(按优先级尝试):
-
首选:Playwright 爬虫(真实浏览器,能绕反爬、渲染 SPA、模拟滚动)
python3 scripts/crawl_website.py --url [URL] --output ./crawl-output --scroll-delay 600输出:每屏视口截图 + tokens.json + structure.json + styles.css
-
备选:Token 提取脚本(轻量,适合简单静态站)
python3 scripts/extract_design_tokens.py --url [URL] --format json -
降级:手动 curl(脚本都不可用时)
curl -Ls [URL]
综合三类输入判断:实际体验(氛围节奏)> 截图(局部气质)> Token 数值(精确参数)。Playwright 爬虫的视口截图可直接作为"体验"和"截图"两类输入。
Motion Audit(参考站有滚动联动动效时必做):
- 触发信号:动效跟随滚动连续变化 / 模块间有 stagger-pin-parallax / 引用 GSAP-Lenis 等
- 记录每个动效区块的 trigger、driver、elements、effect、timing
降级链路:
- Playwright 爬虫失败 → 查
references/design-systems/有无该品牌预置文件(58 个品牌) - 预置文件也无 →
extract_design_tokens.py尝试静态提取 - 静态提取也失败 → 手动 curl CSS
- 全部失败 → 要求用户提供截图,从视觉手动建 Token
A2. 对话引导(无参考物时)
快速锁定风格方向,不要机械走问答清单:
核心问题(按需选问,不全问):
- 亮色 vs 暗色?
- 衬线 vs 无衬线?
- 克制留白 vs 丰富饱满?
- 强调色偏好?
- 有没有喜欢的网站或风格参考?
从 references/style-seeds.md 匹配最近似的种子(10 种预设方向),展示给用户确认或混搭。
用户提到具体品牌时,查阅 references/design-systems/INDEX.md 按类别检索,按需读取对应品牌文件(58 个真实网站设计规范,每个 ~300 行)。品牌文件只含静态设计系统,动效仍需按交互档位补充。
A3. 交互档位确认(不可跳过)
| 档位 | 体验感 |
|---|---|
| L1 精致静态 | 优雅 hover + 柔和入场 |
| L2 流畅交互 | 滚动 reveal、视差、导航变化 |
| L3 沉浸体验 | 滚动驱动时间线、pin、光标跟随、转场 |
L2 追问:reveal 风格(fadeInUp/scaleIn)、视差、导航变化 L3 追问:section pin、光标效果、转场、是否允许 GSAP/ScrollTrigger/Lenis
查阅 references/interaction-patterns.md 获取对应档位的完整代码。
A4. PRD 驱动(检测到项目级规范文件时)
启动时自动扫描项目根目录,按优先级:
PRD.md/prd.mdSPEC.md/spec.mdREADME.md或任何.md— 若含「产品定位 / Target Users / Pages / 核心页面」等关键字段
不绑定特定工具或模板。只要能从文档里抽到下列字段,就足以作为设计输入:
| 字段(中/英别名) | 用途 |
|---|---|
| 产品名称 / Product / Name | Hero 文案 |
| 一句话定位 / Tagline / Pitch | Hero 副标题、定调 |
| 目标用户 / Target Users / Audience | 色彩温度、字体风格决策 |
| 核心页面 / Pages / Screens | Phase C 生成清单 |
| 竞品 / Competitors / References | Phase A 风格参考起点 |
| 技术栈 / Tech Stack | Phase C 代码生成方案 |
| 设计交接 / Design Notes / 调性 | 直接继承的硬约束 |
竞品风格分析:
- PRD 提到竞品 → 优先查
references/design-systems/是否已有预置 - 有预置 → 读取该品牌规范作为起点
- 无预置 → 调用
scripts/crawl_website.py抓取提取 - PRD 没提 → 按产品类型 + 调性从
references/style-seeds.md匹配最近种子
提取不到关键字段时:不要瞎猜,回到对话引导(A2)补齐。
与用户确认(示例):
我从
{文件名}读到了:
- 产品:{名称} — {一句话定位}
- 目标用户:{画像}
- 参考竞品:{列表 或 "无"}
- 建议的风格方向:{1-2 个候选,各附一句理由}
这个方向对吗?
确认后进入 A3(交互档位),然后 Phase B。
Phase B: 输出 DESIGN.md(必走)
必须生成 DESIGN.md 文件。 按 references/design-md-template.md 模板输出 9 个章节:
- Visual Theme & Atmosphere — 设计哲学、氛围关键词、一句话定调
- Color Palette & Roles — 完整 CSS 变量定义(含 RGB 辅助值)
- Typography Rules — 字体族、Google Fonts URL、字号层级表、禁止字体
- Component Stylings — 按钮/卡片/导航/链接/标签等完整 CSS(含所有状态)
- Layout Principles — 网格、间距梯度、容器宽度
- Depth & Elevation — 阴影体系
- Animation & Interaction — 动效档位、入场/滚动/悬停/特效的完整代码
- Do's and Don'ts — 设计护栏、Anti-Patterns(至少 8 条)
- Responsive Behavior — 断点、触摸目标、折叠策略
文字装饰规则:生成 Color Palette 和 Typography 后,按 references/text-decoration-rules.md 决策表对标题逐级判断是否添加渐变/投影。
展示摘要给用户确认,确认后保存 DESIGN.md 到项目目录,再进入 Phase C。
Phase C: 生成项目代码(必走)
C1. シーンの確認 + コンテンツの収集
ユーザーにどのようなページを作るのかを尋ね、具体的なコンテンツを集めます。references/scene-defaults.md を参照して、そのシーンのレイアウトとコンポーネントのベースラインを確認します。
よくあるシーンで必要なコンテンツ:
| シーン | 収集する内容 |
|---|---|
| ランディングページ | タイトル、副題、Feature の一覧、CTA、Social proof |
| 個人サイト / ポートフォリオ | 名前、肩書き、プロジェクト一覧、プロフィール、連絡先、ブログ一覧 |
| ブログ | 名前、記事一覧 / 内容、著者情報、カテゴリタグ |
| 製品ページ | 製品名、セールスポイント、機能のスクリーンショット、価格、FAQ |
ユーザーの情報が不足している場合は、積極的に質問します。ユーザーが「先に作って、内容はあとで入力する」と言った場合は、適切なプレースホルダーを使用します。
C2. デザインフレームワーク + コード生成
- プロジェクト環境を確認:フレームワーク、ルーティング、スタイル方式、既存のコンポーネントと assets
- フレームワークを決定:プロジェクトがある場合 → それを引き継ぐ。ない場合 → 最小限の実行可能な構成
- DESIGN.md に厳密に従って生成:
- すべての色は CSS 変数を参照する
- フォントは DESIGN.md の定義に従って使用する
- インタラクションは DESIGN.md で指定された段階に従って実装する
- Do's and Don'ts に違反しない
- 画像の方針:ユーザー素材 > プロジェクトの assets > 参照サイト URL のプレースホルダー > Unsplash
- アイコンの方針:プロジェクト既存のライブラリ > lucide-react > インライン SVG(
references/icon-library.mdを参照)
C3. 監査
- DESIGN.md 準拠チェック:コードが仕様に厳密に従っているか
- 参照対象との差分監査(参照がある場合):Token / レイアウト / フォント / インタラクション / 素材を項目ごとに比較
- レスポンシブ検証:少なくともモバイル + デスクトップ
- 品質チェックリスト:
references/quality-checklist.mdを読み、項目ごとに自己点検
ホームページの見せ場の原則(ランディングページ専用)
Web プラットフォームで最も重要なのは**ランディングページ(ホームページ)**です。他のページは標準に沿ってスタイルを統一すれば十分ですが、ホームページだけは以下の 2 つの必須原則を満たさなければなりません。妥協はできません。
原則一:「うわっ」と驚く見せ場を 3 つ + ひと工夫
見せ場の位置は固定です。それぞれの位置に、ユーザーを「うわっ」と驚かせる視覚的インパクトを 1 つ設ける必要があります。
| # | 位置 | 要件 | 代表的な手法 |
|---|---|---|---|
| 1 | ファーストビュー / Hero | 読み込みの瞬間に、動き / 3D / 巨大なグラデーションで圧倒する | カードの星座が弾ける演出、WebGL の 3D オブジェクト、巨大な見出しの mask reveal、マウス追従の光斑、3D カードの積み重ね |
| 2 | 最初のスクロール | ファーストビューを離れた最初の section に、引き続き惹きつける「フック」を設ける | 宣言文の横スクロール帯(大きな文字の marquee)、数字の爆発、画面分割、カードが集まるトランジション、全画面動画 |
| 3 | リスト / 配列 / 列挙 / 展示エリア | ありきたりな同サイズの grid にしない | サイズが不揃いな Bento レイアウト、ホバー時に光が追従する SpotlightCard、3D 傾斜カード、抽象アートのトップ |
ひと工夫のデザインポイント 1 つ:「気づかなければ見逃すけれど、気づくと思わず笑ってしまう」ディテールを 1 か所設けます。候補:
- インタラクションのフィードバックを微細なアニメーションで表現(コピー後の sparkle + tooltip)
- イースターエッグ(Konami / Ctrl+K コマンドパネル)
- Meta ネタ(キーワードのホバー時に「これがこのページで使われている仕様です →」と表示)
- 最下部までスクロールしたときの小さなサプライズ(例:「ここまで見てくれて、ありがとうございます」)
原則二:スムーズさ · スクロールを妨げない(絶対厳守)
以下はパフォーマンスのレッドラインであり、見せ場のデザインと同等に優先されます。見せ場が派手でもカクつけば = 0 点です。
- 3D / WebGL:ページ全体で最大 1 か所。IntersectionObserver で不可視になったら必ず一時停止する
- 動く要素に対する
filter: blur():禁止。奥行きの表現には opacity + scale を代わりに使用する backdrop-filter: blur():値は ≤ 14px とし、広いスクロール領域を覆わない- Lenis / scroll-jacking:pin-scrub が本当に必要な場合のみ有効にする。通常のページではネイティブの
scroll-behavior: smoothを使用する - ScrollTrigger pin:1 ページあたり ≤ 2 か所(doubao 級のストーリーテリングでのみ上限を緩和)
- pointermove リスナー:必ず rAF でスロットルする
- Custom cursor の全体置き換え:個性の強いデザイン系 / ツール系のサイトに限って使用し、一般的な SaaS では禁止
実装の提案(パフォーマンスに優しい見せ場のレシピ)
これらの組み合わせは、中程度の性能のデバイスでも 60fps を達成できることを実測済みです。
| 見せ場 | 実装 | パフォーマンスコスト |
|---|---|---|
| マウススポットライト | CSS var --mx/--my + radial-gradient(at var(--mx) var(--my)) | ⭐ 非常に低い(1 回の repaint) |
| カードが弾けて登場 | GSAP の一度きりの timeline(load 時に 1.5s で stagger、終了後すぐ解放) | ⭐⭐ 低い(1 回のみ) |
| 宣言文の横スクロール帯 | 純粋な CSS の transform: translateX keyframes | ⭐ 非常に低い |
| SpotlightCard | 各カードに --mx/--my + ::before の radial-gradient(rAF でスロットル) | ⭐⭐ 低い |
| 大きな文字の mask reveal | 純粋な CSS clip-path の一度きりのアニメーション | ⭐ 非常に低い |
| キーワードのグラデーションが流れる演出 | background-position のアニメーション | ⭐ 非常に低い |
避けるべき組み合わせ:
- ❌ 12 個以上の要素で同時に
filter: blur()+will-changeを使用(GPU メモリが枯渇) - ❌ Lenis + 複数の pin-scrub + WebGL(メインスレッドが停止)
- ❌ Three.js を常時レンダリングし、一時停止しない(画面外に出ても FPS を消費)
品質 100 点の最低ライン(必須の受け入れ基準)
DESIGN.md と生成コードは、以下のすべての条件を同時に満たさなければなりません。1 つでも満たさない場合は未完成と見なされます。詳細な項目は references/quality-checklist.md を参照してください。ここでは最も重要なレッドラインを示します。
DESIGN.md のレッドライン
- 9 つの章すべてに実質的な内容があり、テンプレートのプレースホルダーではない
- 各コンポーネントのスタイルに default / hover / active / focus / disabled のすべての状態が含まれている
- すべての色を CSS 変数として定義し、RGB の補助値(rgba 用)も含める
- フォントに
@importURL + fallback stack がある - アニメーションの段階(L1/L2/L3)が明確で、依存関係の宣言も漏れなく記載されている
- Do's and Don'ts が 8 項目以上あり、そのうち Don'ts が 5 項目以上
- L2+ では
prefers-reduced-motionによる軽減を必ず含める - レスポンシブ対応は少なくとも Desktop + Mobile をカバーする
コードのレッドライン
- すべての色が CSS 変数を参照し、ハードコードされた hex はゼロ
- フォントは DESIGN.md の Typography Rules に厳密に従って実装する
- インタラクティブな各要素に hover + focus 状態がある
- 登場アニメーションを実装する(少なくとも L1 レベルの fadeIn)
- L2+ ではスクロール reveal + ナビゲーションのスクロール時の動的変化 + 視差を実装する
- L3 では pin / カーソル追従 / トランジションを実装する
- 画像の単色ブロックによるプレースホルダーを禁止(参照サイト URL / Unsplash / ユーザー素材を使用)
- アイコンにはプロジェクトのライブラリ / lucide-react / インライン SVG を使用する(Playful なトーン以外では Emoji を禁止)
- DESIGN.md の Do's and Don'ts のいずれにも違反していない
- モバイルの幅 ≤ 600px で横方向にはみ出さず、タッチターゲットは ≥ 44×44px
シグネチャーアニメーションの必須基準(L2 以上で必須、L3 では強化)
L2 以上のページには、以下の 6 種類のアニメーションをすべて含める必要があります。優先して vue-bits / reactbits のディレクトリから選んでください(references/motion-library.md を確認)。ソースコードを再利用できる場合は、自分で実装しないでください。
| カテゴリー | 数量 | 配置 |
|---|---|---|
| Text Animation — Hero H1 | ≥ 1 | 大見出しの表示時または継続状態(例:SplitText / ShinyText / GradientText) |
| Text Animation — Section H2 | ≥ 1 | スクロールでトリガーされるセクション見出し(例:ScrollFloat / BlurText) |
| Text Animation — Body / Label | ≥ 1 | 本文、eyebrow、コードブロック(例:ScrollReveal / TextType / ScrambleText) |
| Animation — 要素単位 | ≥ 1 | CTA のマグネット効果 / カードの hover / カーソル / クリック時のフィードバック(例:Magnet / GlareHover / ClickSpark) |
| Component — インタラクティブな構成要素 | ≥ 1 | カードスタック / ギャラリー / ナビゲーション / 3D カード(例:CardSwap / ScrollStack / SpotlightCard / MagicBento) |
| Background — 雰囲気を作るレイヤー | ≥ 1 | Hero またはグローバル背景(例:Aurora / Silk / Grainient / Threads) |
L3 ではシグネチャーモーメントを合計 6 個以上含めてください。ただし、1 ページあたり 10 個を超えないようにしてください(過剰になるため)。
L3 の映画のような scroll-story に関する必須追加要件
L3 ページ(ユーザーが「doubao / apple / stripe のように」「3D が欲しい」「普通」「映画のような雰囲気が欲しい」などと指定した場合)では、上記の 6 種類のアニメーションに加え、scroll-story パターンの 3/4 以上を必ず取り入れてください。詳しいテンプレートは references/scroll-story-patterns.md を参照してください。
| パターン | 必須数 | 説明 |
|---|---|---|
| Pin-Scrub シーン | ≥ 1 | セクションを固定し、スクロールに合わせてコンテンツの変形やタイムラインを制御 |
| 左側 pin / 右側コンテナ切り替え | ≥ 1 | 左側の見出しを固定し、右側のコンテナをスクロールに応じて複数のシーンに切り替え(doubao の製品紹介で 4 連続する構成と同様) |
| 集束 / 拡散のトランジション | ≥ 1 | 複数の要素が散らばった状態から飛んできて合体する、またはその逆(doubao の hero から次の画面への構成と同様) |
| WebGL / 真の 3D によるシグネチャー演出 | ≥ 1 | Three.js / OGL / Babylon を使用し、CSS 3D だけで済ませない |
黄金律:1〜2 画面ごとに、シグネチャーモーメントを 1 つ入れてください。フェード表示だけの画面が 3 つ連続する構成は不合格です。
最低限のパフォーマンス要件:
- 1 ページにつき WebGL シーンは 1 つ以下
- モバイルでは自動的に品質を下げてください(scroll-story-patterns.md のパフォーマンスに関するセクションを参照)
prefers-reduced-motionに対する完全な代替経路を必ず用意してください
スタイルへの適合:references/motion-library.md の「スタイル × シーン別のおすすめ構成」を厳守してください。Dark Editorial では GlitchText を使わず、Playful では Aurora を使わないでください。派手さよりも、全体のトーンの一貫性が重要です。
最低限のパフォーマンス要件:
- 1 ページにつき重い背景(WebGL)は 1 つ以下
- モバイルでは自動的に品質を下げてください:重い背景 → 静的グラデーション、3D コンポーネント → 2D
- Cursor エフェクトは
matchMedia('(hover: hover)')の場合にのみ有効にしてください - すべてのエフェクトに
prefers-reduced-motion用の代替経路を必ず用意してください
謝辞:vue-bits / reactbits のソースコードを使用する場合は、ページの footer または README に "Motion effects derived from vue-bits by DavidHDev (MIT)" と記載してください。
参考デザインとの整合性監査(参考がある場合)
- 参考サイトのセクション数を数えた
- カラーパレット / フォント / 余白を参考デザインの数値に合わせた(または意図的に異なるデザインにした理由を明記した)
- 参考サイトにスクロールアニメーションがある場合 → 必ず motion audit を行い、対応する段階のアニメーションを実装した
- 生成後に差分監査を明示的に行い、整合させた点と意図的に異ならせた点を列挙する
監査は任意の手順ではありません。Phase C のコードを書き終えたら、各項目を一つずつ確認し、未達の項目はその場で修正してください。
技術スタックの原則
- フレームワーク:プロジェクトで既に使われているスタックを踏襲します。ない場合は、実現可能な最小構成(静的 HTML または Vite)を使用します
- スタイル:既存の方式を踏襲し、新しい依存関係を無理に追加しません
- コンポーネント:ライブラリがあれば再利用し、なければセマンティックなコンポーネントを手書きします
- アニメーション:CSS を優先します。L2 では IntersectionObserver を使用でき、L3 では GSAP / ScrollTrigger / Lenis を使用できます
- 画像:単色のプレースホルダーは禁止です。プレースホルダー画像はコード冒頭の
const IMGで一元管理します
言語ルール
デフォルトでは入力言語に合わせます。言語を無理に固定しないでください。
| 入力の手がかり | デフォルトの生成言語 |
|---|---|
| 参考 URL / スクリーンショット / PRD が中国語 | 中国語 |
| 参考 URL / スクリーンショット / PRD が英語 | 英語 |
| ユーザーとの会話で使われる言語 | 上記で判断できない場合の基準 |
多言語切り替えは必要に応じて追加します:
- ユーザーが明示的に要求した場合、または PRD に複数市場向けの製品と記載されている場合にのみ i18n を追加します
- 各ページに locale オブジェクトや言語切り替え機能をデフォルトで追加しないでください。単一言語の製品ではノイズになります
- i18n が必要な場合:
- 文言は独立した locale オブジェクトにまとめます(
const messages = { zh: {...}, en: {...} })。コンポーネント内ではt('hero.title')を使用します - 切り替えはリロードせず、即時に反映させます
- 小規模なプロジェクト:トップレベルの locale + React state。Next.js:
next-intl/i18next。純粋な HTML:<select>+data-lang
- 文言は独立した locale オブジェクトにまとめます(
中国語ページ固有の要件(中国語コンテンツを生成する場合は必ず遵守):
- 中国語フォントには Noto Sans SC / Noto Serif SC / LXGW WenKai などの中国語フォントファミリーを使用します。英語フォントのみを指定し、システムフォントへのフォールバックに頼ることは禁止です
- 行の高さは 1.7 以上、文字間隔は
letter-spacing: 0.02em - 本文のフォントサイズは 15px 以上、長文では 16px 以上
- 中国語と英語を混在させる場合:中国語フォントファミリーを先に指定し、英語フォントファミリー(Inter / DM Sans)をフォールバックとして
font-familyのリストに追加します
参考文件索引
| 文件 | 内容 | 何时读取 |
|---|---|---|
references/design-md-template.md | DESIGN.md 输出模板(9 章节) | Phase B |
references/interaction-patterns.md | L1/L2/L3 三档交互代码库 | Phase A + B |
references/motion-library.md | vue-bits/reactbits 效果目录 + 按风格推荐组合 | Phase B(选型)+ Phase C(落地) |
references/scroll-story-patterns.md | L3 电影级 scroll-story 招式库(卡片星座 / 左pin右swap / 汇聚转场 / WebGL 签名) | Phase B(L3 判定后)+ Phase C |
references/style-seeds.md | 10 种风格种子 → Token 映射 | Phase A 对话时 |
references/scene-defaults.md | 7 种场景的布局/组件/交互基线 | Phase C |
references/text-decoration-rules.md | 文字渐变/投影决策表 | Phase B |
references/icon-library.md | lucide-react 图标速查 | Phase C |
references/quality-checklist.md | 质量检查清单 | Phase C 审计 |
references/design-systems/INDEX.md | 58 个品牌设计系统索引 | Phase A 用户提到品牌时 |
references/design-systems/{name}.md | 单个品牌完整设计规范 | 按需读取,禁止全量加载 |
scripts/crawl_website.py | Playwright 爬虫(截图 + Token + 结构) | Phase A 首选 |
scripts/extract_design_tokens.py | 轻量 Token 提取(静态站备选) | Phase A 备选 |
scripts/fetch_unsplash_images.py | 占位图候选 URL | Phase C |