mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
## Core Changes
- Pure matrix mode (style × layout combinations)
- Removed standard/creative mode selection
- Simplified parameters: --style-variants, --layout-variants
- Path corrections: standalone mode uses .workflow/.scratchpad/
## Modified Files
- auto.md: Simplified Phase 3, corrected paths
- generate.md: Complete rewrite for matrix-only mode
- consolidate.md: Path corrections for standalone mode
- extract.md: Path corrections for standalone mode
## New Files
- CHANGELOG-v4.1.0.md: Complete changelog
- _template-compare-matrix.html: Interactive visualization template
## Breaking Changes
- Deprecated: --variants, --creative-variants
- New: --style-variants, --layout-variants
- File naming: {page}-style-{s}-layout-{l}.html
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
693 lines
18 KiB
HTML
693 lines
18 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>UI Design Matrix Comparison - {{run_id}}</title>
|
|
<style>
|
|
:root {
|
|
--color-primary: #2563eb;
|
|
--color-bg: #f9fafb;
|
|
--color-surface: #ffffff;
|
|
--color-border: #e5e7eb;
|
|
--color-text: #1f2937;
|
|
--color-text-secondary: #6b7280;
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
background: var(--color-bg);
|
|
color: var(--color-text);
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1600px;
|
|
margin: 0 auto;
|
|
padding: 2rem;
|
|
}
|
|
|
|
header {
|
|
background: var(--color-surface);
|
|
padding: 1.5rem 2rem;
|
|
border-radius: 0.5rem;
|
|
margin-bottom: 2rem;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
h1 {
|
|
color: var(--color-primary);
|
|
font-size: 1.875rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.meta {
|
|
color: var(--color-text-secondary);
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.controls {
|
|
background: var(--color-surface);
|
|
padding: 1.5rem;
|
|
border-radius: 0.5rem;
|
|
margin-bottom: 2rem;
|
|
display: flex;
|
|
gap: 1.5rem;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.control-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
label {
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
select, button {
|
|
padding: 0.5rem 1rem;
|
|
border: 1px solid var(--color-border);
|
|
border-radius: 0.375rem;
|
|
font-size: 0.875rem;
|
|
background: white;
|
|
cursor: pointer;
|
|
}
|
|
|
|
select:focus, button:focus {
|
|
outline: 2px solid var(--color-primary);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
button {
|
|
background: var(--color-primary);
|
|
color: white;
|
|
border: none;
|
|
font-weight: 500;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
button:hover {
|
|
background: #1d4ed8;
|
|
}
|
|
|
|
.matrix-container {
|
|
background: var(--color-surface);
|
|
border-radius: 0.5rem;
|
|
overflow: hidden;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.matrix-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.matrix-table th,
|
|
.matrix-table td {
|
|
border: 1px solid var(--color-border);
|
|
padding: 0.75rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.matrix-table th {
|
|
background: #f3f4f6;
|
|
font-weight: 600;
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.matrix-table thead th {
|
|
background: var(--color-primary);
|
|
color: white;
|
|
}
|
|
|
|
.matrix-table tbody th {
|
|
background: #f9fafb;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.prototype-cell {
|
|
position: relative;
|
|
padding: 0;
|
|
height: 400px;
|
|
vertical-align: top;
|
|
}
|
|
|
|
.prototype-wrapper {
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.prototype-header {
|
|
padding: 0.5rem;
|
|
background: #f9fafb;
|
|
border-bottom: 1px solid var(--color-border);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.prototype-title {
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.prototype-actions {
|
|
display: flex;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.icon-btn {
|
|
width: 24px;
|
|
height: 24px;
|
|
padding: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--color-text-secondary);
|
|
cursor: pointer;
|
|
border-radius: 0.25rem;
|
|
}
|
|
|
|
.icon-btn:hover {
|
|
background: #e5e7eb;
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.icon-btn.selected {
|
|
background: var(--color-primary);
|
|
color: white;
|
|
}
|
|
|
|
.prototype-iframe-container {
|
|
flex: 1;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.prototype-iframe {
|
|
width: 100%;
|
|
height: 100%;
|
|
border: none;
|
|
transform-origin: top left;
|
|
}
|
|
|
|
.zoom-info {
|
|
position: absolute;
|
|
bottom: 0.5rem;
|
|
right: 0.5rem;
|
|
background: rgba(0,0,0,0.7);
|
|
color: white;
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 0.25rem;
|
|
font-size: 0.75rem;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.tabs {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
margin-bottom: 1rem;
|
|
border-bottom: 2px solid var(--color-border);
|
|
}
|
|
|
|
.tab {
|
|
padding: 0.75rem 1.5rem;
|
|
background: transparent;
|
|
border: none;
|
|
border-bottom: 2px solid transparent;
|
|
cursor: pointer;
|
|
font-weight: 500;
|
|
color: var(--color-text-secondary);
|
|
margin-bottom: -2px;
|
|
}
|
|
|
|
.tab.active {
|
|
color: var(--color-primary);
|
|
border-bottom-color: var(--color-primary);
|
|
}
|
|
|
|
.tab-content {
|
|
display: none;
|
|
}
|
|
|
|
.tab-content.active {
|
|
display: block;
|
|
}
|
|
|
|
.fullscreen-overlay {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0,0,0,0.95);
|
|
z-index: 1000;
|
|
padding: 2rem;
|
|
}
|
|
|
|
.fullscreen-overlay.active {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.fullscreen-header {
|
|
color: white;
|
|
margin-bottom: 1rem;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.fullscreen-iframe {
|
|
flex: 1;
|
|
border: none;
|
|
background: white;
|
|
border-radius: 0.5rem;
|
|
}
|
|
|
|
.close-btn {
|
|
background: rgba(255,255,255,0.2);
|
|
color: white;
|
|
border: none;
|
|
padding: 0.5rem 1rem;
|
|
border-radius: 0.375rem;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.close-btn:hover {
|
|
background: rgba(255,255,255,0.3);
|
|
}
|
|
|
|
.selection-summary {
|
|
background: #fef3c7;
|
|
border-left: 4px solid #f59e0b;
|
|
padding: 1rem;
|
|
margin-bottom: 2rem;
|
|
border-radius: 0.375rem;
|
|
}
|
|
|
|
.selection-summary h3 {
|
|
color: #92400e;
|
|
margin-bottom: 0.5rem;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.selection-list {
|
|
list-style: none;
|
|
color: #78350f;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.selection-list li {
|
|
padding: 0.25rem 0;
|
|
}
|
|
|
|
@media (max-width: 1200px) {
|
|
.prototype-cell {
|
|
height: 300px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header>
|
|
<h1>🎨 UI Design Matrix Comparison</h1>
|
|
<div class="meta">
|
|
<strong>Run ID:</strong> {{run_id}} |
|
|
<strong>Session:</strong> {{session_id}} |
|
|
<strong>Generated:</strong> {{timestamp}}
|
|
</div>
|
|
</header>
|
|
|
|
<div class="controls">
|
|
<div class="control-group">
|
|
<label for="page-select">Page:</label>
|
|
<select id="page-select">
|
|
<!-- Populated by JavaScript -->
|
|
</select>
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
<label for="zoom-level">Zoom:</label>
|
|
<select id="zoom-level">
|
|
<option value="0.25">25%</option>
|
|
<option value="0.5">50%</option>
|
|
<option value="0.75">75%</option>
|
|
<option value="1" selected>100%</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
<label> </label>
|
|
<button id="sync-scroll-toggle">🔗 Sync Scroll: ON</button>
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
<label> </label>
|
|
<button id="export-selection">📥 Export Selection</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="selection-summary" class="selection-summary" style="display:none">
|
|
<h3>Selected Prototypes (<span id="selection-count">0</span>)</h3>
|
|
<ul id="selection-list" class="selection-list"></ul>
|
|
</div>
|
|
|
|
<div class="tabs">
|
|
<button class="tab active" data-tab="matrix">Matrix View</button>
|
|
<button class="tab" data-tab="comparison">Side-by-Side</button>
|
|
<button class="tab" data-tab="runs">Compare Runs</button>
|
|
</div>
|
|
|
|
<div class="tab-content active" data-content="matrix">
|
|
<div class="matrix-container">
|
|
<table class="matrix-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Style ↓ / Layout →</th>
|
|
<th>Layout 1</th>
|
|
<th>Layout 2</th>
|
|
<th>Layout 3</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="matrix-body">
|
|
<!-- Populated by JavaScript -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-content" data-content="comparison">
|
|
<p>Select two prototypes from the matrix to compare side-by-side.</p>
|
|
<div id="comparison-view"></div>
|
|
</div>
|
|
|
|
<div class="tab-content" data-content="runs">
|
|
<p>Compare the same prototype across different runs.</p>
|
|
<div id="runs-comparison"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="fullscreen-overlay" class="fullscreen-overlay">
|
|
<div class="fullscreen-header">
|
|
<h2 id="fullscreen-title"></h2>
|
|
<button class="close-btn" onclick="closeFullscreen()">✕ Close</button>
|
|
</div>
|
|
<iframe id="fullscreen-iframe" class="fullscreen-iframe"></iframe>
|
|
</div>
|
|
|
|
<script>
|
|
// Configuration - Replace with actual values during generation
|
|
const CONFIG = {
|
|
runId: "{{run_id}}",
|
|
sessionId: "{{session_id}}",
|
|
styleVariants: {{style_variants}}, // e.g., 3
|
|
layoutVariants: {{layout_variants}}, // e.g., 3
|
|
pages: {{pages_json}}, // e.g., ["dashboard", "auth"]
|
|
basePath: "." // Relative path to prototypes
|
|
};
|
|
|
|
// State
|
|
let state = {
|
|
currentPage: CONFIG.pages[0],
|
|
zoomLevel: 1,
|
|
syncScroll: true,
|
|
selected: new Set(),
|
|
fullscreenSrc: null
|
|
};
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
populatePageSelect();
|
|
renderMatrix();
|
|
setupEventListeners();
|
|
loadSelectionFromStorage();
|
|
});
|
|
|
|
function populatePageSelect() {
|
|
const select = document.getElementById('page-select');
|
|
CONFIG.pages.forEach(page => {
|
|
const option = document.createElement('option');
|
|
option.value = page;
|
|
option.textContent = capitalize(page);
|
|
select.appendChild(option);
|
|
});
|
|
}
|
|
|
|
function renderMatrix() {
|
|
const tbody = document.getElementById('matrix-body');
|
|
tbody.innerHTML = '';
|
|
|
|
for (let s = 1; s <= CONFIG.styleVariants; s++) {
|
|
const row = document.createElement('tr');
|
|
|
|
// Style header cell
|
|
const headerCell = document.createElement('th');
|
|
headerCell.textContent = `Style ${s}`;
|
|
row.appendChild(headerCell);
|
|
|
|
// Prototype cells for each layout
|
|
for (let l = 1; l <= CONFIG.layoutVariants; l++) {
|
|
const cell = document.createElement('td');
|
|
cell.className = 'prototype-cell';
|
|
|
|
const filename = `${state.currentPage}-style-${s}-layout-${l}.html`;
|
|
const id = `${state.currentPage}-s${s}-l${l}`;
|
|
|
|
cell.innerHTML = `
|
|
<div class="prototype-wrapper">
|
|
<div class="prototype-header">
|
|
<span class="prototype-title">S${s}L${l}</span>
|
|
<div class="prototype-actions">
|
|
<button class="icon-btn select-btn" data-id="${id}" title="Select">
|
|
${state.selected.has(id) ? '★' : '☆'}
|
|
</button>
|
|
<button class="icon-btn" onclick="openFullscreen('${filename}', 'Style ${s} Layout ${l}')" title="Fullscreen">
|
|
⛶
|
|
</button>
|
|
<button class="icon-btn" onclick="openInNewTab('${filename}')" title="Open in new tab">
|
|
↗
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="prototype-iframe-container">
|
|
<iframe
|
|
class="prototype-iframe"
|
|
src="${filename}"
|
|
data-cell="s${s}-l${l}"
|
|
style="transform: scale(${state.zoomLevel});"
|
|
></iframe>
|
|
<div class="zoom-info">${Math.round(state.zoomLevel * 100)}%</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
row.appendChild(cell);
|
|
}
|
|
|
|
tbody.appendChild(row);
|
|
}
|
|
|
|
// Re-attach selection event listeners
|
|
document.querySelectorAll('.select-btn').forEach(btn => {
|
|
btn.addEventListener('click', (e) => toggleSelection(e.target.dataset.id, e.target));
|
|
});
|
|
|
|
// Setup scroll sync
|
|
if (state.syncScroll) {
|
|
setupScrollSync();
|
|
}
|
|
}
|
|
|
|
function setupScrollSync() {
|
|
const iframes = document.querySelectorAll('.prototype-iframe');
|
|
let isScrolling = false;
|
|
|
|
iframes.forEach(iframe => {
|
|
iframe.addEventListener('load', () => {
|
|
const iframeWindow = iframe.contentWindow;
|
|
|
|
iframe.contentDocument.addEventListener('scroll', (e) => {
|
|
if (!state.syncScroll || isScrolling) return;
|
|
|
|
isScrolling = true;
|
|
const scrollTop = iframe.contentDocument.documentElement.scrollTop;
|
|
const scrollLeft = iframe.contentDocument.documentElement.scrollLeft;
|
|
|
|
iframes.forEach(otherIframe => {
|
|
if (otherIframe !== iframe && otherIframe.contentDocument) {
|
|
otherIframe.contentDocument.documentElement.scrollTop = scrollTop;
|
|
otherIframe.contentDocument.documentElement.scrollLeft = scrollLeft;
|
|
}
|
|
});
|
|
|
|
setTimeout(() => { isScrolling = false; }, 50);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function setupEventListeners() {
|
|
// Page selector
|
|
document.getElementById('page-select').addEventListener('change', (e) => {
|
|
state.currentPage = e.target.value;
|
|
renderMatrix();
|
|
});
|
|
|
|
// Zoom level
|
|
document.getElementById('zoom-level').addEventListener('change', (e) => {
|
|
state.zoomLevel = parseFloat(e.target.value);
|
|
renderMatrix();
|
|
});
|
|
|
|
// Sync scroll toggle
|
|
document.getElementById('sync-scroll-toggle').addEventListener('click', (e) => {
|
|
state.syncScroll = !state.syncScroll;
|
|
e.target.textContent = `🔗 Sync Scroll: ${state.syncScroll ? 'ON' : 'OFF'}`;
|
|
if (state.syncScroll) setupScrollSync();
|
|
});
|
|
|
|
// Export selection
|
|
document.getElementById('export-selection').addEventListener('click', exportSelection);
|
|
|
|
// Tab switching
|
|
document.querySelectorAll('.tab').forEach(tab => {
|
|
tab.addEventListener('click', (e) => {
|
|
const tabName = e.target.dataset.tab;
|
|
switchTab(tabName);
|
|
});
|
|
});
|
|
}
|
|
|
|
function toggleSelection(id, btn) {
|
|
if (state.selected.has(id)) {
|
|
state.selected.delete(id);
|
|
btn.textContent = '☆';
|
|
btn.classList.remove('selected');
|
|
} else {
|
|
state.selected.add(id);
|
|
btn.textContent = '★';
|
|
btn.classList.add('selected');
|
|
}
|
|
|
|
updateSelectionSummary();
|
|
saveSelectionToStorage();
|
|
}
|
|
|
|
function updateSelectionSummary() {
|
|
const summary = document.getElementById('selection-summary');
|
|
const count = document.getElementById('selection-count');
|
|
const list = document.getElementById('selection-list');
|
|
|
|
count.textContent = state.selected.size;
|
|
|
|
if (state.selected.size > 0) {
|
|
summary.style.display = 'block';
|
|
list.innerHTML = Array.from(state.selected)
|
|
.map(id => `<li>${id}</li>`)
|
|
.join('');
|
|
} else {
|
|
summary.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function saveSelectionToStorage() {
|
|
localStorage.setItem(`selection-${CONFIG.runId}`, JSON.stringify(Array.from(state.selected)));
|
|
}
|
|
|
|
function loadSelectionFromStorage() {
|
|
const stored = localStorage.getItem(`selection-${CONFIG.runId}`);
|
|
if (stored) {
|
|
state.selected = new Set(JSON.parse(stored));
|
|
updateSelectionSummary();
|
|
}
|
|
}
|
|
|
|
function exportSelection() {
|
|
const report = {
|
|
runId: CONFIG.runId,
|
|
sessionId: CONFIG.sessionId,
|
|
timestamp: new Date().toISOString(),
|
|
selections: Array.from(state.selected).map(id => ({
|
|
id,
|
|
file: `${id.replace(/-s(\d+)-l(\d+)/, '-style-$1-layout-$2')}.html`
|
|
}))
|
|
};
|
|
|
|
const blob = new Blob([JSON.stringify(report, null, 2)], { type: 'application/json' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `selection-${CONFIG.runId}.json`;
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
|
|
alert(`Exported ${state.selected.size} selections to selection-${CONFIG.runId}.json`);
|
|
}
|
|
|
|
function openFullscreen(src, title) {
|
|
const overlay = document.getElementById('fullscreen-overlay');
|
|
const iframe = document.getElementById('fullscreen-iframe');
|
|
const titleEl = document.getElementById('fullscreen-title');
|
|
|
|
iframe.src = src;
|
|
titleEl.textContent = title;
|
|
overlay.classList.add('active');
|
|
state.fullscreenSrc = src;
|
|
}
|
|
|
|
function closeFullscreen() {
|
|
const overlay = document.getElementById('fullscreen-overlay');
|
|
const iframe = document.getElementById('fullscreen-iframe');
|
|
|
|
overlay.classList.remove('active');
|
|
iframe.src = '';
|
|
state.fullscreenSrc = null;
|
|
}
|
|
|
|
function openInNewTab(src) {
|
|
window.open(src, '_blank');
|
|
}
|
|
|
|
function switchTab(tabName) {
|
|
document.querySelectorAll('.tab').forEach(tab => {
|
|
tab.classList.toggle('active', tab.dataset.tab === tabName);
|
|
});
|
|
|
|
document.querySelectorAll('.tab-content').forEach(content => {
|
|
content.classList.toggle('active', content.dataset.content === tabName);
|
|
});
|
|
}
|
|
|
|
function capitalize(str) {
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
}
|
|
|
|
// Close fullscreen on ESC key
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape' && state.fullscreenSrc) {
|
|
closeFullscreen();
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|