Move preview to full-width section below controls for more preview space
This commit is contained in:
@@ -131,201 +131,209 @@
|
|||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-lg">
|
<!-- Controls Section -->
|
||||||
<!-- Left Column: Upload & Controls -->
|
<div class="card fade-in" style="margin-bottom: var(--space-xl);">
|
||||||
<div class="card fade-in">
|
<div class="grid grid-cols-2 gap-lg">
|
||||||
<h2>Upload & Transform</h2>
|
<!-- Left Column: Upload & Dimensions -->
|
||||||
|
<div>
|
||||||
|
<h2>Upload & Settings</h2>
|
||||||
|
|
||||||
<!-- File Upload -->
|
<!-- File Upload -->
|
||||||
<div style="margin-bottom: var(--space-xl);">
|
<div style="margin-bottom: var(--space-lg);">
|
||||||
<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">
|
|
||||||
<label style="display: block; margin-bottom: var(--space-sm); font-weight: 500;">
|
<label style="display: block; margin-bottom: var(--space-sm); font-weight: 500;">
|
||||||
Crop Position
|
Select Image
|
||||||
</label>
|
</label>
|
||||||
<select bind:value={position}>
|
<input
|
||||||
<option value="center">Center</option>
|
type="file"
|
||||||
<option value="top">Top</option>
|
accept="image/*"
|
||||||
<option value="bottom">Bottom</option>
|
on:change={onFileChange}
|
||||||
<option value="left">Left</option>
|
style="margin-bottom: var(--space-sm);"
|
||||||
<option value="right">Right</option>
|
/>
|
||||||
<option value="top-left">Top-left</option>
|
{#if file}
|
||||||
<option value="top-right">Top-right</option>
|
<div class="flex gap-sm items-center" style="margin-top: var(--space-sm);">
|
||||||
<option value="bottom-left">Bottom-left</option>
|
<span class="text-sm">{file.name}</span>
|
||||||
<option value="bottom-right">Bottom-right</option>
|
<span class="text-xs" style="color: var(--color-text-secondary);">
|
||||||
</select>
|
({formatFileSize(file.size)})
|
||||||
</div>
|
</span>
|
||||||
{/if}
|
<button class="btn-secondary" style="padding: var(--space-xs) var(--space-sm); font-size: 0.875rem;" on:click={clearFile}>
|
||||||
|
Clear
|
||||||
<!-- Quality -->
|
</button>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
|
||||||
<div style="flex: 1; display: flex; align-items: center; justify-content: center; color: var(--color-text-secondary);">
|
<!-- Dimensions -->
|
||||||
<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 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>
|
</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>
|
||||||
</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>
|
</div>
|
||||||
Reference in New Issue
Block a user