// 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, 0–360) 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; } }