visual-redesign について
既存の React、HTML、CSS の画面を、アプリの動作を保ちながら視覚的に整えるスキルです。コードを監査してから CSS を中心に変更し、JavaScript のロジックや動作中の JSX 構造には手を加えません。
主な機能
- 01ファイルを監査しロジックと表示を分類
- 02状態、ハンドラー、API 呼び出しを維持
- 03具体的な視覚上の問題を特定
- 04既存のデザイントークンとスタイルを抽出
- 05動作中の JSX 構造を変更せずに調整
活用シーン
- 01既存の React 画面の見た目を更新
- 02業務ロジックを変えずにページを改善
- 03デザイン変更前にコードベースを監査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yu-369/ vibecurb --skill visual-redesignインストール後、Claude Code で/visual-redesignを入力して読み込まれたか確認します。
詳細
ビジュアルの再設計:見た目を磨き上げる
ユーザーが既存の React/HTML/CSS コードを提示し、見た目の改善、デザインのアップグレード、美観の向上、プレミアム感の演出、Awwwards のような雰囲気の実現、あるいは「動くけれど見た目がひどい」といった要望をしたときに、このスキルが起動します。ユーザーのコードは機能しています。状態管理、API 呼び出し、イベントハンドラー、ビジネスロジックはすでに動作しています。あなたの仕事は、それらを壊さずにビジュアルレイヤーをアップグレードすることです。あなたは外科医であり、解体業者ではありません。正確に切り込み、患者を生かしたままにしてください。
絶対のルール — 最初に読むこと
JavaScript のロジックは神聖です。絶対に触れてはいけません。
これは、このスキルのあらゆる行に適用される、譲れない、破ることのできないルールです。
SACRED (never modify):
├── useState / useReducer declarations and updates
├── useEffect / useCallback / useMemo bodies
├── API calls (fetch, axios, SWR, React Query)
├── Event handler LOGIC (what happens onClick, not how the button looks)
├── Conditional rendering logic (ternaries, && chains, if blocks)
├── Router/navigation logic
├── Form validation logic
├── Context providers and consumers
├── Custom hook implementations
├── Data transformations (map, filter, reduce on data)
├── Error handling (try/catch, error boundaries)
├── Prop drilling / prop interfaces
└── Third-party library integration logic
SLOP (upgrade aggressively):
├── className strings and CSS classes
├── Inline styles (style={{...}})
├── CSS/SCSS files
├── Tailwind utility classes
├── Bootstrap classes
├── Color values (hex, rgb, hsl)
├── Font families and sizes
├── Spacing values (padding, margin, gap)
├── Border-radius values
├── Shadow values
├── Transition/animation declarations
├── z-index values
├── Layout structure (flex/grid configuration)
└── Wrapper div nesting (for layout, NOT for conditional logic)
⚠ 逸脱への警告: AI が「アプリを壊す」最大の原因は、見た目をすっきりさせようと JSX を再構成し、その結果、条件付きラッパーを誤って削除したり、key prop を移動したり、ref の割り当てを変更したり、DOM 上の位置に依存する子要素の順序を入れ替えたりすることです。既存の構造が機能しているなら、見た目のために JSX を再構成してはいけません。既存の構造に CSS を追加してください。CSS の好みに合わせて構造を作り変えてはいけません。
→ グレーゾーン
ロジックとスタイルの両方に関わる要素もあります。細心の注意を払って扱ってください。
| 要素 | 絶対に触れてはいけないもの? それとも余計なもの? | ルール |
|---|---|---|
className={isActive ? 'active' : ''} | 両方 — ロジックは神聖ですが、クラス名は余計なものです | 三項演算子はそのままにします。クラス名の値だけを変更します:className={isActive ? 'nav-link--active' : 'nav-link'} |
style={{ display: isOpen ? 'block' : 'none' }} | 神聖 — これは条件付き表示のロジックです | CSS クラスに置き換えてはいけません。インラインスタイルは状態によって制御されています。そのまま残し、並行してスタイルを追加してください |
{items.map((item) => <Card key={item.id} ... />)} | 神聖 — map、key、データフローはロジックです | Card コンポーネントの内部にスタイルを適用してください。map の構造や key の割り当ては変更してはいけません |
ref={containerRef} | 神聖 — ref の割り当ては JavaScript の動作を制御します | ref を削除、移動、改名してはいけません |
aria-* 属性 | 神聖 — アクセシビリティ属性は機能の一部です | 削除してはいけません。不足している属性を追加することはできます |
data-* 属性 | おそらく神聖 — JavaScript やテストで使われることがよくあります | 未使用と確認できない限り、削除してはいけません |
id 属性 | おそらく神聖 — JavaScript のセレクターで使われている可能性があります | 未使用と確認できない限り、変更してはいけません |
onClick={() => setOpen(!open)} | 神聖 — ハンドラーはロジックです | 要素にスタイルを適用してください。ハンドラーには触れてはいけません |
条件付きコンテンツを囲む <div> | 神聖 — 描画上の理由から存在している可能性があります | 表示だけを目的としたものだと確認できない限り、「不要な」ラッパーの <div> を削除してはいけません |
グレーゾーンの鉄則: あるものがロジックかスタイルか判断できない場合は、変更せず、並行してスタイルを追加してください。アプリを壊さない、多少洗練されていない CSS の解決策のほうが、バグを生む洗練されたリファクタリングより、はるかに優れています。
パイプライン
┌──────────────────────────────────────────────────────────────────────────┐
│ │
│ CODE IN ──→ Phase 1: Audit │
│ (read every file, classify Sacred vs Slop, │
│ identify the aesthetic crimes) │
│ │
│ ──→ Phase 2: Extraction │
│ (extract current design decisions across │
│ 7 layers, build the Slop Sheet) │
│ │
│ ──→ Phase 3: Prescription │
│ (define target aesthetic, map every │
│ slop item to its gold replacement) │
│ │
│ ──→ Phase 4: Surgery │
│ (execute replacements layer by layer: │
│ tokens → typography → color → spacing → │
│ components → atmosphere → motion) │
│ │
│ ──→ Phase 5: Post-Op │
│ (verify nothing broke, visual diff, │
│ responsive check, motion check) │
│ │
│ Each phase has a ✓ Quality Gate. Failing a gate blocks the next. │
│ │
└──────────────────────────────────────────────────────────────────────────┘
フェーズ 1:監査
一文字でも変更する前に、コードベース全体を読みます。何があるのかを把握し、すべてを分類してください。
→ すべてのファイルを読み、監査テーブルに記入します
| ファイル | 種類 | 保護すべき要素 | 問題のある要素 | リスクレベル |
|---|---|---|---|---|
App.tsx | ルートコンポーネント | ルーターの設定、プロバイダー、グローバル状態 | ルートのclassName、グローバルなラッパースタイル | 低 |
Header.tsx | UI コンポーネント | ナビゲーションの状態(モバイルメニューの切り替え)、認証状態 | すべてのclassName文字列、インラインスタイル、レイアウト | 中 |
Hero.tsx | UI コンポーネント | CTAのクリックハンドラー、アナリティクス呼び出し | タイポグラフィ、色、余白、画像、レイアウト | 低 |
Features.tsx | UI コンポーネント | データ配列、mapによる反復処理 | カードのスタイル、グリッドレイアウト、アイコン | 低 |
Dashboard.tsx | 複雑なコンポーネント | すべての状態、エフェクト、API呼び出し、データ変換 | テーブルのスタイル、カードのスタイル、チャートのラッパースタイル | 高 |
Form.tsx | 複雑なコンポーネント | バリデーション、送信処理、エラー処理、ref | 入力欄のスタイル、ボタンのスタイル、レイアウト | 高 |
index.css | スタイルシート | なし(ただし重要なリセットが含まれている場合があります) | すべて | 低 |
リスクレベル:
- 低 - 大部分がプレゼンテーションです。大胆にスタイルを変更しても安全です。
- 中 - ロジックとプレゼンテーションが混在しています。慎重にスタイルを変更し、その後テストしてください。
- 高 - ロジックとプレゼンテーションが密接に絡み合っています。CSSクラスとスタイルのみ変更してください。変更のたびにテストしてください。
→ デザイン上の問題点を特定します
UIを確認し、見た目の問題をすべて記録してください。具体的に記述します。「見た目が悪い」だけでは診断になりません。
| 問題点 | 場所 | 深刻度 | 例 |
|---|---|---|---|
| 汎用的なフォントスタック | グローバル/body | 重大 | font-family: Arial, sans-serif またはブラウザーのデフォルト |
| デフォルトの影 | カード、ボタン | 大 | box-shadow: 0 2px 4px rgba(0,0,0,0.1) - Bootstrapのデフォルト |
| 真っ白な背景に真っ黒な文字 | 全体 | 大 | color: #000; background: #fff - 温かみがなく、コントラストが強すぎます |
| 余白の不統一 | セクション間 | 大 | あるセクションのmargin-top: 20pxに対し、次のセクションはmargin-top: 50px |
| Bootstrapブルーのアクセントカラー | ボタン、リンク | 重大 | #0d6efd - 「デザインしていません」という印象を最も強く与える色 |
| 汎用的な角丸 | カード、ボタン | 中 | どこもborder-radius: 4pxで、角丸の使い分けがありません |
| 表示時のアニメーションがない | ページ読み込み時 | 中 | 要素が静的で生気なく、ただ表示されます |
| ホバー状態がない | ボタン、カード、リンク | 大 | インタラクティブな要素から何のフィードバックもありません |
| パディングが窮屈 | カード、セクション | 大 | ゆとりを持たせるのに32px必要なカードのpadding: 16px |
| 背景に雰囲気がない | 背景 | 中 | 単調なbackground: whiteまたはbackground: #f5f5f5で、奥行きがありません |
| 角丸の使い分けが不統一 | コンポーネント全体 | 中 | ボタンはrounded-full、カードは理由もなくrounded-smです |
| 見出しに本文用フォントを使用 | H1-H3 | 重大 | Inter/Roboto/Arialをfont-size: 24pxで使い、ディスプレイ見出しのように見せています |
| 視覚的な階層がない | コンテンツセクション | 大 | すべてが同じサイズ、太さ、色です |
| 余白のシステムがない | レイアウト | 大 | パターンもなく、mt-4、mt-6、mt-3をランダムに使用しています |
→ 監査の概要を出力します
見つかった問題を3~5行で述べてください。
「監査の概要:8つのコンポーネントで構成されるReact SPAです。ルーター、認証状態、3つのAPI呼び出しは保護対象で、すべてDashboard.tsxとHeader.tsxにあります。ビジュアルレイヤー全体でBootstrap 5のデフォルトが使われています。#0d6efdの青いアクセントカラー、デフォルトの影、Arialのフォントスタック、すべてに適用された4pxの角丸、ホバー状態なし、表示時のアニメーションなし、カードの窮屈な16pxのパディング、真っ白な背景に真っ黒な文字が見られます。デザインシステムがなく、余白と色がコンポーネントごとに場当たり的です。問題の推定数:重大14件、大23件。全体のリスクは中、Dashboard.tsx(複雑な状態管理とテーブル描画)は高です。」
✓ 品質ゲート:監査
フェーズ 2 に進む前に、以下を確認してください:
- すべてのファイルを読み込み、監査テーブルで分類している
- すべてのファイルで、変更してはならない要素を特定している
- ファイルごとにリスクレベルを割り当てている
- デザイン上の問題を具体例とともに記録している
- 監査の概要を作成している
- 高リスク(JavaScript のロジックが多い)のファイルを把握している
- コードをまだ一切変更していない
フェーズ 2:抽出
7 つのレイヤーにわたって、現在のデザイン上の決定事項を抽出します。これにより、「変更前」のスナップショットである Slop Sheet を作成します。
→ レイヤー 1:トークン(色、フォント、スペーシングスケール)
| トークン | 現在の値(Slop) | ソース |
|---|---|---|
| プライマリ背景 | #ffffff または white | index.css / インライン |
| セカンダリ背景 | #f5f5f5 または #f8f9fa | Bootstrap gray-100 |
| プライマリテキスト | #000000 または #212529 | Bootstrap のデフォルト |
| セカンダリテキスト | #6c757d | Bootstrap gray-600 |
| アクセント/プライマリアクション | #0d6efd | Bootstrap primary |
| アクセントのホバー時 | #0b5ed7 | Bootstrap primary hover |
| 危険/エラー | #dc3545 | Bootstrap danger |
| 成功 | #198754 | Bootstrap success |
| 枠線の色 | #dee2e6 | Bootstrap gray-300 |
| 表示用フォント | system-ui または Arial | ブラウザーのデフォルト |
| 本文フォント | 表示用フォントと同じ | 使い分けなし |
| 等幅フォント | なし | 未設定 |
| 基本スペーシング | システムなし(場当たり的) | ランダムな px 値 |
| デフォルトの角丸 | 4px または 0.375rem | Bootstrap のデフォルト |
→ レイヤー 2:タイポグラフィ
| 要素 | 現在の仕様(Slop) |
|---|---|
| H1 | font-size: 2rem; font-weight: bold; font-family: inherit |
| H2 | font-size: 1.5rem; font-weight: bold |
| H3 | font-size: 1.25rem; font-weight: bold |
| 本文 | font-size: 1rem; line-height: 1.5 |
| 小さい文字/キャプション | font-size: 0.875rem |
| ボタンのテキスト | font-size: 1rem; font-weight: 400 |
| 文字間隔 | 未設定(ブラウザーのデフォルト:normal) |
| 見出しの行の高さ | 1.2(Bootstrap のデフォルト。表示用としては広すぎる) |
| テキストの折り返し | 見出しに text-wrap: balance を設定していない |
| 本文の最大幅 | なし(テキストが端から端まで続く) |
→ レイヤー 3:スペーシング
| 測定項目 | 現在の値(Slop) |
|---|---|
| セクションのパディング | 一貫性なし:py-3、py-4、py-5、ランダムな px 値 |
| カードのパディング | p-3(12px)または p-4(16px)。窮屈 |
| グリッドの間隔 | gap-3(12px)または gap-4(16px)。狭い |
| 見出しから本文までの間隔 | mb-2 または mb-3。狭すぎる |
| 本文から CTA までの間隔 | mt-3。狭すぎる |
| ナビゲーションの高さ | py-2(低く窮屈)または Bootstrap のデフォルトのナビゲーションの高さ |
| コンポーネントの間隔 | 一貫したシステムなし。コンポーネントごとに異なる |
→ レイヤー 4:色の使用方法
| 用途 | 現在の値(Slop) | 問題 |
|---|---|---|
| 背景 | 純粋な #fff または #f8f9fa | 単調で冷たく、温かみがない |
| テキスト | 純粋な #000 または #212529 | きつく、洗練されていない |
| アクセント | Bootstrap の #0d6efd | 「デザインされていない」印象が強い |
| 枠線 | #dee2e6 | ありがちなグレー |
| 影 | rgba(0,0,0,0.1) | ありきたりで、差別化されていない |
| ホバー状態 | わずかに暗い色合い | 個性がない |
| アクティブ状態 | さらに暗い色合い | 機械的で、物理的な感触がない |
| エラー | Bootstrap の #dc3545 | ありきたりな赤 |
→ レイヤー 5:コンポーネント
| コンポーネント | 現在の状態(Slop) |
|---|---|
| ボタン | Bootstrap の .btn.btn-primary。#0d6efd、角丸 4px、ありきたりなパディングで、ホバー時の物理的な感触がない |
| カード | .card。1px solid #dee2e6、角丸 4px、デフォルトの影または影なし、窮屈なパディング |
| 入力欄 | Bootstrap のフォームコントロール。枠線は #dee2e6、フォーカス時のグローなし、フローティングラベルなし |
| ナビゲーション | Bootstrap の navbar。情報量が多く、窮屈で、デフォルトのスタイル |
| テーブル | Bootstrap の .table。ゼブラストライプ、窮屈な行、洗練されていない |
| モーダル | Bootstrap のモーダル。ありきたりなオーバーレイで、表示時のアニメーションなし |
| バッジ/ピル | Bootstrap の .badge。小さく窮屈で、プライマリブルー |
| ドロップダウン | Bootstrap のドロップダウン。ありきたりな影で、アニメーションなし |
→ レイヤー 6:雰囲気
| プロパティ | 現在の状態(Slop) |
|---|---|
| 背景のテクスチャ | なし - 単色でフラット |
| アンビエントグロー/グラデーション | なし - 完全にフラット |
| 粒子/ノイズ | なし |
| すりガラス | なし |
| 奥行きのシステム | Bootstrap のデフォルトのシャドウ、またはなし |
| 視覚的な温かみ | ゼロ - 冷たく無機質 |
→ レイヤー 7:モーション
| プロパティ | 現在の状態(Slop) |
|---|---|
| ページ表示 | なし - 静的なマウントで、すべてが瞬時に表示される |
| スクロール時の表示 | なし - すべてがすぐに表示される |
| ホバー時のトランジション | transition: all 0.15s ease-in-out(Bootstrap のデフォルト)、またはなし |
| ページ遷移 | なし - 瞬時に切り替わる |
| マイクロインタラクション | なし |
| 読み込み状態 | スピナー、または「Loading...」のテキスト |
| イージングカーブ | CSS のキーワード ease-in-out、またはなし |
✓ 品質ゲート:抽出
フェーズ 3 に進む前に、以下を確認してください。
- 7 つの抽出レイヤーすべてに、コードベースから取得した実際の値が入力されている
- 値は曖昧ではなく、具体的である(正確な 16 進数コード、正確な rem/px 値)
- 現在の状態と目標の品質との差が把握できている
- まだコードを一切変更していない
フェーズ 3:処方
フェーズ 2 で抽出した Slop の項目すべてについて、より優れた置き換えを処方します。これは変換マップ、つまり外科的な計画です。
→ トークンの処方
/* PRESCRIPTION: Design tokens - Slop → Gold
WHY: Tokens are the foundation. Changing these first
means every component that references them upgrades
automatically. This is the highest-leverage change. */
:root {
/* ── Colors ────────────────────────────────────── */
/* Background: #ffffff → warm off-white
WHY: Pure white is harsh and clinical. Off-white
with a warm undertone feels premium and intentional.
The difference is subtle but the eye registers it
as "designed" vs "default." */
--color-bg: #FAFAF9; /* was: #ffffff */
--color-surface: #FFFFFF; /* was: #f8f9fa - cards sit ON the bg */
--color-surface-2: #F5F4F2; /* was: none - for alternating sections */
/* Text: #000000 → warm near-black
WHY: Pure black on warm off-white creates a jarring
temperature clash. Near-black (#1a1a1a) matches the
warmth of the background and reduces eye strain. */
--color-text: #1A1A1A; /* was: #000000 or #212529 */
--color-text-2: #6B7280; /* was: #6c757d - muted, for secondary */
--color-text-3: #9CA3AF; /* was: none - for captions, placeholders */
/* Accent: #0d6efd → considered, non-Bootstrap hue
WHY: Bootstrap blue is the single loudest "I didn't
design this" signal on the web. ANY other considered
hue immediately elevates the design. Pick based on
brand context from the user's brief. */
--color-accent: #____; /* MUST be chosen based on brand context */
--color-accent-hover: #____; /* 10-15% darker or more saturated */
/* Borders and shadows: warm, not gray
WHY: Cool gray borders (#dee2e6) clash with warm
backgrounds. Use warm gray or very low-opacity black. */
--color-border: rgba(0, 0, 0, 0.08); /* was: #dee2e6 */
--color-shadow: rgba(0, 0, 0, 0.04); /* was: rgba(0,0,0,0.1) */
/* ── Typography ────────────────────────────────── */
/* Display font: Arial → a real display font
WHY: Arial/system-ui as a heading font is the
typographic equivalent of serving fine dining
on paper plates. Display fonts have optical
refinements for large sizes that body fonts lack. */
--font-display: 'Outfit', system-ui, sans-serif; /* was: Arial/system-ui */
--font-body: 'Inter', system-ui, sans-serif; /* was: same as display */
--font-mono: 'JetBrains Mono', monospace; /* was: none */
/* ── Spacing ───────────────────────────────────── */
/* Section spacing: random values → consistent scale
WHY: A spacing system creates rhythm. Random spacing
creates visual noise - the eye detects inconsistency
even when the brain can't articulate it. */
--space-section: clamp(5rem, 10vw, 8rem); /* was: random py values */
--space-element: 1.5rem; /* was: 0.75rem–1rem */
--space-component: 2rem; /* was: 1rem–1.5rem */
/* ── Radius ────────────────────────────────────── */
/* Radius: 4px everywhere → considered radius language
WHY: A design system commits to a radius language.
Pick ONE and apply consistently. */
--radius-sm: 8px; /* was: 4px (Bootstrap) - inputs, badges */
--radius-md: 12px; /* was: 4px - cards, containers */
--radius-lg: 16px; /* was: 4px - modals, large cards */
--radius-full: 9999px; /* was: 50% - pills, avatars */
/* ── Easing ────────────────────────────────────── */
/* Easing: ease-in-out → custom curves
WHY: CSS keyword easings are the typographic
equivalent of Comic Sans. Zero character,
zero intentionality. Custom curves give
every animation a deliberate feel. */
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-snap: cubic-bezier(0.22, 1, 0.36, 1);
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
}
→ タイポグラフィの処方
/* PRESCRIPTION: Typography scale - Slop → Gold
WHY: The heading is the first thing the eye hits.
A display font with tight tracking and compressed
line-height immediately signals "designed." The body
font stays readable with comfortable line-height. */
/* Import the fonts - add to the top of your CSS or <head> */
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
h1, h2, h3, h4 {
font-family: var(--font-display);
letter-spacing: -0.03em; /* was: normal (too loose at display size) */
line-height: 1.1; /* was: 1.2 (Bootstrap) - tighten */
text-wrap: balance; /* prevents ugly orphan lines */
color: var(--color-text);
}
h1 {
font-size: clamp(2.25rem, 5vw, 3.75rem); /* was: 2rem fixed */
font-weight: 700;
letter-spacing: -0.04em;
line-height: 1.05;
}
h2 {
font-size: clamp(1.75rem, 3.5vw, 2.75rem); /* was: 1.5rem fixed */
font-weight: 600;
}
h3 {
font-size: clamp(1.25rem, 2vw, 1.5rem); /* was: 1.25rem fixed */
font-weight: 600;
}
body, p, span, li {
font-family: var(--font-body);
font-size: clamp(0.9375rem, 1.1vw, 1.0625rem); /* was: 1rem fixed */
line-height: 1.65; /* was: 1.5 - slightly more generous */
color: var(--color-text);
}
/* Body text max-width - prevent wall-to-wall text */
p {
max-width: 65ch; /* was: none - text ran edge to edge */
}
/* Muted secondary text */
.text-muted, .text-secondary {
color: var(--color-text-2) !important; /* override Bootstrap's gray */
}
/* Eyebrow / label style - add where appropriate */
.eyebrow {
font-family: var(--font-mono);
font-size: 0.75rem;
font-weight: 500;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--color-text-3);
}
→ コンポーネントの処方
/* PRESCRIPTION: Button - Bootstrap → Premium
WHY: The button is the most interactive element on the page.
Its hover feel communicates the entire quality level of the site.
A snappy cubic-bezier with a physical lift-and-shadow creates
a tactile impression that Bootstrap's default cannot achieve. */
/* Strip Bootstrap button defaults */
.btn {
font-family: var(--font-body);
font-size: 0.875rem;
font-weight: 600;
letter-spacing: 0.02em;
padding: 0.75rem 1.75rem; /* was: py-2 px-3 - cramped */
border-radius: var(--radius-full); /* was: 4px - now pill */
border: none;
cursor: pointer;
transition:
transform 0.4s var(--ease-snap),
box-shadow 0.4s var(--ease-snap),
background-color 0.3s var(--ease-out);
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px var(--color-shadow);
}
.btn:active {
transform: translateY(0) scale(0.98);
transition-duration: 0.1s;
}
/* Primary button */
.btn-primary {
background: var(--color-accent);
color: #ffffff;
border: none;
box-shadow: 0 1px 3px var(--color-shadow);
}
.btn-primary:hover {
background: var(--color-accent-hover);
}
/* Ghost / outline button */
.btn-outline-primary,
.btn-secondary,
.btn-outline-secondary {
background: transparent;
color: var(--color-text);
border: 1px solid var(--color-border);
}
.btn-outline-primary:hover,
.btn-outline-secondary:hover {
border-color: var(--color-text);
background: rgba(0, 0, 0, 0.02);
}
/* PRESCRIPTION: Card - Bootstrap → Premium */
.card {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-md); /* was: 4px */
padding: var(--space-component); /* was: 1rem - cramped */
box-shadow: none; /* was: default Bootstrap shadow */
transition:
transform 0.4s var(--ease-snap),
box-shadow 0.4s var(--ease-snap),
border-color 0.3s var(--ease-snap);
}
.card:hover {
transform: translateY(-4px);
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.04),
0 2px 8px rgba(0, 0, 0, 0.02);
border-color: rgba(0, 0, 0, 0.12);
}
/* Reset Bootstrap card internals */
.card-body {
padding: 0; /* parent .card already has padding */
}
.card-title {
font-family: var(--font-display);
font-size: 1.125rem;
font-weight: 600;
letter-spacing: -0.01em;
margin-bottom: var(--space-element);
}
/* PRESCRIPTION: Input - Bootstrap → Premium */
.form-control,
input[type="text"],
input[type="email"],
input[type="password"],
textarea,
select {
font-family: var(--font-body);
font-size: 0.9375rem;
padding: 0.75rem 1rem; /* was: py-1.5 px-3 - cramped */
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-surface);
color: var(--color-text);
transition:
border-color 0.3s var(--ease-snap),
box-shadow 0.3s var(--ease-snap);
}
.form-control:focus,
input:focus,
textarea:focus,
select:focus {
outline: none;
border-color: var(--color-accent);
box-shadow: 0 0 0 3px rgba(var(--color-accent-rgb), 0.15);
}
/* PRESCRIPTION: Navigation - Bootstrap → Premium */
.navbar, nav {
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
background: rgba(250, 250, 249, 0.85); /* semi-transparent for frost */
border-bottom: 1px solid var(--color-border);
padding: 0 clamp(1.5rem, 5vw, 5rem);
height: 64px;
display: flex;
align-items: center;
}
.nav-link, .navbar-nav .nav-link {
font-family: var(--font-body);
font-size: 0.875rem;
font-weight: 500;
color: var(--color-text-2);
transition: color 0.3s var(--ease-snap);
position: relative;
}
.nav-link:hover {
color: var(--color-text);
}
/* Sliding underline on nav links */
.nav-link::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 100%;
height: 1.5px;
background: var(--color-text);
transform: scaleX(0);
transform-origin: right;
transition: transform 0.3s var(--ease-snap);
}
.nav-link:hover::after {
transform: scaleX(1);
transform-origin: left;
}
/* PRESCRIPTION: Table - Bootstrap → Premium */
.table, table {
border-collapse: separate;
border-spacing: 0;
width: 100%;
}
.table th, table th {
font-family: var(--font-mono);
font-size: 0.6875rem;
font-weight: 500;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--color-text-3);
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--color-border);
text-align: left;
}
.table td, table td {
font-size: 0.9375rem;
padding: 1rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
color: var(--color-text);
}
.table tbody tr:hover, table tbody tr:hover {
background: rgba(0, 0, 0, 0.015);
}
/* Kill Bootstrap zebra striping - it looks cheap */
.table-striped > tbody > tr:nth-of-type(odd) {
background-color: transparent;
}
→ 雰囲気の処方
/* PRESCRIPTION: Atmosphere - Flat → Alive
WHY: Flat backgrounds feel like nothing. A subtle
radial gradient, grain texture, or warm tint gives
the background depth without adding visual elements. */
/* Subtle warm radial on the body */
body {
background:
radial-gradient(ellipse at 30% 0%, rgba(250, 235, 215, 0.2) 0%, transparent 50%),
var(--color-bg);
}
/* Noise grain overlay - felt, not seen */
body::after {
content: '';
position: fixed;
inset: 0;
pointer-events: none;
z-index: 9999;
opacity: 0.025;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
}
/* Section dividers - subtle border instead of hard lines */
section + section {
border-top: 1px solid var(--color-border);
}
/* Alternating section backgrounds for rhythm */
section:nth-child(even) {
background-color: var(--color-surface-2);
}
→ モーションの処方
/* PRESCRIPTION: Motion - Static → Alive
WHY: Every element that appears without animation
feels like the page is broken. A staggered fade-up
with deblur signals that the page loaded intentionally. */
/* Entry animation keyframe */
@keyframes enter-up {
from {
opacity: 0;
transform: translateY(24px);
filter: blur(6px);
}
to {
opacity: 1;
transform: translateY(0);
filter: blur(0);
}
}
/* Apply to major elements - use inline style for stagger:
<h1 class="enter-up" style="--stagger: 0ms">
<p class="enter-up" style="--stagger: 120ms">
<button class="enter-up" style="--stagger: 240ms"> */
.enter-up {
animation: enter-up 0.7s var(--ease-out) both;
animation-delay: var(--stagger, 0ms);
}
/* Scroll reveal - elements below the fold */
[data-reveal] {
opacity: 0;
transform: translateY(30px);
transition:
opacity 0.8s var(--ease-out),
transform 0.8s var(--ease-out);
}
[data-reveal].is-visible {
opacity: 1;
transform: translateY(0);
}
/* Universal interactive transition - replaces Bootstrap's
transition: all 0.15s ease-in-out on EVERY interactive element */
a, button, [role="button"],
input, select, textarea,
.card, .nav-link, .badge {
transition: all 0.3s var(--ease-snap);
}
/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
.enter-up {
animation: none;
opacity: 1;
transform: none;
filter: none;
}
[data-reveal] {
opacity: 1;
transform: none;
transition: none;
}
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
→ スクロール時に表示する JavaScript(非破壊的)
/* PRESCRIPTION: Scroll reveal - add as a separate script
WHY: This script observes [data-reveal] elements and
adds .is-visible when they enter the viewport. It does
NOT modify any existing JS - it's a new, separate file
that runs independently.
Add to the end of the page or import in main entry. */
class ScrollReveal {
constructor() {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
this.observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
this.observer.unobserve(entry.target);
}
});
},
{ threshold: 0.15, rootMargin: '-50px 0px' }
);
document.querySelectorAll('[data-reveal]').forEach((el) => {
this.observer.observe(el);
});
}
}
// Initialize after DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => new ScrollReveal());
} else {
new ScrollReveal();
}
✓ 品質ゲート:処方
フェーズ 4 に進む前に、以下を確認してください。
- フェーズ 2 の Slop の項目すべてに、より優れた置き換えが処方されている
- トークンの処方に一貫性がある(温かみのある背景には温かみのあるテキストを合わせ、温度感を混在させない)
- アクセントカラーは、汎用的な別の青ではなく、ユーザーのブランドの文脈に基づいて選ばれている
- タイポグラフィの処方では、見出しにディスプレイフォント、本文にボディフォントを使用している
- コンポーネントの処方では、同じ DOM 構造(class の変更のみ)を維持している
- 雰囲気の追加は控えめである(粒子の不透明度は 0.05 未満、グラデーションは 0.3 未満)
- モーションの追加は非破壊的である(新しい CSS クラスと新しいスクリプトを追加し、既存の JS は変更しない)
- アクセントカラーの
#____プレースホルダーに、実際の 16 進数値が入力されている
フェーズ 4:手術
処方を実行します。ここは手術室です。以下の正確な順序に従ってください。各レイヤーは前のレイヤーを基に構築します。
→ 手術の順序
Layer 1: Tokens (CSS custom properties - the foundation)
Layer 2: Typography (font imports + heading/body styles)
Layer 3: Color (replace all Bootstrap/generic color values)
Layer 4: Spacing (padding, margins, gaps - breathing room)
Layer 5: Components (buttons, cards, inputs, nav, tables)
Layer 6: Atmosphere (grain, glow, section alternation)
Layer 7: Motion (entry animations, hover states, scroll reveals)
→ 手術のルール
| ルール | 理由 |
|---|---|
| 一度に 1 つのレイヤーだけ変更する | トークン、タイポグラフィ、コンポーネントを同時に変更して問題が発生すると、原因を特定できません |
| 各レイヤーの後にテストする | アプリを実行してください。引き続き動作しますか?すべてのルートが読み込まれますか?フォームは送信されますか?API 呼び出しは応答を返しますか?問題がなければ、次のレイヤーに進みます |
| 置き換えではなく CSS オーバーライドを使う | 既存のスタイルを上書きする新しいスタイルシート(例:gold.css)を追加してください。オーバーライドが正常に動作することを確認するまで、既存の CSS ファイルを削除してはいけません |
| className の変更は慎重に行う | Bootstrap のクラスをカスタムクラスに置き換える場合、削除する前に各クラスがコードベース全体で使われている箇所を検索してください。JS ロジック(document.querySelector('.btn-primary'))で使われているクラスは変更してはいけません |
| JSX の構造を書き換えない | className と style の props は追加・変更できます。子要素の順序変更、ラッパー div の削除、コンポーネント階層の変更、または純粋に視覚的なものではない props の変更は禁止です |
| 既存ファイルの変更より新規ファイルを優先する | 既存のスタイルシートを直接編集するより、gold.css を作成して既存のスタイルシートの後にインポートする(オーバーライドの優先順位を高める)方法を優先してください。これにより、簡単にロールバックできます |
→ オーバーライド戦略
最も安全なアプローチは、既存のすべてのスタイルシートの後に読み込まれる、単一の新しいスタイルシートです:
/* gold.css - loaded LAST in the cascade
WHY: By loading after Bootstrap/existing CSS, our rules
override the defaults without deleting any existing code.
If something breaks, the user can remove this one import
to revert entirely. This is the safest surgery method.
Import in main entry file:
import './gold.css' // AFTER all other CSS imports */
このファイルには、フェーズ 3 のすべての処方 - トークン、タイポグラフィ、コンポーネント、雰囲気、モーション - が 1 つのファイルにまとめられており、単一の単位として追加または削除できます。
⚠ ドリフト警告: Bootstrap のインポートを削除したり、古いスタイルシートを削除したりして、既存の CSS を「クリーンアップ」したくなる誘惑があります。ユーザーがゴールドオーバーライドが機能していることを確認するまでは、これをしないでください。古い CSS はセーフティネットです。患者が安定していることが確認されてから初めて削除してください。
→ 高リスクファイルの手術(ダッシュボード、フォーム、複雑なコンポーネント)
監査テーブルで 高 リスクとマークされたファイルの場合:
- まずファイル全体を読む - すべての状態変数、エフェクト、ハンドラーを理解する
- すべての className と style プロップをマッピングする - JS ロジックで参照されているものをメモする
- 変更するのは className の文字列値のみ - 属性はそのまま、値だけが変わる
- 状態を参照するインラインスタイルには絶対に触れない -
style={{ display: isOpen ? 'block' : 'none' }}は神聖です - 変更後すぐにテストする - アプリを実行し、すべての状態変化をトリガーし、すべてのフォームを送信し、すべての API 呼び出しを検証する
/* EXAMPLE: Safe className surgery on a complex component
BEFORE (Bootstrap):
<div className={`card ${isSelected ? 'border-primary' : ''}`}>
AFTER (Gold):
<div className={`card ${isSelected ? 'card--selected' : ''}`}>
The ternary logic is IDENTICAL. Only the class name value changed.
Then in gold.css:
.card--selected {
border-color: var(--color-accent);
box-shadow: 0 0 0 2px rgba(var(--color-accent-rgb), 0.2);
}
*/
→ data-reveal 属性の追加(非破壊的)
スクロールリビールアニメーションを追加するには、既存の JSX 要素に data-reveal 属性を追加します。data-* 属性は React のレンダリングロジックに影響しないため、これは安全です:
/* BEFORE: */
<section className="features">
<h2>Features</h2>
{features.map(f => <FeatureCard key={f.id} {...f} />)}
</section>
/* AFTER - added data-reveal, nothing else changed: */
<section className="features" data-reveal>
<h2>Features</h2>
{features.map(f => <FeatureCard key={f.id} {...f} />)}
</section>
/* The data-reveal attribute is inert until the ScrollReveal
script observes it. It does not interfere with React's
reconciliation, event handling, or state management. */
→ エントリーアニメーションクラスの追加(非破壊的)
/* BEFORE: */
<h1 className="hero-heading">Build faster.</h1>
<p className="hero-subtext">The platform for modern teams.</p>
<button className="btn btn-primary" onClick={handleSignup}>Get Started</button>
/* AFTER - added enter-up class and stagger variable: */
<h1 className="hero-heading enter-up" style={{ '--stagger': '0ms' } as React.CSSProperties}>Build faster.</h1>
<p className="hero-subtext enter-up" style={{ '--stagger': '120ms' } as React.CSSProperties}>The platform for modern teams.</p>
<button className="btn btn-primary enter-up" style={{ '--stagger': '240ms' } as React.CSSProperties} onClick={handleSignup}>Get Started</button>
/* onClick handler is UNTOUCHED. Only className and style were added.
The style prop uses a CSS custom property for stagger delay.
In TypeScript, cast as React.CSSProperties to avoid type errors. */
✓ 品質ゲート:手術
7 つのレイヤーをすべて適用した後、以下を確認します:
- アプリがエラーなく実行される(コンソールがクリーン)
- すべてのルートが正しく読み込まれる
- すべてのフォームが正しく送信され、検証される
- すべての API 呼び出しがデータを返し、正しくレンダリングされる
- すべての状態変化が機能する(トグル、モーダル、ドロップダウン、選択)
- すべてのイベントハンドラーが正しく発火する(クリック、送信、キー入力)
- ref エラーや「undefined のプロパティを読み取れません」エラーがない
- 新しい CSS インポートが既存のスタイルシートの後に読み込まれる
- gold.css ファイルを削除すれば完全に元に戻せる
フェーズ 5:術後
手術が成功したことを検証します。すべてのチェックを順に確認します。FAIL があれば診断と修正が必要です。
機能チェック(神聖な整合性)
| チェック | PASS/FAIL |
|---|---|
| すべてのページ/ルートがエラーなく読み込まれる | |
| すべてのフォームが正しく送信される | |
| すべての API 呼び出しがデータを返し、レンダリングする | |
| すべての状態トグルが機能する(開く/閉じる、表示/非表示、選択/選択解除) | |
| すべてのイベントハンドラーが発火する(onClick、onSubmit、onChange、onKeyDown) | |
| 認証フローが機能する(ログイン、ログアウト、保護されたルート) | |
| コンソールエラーがない | |
| TypeScript エラーがない(TS プロジェクトの場合) | |
| 壊れた ref や undefined プロパティエラーがない | |
| すべての条件付きレンダリングが機能する(ローディング状態、エラー状態、空状態) |
⚠ いずれかの機能チェックが失敗した場合は、最後の手術レイヤーを元に戻して診断してください。すべての機能が合格するまで、視覚チェックに進まないでください。
視覚アップグレードチェック
| チェック | PASS/FAIL |
|---|---|
| どこにも Bootstrap ブルー(#0d6efd)が見えない | |
| 純白(#fff)背景に純黒(#000)テキストがない | |
| 見出しフォントがディスプレイフォント(Arial/system-ui ではない) | |
| 見出しのレタースペーシングが負(ゆるくなく、タイト) | |
| 見出しの行の高さが圧縮されている(< 1.15) | |
| 本文テキストに快適な最大幅がある(端から端までではない) | |
| カードに十分なパディングがある(窮屈な 16px ではない) | |
| ボタンがピル型、または規定の角丸を使用している | |
| ボタンにホバーリフト + シャドウ拡張がある | |
| ボタンにアクティブプレスフィードバックがある | |
| ナビにフロステッドガラス処理がある | |
| カラーパレットが暖かく一貫している(冷たいグレーと暖かいトーンが混在していない) | |
| シャドウが繊細で暖かい(デフォルトの Bootstrap ではない) | |
| 同じコンポーネントタイプ間で border-radius が一貫している |
雰囲気のチェック
| チェック項目 | 合格/不合格 |
|---|---|
| 背景にほのかな温かみがある(単調な白/グレーではない) | |
| グレインのオーバーレイがあり、控えめである(目立たず、感じられる程度) | |
| セクションの背景に変化があり、リズムが生まれている(すべて同じ背景ではない) | |
| 平坦で味気ない印象のセクションが残っていない |
モーションのチェック
| チェック項目 | 合格/不合格 |
|---|---|
| ページ読み込み時にヒーロー要素がアニメーションで表示される(フェードアップとぼかし解除を段階的に適用) | |
| ファーストビューより下のセクションで、スクロール時に表示アニメーションが発火する | |
| すべてのボタンにホバー時のトランジションがある(状態が瞬時に切り替わらない) | |
| すべてのカードがホバー時に浮き上がる | |
| ナビゲーションリンクにアニメーション付きの下線がある | |
| 入力欄のフォーカス時に、ボーダーがグローを伴ってトランジションする | |
| CSS のキーワードによるイージング(ease、ease-in、ease-out、ease-in-out)を使ったアニメーションがない | |
prefers-reduced-motion が尊重される(動きを減らす設定ではモーションを適用しない) |
レスポンシブのチェック
| チェック項目 | 合格/不合格 |
|---|---|
| 1440px(デスクトップ)でレイアウトが適切に表示される | |
| 768px(タブレット)でレイアウトが適切に表示される | |
| 375px(モバイル)でレイアウトが適切に表示される | |
| どのビューポートでも横方向にはみ出さない | |
| モバイルでタッチターゲットが最小 44px ある | |
| どのビューポートでも見出しが 3 行を超えない | |
| モバイルでカードが適切に縦積みされる(1 列) |
ロールバックのチェック
| チェック項目 | 合格/不合格 |
|---|---|
| gold.css のインポートを削除すると、すべての見た目の変更が問題なく元に戻る | |
| 既存の CSS ファイルが削除されておらず、フォールバックとしてそのまま残っている | |
| JSX の構造に変更が加えられていない(className と data-* の追加のみ) | |
| ユーザーがアップグレード全体をひとまとまりとして採用または却下できる |
スロップカタログ - よくあるパターンと改善方法
よくある見た目の問題をすばやく確認できます。パターンを見つけたら、改善方法を適用してください。
| スロップのパターン | 問題点 | 改善方法 |
|---|---|---|
font-family: Arial, Helvetica, sans-serif | 本文用フォントを見出しにも使用している | 見出し用に Outfit/Satoshi/Cabinet Grotesk をインポートする |
color: #000; background: #fff | 純粋な黒と白の組み合わせ | color: #1a1a1a; background: #FAFAF9 |
background: #0d6efd | Bootstrap のプライマリブルー | ブランドに合ったアクセントカラーを選ぶ |
box-shadow: 0 2px 4px rgba(0,0,0,0.1) | ありきたりなデフォルトのシャドウ | 通常時は box-shadow: 0 1px 3px rgba(0,0,0,0.04) にし、ホバー時に広げる |
border-radius: 4px | Bootstrap のデフォルトの角丸 | 角丸の基準を統一する:8/12/16/9999 |
border: 1px solid #dee2e6 | 冷たい印象のグレーのボーダー | border: 1px solid rgba(0,0,0,0.08) - 温かみがあり、控えめ |
transition: all 0.15s ease-in-out | Bootstrap のデフォルトのトランジション | transition: all 0.3s cubic-bezier(0.22, 1, 0.36, 1) |
カードの padding: 1rem | 余裕のないカードのパディング | 最低でも padding: 2rem - カードにはゆとりが必要 |
見出しと本文の間隔 margin-bottom: 0.5rem | 見出しとの間隔が窮屈 | margin-bottom: 1.5rem - 見出しをしっかり際立たせる |
カードグリッドの gap: 1rem | グリッドの間隔が狭い | 最低でも gap: 1.5rem - カード同士に間隔を設ける |
ボタンに :hover がない | 反応のない、操作感に乏しいボタン | translateY(-2px) + シャドウの拡大 + カスタムイージング |
カードに :hover がない | 静的で生気のないカード | translateY(-4px) + シャドウの拡大 + ボーダーのグロー |
| 表示時のアニメーションがない | 静的な状態で瞬時に表示される | ファーストビュー内の要素に、フェードアップとぼかし解除を段階的に適用する |
h1 { font-size: 2rem } | 見出しが小さすぎる | font-size: clamp(2.25rem, 5vw, 3.75rem) |
見出しの line-height: 1.2 | 見出しの行間が広すぎる | line-height: 1.05 - 引き締まった構築的な印象にする |
| 見出しに letter-spacing がない | 字間が広く、素人っぽい印象 | letter-spacing: -0.03em - 字間を詰める |
セクションのパディングが py-3 | セクションが窮屈 | py-20 md:py-32 - セクションは段落ではなく章 |
| ゼブラストライプのテーブル | 2010 年代の Bootstrap 風 | ストライプをなくし、ホバー時に行を控えめにハイライトする |
.badge { font-size: 0.75em } | バッジが小さく窮屈 | padding: 0.25rem 0.75rem; font-size: 0.75rem; border-radius: 9999px |
入力欄に ::placeholder のスタイルがない | デフォルトのグレーのプレースホルダー | color: var(--color-text-3); opacity: 0.7 |
エッジケース
コードで CSS-in-JS(styled-components、Emotion)を使用している場合
- トークンの指定を CSS カスタムプロパティのレイヤー(
:root変数)として適用します - トークンを参照するグローバルな
createGlobalStyleで、styled-component のスタイルを上書きします - コンポーネントレベルの上書きには、styled-component の上書きを記述した
gold-overrides.tsファイルを追加します - 既存の styled-component の定義をインラインで変更してはいけません。代わりに上書き用のラッパーを作成します
コードで Tailwind CSS を使用している場合
tailwind.config.jsのthemeをゴールドトークン(色、フォント、角丸、影)で上書きします- 雰囲気や動きの追加には
@layer utilitiesを使用します - Tailwind のカラークラスを体系的に置き換えます。例:
text-gray-900→text-[#1a1a1a]、または設定ファイルでカスタムカラーを定義します shadow-sm/shadow-mdを設定ファイルのカスタムシャドウ値に置き換えますrounded/rounded-mdを設定ファイルのカスタム角丸に置き換えます- Tailwind ではコンポーネントファイルごとに変更するのではなく、設定ファイルのレベルでデザインシステムを変更できるため、この方法が最も安全です
コードで Material UI / Chakra / Ant Design を使用している場合
- テーマプロバイダーの設定を上書きします。これらのライブラリはテーマのカスタマイズを前提に設計されています
- テーマオブジェクト(色、タイポグラフィ、間隔、角丸、影)を中心に調整します
- 雰囲気や動きの追加には、コンポーネントレベルの
sx上書きやstyled()ラッパーを追加します - コンポーネントライブラリの構造に逆らわず、テーマシステムの範囲内で作業します
コードがバニラ HTML/CSS(フレームワークなし)の場合
<head>内の最後のスタイルシートとしてgold.cssを追加します- 既存のスタイルを編集せずに上書きできるよう、CSS の詳細度を利用します
- 既存のスタイルで
!importantが使われている場合は、上書きにも!importantが必要になることがあります(残念ですが必要です) </body>の前に<script>として ScrollReveal スクリプトを追加します
ユーザーが「ヒーロー部分だけを見栄えよくして」と言った場合
サイト全体をアップグレードしてはいけません。次の手順をヒーローセクションだけに適用します。
- ヒーローコンポーネントを監査します
- 現在のデザイン上の判断を抽出します
- そのセクションに適用するゴールドへの置き換えを指定します
- そのセクションの CSS だけを外科的に変更します
- ほかの部分に問題がないことを確認します
ユーザーが指定した範囲を尊重してください。求められていない範囲までアップグレードすると、時間を浪費し、リスクを招きます。
中核となる原則
患者を生かさなければなりません。 動作しなくなった美しいアプリは、動作する見た目の悪いアプリよりも劣ります。変更のレイヤーごとにテストしてください。機能性は常に美しさより優先されます。
CSS の上書きを行い、JS を書き換えてはいけません。 使用するのは className の変更、新しい CSS ファイル、データ属性です。コンポーネントのロジックを書き換えたり、状態管理をリファクタリングしたり、JSX の階層を再構成したりしてはいけません。見た目のアップグレードに JS ロジックの変更が必要な場合は、CSS だけで実現する方法を探してください。
gold.css は単一のまとまりです。 ビジュアルアップグレード全体を含み、最後に読み込まれる 1 つのファイルです。ユーザーは 1 つのインポートを追加するだけでアップグレードでき、削除するだけで元に戻せます。これが外科的なアプローチです。導入も撤去も簡潔にします。
無機質さより温かみを。 プレミアムなデザインには、オフホワイトの背景、温かみのあるほぼ黒のテキスト、温かみのあるグレーの境界線など、温かい色調が使われています。冷たい中間色(純粋なグレー、純白、純黒)は、無機質で、デザインされていない印象を与えます。最も手早く効果が得られるのは、色温度を変えることです。
余白が秘訣です。 最も劇的な効果をもたらす変更は、セクションのパディング、カードのパディング、見出し間の間隔、グリッドの間隔を広げることです。窮屈な余白は素人っぽいデザインの特徴です。ゆとりのある余白は、プレミアムなデザインの特徴です。迷ったら、余白を増やしてください。
ディスプレイフォントが、素人とプロの差を生みます。 見出しのフォントを Arial/system-ui から適切なディスプレイフォント(Outfit、Satoshi、Cabinet Grotesk、Clash Display)に変えることは、単独で最も大きな効果をもたらす変更です。それ以外のすべては、この土台の上に成り立ちます。