Merge pull request 'Move preview to full-width section below controls for more preview space' (#7) from feature/ui-upgrade-dark-mode-preview into main

Reviewed-on: #7
This commit was merged in pull request #7.
This commit is contained in:
2026-03-08 16:52:05 -05:00

View File

@@ -131,201 +131,209 @@
</button>
</header>
<div class="grid grid-cols-2 gap-lg">
<!-- Left Column: Upload & Controls -->
<div class="card fade-in">
<h2>Upload & Transform</h2>
<!-- Controls Section -->
<div class="card fade-in" style="margin-bottom: var(--space-xl);">
<div class="grid grid-cols-2 gap-lg">
<!-- Left Column: Upload & Dimensions -->
<div>
<h2>Upload & Settings</h2>
<!-- File Upload -->
<div style="margin-bottom: var(--space-xl);">
<label style="display: block; margin-bottom: var(--space-sm); font-weight: 500;">
Select Image
</label>
<input
type="file"
accept="image/*"
on:change={onFileChange}
style="margin-bottom: var(--space-sm);"
/>
{#if file}
<div class="flex gap-sm items-center" style="margin-top: var(--space-sm);">
<span class="text-sm">{file.name}</span>
<span class="text-xs" style="color: var(--color-text-secondary);">
({formatFileSize(file.size)})
</span>
<button class="btn-secondary" style="padding: var(--space-xs) var(--space-sm); font-size: 0.875rem;" on:click={clearFile}>
Clear
</button>
</div>
{/if}
</div>
<!-- Dimensions -->
<div style="margin-bottom: var(--space-lg);">
<h3>Dimensions</h3>
<div class="grid grid-cols-2 gap-md">
<div>
<label style="display: block; margin-bottom: var(--space-xs); font-size: 0.875rem;">
Width (px)
</label>
<input type="number" bind:value={width} min="1" placeholder="Auto" />
</div>
<div>
<label style="display: block; margin-bottom: var(--space-xs); font-size: 0.875rem;">
Height (px)
</label>
<input type="number" bind:value={height} min="1" placeholder="Auto" />
</div>
</div>
</div>
<!-- Fit Mode -->
<div style="margin-bottom: var(--space-lg);">
<label style="display: block; margin-bottom: var(--space-sm); font-weight: 500;">
Fit Mode
</label>
<select bind:value={fit}>
<option value="inside">Resize only (no crop)</option>
<option value="cover">Crop to fit box</option>
</select>
</div>
<!-- Crop Position (if cover) -->
{#if fit === "cover"}
<div style="margin-bottom: var(--space-lg);" class="fade-in">
<!-- File Upload -->
<div style="margin-bottom: var(--space-lg);">
<label style="display: block; margin-bottom: var(--space-sm); font-weight: 500;">
Crop Position
Select Image
</label>
<select bind:value={position}>
<option value="center">Center</option>
<option value="top">Top</option>
<option value="bottom">Bottom</option>
<option value="left">Left</option>
<option value="right">Right</option>
<option value="top-left">Top-left</option>
<option value="top-right">Top-right</option>
<option value="bottom-left">Bottom-left</option>
<option value="bottom-right">Bottom-right</option>
</select>
</div>
{/if}
<!-- Quality -->
<div style="margin-bottom: var(--space-lg);">
<div class="flex justify-between" style="margin-bottom: var(--space-sm);">
<label style="font-weight: 500;">Quality</label>
<span style="color: var(--color-accent); font-weight: 600;">{quality}%</span>
</div>
<input type="range" min="10" max="100" bind:value={quality} />
</div>
<!-- Format -->
<div style="margin-bottom: var(--space-xl);">
<label style="display: block; margin-bottom: var(--space-sm); font-weight: 500;">
Output Format
</label>
<select bind:value={format}>
<option value="png">PNG</option>
<option value="webp">WebP</option>
<option value="jpeg">JPEG</option>
</select>
</div>
<!-- Error Message -->
{#if error}
<p style="color: var(--color-error); padding: var(--space-md); background: var(--color-bg-tertiary); border-radius: var(--radius-md); margin-bottom: var(--space-lg);">
{error}
</p>
{/if}
<!-- Action Button -->
<button
on:click|preventDefault={onSubmit}
disabled={processing || !file}
style="width: 100%;"
>
{#if processing}
<span class="spinner" style="width: 16px; height: 16px; border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%;"></span>
Processing...
{:else}
⬇️ Transform & Download
{/if}
</button>
</div>
<!-- Right Column: Preview -->
<div class="card fade-in" style="display: flex; flex-direction: column;">
<h2>Live Preview</h2>
{#if !file}
<div style="flex: 1; display: flex; align-items: center; justify-content: center; color: var(--color-text-secondary); text-align: center; padding: var(--space-2xl);">
<div>
<p style="font-size: 3rem; margin-bottom: var(--space-md)">🖼️</p>
<p class="mb-0">Upload an image to see live preview</p>
</div>
</div>
{:else if showPreview}
<div style="flex: 1; display: flex; flex-direction: column; gap: var(--space-lg);">
<!-- Image Comparison -->
<div class="grid grid-cols-2 gap-md" style="flex: 1;">
<!-- Original -->
<div style="display: flex; flex-direction: column;">
<h3 style="font-size: 1rem; margin-bottom: var(--space-sm);">Original</h3>
<div style="flex: 1; border: 2px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; display: flex; align-items: center; justify-content: center; background: var(--color-bg-tertiary);">
<img
src={filePreviewUrl}
alt="Original"
style="max-width: 100%; max-height: 300px; object-fit: contain;"
/>
</div>
<div style="margin-top: var(--space-sm); text-align: center;">
<p class="text-sm mb-0">
{formatFileSize(originalSize)}
</p>
</div>
</div>
<!-- Preview -->
<div style="display: flex; flex-direction: column;">
<h3 style="font-size: 1rem; margin-bottom: var(--space-sm);">Preview</h3>
<div style="flex: 1; border: 2px solid var(--color-accent); border-radius: var(--radius-md); overflow: hidden; display: flex; align-items: center; justify-content: center; background: var(--color-bg-tertiary);">
<img
src={previewUrl}
alt="Preview"
style="max-width: 100%; max-height: 300px; object-fit: contain;"
/>
</div>
<div style="margin-top: var(--space-sm); text-align: center;">
<p class="text-sm mb-0">
{formatFileSize(previewSize)}
</p>
</div>
</div>
</div>
<!-- Savings Info -->
{#if savings}
<div
class="fade-in"
style="
padding: var(--space-lg);
background: {savings.isReduction ? 'var(--color-success)' : 'var(--color-warning)'}15;
border: 2px solid {savings.isReduction ? 'var(--color-success)' : 'var(--color-warning)'};
border-radius: var(--radius-md);
text-align: center;
"
>
<p class="text-sm font-semibold mb-0" style="color: {savings.isReduction ? 'var(--color-success)' : 'var(--color-warning)'}; font-size: 1.125rem;">
{savings.formatted}
</p>
<input
type="file"
accept="image/*"
on:change={onFileChange}
style="margin-bottom: var(--space-sm);"
/>
{#if file}
<div class="flex gap-sm items-center" style="margin-top: var(--space-sm);">
<span class="text-sm">{file.name}</span>
<span class="text-xs" style="color: var(--color-text-secondary);">
({formatFileSize(file.size)})
</span>
<button class="btn-secondary" style="padding: var(--space-xs) var(--space-sm); font-size: 0.875rem;" on:click={clearFile}>
Clear
</button>
</div>
{/if}
</div>
{:else}
<div style="flex: 1; display: flex; align-items: center; justify-content: center; color: var(--color-text-secondary);">
<div class="spinner" style="width: 40px; height: 40px; border: 3px solid var(--color-border); border-top-color: var(--color-accent); border-radius: 50%;"></div>
<!-- Dimensions -->
<div style="margin-bottom: var(--space-lg);">
<h3>Dimensions</h3>
<div class="grid grid-cols-2 gap-md">
<div>
<label style="display: block; margin-bottom: var(--space-xs); font-size: 0.875rem;">
Width (px)
</label>
<input type="number" bind:value={width} min="1" placeholder="Auto" />
</div>
<div>
<label style="display: block; margin-bottom: var(--space-xs); font-size: 0.875rem;">
Height (px)
</label>
<input type="number" bind:value={height} min="1" placeholder="Auto" />
</div>
</div>
</div>
{/if}
<!-- Fit Mode -->
<div style="margin-bottom: var(--space-lg);">
<label style="display: block; margin-bottom: var(--space-sm); font-weight: 500;">
Fit Mode
</label>
<select bind:value={fit}>
<option value="inside">Resize only (no crop)</option>
<option value="cover">Crop to fit box</option>
</select>
</div>
<!-- Crop Position (if cover) -->
{#if fit === "cover"}
<div style="margin-bottom: var(--space-lg);" class="fade-in">
<label style="display: block; margin-bottom: var(--space-sm); font-weight: 500;">
Crop Position
</label>
<select bind:value={position}>
<option value="center">Center</option>
<option value="top">Top</option>
<option value="bottom">Bottom</option>
<option value="left">Left</option>
<option value="right">Right</option>
<option value="top-left">Top-left</option>
<option value="top-right">Top-right</option>
<option value="bottom-left">Bottom-left</option>
<option value="bottom-right">Bottom-right</option>
</select>
</div>
{/if}
</div>
<!-- Right Column: Quality & Format -->
<div>
<h2>Quality & Format</h2>
<!-- Quality -->
<div style="margin-bottom: var(--space-lg);">
<div class="flex justify-between" style="margin-bottom: var(--space-sm);">
<label style="font-weight: 500;">Quality</label>
<span style="color: var(--color-accent); font-weight: 600;">{quality}%</span>
</div>
<input type="range" min="10" max="100" bind:value={quality} />
</div>
<!-- Format -->
<div style="margin-bottom: var(--space-xl);">
<label style="display: block; margin-bottom: var(--space-sm); font-weight: 500;">
Output Format
</label>
<select bind:value={format}>
<option value="png">PNG</option>
<option value="webp">WebP</option>
<option value="jpeg">JPEG</option>
</select>
</div>
<!-- Error Message -->
{#if error}
<p style="color: var(--color-error); padding: var(--space-md); background: var(--color-bg-tertiary); border-radius: var(--radius-md); margin-bottom: var(--space-lg);">
{error}
</p>
{/if}
<!-- Action Button -->
<button
on:click|preventDefault={onSubmit}
disabled={processing || !file}
style="width: 100%;"
>
{#if processing}
<span class="spinner" style="width: 16px; height: 16px; border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%;"></span>
Processing...
{:else}
⬇️ Transform & Download
{/if}
</button>
</div>
</div>
</div>
<!-- Live Preview Section (Full Width Below) -->
<div class="card fade-in">
<h2>Live Preview</h2>
{#if !file}
<div style="display: flex; align-items: center; justify-content: center; color: var(--color-text-secondary); text-align: center; padding: var(--space-2xl); min-height: 400px;">
<div>
<p style="font-size: 3rem; margin-bottom: var(--space-md)">🖼️</p>
<p class="mb-0">Upload an image to see live preview</p>
</div>
</div>
{:else if showPreview}
<div style="display: flex; flex-direction: column; gap: var(--space-lg);">
<!-- Image Comparison -->
<div class="grid grid-cols-2 gap-lg">
<!-- Original -->
<div style="display: flex; flex-direction: column;">
<h3 style="font-size: 1rem; margin-bottom: var(--space-sm);">Original</h3>
<div style="border: 2px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; display: flex; align-items: center; justify-content: center; background: var(--color-bg-tertiary); min-height: 500px;">
<img
src={filePreviewUrl}
alt="Original"
style="max-width: 100%; max-height: 600px; object-fit: contain;"
/>
</div>
<div style="margin-top: var(--space-sm); text-align: center;">
<p class="text-sm mb-0">
{formatFileSize(originalSize)}
</p>
</div>
</div>
<!-- Preview -->
<div style="display: flex; flex-direction: column;">
<h3 style="font-size: 1rem; margin-bottom: var(--space-sm);">Preview</h3>
<div style="border: 2px solid var(--color-accent); border-radius: var(--radius-md); overflow: hidden; display: flex; align-items: center; justify-content: center; background: var(--color-bg-tertiary); min-height: 500px;">
<img
src={previewUrl}
alt="Preview"
style="max-width: 100%; max-height: 600px; object-fit: contain;"
/>
</div>
<div style="margin-top: var(--space-sm); text-align: center;">
<p class="text-sm mb-0">
{formatFileSize(previewSize)}
</p>
</div>
</div>
</div>
<!-- Savings Info -->
{#if savings}
<div
class="fade-in"
style="
padding: var(--space-lg);
background: {savings.isReduction ? 'var(--color-success)' : 'var(--color-warning)'}15;
border: 2px solid {savings.isReduction ? 'var(--color-success)' : 'var(--color-warning)'};
border-radius: var(--radius-md);
text-align: center;
"
>
<p class="text-sm font-semibold mb-0" style="color: {savings.isReduction ? 'var(--color-success)' : 'var(--color-warning)'}; font-size: 1.125rem;">
{savings.formatted}
</p>
</div>
{/if}
</div>
{:else}
<div style="display: flex; align-items: center; justify-content: center; color: var(--color-text-secondary); min-height: 500px;">
<div class="spinner" style="width: 40px; height: 40px; border: 3px solid var(--color-border); border-top-color: var(--color-accent); border-radius: 50%;"></div>
</div>
{/if}
</div>
</div>