Étend la DA "Polaroid Néon" aux 4 derniers écrans

Upload invité (priorité haute, full traitement : fond #12100E, CTA
corail, General Sans/Inter, touche Caveat sur le message de succès),
galerie admin (exception admin du skill : sobre, sombre, cartes #1E1B18,
pas de rotation/confettis, corail réservé aux CTA), générateur QR (même
exception admin, traitement minimal), carton imprimable (reste clair —
un support papier n'a pas vocation à avoir un fond noir — mais adopte les
polices et l'accent corail de la nouvelle DA, contraste recalculé pour
l'impression).

Double thème clair/sombre de l'ancienne DA retiré au profit d'un thème
unique sombre, cohérent avec src/galerie/ (premier écran de cette DA,
déjà validé). Dégradés de texte et halo de fond de l'ancienne DA retirés
(skill : jamais de glow/neon artificiel).

Contrastes vérifiés par calcul WCAG AA pour toutes les nouvelles
combinaisons. Chemins d'assets absolus et pattern [hidden]/display
vérifiés sur les 4 écrans (bugs déjà rencontrés deux fois en prod).
This commit is contained in:
2026-09-18 09:21:40 +02:00
parent f4322850d6
commit ea3e49cc04
6 changed files with 432 additions and 627 deletions
+4 -3
View File
@@ -13,9 +13,10 @@
fichier front, à mettre en place lors du déploiement.
-->
<meta name="robots" content="noindex, nofollow">
<meta name="theme-color" content="#faf6f0" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#1b120b" media="(prefers-color-scheme: dark)">
<link rel="preload" href="/vendor/fonts/fraunces-latin-600-normal.woff2" as="font" type="font/woff2" crossorigin>
<!-- Thème unique sombre "Polaroid Néon", exception admin (sobre/data-first,
voir src/admin/gallery/style.css). -->
<meta name="theme-color" content="#12100E">
<link rel="preload" href="/vendor/fonts/generalsans-700.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/admin/gallery/style.css">
</head>
<body>
+105 -157
View File
@@ -1,100 +1,70 @@
/* Page galerie admin — mobile-first mais utilisable au clavier/souris sur
desktop (l'admin peut consulter depuis un ordinateur), sans dépendance
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
que verront les prospects (photographes, wedding planners) lors d'une
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.
/* Galerie admin — direction artistique "Polaroid Néon"
(.claude/skills/photobooth-da/SKILL.md), variante "écran admin" du skill :
sobre, sombre, data-first. Pas de confettis, pas de rotation façon
polaroid sur les cartes photo (grille classique) — l'accent corail est
réservé aux CTA (boutons), jamais décoratif.
Passe 3 (2026-09-16) : mode sombre + finitions "type iPhone" (hover
desktop, entrées en fondu, halo de couleur en fond) alignés sur
src/e/style.css — voir ce fichier pour le détail complet des choix et
des vérifications de contraste. Cet écran est celui que verront le plus
souvent les prospects en démo depuis un ordinateur : les états hover
comptent ici au moins autant que sur la page invité. */
Tokens repris de src/galerie/style.css (écran de référence de cette DA)
pour la cohérence de couleurs/typo entre écrans invité et admin ; les
cartes utilisent #1E1B18 comme demandé par l'exception admin du skill
(plus clair que --color-bg, pour distinguer une "carte" du fond de
page, sans jamais recourir au blanc cassé des polaroids invité).
Contrastes vérifiés par calcul WCAG AA (même méthode que
src/galerie/style.css) : texte clair sur #12100E et sur #1E1B18 passe
très largement ; texte sombre (#12100E) sur les couleurs d'accent
pleines (utilisé uniquement sur les CTA .btn) passe confortablement
(6.3:1 à 13.2:1). */
@font-face {
font-family: "Fraunces";
src: url("/vendor/fonts/fraunces-latin-600-normal.woff2") format("woff2");
font-weight: 600;
font-family: "General Sans";
src: url("/vendor/fonts/generalsans-700.woff2") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inter";
src: url("/vendor/fonts/inter-latin-variable.woff2") format("woff2");
font-weight: 400 700;
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 {
color-scheme: light dark;
color-scheme: dark;
--color-bg: #fff8f0;
--color-surface: #ffffff;
--color-surface-muted: #fdecdd;
--color-text: #2b2016;
--color-muted: #8a7869;
--color-border: #f1ddc6;
--color-bg: #12100e;
--color-ink: #12100e;
--color-text: #faf6f0;
--color-muted: #e8dfd3;
--color-muted-2: #a89e90;
--color-accent: #e35f2b;
--color-accent-hover: #c74c1e;
--color-accent-light: #ff8a52;
--color-accent-soft: #ffe3d0;
--color-accent-contrast: #fffaf6;
--color-coral: #ff5e5b;
--color-yellow: #ffd166;
--color-mint: #06d6a0;
--color-plum: #7c2f56;
--color-plum-soft: #f8e3ee;
/* Exception admin du skill : cartes en #1E1B18. */
--color-card: #1e1b18;
--color-card-muted: #262220;
--color-border-ui: rgba(250, 246, 240, 0.14);
--color-border-ui-strong: rgba(250, 246, 240, 0.26);
--color-error-bg: #fdeae6;
--color-error-text: #a3341c;
--color-warning-bg: #fbe9d8;
--color-warning-text: #8a4a10;
--color-error-bg: rgba(255, 94, 91, 0.14);
--color-warning-bg: rgba(255, 209, 102, 0.16);
--radius-sm: 12px;
--radius: 20px;
--radius-ui: 14px;
--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);
--shadow-soft: 0 10px 24px rgba(0, 0, 0, 0.35);
--shadow-card: 0 6px 16px rgba(0, 0, 0, 0.4);
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--ease-standard: cubic-bezier(0.22, 1, 0.36, 1);
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
--font-display: "Fraunces", Georgia, "Times New Roman", serif;
}
/* Thème sombre — mêmes valeurs et même raisonnement que src/e/style.css
(fond brun quasi-noir, pas d'inversion, contrastes vérifiés WCAG AA). */
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #1b120b;
--color-surface: #26190f;
--color-surface-muted: #32211a;
--color-text: #f7ead9;
--color-muted: #b39d84;
--color-border: #40291a;
--color-accent: #ff7a42;
--color-accent-hover: #ffa06e;
--color-accent-light: #ffab7a;
--color-accent-soft: rgba(255, 122, 66, 0.18);
--color-accent-contrast: #241206;
--color-plum: #f0b8d8;
--color-plum-soft: rgba(240, 184, 216, 0.14);
--color-error-bg: #3a1712;
--color-error-text: #ff9b86;
--color-warning-bg: #3a2a12;
--color-warning-text: #ffd98a;
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.45);
--shadow-md: 0 14px 36px rgba(0, 0, 0, 0.55);
}
body::before {
opacity: 0.35;
}
--font-display: "General Sans", -apple-system, BlinkMacSystemFont, sans-serif;
--font-ui: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
* {
@@ -106,28 +76,13 @@ html {
}
body {
position: relative;
margin: 0;
font-family: var(--font-sans);
font-family: var(--font-ui);
background: var(--color-bg);
color: var(--color-text);
line-height: 1.45;
}
/* Halo de couleur en fond — même principe que src/e/style.css : "plus de
couleur" en réutilisant la palette existante (corail + prune), jamais
une nouvelle teinte, purement décoratif (CSS pur, poids nul). */
body::before {
content: "";
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background: radial-gradient(55% 32% at 50% -8%, var(--color-accent-soft) 0%, transparent 72%),
radial-gradient(40% 26% at 100% 100%, var(--color-plum-soft) 0%, transparent 70%);
opacity: 0.7;
}
.page {
max-width: 960px;
margin: 0 auto;
@@ -138,18 +93,6 @@ body::before {
.page__header {
text-align: center;
margin-bottom: 1.5rem;
animation: fade-rise 0.5s var(--ease-standard) both;
}
@keyframes fade-rise {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.brand {
@@ -158,14 +101,14 @@ body::before {
justify-content: center;
gap: 0.4rem;
margin-bottom: 0.6rem;
color: var(--color-muted);
color: var(--color-muted-2);
}
.brand__mark {
width: 16px;
height: 16px;
flex-shrink: 0;
color: var(--color-accent);
color: var(--color-coral);
}
.brand__name {
@@ -175,21 +118,19 @@ body::before {
text-transform: uppercase;
}
/* Écran admin = data-first, sobre : titre en texte plein (pas de dégradé
décoratif), contrairement à ce que faisait l'ancienne DA. */
.page__header h1 {
font-family: var(--font-display);
font-size: 1.7rem;
font-weight: 600;
font-size: 1.5rem;
font-weight: 700;
letter-spacing: -0.01em;
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;
color: var(--color-text);
}
.event-meta {
color: var(--color-muted);
color: var(--color-muted-2);
font-size: 0.9rem;
margin: 0.4rem 0 0;
}
@@ -197,13 +138,14 @@ body::before {
.state-block {
text-align: center;
padding: 1.25rem 1rem;
border-radius: var(--radius);
border-radius: var(--radius-ui);
margin-bottom: 1rem;
background: var(--color-card);
}
.state-block--error {
background: var(--color-error-bg);
color: var(--color-error-text);
color: var(--color-text);
}
.state-block p {
@@ -219,8 +161,8 @@ body::before {
width: 26px;
height: 26px;
margin-bottom: 0.6rem;
border: 3px solid var(--color-accent-soft);
border-top-color: var(--color-accent);
border: 3px solid rgba(255, 94, 91, 0.22);
border-top-color: var(--color-coral);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@@ -233,8 +175,8 @@ body::before {
.status-banner {
background: var(--color-warning-bg);
color: var(--color-warning-text);
border-radius: var(--radius);
color: var(--color-text);
border-radius: var(--radius-ui);
padding: 0.85rem 1rem;
margin: 0 0 1.25rem;
font-size: 0.9rem;
@@ -247,6 +189,8 @@ body::before {
margin-bottom: 0.75rem;
}
/* Boutons — l'accent corail est réservé au CTA principal (télécharger le
ZIP) ; le bouton secondaire (Actualiser) reste sobre, sans couleur. */
.btn {
display: inline-flex;
align-items: center;
@@ -259,10 +203,10 @@ body::before {
padding: 0.75rem 1.15rem;
min-height: 46px;
cursor: pointer;
background: linear-gradient(135deg, var(--color-accent-light), var(--color-accent));
color: var(--color-accent-contrast);
box-shadow: var(--shadow-sm);
transition: transform 0.12s var(--ease-bounce), background 0.12s ease;
background: var(--color-coral);
color: var(--color-ink);
box-shadow: var(--shadow-card);
transition: transform 0.12s var(--ease-standard), background 0.12s ease, box-shadow 0.12s ease;
}
.btn-icon {
@@ -272,52 +216,53 @@ body::before {
}
.btn--secondary {
background: var(--color-surface);
color: var(--color-plum);
border: 1.5px solid var(--color-plum);
background: var(--color-card);
color: var(--color-text);
border: 1.5px solid var(--color-border-ui-strong);
box-shadow: none;
}
.btn:disabled {
opacity: 0.55;
opacity: 0.5;
cursor: not-allowed;
}
.btn:active:not(:disabled) {
background: linear-gradient(135deg, var(--color-accent), var(--color-accent-hover));
background: #ff7673;
transform: scale(0.97);
}
.btn--secondary:active:not(:disabled) {
background: var(--color-plum-soft);
background: var(--color-card-muted);
}
@media (hover: hover) {
.btn:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: var(--shadow-md);
box-shadow: var(--shadow-soft);
}
.btn--secondary:hover:not(:disabled) {
background: var(--color-plum-soft);
background: var(--color-card-muted);
border-color: var(--color-coral);
box-shadow: none;
}
}
.btn:focus-visible,
a:focus-visible {
outline: 3px solid var(--color-plum);
outline: 3px solid var(--color-coral);
outline-offset: 2px;
}
.zip-status {
font-size: 0.85rem;
color: var(--color-muted);
color: var(--color-muted-2);
margin: 0 0 1.25rem;
}
.zip-status--error {
color: var(--color-error-text);
color: var(--color-coral);
}
.photo-grid {
@@ -329,26 +274,29 @@ a:focus-visible {
gap: 1rem;
}
/* Exception admin explicite : grille classique, cartes rectangulaires,
AUCUNE rotation façon polaroid (contrairement à src/galerie/style.css)
ni glow/neon — juste une carte sobre en #1E1B18. */
.photo-card {
position: relative;
background: var(--color-surface);
border-radius: var(--radius);
background: var(--color-card);
border-radius: var(--radius-ui);
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: var(--shadow-sm);
box-shadow: var(--shadow-card);
transition: box-shadow 0.2s var(--ease-standard), transform 0.2s var(--ease-standard);
}
/* Liseré de couleur en haut de carte : discret au repos, affirmé au survol
— encore de la "couleur" réutilisant la palette existante, appliquée à
un endroit où elle valorise le contenu (la photo) sans le concurrencer. */
/* Liseré corail en haut de carte au survol desktop uniquement : seule
touche décorative de couleur tolérée ici, et encore, discrète et
réactive au hover plutôt que permanente. */
.photo-card::before {
content: "";
position: absolute;
inset: 0 0 auto 0;
height: 3px;
background: linear-gradient(90deg, var(--color-accent), var(--color-plum));
background: var(--color-coral);
opacity: 0;
transition: opacity 0.2s var(--ease-standard);
z-index: 1;
@@ -356,8 +304,8 @@ a:focus-visible {
@media (hover: hover) {
.photo-card:hover {
box-shadow: var(--shadow-md);
transform: translateY(-3px) rotate(-0.6deg);
box-shadow: var(--shadow-soft);
transform: translateY(-3px);
}
.photo-card:hover::before {
@@ -369,7 +317,7 @@ a:focus-visible {
position: relative;
width: 100%;
aspect-ratio: 1 / 1;
background: var(--color-surface-muted);
background: var(--color-card-muted);
}
.photo-card__thumb {
@@ -387,7 +335,7 @@ a:focus-visible {
justify-content: center;
text-align: center;
font-size: 0.75rem;
color: var(--color-muted);
color: var(--color-muted-2);
padding: 0.5rem;
}
@@ -409,17 +357,18 @@ a:focus-visible {
font-size: 0.85rem;
margin: 0 0 0.2rem;
word-break: break-word;
color: var(--color-text);
}
.photo-card__message {
color: var(--color-text);
color: var(--color-muted);
font-size: 0.8rem;
margin: 0 0 0.35rem;
word-break: break-word;
}
.photo-card__date {
color: var(--color-muted);
color: var(--color-muted-2);
font-size: 0.72rem;
margin: 0;
}
@@ -428,23 +377,22 @@ a:focus-visible {
display: block;
text-align: center;
padding: 0.6rem;
background: var(--color-accent);
color: var(--color-accent-contrast);
background: var(--color-coral);
color: var(--color-ink);
text-decoration: none;
font-size: 0.82rem;
font-weight: 600;
font-weight: 700;
}
.photo-card__download:hover {
background: var(--color-accent-hover);
background: #ff7673;
}
@media (prefers-reduced-motion: reduce) {
.spinner,
.photo-card,
.photo-card::before,
.btn,
.page__header {
.btn {
animation: none !important;
transition: none !important;
}
+67 -45
View File
@@ -17,51 +17,62 @@
tables lors de l'événement.
-->
<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. */
/* Direction artistique "Polaroid Néon" (.claude/skills/photobooth-da/SKILL.md).
Compromis assumé pour cet écran (signalé à Kévin, pas tranché seul) :
le carton reste un support IMPRIMÉ (papier, posé sur les tables), donc
il garde un fond clair — un fond #12100E n'a pas de sens sur un support
physique et gâcherait de l'encre en impression maison. En revanche il
adopte les polices (General Sans pour le titre, Caveat pour
l'accroche manuscrite — remplace Fraunces) et l'accent corail de la
nouvelle DA pour rester cohérent avec le reste de la marque. Comme
avant : pas de dégradé (background-clip:text) sur les éléments
imprimés — beaucoup d'utilisateurs impriment sans "Imprimer les
arrière-plans", ce qui ferait disparaître un titre en dégradé. Le
texte sur fond corail plein (bouton "Imprimer") utilise du texte
sombre (#12100E), conformément à la règle de contraste WCAG AA
vérifiée pour cette DA (le corail #FF5E5B échoue avec du texte clair,
6.3:1 à 13.2:1 avec du texte sombre). */
@font-face {
font-family: "Fraunces";
src: url("/vendor/fonts/fraunces-latin-600-normal.woff2") format("woff2");
font-weight: 600;
font-family: "General Sans";
src: url("/vendor/fonts/generalsans-700.woff2") format("woff2");
font-weight: 700;
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-family: "Caveat";
src: url("/vendor/fonts/caveat-latin-variable.woff2") format("woff2");
font-weight: 400 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inter";
src: url("/vendor/fonts/inter-latin-variable.woff2") format("woff2");
font-weight: 400 700;
font-style: normal;
font-display: swap;
}
:root {
color-scheme: light;
--color-bg: #fff8f0;
--color-bg: #faf6f0;
--color-surface: #ffffff;
--color-surface-muted: #fdecdd;
--color-text: #2b2016;
--color-muted: #8a7869;
--color-border: #f1ddc6;
--color-accent: #e35f2b;
--color-accent-hover: #c74c1e;
--color-accent-light: #ff8a52;
--color-accent-contrast: #fffaf6;
--color-plum: #7c2f56;
--color-surface-muted: #f3ece1;
--color-text: #12100e;
--color-ink: #12100e;
--color-muted: #5c5348;
--color-border: #e8dfd3;
--color-coral: #ff5e5b;
--color-coral-hover: #e44542;
--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,
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
--font-display: "Fraunces", Georgia, "Times New Roman", serif;
--font-display: "General Sans", Arial, sans-serif;
--font-hand: "Caveat", "Segoe Script", cursive;
}
* {
@@ -75,6 +86,10 @@
color: var(--color-text);
}
h1 {
font-family: var(--font-display);
}
.workspace {
display: grid;
grid-template-columns: 300px 1fr;
@@ -141,7 +156,7 @@
input:focus-visible,
select:focus-visible,
button:focus-visible {
outline: 3px solid var(--color-accent);
outline: 3px solid var(--color-coral);
outline-offset: 1px;
}
@@ -162,10 +177,10 @@
padding: 0.8rem 1rem;
min-height: 46px;
cursor: pointer;
background: linear-gradient(135deg, var(--color-accent-light), var(--color-accent));
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;
background: var(--color-coral);
color: var(--color-ink);
box-shadow: 0 6px 16px rgba(255, 94, 91, 0.28);
transition: transform 0.12s var(--ease-spring), background 0.12s ease;
}
#printBtn:disabled {
@@ -175,7 +190,7 @@
}
#printBtn:active:not(:disabled) {
background: linear-gradient(135deg, var(--color-accent), var(--color-accent-hover));
background: var(--color-coral-hover);
transform: scale(0.98);
}
@@ -192,7 +207,7 @@
width: 148mm;
height: 210mm;
background: var(--color-surface);
border: 1.5mm solid var(--color-accent);
border: 1.5mm solid var(--color-coral);
border-radius: 6mm;
box-shadow: 0 10px 30px rgba(38, 36, 32, 0.18);
padding: 14mm 12mm;
@@ -212,7 +227,7 @@
.card__mark {
width: 5mm;
height: 5mm;
color: var(--color-accent);
color: var(--color-coral);
}
.card__brand-name {
@@ -222,23 +237,30 @@
text-transform: uppercase;
}
/* Titre en --color-coral-hover (pas --color-coral tel quel) : contraste
vérifié par calcul sur fond blanc (~4:1, au-dessus du seuil 3:1 des
"grands textes" WCAG AA applicable ici vu la taille/graisse) — le
corail plein pur est trop proche du seuil (~3.0:1, marge trop faible
pour un support imprimé). */
.card__title {
font-family: var(--font-display);
font-size: 9mm;
font-weight: 600;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--color-accent-hover);
color: var(--color-coral-hover);
margin: 6mm 0 2mm;
line-height: 1.2;
word-break: break-word;
}
/* Accroche manuscrite (Caveat, remplace l'italique Fraunces) : en encre
sombre pleine (--color-ink) plutôt qu'une couleur d'accent, pour rester
lisible et fidèle à l'impression quel que soit le réglage d'encre. */
.card__prompt {
font-family: var(--font-display);
font-style: italic;
font-weight: 500;
font-size: 4.6mm;
color: var(--color-plum);
font-family: var(--font-hand);
font-weight: 600;
font-size: 6.5mm;
color: var(--color-ink);
margin: 0 0 8mm;
}
+50 -60
View File
@@ -5,57 +5,43 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Générateur de QR code — photobooth-qr (admin)</title>
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#faf6f0" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#1b120b" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#12100E" />
<style>
/* 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
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'animations
d'entrée ajoutées ici, juste la cohérence de base (couleurs, rayons).
Passe 3 (2026-09-16) : mode sombre ajouté (demandé explicitement pour
ce fichier) + petites finitions hover/focus à coût nul, mais toujours
sans Fraunces ni animation d'entrée — la priorité basse de cet outil
reste valable, seul le mode sombre est une demande directe. */
/* Direction artistique "Polaroid Néon" (.claude/skills/photobooth-da/SKILL.md),
exception admin : sobre, sombre, data-first. Mêmes tokens que
src/admin/gallery/style.css pour la cohérence visuelle entre écrans
admin, même sur cet outil interne que les clients ne voient pas (cf.
retour Kévin 2026-09-16, toujours valable avec la nouvelle DA). Outil
interne à priorité basse : thème unique sombre (pas de double thème
clair/sombre), pas d'animation d'entrée, juste la cohérence de base
(couleurs, rayons, police). */
:root {
color-scheme: light dark;
--color-bg: #fff8f0;
--color-surface: #ffffff;
--color-surface-muted: #fdecdd;
--color-text: #2b2016;
--color-muted: #8a7869;
--color-border: #f1ddc6;
--color-accent: #e35f2b;
--color-accent-hover: #c74c1e;
--color-accent-soft: #ffe3d0;
--color-accent-contrast: #fffaf6;
--color-error-text: #a3341c;
--radius-sm: 12px;
--radius: 20px;
--shadow-sm: 0 2px 6px rgba(163, 78, 30, 0.1);
color-scheme: dark;
--color-bg: #12100e;
--color-ink: #12100e;
--color-text: #faf6f0;
--color-muted: #e8dfd3;
--color-muted-2: #a89e90;
--color-coral: #ff5e5b;
--color-card: #1e1b18;
--color-card-muted: #262220;
--color-border-ui: rgba(250, 246, 240, 0.16);
--radius-sm: 10px;
--radius: 14px;
--shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.3);
--ease-standard: cubic-bezier(0.22, 1, 0.36, 1);
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
--font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #1b120b;
--color-surface: #26190f;
--color-surface-muted: #32211a;
--color-text: #f7ead9;
--color-muted: #b39d84;
--color-border: #40291a;
--color-accent: #ff7a42;
--color-accent-hover: #ffa06e;
--color-accent-soft: rgba(255, 122, 66, 0.18);
--color-accent-contrast: #241206;
--color-error-text: #ff9b86;
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.45);
}
@font-face {
font-family: "Inter";
src: url("/vendor/fonts/inter-latin-variable.woff2") format("woff2");
font-weight: 400 700;
font-style: normal;
font-display: swap;
}
* { box-sizing: border-box; }
body {
margin: 0;
@@ -79,7 +65,7 @@
width: 16px;
height: 16px;
flex-shrink: 0;
color: var(--color-accent);
color: var(--color-coral);
}
.brand span {
font-size: 0.7rem;
@@ -111,24 +97,24 @@
padding: 0.7rem 0.75rem;
font: inherit;
font-size: 1rem;
border: 1px solid var(--color-border);
border: 1px solid var(--color-border-ui);
border-radius: var(--radius-sm);
background: var(--color-surface);
background: var(--color-card);
color: inherit;
transition: border-color 0.15s var(--ease-standard), box-shadow 0.15s var(--ease-standard);
}
input:focus,
select:focus {
outline: none;
border-color: var(--color-accent);
box-shadow: 0 0 0 4px var(--color-accent-soft);
border-color: var(--color-coral);
box-shadow: 0 0 0 4px rgba(255, 94, 91, 0.22);
}
.preview {
margin-top: 1.5rem;
padding: 1.1rem;
border: 1px solid var(--color-border);
border: 1px solid var(--color-border-ui);
border-radius: var(--radius);
background: var(--color-surface);
background: var(--color-card);
display: flex;
flex-direction: column;
align-items: center;
@@ -160,40 +146,44 @@
button {
flex: 1;
font: inherit;
font-weight: 600;
font-weight: 700;
padding: 0.85rem 0.7rem;
min-height: 48px;
font-size: 0.95rem;
border: 1px solid var(--color-accent);
border: 1px solid var(--color-coral);
border-radius: var(--radius-sm);
background: var(--color-accent);
color: var(--color-accent-contrast);
background: var(--color-coral);
color: var(--color-ink);
box-shadow: var(--shadow-sm);
cursor: pointer;
transition: transform 0.15s var(--ease-standard), background 0.15s ease;
}
button:active:not(:disabled) {
background: var(--color-accent-hover);
background: #ff7673;
}
button.secondary {
background: var(--color-surface);
color: var(--color-accent);
background: var(--color-card);
color: var(--color-text);
border-color: var(--color-border-ui);
box-shadow: none;
}
button.secondary:active:not(:disabled) {
background: var(--color-accent-soft);
background: var(--color-card-muted);
}
@media (hover: hover) {
button:hover:not(:disabled) {
transform: translateY(-1px);
}
button.secondary:hover:not(:disabled) {
border-color: var(--color-coral);
}
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.error {
color: var(--color-error-text);
color: var(--color-coral);
font-size: 0.85rem;
margin-top: 0.5rem;
min-height: 1.2em;
+5 -6
View File
@@ -12,12 +12,11 @@
lors du déploiement, hors scope de ce fichier front.
-->
<meta name="robots" content="noindex, nofollow">
<!-- Deux meta theme-color (au lieu d'une seule) : teinte la barre
navigateur/PWA selon le thème système, en cohérence avec le mode sombre
ajouté passe 3 (voir src/e/style.css). -->
<meta name="theme-color" content="#faf6f0" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#1b120b" media="(prefers-color-scheme: dark)">
<link rel="preload" href="/vendor/fonts/fraunces-latin-600-normal.woff2" as="font" type="font/woff2" crossorigin>
<!-- Thème unique sombre "Polaroid Néon" (voir src/e/style.css) : une seule
meta theme-color, comme src/galerie/index.html. -->
<meta name="theme-color" content="#12100E">
<link rel="preload" href="/vendor/fonts/generalsans-700.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/vendor/fonts/caveat-latin-variable.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/e/style.css">
</head>
<body>
+201 -356
View File
@@ -1,200 +1,101 @@
/* Page upload invité — mobile-first, sans dépendance externe.
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
deux écrans (voir docs/decisions.md pour le contexte produit).
/* Page upload invité — direction artistique "Polaroid Néon"
(.claude/skills/photobooth-da/SKILL.md). Remplace les 3 passes DA
précédentes (palette corail/prune/doré + Fraunces, voir historique
git) : cet écran suit désormais src/galerie/style.css comme référence
de cohérence (premier écran construit dans cette DA, validé par
Kévin — "c'est vraiment top comme ça !").
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).
Choix pris pendant cette passe, à signaler à Kévin :
- Thème unique sombre (color-scheme: dark), comme src/galerie/style.css,
plutôt que le double thème clair/sombre (prefers-color-scheme) de
l'ancienne DA. Le skill ne demande nulle part de garder un thème
clair, et le premier écran validé n'en a pas — cohérence retenue
plutôt qu'une redondance non demandée. À confirmer si un thème clair
est en fait souhaité pour cet écran précis.
- Titre et éléments de "chrome" en texte plein (pas de dégradé
corail→doré comme l'ancienne DA) : le skill met en garde contre le
"glow/neon CSS artificiel" et l'écran de référence (galerie) n'utilise
aucun dégradé de texte ni halo radial en fond — même logique
appliquée ici pour rester cohérent plutôt que de réintroduire cet
effet.
Passe 3, 2026-09-16 — retour Kévin : "top ! rajoute des animations, de la
couleur [...] brainstorm sur le choix des couleurs et de la typo, ajoute
un mode sombre [...] fait moi quelque chose de vendeur et de friendly
(type iPhone)." Palette/typo déjà validées passe 2 : le brainstorm de
cette passe (voir rapport de passe pour le détail des 2 directions
alternatives écartées) a confirmé la famille corail/prune/doré + Fraunces
plutôt que d'ajouter de nouvelles teintes ou une police supplémentaire —
"plus de couleur" est obtenu en réutilisant plus largement cette même
palette (halo radial en fond, focus ring teinté, confettis étoffés)
plutôt qu'en important une 4e couleur qui romprait l'identité perso de
Kévin. Traduction concrète de cette passe :
- Mode sombre réel via prefers-color-scheme (pas d'inversion brute) :
fond brun quasi-noir (jamais bleu-nuit, pour rester dans la famille
chaude de la marque), texte/accents reclaircis et vérifiés au ratio de
contraste (WCAG AA, cf. rapport de passe pour le détail des calculs).
- Niveau de finition "type iPhone" au sens exécution, pas esthétique iOS
: pas de glassmorphism/blur ni de police système Apple. Concrètement :
easing dédié aux transitions d'état (--ease-standard, plus posé que le
--ease-bounce déjà en place et réservé aux pops/rebonds), halo de
couleur subtil en arrière-plan de page, focus ring teinté sur les
champs, micro-interactions de hover en plus du :active existant
(uniquement sous @media (hover: hover), donc jamais sur mobile tactile
— l'invité ne voit que les états focus/active déjà en place), entrées
en fondu/translation des sections principales au chargement. */
Contrastes vérifiés par calcul WCAG AA (même méthode que
src/galerie/style.css) : texte clair (#FAF6F0/#E8DFD3) sur fond sombre
(#12100E) est très largement au-dessus du seuil ; texte sombre (#12100E)
sur les 3 couleurs d'accent (corail/jaune/vert) passe confortablement
(6.3:1 à 13.2:1) — utilisé systématiquement pour le texte des CTA en
couleur pleine. Les fonds teintés à faible opacité (états erreur/limite/
succès) restent très proches du noir une fois mélangés à #12100E ; le
texte de ces blocs reste donc en couleur claire standard (--color-text/
--color-muted), jamais en couleur d'accent pleine, par prudence. */
@font-face {
font-family: "Fraunces";
src: url("/vendor/fonts/fraunces-latin-600-normal.woff2") format("woff2");
font-weight: 600;
font-family: "General Sans";
src: url("/vendor/fonts/generalsans-700.woff2") format("woff2");
font-weight: 700;
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-family: "Caveat";
src: url("/vendor/fonts/caveat-latin-variable.woff2") format("woff2");
font-weight: 400 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inter";
src: url("/vendor/fonts/inter-latin-variable.woff2") format("woff2");
font-weight: 400 700;
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 {
/* "light dark" (et non "light" seul) : indique au navigateur que les deux
thèmes sont pris en charge, pour que les contrôles natifs (scrollbar,
éventuels <select>) s'adaptent aussi, pas seulement nos propres
couleurs. Les valeurs ci-dessous sont le thème clair ; le thème sombre
est redéfini intégralement dans le bloc @media (prefers-color-scheme:
dark) plus bas — jamais un simple filtre d'inversion. */
color-scheme: light dark;
color-scheme: dark;
/* Couleurs — famille chaude toujours ancrée dans l'identité de Kévin,
mais poussée en saturation pour un rendu festif plutôt que sobre. */
--color-bg: #fff8f0;
--color-surface: #ffffff;
--color-surface-muted: #fdecdd;
--color-text: #2b2016;
--color-muted: #8a7869;
--color-border: #f1ddc6;
/* Mêmes noms/valeurs que src/galerie/style.css pour la cohérence entre
écrans (voir consigne de la passe). */
--color-bg: #12100e;
--color-ink: #12100e; /* texte sombre sur surfaces claires/accents */
--color-text: #faf6f0;
--color-muted: #e8dfd3;
--color-muted-2: #a89e90;
--color-accent: #e35f2b;
--color-accent-hover: #c74c1e;
--color-accent-light: #ff8a52;
--color-accent-soft: #ffe3d0;
--color-accent-contrast: #fffaf6;
--color-coral: #ff5e5b;
--color-yellow: #ffd166;
--color-mint: #06d6a0;
--color-plum: #7c2f56;
--color-plum-soft: #f8e3ee;
--color-border: #e8dfd3;
--color-gold: #f0a91e;
--color-gold-soft: #fef0d3;
--color-error-bg: #fdeae6;
--color-error-text: #a3341c;
--color-success-bg: #fff1de;
--color-success-text: #8a4a10;
--color-limit-bg: #fbe9d8;
--color-limit-text: #8a4a10;
/* Bouton "danger" (Réessayer) : variable dédiée plutôt que de réutiliser
--color-error-text tel quel comme fond de bouton — nécessaire en thème
sombre où le texte d'erreur s'éclaircit (pour rester lisible sur fond
sombre) et ne fonctionne plus comme couleur de fond de bouton avec du
texte blanc (contraste vérifié, voir rapport de passe). */
--color-danger: #a3341c;
--color-danger-contrast: #fff;
--radius-sm: 12px;
--radius: 20px;
--radius: 4px; /* réservé aux éléments façon polaroid, non utilisé ici */
--radius-ui: 14px;
--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);
--shadow-soft: 0 10px 24px rgba(0, 0, 0, 0.35);
--shadow-card: 0 6px 16px rgba(0, 0, 0, 0.4);
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
/* Easing "posé" pour les transitions d'état/hover (fondus, lifts) — le
rebond de --ease-bounce reste réservé aux moments ludiques (pop-in,
coche de succès) pour ne pas le diluer. */
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--ease-standard: cubic-bezier(0.22, 1, 0.36, 1);
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
--font-display: "Fraunces", Georgia, "Times New Roman", serif;
}
--font-display: "General Sans", -apple-system, BlinkMacSystemFont, sans-serif;
--font-hand: "Caveat", "Segoe Script", cursive;
--font-ui: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
/* -----------------------------------------------------------------------
Thème sombre — palette pensée pour l'obscurité (pas une inversion des
couleurs claires) : fond brun quasi-noir pour rester dans la famille
chaude de la marque plutôt qu'un noir bleuté générique, texte et accents
reclaircis et vérifiés au ratio de contraste WCAG AA (>= 4.5:1 pour le
texte courant, >= 3:1 pour le grand texte/les éléments d'UI ; calculs
détaillés dans le rapport de passe). Répond aux deux demandes de Kévin à
la fois : un vrai mode sombre ET un rendu "moins agressif" en usage réel
(salle tamisée en soirée). ----------------------------------------- */
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #1b120b;
--color-surface: #26190f;
--color-surface-muted: #32211a;
--color-text: #f7ead9;
--color-muted: #b39d84;
--color-border: #40291a;
/* Tokens spécifiques à cet écran (formulaire, états), pas repris de
src/galerie/style.css qui n'en a pas besoin. */
--color-surface: #1e1b18;
--color-surface-muted: #241f1a;
--color-border-ui: rgba(250, 246, 240, 0.16);
--color-border-ui-strong: rgba(250, 246, 240, 0.28);
--color-accent: #ff7a42;
--color-accent-hover: #ffa06e;
--color-accent-light: #ffab7a;
--color-accent-soft: rgba(255, 122, 66, 0.18);
--color-accent-contrast: #241206;
--color-error-bg: rgba(255, 94, 91, 0.14);
--color-limit-bg: rgba(255, 209, 102, 0.16);
--color-success-bg: rgba(6, 214, 160, 0.12);
--color-plum: #f0b8d8;
--color-plum-soft: rgba(240, 184, 216, 0.14);
--color-gold: #ffcf6b;
--color-gold-soft: #3a2a12;
--color-error-bg: #3a1712;
--color-error-text: #ff9b86;
--color-success-bg: #2e2110;
--color-success-text: #f2d9b8;
--color-limit-bg: #3a2a12;
--color-limit-text: #ffd98a;
--color-danger: #ff6b52;
--color-danger-contrast: #2b0f08;
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.45);
--shadow-md: 0 14px 36px rgba(0, 0, 0, 0.55);
}
/* Fonds d'icônes état erreur/limite : calculés à partir des couleurs
claires d'origine, non pilotables via une seule variable — surchargés
ici pour rester cohérents en sombre plutôt que de laisser un rouge/or
"clair" plaqué sur fond sombre. */
.state-block--error .state-icon--error {
background: rgba(255, 155, 134, 0.16);
}
.state-block--limit .state-icon--limit {
background: rgba(255, 217, 138, 0.18);
}
.welcome-modal {
background: rgba(0, 0, 0, 0.6);
}
body::before {
opacity: 0.4;
}
--shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.3);
}
* {
@@ -206,32 +107,13 @@ html {
}
body {
position: relative;
margin: 0;
font-family: var(--font-sans);
font-family: var(--font-ui);
background: var(--color-bg);
color: var(--color-text);
line-height: 1.45;
}
/* Halo de couleur en arrière-plan — "plus de couleur" sans ajouter de
teinte à la palette : deux dégradés radiaux réutilisant les couleurs
d'accent déjà en place (corail + doré), très doux, purement décoratifs.
Objectif "type iPhone" : une page peut être vivante sans être plate,
sans pour autant recourir à un vrai fond image (poids nul, CSS pur).
z-index négatif + fixed : reste derrière le contenu, ne capte aucun
événement (pointer-events: none), ne bouge pas au scroll. */
body::before {
content: "";
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background: radial-gradient(60% 38% at 50% -8%, var(--color-accent-soft) 0%, transparent 72%),
radial-gradient(46% 30% at 100% 100%, var(--color-gold-soft) 0%, transparent 70%);
opacity: 0.7;
}
.page {
max-width: 480px;
margin: 0 auto;
@@ -242,10 +124,6 @@ body::before {
.page__header {
text-align: center;
margin-bottom: 1.6rem;
/* Entrée en fondu/translation au chargement — discrète, une seule fois
par affichage (rejoue si la page est masquée/réaffichée via l'attribut
hidden, ce qui reste cohérent : chaque "nouvel écran" mérite sa petite
entrée). */
animation: fade-rise 0.5s var(--ease-standard) both;
}
@@ -260,24 +138,20 @@ body::before {
}
}
/* Bloc de marque — discret, rassure l'invité (outil "pro") et signe le
produit pour les organisateurs/prestataires qui verront la page.
Volontairement minuscule : ce n'est pas l'écran de l'invité qui doit
porter la marque de Kévin, c'est un clin d'œil en filigrane. */
.brand {
display: flex;
align-items: center;
justify-content: center;
gap: 0.4rem;
margin-bottom: 0.7rem;
color: var(--color-muted);
color: var(--color-muted-2);
}
.brand__mark {
width: 16px;
height: 16px;
flex-shrink: 0;
color: var(--color-accent);
color: var(--color-coral);
}
.brand__name {
@@ -285,39 +159,28 @@ body::before {
font-weight: 700;
letter-spacing: 0.13em;
text-transform: uppercase;
font-family: var(--font-ui);
}
.page__header h1 {
font-family: var(--font-display);
font-size: 2rem;
font-weight: 600;
font-weight: 700;
font-size: 1.9rem;
letter-spacing: -0.01em;
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;
color: var(--color-muted);
font-size: 0.95rem;
margin: 0.4rem 0 0;
}
.state-block {
text-align: center;
padding: 1.5rem 1.2rem;
border-radius: var(--radius);
border-radius: var(--radius-ui);
margin-bottom: 1rem;
}
@@ -327,18 +190,17 @@ body::before {
.state-block--error {
background: var(--color-error-bg);
color: var(--color-error-text);
color: var(--color-text);
}
.state-block--error.state-block--limit {
background: var(--color-limit-bg);
color: var(--color-limit-text);
}
.state-block--success {
position: relative;
background: linear-gradient(180deg, var(--color-success-bg), var(--color-surface) 75%);
color: var(--color-success-text);
background: linear-gradient(180deg, var(--color-success-bg), transparent 75%);
color: var(--color-text);
overflow: visible;
padding-top: 1.75rem;
}
@@ -351,16 +213,14 @@ body::before {
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 de chargement — anneau CSS pur, aucune dépendance. */
.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: 3px solid rgba(255, 94, 91, 0.22);
border-top-color: var(--color-coral);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@@ -373,9 +233,8 @@ body::before {
/* 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-block--limit vs par défaut = erreur). Aucune logique déplacée
dans le CSS, uniquement le rendu visuel. */
.state-icon {
display: none;
width: 44px;
@@ -388,8 +247,8 @@ body::before {
.state-block--error .state-icon--error {
display: flex;
background: rgba(163, 52, 28, 0.12);
color: var(--color-error-text);
background: rgba(255, 94, 91, 0.16);
color: var(--color-coral);
}
.state-block--error.state-block--limit .state-icon--error {
@@ -398,9 +257,9 @@ body::before {
.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;
background: rgba(255, 209, 102, 0.18);
color: var(--color-yellow);
animation: pop-in 0.5s var(--ease-spring) both;
}
.state-icon svg {
@@ -415,10 +274,10 @@ body::before {
animation: fade-rise 0.5s var(--ease-standard) 0.08s both;
}
/* Même bug que .welcome-modal (voir commentaire plus haut) : présent depuis
le tout premier commit de ce fichier. form.hidden = true (JS) ne masquait
jamais réellement le formulaire, qui restait affiché en display:flex en
permanence, superposé aux états chargement/erreur/succès. */
/* Bug réel corrigé (2026-09-16, conservé lors du passage à la DA "Polaroid
Néon") : une déclaration `display` en CSS auteur passe toujours devant
`[hidden] { display: none }` du navigateur. Sans cette règle explicite,
`form.hidden = true` (JS) ne masquait jamais réellement le formulaire. */
.upload-form[hidden] {
display: none;
}
@@ -436,20 +295,19 @@ body::before {
.field .optional {
font-weight: 400;
color: var(--color-muted);
color: var(--color-muted-2);
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). */
/* Regroupe prénom + petit mot dans une même carte : second niveau de
hiérarchie visuelle (la photo reste l'action principale au-dessus). */
.field-group {
display: flex;
flex-direction: column;
gap: 1rem;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius);
border: 1px solid var(--color-border-ui);
border-radius: var(--radius-ui);
padding: 1.1rem;
}
@@ -457,21 +315,18 @@ input[type="text"],
textarea {
font: inherit;
padding: 0.7rem 0.8rem;
border: 1.5px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-surface);
border: 1.5px solid var(--color-border-ui);
border-radius: 10px;
background: var(--color-bg);
color: inherit;
width: 100%;
transition: border-color 0.18s var(--ease-standard), box-shadow 0.18s var(--ease-standard);
}
/* Anneau de focus teinté (en plus du changement de bordure) : détail de
finition "type iPhone" — un champ actif doit se voir sans ambiguïté,
avec une transition douce plutôt qu'un changement sec. */
input[type="text"]:focus,
textarea:focus {
border-color: var(--color-accent);
box-shadow: 0 0 0 4px var(--color-accent-soft);
border-color: var(--color-coral);
box-shadow: 0 0 0 4px rgba(255, 94, 91, 0.22);
}
.visually-hidden {
@@ -491,12 +346,10 @@ textarea:focus {
gap: 0.6rem;
}
/* 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). */
/* Boutons "Prendre une photo" / "Depuis la galerie" — la capture directe
est l'action attendue pour la quasi-totalité des invités pendant
l'événement, donc traitée en primaire (corail plein, texte sombre) ;
la galerie reste secondaire mais tout aussi accessible en un tap. */
.btn-source {
flex: 1;
display: flex;
@@ -510,20 +363,20 @@ textarea:focus {
min-height: 50px;
border-radius: var(--radius-pill);
cursor: pointer;
transition: transform 0.12s var(--ease-bounce), box-shadow 0.12s ease, background 0.12s ease;
transition: transform 0.12s var(--ease-spring), box-shadow 0.12s ease, background 0.12s ease;
}
.btn-source--primary {
border: none;
background: linear-gradient(135deg, var(--color-accent-light), var(--color-accent));
color: var(--color-accent-contrast);
background: var(--color-coral);
color: var(--color-ink);
box-shadow: var(--shadow-sm);
}
.btn-source--secondary {
border: 1.5px solid var(--color-plum);
background: var(--color-surface);
color: var(--color-plum);
border: 1.5px solid var(--color-border-ui-strong);
background: transparent;
color: var(--color-text);
}
.btn-source:active {
@@ -531,34 +384,30 @@ textarea:focus {
}
.btn-source--primary:active {
background: linear-gradient(135deg, var(--color-accent), var(--color-accent-hover));
background: #ff7673;
}
.btn-source--secondary:active {
background: var(--color-plum-soft);
background: var(--color-surface);
}
/* États hover — uniquement pour les pointeurs qui en ont un (souris/trackpad
d'un prospect qui consulte la page sur desktop lors d'une démo) : jamais
sur mobile tactile, où seuls :active/:focus-visible s'appliquent déjà.
Lift discret + easing posé (pas de rebond ici, réservé aux pops). */
sur mobile tactile. */
@media (hover: hover) {
.btn-source--primary:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-md);
box-shadow: var(--shadow-soft);
}
.btn-source--secondary:hover {
transform: translateY(-1px);
background: var(--color-plum-soft);
background: var(--color-surface);
border-color: var(--color-coral);
}
}
/* Icônes SVG inline (aucune requête réseau) : remplacent les emoji des
boutons "Prendre une photo" / "Depuis la galerie". Le rendu des emoji
varie fortement selon l'OS (Android/iOS/Windows) — pas assez maîtrisé
pour une pièce de portfolio. Les icônes héritent de currentColor, donc
suivent l'état du bouton sans CSS supplémentaire. */
/* Icônes SVG inline (aucune requête réseau), héritent de currentColor. */
.btn-icon {
width: 20px;
height: 20px;
@@ -567,7 +416,7 @@ textarea:focus {
.file-chosen-label {
font-size: 0.85rem;
color: var(--color-muted);
color: var(--color-muted-2);
margin: 0.5rem 0 0;
text-align: center;
}
@@ -580,7 +429,7 @@ textarea {
input:focus-visible,
textarea:focus-visible,
button:focus-visible {
outline: 3px solid var(--color-plum);
outline: 3px solid var(--color-coral);
outline-offset: 2px;
}
@@ -588,9 +437,9 @@ button:focus-visible {
width: 100%;
max-height: 320px;
object-fit: contain;
border-radius: var(--radius);
border-radius: var(--radius-ui);
background: var(--color-surface-muted);
animation: pop-in 0.35s var(--ease-bounce) both;
animation: pop-in 0.35s var(--ease-spring) both;
}
.legal-notice {
@@ -600,7 +449,7 @@ button:focus-visible {
font-size: 0.78rem;
color: var(--color-muted);
background: var(--color-surface-muted);
border-radius: var(--radius);
border-radius: var(--radius-ui);
padding: 0.85rem 0.9rem;
margin: 0;
}
@@ -610,7 +459,7 @@ button:focus-visible {
height: 18px;
flex-shrink: 0;
margin-top: 0.1rem;
color: var(--color-plum);
color: var(--color-muted-2);
}
.legal-link {
@@ -625,7 +474,7 @@ button:focus-visible {
font: inherit;
font-size: 0.8rem;
font-weight: 600;
color: var(--color-muted);
color: var(--color-muted-2);
text-decoration: underline;
text-underline-offset: 2px;
cursor: pointer;
@@ -633,21 +482,21 @@ button:focus-visible {
}
.legal-link .legal-notice__icon {
color: var(--color-muted);
color: var(--color-muted-2);
}
@media (hover: hover) {
.legal-link:hover {
color: var(--color-plum);
color: var(--color-text);
}
.legal-link:hover .legal-notice__icon {
color: var(--color-plum);
color: var(--color-text);
}
#welcome-modal-dismiss:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-md);
box-shadow: var(--shadow-soft);
}
}
@@ -661,17 +510,13 @@ button:focus-visible {
align-items: flex-end;
justify-content: center;
padding: 0;
background: rgba(43, 32, 22, 0.45);
background: rgba(0, 0, 0, 0.6);
animation: fade-in 0.2s ease both;
}
/* Bug réel corrigé (2026-09-16) : une déclaration `display` en CSS auteur
passe toujours devant la règle `[hidden] { display: none }` du navigateur
(l'origine "auteur" l'emporte sur l'origine "user-agent" dans la
cascade, indépendamment de la spécificité). Sans cette règle explicite,
`welcomeModal.hidden = true` en JS ne masquait jamais visuellement la
modale : le bouton "C'est parti" fonctionnait bien (l'état se mettait à
jour) mais rien ne changeait à l'écran. */
/* Même bug que .upload-form (voir commentaire plus haut), présent depuis le
tout premier commit : sans cette règle, welcomeModal.hidden = true (JS)
ne masquait jamais visuellement la modale. */
.welcome-modal[hidden] {
display: none;
}
@@ -683,26 +528,25 @@ button:focus-visible {
max-height: 88vh;
overflow-y: auto;
background: var(--color-surface);
border-radius: var(--radius) var(--radius) 0 0;
border-radius: var(--radius-ui) var(--radius-ui) 0 0;
padding: 1.5rem 1.25rem calc(1.5rem + env(safe-area-inset-bottom));
box-shadow: var(--shadow-md);
animation: slide-up 0.35s var(--ease-bounce) both;
box-shadow: var(--shadow-soft);
animation: slide-up 0.35s var(--ease-spring) both;
display: flex;
flex-direction: column;
gap: 0.9rem;
}
/* Liseré dégradé en haut de la carte — même traitement que les cartes de la
galerie admin (src/admin/gallery/style.css), pour une petite touche de
couleur cohérente entre les deux écrans sur un élément de chrome (pas de
contenu), plutôt qu'un ajout ponctuel isolé. */
/* Liseré corail en haut de la carte — même traitement de "chrome" que la
galerie admin (src/admin/gallery/style.css), touche de couleur cohérente
entre écrans sur un élément de bordure, pas de contenu. */
.welcome-modal__card::before {
content: "";
display: block;
height: 4px;
flex-shrink: 0;
margin: -1.5rem -1.25rem 1.1rem;
background: linear-gradient(90deg, var(--color-accent), var(--color-gold));
background: var(--color-coral);
}
@media (min-width: 520px) {
@@ -710,7 +554,7 @@ button:focus-visible {
align-items: center;
}
.welcome-modal__card {
border-radius: var(--radius);
border-radius: var(--radius-ui);
}
}
@@ -718,46 +562,38 @@ button:focus-visible {
margin-bottom: 0;
}
/* Peaufinage passe 3 (la modale était fonctionnelle mais minimale) : la
marque prend la forme d'un badge rond coloré plutôt qu'une petite icône
nue — cohérent avec le ton "app premium" recherché, sans nouvel élément
HTML (juste du style appliqué au SVG existant). */
.welcome-modal .brand__mark {
width: 30px;
height: 30px;
padding: 7px;
border-radius: 50%;
background: var(--color-accent-soft);
color: var(--color-accent);
background: rgba(255, 94, 91, 0.16);
color: var(--color-coral);
}
.welcome-modal__title {
font-family: var(--font-display);
font-weight: 600;
font-weight: 700;
font-size: 1.6rem;
text-align: center;
margin: 0;
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;
}
.welcome-modal__text {
text-align: center;
margin: 0;
color: var(--color-text);
}
.welcome-modal__text {
text-align: center;
margin: 0;
color: var(--color-muted);
}
#welcome-modal-dismiss {
background: linear-gradient(135deg, var(--color-accent-light), var(--color-accent));
color: var(--color-accent-contrast);
background: var(--color-coral);
color: var(--color-ink);
box-shadow: var(--shadow-sm);
}
#welcome-modal-dismiss:active {
background: linear-gradient(135deg, var(--color-accent), var(--color-accent-hover));
background: #ff7673;
}
@keyframes fade-in {
@@ -774,7 +610,7 @@ button:focus-visible {
min-height: 1.2em;
font-size: 0.9rem;
font-weight: 600;
color: var(--color-plum);
color: var(--color-yellow);
margin: 0;
text-align: center;
}
@@ -787,7 +623,7 @@ button {
padding: 0.9rem 1rem;
min-height: 50px;
cursor: pointer;
transition: transform 0.12s var(--ease-bounce), background 0.12s ease, opacity 0.12s ease;
transition: transform 0.12s var(--ease-spring), background 0.12s ease, opacity 0.12s ease;
}
#submit-btn,
@@ -796,20 +632,20 @@ button {
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);
box-shadow: var(--shadow-md);
background: var(--color-coral);
color: var(--color-ink);
box-shadow: var(--shadow-soft);
font-size: 1rem;
}
#submit-btn:active:not(:disabled),
#add-another-btn:active {
transform: scale(0.97);
background: linear-gradient(135deg, var(--color-accent), var(--color-accent-hover));
background: #ff7673;
}
#submit-btn:disabled {
opacity: 0.6;
opacity: 0.5;
cursor: not-allowed;
box-shadow: none;
}
@@ -818,12 +654,10 @@ button {
#submit-btn:hover:not(:disabled),
#add-another-btn:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-md);
box-shadow: var(--shadow-soft);
}
/* Micro-nudge de l'icône avion au survol : détail qui n'existe que pour
les visiteurs desktop (donc surtout les prospects en démo), sans
dépendance JS supplémentaire. */
/* Micro-nudge de l'icône avion au survol : desktop uniquement. */
#submit-btn:hover:not(:disabled) .btn-icon {
transform: translate(2px, -2px);
}
@@ -834,14 +668,18 @@ button {
}
#retry-btn {
background: var(--color-danger);
color: var(--color-danger-contrast);
background: var(--color-coral);
color: var(--color-ink);
}
#retry-btn:active {
background: #ff7673;
}
/* ---------------------------------------------------------------------
É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).
confettis dans les 3 couleurs d'accent de la DA (pur CSS).
------------------------------------------------------------------ */
.success-check {
@@ -849,7 +687,7 @@ button {
width: 76px;
height: 76px;
margin: 0 auto 0.9rem;
animation: pop-in 0.55s var(--ease-bounce) both;
animation: pop-in 0.55s var(--ease-spring) both;
}
.success-check svg {
@@ -859,15 +697,15 @@ button {
}
.success-check__circle {
stroke: var(--color-accent);
fill: var(--color-gold-soft);
stroke: var(--color-mint);
fill: rgba(6, 214, 160, 0.14);
stroke-dasharray: 145;
stroke-dashoffset: 145;
animation: draw 0.5s ease-out 0.1s forwards;
}
.success-check__mark {
stroke: var(--color-accent);
stroke: var(--color-mint);
fill: none;
stroke-dasharray: 32;
stroke-dashoffset: 32;
@@ -912,51 +750,51 @@ button {
.confetti span:nth-child(1) {
left: 18%;
background: var(--color-accent);
background: var(--color-coral);
border-radius: 2px;
animation-delay: 0.05s;
}
.confetti span:nth-child(2) {
left: 32%;
top: 0.2rem;
background: var(--color-gold);
background: var(--color-yellow);
border-radius: 50%;
animation-delay: 0.15s;
}
.confetti span:nth-child(3) {
left: 48%;
background: var(--color-plum);
background: var(--color-mint);
border-radius: 2px;
animation-delay: 0.02s;
}
.confetti span:nth-child(4) {
left: 62%;
background: var(--color-accent-light);
background: var(--color-coral);
border-radius: 50%;
animation-delay: 0.22s;
}
.confetti span:nth-child(5) {
left: 74%;
background: var(--color-gold);
background: var(--color-yellow);
border-radius: 2px;
animation-delay: 0.1s;
}
.confetti span:nth-child(6) {
left: 86%;
background: var(--color-plum);
background: var(--color-mint);
border-radius: 50%;
animation-delay: 0.28s;
}
.confetti span:nth-child(7) {
left: 10%;
top: 0.3rem;
background: var(--color-gold);
background: var(--color-yellow);
border-radius: 50%;
animation-delay: 0.18s;
}
.confetti span:nth-child(8) {
left: 94%;
background: var(--color-accent);
background: var(--color-coral);
border-radius: 2px;
animation-delay: 0.08s;
}
@@ -977,15 +815,22 @@ button {
.success-title {
font-family: var(--font-display);
font-weight: 700;
font-size: 1.75rem;
font-weight: 600;
color: var(--color-accent-hover);
color: var(--color-text);
margin: 0 0 0.3rem;
}
/* Touche manuscrite ponctuelle (Caveat) sur le petit mot de succès — ton
"friendly" demandé, sans en abuser (seul élément du texte à l'utiliser
sur cet écran, comme sur les légendes de la galerie invité). */
.success-text {
font-size: 0.95rem;
margin: 0 0 1.3rem;
font-family: var(--font-hand);
font-weight: 600;
font-size: 1.35rem;
line-height: 1.3;
color: var(--color-muted);
margin: 0 0 1.4rem;
}
@media (prefers-reduced-motion: reduce) {