Move preview to full-width section below controls for more preview space
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user