/* Page upload invité — direction artistique "Polaroid Néon" (.claude/skills/photobooth-da/SKILL.md). Remplace les 3 passes DA précédentes (palette corail/prune/doré + Fraunces, voir historique git) : cet écran suit désormais src/galerie/style.css comme référence de cohérence (premier écran construit dans cette DA, validé par Kévin — "c'est vraiment top comme ça !"). Choix pris pendant cette passe, à signaler à Kévin : - Thème unique sombre (color-scheme: dark), comme src/galerie/style.css, plutôt que le double thème clair/sombre (prefers-color-scheme) de l'ancienne DA. Le skill ne demande nulle part de garder un thème clair, et le premier écran validé n'en a pas — cohérence retenue plutôt qu'une redondance non demandée. À confirmer si un thème clair est en fait souhaité pour cet écran précis. - Titre et éléments de "chrome" en texte plein (pas de dégradé corail→doré comme l'ancienne DA) : le skill met en garde contre le "glow/neon CSS artificiel" et l'écran de référence (galerie) n'utilise aucun dégradé de texte ni halo radial en fond — même logique appliquée ici pour rester cohérent plutôt que de réintroduire cet effet. Contrastes vérifiés par calcul WCAG AA (même méthode que src/galerie/style.css) : texte clair (#FAF6F0/#E8DFD3) sur fond sombre (#12100E) est très largement au-dessus du seuil ; texte sombre (#12100E) sur les 3 couleurs d'accent (corail/jaune/vert) passe confortablement (6.3:1 à 13.2:1) — utilisé systématiquement pour le texte des CTA en couleur pleine. Les fonds teintés à faible opacité (états erreur/limite/ succès) restent très proches du noir une fois mélangés à #12100E ; le texte de ces blocs reste donc en couleur claire standard (--color-text/ --color-muted), jamais en couleur d'accent pleine, par prudence. */ @font-face { font-family: "General Sans"; src: url("/vendor/fonts/generalsans-700.woff2") format("woff2"); font-weight: 700; font-style: normal; font-display: swap; } @font-face { font-family: "Caveat"; src: url("/vendor/fonts/caveat-latin-variable.woff2") format("woff2"); font-weight: 400 700; font-style: normal; font-display: swap; } @font-face { font-family: "Inter"; src: url("/vendor/fonts/inter-latin-variable.woff2") format("woff2"); font-weight: 400 700; font-style: normal; font-display: swap; } :root { color-scheme: dark; /* Mêmes noms/valeurs que src/galerie/style.css pour la cohérence entre écrans (voir consigne de la passe). */ --color-bg: #12100e; --color-ink: #12100e; /* texte sombre sur surfaces claires/accents */ --color-text: #faf6f0; --color-muted: #e8dfd3; --color-muted-2: #a89e90; --color-coral: #ff5e5b; --color-yellow: #ffd166; --color-mint: #06d6a0; --color-border: #e8dfd3; --radius: 4px; /* réservé aux éléments façon polaroid, non utilisé ici */ --radius-ui: 14px; --radius-pill: 999px; --shadow-soft: 0 10px 24px rgba(0, 0, 0, 0.35); --shadow-card: 0 6px 16px rgba(0, 0, 0, 0.4); --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); --ease-standard: cubic-bezier(0.22, 1, 0.36, 1); --font-display: "General Sans", -apple-system, BlinkMacSystemFont, sans-serif; --font-hand: "Caveat", "Segoe Script", cursive; --font-ui: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; /* Tokens spécifiques à cet écran (formulaire, états), pas repris de src/galerie/style.css qui n'en a pas besoin. */ --color-surface: #1e1b18; --color-surface-muted: #241f1a; --color-border-ui: rgba(250, 246, 240, 0.16); --color-border-ui-strong: rgba(250, 246, 240, 0.28); --color-error-bg: rgba(255, 94, 91, 0.14); --color-limit-bg: rgba(255, 209, 102, 0.16); --color-success-bg: rgba(6, 214, 160, 0.12); --shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.3); } * { box-sizing: border-box; } html { -webkit-text-size-adjust: 100%; } body { margin: 0; font-family: var(--font-ui); background: var(--color-bg); color: var(--color-text); line-height: 1.45; } .page { max-width: 480px; margin: 0 auto; padding: 1.5rem 1.1rem 3rem; min-height: 100dvh; } .page__header { text-align: center; margin-bottom: 1.6rem; 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 { display: flex; align-items: center; justify-content: center; gap: 0.4rem; margin-bottom: 0.7rem; color: var(--color-muted-2); } .brand__mark { width: 16px; height: 16px; flex-shrink: 0; color: var(--color-coral); } .brand__name { font-size: 0.7rem; font-weight: 700; letter-spacing: 0.13em; text-transform: uppercase; font-family: var(--font-ui); } .page__header h1 { font-family: var(--font-display); font-weight: 700; font-size: 1.9rem; letter-spacing: -0.01em; line-height: 1.15; margin: 0; } .wall-link { display: inline-flex; margin-top: 0.7rem; font-family: var(--font-ui); font-weight: 700; font-size: 0.85rem; color: var(--color-coral); text-decoration: none; } .wall-link:active { opacity: 0.7; } .page__kicker { color: var(--color-muted); font-size: 0.95rem; margin: 0.4rem 0 0; } .state-block { text-align: center; padding: 1.5rem 1.2rem; border-radius: var(--radius-ui); margin-bottom: 1rem; } .state-block--loading { color: var(--color-muted); } .state-block--error { background: var(--color-error-bg); color: var(--color-text); } .state-block--error.state-block--limit { background: var(--color-limit-bg); } .state-block--success { position: relative; background: linear-gradient(180deg, var(--color-success-bg), transparent 75%); color: var(--color-text); overflow: visible; padding-top: 1.75rem; } .state-block p { margin: 0 0 0.75rem; } .state-block p:last-child { margin-bottom: 0; } /* Spinner de chargement — anneau CSS pur, aucune dépendance. */ .spinner { display: inline-block; width: 26px; height: 26px; margin-bottom: 0.6rem; border: 3px solid rgba(255, 94, 91, 0.22); border-top-color: var(--color-coral); border-radius: 50%; animation: spin 0.8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* Icônes des états erreur / limite atteinte — les deux existent toujours dans le DOM, affichées/masquées via la classe portée par le bloc parent (state-block--limit vs par défaut = erreur). Aucune logique déplacée dans le CSS, uniquement le rendu visuel. */ .state-icon { display: none; width: 44px; height: 44px; margin: 0 auto 0.6rem; align-items: center; justify-content: center; border-radius: 50%; } .state-block--error .state-icon--error { display: flex; background: rgba(255, 94, 91, 0.16); color: var(--color-coral); } .state-block--error.state-block--limit .state-icon--error { display: none; } .state-block--limit .state-icon--limit { display: flex; background: rgba(255, 209, 102, 0.18); color: var(--color-yellow); animation: pop-in 0.5s var(--ease-spring) both; } .state-icon svg { width: 24px; height: 24px; } .upload-form { display: flex; flex-direction: column; gap: 1.15rem; animation: fade-rise 0.5s var(--ease-standard) 0.08s both; } /* Bug réel corrigé (2026-09-16, conservé lors du passage à la DA "Polaroid Néon") : une déclaration `display` en CSS auteur passe toujours devant `[hidden] { display: none }` du navigateur. Sans cette règle explicite, `form.hidden = true` (JS) ne masquait jamais réellement le formulaire. */ .upload-form[hidden] { display: none; } .field { display: flex; flex-direction: column; gap: 0.4rem; } .field label { font-weight: 700; font-size: 0.95rem; } .field .optional { font-weight: 400; color: var(--color-muted-2); font-size: 0.85rem; } /* Regroupe prénom + petit mot dans une même carte : second niveau de hiérarchie visuelle (la photo reste l'action principale au-dessus). */ .field-group { display: flex; flex-direction: column; gap: 1rem; background: var(--color-surface); border: 1px solid var(--color-border-ui); border-radius: var(--radius-ui); padding: 1.1rem; } input[type="text"], textarea { font: inherit; padding: 0.7rem 0.8rem; border: 1.5px solid var(--color-border-ui); border-radius: 10px; background: var(--color-bg); color: inherit; width: 100%; transition: border-color 0.18s var(--ease-standard), box-shadow 0.18s var(--ease-standard); } input[type="text"]:focus, textarea:focus { border-color: var(--color-coral); box-shadow: 0 0 0 4px rgba(255, 94, 91, 0.22); } .visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } .photo-source-buttons { display: flex; gap: 0.6rem; } /* Boutons "Prendre une photo" / "Depuis la galerie" — la capture directe est l'action attendue pour la quasi-totalité des invités pendant l'événement, donc traitée en primaire (corail plein, texte sombre) ; la galerie reste secondaire mais tout aussi accessible en un tap. */ .btn-source { flex: 1; display: flex; align-items: center; justify-content: center; gap: 0.45rem; text-align: center; font-weight: 700; font-size: 0.88rem; padding: 0.9rem 0.6rem; min-height: 50px; border-radius: var(--radius-pill); cursor: pointer; transition: transform 0.12s var(--ease-spring), box-shadow 0.12s ease, background 0.12s ease; } .btn-source--primary { border: none; background: var(--color-coral); color: var(--color-ink); box-shadow: var(--shadow-sm); } .btn-source--secondary { border: 1.5px solid var(--color-border-ui-strong); background: transparent; color: var(--color-text); } .btn-source:active { transform: scale(0.96); } .btn-source--primary:active { background: #ff7673; } .btn-source--secondary:active { background: var(--color-surface); } /* États hover — uniquement pour les pointeurs qui en ont un (souris/trackpad d'un prospect qui consulte la page sur desktop lors d'une démo) : jamais sur mobile tactile. */ @media (hover: hover) { .btn-source--primary:hover { transform: translateY(-1px); box-shadow: var(--shadow-soft); } .btn-source--secondary:hover { transform: translateY(-1px); background: var(--color-surface); border-color: var(--color-coral); } } /* Icônes SVG inline (aucune requête réseau), héritent de currentColor. */ .btn-icon { width: 20px; height: 20px; flex-shrink: 0; } .file-chosen-label { font-size: 0.85rem; color: var(--color-muted-2); margin: 0.5rem 0 0; text-align: center; } textarea { resize: vertical; min-height: 4.5rem; } input:focus-visible, textarea:focus-visible, button:focus-visible { outline: 3px solid var(--color-coral); outline-offset: 2px; } .preview { width: 100%; max-height: 320px; object-fit: contain; border-radius: var(--radius-ui); background: var(--color-surface-muted); animation: pop-in 0.35s var(--ease-spring) both; } .legal-notice { display: flex; align-items: flex-start; gap: 0.6rem; font-size: 0.78rem; color: var(--color-muted); background: var(--color-surface-muted); border-radius: var(--radius-ui); padding: 0.85rem 0.9rem; margin: 0; } .legal-notice__icon { width: 18px; height: 18px; flex-shrink: 0; margin-top: 0.1rem; color: var(--color-muted-2); } .legal-link { display: flex; align-items: center; gap: 0.5rem; background: none; border: none; box-shadow: none; padding: 0.4rem 0.1rem; margin: 0; font: inherit; font-size: 0.8rem; font-weight: 600; color: var(--color-muted-2); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; min-height: 0; } .legal-link .legal-notice__icon { color: var(--color-muted-2); } @media (hover: hover) { .legal-link:hover { color: var(--color-text); } .legal-link:hover .legal-notice__icon { color: var(--color-text); } #welcome-modal-dismiss:hover { transform: translateY(-1px); box-shadow: var(--shadow-soft); } } /* 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). */ .welcome-modal { position: fixed; inset: 0; z-index: 100; display: flex; align-items: flex-end; justify-content: center; padding: 0; background: rgba(0, 0, 0, 0.6); animation: fade-in 0.2s ease both; } /* Même bug que .upload-form (voir commentaire plus haut), présent depuis le tout premier commit : sans cette règle, welcomeModal.hidden = true (JS) ne masquait jamais visuellement la modale. */ .welcome-modal[hidden] { display: none; } .welcome-modal__card { position: relative; width: 100%; max-width: 480px; max-height: 88vh; overflow-y: auto; background: var(--color-surface); border-radius: var(--radius-ui) var(--radius-ui) 0 0; padding: 1.5rem 1.25rem calc(1.5rem + env(safe-area-inset-bottom)); box-shadow: var(--shadow-soft); animation: slide-up 0.35s var(--ease-spring) both; display: flex; flex-direction: column; gap: 0.9rem; } /* Liseré corail en haut de la carte — même traitement de "chrome" que la galerie admin (src/admin/gallery/style.css), touche de couleur cohérente entre écrans sur un élément de bordure, pas de contenu. */ .welcome-modal__card::before { content: ""; display: block; height: 4px; flex-shrink: 0; margin: -1.5rem -1.25rem 1.1rem; background: var(--color-coral); } @media (min-width: 520px) { .welcome-modal { align-items: center; } .welcome-modal__card { border-radius: var(--radius-ui); } } .welcome-modal .brand { margin-bottom: 0; } .welcome-modal .brand__mark { width: 30px; height: 30px; padding: 7px; border-radius: 50%; background: rgba(255, 94, 91, 0.16); color: var(--color-coral); } .welcome-modal__title { font-family: var(--font-display); font-weight: 700; font-size: 1.6rem; text-align: center; margin: 0; color: var(--color-text); } .welcome-modal__text { text-align: center; margin: 0; color: var(--color-muted); } #welcome-modal-dismiss { background: var(--color-coral); color: var(--color-ink); box-shadow: var(--shadow-sm); } #welcome-modal-dismiss:active { background: #ff7673; } @keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } @keyframes slide-up { from { transform: translateY(24px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } .progress-text { min-height: 1.2em; font-size: 0.9rem; font-weight: 600; color: var(--color-yellow); margin: 0; text-align: center; } button { font: inherit; font-weight: 700; border: none; border-radius: var(--radius-pill); padding: 0.9rem 1rem; min-height: 50px; cursor: pointer; transition: transform 0.12s var(--ease-spring), background 0.12s ease, opacity 0.12s ease; } #submit-btn, #add-another-btn { display: flex; align-items: center; justify-content: center; gap: 0.5rem; background: var(--color-coral); color: var(--color-ink); box-shadow: var(--shadow-soft); font-size: 1rem; } #submit-btn:active:not(:disabled), #add-another-btn:active { transform: scale(0.97); background: #ff7673; } .success-actions { display: flex; flex-direction: column; align-items: center; gap: 0.7rem; } .btn-secondary-link { display: block; text-align: center; font-family: var(--font-ui); font-weight: 700; font-size: 0.9rem; color: var(--color-muted); text-decoration: none; } .btn-secondary-link:active { opacity: 0.7; } #submit-btn:disabled { opacity: 0.5; cursor: not-allowed; box-shadow: none; } @media (hover: hover) { #submit-btn:hover:not(:disabled), #add-another-btn:hover { transform: translateY(-1px); box-shadow: var(--shadow-soft); } /* Micro-nudge de l'icône avion au survol : desktop uniquement. */ #submit-btn:hover:not(:disabled) .btn-icon { transform: translate(2px, -2px); } } #submit-btn .btn-icon { transition: transform 0.18s var(--ease-standard); } #retry-btn { background: var(--color-coral); color: var(--color-ink); } #retry-btn:active { background: #ff7673; } /* --------------------------------------------------------------------- Écran de succès — LE moment à soigner : c'est la dernière impression que garde l'invité. Coche qui "pop" avec rebond + petite volée de confettis dans les 3 couleurs d'accent de la DA (pur CSS). ------------------------------------------------------------------ */ .success-check { position: relative; width: 76px; height: 76px; margin: 0 auto 0.9rem; animation: pop-in 0.55s var(--ease-spring) both; } .success-check svg { width: 100%; height: 100%; overflow: visible; } .success-check__circle { stroke: var(--color-mint); fill: rgba(6, 214, 160, 0.14); stroke-dasharray: 145; stroke-dashoffset: 145; animation: draw 0.5s ease-out 0.1s forwards; } .success-check__mark { stroke: var(--color-mint); fill: none; stroke-dasharray: 32; stroke-dashoffset: 32; animation: draw 0.35s ease-out 0.5s forwards; } @keyframes draw { to { stroke-dashoffset: 0; } } @keyframes pop-in { 0% { transform: scale(0.3); opacity: 0; } 65% { transform: scale(1.12); opacity: 1; } 100% { transform: scale(1); } } .confetti { position: absolute; inset: 0; pointer-events: none; overflow: visible; } .confetti span { position: absolute; top: 0.5rem; width: 9px; height: 9px; opacity: 0; animation: confetti-fall 1.1s ease-out forwards; } .confetti span:nth-child(1) { left: 18%; background: var(--color-coral); border-radius: 2px; animation-delay: 0.05s; } .confetti span:nth-child(2) { left: 32%; top: 0.2rem; background: var(--color-yellow); border-radius: 50%; animation-delay: 0.15s; } .confetti span:nth-child(3) { left: 48%; background: var(--color-mint); border-radius: 2px; animation-delay: 0.02s; } .confetti span:nth-child(4) { left: 62%; background: var(--color-coral); border-radius: 50%; animation-delay: 0.22s; } .confetti span:nth-child(5) { left: 74%; background: var(--color-yellow); border-radius: 2px; animation-delay: 0.1s; } .confetti span:nth-child(6) { left: 86%; background: var(--color-mint); border-radius: 50%; animation-delay: 0.28s; } .confetti span:nth-child(7) { left: 10%; top: 0.3rem; background: var(--color-yellow); border-radius: 50%; animation-delay: 0.18s; } .confetti span:nth-child(8) { left: 94%; background: var(--color-coral); border-radius: 2px; animation-delay: 0.08s; } @keyframes confetti-fall { 0% { transform: translateY(-6px) rotate(0deg) scale(0.6); opacity: 0; } 15% { opacity: 1; } 100% { transform: translateY(92px) rotate(300deg) scale(1); opacity: 0; } } .success-title { font-family: var(--font-display); font-weight: 700; font-size: 1.75rem; color: var(--color-text); margin: 0 0 0.3rem; } /* Touche manuscrite ponctuelle (Caveat) sur le petit mot de succès — ton "friendly" demandé, sans en abuser (seul élément du texte à l'utiliser sur cet écran, comme sur les légendes de la galerie invité). */ .success-text { font-family: var(--font-hand); font-weight: 600; font-size: 1.35rem; line-height: 1.3; color: var(--color-muted); margin: 0 0 1.4rem; } @media (prefers-reduced-motion: reduce) { .spinner, .success-check, .success-check__circle, .success-check__mark, .confetti span, .preview, .state-icon--limit, button, .btn-source, .page__header, .upload-form, .welcome-modal, .welcome-modal__card, input[type="text"], textarea, .btn-icon { animation: none !important; transition: none !important; } }