feat: comparison board UI improvements — option headers, pick confirmation, grid view

Three changes to the design comparison board:

1. Pick confirmation: selecting "Pick" on Option A shows "We'll move
   forward with Option A" in green, plus a status line above the submit
   button repeating the choice.

2. Clear option headers: each variant now has "Option A" in bold with a
   subtitle above the image, instead of just the raw image.

3. View toggle: top-right Large/Grid buttons switch between single-column
   (default) and 3-across grid view.

Also restructured the bottom section into a 2-column grid: submit/overall
feedback on the left, regenerate controls on the right.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-03-27 09:46:41 -06:00
parent ebadef024a
commit e665183265
+185 -150
View File
@@ -28,11 +28,16 @@ export function generateCompareHtml(images: string[]): string {
return `
<div class="variant" data-variant="${label}">
<img src="data:image/${ext};base64,${imgData}" alt="Variant ${label}" />
<div class="variant-header">
<span class="variant-label">Option ${label}</span>
<span class="variant-desc" id="variant-desc-${label}">Design direction ${label}</span>
</div>
<img src="data:image/${ext};base64,${imgData}" alt="Option ${label}" />
<div class="variant-controls">
<label class="pick-label">
<input type="radio" name="preferred" value="${label}" />
<span class="pick-text">Pick</span>
<span class="pick-confirm" style="display:none;">We'll move forward with Option ${label}</span>
</label>
<div class="stars" data-variant="${label}">
${[1,2,3,4,5].map(n => `<span class="star" data-value="${n}">★</span>`).join("")}
@@ -66,9 +71,78 @@ export function generateCompareHtml(images: string[]): string {
align-items: center;
}
.header h1 { font-size: 16px; font-weight: 600; }
.header .meta { font-size: 13px; color: #999; }
.header .meta { font-size: 13px; color: #999; display: flex; align-items: center; gap: 12px; }
.variants { max-width: 1200px; margin: 0 auto; padding: 0 24px; }
.view-toggle {
display: flex;
gap: 2px;
background: #f0f0f0;
border-radius: 6px;
padding: 2px;
}
.view-toggle button {
padding: 4px 10px;
border: none;
background: none;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
color: #666;
font-weight: 500;
}
.view-toggle button.active {
background: #fff;
color: #333;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.variants { max-width: 1400px; margin: 0 auto; padding: 20px 24px; }
.variants.grid-view {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.variants.grid-view .variant {
border-bottom: none;
border: 1px solid #e5e5e5;
border-radius: 8px;
padding: 20px;
}
.variants.grid-view .variant-controls {
flex-direction: column;
align-items: stretch;
gap: 10px;
}
.variants.grid-view .variant-controls .pick-label {
padding: 8px 0 4px;
}
.variants.grid-view .feedback-input { min-width: 0; width: 100%; }
.variants.grid-view .more-like-this { align-self: flex-start; }
.variants.grid-view .variant-header { margin-bottom: 12px; }
.variant-header {
display: flex;
align-items: baseline;
gap: 8px;
margin-bottom: 12px;
}
.variant-label {
font-size: 15px;
font-weight: 700;
color: #111;
letter-spacing: -0.01em;
}
.variant-desc {
font-size: 13px;
color: #888;
}
.pick-confirm {
font-size: 13px;
color: #2a7d2a;
font-weight: 500;
margin-left: 4px;
}
.variant {
border-bottom: 1px solid #e5e5e5;
@@ -135,47 +209,79 @@ export function generateCompareHtml(images: string[]): string {
}
.more-like-this:hover { border-color: #999; color: #333; }
.overall-section {
max-width: 1200px;
.bottom-section {
max-width: 1400px;
margin: 0 auto;
padding: 16px 24px;
border-top: 1px solid #e5e5e5;
padding: 24px 24px 32px;
display: grid;
grid-template-columns: 1fr 380px;
gap: 24px;
}
.overall-section summary {
font-size: 14px;
color: #666;
cursor: pointer;
padding: 8px 0;
.submit-column {}
.submit-column h3 {
font-size: 15px;
font-weight: 700;
color: #111;
margin-bottom: 4px;
}
.submit-column .direction-hint {
font-size: 13px;
color: #888;
margin-bottom: 10px;
line-height: 1.5;
}
.overall-textarea {
width: 100%;
padding: 8px 10px;
padding: 10px 12px;
border: 1px solid #e5e5e5;
border-radius: 4px;
border-radius: 6px;
font-size: 13px;
resize: vertical;
min-height: 60px;
margin-top: 8px;
min-height: 80px;
outline: none;
font-family: inherit;
line-height: 1.5;
}
.overall-textarea:focus { border-color: #999; }
.submit-status {
font-size: 14px;
font-weight: 600;
color: #111;
margin: 12px 0;
min-height: 20px;
}
.submit-btn {
padding: 10px 24px;
background: #000;
color: #fff;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
width: 100%;
}
.submit-btn:hover { background: #333; }
.submit-btn:disabled { background: #ccc; cursor: not-allowed; }
.regenerate-bar {
.regen-column {
background: #f7f7f7;
padding: 16px 24px;
margin-top: 8px;
border-radius: 8px;
padding: 20px;
}
.regenerate-bar .inner {
max-width: 1200px;
margin: 0 auto;
.regen-column h3 {
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
}
.regenerate-bar h3 { font-size: 14px; font-weight: 600; margin-bottom: 10px; }
.regen-controls {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
margin-bottom: 10px;
}
.regen-chiclet {
padding: 6px 14px;
@@ -188,46 +294,27 @@ export function generateCompareHtml(images: string[]): string {
.regen-chiclet:hover { border-color: #999; }
.regen-chiclet.active { border-color: #000; background: #f0f0f0; }
.regen-custom {
flex: 1;
min-width: 150px;
padding: 6px 10px;
width: 100%;
padding: 8px 10px;
border: 1px solid #e5e5e5;
border-radius: 4px;
border-radius: 6px;
font-size: 13px;
outline: none;
margin-bottom: 10px;
}
.regen-custom:focus { border-color: #999; }
.regen-btn {
padding: 6px 16px;
padding: 8px 16px;
background: #fff;
border: 1px solid #e5e5e5;
border-radius: 4px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 13px;
cursor: pointer;
font-weight: 600;
width: 100%;
}
.regen-btn:hover { border-color: #000; }
.submit-bar {
max-width: 1200px;
margin: 0 auto;
padding: 16px 24px;
display: flex;
justify-content: flex-end;
}
.submit-btn {
padding: 10px 24px;
background: #000;
color: #fff;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
}
.submit-btn:hover { background: #333; }
.submit-btn:disabled { background: #ccc; cursor: not-allowed; }
.success-msg {
display: none;
max-width: 1200px;
@@ -243,43 +330,6 @@ export function generateCompareHtml(images: string[]): string {
/* Hidden result elements for agent polling */
#status, #feedback-result { display: none; }
/* Remix section */
.remix-bar {
background: #fafafa;
padding: 16px 24px;
border-top: 1px solid #e5e5e5;
}
.remix-bar .inner { max-width: 1200px; margin: 0 auto; }
.remix-bar h3 { font-size: 14px; font-weight: 600; margin-bottom: 10px; }
.remix-grid {
display: grid;
grid-template-columns: auto repeat(${images.length}, 1fr);
gap: 8px 16px;
align-items: center;
font-size: 13px;
}
.remix-grid .remix-header { font-weight: 600; text-align: center; }
.remix-grid .remix-label { color: #666; }
.remix-grid label {
display: flex;
justify-content: center;
cursor: pointer;
}
.remix-grid input[type="radio"] { accent-color: #000; }
.remix-btn {
margin-top: 12px;
padding: 8px 18px;
background: #000;
color: #fff;
border: none;
border-radius: 4px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
}
.remix-btn:hover { background: #333; }
.remix-btn:disabled { background: #ccc; cursor: not-allowed; }
/* Skeleton loading state */
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
@@ -298,53 +348,40 @@ export function generateCompareHtml(images: string[]): string {
<div class="header">
<h1>Design Exploration</h1>
<span class="meta">${images.length} variants</span>
<span class="meta">
${images.length} options
<span class="view-toggle">
<button class="active" data-view="list">Large</button>
<button data-view="grid">Grid</button>
</span>
</span>
</div>
<div class="variants">
${variantCards}
</div>
<div class="overall-section">
<details>
<summary>Overall direction (optional)</summary>
<div class="bottom-section">
<div class="submit-column">
<h3>Overall direction</h3>
<p class="direction-hint">e.g. "Use A's layout with C's fox icon" or "Make it more minimal" or "I want the problem statement text but bigger"</p>
<textarea class="overall-textarea" id="overall-feedback"
placeholder="Any overall notes about direction?"></textarea>
</details>
</div>
<div class="regenerate-bar">
<div class="inner">
placeholder="Combine elements, request changes, or describe what you want..."></textarea>
<div class="submit-status" id="submit-status"></div>
<button class="submit-btn" id="submit-btn">Take my feedback and continue →</button>
</div>
<div class="regen-column">
<h3>Want to explore more?</h3>
<div class="regen-controls">
<button class="regen-chiclet" data-action="different">Totally different</button>
<button class="regen-chiclet" data-action="match">Match my design</button>
<input type="text" class="regen-custom" id="regen-custom-input"
placeholder="Tell us what you want different..." />
<button class="regen-btn" id="regen-btn">Regenerate →</button>
</div>
<input type="text" class="regen-custom" id="regen-custom-input"
placeholder="Tell us what you want different..." />
<button class="regen-btn" id="regen-btn">Regenerate →</button>
</div>
</div>
<div class="remix-bar">
<div class="inner">
<h3>Remix — mix elements from different variants</h3>
<div class="remix-grid">
<div></div>
${images.map((_, i) => `<div class="remix-header">${variantLabels[i]}</div>`).join("")}
${["Layout", "Colors", "Typography", "Spacing"].map(element => `
<div class="remix-label">${element}</div>
${images.map((_, i) => `<label><input type="radio" name="remix-${element.toLowerCase()}" value="${variantLabels[i]}" /></label>`).join("")}
`).join("")}
</div>
<button class="remix-btn" id="remix-btn" disabled>Remix →</button>
</div>
</div>
<div class="submit-bar">
<button class="submit-btn" id="submit-btn">✓ Submit</button>
</div>
<div class="success-msg" id="success-msg">
Feedback submitted! Return to your coding agent.
</div>
@@ -354,6 +391,35 @@ export function generateCompareHtml(images: string[]): string {
<div id="feedback-result"></div>
<script>
// View toggle
document.querySelectorAll('.view-toggle button').forEach(function(btn) {
btn.addEventListener('click', function() {
document.querySelectorAll('.view-toggle button').forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active');
var variants = document.querySelector('.variants');
if (btn.dataset.view === 'grid') {
variants.classList.add('grid-view');
} else {
variants.classList.remove('grid-view');
}
});
});
// Pick confirmation
document.querySelectorAll('input[name="preferred"]').forEach(function(radio) {
radio.addEventListener('change', function() {
// Hide all confirmations first
document.querySelectorAll('.pick-confirm').forEach(function(el) { el.style.display = 'none'; });
document.querySelectorAll('.pick-text').forEach(function(el) { el.style.display = ''; });
// Show confirmation on the selected one
var label = radio.closest('.pick-label');
label.querySelector('.pick-text').style.display = 'none';
label.querySelector('.pick-confirm').style.display = '';
// Update submit status
document.getElementById('submit-status').textContent = "We'll run with Option " + radio.value;
});
});
// Star rating
document.querySelectorAll('.stars').forEach(starsEl => {
const stars = starsEl.querySelectorAll('.star');
@@ -369,37 +435,6 @@ export function generateCompareHtml(images: string[]): string {
});
});
// Remix radio buttons — enable remix button when at least one element is selected
document.querySelectorAll('.remix-grid input[type="radio"]').forEach(function(radio) {
radio.addEventListener('change', function() {
var anySelected = document.querySelector('.remix-grid input[type="radio"]:checked');
document.getElementById('remix-btn').disabled = !anySelected;
});
});
// Remix button
document.getElementById('remix-btn').addEventListener('click', function() {
var remixSpec = {};
['layout', 'colors', 'typography', 'spacing'].forEach(function(element) {
var selected = document.querySelector('input[name="remix-' + element + '"]:checked');
if (selected) remixSpec[element] = selected.value;
});
if (Object.keys(remixSpec).length === 0) return;
var feedback = collectFeedback();
feedback.regenerated = true;
feedback.regenerateAction = 'remix';
feedback.remixSpec = remixSpec;
document.getElementById('feedback-result').textContent = JSON.stringify(feedback);
document.getElementById('status').textContent = 'regenerate';
postFeedback(feedback).then(function(result) {
if (result && result.received) {
showRegeneratingState();
} else if (window.__GSTACK_SERVER_URL) {
showPostFailure(feedback);
}
});
});
// Regenerate chiclets (toggle active)
document.querySelectorAll('.regen-chiclet').forEach(chiclet => {
chiclet.addEventListener('click', () => {