Deuxième passe DA : direction "punchy, friendly, donne envie de fun"
Retour direct de Kévin après la 1ère passe (trop sage) : palette poussée en saturation (corail vif en primaire, prune + doré en accents), formes plus rondes (boutons pilule, rayons 20px), micro-animations avec du peps (easing overshoot, coche qui se dessine + confettis CSS purs à l'envoi, spinner animé). Police d'accent Fraunces ajoutée pour les titres (2 fichiers .woff2 auto-hébergés, ~44 Ko, SIL OFL, jamais de CDN). Ton de voix retravaillé : micro-copies plus souriantes, nouvel état "limite de photos atteinte" traité chaleureusement (pas comme une erreur). Cohérence maintenue sur galerie admin, carton imprimable, et générateur QR (sync légère, priorité basse). Testé en conditions réelles sur photobooth.assistantaikev.eu.
This commit is contained in:
@@ -13,6 +13,8 @@
|
|||||||
fichier front, à mettre en place lors du déploiement.
|
fichier front, à mettre en place lors du déploiement.
|
||||||
-->
|
-->
|
||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<meta name="theme-color" content="#faf6f0">
|
||||||
|
<link rel="preload" href="/vendor/fonts/fraunces-latin-600-normal.woff2" as="font" type="font/woff2" crossorigin>
|
||||||
<link rel="stylesheet" href="/admin/gallery/style.css">
|
<link rel="stylesheet" href="/admin/gallery/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -30,6 +32,7 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section id="status-loading" class="state-block" role="status">
|
<section id="status-loading" class="state-block" role="status">
|
||||||
|
<span class="spinner" aria-hidden="true"></span>
|
||||||
<p>Chargement de la galerie…</p>
|
<p>Chargement de la galerie…</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -42,8 +45,21 @@
|
|||||||
<p id="status-banner" class="status-banner" hidden></p>
|
<p id="status-banner" class="status-banner" hidden></p>
|
||||||
|
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<button type="button" id="refresh-btn" class="btn btn--secondary">Actualiser</button>
|
<button type="button" id="refresh-btn" class="btn btn--secondary">
|
||||||
<button type="button" id="download-zip-btn" class="btn">Télécharger tout (ZIP)</button>
|
<svg class="btn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
|
||||||
|
<path d="M20 11A8 8 0 1 0 18.5 16"/>
|
||||||
|
<path d="M20 5v6h-6"/>
|
||||||
|
</svg>
|
||||||
|
<span>Actualiser</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" id="download-zip-btn" class="btn">
|
||||||
|
<svg class="btn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
|
||||||
|
<path d="M12 4v11"/>
|
||||||
|
<path d="M7 10.5 12 15.5 17 10.5"/>
|
||||||
|
<path d="M5 19.5h14"/>
|
||||||
|
</svg>
|
||||||
|
<span>Télécharger tout (ZIP)</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p id="zip-status" class="zip-status" aria-live="polite" hidden></p>
|
<p id="zip-status" class="zip-status" aria-live="polite" hidden></p>
|
||||||
|
|
||||||
|
|||||||
+98
-35
@@ -1,38 +1,58 @@
|
|||||||
/* Page galerie admin — mobile-first mais utilisable au clavier/souris sur
|
/* Page galerie admin — mobile-first mais utilisable au clavier/souris sur
|
||||||
desktop (l'admin peut consulter depuis un ordinateur), sans dépendance
|
desktop (l'admin peut consulter depuis un ordinateur), sans dépendance
|
||||||
externe. Mêmes tokens de couleur/rayon/ombre que la page invité
|
externe. Mêmes tokens de couleur/rayon/ombre/police que la page invité
|
||||||
(src/e/style.css) pour rester cohérent visuellement — c'est aussi l'écran
|
(src/e/style.css) pour rester cohérent visuellement — c'est aussi l'écran
|
||||||
que verront les prospects (photographes, wedding planners) lors d'une
|
que verront les prospects (photographes, wedding planners) lors d'une
|
||||||
démo, donc soigné au même niveau que la page invité. */
|
démo, donc soigné au même niveau que la page invité. Palette et police
|
||||||
|
Fraunces alignées sur la passe 2 (2026-09-16) : voir src/e/style.css pour
|
||||||
|
la justification complète de la direction créative. */
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Fraunces";
|
||||||
|
src: url("/vendor/fonts/fraunces-latin-600-normal.woff2") format("woff2");
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F,
|
||||||
|
U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
|
|
||||||
--color-bg: #faf8f5;
|
--color-bg: #fff8f0;
|
||||||
--color-surface: #ffffff;
|
--color-surface: #ffffff;
|
||||||
--color-surface-muted: #f2ede4;
|
--color-surface-muted: #fdecdd;
|
||||||
--color-text: #262420;
|
--color-text: #2b2016;
|
||||||
--color-muted: #6f6a62;
|
--color-muted: #8a7869;
|
||||||
--color-border: #e3ddd2;
|
--color-border: #f1ddc6;
|
||||||
|
|
||||||
--color-accent: #8a5a44;
|
--color-accent: #e35f2b;
|
||||||
--color-accent-hover: #6e4735;
|
--color-accent-hover: #c74c1e;
|
||||||
--color-accent-soft: #f1e3d8;
|
--color-accent-light: #ff8a52;
|
||||||
--color-accent-contrast: #ffffff;
|
--color-accent-soft: #ffe3d0;
|
||||||
|
--color-accent-contrast: #fffaf6;
|
||||||
|
|
||||||
--color-error-bg: #fbeceb;
|
--color-plum: #7c2f56;
|
||||||
--color-error-text: #7a1f14;
|
--color-plum-soft: #f8e3ee;
|
||||||
--color-warning-bg: #fbf1de;
|
|
||||||
--color-warning-text: #7a5a14;
|
|
||||||
|
|
||||||
--radius-sm: 8px;
|
--color-error-bg: #fdeae6;
|
||||||
--radius: 14px;
|
--color-error-text: #a3341c;
|
||||||
|
--color-warning-bg: #fbe9d8;
|
||||||
|
--color-warning-text: #8a4a10;
|
||||||
|
|
||||||
--shadow-sm: 0 1px 2px rgba(38, 36, 32, 0.06);
|
--radius-sm: 12px;
|
||||||
--shadow-md: 0 6px 20px rgba(38, 36, 32, 0.10);
|
--radius: 20px;
|
||||||
|
--radius-pill: 999px;
|
||||||
|
|
||||||
|
--shadow-sm: 0 2px 6px rgba(163, 78, 30, 0.1);
|
||||||
|
--shadow-md: 0 10px 28px rgba(163, 78, 30, 0.16);
|
||||||
|
|
||||||
|
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
|
||||||
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||||
"Helvetica Neue", Arial, sans-serif;
|
"Helvetica Neue", Arial, sans-serif;
|
||||||
|
--font-display: "Fraunces", Georgia, "Times New Roman", serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -87,10 +107,16 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.page__header h1 {
|
.page__header h1 {
|
||||||
font-size: 1.5rem;
|
font-family: var(--font-display);
|
||||||
font-weight: 700;
|
font-size: 1.7rem;
|
||||||
|
font-weight: 600;
|
||||||
letter-spacing: -0.01em;
|
letter-spacing: -0.01em;
|
||||||
margin: 0.15rem 0 0;
|
margin: 0.15rem 0 0;
|
||||||
|
color: var(--color-accent);
|
||||||
|
background: linear-gradient(100deg, var(--color-accent) 20%, var(--color-plum) 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-meta {
|
.event-meta {
|
||||||
@@ -119,6 +145,23 @@ body {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
display: inline-block;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
margin-bottom: 0.6rem;
|
||||||
|
border: 3px solid var(--color-accent-soft);
|
||||||
|
border-top-color: var(--color-accent);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.status-banner {
|
.status-banner {
|
||||||
background: var(--color-warning-bg);
|
background: var(--color-warning-bg);
|
||||||
color: var(--color-warning-text);
|
color: var(--color-warning-text);
|
||||||
@@ -136,22 +179,33 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.45rem;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-pill);
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1.15rem;
|
||||||
min-height: 44px;
|
min-height: 46px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: var(--color-accent);
|
background: linear-gradient(135deg, var(--color-accent-light), var(--color-accent));
|
||||||
color: var(--color-accent-contrast);
|
color: var(--color-accent-contrast);
|
||||||
box-shadow: var(--shadow-sm);
|
box-shadow: var(--shadow-sm);
|
||||||
|
transition: transform 0.12s var(--ease-bounce), background 0.12s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn--secondary {
|
.btn--secondary {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
color: var(--color-accent);
|
color: var(--color-plum);
|
||||||
border: 1px solid var(--color-accent);
|
border: 1.5px solid var(--color-plum);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,18 +215,18 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn:active:not(:disabled) {
|
.btn:active:not(:disabled) {
|
||||||
background: var(--color-accent-hover);
|
background: linear-gradient(135deg, var(--color-accent), var(--color-accent-hover));
|
||||||
transform: translateY(1px);
|
transform: scale(0.97);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn--secondary:active:not(:disabled) {
|
.btn--secondary:active:not(:disabled) {
|
||||||
background: var(--color-accent-soft);
|
background: var(--color-plum-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn:focus-visible,
|
.btn:focus-visible,
|
||||||
a:focus-visible {
|
a:focus-visible {
|
||||||
outline: 3px solid var(--color-accent);
|
outline: 3px solid var(--color-plum);
|
||||||
outline-offset: 1px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zip-status {
|
.zip-status {
|
||||||
@@ -201,13 +255,13 @@ a:focus-visible {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
box-shadow: var(--shadow-sm);
|
box-shadow: var(--shadow-sm);
|
||||||
transition: box-shadow 0.15s ease, transform 0.15s ease;
|
transition: box-shadow 0.15s var(--ease-bounce), transform 0.15s var(--ease-bounce);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
.photo-card:hover {
|
.photo-card:hover {
|
||||||
box-shadow: var(--shadow-md);
|
box-shadow: var(--shadow-md);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-3px) rotate(-0.6deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,3 +338,12 @@ a:focus-visible {
|
|||||||
.photo-card__download:hover {
|
.photo-card__download:hover {
|
||||||
background: var(--color-accent-hover);
|
background: var(--color-accent-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.spinner,
|
||||||
|
.photo-card,
|
||||||
|
.btn {
|
||||||
|
animation: none !important;
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,21 +17,51 @@
|
|||||||
tables lors de l'événement.
|
tables lors de l'événement.
|
||||||
-->
|
-->
|
||||||
<style>
|
<style>
|
||||||
|
/* Palette/police alignées sur la passe 2 (2026-09-16) du reste du produit
|
||||||
|
— voir src/e/style.css pour la justification complète de la direction
|
||||||
|
créative (punchy/friendly plutôt que sobre). Le carton imprimé est un
|
||||||
|
livrable physique posé sur les tables : mêmes contraintes qu'ailleurs
|
||||||
|
côté fond (pas de dépendance CDN), mais on évite ici le texte en
|
||||||
|
dégradé (background-clip:text) sur les éléments imprimés — beaucoup
|
||||||
|
d'utilisateurs impriment sans l'option "Imprimer les arrière-plans",
|
||||||
|
ce qui ferait disparaître un titre en dégradé. La personnalité vient
|
||||||
|
donc de la police Fraunces + d'une couleur pleine, pas d'un effet qui
|
||||||
|
dépend d'un réglage d'impression. */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Fraunces";
|
||||||
|
src: url("/vendor/fonts/fraunces-latin-600-normal.woff2") format("woff2");
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "Fraunces";
|
||||||
|
src: url("/vendor/fonts/fraunces-latin-500-italic.woff2") format("woff2");
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: italic;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
--color-bg: #faf8f5;
|
--color-bg: #fff8f0;
|
||||||
--color-surface: #ffffff;
|
--color-surface: #ffffff;
|
||||||
--color-surface-muted: #f2ede4;
|
--color-surface-muted: #fdecdd;
|
||||||
--color-text: #262420;
|
--color-text: #2b2016;
|
||||||
--color-muted: #6f6a62;
|
--color-muted: #8a7869;
|
||||||
--color-border: #e3ddd2;
|
--color-border: #f1ddc6;
|
||||||
--color-accent: #8a5a44;
|
--color-accent: #e35f2b;
|
||||||
--color-accent-hover: #6e4735;
|
--color-accent-hover: #c74c1e;
|
||||||
--color-accent-contrast: #ffffff;
|
--color-accent-light: #ff8a52;
|
||||||
--radius-sm: 8px;
|
--color-accent-contrast: #fffaf6;
|
||||||
--radius: 14px;
|
--color-plum: #7c2f56;
|
||||||
|
--radius-sm: 12px;
|
||||||
|
--radius: 20px;
|
||||||
|
--radius-pill: 999px;
|
||||||
|
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||||
"Helvetica Neue", Arial, sans-serif;
|
"Helvetica Neue", Arial, sans-serif;
|
||||||
|
--font-display: "Fraunces", Georgia, "Times New Roman", serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -126,23 +156,27 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 1.1rem;
|
margin-top: 1.1rem;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-pill);
|
||||||
padding: 0.8rem 1rem;
|
padding: 0.8rem 1rem;
|
||||||
min-height: 46px;
|
min-height: 46px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: var(--color-accent);
|
background: linear-gradient(135deg, var(--color-accent-light), var(--color-accent));
|
||||||
color: var(--color-accent-contrast);
|
color: var(--color-accent-contrast);
|
||||||
|
box-shadow: 0 6px 16px rgba(195, 76, 30, 0.22);
|
||||||
|
transition: transform 0.12s var(--ease-bounce), background 0.12s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#printBtn:disabled {
|
#printBtn:disabled {
|
||||||
opacity: 0.55;
|
opacity: 0.55;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#printBtn:active:not(:disabled) {
|
#printBtn:active:not(:disabled) {
|
||||||
background: var(--color-accent-hover);
|
background: linear-gradient(135deg, var(--color-accent), var(--color-accent-hover));
|
||||||
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Zone d'aperçu (écran uniquement) --- */
|
/* --- Zone d'aperçu (écran uniquement) --- */
|
||||||
@@ -158,8 +192,8 @@
|
|||||||
width: 148mm;
|
width: 148mm;
|
||||||
height: 210mm;
|
height: 210mm;
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
border: 1px solid var(--color-border);
|
border: 1.5mm solid var(--color-accent);
|
||||||
border-radius: 4mm;
|
border-radius: 6mm;
|
||||||
box-shadow: 0 10px 30px rgba(38, 36, 32, 0.18);
|
box-shadow: 0 10px 30px rgba(38, 36, 32, 0.18);
|
||||||
padding: 14mm 12mm;
|
padding: 14mm 12mm;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -189,17 +223,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card__title {
|
.card__title {
|
||||||
font-size: 8mm;
|
font-family: var(--font-display);
|
||||||
font-weight: 700;
|
font-size: 9mm;
|
||||||
|
font-weight: 600;
|
||||||
letter-spacing: -0.01em;
|
letter-spacing: -0.01em;
|
||||||
|
color: var(--color-accent-hover);
|
||||||
margin: 6mm 0 2mm;
|
margin: 6mm 0 2mm;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card__prompt {
|
.card__prompt {
|
||||||
font-size: 4.2mm;
|
font-family: var(--font-display);
|
||||||
color: var(--color-muted);
|
font-style: italic;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 4.6mm;
|
||||||
|
color: var(--color-plum);
|
||||||
margin: 0 0 8mm;
|
margin: 0 0 8mm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +249,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: var(--color-surface-muted);
|
background: var(--color-surface-muted);
|
||||||
border-radius: 3mm;
|
border-radius: 5mm;
|
||||||
padding: 5mm;
|
padding: 5mm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,7 +365,7 @@
|
|||||||
<span class="card__brand-name">photobooth</span>
|
<span class="card__brand-name">photobooth</span>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="card__title" id="cardTitle">Votre événement</h2>
|
<h2 class="card__title" id="cardTitle">Votre événement</h2>
|
||||||
<p class="card__prompt">Scannez pour déposer vos photos</p>
|
<p class="card__prompt">Scanne, souris, envoie ta photo !</p>
|
||||||
<div class="card__qr" id="qrContainer">
|
<div class="card__qr" id="qrContainer">
|
||||||
<span class="card__qr-placeholder">Saisissez un slug pour générer le QR</span>
|
<span class="card__qr-placeholder">Saisissez un slug pour générer le QR</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -348,7 +387,7 @@
|
|||||||
<span class="card__brand-name">photobooth</span>
|
<span class="card__brand-name">photobooth</span>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="card__title" id="cardTitlePrint">Votre événement</h2>
|
<h2 class="card__title" id="cardTitlePrint">Votre événement</h2>
|
||||||
<p class="card__prompt">Scannez pour déposer vos photos</p>
|
<p class="card__prompt">Scanne, souris, envoie ta photo !</p>
|
||||||
<div class="card__qr" id="qrContainerPrint"></div>
|
<div class="card__qr" id="qrContainerPrint"></div>
|
||||||
<p class="card__url" id="cardUrlPrint"></p>
|
<p class="card__url" id="cardUrlPrint"></p>
|
||||||
<p class="card__footnote">Vos photos sont supprimées automatiquement 7 jours après l'événement.</p>
|
<p class="card__footnote">Vos photos sont supprimées automatiquement 7 jours après l'événement.</p>
|
||||||
|
|||||||
+17
-14
@@ -8,23 +8,26 @@
|
|||||||
<style>
|
<style>
|
||||||
/* Mêmes tokens que src/e/style.css et src/admin/gallery/style.css —
|
/* Mêmes tokens que src/e/style.css et src/admin/gallery/style.css —
|
||||||
cohérence visuelle même sur cet outil interne que les clients ne
|
cohérence visuelle même sur cet outil interne que les clients ne
|
||||||
voient pas (cf. retour Kévin 2026-09-16). */
|
voient pas (cf. retour Kévin 2026-09-16). Palette/rayons resynchronisés
|
||||||
|
avec la passe 2 (2026-09-16, direction "punchy/friendly") ; outil
|
||||||
|
interne, priorité basse donc pas de police d'accent ni d'animation
|
||||||
|
ajoutées ici, juste la cohérence de base (couleurs, rayons). */
|
||||||
:root {
|
:root {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
--color-bg: #faf8f5;
|
--color-bg: #fff8f0;
|
||||||
--color-surface: #ffffff;
|
--color-surface: #ffffff;
|
||||||
--color-surface-muted: #f2ede4;
|
--color-surface-muted: #fdecdd;
|
||||||
--color-text: #262420;
|
--color-text: #2b2016;
|
||||||
--color-muted: #6f6a62;
|
--color-muted: #8a7869;
|
||||||
--color-border: #e3ddd2;
|
--color-border: #f1ddc6;
|
||||||
--color-accent: #8a5a44;
|
--color-accent: #e35f2b;
|
||||||
--color-accent-hover: #6e4735;
|
--color-accent-hover: #c74c1e;
|
||||||
--color-accent-soft: #f1e3d8;
|
--color-accent-soft: #ffe3d0;
|
||||||
--color-accent-contrast: #ffffff;
|
--color-accent-contrast: #fffaf6;
|
||||||
--color-error-text: #7a1f14;
|
--color-error-text: #a3341c;
|
||||||
--radius-sm: 8px;
|
--radius-sm: 12px;
|
||||||
--radius: 14px;
|
--radius: 20px;
|
||||||
--shadow-sm: 0 1px 2px rgba(38, 36, 32, 0.06);
|
--shadow-sm: 0 2px 6px rgba(163, 78, 30, 0.1);
|
||||||
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||||
"Helvetica Neue", Arial, sans-serif;
|
"Helvetica Neue", Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-6
@@ -12,6 +12,8 @@
|
|||||||
lors du déploiement, hors scope de ce fichier front.
|
lors du déploiement, hors scope de ce fichier front.
|
||||||
-->
|
-->
|
||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<meta name="theme-color" content="#faf6f0">
|
||||||
|
<link rel="preload" href="/vendor/fonts/fraunces-latin-600-normal.woff2" as="font" type="font/woff2" crossorigin>
|
||||||
<link rel="stylesheet" href="/e/style.css">
|
<link rel="stylesheet" href="/e/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -25,19 +27,43 @@
|
|||||||
<span class="brand__name">photobooth</span>
|
<span class="brand__name">photobooth</span>
|
||||||
</p>
|
</p>
|
||||||
<h1 id="event-name">Ajouter une photo</h1>
|
<h1 id="event-name">Ajouter une photo</h1>
|
||||||
|
<p class="page__kicker">Une photo, un petit mot, et hop dans l'album !</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section id="status-loading" class="state-block" role="status">
|
<section id="status-loading" class="state-block state-block--loading" role="status">
|
||||||
|
<span class="spinner" aria-hidden="true"></span>
|
||||||
<p>Chargement de l'événement…</p>
|
<p>Chargement de l'événement…</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="status-error" class="state-block state-block--error" hidden role="alert">
|
<section id="status-error" class="state-block state-block--error" hidden role="alert">
|
||||||
|
<div class="state-icon state-icon--error" aria-hidden="true">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" focusable="false">
|
||||||
|
<circle cx="12" cy="12" r="9"/>
|
||||||
|
<path d="M12 8v5"/>
|
||||||
|
<path d="M12 16.2v.1"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="state-icon state-icon--limit" aria-hidden="true">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" focusable="false">
|
||||||
|
<path d="M12 20.2s-7.5-4.6-7.5-10.1a4.4 4.4 0 0 1 7.5-3.1 4.4 4.4 0 0 1 7.5 3.1c0 5.5-7.5 10.1-7.5 10.1Z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
<p id="status-error-message"></p>
|
<p id="status-error-message"></p>
|
||||||
<button type="button" id="retry-btn" hidden>Réessayer</button>
|
<button type="button" id="retry-btn" hidden>Réessayer</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="success-block" class="state-block state-block--success" hidden role="status">
|
<section id="success-block" class="state-block state-block--success" hidden role="status">
|
||||||
<p>Merci ! Ta photo est bien envoyée 🎉</p>
|
<div class="confetti" aria-hidden="true">
|
||||||
|
<span></span><span></span><span></span><span></span><span></span><span></span>
|
||||||
|
</div>
|
||||||
|
<div class="success-check" aria-hidden="true">
|
||||||
|
<svg viewBox="0 0 52 52" fill="none" focusable="false">
|
||||||
|
<circle class="success-check__circle" cx="26" cy="26" r="23" stroke-width="2.5"/>
|
||||||
|
<path class="success-check__mark" d="M15 27.5 22.5 35 38 18" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p class="success-title">C'est envoyé !</p>
|
||||||
|
<p class="success-text">Ta photo vient de rejoindre la fête. Merci d'avoir été là !</p>
|
||||||
<button type="button" id="add-another-btn">Ajouter une autre photo</button>
|
<button type="button" id="add-another-btn">Ajouter une autre photo</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -47,16 +73,20 @@
|
|||||||
<!-- Deux entrées séparées : "photo-input" (capture="environment")
|
<!-- Deux entrées séparées : "photo-input" (capture="environment")
|
||||||
ouvre directement l'appareil photo, "photo-input-gallery" ouvre
|
ouvre directement l'appareil photo, "photo-input-gallery" ouvre
|
||||||
le sélecteur de fichiers classique. Décision actée avec Kévin le
|
le sélecteur de fichiers classique. Décision actée avec Kévin le
|
||||||
2026-09-16 : les deux doivent être accessibles en un tap. -->
|
2026-09-16 : les deux doivent être accessibles en un tap.
|
||||||
|
Hiérarchie visuelle ajoutée : "Prendre une photo" est l'action
|
||||||
|
attendue pour l'immense majorité des invités pendant l'événement
|
||||||
|
(capture sur le moment), donc traitée en primaire ; "Depuis la
|
||||||
|
galerie" reste secondaire mais tout aussi accessible. -->
|
||||||
<div class="photo-source-buttons">
|
<div class="photo-source-buttons">
|
||||||
<label for="photo-input" class="btn-secondary">
|
<label for="photo-input" class="btn-source btn-source--primary">
|
||||||
<svg class="btn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
|
<svg class="btn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
|
||||||
<path d="M4 8h3l1.5-2h7L17 8h3a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1Z"/>
|
<path d="M4 8h3l1.5-2h7L17 8h3a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1Z"/>
|
||||||
<circle cx="12" cy="13" r="3.5"/>
|
<circle cx="12" cy="13" r="3.5"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span>Prendre une photo</span>
|
<span>Prendre une photo</span>
|
||||||
</label>
|
</label>
|
||||||
<label for="photo-input-gallery" class="btn-secondary">
|
<label for="photo-input-gallery" class="btn-source btn-source--secondary">
|
||||||
<svg class="btn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
|
<svg class="btn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
|
||||||
<rect x="3" y="4" width="18" height="16" rx="2.5"/>
|
<rect x="3" y="4" width="18" height="16" rx="2.5"/>
|
||||||
<circle cx="8.5" cy="9.5" r="1.5"/>
|
<circle cx="8.5" cy="9.5" r="1.5"/>
|
||||||
@@ -87,6 +117,7 @@
|
|||||||
|
|
||||||
<img id="preview" class="preview" alt="Aperçu de la photo choisie" hidden>
|
<img id="preview" class="preview" alt="Aperçu de la photo choisie" hidden>
|
||||||
|
|
||||||
|
<div class="field-group">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="nom-input">Ton prénom <span class="optional">(facultatif)</span></label>
|
<label for="nom-input">Ton prénom <span class="optional">(facultatif)</span></label>
|
||||||
<input
|
<input
|
||||||
@@ -109,8 +140,14 @@
|
|||||||
placeholder="Ton petit mot pour l'occasion…"
|
placeholder="Ton petit mot pour l'occasion…"
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="legal-notice">
|
<p class="legal-notice">
|
||||||
|
<svg class="legal-notice__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
|
||||||
|
<rect x="4.5" y="10.5" width="15" height="9.5" rx="2"/>
|
||||||
|
<path d="M8 10.5V7.5a4 4 0 0 1 8 0v3"/>
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
En déposant une photo, vous acceptez qu'elle soit stockée temporairement
|
En déposant une photo, vous acceptez qu'elle soit stockée temporairement
|
||||||
dans le cadre de cet événement et mise à disposition de l'organisateur.
|
dans le cadre de cet événement et mise à disposition de l'organisateur.
|
||||||
Vos photos sont automatiquement supprimées 7 jours après la date de
|
Vos photos sont automatiquement supprimées 7 jours après la date de
|
||||||
@@ -118,11 +155,18 @@
|
|||||||
d'autres fins, ni transmise à des tiers. Vous pouvez demander la
|
d'autres fins, ni transmise à des tiers. Vous pouvez demander la
|
||||||
suppression anticipée d'une photo en contactant l'organisateur de
|
suppression anticipée d'une photo en contactant l'organisateur de
|
||||||
l'événement.
|
l'événement.
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p id="progress-text" class="progress-text" aria-live="polite"></p>
|
<p id="progress-text" class="progress-text" aria-live="polite"></p>
|
||||||
|
|
||||||
<button type="submit" id="submit-btn">Envoyer ma photo</button>
|
<button type="submit" id="submit-btn">
|
||||||
|
<svg class="btn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
|
||||||
|
<path d="M21 4 3 11.5l7 2.5"/>
|
||||||
|
<path d="M10 14l2.5 7L21 4"/>
|
||||||
|
</svg>
|
||||||
|
<span id="submit-btn-label">Envoyer ma photo</span>
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
+413
-66
@@ -1,42 +1,92 @@
|
|||||||
/* Page upload invité — mobile-first, sobre, sans dépendance externe.
|
/* Page upload invité — mobile-first, sans dépendance externe.
|
||||||
Système de design partagé avec la galerie admin (src/admin/gallery/style.css) :
|
Système de design partagé avec la galerie admin (src/admin/gallery/style.css) :
|
||||||
mêmes tokens de couleur/rayon/ombre, pour une identité cohérente entre les
|
mêmes tokens de couleur/rayon/ombre, pour une identité cohérente entre les
|
||||||
deux écrans (voir docs/decisions.md pour le contexte produit). */
|
deux écrans (voir docs/decisions.md pour le contexte produit).
|
||||||
|
|
||||||
|
Direction créative (passe 2, 2026-09-16) — voir rapport de la passe pour
|
||||||
|
le détail complet, résumé ici :
|
||||||
|
Retour direct de Kévin sur la 1ère passe : trop "démo sage", pas assez
|
||||||
|
"envie de participer". Consigne resserrée en cours de route : viser
|
||||||
|
PUNCHY / FRIENDLY / FUN, pas un premium feutré silencieux. Traduction :
|
||||||
|
- Palette plus affirmée : on garde la famille chaude (cohérente avec
|
||||||
|
l'identité perso de Kévin / Rêves Liquides) mais on la sature — un
|
||||||
|
corail vif en primaire plutôt que le brun terracotta sourd de la V1,
|
||||||
|
avec un prune et un doré en accents secondaires (confettis, badges).
|
||||||
|
- Formes plus rondes/organiques (rayons agrandis, boutons pilule).
|
||||||
|
- Micro-animations qui ont du peps : coche qui "pop" avec rebond,
|
||||||
|
confettis colorés au succès, spinner de chargement, easing "overshoot"
|
||||||
|
sur les interactions plutôt que du linear plat.
|
||||||
|
- Ton de voix plus souriant dans les micro-copies (succès, limite
|
||||||
|
atteinte) — cf. src/e/upload.js pour les textes.
|
||||||
|
- Police d'accent Fraunces (serif à forte personnalité, chaleureuse et
|
||||||
|
un peu "pop") en plus de la pile système pour les titres/moments clés
|
||||||
|
— auto-hébergée en .woff2, 2 graisses (~41 Ko au total), voir
|
||||||
|
src/vendor/fonts/ et src/vendor/NOTICE.md. Le corps de texte reste
|
||||||
|
100% système (poids nul). */
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Fraunces";
|
||||||
|
src: url("/vendor/fonts/fraunces-latin-600-normal.woff2") format("woff2");
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F,
|
||||||
|
U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Fraunces";
|
||||||
|
src: url("/vendor/fonts/fraunces-latin-500-italic.woff2") format("woff2");
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: italic;
|
||||||
|
font-display: swap;
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F,
|
||||||
|
U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
|
|
||||||
/* Couleurs — palette chaude et sobre, cohérente avec l'identité perso de
|
/* Couleurs — famille chaude toujours ancrée dans l'identité de Kévin,
|
||||||
Kévin Lecou (Rêves Liquides). L'accent brun/terracotta est inchangé par
|
mais poussée en saturation pour un rendu festif plutôt que sobre. */
|
||||||
rapport à la V1 déployée : on affine la palette autour, pas de rupture. */
|
--color-bg: #fff8f0;
|
||||||
--color-bg: #faf8f5;
|
|
||||||
--color-surface: #ffffff;
|
--color-surface: #ffffff;
|
||||||
--color-surface-muted: #f2ede4;
|
--color-surface-muted: #fdecdd;
|
||||||
--color-text: #262420;
|
--color-text: #2b2016;
|
||||||
--color-muted: #6f6a62;
|
--color-muted: #8a7869;
|
||||||
--color-border: #e3ddd2;
|
--color-border: #f1ddc6;
|
||||||
|
|
||||||
--color-accent: #8a5a44;
|
--color-accent: #e35f2b;
|
||||||
--color-accent-hover: #6e4735;
|
--color-accent-hover: #c74c1e;
|
||||||
--color-accent-soft: #f1e3d8;
|
--color-accent-light: #ff8a52;
|
||||||
--color-accent-contrast: #ffffff;
|
--color-accent-soft: #ffe3d0;
|
||||||
|
--color-accent-contrast: #fffaf6;
|
||||||
|
|
||||||
--color-error-bg: #fbeceb;
|
--color-plum: #7c2f56;
|
||||||
--color-error-text: #7a1f14;
|
--color-plum-soft: #f8e3ee;
|
||||||
--color-success-bg: #eaf3ec;
|
|
||||||
--color-success-text: #1e5c2d;
|
|
||||||
|
|
||||||
/* Rayon à deux niveaux : sm pour les petits éléments (inputs, chips),
|
--color-gold: #f0a91e;
|
||||||
base pour les blocs/cards — donne une identité "arrondie/artisanale"
|
--color-gold-soft: #fef0d3;
|
||||||
un peu plus marquée que le 10px uniforme de la V1. */
|
|
||||||
--radius-sm: 8px;
|
|
||||||
--radius: 14px;
|
|
||||||
|
|
||||||
--shadow-sm: 0 1px 2px rgba(38, 36, 32, 0.06);
|
--color-error-bg: #fdeae6;
|
||||||
--shadow-md: 0 6px 20px rgba(38, 36, 32, 0.10);
|
--color-error-text: #a3341c;
|
||||||
|
--color-success-bg: #fff1de;
|
||||||
|
--color-success-text: #8a4a10;
|
||||||
|
--color-limit-bg: #fbe9d8;
|
||||||
|
--color-limit-text: #8a4a10;
|
||||||
|
|
||||||
|
--radius-sm: 12px;
|
||||||
|
--radius: 20px;
|
||||||
|
--radius-pill: 999px;
|
||||||
|
|
||||||
|
--shadow-sm: 0 2px 6px rgba(163, 78, 30, 0.1);
|
||||||
|
--shadow-md: 0 10px 28px rgba(163, 78, 30, 0.16);
|
||||||
|
|
||||||
|
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
|
||||||
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||||
"Helvetica Neue", Arial, sans-serif;
|
"Helvetica Neue", Arial, sans-serif;
|
||||||
|
--font-display: "Fraunces", Georgia, "Times New Roman", serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -64,7 +114,7 @@ body {
|
|||||||
|
|
||||||
.page__header {
|
.page__header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bloc de marque — discret, rassure l'invité (outil "pro") et signe le
|
/* Bloc de marque — discret, rassure l'invité (outil "pro") et signe le
|
||||||
@@ -76,7 +126,7 @@ body {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
margin-bottom: 0.6rem;
|
margin-bottom: 0.7rem;
|
||||||
color: var(--color-muted);
|
color: var(--color-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,27 +145,59 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.page__header h1 {
|
.page__header h1 {
|
||||||
font-size: 1.5rem;
|
font-family: var(--font-display);
|
||||||
font-weight: 700;
|
font-size: 2rem;
|
||||||
|
font-weight: 600;
|
||||||
letter-spacing: -0.01em;
|
letter-spacing: -0.01em;
|
||||||
margin: 0.15rem 0 0;
|
line-height: 1.15;
|
||||||
|
margin: 0;
|
||||||
|
/* Titre en dégradé corail -> doré : la touche "pop" la plus visible de la
|
||||||
|
page, réservée à ce seul élément pour ne pas diluer son effet. Fallback
|
||||||
|
: couleur pleine (--color-accent) si background-clip:text n'est pas
|
||||||
|
supporté (support large sur mobile Safari/Chrome en pratique). */
|
||||||
|
color: var(--color-accent);
|
||||||
|
background: linear-gradient(100deg, var(--color-accent) 20%, var(--color-gold) 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page__kicker {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
color: var(--color-plum);
|
||||||
|
margin: 0.35rem 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.state-block {
|
.state-block {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 1.25rem 1rem;
|
padding: 1.5rem 1.2rem;
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.state-block--loading {
|
||||||
|
color: var(--color-muted);
|
||||||
|
}
|
||||||
|
|
||||||
.state-block--error {
|
.state-block--error {
|
||||||
background: var(--color-error-bg);
|
background: var(--color-error-bg);
|
||||||
color: var(--color-error-text);
|
color: var(--color-error-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.state-block--error.state-block--limit {
|
||||||
|
background: var(--color-limit-bg);
|
||||||
|
color: var(--color-limit-text);
|
||||||
|
}
|
||||||
|
|
||||||
.state-block--success {
|
.state-block--success {
|
||||||
background: var(--color-success-bg);
|
position: relative;
|
||||||
|
background: linear-gradient(180deg, var(--color-success-bg), var(--color-surface) 75%);
|
||||||
color: var(--color-success-text);
|
color: var(--color-success-text);
|
||||||
|
overflow: visible;
|
||||||
|
padding-top: 1.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.state-block p {
|
.state-block p {
|
||||||
@@ -126,10 +208,67 @@ body {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Spinner de chargement — anneau CSS pur, aucune dépendance. Remplace le
|
||||||
|
simple texte "Chargement…" par un repère visuel qui bouge : la première
|
||||||
|
impression de la page ne doit jamais sembler figée/en panne. */
|
||||||
|
.spinner {
|
||||||
|
display: inline-block;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
margin-bottom: 0.6rem;
|
||||||
|
border: 3px solid var(--color-accent-soft);
|
||||||
|
border-top-color: var(--color-accent);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icônes des états erreur / limite atteinte — les deux existent toujours
|
||||||
|
dans le DOM, affichées/masquées via la classe portée par le bloc parent
|
||||||
|
(state-block--limit vs par défaut = erreur). Le choix entre les deux se
|
||||||
|
fait côté JS uniquement en togglant cette classe (aucune autre logique
|
||||||
|
déplacée dans le CSS). */
|
||||||
|
.state-icon {
|
||||||
|
display: none;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
margin: 0 auto 0.6rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-block--error .state-icon--error {
|
||||||
|
display: flex;
|
||||||
|
background: rgba(163, 52, 28, 0.12);
|
||||||
|
color: var(--color-error-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-block--error.state-block--limit .state-icon--error {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-block--limit .state-icon--limit {
|
||||||
|
display: flex;
|
||||||
|
background: rgba(138, 74, 16, 0.14);
|
||||||
|
color: var(--color-gold);
|
||||||
|
animation: pop-in 0.5s var(--ease-bounce) both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-icon svg {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.upload-form {
|
.upload-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1.1rem;
|
gap: 1.15rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field {
|
.field {
|
||||||
@@ -139,7 +278,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.field label {
|
.field label {
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,20 +288,34 @@ body {
|
|||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Regroupe prénom + petit mot dans une même carte : crée un second niveau
|
||||||
|
de hiérarchie visuelle (la photo reste l'action principale au-dessus,
|
||||||
|
ces champs facultatifs sont secondaires et "posés" ensemble). */
|
||||||
|
.field-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
background: var(--color-surface);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
input[type="text"],
|
input[type="text"],
|
||||||
input[type="file"],
|
|
||||||
textarea {
|
textarea {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
padding: 0.7rem 0.75rem;
|
padding: 0.7rem 0.8rem;
|
||||||
border: 1px solid var(--color-border);
|
border: 1.5px solid var(--color-border);
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
color: inherit;
|
color: inherit;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
transition: border-color 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="file"] {
|
input[type="text"]:focus,
|
||||||
padding: 0.55rem 0.5rem;
|
textarea:focus {
|
||||||
|
border-color: var(--color-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.visually-hidden {
|
.visually-hidden {
|
||||||
@@ -182,27 +335,51 @@ input[type="file"] {
|
|||||||
gap: 0.6rem;
|
gap: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary {
|
/* Boutons "Prendre une photo" / "Depuis la galerie" — forme pilule (identité
|
||||||
|
plus ronde/organique que la V1) avec une hiérarchie claire : la capture
|
||||||
|
directe est l'action attendue pour la quasi-totalité des invités pendant
|
||||||
|
l'événement (le moment se vit là, tout de suite), donc traitée en
|
||||||
|
primaire pleine couleur ; la galerie reste secondaire mais tout aussi
|
||||||
|
accessible en un tap (contrainte actée le 2026-09-16). */
|
||||||
|
.btn-source {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 0.45rem;
|
gap: 0.45rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
font-size: 0.9rem;
|
font-size: 0.88rem;
|
||||||
padding: 0.85rem 0.6rem;
|
padding: 0.9rem 0.6rem;
|
||||||
min-height: 48px;
|
min-height: 50px;
|
||||||
border: 1px solid var(--color-accent);
|
border-radius: var(--radius-pill);
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
color: var(--color-accent);
|
|
||||||
background: var(--color-surface);
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: transform 0.12s var(--ease-bounce), box-shadow 0.12s ease, background 0.12s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary:active {
|
.btn-source--primary {
|
||||||
background: var(--color-accent-soft);
|
border: none;
|
||||||
transform: translateY(1px);
|
background: linear-gradient(135deg, var(--color-accent-light), var(--color-accent));
|
||||||
|
color: var(--color-accent-contrast);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-source--secondary {
|
||||||
|
border: 1.5px solid var(--color-plum);
|
||||||
|
background: var(--color-surface);
|
||||||
|
color: var(--color-plum);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-source:active {
|
||||||
|
transform: scale(0.96);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-source--primary:active {
|
||||||
|
background: linear-gradient(135deg, var(--color-accent), var(--color-accent-hover));
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-source--secondary:active {
|
||||||
|
background: var(--color-plum-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Icônes SVG inline (aucune requête réseau) : remplacent les emoji des
|
/* Icônes SVG inline (aucune requête réseau) : remplacent les emoji des
|
||||||
@@ -219,7 +396,8 @@ input[type="file"] {
|
|||||||
.file-chosen-label {
|
.file-chosen-label {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: var(--color-muted);
|
color: var(--color-muted);
|
||||||
margin: 0;
|
margin: 0.5rem 0 0;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
@@ -230,8 +408,8 @@ textarea {
|
|||||||
input:focus-visible,
|
input:focus-visible,
|
||||||
textarea:focus-visible,
|
textarea:focus-visible,
|
||||||
button:focus-visible {
|
button:focus-visible {
|
||||||
outline: 3px solid var(--color-accent);
|
outline: 3px solid var(--color-plum);
|
||||||
outline-offset: 1px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview {
|
.preview {
|
||||||
@@ -240,50 +418,71 @@ button:focus-visible {
|
|||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
background: var(--color-surface-muted);
|
background: var(--color-surface-muted);
|
||||||
|
animation: pop-in 0.35s var(--ease-bounce) both;
|
||||||
}
|
}
|
||||||
|
|
||||||
.legal-notice {
|
.legal-notice {
|
||||||
font-size: 0.8rem;
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.6rem;
|
||||||
|
font-size: 0.78rem;
|
||||||
color: var(--color-muted);
|
color: var(--color-muted);
|
||||||
background: var(--color-surface-muted);
|
background: var(--color-surface-muted);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
padding: 0.8rem 0.9rem;
|
padding: 0.85rem 0.9rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.legal-notice__icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 0.1rem;
|
||||||
|
color: var(--color-plum);
|
||||||
|
}
|
||||||
|
|
||||||
.progress-text {
|
.progress-text {
|
||||||
min-height: 1.2em;
|
min-height: 1.2em;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: var(--color-muted);
|
font-weight: 600;
|
||||||
|
color: var(--color-plum);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-pill);
|
||||||
padding: 0.85rem 1rem;
|
padding: 0.9rem 1rem;
|
||||||
min-height: 48px;
|
min-height: 50px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: transform 0.12s var(--ease-bounce), background 0.12s ease, opacity 0.12s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#submit-btn,
|
#submit-btn,
|
||||||
#add-another-btn {
|
#add-another-btn {
|
||||||
background: var(--color-accent);
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
background: linear-gradient(135deg, var(--color-accent-light), var(--color-accent));
|
||||||
color: var(--color-accent-contrast);
|
color: var(--color-accent-contrast);
|
||||||
box-shadow: var(--shadow-sm);
|
box-shadow: var(--shadow-md);
|
||||||
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#submit-btn:active:not(:disabled),
|
#submit-btn:active:not(:disabled),
|
||||||
#add-another-btn:active {
|
#add-another-btn:active {
|
||||||
background: var(--color-accent-hover);
|
transform: scale(0.97);
|
||||||
|
background: linear-gradient(135deg, var(--color-accent), var(--color-accent-hover));
|
||||||
}
|
}
|
||||||
|
|
||||||
#submit-btn:disabled {
|
#submit-btn:disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#retry-btn {
|
#retry-btn {
|
||||||
@@ -291,6 +490,154 @@ button {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:active {
|
/* ---------------------------------------------------------------------
|
||||||
transform: translateY(1px);
|
Écran de succès — LE moment à soigner : c'est la dernière impression
|
||||||
|
que garde l'invité. Coche qui "pop" avec rebond + petite volée de
|
||||||
|
confettis colorés (pur CSS, aucune dépendance JS supplémentaire).
|
||||||
|
------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
.success-check {
|
||||||
|
position: relative;
|
||||||
|
width: 76px;
|
||||||
|
height: 76px;
|
||||||
|
margin: 0 auto 0.9rem;
|
||||||
|
animation: pop-in 0.55s var(--ease-bounce) both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-check svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-check__circle {
|
||||||
|
stroke: var(--color-accent);
|
||||||
|
fill: var(--color-gold-soft);
|
||||||
|
stroke-dasharray: 145;
|
||||||
|
stroke-dashoffset: 145;
|
||||||
|
animation: draw 0.5s ease-out 0.1s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-check__mark {
|
||||||
|
stroke: var(--color-accent);
|
||||||
|
fill: none;
|
||||||
|
stroke-dasharray: 32;
|
||||||
|
stroke-dashoffset: 32;
|
||||||
|
animation: draw 0.35s ease-out 0.5s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes draw {
|
||||||
|
to {
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pop-in {
|
||||||
|
0% {
|
||||||
|
transform: scale(0.3);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
65% {
|
||||||
|
transform: scale(1.12);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.confetti {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confetti span {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
width: 9px;
|
||||||
|
height: 9px;
|
||||||
|
opacity: 0;
|
||||||
|
animation: confetti-fall 1.1s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confetti span:nth-child(1) {
|
||||||
|
left: 18%;
|
||||||
|
background: var(--color-accent);
|
||||||
|
border-radius: 2px;
|
||||||
|
animation-delay: 0.05s;
|
||||||
|
}
|
||||||
|
.confetti span:nth-child(2) {
|
||||||
|
left: 32%;
|
||||||
|
top: 0.2rem;
|
||||||
|
background: var(--color-gold);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation-delay: 0.15s;
|
||||||
|
}
|
||||||
|
.confetti span:nth-child(3) {
|
||||||
|
left: 48%;
|
||||||
|
background: var(--color-plum);
|
||||||
|
border-radius: 2px;
|
||||||
|
animation-delay: 0.02s;
|
||||||
|
}
|
||||||
|
.confetti span:nth-child(4) {
|
||||||
|
left: 62%;
|
||||||
|
background: var(--color-accent-light);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation-delay: 0.22s;
|
||||||
|
}
|
||||||
|
.confetti span:nth-child(5) {
|
||||||
|
left: 74%;
|
||||||
|
background: var(--color-gold);
|
||||||
|
border-radius: 2px;
|
||||||
|
animation-delay: 0.1s;
|
||||||
|
}
|
||||||
|
.confetti span:nth-child(6) {
|
||||||
|
left: 86%;
|
||||||
|
background: var(--color-plum);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation-delay: 0.28s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes confetti-fall {
|
||||||
|
0% {
|
||||||
|
transform: translateY(-6px) rotate(0deg) scale(0.6);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
15% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(92px) rotate(300deg) scale(1);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-title {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: 1.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-accent-hover);
|
||||||
|
margin: 0 0 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-text {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
margin: 0 0 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.spinner,
|
||||||
|
.success-check,
|
||||||
|
.success-check__circle,
|
||||||
|
.success-check__mark,
|
||||||
|
.confetti span,
|
||||||
|
.preview,
|
||||||
|
.state-icon--limit,
|
||||||
|
button,
|
||||||
|
.btn-source {
|
||||||
|
animation: none !important;
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-2
@@ -47,6 +47,7 @@ const nomInput = document.getElementById("nom-input");
|
|||||||
const messageInput = document.getElementById("message-input");
|
const messageInput = document.getElementById("message-input");
|
||||||
const progressText = document.getElementById("progress-text");
|
const progressText = document.getElementById("progress-text");
|
||||||
const submitBtn = document.getElementById("submit-btn");
|
const submitBtn = document.getElementById("submit-btn");
|
||||||
|
const submitBtnLabel = document.getElementById("submit-btn-label");
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// État
|
// État
|
||||||
@@ -72,7 +73,7 @@ const ERROR_MESSAGES = {
|
|||||||
"event-closed":
|
"event-closed":
|
||||||
"Cet événement n'accepte plus de photos (il est clos). Merci quand même d'avoir essayé !",
|
"Cet événement n'accepte plus de photos (il est clos). Merci quand même d'avoir essayé !",
|
||||||
"guest-limit-reached":
|
"guest-limit-reached":
|
||||||
"Tu as déjà envoyé le nombre maximum de photos autorisé pour cet événement. Merci pour ta participation !",
|
"Dis donc, on ne t'arrête plus ! Tu as atteint le nombre de photos autorisé pour cet événement. Merci d'avoir aussi bien joué le jeu !",
|
||||||
"file-missing": "Choisis d'abord une photo avant d'envoyer.",
|
"file-missing": "Choisis d'abord une photo avant d'envoyer.",
|
||||||
"file-not-image":
|
"file-not-image":
|
||||||
"Ce fichier n'est pas une photo. Choisis une image (JPEG, PNG, HEIC…).",
|
"Ce fichier n'est pas une photo. Choisis une image (JPEG, PNG, HEIC…).",
|
||||||
@@ -239,6 +240,11 @@ function showError(rawErr, retryAction) {
|
|||||||
successBlock.hidden = true;
|
successBlock.hidden = true;
|
||||||
errorBlock.hidden = false;
|
errorBlock.hidden = false;
|
||||||
errorMessageEl.textContent = ERROR_MESSAGES[err.code] || ERROR_MESSAGES.unknown;
|
errorMessageEl.textContent = ERROR_MESSAGES[err.code] || ERROR_MESSAGES.unknown;
|
||||||
|
// Traitement visuel dédié (chaleureux, pas rouge "erreur") pour la limite
|
||||||
|
// de photos atteinte : ce n'est pas un échec pour l'invité, juste une
|
||||||
|
// limite après participation. Pur toggle de classe CSS, cf. style.css
|
||||||
|
// (.state-block--limit) — aucun changement de logique ici.
|
||||||
|
errorBlock.classList.toggle("state-block--limit", err.code === "guest-limit-reached");
|
||||||
|
|
||||||
state.lastRetryAction = retryAction || null;
|
state.lastRetryAction = retryAction || null;
|
||||||
const canRetry = Boolean(retryAction) && RETRYABLE_CODES.has(err.code);
|
const canRetry = Boolean(retryAction) && RETRYABLE_CODES.has(err.code);
|
||||||
@@ -290,7 +296,10 @@ function setProgress(text) {
|
|||||||
function setSubmitting(isSubmitting) {
|
function setSubmitting(isSubmitting) {
|
||||||
state.isSubmitting = isSubmitting;
|
state.isSubmitting = isSubmitting;
|
||||||
submitBtn.disabled = isSubmitting;
|
submitBtn.disabled = isSubmitting;
|
||||||
submitBtn.textContent = isSubmitting ? "Envoi en cours…" : "Envoyer ma photo";
|
// On cible le <span> du libellé (et non submitBtn.textContent) pour ne pas
|
||||||
|
// effacer l'icône SVG du bouton à chaque bascule — changement purement
|
||||||
|
// visuel, la logique de soumission elle-même est inchangée.
|
||||||
|
submitBtnLabel.textContent = isSubmitting ? "Envoi en cours…" : "Envoyer ma photo";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Vendored
+20
@@ -25,3 +25,23 @@ depuis la source indiquée.
|
|||||||
`src/e/upload.js`. La majorité des invités (Android, ou iPhone dont Safari
|
`src/e/upload.js`. La majorité des invités (Android, ou iPhone dont Safari
|
||||||
a déjà transcodé le fichier en JPEG lors de la sélection) ne téléchargeront
|
a déjà transcodé le fichier en JPEG lors de la sélection) ne téléchargeront
|
||||||
jamais ce fichier.
|
jamais ce fichier.
|
||||||
|
|
||||||
|
## Fraunces (fonts/)
|
||||||
|
|
||||||
|
- Fichiers : `fonts/fraunces-latin-600-normal.woff2` (18 Ko),
|
||||||
|
`fonts/fraunces-latin-500-italic.woff2` (22,9 Ko).
|
||||||
|
Sous-ensemble « latin » uniquement (couvre les accents français : la
|
||||||
|
plage Google Fonts « latin » inclut Latin-1 Supplement + œ/Œ, suffisant
|
||||||
|
pour le français — pas besoin de « latin-ext », plus lourd).
|
||||||
|
- Licence : SIL Open Font License 1.1 (police Fraunces par Undercase Type /
|
||||||
|
Oh no Type Co, distribuée via Google Fonts).
|
||||||
|
- Source : régénérée par le projet **Fontsource**
|
||||||
|
(`https://cdn.jsdelivr.net/npm/@fontsource/fraunces@5/files/`), qui
|
||||||
|
convertit Google Fonts en fichiers statiques par graisse — évite de servir
|
||||||
|
la version variable complète (beaucoup plus lourde) alors qu'on n'utilise
|
||||||
|
que 2 graisses fixes.
|
||||||
|
- Usage : police d'accent (titres, moment de succès) chargée en
|
||||||
|
`@font-face` avec `font-display: swap`, jamais via lien Google Fonts en
|
||||||
|
direct (règle du projet). Voir `src/e/style.css` (`--font-display`).
|
||||||
|
Le corps de texte reste sur la pile système (`--font-sans`), aucun poids
|
||||||
|
supplémentaire pour le texte courant.
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user