É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. 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" media="(prefers-color-scheme: light)"> <!-- Thème unique sombre "Polaroid Néon", exception admin (sobre/data-first,
<meta name="theme-color" content="#1b120b" media="(prefers-color-scheme: dark)"> voir src/admin/gallery/style.css). -->
<link rel="preload" href="/vendor/fonts/fraunces-latin-600-normal.woff2" as="font" type="font/woff2" crossorigin> <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"> <link rel="stylesheet" href="/admin/gallery/style.css">
</head> </head>
<body> <body>
+105 -157
View File
@@ -1,100 +1,70 @@
/* Page galerie admin — mobile-first mais utilisable au clavier/souris sur /* Galerie admin — direction artistique "Polaroid Néon"
desktop (l'admin peut consulter depuis un ordinateur), sans dépendance (.claude/skills/photobooth-da/SKILL.md), variante "écran admin" du skill :
externe. Mêmes tokens de couleur/rayon/ombre/police que la page invité sobre, sombre, data-first. Pas de confettis, pas de rotation façon
(src/e/style.css) pour rester cohérent visuellement — c'est aussi l'écran polaroid sur les cartes photo (grille classique) — l'accent corail est
que verront les prospects (photographes, wedding planners) lors d'une réservé aux CTA (boutons), jamais décoratif.
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.
Passe 3 (2026-09-16) : mode sombre + finitions "type iPhone" (hover Tokens repris de src/galerie/style.css (écran de référence de cette DA)
desktop, entrées en fondu, halo de couleur en fond) alignés sur pour la cohérence de couleurs/typo entre écrans invité et admin ; les
src/e/style.css — voir ce fichier pour le détail complet des choix et cartes utilisent #1E1B18 comme demandé par l'exception admin du skill
des vérifications de contraste. Cet écran est celui que verront le plus (plus clair que --color-bg, pour distinguer une "carte" du fond de
souvent les prospects en démo depuis un ordinateur : les états hover page, sans jamais recourir au blanc cassé des polaroids invité).
comptent ici au moins autant que sur la page 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-face {
font-family: "Fraunces"; font-family: "General Sans";
src: url("/vendor/fonts/fraunces-latin-600-normal.woff2") format("woff2"); src: url("/vendor/fonts/generalsans-700.woff2") format("woff2");
font-weight: 600; 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-style: normal;
font-display: swap; 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 dark; color-scheme: dark;
--color-bg: #fff8f0; --color-bg: #12100e;
--color-surface: #ffffff; --color-ink: #12100e;
--color-surface-muted: #fdecdd; --color-text: #faf6f0;
--color-text: #2b2016; --color-muted: #e8dfd3;
--color-muted: #8a7869; --color-muted-2: #a89e90;
--color-border: #f1ddc6;
--color-accent: #e35f2b; --color-coral: #ff5e5b;
--color-accent-hover: #c74c1e; --color-yellow: #ffd166;
--color-accent-light: #ff8a52; --color-mint: #06d6a0;
--color-accent-soft: #ffe3d0;
--color-accent-contrast: #fffaf6;
--color-plum: #7c2f56; /* Exception admin du skill : cartes en #1E1B18. */
--color-plum-soft: #f8e3ee; --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-bg: rgba(255, 94, 91, 0.14);
--color-error-text: #a3341c; --color-warning-bg: rgba(255, 209, 102, 0.16);
--color-warning-bg: #fbe9d8;
--color-warning-text: #8a4a10;
--radius-sm: 12px; --radius-ui: 14px;
--radius: 20px;
--radius-pill: 999px; --radius-pill: 999px;
--shadow-sm: 0 2px 6px rgba(163, 78, 30, 0.1); --shadow-soft: 0 10px 24px rgba(0, 0, 0, 0.35);
--shadow-md: 0 10px 28px rgba(163, 78, 30, 0.16); --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); --ease-standard: cubic-bezier(0.22, 1, 0.36, 1);
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, --font-display: "General Sans", -apple-system, BlinkMacSystemFont, sans-serif;
"Helvetica Neue", Arial, sans-serif; --font-ui: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", 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;
}
} }
* { * {
@@ -106,28 +76,13 @@ html {
} }
body { body {
position: relative;
margin: 0; margin: 0;
font-family: var(--font-sans); font-family: var(--font-ui);
background: var(--color-bg); background: var(--color-bg);
color: var(--color-text); color: var(--color-text);
line-height: 1.45; 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 { .page {
max-width: 960px; max-width: 960px;
margin: 0 auto; margin: 0 auto;
@@ -138,18 +93,6 @@ body::before {
.page__header { .page__header {
text-align: center; text-align: center;
margin-bottom: 1.5rem; 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 { .brand {
@@ -158,14 +101,14 @@ body::before {
justify-content: center; justify-content: center;
gap: 0.4rem; gap: 0.4rem;
margin-bottom: 0.6rem; margin-bottom: 0.6rem;
color: var(--color-muted); color: var(--color-muted-2);
} }
.brand__mark { .brand__mark {
width: 16px; width: 16px;
height: 16px; height: 16px;
flex-shrink: 0; flex-shrink: 0;
color: var(--color-accent); color: var(--color-coral);
} }
.brand__name { .brand__name {
@@ -175,21 +118,19 @@ body::before {
text-transform: uppercase; 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 { .page__header h1 {
font-family: var(--font-display); font-family: var(--font-display);
font-size: 1.7rem; font-size: 1.5rem;
font-weight: 600; font-weight: 700;
letter-spacing: -0.01em; letter-spacing: -0.01em;
margin: 0.15rem 0 0; margin: 0.15rem 0 0;
color: var(--color-accent); color: var(--color-text);
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 {
color: var(--color-muted); color: var(--color-muted-2);
font-size: 0.9rem; font-size: 0.9rem;
margin: 0.4rem 0 0; margin: 0.4rem 0 0;
} }
@@ -197,13 +138,14 @@ body::before {
.state-block { .state-block {
text-align: center; text-align: center;
padding: 1.25rem 1rem; padding: 1.25rem 1rem;
border-radius: var(--radius); border-radius: var(--radius-ui);
margin-bottom: 1rem; margin-bottom: 1rem;
background: var(--color-card);
} }
.state-block--error { .state-block--error {
background: var(--color-error-bg); background: var(--color-error-bg);
color: var(--color-error-text); color: var(--color-text);
} }
.state-block p { .state-block p {
@@ -219,8 +161,8 @@ body::before {
width: 26px; width: 26px;
height: 26px; height: 26px;
margin-bottom: 0.6rem; margin-bottom: 0.6rem;
border: 3px solid var(--color-accent-soft); border: 3px solid rgba(255, 94, 91, 0.22);
border-top-color: var(--color-accent); border-top-color: var(--color-coral);
border-radius: 50%; border-radius: 50%;
animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite;
} }
@@ -233,8 +175,8 @@ body::before {
.status-banner { .status-banner {
background: var(--color-warning-bg); background: var(--color-warning-bg);
color: var(--color-warning-text); color: var(--color-text);
border-radius: var(--radius); border-radius: var(--radius-ui);
padding: 0.85rem 1rem; padding: 0.85rem 1rem;
margin: 0 0 1.25rem; margin: 0 0 1.25rem;
font-size: 0.9rem; font-size: 0.9rem;
@@ -247,6 +189,8 @@ body::before {
margin-bottom: 0.75rem; 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 { .btn {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -259,10 +203,10 @@ body::before {
padding: 0.75rem 1.15rem; padding: 0.75rem 1.15rem;
min-height: 46px; min-height: 46px;
cursor: pointer; cursor: pointer;
background: linear-gradient(135deg, var(--color-accent-light), var(--color-accent)); background: var(--color-coral);
color: var(--color-accent-contrast); color: var(--color-ink);
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-card);
transition: transform 0.12s var(--ease-bounce), background 0.12s ease; transition: transform 0.12s var(--ease-standard), background 0.12s ease, box-shadow 0.12s ease;
} }
.btn-icon { .btn-icon {
@@ -272,52 +216,53 @@ body::before {
} }
.btn--secondary { .btn--secondary {
background: var(--color-surface); background: var(--color-card);
color: var(--color-plum); color: var(--color-text);
border: 1.5px solid var(--color-plum); border: 1.5px solid var(--color-border-ui-strong);
box-shadow: none; box-shadow: none;
} }
.btn:disabled { .btn:disabled {
opacity: 0.55; opacity: 0.5;
cursor: not-allowed; cursor: not-allowed;
} }
.btn:active:not(:disabled) { .btn:active:not(:disabled) {
background: linear-gradient(135deg, var(--color-accent), var(--color-accent-hover)); background: #ff7673;
transform: scale(0.97); transform: scale(0.97);
} }
.btn--secondary:active:not(:disabled) { .btn--secondary:active:not(:disabled) {
background: var(--color-plum-soft); background: var(--color-card-muted);
} }
@media (hover: hover) { @media (hover: hover) {
.btn:hover:not(:disabled) { .btn:hover:not(:disabled) {
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: var(--shadow-md); box-shadow: var(--shadow-soft);
} }
.btn--secondary:hover:not(:disabled) { .btn--secondary:hover:not(:disabled) {
background: var(--color-plum-soft); background: var(--color-card-muted);
border-color: var(--color-coral);
box-shadow: none; box-shadow: none;
} }
} }
.btn:focus-visible, .btn:focus-visible,
a:focus-visible { a:focus-visible {
outline: 3px solid var(--color-plum); outline: 3px solid var(--color-coral);
outline-offset: 2px; outline-offset: 2px;
} }
.zip-status { .zip-status {
font-size: 0.85rem; font-size: 0.85rem;
color: var(--color-muted); color: var(--color-muted-2);
margin: 0 0 1.25rem; margin: 0 0 1.25rem;
} }
.zip-status--error { .zip-status--error {
color: var(--color-error-text); color: var(--color-coral);
} }
.photo-grid { .photo-grid {
@@ -329,26 +274,29 @@ a:focus-visible {
gap: 1rem; 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 { .photo-card {
position: relative; position: relative;
background: var(--color-surface); background: var(--color-card);
border-radius: var(--radius); border-radius: var(--radius-ui);
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; 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); 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 /* Liseré corail en haut de carte au survol desktop uniquement : seule
— encore de la "couleur" réutilisant la palette existante, appliquée à touche décorative de couleur tolérée ici, et encore, discrète et
un endroit où elle valorise le contenu (la photo) sans le concurrencer. */ réactive au hover plutôt que permanente. */
.photo-card::before { .photo-card::before {
content: ""; content: "";
position: absolute; position: absolute;
inset: 0 0 auto 0; inset: 0 0 auto 0;
height: 3px; height: 3px;
background: linear-gradient(90deg, var(--color-accent), var(--color-plum)); background: var(--color-coral);
opacity: 0; opacity: 0;
transition: opacity 0.2s var(--ease-standard); transition: opacity 0.2s var(--ease-standard);
z-index: 1; z-index: 1;
@@ -356,8 +304,8 @@ a:focus-visible {
@media (hover: hover) { @media (hover: hover) {
.photo-card:hover { .photo-card:hover {
box-shadow: var(--shadow-md); box-shadow: var(--shadow-soft);
transform: translateY(-3px) rotate(-0.6deg); transform: translateY(-3px);
} }
.photo-card:hover::before { .photo-card:hover::before {
@@ -369,7 +317,7 @@ a:focus-visible {
position: relative; position: relative;
width: 100%; width: 100%;
aspect-ratio: 1 / 1; aspect-ratio: 1 / 1;
background: var(--color-surface-muted); background: var(--color-card-muted);
} }
.photo-card__thumb { .photo-card__thumb {
@@ -387,7 +335,7 @@ a:focus-visible {
justify-content: center; justify-content: center;
text-align: center; text-align: center;
font-size: 0.75rem; font-size: 0.75rem;
color: var(--color-muted); color: var(--color-muted-2);
padding: 0.5rem; padding: 0.5rem;
} }
@@ -409,17 +357,18 @@ a:focus-visible {
font-size: 0.85rem; font-size: 0.85rem;
margin: 0 0 0.2rem; margin: 0 0 0.2rem;
word-break: break-word; word-break: break-word;
color: var(--color-text);
} }
.photo-card__message { .photo-card__message {
color: var(--color-text); color: var(--color-muted);
font-size: 0.8rem; font-size: 0.8rem;
margin: 0 0 0.35rem; margin: 0 0 0.35rem;
word-break: break-word; word-break: break-word;
} }
.photo-card__date { .photo-card__date {
color: var(--color-muted); color: var(--color-muted-2);
font-size: 0.72rem; font-size: 0.72rem;
margin: 0; margin: 0;
} }
@@ -428,23 +377,22 @@ a:focus-visible {
display: block; display: block;
text-align: center; text-align: center;
padding: 0.6rem; padding: 0.6rem;
background: var(--color-accent); background: var(--color-coral);
color: var(--color-accent-contrast); color: var(--color-ink);
text-decoration: none; text-decoration: none;
font-size: 0.82rem; font-size: 0.82rem;
font-weight: 600; font-weight: 700;
} }
.photo-card__download:hover { .photo-card__download:hover {
background: var(--color-accent-hover); background: #ff7673;
} }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.spinner, .spinner,
.photo-card, .photo-card,
.photo-card::before, .photo-card::before,
.btn, .btn {
.page__header {
animation: none !important; animation: none !important;
transition: none !important; transition: none !important;
} }
+67 -45
View File
@@ -17,51 +17,62 @@
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 /* Direction artistique "Polaroid Néon" (.claude/skills/photobooth-da/SKILL.md).
— voir src/e/style.css pour la justification complète de la direction Compromis assumé pour cet écran (signalé à Kévin, pas tranché seul) :
créative (punchy/friendly plutôt que sobre). Le carton imprimé est un le carton reste un support IMPRIMÉ (papier, posé sur les tables), donc
livrable physique posé sur les tables : mêmes contraintes qu'ailleurs il garde un fond clair — un fond #12100E n'a pas de sens sur un support
côté fond (pas de dépendance CDN), mais on évite ici le texte en physique et gâcherait de l'encre en impression maison. En revanche il
dégradé (background-clip:text) sur les éléments imprimés — beaucoup adopte les polices (General Sans pour le titre, Caveat pour
d'utilisateurs impriment sans l'option "Imprimer les arrière-plans", l'accroche manuscrite — remplace Fraunces) et l'accent corail de la
ce qui ferait disparaître un titre en dégradé. La personnalité vient nouvelle DA pour rester cohérent avec le reste de la marque. Comme
donc de la police Fraunces + d'une couleur pleine, pas d'un effet qui avant : pas de dégradé (background-clip:text) sur les éléments
dépend d'un réglage d'impression. */ 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-face {
font-family: "Fraunces"; font-family: "General Sans";
src: url("/vendor/fonts/fraunces-latin-600-normal.woff2") format("woff2"); src: url("/vendor/fonts/generalsans-700.woff2") format("woff2");
font-weight: 600; font-weight: 700;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
} }
@font-face { @font-face {
font-family: "Fraunces"; font-family: "Caveat";
src: url("/vendor/fonts/fraunces-latin-500-italic.woff2") format("woff2"); src: url("/vendor/fonts/caveat-latin-variable.woff2") format("woff2");
font-weight: 500; font-weight: 400 700;
font-style: italic; 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; font-display: swap;
} }
:root { :root {
color-scheme: light; color-scheme: light;
--color-bg: #fff8f0; --color-bg: #faf6f0;
--color-surface: #ffffff; --color-surface: #ffffff;
--color-surface-muted: #fdecdd; --color-surface-muted: #f3ece1;
--color-text: #2b2016; --color-text: #12100e;
--color-muted: #8a7869; --color-ink: #12100e;
--color-border: #f1ddc6; --color-muted: #5c5348;
--color-accent: #e35f2b; --color-border: #e8dfd3;
--color-accent-hover: #c74c1e; --color-coral: #ff5e5b;
--color-accent-light: #ff8a52; --color-coral-hover: #e44542;
--color-accent-contrast: #fffaf6;
--color-plum: #7c2f56;
--radius-sm: 12px; --radius-sm: 12px;
--radius: 20px; --radius: 20px;
--radius-pill: 999px; --radius-pill: 999px;
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1); --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif; "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); color: var(--color-text);
} }
h1 {
font-family: var(--font-display);
}
.workspace { .workspace {
display: grid; display: grid;
grid-template-columns: 300px 1fr; grid-template-columns: 300px 1fr;
@@ -141,7 +156,7 @@
input:focus-visible, input:focus-visible,
select:focus-visible, select:focus-visible,
button:focus-visible { button:focus-visible {
outline: 3px solid var(--color-accent); outline: 3px solid var(--color-coral);
outline-offset: 1px; outline-offset: 1px;
} }
@@ -162,10 +177,10 @@
padding: 0.8rem 1rem; padding: 0.8rem 1rem;
min-height: 46px; min-height: 46px;
cursor: pointer; cursor: pointer;
background: linear-gradient(135deg, var(--color-accent-light), var(--color-accent)); background: var(--color-coral);
color: var(--color-accent-contrast); color: var(--color-ink);
box-shadow: 0 6px 16px rgba(195, 76, 30, 0.22); box-shadow: 0 6px 16px rgba(255, 94, 91, 0.28);
transition: transform 0.12s var(--ease-bounce), background 0.12s ease; transition: transform 0.12s var(--ease-spring), background 0.12s ease;
} }
#printBtn:disabled { #printBtn:disabled {
@@ -175,7 +190,7 @@
} }
#printBtn:active:not(:disabled) { #printBtn:active:not(:disabled) {
background: linear-gradient(135deg, var(--color-accent), var(--color-accent-hover)); background: var(--color-coral-hover);
transform: scale(0.98); transform: scale(0.98);
} }
@@ -192,7 +207,7 @@
width: 148mm; width: 148mm;
height: 210mm; height: 210mm;
background: var(--color-surface); background: var(--color-surface);
border: 1.5mm solid var(--color-accent); border: 1.5mm solid var(--color-coral);
border-radius: 6mm; 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;
@@ -212,7 +227,7 @@
.card__mark { .card__mark {
width: 5mm; width: 5mm;
height: 5mm; height: 5mm;
color: var(--color-accent); color: var(--color-coral);
} }
.card__brand-name { .card__brand-name {
@@ -222,23 +237,30 @@
text-transform: uppercase; 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 { .card__title {
font-family: var(--font-display); font-family: var(--font-display);
font-size: 9mm; font-size: 9mm;
font-weight: 600; font-weight: 700;
letter-spacing: -0.01em; letter-spacing: -0.01em;
color: var(--color-accent-hover); color: var(--color-coral-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;
} }
/* 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 { .card__prompt {
font-family: var(--font-display); font-family: var(--font-hand);
font-style: italic; font-weight: 600;
font-weight: 500; font-size: 6.5mm;
font-size: 4.6mm; color: var(--color-ink);
color: var(--color-plum);
margin: 0 0 8mm; margin: 0 0 8mm;
} }
+50 -60
View File
@@ -5,57 +5,43 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Générateur de QR code — photobooth-qr (admin)</title> <title>Générateur de QR code — photobooth-qr (admin)</title>
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#faf6f0" media="(prefers-color-scheme: light)" /> <meta name="theme-color" content="#12100E" />
<meta name="theme-color" content="#1b120b" media="(prefers-color-scheme: dark)" />
<style> <style>
/* Mêmes tokens que src/e/style.css et src/admin/gallery/style.css — /* Direction artistique "Polaroid Néon" (.claude/skills/photobooth-da/SKILL.md),
cohérence visuelle même sur cet outil interne que les clients ne exception admin : sobre, sombre, data-first. Mêmes tokens que
voient pas (cf. retour Kévin 2026-09-16). Palette/rayons resynchronisés src/admin/gallery/style.css pour la cohérence visuelle entre écrans
avec la passe 2 (2026-09-16, direction "punchy/friendly") ; outil admin, même sur cet outil interne que les clients ne voient pas (cf.
interne, priorité basse donc pas de police d'accent ni d'animations retour Kévin 2026-09-16, toujours valable avec la nouvelle DA). Outil
d'entrée ajoutées ici, juste la cohérence de base (couleurs, rayons). 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
Passe 3 (2026-09-16) : mode sombre ajouté (demandé explicitement pour (couleurs, rayons, police). */
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. */
:root { :root {
color-scheme: light dark; color-scheme: dark;
--color-bg: #fff8f0; --color-bg: #12100e;
--color-surface: #ffffff; --color-ink: #12100e;
--color-surface-muted: #fdecdd; --color-text: #faf6f0;
--color-text: #2b2016; --color-muted: #e8dfd3;
--color-muted: #8a7869; --color-muted-2: #a89e90;
--color-border: #f1ddc6; --color-coral: #ff5e5b;
--color-accent: #e35f2b; --color-card: #1e1b18;
--color-accent-hover: #c74c1e; --color-card-muted: #262220;
--color-accent-soft: #ffe3d0; --color-border-ui: rgba(250, 246, 240, 0.16);
--color-accent-contrast: #fffaf6; --radius-sm: 10px;
--color-error-text: #a3341c; --radius: 14px;
--radius-sm: 12px; --shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.3);
--radius: 20px;
--shadow-sm: 0 2px 6px rgba(163, 78, 30, 0.1);
--ease-standard: cubic-bezier(0.22, 1, 0.36, 1); --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; "Helvetica Neue", Arial, sans-serif;
} }
@media (prefers-color-scheme: dark) { @font-face {
:root { font-family: "Inter";
--color-bg: #1b120b; src: url("/vendor/fonts/inter-latin-variable.woff2") format("woff2");
--color-surface: #26190f; font-weight: 400 700;
--color-surface-muted: #32211a; font-style: normal;
--color-text: #f7ead9; font-display: swap;
--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);
}
} }
* { box-sizing: border-box; } * { box-sizing: border-box; }
body { body {
margin: 0; margin: 0;
@@ -79,7 +65,7 @@
width: 16px; width: 16px;
height: 16px; height: 16px;
flex-shrink: 0; flex-shrink: 0;
color: var(--color-accent); color: var(--color-coral);
} }
.brand span { .brand span {
font-size: 0.7rem; font-size: 0.7rem;
@@ -111,24 +97,24 @@
padding: 0.7rem 0.75rem; padding: 0.7rem 0.75rem;
font: inherit; font: inherit;
font-size: 1rem; font-size: 1rem;
border: 1px solid var(--color-border); border: 1px solid var(--color-border-ui);
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
background: var(--color-surface); background: var(--color-card);
color: inherit; color: inherit;
transition: border-color 0.15s var(--ease-standard), box-shadow 0.15s var(--ease-standard); transition: border-color 0.15s var(--ease-standard), box-shadow 0.15s var(--ease-standard);
} }
input:focus, input:focus,
select:focus { select:focus {
outline: none; outline: none;
border-color: var(--color-accent); border-color: var(--color-coral);
box-shadow: 0 0 0 4px var(--color-accent-soft); box-shadow: 0 0 0 4px rgba(255, 94, 91, 0.22);
} }
.preview { .preview {
margin-top: 1.5rem; margin-top: 1.5rem;
padding: 1.1rem; padding: 1.1rem;
border: 1px solid var(--color-border); border: 1px solid var(--color-border-ui);
border-radius: var(--radius); border-radius: var(--radius);
background: var(--color-surface); background: var(--color-card);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@@ -160,40 +146,44 @@
button { button {
flex: 1; flex: 1;
font: inherit; font: inherit;
font-weight: 600; font-weight: 700;
padding: 0.85rem 0.7rem; padding: 0.85rem 0.7rem;
min-height: 48px; min-height: 48px;
font-size: 0.95rem; font-size: 0.95rem;
border: 1px solid var(--color-accent); border: 1px solid var(--color-coral);
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
background: var(--color-accent); background: var(--color-coral);
color: var(--color-accent-contrast); color: var(--color-ink);
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
cursor: pointer; cursor: pointer;
transition: transform 0.15s var(--ease-standard), background 0.15s ease; transition: transform 0.15s var(--ease-standard), background 0.15s ease;
} }
button:active:not(:disabled) { button:active:not(:disabled) {
background: var(--color-accent-hover); background: #ff7673;
} }
button.secondary { button.secondary {
background: var(--color-surface); background: var(--color-card);
color: var(--color-accent); color: var(--color-text);
border-color: var(--color-border-ui);
box-shadow: none; box-shadow: none;
} }
button.secondary:active:not(:disabled) { button.secondary:active:not(:disabled) {
background: var(--color-accent-soft); background: var(--color-card-muted);
} }
@media (hover: hover) { @media (hover: hover) {
button:hover:not(:disabled) { button:hover:not(:disabled) {
transform: translateY(-1px); transform: translateY(-1px);
} }
button.secondary:hover:not(:disabled) {
border-color: var(--color-coral);
}
} }
button:disabled { button:disabled {
opacity: 0.5; opacity: 0.5;
cursor: not-allowed; cursor: not-allowed;
} }
.error { .error {
color: var(--color-error-text); color: var(--color-coral);
font-size: 0.85rem; font-size: 0.85rem;
margin-top: 0.5rem; margin-top: 0.5rem;
min-height: 1.2em; min-height: 1.2em;
+5 -6
View File
@@ -12,12 +12,11 @@
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">
<!-- Deux meta theme-color (au lieu d'une seule) : teinte la barre <!-- Thème unique sombre "Polaroid Néon" (voir src/e/style.css) : une seule
navigateur/PWA selon le thème système, en cohérence avec le mode sombre meta theme-color, comme src/galerie/index.html. -->
ajouté passe 3 (voir src/e/style.css). --> <meta name="theme-color" content="#12100E">
<meta name="theme-color" content="#faf6f0" media="(prefers-color-scheme: light)"> <link rel="preload" href="/vendor/fonts/generalsans-700.woff2" as="font" type="font/woff2" crossorigin>
<meta name="theme-color" content="#1b120b" media="(prefers-color-scheme: dark)"> <link rel="preload" href="/vendor/fonts/caveat-latin-variable.woff2" as="font" type="font/woff2" crossorigin>
<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>
+201 -356
View File
@@ -1,200 +1,101 @@
/* Page upload invité — mobile-first, sans dépendance externe. /* Page upload invité — direction artistique "Polaroid Néon"
Système de design partagé avec la galerie admin (src/admin/gallery/style.css) : (.claude/skills/photobooth-da/SKILL.md). Remplace les 3 passes DA
mêmes tokens de couleur/rayon/ombre, pour une identité cohérente entre les précédentes (palette corail/prune/doré + Fraunces, voir historique
deux écrans (voir docs/decisions.md pour le contexte produit). 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 Choix pris pendant cette passe, à signaler à Kévin :
le détail complet, résumé ici : - Thème unique sombre (color-scheme: dark), comme src/galerie/style.css,
Retour direct de Kévin sur la 1ère passe : trop "démo sage", pas assez plutôt que le double thème clair/sombre (prefers-color-scheme) de
"envie de participer". Consigne resserrée en cours de route : viser l'ancienne DA. Le skill ne demande nulle part de garder un thème
PUNCHY / FRIENDLY / FUN, pas un premium feutré silencieux. Traduction : clair, et le premier écran validé n'en a pas — cohérence retenue
- Palette plus affirmée : on garde la famille chaude (cohérente avec plutôt qu'une redondance non demandée. À confirmer si un thème clair
l'identité perso de Kévin / Rêves Liquides) mais on la sature — un est en fait souhaité pour cet écran précis.
corail vif en primaire plutôt que le brun terracotta sourd de la V1, - Titre et éléments de "chrome" en texte plein (pas de dégradé
avec un prune et un doré en accents secondaires (confettis, badges). corail→doré comme l'ancienne DA) : le skill met en garde contre le
- Formes plus rondes/organiques (rayons agrandis, boutons pilule). "glow/neon CSS artificiel" et l'écran de référence (galerie) n'utilise
- Micro-animations qui ont du peps : coche qui "pop" avec rebond, aucun dégradé de texte ni halo radial en fond — même logique
confettis colorés au succès, spinner de chargement, easing "overshoot" appliquée ici pour rester cohérent plutôt que de réintroduire cet
sur les interactions plutôt que du linear plat. effet.
- 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).
Passe 3, 2026-09-16 — retour Kévin : "top ! rajoute des animations, de la Contrastes vérifiés par calcul WCAG AA (même méthode que
couleur [...] brainstorm sur le choix des couleurs et de la typo, ajoute src/galerie/style.css) : texte clair (#FAF6F0/#E8DFD3) sur fond sombre
un mode sombre [...] fait moi quelque chose de vendeur et de friendly (#12100E) est très largement au-dessus du seuil ; texte sombre (#12100E)
(type iPhone)." Palette/typo déjà validées passe 2 : le brainstorm de sur les 3 couleurs d'accent (corail/jaune/vert) passe confortablement
cette passe (voir rapport de passe pour le détail des 2 directions (6.3:1 à 13.2:1) — utilisé systématiquement pour le texte des CTA en
alternatives écartées) a confirmé la famille corail/prune/doré + Fraunces couleur pleine. Les fonds teintés à faible opacité (états erreur/limite/
plutôt que d'ajouter de nouvelles teintes ou une police supplémentaire — succès) restent très proches du noir une fois mélangés à #12100E ; le
"plus de couleur" est obtenu en réutilisant plus largement cette même texte de ces blocs reste donc en couleur claire standard (--color-text/
palette (halo radial en fond, focus ring teinté, confettis étoffés) --color-muted), jamais en couleur d'accent pleine, par prudence. */
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. */
@font-face { @font-face {
font-family: "Fraunces"; font-family: "General Sans";
src: url("/vendor/fonts/fraunces-latin-600-normal.woff2") format("woff2"); src: url("/vendor/fonts/generalsans-700.woff2") format("woff2");
font-weight: 600; font-weight: 700;
font-style: normal; font-style: normal;
font-display: swap; 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-face {
font-family: "Fraunces"; font-family: "Caveat";
src: url("/vendor/fonts/fraunces-latin-500-italic.woff2") format("woff2"); src: url("/vendor/fonts/caveat-latin-variable.woff2") format("woff2");
font-weight: 500; font-weight: 400 700;
font-style: italic; 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; 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 {
/* "light dark" (et non "light" seul) : indique au navigateur que les deux color-scheme: dark;
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;
/* Couleurs — famille chaude toujours ancrée dans l'identité de Kévin, /* Mêmes noms/valeurs que src/galerie/style.css pour la cohérence entre
mais poussée en saturation pour un rendu festif plutôt que sobre. */ écrans (voir consigne de la passe). */
--color-bg: #fff8f0; --color-bg: #12100e;
--color-surface: #ffffff; --color-ink: #12100e; /* texte sombre sur surfaces claires/accents */
--color-surface-muted: #fdecdd; --color-text: #faf6f0;
--color-text: #2b2016; --color-muted: #e8dfd3;
--color-muted: #8a7869; --color-muted-2: #a89e90;
--color-border: #f1ddc6;
--color-accent: #e35f2b; --color-coral: #ff5e5b;
--color-accent-hover: #c74c1e; --color-yellow: #ffd166;
--color-accent-light: #ff8a52; --color-mint: #06d6a0;
--color-accent-soft: #ffe3d0;
--color-accent-contrast: #fffaf6;
--color-plum: #7c2f56; --color-border: #e8dfd3;
--color-plum-soft: #f8e3ee;
--color-gold: #f0a91e; --radius: 4px; /* réservé aux éléments façon polaroid, non utilisé ici */
--color-gold-soft: #fef0d3; --radius-ui: 14px;
--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-pill: 999px; --radius-pill: 999px;
--shadow-sm: 0 2px 6px rgba(163, 78, 30, 0.1); --shadow-soft: 0 10px 24px rgba(0, 0, 0, 0.35);
--shadow-md: 0 10px 28px rgba(163, 78, 30, 0.16); --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);
/* 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-standard: cubic-bezier(0.22, 1, 0.36, 1); --ease-standard: cubic-bezier(0.22, 1, 0.36, 1);
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, --font-display: "General Sans", -apple-system, BlinkMacSystemFont, sans-serif;
"Helvetica Neue", Arial, sans-serif; --font-hand: "Caveat", "Segoe Script", cursive;
--font-display: "Fraunces", Georgia, "Times New Roman", serif; --font-ui: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
/* ----------------------------------------------------------------------- /* Tokens spécifiques à cet écran (formulaire, états), pas repris de
Thème sombre — palette pensée pour l'obscurité (pas une inversion des src/galerie/style.css qui n'en a pas besoin. */
couleurs claires) : fond brun quasi-noir pour rester dans la famille --color-surface: #1e1b18;
chaude de la marque plutôt qu'un noir bleuté générique, texte et accents --color-surface-muted: #241f1a;
reclaircis et vérifiés au ratio de contraste WCAG AA (>= 4.5:1 pour le --color-border-ui: rgba(250, 246, 240, 0.16);
texte courant, >= 3:1 pour le grand texte/les éléments d'UI ; calculs --color-border-ui-strong: rgba(250, 246, 240, 0.28);
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;
--color-accent: #ff7a42; --color-error-bg: rgba(255, 94, 91, 0.14);
--color-accent-hover: #ffa06e; --color-limit-bg: rgba(255, 209, 102, 0.16);
--color-accent-light: #ffab7a; --color-success-bg: rgba(6, 214, 160, 0.12);
--color-accent-soft: rgba(255, 122, 66, 0.18);
--color-accent-contrast: #241206;
--color-plum: #f0b8d8; --shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.3);
--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;
}
} }
* { * {
@@ -206,32 +107,13 @@ html {
} }
body { body {
position: relative;
margin: 0; margin: 0;
font-family: var(--font-sans); font-family: var(--font-ui);
background: var(--color-bg); background: var(--color-bg);
color: var(--color-text); color: var(--color-text);
line-height: 1.45; 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 { .page {
max-width: 480px; max-width: 480px;
margin: 0 auto; margin: 0 auto;
@@ -242,10 +124,6 @@ body::before {
.page__header { .page__header {
text-align: center; text-align: center;
margin-bottom: 1.6rem; 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; 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 { .brand {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 0.4rem; gap: 0.4rem;
margin-bottom: 0.7rem; margin-bottom: 0.7rem;
color: var(--color-muted); color: var(--color-muted-2);
} }
.brand__mark { .brand__mark {
width: 16px; width: 16px;
height: 16px; height: 16px;
flex-shrink: 0; flex-shrink: 0;
color: var(--color-accent); color: var(--color-coral);
} }
.brand__name { .brand__name {
@@ -285,39 +159,28 @@ body::before {
font-weight: 700; font-weight: 700;
letter-spacing: 0.13em; letter-spacing: 0.13em;
text-transform: uppercase; text-transform: uppercase;
font-family: var(--font-ui);
} }
.page__header h1 { .page__header h1 {
font-family: var(--font-display); font-family: var(--font-display);
font-size: 2rem; font-weight: 700;
font-weight: 600; font-size: 1.9rem;
letter-spacing: -0.01em; letter-spacing: -0.01em;
line-height: 1.15; line-height: 1.15;
margin: 0; 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 { .page__kicker {
font-family: var(--font-display); color: var(--color-muted);
font-style: italic; font-size: 0.95rem;
font-weight: 500; margin: 0.4rem 0 0;
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.5rem 1.2rem; padding: 1.5rem 1.2rem;
border-radius: var(--radius); border-radius: var(--radius-ui);
margin-bottom: 1rem; margin-bottom: 1rem;
} }
@@ -327,18 +190,17 @@ body::before {
.state-block--error { .state-block--error {
background: var(--color-error-bg); background: var(--color-error-bg);
color: var(--color-error-text); color: var(--color-text);
} }
.state-block--error.state-block--limit { .state-block--error.state-block--limit {
background: var(--color-limit-bg); background: var(--color-limit-bg);
color: var(--color-limit-text);
} }
.state-block--success { .state-block--success {
position: relative; position: relative;
background: linear-gradient(180deg, var(--color-success-bg), var(--color-surface) 75%); background: linear-gradient(180deg, var(--color-success-bg), transparent 75%);
color: var(--color-success-text); color: var(--color-text);
overflow: visible; overflow: visible;
padding-top: 1.75rem; padding-top: 1.75rem;
} }
@@ -351,16 +213,14 @@ body::before {
margin-bottom: 0; margin-bottom: 0;
} }
/* Spinner de chargement — anneau CSS pur, aucune dépendance. Remplace le /* Spinner de chargement — anneau CSS pur, aucune dépendance. */
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 { .spinner {
display: inline-block; display: inline-block;
width: 26px; width: 26px;
height: 26px; height: 26px;
margin-bottom: 0.6rem; margin-bottom: 0.6rem;
border: 3px solid var(--color-accent-soft); border: 3px solid rgba(255, 94, 91, 0.22);
border-top-color: var(--color-accent); border-top-color: var(--color-coral);
border-radius: 50%; border-radius: 50%;
animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite;
} }
@@ -373,9 +233,8 @@ body::before {
/* Icônes des états erreur / limite atteinte — les deux existent toujours /* 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 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 (state-block--limit vs par défaut = erreur). Aucune logique déplacée
fait côté JS uniquement en togglant cette classe (aucune autre logique dans le CSS, uniquement le rendu visuel. */
déplacée dans le CSS). */
.state-icon { .state-icon {
display: none; display: none;
width: 44px; width: 44px;
@@ -388,8 +247,8 @@ body::before {
.state-block--error .state-icon--error { .state-block--error .state-icon--error {
display: flex; display: flex;
background: rgba(163, 52, 28, 0.12); background: rgba(255, 94, 91, 0.16);
color: var(--color-error-text); color: var(--color-coral);
} }
.state-block--error.state-block--limit .state-icon--error { .state-block--error.state-block--limit .state-icon--error {
@@ -398,9 +257,9 @@ body::before {
.state-block--limit .state-icon--limit { .state-block--limit .state-icon--limit {
display: flex; display: flex;
background: rgba(138, 74, 16, 0.14); background: rgba(255, 209, 102, 0.18);
color: var(--color-gold); color: var(--color-yellow);
animation: pop-in 0.5s var(--ease-bounce) both; animation: pop-in 0.5s var(--ease-spring) both;
} }
.state-icon svg { .state-icon svg {
@@ -415,10 +274,10 @@ body::before {
animation: fade-rise 0.5s var(--ease-standard) 0.08s both; animation: fade-rise 0.5s var(--ease-standard) 0.08s both;
} }
/* Même bug que .welcome-modal (voir commentaire plus haut) : présent depuis /* Bug réel corrigé (2026-09-16, conservé lors du passage à la DA "Polaroid
le tout premier commit de ce fichier. form.hidden = true (JS) ne masquait Néon") : une déclaration `display` en CSS auteur passe toujours devant
jamais réellement le formulaire, qui restait affiché en display:flex en `[hidden] { display: none }` du navigateur. Sans cette règle explicite,
permanence, superposé aux états chargement/erreur/succès. */ `form.hidden = true` (JS) ne masquait jamais réellement le formulaire. */
.upload-form[hidden] { .upload-form[hidden] {
display: none; display: none;
} }
@@ -436,20 +295,19 @@ body::before {
.field .optional { .field .optional {
font-weight: 400; font-weight: 400;
color: var(--color-muted); color: var(--color-muted-2);
font-size: 0.85rem; font-size: 0.85rem;
} }
/* Regroupe prénom + petit mot dans une même carte : crée un second niveau /* Regroupe prénom + petit mot dans une même carte : second niveau de
de hiérarchie visuelle (la photo reste l'action principale au-dessus, hiérarchie visuelle (la photo reste l'action principale au-dessus). */
ces champs facultatifs sont secondaires et "posés" ensemble). */
.field-group { .field-group {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;
background: var(--color-surface); background: var(--color-surface);
border: 1px solid var(--color-border); border: 1px solid var(--color-border-ui);
border-radius: var(--radius); border-radius: var(--radius-ui);
padding: 1.1rem; padding: 1.1rem;
} }
@@ -457,21 +315,18 @@ input[type="text"],
textarea { textarea {
font: inherit; font: inherit;
padding: 0.7rem 0.8rem; padding: 0.7rem 0.8rem;
border: 1.5px solid var(--color-border); border: 1.5px solid var(--color-border-ui);
border-radius: var(--radius-sm); border-radius: 10px;
background: var(--color-surface); background: var(--color-bg);
color: inherit; color: inherit;
width: 100%; width: 100%;
transition: border-color 0.18s var(--ease-standard), box-shadow 0.18s var(--ease-standard); 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, input[type="text"]:focus,
textarea:focus { textarea:focus {
border-color: var(--color-accent); border-color: var(--color-coral);
box-shadow: 0 0 0 4px var(--color-accent-soft); box-shadow: 0 0 0 4px rgba(255, 94, 91, 0.22);
} }
.visually-hidden { .visually-hidden {
@@ -491,12 +346,10 @@ textarea:focus {
gap: 0.6rem; gap: 0.6rem;
} }
/* Boutons "Prendre une photo" / "Depuis la galerie" — forme pilule (identité /* Boutons "Prendre une photo" / "Depuis la galerie" — la capture directe
plus ronde/organique que la V1) avec une hiérarchie claire : la capture est l'action attendue pour la quasi-totalité des invités pendant
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) ;
l'événement (le moment se vit là, tout de suite), donc traitée en la galerie reste secondaire mais tout aussi accessible en un tap. */
primaire pleine couleur ; la galerie reste secondaire mais tout aussi
accessible en un tap (contrainte actée le 2026-09-16). */
.btn-source { .btn-source {
flex: 1; flex: 1;
display: flex; display: flex;
@@ -510,20 +363,20 @@ textarea:focus {
min-height: 50px; min-height: 50px;
border-radius: var(--radius-pill); border-radius: var(--radius-pill);
cursor: pointer; 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 { .btn-source--primary {
border: none; border: none;
background: linear-gradient(135deg, var(--color-accent-light), var(--color-accent)); background: var(--color-coral);
color: var(--color-accent-contrast); color: var(--color-ink);
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
} }
.btn-source--secondary { .btn-source--secondary {
border: 1.5px solid var(--color-plum); border: 1.5px solid var(--color-border-ui-strong);
background: var(--color-surface); background: transparent;
color: var(--color-plum); color: var(--color-text);
} }
.btn-source:active { .btn-source:active {
@@ -531,34 +384,30 @@ textarea:focus {
} }
.btn-source--primary:active { .btn-source--primary:active {
background: linear-gradient(135deg, var(--color-accent), var(--color-accent-hover)); background: #ff7673;
} }
.btn-source--secondary:active { .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 /* É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 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à. sur mobile tactile. */
Lift discret + easing posé (pas de rebond ici, réservé aux pops). */
@media (hover: hover) { @media (hover: hover) {
.btn-source--primary:hover { .btn-source--primary:hover {
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: var(--shadow-md); box-shadow: var(--shadow-soft);
} }
.btn-source--secondary:hover { .btn-source--secondary:hover {
transform: translateY(-1px); 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 /* Icônes SVG inline (aucune requête réseau), héritent de currentColor. */
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. */
.btn-icon { .btn-icon {
width: 20px; width: 20px;
height: 20px; height: 20px;
@@ -567,7 +416,7 @@ textarea:focus {
.file-chosen-label { .file-chosen-label {
font-size: 0.85rem; font-size: 0.85rem;
color: var(--color-muted); color: var(--color-muted-2);
margin: 0.5rem 0 0; margin: 0.5rem 0 0;
text-align: center; text-align: center;
} }
@@ -580,7 +429,7 @@ textarea {
input:focus-visible, input:focus-visible,
textarea:focus-visible, textarea:focus-visible,
button:focus-visible { button:focus-visible {
outline: 3px solid var(--color-plum); outline: 3px solid var(--color-coral);
outline-offset: 2px; outline-offset: 2px;
} }
@@ -588,9 +437,9 @@ button:focus-visible {
width: 100%; width: 100%;
max-height: 320px; max-height: 320px;
object-fit: contain; object-fit: contain;
border-radius: var(--radius); border-radius: var(--radius-ui);
background: var(--color-surface-muted); 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 { .legal-notice {
@@ -600,7 +449,7 @@ button:focus-visible {
font-size: 0.78rem; 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-ui);
padding: 0.85rem 0.9rem; padding: 0.85rem 0.9rem;
margin: 0; margin: 0;
} }
@@ -610,7 +459,7 @@ button:focus-visible {
height: 18px; height: 18px;
flex-shrink: 0; flex-shrink: 0;
margin-top: 0.1rem; margin-top: 0.1rem;
color: var(--color-plum); color: var(--color-muted-2);
} }
.legal-link { .legal-link {
@@ -625,7 +474,7 @@ button:focus-visible {
font: inherit; font: inherit;
font-size: 0.8rem; font-size: 0.8rem;
font-weight: 600; font-weight: 600;
color: var(--color-muted); color: var(--color-muted-2);
text-decoration: underline; text-decoration: underline;
text-underline-offset: 2px; text-underline-offset: 2px;
cursor: pointer; cursor: pointer;
@@ -633,21 +482,21 @@ button:focus-visible {
} }
.legal-link .legal-notice__icon { .legal-link .legal-notice__icon {
color: var(--color-muted); color: var(--color-muted-2);
} }
@media (hover: hover) { @media (hover: hover) {
.legal-link:hover { .legal-link:hover {
color: var(--color-plum); color: var(--color-text);
} }
.legal-link:hover .legal-notice__icon { .legal-link:hover .legal-notice__icon {
color: var(--color-plum); color: var(--color-text);
} }
#welcome-modal-dismiss:hover { #welcome-modal-dismiss:hover {
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: var(--shadow-md); box-shadow: var(--shadow-soft);
} }
} }
@@ -661,17 +510,13 @@ button:focus-visible {
align-items: flex-end; align-items: flex-end;
justify-content: center; justify-content: center;
padding: 0; padding: 0;
background: rgba(43, 32, 22, 0.45); background: rgba(0, 0, 0, 0.6);
animation: fade-in 0.2s ease both; animation: fade-in 0.2s ease both;
} }
/* Bug réel corrigé (2026-09-16) : une déclaration `display` en CSS auteur /* Même bug que .upload-form (voir commentaire plus haut), présent depuis le
passe toujours devant la règle `[hidden] { display: none }` du navigateur tout premier commit : sans cette règle, welcomeModal.hidden = true (JS)
(l'origine "auteur" l'emporte sur l'origine "user-agent" dans la ne masquait jamais visuellement la modale. */
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. */
.welcome-modal[hidden] { .welcome-modal[hidden] {
display: none; display: none;
} }
@@ -683,26 +528,25 @@ button:focus-visible {
max-height: 88vh; max-height: 88vh;
overflow-y: auto; overflow-y: auto;
background: var(--color-surface); 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)); padding: 1.5rem 1.25rem calc(1.5rem + env(safe-area-inset-bottom));
box-shadow: var(--shadow-md); box-shadow: var(--shadow-soft);
animation: slide-up 0.35s var(--ease-bounce) both; animation: slide-up 0.35s var(--ease-spring) both;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.9rem; gap: 0.9rem;
} }
/* Liseré dégradé en haut de la carte — même traitement que les cartes de la /* Liseré corail en haut de la carte — même traitement de "chrome" que la
galerie admin (src/admin/gallery/style.css), pour une petite touche de galerie admin (src/admin/gallery/style.css), touche de couleur cohérente
couleur cohérente entre les deux écrans sur un élément de chrome (pas de entre écrans sur un élément de bordure, pas de contenu. */
contenu), plutôt qu'un ajout ponctuel isolé. */
.welcome-modal__card::before { .welcome-modal__card::before {
content: ""; content: "";
display: block; display: block;
height: 4px; height: 4px;
flex-shrink: 0; flex-shrink: 0;
margin: -1.5rem -1.25rem 1.1rem; 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) { @media (min-width: 520px) {
@@ -710,7 +554,7 @@ button:focus-visible {
align-items: center; align-items: center;
} }
.welcome-modal__card { .welcome-modal__card {
border-radius: var(--radius); border-radius: var(--radius-ui);
} }
} }
@@ -718,46 +562,38 @@ button:focus-visible {
margin-bottom: 0; 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 { .welcome-modal .brand__mark {
width: 30px; width: 30px;
height: 30px; height: 30px;
padding: 7px; padding: 7px;
border-radius: 50%; border-radius: 50%;
background: var(--color-accent-soft); background: rgba(255, 94, 91, 0.16);
color: var(--color-accent); color: var(--color-coral);
} }
.welcome-modal__title { .welcome-modal__title {
font-family: var(--font-display); font-family: var(--font-display);
font-weight: 600; font-weight: 700;
font-size: 1.6rem; 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; text-align: center;
margin: 0; margin: 0;
color: var(--color-text); color: var(--color-text);
} }
.welcome-modal__text {
text-align: center;
margin: 0;
color: var(--color-muted);
}
#welcome-modal-dismiss { #welcome-modal-dismiss {
background: linear-gradient(135deg, var(--color-accent-light), var(--color-accent)); background: var(--color-coral);
color: var(--color-accent-contrast); color: var(--color-ink);
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
} }
#welcome-modal-dismiss:active { #welcome-modal-dismiss:active {
background: linear-gradient(135deg, var(--color-accent), var(--color-accent-hover)); background: #ff7673;
} }
@keyframes fade-in { @keyframes fade-in {
@@ -774,7 +610,7 @@ button:focus-visible {
min-height: 1.2em; min-height: 1.2em;
font-size: 0.9rem; font-size: 0.9rem;
font-weight: 600; font-weight: 600;
color: var(--color-plum); color: var(--color-yellow);
margin: 0; margin: 0;
text-align: center; text-align: center;
} }
@@ -787,7 +623,7 @@ button {
padding: 0.9rem 1rem; padding: 0.9rem 1rem;
min-height: 50px; min-height: 50px;
cursor: pointer; 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, #submit-btn,
@@ -796,20 +632,20 @@ button {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 0.5rem; gap: 0.5rem;
background: linear-gradient(135deg, var(--color-accent-light), var(--color-accent)); background: var(--color-coral);
color: var(--color-accent-contrast); color: var(--color-ink);
box-shadow: var(--shadow-md); box-shadow: var(--shadow-soft);
font-size: 1rem; font-size: 1rem;
} }
#submit-btn:active:not(:disabled), #submit-btn:active:not(:disabled),
#add-another-btn:active { #add-another-btn:active {
transform: scale(0.97); transform: scale(0.97);
background: linear-gradient(135deg, var(--color-accent), var(--color-accent-hover)); background: #ff7673;
} }
#submit-btn:disabled { #submit-btn:disabled {
opacity: 0.6; opacity: 0.5;
cursor: not-allowed; cursor: not-allowed;
box-shadow: none; box-shadow: none;
} }
@@ -818,12 +654,10 @@ button {
#submit-btn:hover:not(:disabled), #submit-btn:hover:not(:disabled),
#add-another-btn:hover { #add-another-btn:hover {
transform: translateY(-1px); 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 /* Micro-nudge de l'icône avion au survol : desktop uniquement. */
les visiteurs desktop (donc surtout les prospects en démo), sans
dépendance JS supplémentaire. */
#submit-btn:hover:not(:disabled) .btn-icon { #submit-btn:hover:not(:disabled) .btn-icon {
transform: translate(2px, -2px); transform: translate(2px, -2px);
} }
@@ -834,14 +668,18 @@ button {
} }
#retry-btn { #retry-btn {
background: var(--color-danger); background: var(--color-coral);
color: var(--color-danger-contrast); color: var(--color-ink);
}
#retry-btn:active {
background: #ff7673;
} }
/* --------------------------------------------------------------------- /* ---------------------------------------------------------------------
Écran de succès — LE moment à soigner : c'est la dernière impression É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 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 { .success-check {
@@ -849,7 +687,7 @@ button {
width: 76px; width: 76px;
height: 76px; height: 76px;
margin: 0 auto 0.9rem; 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 { .success-check svg {
@@ -859,15 +697,15 @@ button {
} }
.success-check__circle { .success-check__circle {
stroke: var(--color-accent); stroke: var(--color-mint);
fill: var(--color-gold-soft); fill: rgba(6, 214, 160, 0.14);
stroke-dasharray: 145; stroke-dasharray: 145;
stroke-dashoffset: 145; stroke-dashoffset: 145;
animation: draw 0.5s ease-out 0.1s forwards; animation: draw 0.5s ease-out 0.1s forwards;
} }
.success-check__mark { .success-check__mark {
stroke: var(--color-accent); stroke: var(--color-mint);
fill: none; fill: none;
stroke-dasharray: 32; stroke-dasharray: 32;
stroke-dashoffset: 32; stroke-dashoffset: 32;
@@ -912,51 +750,51 @@ button {
.confetti span:nth-child(1) { .confetti span:nth-child(1) {
left: 18%; left: 18%;
background: var(--color-accent); background: var(--color-coral);
border-radius: 2px; border-radius: 2px;
animation-delay: 0.05s; animation-delay: 0.05s;
} }
.confetti span:nth-child(2) { .confetti span:nth-child(2) {
left: 32%; left: 32%;
top: 0.2rem; top: 0.2rem;
background: var(--color-gold); background: var(--color-yellow);
border-radius: 50%; border-radius: 50%;
animation-delay: 0.15s; animation-delay: 0.15s;
} }
.confetti span:nth-child(3) { .confetti span:nth-child(3) {
left: 48%; left: 48%;
background: var(--color-plum); background: var(--color-mint);
border-radius: 2px; border-radius: 2px;
animation-delay: 0.02s; animation-delay: 0.02s;
} }
.confetti span:nth-child(4) { .confetti span:nth-child(4) {
left: 62%; left: 62%;
background: var(--color-accent-light); background: var(--color-coral);
border-radius: 50%; border-radius: 50%;
animation-delay: 0.22s; animation-delay: 0.22s;
} }
.confetti span:nth-child(5) { .confetti span:nth-child(5) {
left: 74%; left: 74%;
background: var(--color-gold); background: var(--color-yellow);
border-radius: 2px; border-radius: 2px;
animation-delay: 0.1s; animation-delay: 0.1s;
} }
.confetti span:nth-child(6) { .confetti span:nth-child(6) {
left: 86%; left: 86%;
background: var(--color-plum); background: var(--color-mint);
border-radius: 50%; border-radius: 50%;
animation-delay: 0.28s; animation-delay: 0.28s;
} }
.confetti span:nth-child(7) { .confetti span:nth-child(7) {
left: 10%; left: 10%;
top: 0.3rem; top: 0.3rem;
background: var(--color-gold); background: var(--color-yellow);
border-radius: 50%; border-radius: 50%;
animation-delay: 0.18s; animation-delay: 0.18s;
} }
.confetti span:nth-child(8) { .confetti span:nth-child(8) {
left: 94%; left: 94%;
background: var(--color-accent); background: var(--color-coral);
border-radius: 2px; border-radius: 2px;
animation-delay: 0.08s; animation-delay: 0.08s;
} }
@@ -977,15 +815,22 @@ button {
.success-title { .success-title {
font-family: var(--font-display); font-family: var(--font-display);
font-weight: 700;
font-size: 1.75rem; font-size: 1.75rem;
font-weight: 600; color: var(--color-text);
color: var(--color-accent-hover);
margin: 0 0 0.3rem; 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 { .success-text {
font-size: 0.95rem; font-family: var(--font-hand);
margin: 0 0 1.3rem; 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) { @media (prefers-reduced-motion: reduce) {