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

View File

@@ -28,11 +28,16 @@ export function generateCompareHtml(images: string[]): string {
return ` return `
<div class="variant" data-variant="${label}"> <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"> <div class="variant-controls">
<label class="pick-label"> <label class="pick-label">
<input type="radio" name="preferred" value="${label}" /> <input type="radio" name="preferred" value="${label}" />
<span class="pick-text">Pick</span> <span class="pick-text">Pick</span>
<span class="pick-confirm" style="display:none;">We'll move forward with Option ${label}</span>
</label> </label>
<div class="stars" data-variant="${label}"> <div class="stars" data-variant="${label}">
${[1,2,3,4,5].map(n => `<span class="star" data-value="${n}">★</span>`).join("")} ${[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; align-items: center;
} }
.header h1 { font-size: 16px; font-weight: 600; } .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 { .variant {
border-bottom: 1px solid #e5e5e5; border-bottom: 1px solid #e5e5e5;
@@ -135,47 +209,79 @@ export function generateCompareHtml(images: string[]): string {
} }
.more-like-this:hover { border-color: #999; color: #333; } .more-like-this:hover { border-color: #999; color: #333; }
.overall-section { .bottom-section {
max-width: 1200px; max-width: 1400px;
margin: 0 auto; margin: 0 auto;
padding: 16px 24px; padding: 24px 24px 32px;
border-top: 1px solid #e5e5e5; display: grid;
grid-template-columns: 1fr 380px;
gap: 24px;
} }
.overall-section summary {
font-size: 14px; .submit-column {}
color: #666; .submit-column h3 {
cursor: pointer; font-size: 15px;
padding: 8px 0; 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 { .overall-textarea {
width: 100%; width: 100%;
padding: 8px 10px; padding: 10px 12px;
border: 1px solid #e5e5e5; border: 1px solid #e5e5e5;
border-radius: 4px; border-radius: 6px;
font-size: 13px; font-size: 13px;
resize: vertical; resize: vertical;
min-height: 60px; min-height: 80px;
margin-top: 8px;
outline: none; outline: none;
font-family: inherit; font-family: inherit;
line-height: 1.5;
} }
.overall-textarea:focus { border-color: #999; } .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; background: #f7f7f7;
padding: 16px 24px; border-radius: 8px;
margin-top: 8px; padding: 20px;
} }
.regenerate-bar .inner { .regen-column h3 {
max-width: 1200px; font-size: 14px;
margin: 0 auto; font-weight: 600;
color: #333;
margin-bottom: 12px;
} }
.regenerate-bar h3 { font-size: 14px; font-weight: 600; margin-bottom: 10px; }
.regen-controls { .regen-controls {
display: flex; display: flex;
gap: 8px; gap: 8px;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
margin-bottom: 10px;
} }
.regen-chiclet { .regen-chiclet {
padding: 6px 14px; padding: 6px 14px;
@@ -188,46 +294,27 @@ export function generateCompareHtml(images: string[]): string {
.regen-chiclet:hover { border-color: #999; } .regen-chiclet:hover { border-color: #999; }
.regen-chiclet.active { border-color: #000; background: #f0f0f0; } .regen-chiclet.active { border-color: #000; background: #f0f0f0; }
.regen-custom { .regen-custom {
flex: 1; width: 100%;
min-width: 150px; padding: 8px 10px;
padding: 6px 10px;
border: 1px solid #e5e5e5; border: 1px solid #e5e5e5;
border-radius: 4px; border-radius: 6px;
font-size: 13px; font-size: 13px;
outline: none; outline: none;
margin-bottom: 10px;
} }
.regen-custom:focus { border-color: #999; } .regen-custom:focus { border-color: #999; }
.regen-btn { .regen-btn {
padding: 6px 16px; padding: 8px 16px;
background: #fff; background: #fff;
border: 1px solid #e5e5e5; border: 1px solid #ddd;
border-radius: 4px; border-radius: 6px;
font-size: 13px; font-size: 13px;
cursor: pointer; cursor: pointer;
font-weight: 600; font-weight: 600;
width: 100%;
} }
.regen-btn:hover { border-color: #000; } .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 { .success-msg {
display: none; display: none;
max-width: 1200px; max-width: 1200px;
@@ -243,43 +330,6 @@ export function generateCompareHtml(images: string[]): string {
/* Hidden result elements for agent polling */ /* Hidden result elements for agent polling */
#status, #feedback-result { display: none; } #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 loading state */
.skeleton { .skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
@@ -298,53 +348,40 @@ export function generateCompareHtml(images: string[]): string {
<div class="header"> <div class="header">
<h1>Design Exploration</h1> <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>
<div class="variants"> <div class="variants">
${variantCards} ${variantCards}
</div> </div>
<div class="overall-section"> <div class="bottom-section">
<details> <div class="submit-column">
<summary>Overall direction (optional)</summary> <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" <textarea class="overall-textarea" id="overall-feedback"
placeholder="Any overall notes about direction?"></textarea> placeholder="Combine elements, request changes, or describe what you want..."></textarea>
</details> <div class="submit-status" id="submit-status"></div>
</div> <button class="submit-btn" id="submit-btn">Take my feedback and continue →</button>
</div>
<div class="regenerate-bar"> <div class="regen-column">
<div class="inner">
<h3>Want to explore more?</h3> <h3>Want to explore more?</h3>
<div class="regen-controls"> <div class="regen-controls">
<button class="regen-chiclet" data-action="different">Totally different</button> <button class="regen-chiclet" data-action="different">Totally different</button>
<button class="regen-chiclet" data-action="match">Match my design</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> </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> </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"> <div class="success-msg" id="success-msg">
Feedback submitted! Return to your coding agent. Feedback submitted! Return to your coding agent.
</div> </div>
@@ -354,6 +391,35 @@ export function generateCompareHtml(images: string[]): string {
<div id="feedback-result"></div> <div id="feedback-result"></div>
<script> <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 // Star rating
document.querySelectorAll('.stars').forEach(starsEl => { document.querySelectorAll('.stars').forEach(starsEl => {
const stars = starsEl.querySelectorAll('.star'); 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) // Regenerate chiclets (toggle active)
document.querySelectorAll('.regen-chiclet').forEach(chiclet => { document.querySelectorAll('.regen-chiclet').forEach(chiclet => {
chiclet.addEventListener('click', () => { chiclet.addEventListener('click', () => {