feat(frontend): redesign homepage and file config form with aurora palette

Rebuild the visual identity from the actual Ombrora mark (violet/cyan aurora
blobs) instead of the generic blue/amber scheme, add Bricolage Grotesque and
IBM Plex Mono for display and code-like text, and give the hero an animated
aurora backdrop, gradient headline, and a format-pair marquee driven by the
real registered converters.

Rework the file config form: target format and icon size are both chip
groups now instead of a native select, and quality/compression sliders get a
gradient-filled track, custom thumb, and a mono value pill to match. File
type icons are color-coded by family (images/documents/fonts/ebooks) and
reused across FileConfigCard, FileCard, and FormatsGrid.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 16:34:13 +02:00
co-authored by Claude Sonnet 5
parent 023a96e5d8
commit 987af80286
14 changed files with 770 additions and 206 deletions
+50 -44
View File
@@ -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() {
<>
<SeoHead lang={lang} />
<section className="hero">
<h1>{t('hero.title')}</h1>
<p>{t('hero.subtitle')}</p>
<Dropzone label={t('hero.dropzoneLabel')} onFilesSelected={handleFilesSelected} />
<div className="hero-aurora" aria-hidden="true" />
<div className="hero-content">
<span className="hero-kicker">{t('hero.kicker')}</span>
<h1 className="hero-title">{t('hero.title')}</h1>
<p>{t('hero.subtitle')}</p>
<Dropzone label={t('hero.dropzoneLabel')} onFilesSelected={handleFilesSelected} />
<FormatMarquee />
{pendingFiles.length > 0 && (
<div className="pending-files">
<ul>
{pendingFiles.map((item, index) => (
<FileConfigCard
key={`${item.file.name}-${index}`}
item={item}
index={index}
t={t}
onTargetFormatChange={updateTargetFormat}
onQualityChange={updateQuality}
onIconSizeChange={updateIconSize}
/>
))}
</ul>
<button className="convert-button" onClick={handleConvert}>
{t('hero.convert')}
</button>
</div>
)}
<ul className="job-list">
{submittedJobs.map((job, index) =>
job.id ? (
<FileCard
key={job.id}
fileName={job.file}
fileSize={job.size}
targetFormat={job.targetFormat}
jobId={job.id}
/>
) : (
<FileCard
key={`${job.file}-${index}`}
fileName={job.file}
fileSize={job.size}
targetFormat={job.targetFormat}
initialError={job.error}
/>
)
{pendingFiles.length > 0 && (
<div className="pending-files">
<ul>
{pendingFiles.map((item, index) => (
<FileConfigCard
key={`${item.file.name}-${index}`}
item={item}
index={index}
t={t}
onTargetFormatChange={updateTargetFormat}
onQualityChange={updateQuality}
onIconSizeChange={updateIconSize}
/>
))}
</ul>
<button className="convert-button" onClick={handleConvert}>
{t('hero.convert')}
</button>
</div>
)}
</ul>
<ul className="job-list">
{submittedJobs.map((job, index) =>
job.id ? (
<FileCard
key={job.id}
fileName={job.file}
fileSize={job.size}
targetFormat={job.targetFormat}
jobId={job.id}
/>
) : (
<FileCard
key={`${job.file}-${index}`}
fileName={job.file}
fileSize={job.size}
targetFormat={job.targetFormat}
initialError={job.error}
/>
)
)}
</ul>
</div>
</section>
<ReassuranceStrip />