-
-
) : (
diff --git a/frontend/src/components/FormatMarquee.jsx b/frontend/src/components/FormatMarquee.jsx
new file mode 100644
index 0000000..5038576
--- /dev/null
+++ b/frontend/src/components/FormatMarquee.jsx
@@ -0,0 +1,31 @@
+import { ArrowRight } from '@phosphor-icons/react';
+
+const PAIRS = [
+ ['heic', 'jpg'],
+ ['png', 'webp'],
+ ['docx', 'pdf'],
+ ['ttf', 'woff'],
+ ['epub', 'mobi'],
+];
+
+function Pill({ from, to }) {
+ return (
+
+ {from.toUpperCase()}
+
+ {to.toUpperCase()}
+
+ );
+}
+
+export function FormatMarquee() {
+ return (
+
+
+ {[...PAIRS, ...PAIRS].map(([from, to], index) => (
+
+ ))}
+
+
+ );
+}
diff --git a/frontend/src/components/FormatsGrid.jsx b/frontend/src/components/FormatsGrid.jsx
index f52e017..ebb76b2 100644
--- a/frontend/src/components/FormatsGrid.jsx
+++ b/frontend/src/components/FormatsGrid.jsx
@@ -1,18 +1,32 @@
import { useTranslation } from 'react-i18next';
+import { Image, FileText, TextAa, BookOpen } from '@phosphor-icons/react';
import { FORMAT_FAMILIES } from '../data/formats.js';
+const FAMILY_ICONS = {
+ images: Image,
+ documents: FileText,
+ fonts: TextAa,
+ ebooks: BookOpen,
+};
+
export function FormatsGrid() {
const { t } = useTranslation();
return (
{t('formats.title')}
- {FORMAT_FAMILIES.map(({ key, formats }) => (
-
-
{t(`formats.${key}`)}
-
{formats.join(', ').toUpperCase()}
-
- ))}
+ {FORMAT_FAMILIES.map(({ key, formats }) => {
+ const Icon = FAMILY_ICONS[key];
+ return (
+
+
+
+
+
{t(`formats.${key}`)}
+
{formats.join(', ').toUpperCase()}
+
+ );
+ })}
);
diff --git a/frontend/src/components/ReassuranceStrip.jsx b/frontend/src/components/ReassuranceStrip.jsx
index 6fccc4d..36158b0 100644
--- a/frontend/src/components/ReassuranceStrip.jsx
+++ b/frontend/src/components/ReassuranceStrip.jsx
@@ -1,5 +1,7 @@
+import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Clock, LockKey, CloudArrowUp, HardDrives } from '@phosphor-icons/react';
+import { fetchConfig } from '../api.js';
const ITEMS = [
{ key: 'autoDelete', Icon: Clock },
@@ -10,14 +12,29 @@ const ITEMS = [
export function ReassuranceStrip() {
const { t } = useTranslation();
+ const [config, setConfig] = useState(null);
+
+ useEffect(() => {
+ fetchConfig().then(setConfig).catch(() => {});
+ }, []);
+
+ if (!config) return null;
+
+ const values = {
+ autoDelete: config.retentionHours,
+ maxSize: config.maxFileSizeMb,
+ };
+
return (
{t('reassurance.title')}
{ITEMS.map(({ key, Icon }) => (
-
-
- {t(`reassurance.${key}`)}
+
+
+
+ {t(`reassurance.${key}`, { value: values[key] })}
))}
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 35aae58..34ab1be 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -1,34 +1,56 @@
-@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Bricolage+Grotesque:opsz,wght@12..96,500..800&family=IBM+Plex+Mono:wght@500;600&display=swap');
:root {
- --color-primary: #2563eb;
+ /* interactive-safe (AA-contrast) roles */
+ --color-primary: #6d28d9;
--color-on-primary: #ffffff;
- --color-secondary: #3b82f6;
- --color-accent: #d97706;
- --color-background: #f8fafc;
- --color-foreground: #0f172a;
- --color-muted: #f1f5fd;
- --color-border: #e4ecfc;
+ --color-secondary: #0e7ea8;
+ --color-background: #faf9fe;
+ --color-foreground: #150f26;
+ --color-muted: #ede6ff;
+ --color-border: #e3daf7;
--color-destructive: #dc2626;
- --color-ring: #2563eb;
+ --color-ring: #6d28d9;
+
+ /* decorative aurora duo, drawn from the Ombrora mark — used for gradients, blobs, glow */
+ --color-aurora-violet: #7e14ff;
+ --color-aurora-cyan: #47bfff;
+ --color-aurora-mist: #ede6ff;
+ --gradient-aurora: linear-gradient(135deg, var(--color-aurora-violet), var(--color-aurora-cyan));
+
+ /* file-family accents, reused for icon chips across the upload flow */
+ --color-family-images: var(--color-aurora-violet);
+ --color-family-documents: var(--color-aurora-cyan);
+ --color-family-fonts: #f5a524;
+ --color-family-ebooks: #10b981;
--font-sans: 'Plus Jakarta Sans', system-ui, 'Segoe UI', Roboto, sans-serif;
+ --font-display: 'Bricolage Grotesque', var(--font-sans);
+ --font-mono: 'IBM Plex Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;
color-scheme: light dark;
font: 16px/1.5 var(--font-sans);
}
:root[data-theme='dark'] {
- --color-primary: #3b82f6;
- --color-on-primary: #0f172a;
- --color-secondary: #60a5fa;
- --color-accent: #f59e0b;
- --color-background: #0f172a;
- --color-foreground: #f1f5f9;
- --color-muted: #1e293b;
- --color-border: #334155;
- --color-destructive: #f87171;
- --color-ring: #3b82f6;
+ --color-primary: #9b6bff;
+ --color-on-primary: #150f26;
+ --color-secondary: #47bfff;
+ --color-background: #0d0a18;
+ --color-foreground: #f3f0ff;
+ --color-muted: #1c1530;
+ --color-border: #332853;
+ --color-destructive: #fb7185;
+ --color-ring: #9b6bff;
+
+ --color-aurora-violet: #9b6bff;
+ --color-aurora-cyan: #47bfff;
+ --color-aurora-mist: #241a3d;
+
+ --color-family-images: var(--color-aurora-violet);
+ --color-family-documents: var(--color-aurora-cyan);
+ --color-family-fonts: #ffb84d;
+ --color-family-ebooks: #34d399;
}
*, *::before, *::after {
diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json
index b92e0d9..ca480e0 100644
--- a/frontend/src/locales/en.json
+++ b/frontend/src/locales/en.json
@@ -7,6 +7,7 @@
"themeToDark": "Switch to dark mode"
},
"hero": {
+ "kicker": "Drop. Convert. Done.",
"title": "Convert your files online, for free",
"subtitle": "Drag and drop your files, pick the output format, download the result. No install, no account.",
"dropzoneLabel": "Drag your files here or click to browse",
@@ -14,8 +15,9 @@
"unsupportedFormat": "Unsupported format"
},
"quality": {
- "label": "Quality ({{value}})",
- "compression": "Compression ({{value}})",
+ "targetFormat": "Convert to",
+ "label": "Quality",
+ "compression": "Compression",
"iconSize": "Icon size",
"compressPdf": "Compress as JPEG"
},
@@ -25,10 +27,10 @@
},
"reassurance": {
"title": "Why you can trust us",
- "autoDelete": "Files deleted automatically after 1h",
+ "autoDelete": "Files deleted automatically after {{value}}h",
"noAccount": "No account required",
"online": "100% online — nothing to install",
- "maxSize": "Up to 100MB per file"
+ "maxSize": "Up to {{value}}MB per file"
},
"formats": {
"title": "Supported formats",
diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json
index 1225e56..f0e12bf 100644
--- a/frontend/src/locales/fr.json
+++ b/frontend/src/locales/fr.json
@@ -7,6 +7,7 @@
"themeToDark": "Activer le mode sombre"
},
"hero": {
+ "kicker": "Glissez. Convertissez. Terminé.",
"title": "Convertissez vos fichiers en ligne, gratuitement",
"subtitle": "Glissez-déposez vos fichiers, choisissez le format de sortie, téléchargez le résultat. Aucune installation, aucun compte.",
"dropzoneLabel": "Glissez vos fichiers ici ou cliquez pour parcourir",
@@ -14,8 +15,9 @@
"unsupportedFormat": "Format non supporté"
},
"quality": {
- "label": "Qualité ({{value}})",
- "compression": "Compression ({{value}})",
+ "targetFormat": "Convertir en",
+ "label": "Qualité",
+ "compression": "Compression",
"iconSize": "Taille de l'icône",
"compressPdf": "Compresser en JPEG"
},
@@ -25,10 +27,10 @@
},
"reassurance": {
"title": "Pourquoi nous faire confiance",
- "autoDelete": "Fichiers supprimés automatiquement après 1h",
+ "autoDelete": "Fichiers supprimés automatiquement après {{value}}h",
"noAccount": "Aucun compte requis",
"online": "100% en ligne — rien à installer",
- "maxSize": "Jusqu'à 100 Mo par fichier"
+ "maxSize": "Jusqu'à {{value}} Mo par fichier"
},
"formats": {
"title": "Formats pris en charge",
diff --git a/frontend/src/pages/HomePage.jsx b/frontend/src/pages/HomePage.jsx
index 235c0ab..e9e1112 100644
--- a/frontend/src/pages/HomePage.jsx
+++ b/frontend/src/pages/HomePage.jsx
@@ -7,6 +7,7 @@ import { Dropzone } from '../components/Dropzone.jsx';
import { FileConfigCard } from '../components/FileConfigCard.jsx';
import { ReassuranceStrip } from '../components/ReassuranceStrip.jsx';
import { FormatsGrid } from '../components/FormatsGrid.jsx';
+import { FormatMarquee } from '../components/FormatMarquee.jsx';
import { SeoHead } from '../components/SeoHead.jsx';
import '../styles/home.css';
import '../styles/sections.css';
@@ -93,52 +94,57 @@ export function HomePage() {
<>
- {t('hero.title')}
- {t('hero.subtitle')}
-
+
+
+
{t('hero.kicker')}
+
{t('hero.title')}
+
{t('hero.subtitle')}
+
+
- {pendingFiles.length > 0 && (
-
-
- {pendingFiles.map((item, index) => (
-
- ))}
-
-
-
- )}
-
-
- {submittedJobs.map((job, index) =>
- job.id ? (
-
- ) : (
-
- )
+ {pendingFiles.length > 0 && (
+
+
+ {pendingFiles.map((item, index) => (
+
+ ))}
+
+
+
)}
-
+
+
+ {submittedJobs.map((job, index) =>
+ job.id ? (
+
+ ) : (
+
+ )
+ )}
+
+
diff --git a/frontend/src/styles/home.css b/frontend/src/styles/home.css
index 9988ac1..511b75c 100644
--- a/frontend/src/styles/home.css
+++ b/frontend/src/styles/home.css
@@ -1,47 +1,157 @@
.hero {
- max-width: 720px;
- margin: 0 auto;
- padding: 3rem 1.5rem;
+ position: relative;
+ overflow: hidden;
+ padding: 5rem 1.5rem 3.5rem;
text-align: center;
+ isolation: isolate;
}
-.hero h1 {
- font-size: 2.5rem;
+.hero-aurora {
+ position: absolute;
+ inset: -20% -10% auto -10%;
+ height: 32rem;
+ background-image:
+ radial-gradient(38rem 22rem at 18% 15%, color-mix(in srgb, var(--color-aurora-violet) 32%, transparent), transparent 70%),
+ radial-gradient(34rem 20rem at 82% 25%, color-mix(in srgb, var(--color-aurora-cyan) 28%, transparent), transparent 70%),
+ radial-gradient(30rem 18rem at 50% 0%, color-mix(in srgb, var(--color-aurora-mist) 55%, transparent), transparent 75%);
+ filter: blur(10px);
+ animation: aurora-drift 22s ease-in-out infinite alternate;
+ pointer-events: none;
+ z-index: -1;
+}
+
+@keyframes aurora-drift {
+ from {
+ transform: translate3d(0, 0, 0) scale(1);
+ }
+ to {
+ transform: translate3d(1.5%, 2%, 0) scale(1.04);
+ }
+}
+
+.hero-content {
+ max-width: 720px;
+ margin: 0 auto;
+}
+
+.hero-kicker {
+ display: inline-block;
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--color-primary);
+ background: var(--color-muted);
+ border: 1px solid var(--color-border);
+ border-radius: 999px;
+ padding: 0.4rem 0.9rem;
+ margin: 0 0 1.25rem;
+}
+
+.hero-title {
+ font-family: var(--font-display);
+ font-size: clamp(2.25rem, 4.5vw + 1rem, 3.5rem);
+ font-weight: 700;
+ line-height: 1.08;
+ letter-spacing: -0.01em;
margin: 0 0 1rem;
+ background: var(--gradient-aurora);
+ background-clip: text;
+ -webkit-background-clip: text;
+ color: transparent;
}
.hero p {
color: var(--color-foreground);
- opacity: 0.8;
+ opacity: 0.72;
+ font-size: 1.05rem;
margin: 0 0 2rem;
}
.dropzone {
+ position: relative;
border: 2px dashed var(--color-border);
- border-radius: 12px;
- padding: 2.5rem 1.5rem;
+ border-radius: 20px;
+ padding: 3rem 1.5rem;
display: flex;
flex-direction: column;
align-items: center;
- gap: 0.75rem;
+ gap: 0.85rem;
cursor: pointer;
min-height: 44px;
+ background: color-mix(in srgb, var(--color-muted) 45%, transparent);
+ transition: border-color 200ms ease, box-shadow 200ms ease, transform 200ms ease;
+}
+
+.dropzone:hover {
+ border-color: var(--color-primary);
+ transform: translateY(-2px);
}
.dropzone-active {
border-color: var(--color-primary);
+ border-style: solid;
background: var(--color-muted);
+ box-shadow: 0 0 0 6px color-mix(in srgb, var(--color-aurora-violet) 14%, transparent);
}
.dropzone-input {
display: none;
}
+.dropzone svg {
+ color: var(--color-primary);
+}
+
+.format-marquee {
+ overflow: hidden;
+ margin: 1.75rem auto 0;
+ max-width: 100%;
+ mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
+}
+
+.format-marquee-track {
+ display: flex;
+ width: max-content;
+ gap: 0.6rem;
+ animation: marquee-scroll 26s linear infinite;
+}
+
+@keyframes marquee-scroll {
+ from {
+ transform: translateX(0);
+ }
+ to {
+ transform: translateX(-50%);
+ }
+}
+
+.format-pill {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4rem;
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ font-weight: 500;
+ color: var(--color-foreground);
+ opacity: 0.8;
+ background: var(--color-muted);
+ border: 1px solid var(--color-border);
+ border-radius: 999px;
+ padding: 0.4rem 0.85rem;
+ white-space: nowrap;
+}
+
+.format-pill svg {
+ color: var(--color-primary);
+}
+
.pending-files ul,
.job-list {
list-style: none;
padding: 0;
- margin: 1.5rem 0;
+ margin: 1.75rem 0;
display: flex;
flex-direction: column;
gap: 0.75rem;
@@ -50,22 +160,47 @@
.file-config-card,
.file-card {
+ position: relative;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.75rem;
- padding: 1rem;
+ padding: 1.1rem;
border: 1px solid var(--color-border);
- border-radius: 8px;
+ border-radius: 16px;
+ background: var(--color-background);
+ box-shadow: 0 1px 2px color-mix(in srgb, var(--color-foreground) 4%, transparent);
+ transition: box-shadow 200ms ease, transform 200ms ease;
+}
+
+.file-config-card:hover,
+.file-card:hover {
+ box-shadow: 0 8px 24px color-mix(in srgb, var(--color-foreground) 8%, transparent);
+}
+
+.file-card-failed {
+ border-color: color-mix(in srgb, var(--color-destructive) 45%, var(--color-border));
}
.file-tile-header {
display: flex;
align-items: center;
- gap: 0.5rem;
+ gap: 0.65rem;
flex: 1 1 100%;
}
+.file-tile-icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 36px;
+ height: 36px;
+ flex-shrink: 0;
+ border-radius: 10px;
+ color: var(--tile-color, var(--color-primary));
+ background: color-mix(in srgb, var(--tile-color, var(--color-primary)) 16%, transparent);
+}
+
.file-tile-name {
font-weight: 500;
flex: 1 1 auto;
@@ -73,73 +208,231 @@
}
.file-tile-size {
- font-size: 0.875rem;
+ font-family: var(--font-mono);
+ font-size: 0.8rem;
color: var(--color-foreground);
- opacity: 0.65;
+ opacity: 0.6;
white-space: nowrap;
}
.file-config-controls {
display: flex;
flex-direction: column;
- gap: 0.5rem;
+ gap: 0.65rem;
flex: 1 1 100%;
}
-.file-config-controls label {
+.range-field {
display: flex;
- align-items: center;
- gap: 0.5rem;
+ flex-direction: column;
+ gap: 0.4rem;
font-size: 0.875rem;
+ flex: 1 1 220px;
}
-.format-select-group {
+.range-field-header {
display: flex;
align-items: center;
+ justify-content: space-between;
gap: 0.5rem;
}
-.format-select {
- appearance: none;
- background-color: var(--color-background);
- color: var(--color-foreground);
- border: 1px solid var(--color-border);
- border-radius: 8px;
- padding: 0.5rem 2rem 0.5rem 0.75rem;
- font-weight: 500;
- min-height: 44px;
- background-image:
- linear-gradient(45deg, transparent 50%, var(--color-foreground) 50%),
- linear-gradient(135deg, var(--color-foreground) 50%, transparent 50%);
- background-position:
- calc(100% - 17px) calc(1.15em),
- calc(100% - 12px) calc(1.15em);
- background-size: 5px 5px, 5px 5px;
- background-repeat: no-repeat;
+.value-pill {
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ font-weight: 600;
+ color: var(--color-primary);
+ background: var(--color-muted);
+ border-radius: 999px;
+ padding: 0.15rem 0.55rem;
}
-.format-select:focus-visible {
- border-color: var(--color-ring);
+.range-field input[type='range'],
+.switch-row input[type='range'] {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 100%;
+ height: 6px;
+ border-radius: 999px;
+ background: linear-gradient(
+ to right,
+ var(--color-primary) var(--range-progress, 50%),
+ var(--color-border) var(--range-progress, 50%)
+ );
+ outline: none;
+}
+
+.range-field input[type='range']::-webkit-slider-thumb,
+.switch-row input[type='range']::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background: var(--color-background);
+ border: 3px solid var(--color-primary);
+ box-shadow: 0 1px 3px color-mix(in srgb, var(--color-foreground) 25%, transparent);
+ cursor: pointer;
+}
+
+.range-field input[type='range']::-moz-range-track,
+.switch-row input[type='range']::-moz-range-track {
+ height: 6px;
+ border-radius: 999px;
+ background: var(--color-border);
+}
+
+.range-field input[type='range']::-moz-range-progress,
+.switch-row input[type='range']::-moz-range-progress {
+ height: 6px;
+ border-radius: 999px;
+ background: var(--color-primary);
+}
+
+.range-field input[type='range']::-moz-range-thumb,
+.switch-row input[type='range']::-moz-range-thumb {
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background: var(--color-background);
+ border: 3px solid var(--color-primary);
+ cursor: pointer;
+}
+
+.range-field input[type='range']:focus-visible,
+.switch-row input[type='range']:focus-visible {
+ outline: 2px solid var(--color-ring);
+ outline-offset: 3px;
+}
+
+.chip-field {
+ display: flex;
+ flex-direction: column;
+ gap: 0.4rem;
+ font-size: 0.875rem;
+ flex: 1 1 100%;
+}
+
+.pdf-field {
+ display: flex;
+ flex-direction: column;
+ gap: 0.65rem;
+ flex: 1 1 100%;
+}
+
+.chip-group {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.4rem;
+}
+
+.chip {
+ font-family: var(--font-mono);
+ font-size: 0.8rem;
+ font-weight: 500;
+ color: var(--color-foreground);
+ background: var(--color-muted);
+ border: 1px solid var(--color-border);
+ border-radius: 999px;
+ padding: 0.4rem 0.75rem;
+ min-height: 36px;
+ transition: background 150ms ease, color 150ms ease, border-color 150ms ease;
+}
+
+.chip-active {
+ background: var(--color-primary);
+ border-color: var(--color-primary);
+ color: var(--color-on-primary);
+}
+
+.switch-row {
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+ font-size: 0.875rem;
+ flex-wrap: wrap;
+}
+
+.switch {
+ position: relative;
+ display: inline-flex;
+ width: 38px;
+ height: 22px;
+ flex-shrink: 0;
+}
+
+.switch input {
+ position: absolute;
+ inset: 0;
+ opacity: 0;
+ margin: 0;
+ cursor: pointer;
+}
+
+.switch-track {
+ position: absolute;
+ inset: 0;
+ background: var(--color-border);
+ border-radius: 999px;
+ transition: background 150ms ease;
+}
+
+.switch-track::before {
+ content: '';
+ position: absolute;
+ top: 2px;
+ left: 2px;
+ width: 18px;
+ height: 18px;
+ border-radius: 50%;
+ background: var(--color-background);
+ transition: transform 150ms ease;
+}
+
+.switch input:checked + .switch-track {
+ background: var(--color-primary);
+}
+
+.switch input:checked + .switch-track::before {
+ transform: translateX(16px);
+}
+
+.switch input:focus-visible + .switch-track {
+ outline: 2px solid var(--color-ring);
+ outline-offset: 2px;
}
.format-badge {
+ font-family: var(--font-mono);
background: var(--color-muted);
color: var(--color-primary);
border-radius: 999px;
padding: 0.25rem 0.6rem;
font-size: 0.75rem;
- font-weight: 700;
- letter-spacing: 0.03em;
+ font-weight: 600;
+ letter-spacing: 0.02em;
}
.convert-button {
- background: var(--color-accent);
+ background: var(--color-primary);
color: var(--color-on-primary);
border: none;
- border-radius: 8px;
- padding: 0.75rem 1.5rem;
+ border-radius: 10px;
+ padding: 0.85rem 1.75rem;
font-weight: 600;
min-height: 44px;
+ transition: box-shadow 200ms ease, transform 200ms ease;
+}
+
+.convert-button:hover {
+ box-shadow: 0 10px 24px color-mix(in srgb, var(--color-aurora-violet) 35%, transparent);
+ transform: translateY(-1px);
+}
+
+.job-progress {
+ flex: 1 1 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
}
.job-status {
@@ -148,6 +441,34 @@
gap: 0.5rem;
color: var(--color-foreground);
opacity: 0.75;
+ font-size: 0.875rem;
+}
+
+.progress-track {
+ display: block;
+ width: 100%;
+ height: 4px;
+ border-radius: 999px;
+ background: var(--color-muted);
+ overflow: hidden;
+}
+
+.progress-fill {
+ display: block;
+ width: 40%;
+ height: 100%;
+ border-radius: 999px;
+ background: var(--gradient-aurora);
+ animation: progress-slide 1.4s ease-in-out infinite;
+}
+
+@keyframes progress-slide {
+ 0% {
+ transform: translateX(-100%);
+ }
+ 100% {
+ transform: translateX(250%);
+ }
}
.spin {
@@ -167,42 +488,53 @@
display: inline-flex;
align-items: center;
gap: 0.5rem;
- background: var(--color-accent);
+ background: var(--color-primary);
color: var(--color-on-primary);
border: none;
- border-radius: 8px;
+ border-radius: 10px;
padding: 0.6rem 1.25rem;
font-weight: 600;
min-height: 44px;
text-decoration: none;
+ transition: box-shadow 200ms ease, transform 200ms ease;
}
.download-button:hover {
- filter: brightness(1.05);
+ box-shadow: 0 10px 24px color-mix(in srgb, var(--color-aurora-cyan) 35%, transparent);
+ transform: translateY(-1px);
+}
+
+.status-icon-success {
+ color: #10b981;
}
.error {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4rem;
color: var(--color-destructive);
+ font-size: 0.9rem;
}
@media (min-width: 768px) {
.file-config-controls {
flex-direction: row;
align-items: center;
+ flex-wrap: wrap;
flex: 1 1 auto;
}
}
@media (max-width: 480px) {
.hero {
- padding: 2rem 1rem;
+ padding: 3rem 1rem 2.5rem;
}
- .hero h1 {
- font-size: 1.75rem;
+ .hero-title {
+ font-size: 2rem;
}
.dropzone {
- padding: 1.75rem 1rem;
+ padding: 2.25rem 1rem;
}
}
diff --git a/frontend/src/styles/layout.css b/frontend/src/styles/layout.css
index 4f32b24..36707df 100644
--- a/frontend/src/styles/layout.css
+++ b/frontend/src/styles/layout.css
@@ -1,14 +1,20 @@
.site-header {
+ position: sticky;
+ top: 0;
+ z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--color-border);
+ background: color-mix(in srgb, var(--color-background) 82%, transparent);
+ backdrop-filter: blur(10px);
}
.brand {
+ font-family: var(--font-display);
font-weight: 700;
- font-size: 1.25rem;
+ font-size: 1.35rem;
text-decoration: none;
color: var(--color-foreground);
}
@@ -37,13 +43,19 @@
.theme-toggle {
background: none;
border: 1px solid var(--color-border);
- border-radius: 8px;
+ border-radius: 10px;
width: 44px;
height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--color-foreground);
+ transition: border-color 150ms ease, color 150ms ease;
+}
+
+.theme-toggle:hover {
+ border-color: var(--color-primary);
+ color: var(--color-primary);
}
.site-footer {
diff --git a/frontend/src/styles/sections.css b/frontend/src/styles/sections.css
index b0c346d..bb2f95e 100644
--- a/frontend/src/styles/sections.css
+++ b/frontend/src/styles/sections.css
@@ -2,14 +2,21 @@
.formats-grid-section {
max-width: 960px;
margin: 0 auto;
- padding: 2.5rem 1.5rem;
+ padding: 3rem 1.5rem;
text-align: center;
}
+.reassurance h2,
+.formats-grid-section h2 {
+ font-family: var(--font-display);
+ font-size: 1.5rem;
+ font-weight: 700;
+}
+
.reassurance-list {
list-style: none;
padding: 0;
- margin: 1.5rem 0 0;
+ margin: 1.75rem 0 0;
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
@@ -18,25 +25,61 @@
.reassurance-list li {
display: flex;
align-items: center;
- gap: 0.75rem;
- padding: 1rem;
+ gap: 0.85rem;
+ padding: 1.1rem;
border: 1px solid var(--color-border);
- border-radius: 8px;
+ border-radius: 14px;
text-align: left;
+ transition: box-shadow 200ms ease, transform 200ms ease;
+}
+
+.reassurance-list li:hover {
+ box-shadow: 0 8px 24px color-mix(in srgb, var(--color-foreground) 6%, transparent);
+ transform: translateY(-2px);
+}
+
+.reassurance-icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 40px;
+ height: 40px;
+ flex-shrink: 0;
+ border-radius: 12px;
+ color: var(--color-primary);
+ background: var(--color-muted);
}
.formats-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
- margin-top: 1.5rem;
+ margin-top: 1.75rem;
}
.formats-family {
- padding: 1rem;
+ padding: 1.25rem;
border: 1px solid var(--color-border);
- border-radius: 8px;
+ border-radius: 14px;
text-align: left;
+ transition: box-shadow 200ms ease, transform 200ms ease;
+}
+
+.formats-family:hover {
+ box-shadow: 0 8px 24px color-mix(in srgb, var(--color-foreground) 6%, transparent);
+ transform: translateY(-2px);
+}
+
+.formats-family-icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 40px;
+ height: 40px;
+ margin-bottom: 0.75rem;
+ border-radius: 12px;
+ color: var(--tile-color, var(--color-primary));
+ background: color-mix(in srgb, var(--tile-color, var(--color-primary)) 16%, transparent);
}
.formats-family h3 {
@@ -45,8 +88,9 @@
.formats-family p {
margin: 0;
- opacity: 0.8;
- font-size: 0.875rem;
+ font-family: var(--font-mono);
+ opacity: 0.75;
+ font-size: 0.8rem;
}
@media (min-width: 768px) {
diff --git a/frontend/src/utils/fileFamily.js b/frontend/src/utils/fileFamily.js
new file mode 100644
index 0000000..8477c1f
--- /dev/null
+++ b/frontend/src/utils/fileFamily.js
@@ -0,0 +1,22 @@
+import { Image, FileText, TextAa, BookOpen, File as FileIcon } from '@phosphor-icons/react';
+import { FORMAT_FAMILIES } from '../data/formats.js';
+
+const FAMILY_ICONS = {
+ images: Image,
+ documents: FileText,
+ fonts: TextAa,
+ ebooks: BookOpen,
+};
+
+export function familyForExtension(extension) {
+ const ext = extension?.toLowerCase();
+ return FORMAT_FAMILIES.find((family) => family.formats.includes(ext))?.key ?? null;
+}
+
+export function fileTypeMeta(extension) {
+ const family = familyForExtension(extension);
+ return {
+ Icon: FAMILY_ICONS[family] ?? FileIcon,
+ colorVar: family ? `--color-family-${family}` : '--color-primary',
+ };
+}