Files
Odoo-Modules/fusion_clock/static/src/scss/nfc_kiosk.scss
gsinghpal 3fd074ff6d fix(fusion_clock): kiosk photo now shows on clock + profile (right image fields)
Root-caused on live entech (not guessed):
- The kiosk runs as a non-HR operator (uid 141) who gets AccessError reading
  hr.employee images, so /web/image served a placeholder. Point the result-card
  avatar at hr.employee.public/avatar_128 — verified readable as the operator,
  returns the real photo. (Odoo's own UI uses .public for employee images.)
- The Odoo profile/preferences avatar is res.users → res.partner.image_1920,
  which the capture never wrote. Propagate the captured photo to the linked
  user's partner image so the profile updates too.
- Enlarge the capture oval (it was small): stage 62vh/520px, guide width 64%.

Live as 19.0.3.11.4. Also backfilled the existing test photo to the user's
partner image so the profile shows it without re-capturing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 18:29:46 -04:00

761 lines
26 KiB
SCSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// NFC Clock Kiosk — premium glass + animated mesh, always-dark.
//
// CRITICAL: All styles in this file are scoped under `:has(#nfc_kiosk_root)`
// to prevent leaking into other frontend pages. The previous version applied
// `html,body { overflow:hidden; height:100vh }` and `header,footer{display:none}`
// globally, which broke website scrolling and chrome on every frontend page.
//
// The single CSS custom property `--nfc-h` (hue, 0360) is set by JS after
// extracting the dominant color from the company logo. All colors interpolate
// from that hue via HSL, so the entire palette adapts to the customer brand.
// ─────────────────────────────────────────────────────────────────────
// Defaults (overridden by JS once logo dominant-hue is extracted)
// ─────────────────────────────────────────────────────────────────────
:root {
--nfc-h: 220; // fallback aurora-blue hue
--nfc-bg: #0b0d10;
--nfc-text: #ffffff;
--nfc-text-muted: #9ba3ad;
--nfc-success: #18a957;
--nfc-error: #d9374e;
}
// ─────────────────────────────────────────────────────────────────────
// Page-level styling — ONLY when the kiosk is on the page
// ─────────────────────────────────────────────────────────────────────
html:has(#nfc_kiosk_root) {
overflow: hidden;
height: 100%;
body {
overflow: hidden;
height: 100%;
margin: 0;
padding: 0;
background: var(--nfc-bg) !important;
color: var(--nfc-text);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
// Hide site chrome on the kiosk page only
.o_main_navbar, header, footer, .o_header_standard, .o_footer { display: none !important; }
}
// ─────────────────────────────────────────────────────────────────────
// Kiosk root container with animated mesh gradient background
// ─────────────────────────────────────────────────────────────────────
.nfc-kiosk {
position: fixed;
inset: 0;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
box-sizing: border-box;
user-select: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
overflow: hidden;
background: var(--nfc-bg);
// Animated mesh gradient (drifts behind everything)
&::before {
content: "";
position: absolute;
inset: -15%;
background:
radial-gradient(circle at 20% 30%, hsla(var(--nfc-h), 75%, 40%, 0.55) 0%, transparent 45%),
radial-gradient(circle at 80% 20%, hsla(calc(var(--nfc-h) + 40), 65%, 35%, 0.50) 0%, transparent 50%),
radial-gradient(circle at 70% 75%, hsla(calc(var(--nfc-h) - 25), 70%, 35%, 0.45) 0%, transparent 55%),
radial-gradient(circle at 15% 85%, hsla(calc(var(--nfc-h) + 80), 60%, 30%, 0.40) 0%, transparent 50%);
filter: blur(60px) saturate(140%);
animation: nfc-mesh-drift 28s ease-in-out infinite alternate;
z-index: 0;
will-change: transform;
}
// Subtle vignette on top so edges don't feel washed out
&::after {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(ellipse at center, transparent 50%, rgba(0,0,0,0.45) 100%);
z-index: 1;
pointer-events: none;
}
> * { position: relative; z-index: 2; }
}
@keyframes nfc-mesh-drift {
0% { transform: translate(0%, 0%) rotate(0deg) scale(1); }
50% { transform: translate(3%, -2%) rotate(2deg) scale(1.05); }
100% { transform: translate(-3%, 3%) rotate(-1deg) scale(0.98); }
}
// ─────────────────────────────────────────────────────────────────────
// Header chrome — logo, time, date, location, settings
// ─────────────────────────────────────────────────────────────────────
// Logo centered at the top, on a subtle frosted-glass pill — just enough lift
// to keep dark logos readable on the dark gradient.
.nfc-kiosk__logo {
position: absolute;
top: 1.25rem;
left: 50%;
transform: translateX(-50%);
max-height: 64px;
max-width: 260px;
object-fit: contain;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(24px) saturate(180%);
-webkit-backdrop-filter: blur(24px) saturate(180%);
padding: 0.6rem 1.1rem;
border-radius: 1rem;
border: 2px solid hsla(var(--nfc-h), 85%, 72%, 0.95);
box-shadow:
0 8px 28px rgba(0, 0, 0, 0.4),
0 0 30px hsla(var(--nfc-h), 90%, 62%, 0.6),
inset 0 1px 0 rgba(255, 255, 255, 0.7);
box-sizing: content-box;
animation: nfc-logo-in 1.2s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes nfc-logo-in {
from { opacity: 0; transform: translateX(-50%) translateY(-12px) scale(0.94); }
to { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
}
.nfc-kiosk__company {
position: absolute;
top: 5.75rem;
left: 1.5rem;
font-size: 0.85rem;
color: var(--nfc-text-muted);
letter-spacing: 0.05em;
text-transform: uppercase;
}
// When a logo is present, the company-name text is redundant — hide it.
.nfc-kiosk__logo ~ .nfc-kiosk__company { display: none; }
// Clock + date stack vertically below the logo, all centered.
.nfc-kiosk__time {
position: absolute;
top: 6.25rem; // sits below the logo (logo bottom ≈ 90px)
left: 50%;
transform: translateX(-50%);
font-size: 2.5rem;
font-weight: 300;
color: var(--nfc-text);
font-variant-numeric: tabular-nums;
letter-spacing: -0.02em;
text-shadow: 0 2px 12px rgba(0,0,0,0.4);
display: flex;
align-items: baseline;
gap: 0.45rem;
white-space: nowrap;
.ampm {
font-size: 1rem;
font-weight: 500;
color: var(--nfc-text-muted);
letter-spacing: 0.08em;
}
}
.nfc-kiosk__date {
position: absolute;
top: 9.75rem; // sits below the time
left: 50%;
transform: translateX(-50%);
font-size: 0.85rem;
color: var(--nfc-text-muted);
letter-spacing: 0.05em;
text-transform: uppercase;
}
.nfc-kiosk__location {
position: absolute;
bottom: 1.5rem;
left: 1.5rem;
font-size: 0.85rem;
color: var(--nfc-text-muted);
background: rgba(255,255,255,0.04);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255,255,255,0.08);
padding: 0.5rem 1rem;
border-radius: 999px;
}
.nfc-kiosk__lock {
position: absolute;
bottom: 1.5rem;
right: 4.85rem; // sits just left of the ⚙
width: 2.75rem;
height: 2.75rem;
border-radius: 50%;
background: rgba(255,255,255,0.04);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
color: var(--nfc-text-muted);
border: 1px solid rgba(255,255,255,0.08);
cursor: pointer;
font-size: 1.05rem;
display: none; // JS shows it only when unlocked
align-items: center;
justify-content: center;
z-index: 3;
}
// Unlock button sits in the primary bottom-right corner (shown only while locked)
#nfc_unlock_btn { right: 1.5rem; }
.nfc-kiosk__settings {
position: absolute;
bottom: 1.5rem;
right: 1.5rem;
width: 2.75rem;
height: 2.75rem;
border-radius: 50%;
background: rgba(255,255,255,0.04);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
color: var(--nfc-text-muted);
border: 1px solid rgba(255,255,255,0.08);
cursor: pointer;
font-size: 1.2rem;
display: flex;
align-items: center;
justify-content: center;
transition: color 200ms ease, border-color 200ms ease, transform 200ms ease;
&:hover, &:active {
color: var(--nfc-text);
border-color: rgba(255,255,255,0.2);
transform: scale(1.05);
}
}
// ─────────────────────────────────────────────────────────────────────
// Reusable glass panel
// ─────────────────────────────────────────────────────────────────────
%nfc-glass {
background: rgba(255,255,255,0.05);
backdrop-filter: blur(24px) saturate(160%);
-webkit-backdrop-filter: blur(24px) saturate(160%);
border: 1px solid rgba(255,255,255,0.1);
box-shadow:
0 20px 60px rgba(0,0,0,0.5),
inset 0 1px 0 rgba(255,255,255,0.08);
border-radius: 1.5rem;
}
// ─────────────────────────────────────────────────────────────────────
// State container — base fade-in for whatever child renders
// ─────────────────────────────────────────────────────────────────────
#nfc_state_container > * {
animation: nfc-state-in 200ms cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes nfc-state-in {
from { opacity: 0; transform: scale(0.96) translateY(10px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
// ─────────────────────────────────────────────────────────────────────
// IDLE — large NFC icon + prompt
// ─────────────────────────────────────────────────────────────────────
.nfc-kiosk__idle {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 2rem;
margin-top: 7rem; // push icon + prompt down so waves clear the clock/time
}
.nfc-kiosk__icon-svg {
width: 14rem;
height: 14rem;
overflow: visible; // let waves expand past viewBox without clipping
color: hsl(var(--nfc-h), 80%, 65%);
filter: drop-shadow(0 0 30px hsla(var(--nfc-h), 80%, 55%, 0.6));
.nfc-chip {
animation: nfc-chip-pulse 2.5s ease-in-out infinite;
transform-origin: center;
transform-box: fill-box;
}
.nfc-wave {
transform-origin: center;
transform-box: fill-box; // scale around the wave's own center, not viewBox origin
opacity: 0;
animation: nfc-wave-emit 2.5s ease-out infinite;
}
.nfc-wave-1 { animation-delay: 0s; }
.nfc-wave-2 { animation-delay: 0.6s; }
.nfc-wave-3 { animation-delay: 1.2s; }
}
@keyframes nfc-chip-pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
@keyframes nfc-wave-emit {
0% { transform: scale(0.6); opacity: 0; }
25% { opacity: 0.85; }
80% { opacity: 0.25; }
100% { transform: scale(1.35); opacity: 0; }
}
.nfc-kiosk__prompt {
font-size: 2.25rem;
font-weight: 300;
letter-spacing: -0.01em;
color: var(--nfc-text);
text-shadow: 0 2px 20px rgba(0,0,0,0.4);
}
// ─────────────────────────────────────────────────────────────────────
// PROCESSING — pulsing dots
// ─────────────────────────────────────────────────────────────────────
.nfc-kiosk__processing {
@extend %nfc-glass;
padding: 2.5rem 3.5rem;
text-align: center;
font-size: 1.5rem;
color: var(--nfc-text);
display: inline-flex;
align-items: center;
gap: 1rem;
.dots {
display: inline-flex;
gap: 0.4rem;
span {
width: 0.6rem;
height: 0.6rem;
border-radius: 50%;
background: hsl(var(--nfc-h), 80%, 65%);
animation: nfc-dot-bounce 1.2s ease-in-out infinite;
&:nth-child(2) { animation-delay: 0.15s; }
&:nth-child(3) { animation-delay: 0.3s; }
}
}
}
@keyframes nfc-dot-bounce {
0%, 80%, 100% { transform: scale(0.7); opacity: 0.5; }
40% { transform: scale(1.0); opacity: 1.0; }
}
// ─────────────────────────────────────────────────────────────────────
// RESULT — glass card with avatar + name + action
// ─────────────────────────────────────────────────────────────────────
.nfc-kiosk__result {
@extend %nfc-glass;
width: 80vw;
max-width: 720px;
padding: 2.5rem 3rem;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 1.25rem;
position: relative;
&--success {
border-color: rgba(24,169,87,0.55);
box-shadow:
0 20px 60px rgba(0,0,0,0.5),
0 0 80px rgba(24,169,87,0.35),
inset 0 1px 0 rgba(255,255,255,0.1);
animation: nfc-success-burst 350ms cubic-bezier(0.16, 1, 0.3, 1);
}
&--error {
border-color: rgba(217,55,78,0.55);
box-shadow:
0 20px 60px rgba(0,0,0,0.5),
0 0 60px rgba(217,55,78,0.3),
inset 0 1px 0 rgba(255,255,255,0.1);
animation: nfc-shake 350ms ease-in-out, nfc-state-in 200ms cubic-bezier(0.16, 1, 0.3, 1);
}
}
@keyframes nfc-success-burst {
0% {
opacity: 0;
transform: scale(0.88);
box-shadow:
0 20px 60px rgba(0,0,0,0.5),
0 0 0 rgba(24,169,87,0),
inset 0 1px 0 rgba(255,255,255,0.1);
}
50% {
box-shadow:
0 20px 60px rgba(0,0,0,0.5),
0 0 140px rgba(24,169,87,0.7),
inset 0 1px 0 rgba(255,255,255,0.1);
}
100% {
opacity: 1;
transform: scale(1);
box-shadow:
0 20px 60px rgba(0,0,0,0.5),
0 0 80px rgba(24,169,87,0.35),
inset 0 1px 0 rgba(255,255,255,0.1);
}
}
@keyframes nfc-shake {
0%, 100% { transform: translateX(0); }
20% { transform: translateX(-10px); }
40% { transform: translateX(10px); }
60% { transform: translateX(-6px); }
80% { transform: translateX(6px); }
}
.nfc-kiosk__avatar {
width: 7rem;
height: 7rem;
border-radius: 50%;
background-size: cover;
background-position: center;
background-color: rgba(255,255,255,0.15);
flex-shrink: 0;
border: 2px solid rgba(255,255,255,0.2);
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
animation: nfc-avatar-in 300ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
@keyframes nfc-avatar-in {
from { opacity: 0; transform: scale(0.4); }
to { opacity: 1; transform: scale(1); }
}
.nfc-kiosk__result-text {
text-align: center;
.name { font-size: 2.25rem; font-weight: 600; letter-spacing: -0.02em; }
.action { font-size: 1.5rem; margin-top: 0.5rem; opacity: 0.95; font-weight: 400; }
.hours { font-size: 1.35rem; opacity: 0.9; margin-top: 0.6rem; font-weight: 500; }
}
// ─────────────────────────────────────────────────────────────────────
// SETUP wizard
// ─────────────────────────────────────────────────────────────────────
.nfc-kiosk__setup {
@extend %nfc-glass;
text-align: center;
max-width: 600px;
padding: 3.5rem 3rem;
h2 { font-size: 2rem; margin-bottom: 1rem; font-weight: 300; letter-spacing: -0.01em; }
p { color: var(--nfc-text-muted); margin-bottom: 2rem; line-height: 1.5; }
button {
font-size: 1.25rem;
padding: 1rem 2.5rem;
background: hsl(var(--nfc-h), 80%, 55%);
color: white;
border: none;
border-radius: 999px;
cursor: pointer;
font-weight: 500;
box-shadow: 0 8px 32px hsla(var(--nfc-h), 80%, 50%, 0.4);
transition: transform 200ms ease, box-shadow 200ms ease;
&:hover, &:active {
transform: translateY(-2px);
box-shadow: 0 12px 36px hsla(var(--nfc-h), 80%, 50%, 0.5);
}
}
}
// ─────────────────────────────────────────────────────────────────────
// ENROLL Mode overlay — glass panel with numpad / search / tap-prompt
// ─────────────────────────────────────────────────────────────────────
.nfc-kiosk__enroll-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.7);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
animation: nfc-overlay-in 250ms ease-out;
}
@keyframes nfc-overlay-in {
from { opacity: 0; }
to { opacity: 1; }
}
.nfc-kiosk__enroll-panel {
@extend %nfc-glass;
padding: 2rem;
width: 80vw;
max-width: 720px;
max-height: 92vh;
overflow-y: auto;
// Compact PIN-pad variant — narrower than the wide list panels, but a
// definite width so the centred flex overlay doesn't collapse it.
// (Don't use CSS min() here — Odoo's Sass compiler evaluates the built-in
// min() and errors on mixed vw/px units; width+max-width is equivalent.)
&--pin { width: 86vw; max-width: 380px; padding: 1.75rem 1.75rem 1.5rem; }
h2 {
font-size: 1.5rem;
margin: 0 0 1.5rem;
font-weight: 400;
color: var(--nfc-text);
}
.numpad {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.6rem;
margin: 0.5rem 0 0.75rem;
button {
font-size: 1.6rem;
min-height: 60px;
padding: 0.4rem 0;
background: rgba(255,255,255,0.05);
color: var(--nfc-text);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 0.75rem;
cursor: pointer;
transition: background 150ms ease, transform 100ms ease;
font-weight: 300;
&:hover { background: rgba(255,255,255,0.1); }
&:active { background: rgba(255,255,255,0.15); transform: scale(0.96); }
}
}
.pin-display {
font-size: 2.2rem;
letter-spacing: 0.5rem;
text-align: center;
margin: 0.75rem 0 1rem;
font-variant-numeric: tabular-nums;
min-height: 2.6rem;
color: hsl(var(--nfc-h), 80%, 70%);
}
.employee-search {
width: 100%;
padding: 1rem 1.25rem;
font-size: 1.15rem;
background: rgba(255,255,255,0.05);
color: var(--nfc-text);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 0.75rem;
margin-bottom: 1rem;
box-sizing: border-box;
outline: none;
&:focus { border-color: hsl(var(--nfc-h), 80%, 55%); }
&::placeholder { color: var(--nfc-text-muted); }
}
.employee-list {
max-height: 40vh;
overflow-y: auto;
.employee-row {
padding: 0.85rem 1rem;
border-bottom: 1px solid rgba(255,255,255,0.05);
cursor: pointer;
font-size: 1.05rem;
border-radius: 0.5rem;
transition: background 150ms ease;
&:hover, &:active { background: rgba(255,255,255,0.06); }
}
}
.actions {
display: flex;
gap: 1rem;
justify-content: flex-end;
margin-top: 1.5rem;
button {
font-size: 1rem;
padding: 0.85rem 1.75rem;
border-radius: 999px;
cursor: pointer;
border: none;
font-weight: 500;
transition: transform 150ms ease, opacity 150ms ease;
&:hover, &:active { transform: scale(0.97); }
}
.cancel { background: rgba(255,255,255,0.08); color: var(--nfc-text); }
.confirm {
background: hsl(var(--nfc-h), 80%, 55%);
color: white;
box-shadow: 0 4px 20px hsla(var(--nfc-h), 80%, 50%, 0.4);
}
}
}
// Amber accent for the "unknown card → program it" prompt + the inline
// status line in the new-employee form.
.nfc-kiosk__enroll-panel.nfc-kiosk__unknown {
border-color: rgba(224, 168, 62, 0.55);
box-shadow:
0 20px 60px rgba(0,0,0,0.5),
0 0 60px rgba(224, 168, 62, 0.28),
inset 0 1px 0 rgba(255,255,255,0.08);
.unknown-icon { font-size: 3.5rem; line-height: 1; margin-bottom: 0.5rem; color: #e0a83e; }
h2 { color: #e0a83e; }
}
.nfc-kiosk__enroll-panel .enroll-msg {
min-height: 1.4rem;
margin: 0.25rem 0 0.5rem;
color: var(--nfc-error);
font-size: 0.95rem;
text-align: center;
}
// Manager page rows (employee + tag status + per-row actions)
.nfc-kiosk__enroll-panel .manager-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 0.7rem 0.4rem;
border-bottom: 1px solid rgba(255,255,255,0.06);
flex-wrap: wrap;
.m-info { display: flex; align-items: baseline; gap: 0.5rem; flex: 1; min-width: 12rem; }
.m-name { font-size: 1.05rem; }
.m-dept { color: var(--nfc-text-muted); font-size: 0.8rem; }
.m-tag { font-size: 0.78rem; color: var(--nfc-text-muted); white-space: nowrap; }
.m-tag--on { color: hsl(var(--nfc-h), 70%, 66%); }
.m-actions { display: flex; gap: 0.4rem; flex-shrink: 0; flex-wrap: wrap; }
.m-btn {
font-size: 0.85rem;
padding: 0.45rem 0.85rem;
border-radius: 999px;
background: rgba(255,255,255,0.06);
color: var(--nfc-text);
border: 1px solid rgba(255,255,255,0.1);
cursor: pointer;
&.m-danger { color: #ff8b9a; border-color: rgba(217,55,78,0.45); }
&:active { transform: scale(0.96); }
}
}
// ─────────────────────────────────────────────────────────────────────
// Guided profile-photo capture — live camera with an oval face guide
// ─────────────────────────────────────────────────────────────────────
.nfc-kiosk__photo-panel {
@extend %nfc-glass;
padding: 1.5rem;
width: 80vw;
max-width: 540px;
max-height: 92vh;
overflow-y: auto;
text-align: center;
h2 { font-size: 1.4rem; margin: 0 0 1rem; font-weight: 400; }
}
.nfc-photo-stage {
position: relative;
aspect-ratio: 3 / 4; // portrait — width follows the (height-driven) box
height: 62vh;
max-height: 520px;
max-width: 100%;
margin: 0 auto;
border-radius: 1rem;
overflow: hidden;
background: #000;
}
.nfc-photo-video,
.nfc-photo-preview {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.nfc-photo-video { transform: scaleX(-1); } // mirror for a natural selfie view
.nfc-photo-guide {
position: absolute;
top: 47%;
left: 50%;
width: 64%;
aspect-ratio: 3 / 4; // VERTICAL oval — a face is taller than it is wide
transform: translate(-50%, -50%);
border: 3px dashed rgba(255, 255, 255, 0.92);
border-radius: 50%;
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5); // dim everything outside the oval
pointer-events: none;
}
.nfc-photo-countdown {
position: absolute;
top: 47%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 6rem;
font-weight: 200;
line-height: 1;
color: #fff;
text-shadow: 0 2px 24px rgba(0, 0, 0, 0.85);
pointer-events: none;
opacity: 0;
&.is-active { opacity: 1; animation: nfc-cd-pop 1s ease-out infinite; }
}
@keyframes nfc-cd-pop {
0% { transform: translate(-50%, -50%) scale(1.25); opacity: 0.35; }
40% { opacity: 1; }
100% { transform: translate(-50%, -50%) scale(0.9); opacity: 0.7; }
}
.nfc-photo-hint {
position: absolute;
left: 0;
right: 0;
bottom: 0.75rem;
color: #fff;
font-size: 0.95rem;
text-shadow: 0 1px 5px rgba(0, 0, 0, 0.9);
pointer-events: none;
}
// ─────────────────────────────────────────────────────────────────────
// Reduced-motion fallback — respect users who prefer no animation
// ─────────────────────────────────────────────────────────────────────
@media (prefers-reduced-motion: reduce) {
.nfc-kiosk::before { animation: none; }
.nfc-kiosk__icon-svg .nfc-wave,
.nfc-kiosk__icon-svg .nfc-chip,
#nfc_state_container > *,
.nfc-kiosk__logo,
.nfc-kiosk__result--success,
.nfc-kiosk__result--error,
.nfc-kiosk__avatar { animation: none; }
}