review theme

This commit is contained in:
Anaïs Saraiva
2026-08-25 12:24:40 +02:00
parent abf8736fdd
commit b1b4f03814
12 changed files with 124 additions and 184 deletions
+23 -23
View File
@@ -106,45 +106,45 @@ pub async fn auth(
pub fn render_password_form(key: &str, filename: &str, error: bool) -> Response {
let message = if error {
"<p class=\"err\">Incorrect password. Please try again.</p>"
"<p class=\"err\">Mot de passe incorrect. Réessayez.</p>"
} else {
""
};
let safe_filename = escape_html(filename);
let html = format!(
r#"<!doctype html>
<html lang="en">
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="referrer" content="no-referrer" />
<title>Protected file</title>
<title>Fichier protégé</title>
<style>
* {{ box-sizing: border-box; }}
body {{
margin: 0; font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
background: #0e1013; color: #e7eaf0; min-height: 100vh;
background: #fafafa; color: #171717; min-height: 100vh;
display: flex; align-items: center; justify-content: center; padding: 16px;
}}
.card {{
width: 100%; max-width: 400px; background: #15181d; border: 1px solid #262b33;
border-radius: 14px; padding: 28px; box-shadow: 0 10px 40px rgba(0,0,0,.4);
width: 100%; max-width: 400px; background: #ffffff; border: 1px solid #e5e5e5;
border-radius: 8px; padding: 28px;
}}
.brand {{ display: flex; align-items: center; gap: 10px; margin-bottom: 18px; }}
.brand img {{ width: 34px; height: 34px; }}
.brand span {{ font-size: 15px; font-weight: 650; }}
h1 {{ margin: 0 0 6px; font-size: 20px; }}
p {{ margin: 0 0 18px; color: #98a1af; font-size: 14px; word-break: break-all; }}
.err {{ margin: 0 0 14px; color: #e05555; background: rgba(224,85,85,.08);
border: 1px solid rgba(224,85,85,.3); border-radius: 8px; padding: 10px 12px; }}
.brand {{ display: flex; align-items: center; gap: 10px; margin-bottom: 20px; }}
.brand img {{ width: 32px; height: 32px; }}
.brand span {{ font-size: 15px; font-weight: 600; }}
h1 {{ margin: 0 0 6px; font-size: 18px; }}
p {{ margin: 0 0 18px; color: #737373; font-size: 14px; word-break: break-all; }}
.err {{ margin: 0 0 14px; color: #dc2626; background: #fef2f2;
border: 1px solid #fecaca; border-radius: 6px; padding: 10px 12px; }}
input[type="password"] {{
width: 100%; background: #0e1013; border: 1px solid #262b33; color: #e7eaf0;
border-radius: 8px; padding: 12px 13px; font-size: 14px; margin-bottom: 12px;
width: 100%; background: #ffffff; border: 1px solid #e5e5e5; color: #171717;
border-radius: 6px; padding: 11px 12px; font-size: 14px; margin-bottom: 12px;
}}
input[type="password"]:focus {{ outline: none; border-color: #3f7bf6; }}
button {{ width: 100%; font: inherit; cursor: pointer; border: none; border-radius: 8px;
padding: 12px 14px; font-size: 14px; font-weight: 600; background: #3f7bf6; color: #fff; }}
button:hover {{ background: #2f63d8; }}
input[type="password"]:focus {{ outline: none; border-color: #171717; }}
button {{ width: 100%; font: inherit; cursor: pointer; border: none; border-radius: 6px;
padding: 11px 14px; font-size: 14px; font-weight: 500; background: #171717; color: #fff; }}
button:hover {{ background: #0a0a0a; }}
</style>
</head>
<body>
@@ -153,12 +153,12 @@ pub fn render_password_form(key: &str, filename: &str, error: bool) -> Response
<img src="/logo.svg" alt="logo" />
<span>File Drop</span>
</div>
<h1>Protected file</h1>
<p>Enter the password to download <strong>{safe_filename}</strong>.</p>
<h1>Fichier protégé</h1>
<p>Entrez le mot de passe pour télécharger <strong>{safe_filename}</strong>.</p>
{message}
<form method="post" action="/api/auth/{key}">
<input type="password" name="password" placeholder="Password" autofocus autocomplete="off" />
<button type="submit">Download</button>
<input type="password" name="password" placeholder="Mot de passe" autofocus autocomplete="off" />
<button type="submit">Télécharger</button>
</form>
</main>
</body>
+1 -7
View File
@@ -1,11 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<linearGradient id="g" x1="4" y1="4" x2="60" y2="60" gradientUnits="userSpaceOnUse">
<stop stop-color="#5b8cff"/>
<stop offset="1" stop-color="#a78bfa"/>
</linearGradient>
</defs>
<rect x="4" y="4" width="56" height="56" rx="16" fill="url(#g)"/>
<rect x="4" y="4" width="56" height="56" rx="14" fill="#171717"/>
<path d="M32 42V24m0 0-6 6m6-6 6 6" stroke="#fff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<path d="M20 42v2a4 4 0 0 0 4 4h16a4 4 0 0 0 4-4v-2" stroke="#fff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 624 B

After

Width:  |  Height:  |  Size: 413 B

+1 -7
View File
@@ -1,11 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<linearGradient id="g" x1="4" y1="4" x2="60" y2="60" gradientUnits="userSpaceOnUse">
<stop stop-color="#5b8cff"/>
<stop offset="1" stop-color="#a78bfa"/>
</linearGradient>
</defs>
<rect x="4" y="4" width="56" height="56" rx="16" fill="url(#g)"/>
<rect x="4" y="4" width="56" height="56" rx="14" fill="#171717"/>
<path d="M32 42V24m0 0-6 6m6-6 6 6" stroke="#fff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<path d="M20 42v2a4 4 0 0 0 4 4h16a4 4 0 0 0 4-4v-2" stroke="#fff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 624 B

After

Width:  |  Height:  |  Size: 413 B

+19 -22
View File
@@ -21,7 +21,7 @@
const file = e.detail;
if (!file) return;
if (file.size === 0) {
notify('Empty files are not supported.', 'error');
notify('Les fichiers vides ne sont pas pris en charge.', 'error');
return;
}
currentFile = file;
@@ -49,11 +49,11 @@
createdAt: new Date().toISOString(),
protected: !!data.protected
});
notify('Upload complete.', 'success');
notify('Envoi terminé.', 'success');
currentFile = null;
password = '';
} catch (e) {
notify(e.message || 'Upload failed.', 'error');
notify(e.message || 'Échec de l\'envoi.', 'error');
} finally {
uploading = false;
progress = 0;
@@ -62,33 +62,33 @@
</script>
<div class="mx-auto max-w-6xl px-4 pb-16 sm:px-6">
<header class="flex items-center justify-between gap-4 border-b border-edge/60 py-5">
<header class="flex items-center justify-between gap-4 py-6">
<div class="flex items-center gap-3">
<img src="/logo.svg" alt="File Drop" class="h-9 w-9" />
<img src="/logo.svg" alt="File Drop" class="h-8 w-8" />
<div>
<h1 class="text-lg font-bold leading-tight text-ink">File Drop</h1>
<p class="text-xs text-muted">Private file hosting</p>
<h1 class="text-base font-semibold leading-tight text-ink">File Drop</h1>
<p class="text-xs text-muted">Hébergement de fichiers privé</p>
</div>
</div>
<a href="/" class="btn btn-ghost px-4 py-2 text-xs">New upload</a>
<a href="/" class="btn btn-ghost px-3 py-1.5 text-xs">Nouvel envoi</a>
</header>
<main class="mt-6 grid grid-cols-1 items-start gap-5 lg:grid-cols-[360px_1fr]">
<main class="grid grid-cols-1 items-start gap-4 lg:grid-cols-[360px_1fr]">
<aside class="card p-5 lg:sticky lg:top-6">
<h2 class="mb-4 text-base font-semibold text-ink">Upload a file</h2>
<h2 class="mb-4 text-sm font-semibold text-ink">Envoyer un fichier</h2>
<Dropzone bind:file={currentFile} disabled={uploading} on:file={onFile} />
<div class="mt-4">
<label for="pw" class="mb-1.5 block text-xs font-semibold text-muted">
Password <span class="font-normal opacity-70">(optional)</span>
<label for="pw" class="mb-1.5 block text-xs font-medium text-muted">
Mot de passe <span class="font-normal opacity-70">(optionnel)</span>
</label>
<input
id="pw"
type="password"
class="field"
bind:value={password}
placeholder="Protect the download"
placeholder="Protéger le téléchargement"
autocomplete="new-password"
on:keydown={(e) => {
if (e.key === 'Enter') submit();
@@ -98,26 +98,23 @@
{#if uploading}
<div class="mt-4">
<div class="mb-2 flex items-center justify-between gap-2 text-xs text-muted">
<span class="truncate">{currentFile ? currentFile.name : 'Uploading…'}</span>
<div class="mb-1.5 flex items-center justify-between gap-2 text-xs text-muted">
<span class="truncate">{currentFile ? currentFile.name : 'Envoi…'}</span>
<span class="tabular-nums">{progress}%</span>
</div>
<div class="h-1.5 w-full overflow-hidden rounded-full bg-surface">
<div
class="h-full rounded-full bg-gradient-to-r from-accent to-[#a78bfa] transition-all duration-150"
style="width: {progress}%"
></div>
<div class="h-1 w-full overflow-hidden rounded-full bg-raised">
<div class="h-full bg-accent transition-all duration-150" style="width: {progress}%"></div>
</div>
</div>
{/if}
<button
type="button"
class="btn btn-primary mt-4 w-full py-3"
class="btn btn-primary mt-4 w-full py-2.5"
disabled={!currentFile || uploading}
on:click={submit}
>
{uploading ? 'Uploading…' : 'Upload'}
{uploading ? 'Envoi…' : 'Envoyer'}
</button>
<div class="mt-4">
+9 -42
View File
@@ -11,35 +11,10 @@
body {
@apply bg-base text-ink font-sans;
min-height: 100vh;
background-image:
radial-gradient(1200px 600px at 15% -10%, rgba(91, 140, 255, 0.14), transparent 60%),
radial-gradient(1000px 500px at 100% 0%, rgba(91, 140, 255, 0.08), transparent 55%);
background-attachment: fixed;
}
::selection {
background: rgba(91, 140, 255, 0.3);
}
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #232936;
border-radius: 999px;
border: 2px solid transparent;
background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover {
background: #2f3746;
background-clip: content-box;
background: rgba(23, 23, 23, 0.08);
}
summary {
@@ -53,36 +28,28 @@
@layer components {
.card {
@apply rounded-2xl border border-edge bg-surface/80 backdrop-blur-sm;
@apply rounded-lg border border-edge bg-surface;
}
.btn {
@apply inline-flex items-center justify-center gap-2 rounded-xl text-sm font-semibold
transition-all duration-150 disabled:cursor-not-allowed disabled:opacity-50;
@apply inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium
transition-colors disabled:cursor-not-allowed disabled:opacity-50;
}
.btn-primary {
@apply bg-accent text-white hover:bg-accent-strong
shadow-[0_8px_24px_-8px_rgba(91,140,255,0.7)];
@apply bg-accent text-white hover:bg-accent-strong;
}
.btn-ghost {
@apply bg-transparent text-muted border border-edge hover:text-ink hover:border-muted/60;
@apply border border-edge bg-surface text-ink hover:bg-raised;
}
.btn-danger {
@apply bg-transparent text-danger border border-danger/40 hover:bg-danger/10;
@apply border border-edge bg-surface text-danger hover:border-danger/40;
}
.field {
@apply w-full rounded-xl border border-edge bg-base/70 px-3.5 py-3 text-sm text-ink
placeholder:text-muted/60 outline-none transition focus:border-accent focus:ring-2
focus:ring-accent/20;
}
}
@layer utilities {
.text-gradient {
@apply bg-gradient-to-r from-accent to-[#a78bfa] bg-clip-text text-transparent;
@apply w-full rounded-md border border-edge bg-surface px-3 py-2.5 text-sm text-ink
placeholder:text-muted outline-none transition-colors focus:border-ink;
}
}
+12 -12
View File
@@ -50,8 +50,7 @@
</script>
<div
class="group relative flex cursor-pointer flex-col items-center justify-center rounded-2xl border-2 border-dashed border-edge bg-surface/40 px-6 py-10 text-center transition-all duration-200 hover:border-accent/60 hover:bg-surface/70 {dragging ? 'border-accent bg-accent/10' : ''} {disabled ? 'cursor-not-allowed opacity-60' : ''}"
class:dragging
class="flex cursor-pointer flex-col items-center justify-center rounded-lg border border-dashed border-edge bg-surface px-6 py-10 text-center transition-colors hover:border-ink {dragging ? 'border-ink bg-raised' : ''} {disabled ? 'cursor-not-allowed opacity-60' : ''}"
role="button"
tabindex="0"
aria-label="Choose a file"
@@ -72,20 +71,21 @@
}}
/>
<div
class="mb-4 flex h-14 w-14 items-center justify-center rounded-2xl bg-gradient-to-br from-accent/20 to-[#a78bfa]/20 text-accent transition-transform duration-200 {dragging ? 'scale-110' : 'group-hover:scale-105'}"
>
<svg viewBox="0 0 24 24" fill="none" class="h-7 w-7" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 16V4m0 0L7 9m5-5l5 5" />
<path stroke-linecap="round" stroke-linejoin="round" d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2" />
<div class="mb-3 flex h-10 w-10 items-center justify-center text-muted">
<svg viewBox="0 0 24 24" fill="none" class="h-5 w-5" stroke="currentColor" stroke-width="1.5">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"
/>
</svg>
</div>
{#if file}
<p class="max-w-full truncate text-sm font-semibold text-ink">{file.name}</p>
<p class="mt-1 text-xs text-muted">Drop another file to replace it</p>
<p class="max-w-full truncate text-sm font-medium text-ink">{file.name}</p>
<p class="mt-1 text-xs text-muted">Déposez un autre fichier pour le remplacer</p>
{:else}
<p class="text-sm font-semibold text-ink">Drag &amp; drop a file here</p>
<p class="mt-1 text-xs text-muted">or click to browse — up to 10&nbsp;GB</p>
<p class="text-sm font-medium text-ink">Glissez-déposez un fichier ici</p>
<p class="mt-1 text-xs text-muted">ou cliquez pour parcourir</p>
{/if}
</div>
+18 -14
View File
@@ -8,22 +8,22 @@
async function onDelete(item) {
const ok = window.confirm(
`Delete "${item.originalName || item.downloadName}" permanently? This cannot be undone.`
`Supprimer définitivement « ${item.originalName || item.downloadName} » ?`
);
if (!ok) return;
try {
await deleteFile(item);
removeUpload(item.id);
dispatch('notify', { message: 'File deleted.', kind: 'success' });
dispatch('notify', { message: 'Fichier supprimé.', kind: 'success' });
} catch (e) {
dispatch('notify', { message: e.message || 'Deletion failed.', kind: 'error' });
dispatch('notify', { message: e.message || 'Suppression impossible.', kind: 'error' });
}
}
function onClear() {
if ($uploads.length === 0) return;
const ok = window.confirm(
'Clear this list? This only removes entries from THIS browser. Files on the server are not touched.'
'Vider cette liste ? Seules les entrées de CE navigateur sont supprimées. Les fichiers restent sur le serveur.'
);
if (!ok) return;
clearUploads();
@@ -33,33 +33,37 @@
<section class="card p-5">
<div class="mb-4 flex items-center justify-between gap-3">
<div class="flex items-baseline gap-2">
<h2 class="text-base font-semibold text-ink">Your files</h2>
<h2 class="text-sm font-semibold text-ink">Fichiers</h2>
{#if $uploads.length}
<span class="text-xs text-muted">{$uploads.length} file{$uploads.length === 1 ? '' : 's'}</span>
<span class="text-xs text-muted">{$uploads.length}</span>
{/if}
</div>
{#if $uploads.length}
<button type="button" class="text-xs font-semibold text-muted transition hover:text-danger" on:click={onClear}>
Clear list
<button type="button" class="text-xs font-medium text-muted transition-colors hover:text-danger" on:click={onClear}>
Vider la liste
</button>
{/if}
</div>
{#if $uploads.length}
<div class="flex flex-col gap-3">
<div class="flex flex-col gap-2">
{#each $uploads as item (item.id)}
<FileRow {item} on:delete={(e) => onDelete(e.detail)} />
{/each}
</div>
{:else}
<div class="flex flex-col items-center justify-center py-14 text-center">
<div class="mb-3 flex h-12 w-12 items-center justify-center rounded-2xl bg-surface">
<svg viewBox="0 0 24 24" fill="none" class="h-6 w-6 text-muted/60" stroke="currentColor" stroke-width="1.6">
<path stroke-linecap="round" stroke-linejoin="round" d="M4 7V5a2 2 0 012-2h12a2 2 0 012 2v2M4 7h16M4 7v10a2 2 0 002 2h12a2 2 0 002-2V7" />
<div class="mb-3 flex h-10 w-10 items-center justify-center text-muted/70">
<svg viewBox="0 0 24 24" fill="none" class="h-6 w-6" stroke="currentColor" stroke-width="1.5">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5m8.25 3v6.75m0 0l-3-3m3 3l3-3M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z"
/>
</svg>
</div>
<p class="text-sm text-muted">No files yet.</p>
<p class="mt-1 text-xs text-muted/70">Upload your first file to see it here.</p>
<p class="text-sm text-muted">Aucun fichier pour l'instant.</p>
<p class="mt-1 text-xs text-muted">Envoyez votre premier fichier pour le voir ici.</p>
</div>
{/if}
</section>
+17 -26
View File
@@ -3,8 +3,6 @@
import {
forcedDownloadUrl,
downloadUrl,
displayName,
fileExtension,
formatBytes,
formatDate,
copyText
@@ -16,8 +14,7 @@
let copied = false;
$: name = displayName(item);
$: ext = fileExtension(item);
$: name = item.originalName || item.downloadName || 'file';
async function copy() {
await copyText(downloadUrl(item));
@@ -28,27 +25,21 @@
<div class="card flex flex-col gap-3 p-4 md:flex-row md:items-center md:justify-between">
<div class="flex min-w-0 items-center gap-3">
<div
class="relative flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-accent/20 to-[#a78bfa]/20"
>
<svg viewBox="0 0 24 24" fill="none" class="h-5 w-5 text-accent" stroke="currentColor" stroke-width="1.7">
<path stroke-linecap="round" stroke-linejoin="round" d="M7 3h7l4 4v11a2 2 0 01-2 2H7a2 2 0 01-2-2V5a2 2 0 012-2z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M14 3v4h4" />
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-md border border-edge bg-raised">
<svg viewBox="0 0 24 24" fill="none" class="h-5 w-5 text-muted" stroke="currentColor" stroke-width="1.5">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
/>
</svg>
{#if ext}
<span
class="absolute -bottom-1.5 -right-1.5 max-w-[40px] truncate rounded-md border border-edge bg-raised px-1.5 py-0.5 text-[9px] font-bold uppercase tracking-wide text-muted"
>
{ext}
</span>
{/if}
</div>
<div class="min-w-0">
<div class="flex items-center gap-2">
<span class="truncate text-sm font-semibold text-ink" title={name}>{name}</span>
<span class="truncate text-sm font-medium text-ink" title={name}>{name}</span>
<span
class="shrink-0 rounded-full border px-2 py-0.5 text-[9px] font-bold uppercase tracking-wider {item.protected ? 'border-accent/40 bg-accent/10 text-accent' : 'border-ok/40 bg-ok/10 text-ok'}"
class="shrink-0 rounded-md border px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide {item.protected ? 'border-edge bg-raised text-muted' : 'border-ok/30 bg-ok/10 text-ok'}"
>
{item.protected ? 'Protected' : 'Public'}
</span>
@@ -59,18 +50,18 @@
<div class="flex flex-col gap-2 md:items-end">
<div class="flex flex-wrap gap-2">
<a href={forcedDownloadUrl(item)} class="btn btn-primary px-3 py-2 text-xs">Download</a>
<button type="button" class="btn btn-ghost px-3 py-2 text-xs" on:click={copy}>
{copied ? 'Copied' : 'Copy'}
<a href={forcedDownloadUrl(item)} class="btn btn-primary px-3 py-1.5 text-xs">Télécharger</a>
<button type="button" class="btn btn-ghost px-3 py-1.5 text-xs" on:click={copy}>
{copied ? 'Copié' : 'Copier'}
</button>
<button type="button" class="btn btn-danger px-3 py-2 text-xs" on:click={() => dispatch('delete', item)}>
Delete
<button type="button" class="btn btn-danger px-3 py-1.5 text-xs" on:click={() => dispatch('delete', item)}>
Supprimer
</button>
</div>
<details>
<summary class="cursor-pointer select-none text-xs font-semibold text-muted transition hover:text-ink">
Links ▾
<summary class="cursor-pointer select-none text-xs font-medium text-muted transition-colors hover:text-ink">
Liens
</summary>
<div class="mt-2 md:flex md:justify-end">
<LinkFormats {item} />
+1 -1
View File
@@ -21,7 +21,7 @@
<button
type="button"
title={fmt.value}
class="rounded-lg border border-edge bg-surface px-2.5 font-semibold text-muted transition hover:border-muted/60 hover:text-ink {size === 'sm' ? 'py-1.5 text-[11px]' : 'py-2 text-xs'}"
class="rounded-md border border-edge bg-surface font-medium text-muted transition-colors hover:bg-raised hover:text-ink {size === 'sm' ? 'px-2.5 py-1 text-[11px]' : 'px-2.5 py-1.5 text-xs'}"
on:click={() => copy(fmt)}
>
{copied === fmt.label ? 'Copied' : fmt.label}
+7 -7
View File
@@ -1,6 +1,6 @@
<script>
import { fade } from 'svelte/transition';
import { downloadUrl, displayName, copyText } from '../lib/formats.js';
import { downloadUrl, copyText } from '../lib/formats.js';
import LinkFormats from './LinkFormats.svelte';
export let result;
@@ -8,7 +8,7 @@
let copied = false;
$: url = result ? downloadUrl(result) : '';
$: name = result ? displayName(result) : '';
$: name = result ? result.originalName || result.downloadName || 'file' : '';
async function copy() {
await copyText(url);
@@ -18,25 +18,25 @@
</script>
{#if result}
<div class="card p-4" transition:fade={{ duration: 180 }}>
<div class="card p-4" transition:fade={{ duration: 150 }}>
<div class="mb-3 flex items-center justify-between gap-3">
<span class="truncate text-sm font-semibold text-ink" title={name}>{name}</span>
<span class="truncate text-sm font-medium text-ink" title={name}>{name}</span>
<span
class="shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider {result.protected ? 'border-accent/40 bg-accent/10 text-accent' : 'border-ok/40 bg-ok/10 text-ok'}"
class="shrink-0 rounded-md border px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide {result.protected ? 'border-edge bg-raised text-muted' : 'border-ok/30 bg-ok/10 text-ok'}"
>
{result.protected ? 'Protected' : 'Public'}
</span>
</div>
<div class="flex gap-2">
<input class="field !py-2.5 text-xs" type="text" readonly value={url} aria-label="Download URL" />
<input class="field !py-2 text-xs" type="text" readonly value={url} aria-label="Download URL" />
<button type="button" class="btn btn-primary px-4" on:click={copy}>
{copied ? 'Copied' : 'Copy'}
</button>
</div>
<div class="mt-3">
<p class="mb-2 text-[11px] font-semibold uppercase tracking-wider text-muted">Share links</p>
<p class="mb-2 text-[11px] font-medium uppercase tracking-wide text-muted">Partager</p>
<LinkFormats item={result} size="md" />
</div>
</div>
+5 -5
View File
@@ -27,13 +27,13 @@
</script>
{#if visible}
<div class="fixed bottom-5 left-1/2 z-50 -translate-x-1/2 px-4" transition:fly={{ y: 12, duration: 180 }}>
<div class="fixed bottom-5 left-1/2 z-50 -translate-x-1/2 px-4" transition:fly={{ y: 8, duration: 160 }}>
<div
class="rounded-xl border px-4 py-3 text-sm shadow-2xl backdrop-blur-md {kind === 'error'
? 'border-danger/40 bg-danger/15 text-danger'
class="rounded-md border px-4 py-2.5 text-sm {kind === 'error'
? 'border-danger/40 bg-surface text-danger'
: kind === 'success'
? 'border-ok/40 bg-ok/15 text-ok'
: 'border-edge bg-raised/95 text-ink'}"
? 'border-ok/40 bg-surface text-ok'
: 'border-edge bg-surface text-ink'}"
>
{message}
</div>
+11 -18
View File
@@ -4,26 +4,19 @@ export default {
theme: {
extend: {
colors: {
base: '#0b0d12',
surface: '#12151c',
raised: '#171b24',
edge: '#232936',
ink: '#e8ecf3',
muted: '#98a1b3',
accent: '#5b8cff',
'accent-strong': '#3f6ef0',
danger: '#ef5b5b',
ok: '#46b684'
base: '#fafafa',
surface: '#ffffff',
raised: '#f5f5f5',
edge: '#e5e5e5',
ink: '#171717',
muted: '#737373',
accent: '#171717',
'accent-strong': '#0a0a0a',
danger: '#dc2626',
ok: '#16a34a'
},
fontFamily: {
sans: [
'Inter',
'system-ui',
'-apple-system',
'Segoe UI',
'Roboto',
'sans-serif'
]
sans: ['system-ui', '-apple-system', 'Segoe UI', 'Roboto', 'sans-serif']
}
}
},