Passe 3 DA : mode sombre + finition "premium type iPhone"

Vrai mode sombre via prefers-color-scheme (palette repensée, pas une
inversion — fond brun quasi-noir pour rester dans la famille chaude de la
marque, contrastes vérifiés WCAG AA). Répond aux deux demandes de Kévin en
une implémentation : mode sombre réel + rendu moins agressif en usage
réel (soirée en intérieur tamisé). Appliqué à la page upload, la galerie
admin et le générateur QR ; le carton imprimable reste volontairement en
clair uniquement (pas de mode sombre pertinent pour un PDF imprimé).

Finition : easing dédié aux transitions d'état (séparé du rebond ludique
existant), halo de couleur en fond de page, hover desktop enrichi,
focus ring teinté, entrées en fondu au chargement, modale de bienvenue
peaufinée.

Brainstorm documenté (voir rapport de tâche) : palette/typo confirmées
plutôt qu'étendues avec une 4e couleur ou une police supplémentaire, pour
rester fidèle à l'identité perso de Kévin plutôt que de la diluer.
This commit is contained in:
2026-09-16 16:10:23 +02:00
parent 45e0bd7bcc
commit 6d6be3ef99
5 changed files with 428 additions and 19 deletions
+7 -2
View File
@@ -13,7 +13,8 @@
fichier front, à mettre en place lors du déploiement. fichier front, à mettre en place lors du déploiement.
--> -->
<meta name="robots" content="noindex, nofollow"> <meta name="robots" content="noindex, nofollow">
<meta name="theme-color" content="#faf6f0"> <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> <link rel="preload" href="/vendor/fonts/fraunces-latin-600-normal.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/admin/gallery/style.css"> <link rel="stylesheet" href="/admin/gallery/style.css">
</head> </head>
@@ -38,7 +39,11 @@
<section id="status-error" class="state-block state-block--error" hidden role="alert"> <section id="status-error" class="state-block state-block--error" hidden role="alert">
<p id="status-error-message"></p> <p id="status-error-message"></p>
<button type="button" id="retry-btn" hidden>Réessayer</button> <!-- class="btn btn--secondary" ajoutée passe 3 : ce bouton n'avait
jusqu'ici aucune classe et retombait donc sur le style navigateur
par défaut (papercut visuel repéré pendant la passe DA, corrigé
ici — rendu uniquement, aucune logique touchée). -->
<button type="button" id="retry-btn" class="btn btn--secondary" hidden>Réessayer</button>
</section> </section>
<section id="gallery-content" hidden> <section id="gallery-content" hidden>
+106 -4
View File
@@ -5,7 +5,14 @@
que verront les prospects (photographes, wedding planners) lors d'une que verront les prospects (photographes, wedding planners) lors d'une
démo, donc soigné au même niveau que la page invité. Palette et police 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 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. */ la justification complète de la direction créative.
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é. */
@font-face { @font-face {
font-family: "Fraunces"; font-family: "Fraunces";
@@ -18,7 +25,7 @@
} }
:root { :root {
color-scheme: light; color-scheme: light dark;
--color-bg: #fff8f0; --color-bg: #fff8f0;
--color-surface: #ffffff; --color-surface: #ffffff;
@@ -49,12 +56,47 @@
--shadow-md: 0 10px 28px rgba(163, 78, 30, 0.16); --shadow-md: 0 10px 28px rgba(163, 78, 30, 0.16);
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1); --ease-bounce: 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, --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif; "Helvetica Neue", Arial, sans-serif;
--font-display: "Fraunces", Georgia, "Times New Roman", serif; --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;
}
}
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
@@ -64,6 +106,7 @@ html {
} }
body { body {
position: relative;
margin: 0; margin: 0;
font-family: var(--font-sans); font-family: var(--font-sans);
background: var(--color-bg); background: var(--color-bg);
@@ -71,6 +114,20 @@ body {
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;
@@ -81,6 +138,18 @@ body {
.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 {
@@ -223,6 +292,18 @@ body {
background: var(--color-plum-soft); background: var(--color-plum-soft);
} }
@media (hover: hover) {
.btn:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.btn--secondary:hover:not(:disabled) {
background: var(--color-plum-soft);
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-plum);
@@ -249,13 +330,28 @@ a:focus-visible {
} }
.photo-card { .photo-card {
position: relative;
background: var(--color-surface); background: var(--color-surface);
border-radius: var(--radius); border-radius: var(--radius);
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
transition: box-shadow 0.15s var(--ease-bounce), transform 0.15s var(--ease-bounce); 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. */
.photo-card::before {
content: "";
position: absolute;
inset: 0 0 auto 0;
height: 3px;
background: linear-gradient(90deg, var(--color-accent), var(--color-plum));
opacity: 0;
transition: opacity 0.2s var(--ease-standard);
z-index: 1;
} }
@media (hover: hover) { @media (hover: hover) {
@@ -263,6 +359,10 @@ a:focus-visible {
box-shadow: var(--shadow-md); box-shadow: var(--shadow-md);
transform: translateY(-3px) rotate(-0.6deg); transform: translateY(-3px) rotate(-0.6deg);
} }
.photo-card:hover::before {
opacity: 1;
}
} }
.photo-card__thumb-wrap { .photo-card__thumb-wrap {
@@ -342,7 +442,9 @@ a:focus-visible {
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.spinner, .spinner,
.photo-card, .photo-card,
.btn { .photo-card::before,
.btn,
.page__header {
animation: none !important; animation: none !important;
transition: none !important; transition: none !important;
} }
+48 -3
View File
@@ -5,15 +5,22 @@
<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="#1b120b" media="(prefers-color-scheme: dark)" />
<style> <style>
/* Mêmes tokens que src/e/style.css et src/admin/gallery/style.css — /* Mêmes tokens que src/e/style.css et src/admin/gallery/style.css —
cohérence visuelle même sur cet outil interne que les clients ne cohérence visuelle même sur cet outil interne que les clients ne
voient pas (cf. retour Kévin 2026-09-16). Palette/rayons resynchronisés voient pas (cf. retour Kévin 2026-09-16). Palette/rayons resynchronisés
avec la passe 2 (2026-09-16, direction "punchy/friendly") ; outil avec la passe 2 (2026-09-16, direction "punchy/friendly") ; outil
interne, priorité basse donc pas de police d'accent ni d'animation interne, priorité basse donc pas de police d'accent ni d'animations
ajoutées ici, juste la cohérence de base (couleurs, rayons). */ 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. */
:root { :root {
color-scheme: light; color-scheme: light dark;
--color-bg: #fff8f0; --color-bg: #fff8f0;
--color-surface: #ffffff; --color-surface: #ffffff;
--color-surface-muted: #fdecdd; --color-surface-muted: #fdecdd;
@@ -28,9 +35,27 @@
--radius-sm: 12px; --radius-sm: 12px;
--radius: 20px; --radius: 20px;
--shadow-sm: 0 2px 6px rgba(163, 78, 30, 0.1); --shadow-sm: 0 2px 6px rgba(163, 78, 30, 0.1);
--ease-standard: cubic-bezier(0.22, 1, 0.36, 1);
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif; "Helvetica Neue", Arial, sans-serif;
} }
@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);
}
}
* { box-sizing: border-box; } * { box-sizing: border-box; }
body { body {
margin: 0; margin: 0;
@@ -90,6 +115,13 @@
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
background: var(--color-surface); background: var(--color-surface);
color: inherit; 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);
} }
.preview { .preview {
margin-top: 1.5rem; margin-top: 1.5rem;
@@ -131,6 +163,7 @@
color: var(--color-accent-contrast); color: var(--color-accent-contrast);
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;
} }
button:active:not(:disabled) { button:active:not(:disabled) {
background: var(--color-accent-hover); background: var(--color-accent-hover);
@@ -143,6 +176,11 @@
button.secondary:active:not(:disabled) { button.secondary:active:not(:disabled) {
background: var(--color-accent-soft); background: var(--color-accent-soft);
} }
@media (hover: hover) {
button:hover:not(:disabled) {
transform: translateY(-1px);
}
}
button:disabled { button:disabled {
opacity: 0.5; opacity: 0.5;
cursor: not-allowed; cursor: not-allowed;
@@ -153,6 +191,13 @@
margin-top: 0.5rem; margin-top: 0.5rem;
min-height: 1.2em; min-height: 1.2em;
} }
@media (prefers-reduced-motion: reduce) {
button,
input,
select {
transition: none !important;
}
}
</style> </style>
</head> </head>
<body> <body>
+6 -2
View File
@@ -12,7 +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">
<meta name="theme-color" content="#faf6f0"> <!-- 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> <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>
@@ -96,7 +100,7 @@
<section id="success-block" class="state-block state-block--success" hidden role="status"> <section id="success-block" class="state-block state-block--success" hidden role="status">
<div class="confetti" aria-hidden="true"> <div class="confetti" aria-hidden="true">
<span></span><span></span><span></span><span></span><span></span><span></span> <span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>
</div> </div>
<div class="success-check" aria-hidden="true"> <div class="success-check" aria-hidden="true">
<svg viewBox="0 0 52 52" fill="none" focusable="false"> <svg viewBox="0 0 52 52" fill="none" focusable="false">
+261 -8
View File
@@ -22,7 +22,32 @@
un peu "pop") en plus de la pile système pour les titres/moments clés 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 — 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 src/vendor/fonts/ et src/vendor/NOTICE.md. Le corps de texte reste
100% système (poids nul). */ 100% système (poids nul).
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. */
@font-face { @font-face {
font-family: "Fraunces"; font-family: "Fraunces";
@@ -45,7 +70,13 @@
} }
:root { :root {
color-scheme: light; /* "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;
/* Couleurs — famille chaude toujours ancrée dans l'identité de Kévin, /* 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. */ mais poussée en saturation pour un rendu festif plutôt que sobre. */
@@ -75,6 +106,14 @@
--color-limit-bg: #fbe9d8; --color-limit-bg: #fbe9d8;
--color-limit-text: #8a4a10; --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-sm: 12px;
--radius: 20px; --radius: 20px;
--radius-pill: 999px; --radius-pill: 999px;
@@ -83,12 +122,81 @@
--shadow-md: 0 10px 28px rgba(163, 78, 30, 0.16); --shadow-md: 0 10px 28px rgba(163, 78, 30, 0.16);
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1); --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-standard: cubic-bezier(0.22, 1, 0.36, 1);
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif; "Helvetica Neue", Arial, sans-serif;
--font-display: "Fraunces", Georgia, "Times New Roman", serif; --font-display: "Fraunces", Georgia, "Times New Roman", 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;
--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-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;
}
}
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
@@ -98,6 +206,7 @@ html {
} }
body { body {
position: relative;
margin: 0; margin: 0;
font-family: var(--font-sans); font-family: var(--font-sans);
background: var(--color-bg); background: var(--color-bg);
@@ -105,6 +214,24 @@ body {
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;
@@ -115,6 +242,22 @@ body {
.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;
}
@keyframes fade-rise {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
} }
/* Bloc de marque — discret, rassure l'invité (outil "pro") et signe le /* Bloc de marque — discret, rassure l'invité (outil "pro") et signe le
@@ -269,6 +412,7 @@ body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1.15rem; gap: 1.15rem;
animation: fade-rise 0.5s var(--ease-standard) 0.08s both;
} }
.field { .field {
@@ -310,12 +454,16 @@ textarea {
background: var(--color-surface); background: var(--color-surface);
color: inherit; color: inherit;
width: 100%; width: 100%;
transition: border-color 0.15s ease; 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-accent);
box-shadow: 0 0 0 4px var(--color-accent-soft);
} }
.visually-hidden { .visually-hidden {
@@ -382,6 +530,22 @@ textarea:focus {
background: var(--color-plum-soft); background: var(--color-plum-soft);
} }
/* É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). */
@media (hover: hover) {
.btn-source--primary:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.btn-source--secondary:hover {
transform: translateY(-1px);
background: var(--color-plum-soft);
}
}
/* Icônes SVG inline (aucune requête réseau) : remplacent les emoji des /* Icônes SVG inline (aucune requête réseau) : remplacent les emoji des
boutons "Prendre une photo" / "Depuis la galerie". Le rendu des emoji boutons "Prendre une photo" / "Depuis la galerie". Le rendu des emoji
varie fortement selon l'OS (Android/iOS/Windows) — pas assez maîtrisé varie fortement selon l'OS (Android/iOS/Windows) — pas assez maîtrisé
@@ -464,6 +628,21 @@ button:focus-visible {
color: var(--color-muted); color: var(--color-muted);
} }
@media (hover: hover) {
.legal-link:hover {
color: var(--color-plum);
}
.legal-link:hover .legal-notice__icon {
color: var(--color-plum);
}
#welcome-modal-dismiss:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
}
/* Mot de bienvenue + RGPD — overlay affiché une fois par événement, /* Mot de bienvenue + RGPD — overlay affiché une fois par événement,
au-dessus du formulaire déjà présent en arrière-plan (cf. upload.js). */ au-dessus du formulaire déjà présent en arrière-plan (cf. upload.js). */
.welcome-modal { .welcome-modal {
@@ -479,6 +658,7 @@ button:focus-visible {
} }
.welcome-modal__card { .welcome-modal__card {
position: relative;
width: 100%; width: 100%;
max-width: 480px; max-width: 480px;
max-height: 88vh; max-height: 88vh;
@@ -493,6 +673,19 @@ button:focus-visible {
gap: 0.9rem; 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é. */
.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));
}
@media (min-width: 520px) { @media (min-width: 520px) {
.welcome-modal { .welcome-modal {
align-items: center; align-items: center;
@@ -506,13 +699,30 @@ 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 {
width: 30px;
height: 30px;
padding: 7px;
border-radius: 50%;
background: var(--color-accent-soft);
color: var(--color-accent);
}
.welcome-modal__title { .welcome-modal__title {
font-family: var(--font-display); font-family: var(--font-display);
font-weight: 600; font-weight: 600;
font-size: 1.5rem; font-size: 1.6rem;
text-align: center; text-align: center;
margin: 0; margin: 0;
color: var(--color-accent); 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 { .welcome-modal__text {
@@ -522,11 +732,15 @@ button:focus-visible {
} }
#welcome-modal-dismiss { #welcome-modal-dismiss {
background: var(--color-accent); background: linear-gradient(135deg, var(--color-accent-light), var(--color-accent));
color: var(--color-accent-contrast); color: var(--color-accent-contrast);
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
} }
#welcome-modal-dismiss:active {
background: linear-gradient(135deg, var(--color-accent), var(--color-accent-hover));
}
@keyframes fade-in { @keyframes fade-in {
from { opacity: 0; } from { opacity: 0; }
to { opacity: 1; } to { opacity: 1; }
@@ -581,9 +795,28 @@ button {
box-shadow: none; box-shadow: none;
} }
@media (hover: hover) {
#submit-btn:hover:not(:disabled),
#add-another-btn:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
/* 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. */
#submit-btn:hover:not(:disabled) .btn-icon {
transform: translate(2px, -2px);
}
}
#submit-btn .btn-icon {
transition: transform 0.18s var(--ease-standard);
}
#retry-btn { #retry-btn {
background: var(--color-error-text); background: var(--color-danger);
color: #fff; color: var(--color-danger-contrast);
} }
/* --------------------------------------------------------------------- /* ---------------------------------------------------------------------
@@ -695,6 +928,19 @@ button {
border-radius: 50%; border-radius: 50%;
animation-delay: 0.28s; animation-delay: 0.28s;
} }
.confetti span:nth-child(7) {
left: 10%;
top: 0.3rem;
background: var(--color-gold);
border-radius: 50%;
animation-delay: 0.18s;
}
.confetti span:nth-child(8) {
left: 94%;
background: var(--color-accent);
border-radius: 2px;
animation-delay: 0.08s;
}
@keyframes confetti-fall { @keyframes confetti-fall {
0% { 0% {
@@ -732,7 +978,14 @@ button {
.preview, .preview,
.state-icon--limit, .state-icon--limit,
button, button,
.btn-source { .btn-source,
.page__header,
.upload-form,
.welcome-modal,
.welcome-modal__card,
input[type="text"],
textarea,
.btn-icon {
animation: none !important; animation: none !important;
transition: none !important; transition: none !important;
} }