/* ═══════════════════════════════════════════════════════════
   Scanner sheet — fullscreen camera takeover (Swish-model)

   One camera surface for barcode/QR scanning, reused anywhere a product
   needs "point the camera at something": ScanEasy's IN/OUT scan + order
   picking, and any future LogEasy/ColibriAuth capture flow. Owns the whole
   screen while open; closes itself the instant it has a result.

   Page shape (video + chrome layered over it, sheet is the positioning
   context):
     <div class="tn-scanner-sheet">
       <video class="tn-scanner-video" autoplay playsinline muted></video>
       <div class="tn-scanner-topbar">
         <button class="tn-scanner-btn tn-scanner-close">✕</button>
         <div class="tn-scanner-title">Skanna artikel</div>
         <button class="tn-scanner-btn tn-scanner-torch is-active">⚡</button>
       </div>
       <div class="tn-scanner-stage">
         <div class="tn-scanner-reticle tn-scanner-reticle--hunting">
           <span class="tn-scanner-reticle-corner tn-scanner-reticle-corner--tl"></span>
           <span class="tn-scanner-reticle-corner tn-scanner-reticle-corner--tr"></span>
           <span class="tn-scanner-reticle-corner tn-scanner-reticle-corner--bl"></span>
           <span class="tn-scanner-reticle-corner tn-scanner-reticle-corner--br"></span>
           <span class="tn-scanner-scanline"></span>
         </div>
         <p class="tn-scanner-hint">Rikta mot streckkoden</p>
         <p class="tn-scanner-value">7 350156 480012</p>
       </div>
       <div class="tn-scanner-fail">
         <p class="tn-scanner-fail-msg">Ingen kameraatkomst</p>
         <button class="tn-btn tn-btn--primary">Forsok igen</button>
         <button class="tn-btn tn-btn--ghost">Skriv in koden manuellt</button>
       </div>
     </div>

   Reticle state is a modifier on .tn-scanner-reticle — swap it, nothing
   else, when the JS decode loop changes state:
     --starting   dimmed, spinner (acquiring the camera)
     --hunting    neutral, scanline pulses (default "live" state)
     --hit        green flash (decoded + accepted)
     --rejected   red, shake (decoded but format/lookup rejected it)

   Uses 100dvh (not 100vh) so Safari's collapsing URL bar can't overflow
   the sheet, and env(safe-area-inset-*) so the close button and the hint
   text clear the notch / home indicator on iOS.

   Body scroll lock (apply/remove on <body> while the sheet is open):
     <body class="tn-scroll-lock">
   ═══════════════════════════════════════════════════════════ */

.tn-scanner-sheet {
    position: fixed;
    inset: 0;
    height: 100dvh;
    z-index: var(--tn-z-scanner);
    display: flex;
    flex-direction: column;
    background: #0A0B0D;
    overflow: hidden;
    animation: tnScannerFadeIn 0.15s ease;
}

@keyframes tnScannerFadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* Locks page scroll behind the sheet while it's open. */
body.tn-scroll-lock {
    overflow: hidden;
}

.tn-scanner-video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    background: #000;
}

/* ── Top bar — close / title / torch. Floats over the video on a soft
   scrim so the controls stay legible over any footage, and clears the
   iOS notch / status bar via safe-area padding. ── */
.tn-scanner-topbar {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--tn-space-md);
    flex-shrink: 0;
    padding: calc(var(--tn-space-md) + env(safe-area-inset-top)) calc(var(--tn-space-md) + env(safe-area-inset-right)) var(--tn-space-lg) calc(var(--tn-space-md) + env(safe-area-inset-left));
    background: linear-gradient(to bottom, rgba(0, 0, 0, 0.55) 0%, rgba(0, 0, 0, 0) 100%);
}

.tn-scanner-title {
    flex: 1;
    text-align: center;
    color: #fff;
    font-size: var(--tn-font-size-base);
    font-weight: 600;
}

.tn-scanner-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: var(--tn-touch-target);
    height: var(--tn-touch-target);
    border: none;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.16);
    color: #fff;
    cursor: pointer;
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    transition: background var(--tn-transition);
}

.tn-scanner-btn:hover {
    background: rgba(255, 255, 255, 0.26);
}

.tn-scanner-btn.is-active {
    background: var(--tn-accent);
}

/* ── Stage — reticle + hint + decoded value, centered over the video. ── */
.tn-scanner-stage {
    position: relative;
    z-index: 1;
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--tn-space-lg);
    padding: 0 var(--tn-space-lg) calc(var(--tn-space-xl) + env(safe-area-inset-bottom));
    pointer-events: none;
    /* Scrim behind the reticle/hint/value stack — mirrors .tn-scanner-topbar's
       gradient. Without this, a bright subject (a warehouse's normal case:
       white shipping labels, pale boxes, a lit floor — not a dim edge case)
       washes out every light-on-video element: white reticle corners vanish
       on white, the hint text and scanline disappear. Purely a legibility
       overlay — it does not touch the underlying <video> pixels the decode
       loop reads, so it cannot affect scan accuracy. */
    background: radial-gradient(ellipse 62% 55% at 50% 50%, rgba(0, 0, 0, 0.38) 0%, rgba(0, 0, 0, 0.16) 55%, rgba(0, 0, 0, 0) 80%);
}

.tn-scanner-reticle {
    position: relative;
    width: min(72vw, 280px);
    aspect-ratio: 1;
    --tn-scanner-reticle-color: rgba(255, 255, 255, 0.85);
    transition: opacity var(--tn-transition-slow);
}

.tn-scanner-reticle-corner {
    position: absolute;
    width: 28px;
    height: 28px;
    border: 3px solid var(--tn-scanner-reticle-color);
    /* Dark halo so a corner stays legible even against a bright subject
       (shipping labels, pale boxes) right at the scrim's faded edge. */
    filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.65));
    transition: border-color var(--tn-transition), opacity var(--tn-transition);
}

.tn-scanner-reticle-corner--tl { top: 0; left: 0; border-right: none; border-bottom: none; border-top-left-radius: var(--tn-radius); }
.tn-scanner-reticle-corner--tr { top: 0; right: 0; border-left: none; border-bottom: none; border-top-right-radius: var(--tn-radius); }
.tn-scanner-reticle-corner--bl { bottom: 0; left: 0; border-right: none; border-top: none; border-bottom-left-radius: var(--tn-radius); }
.tn-scanner-reticle-corner--br { bottom: 0; right: 0; border-left: none; border-top: none; border-bottom-right-radius: var(--tn-radius); }

.tn-scanner-scanline {
    position: absolute;
    left: 6%;
    right: 6%;
    top: 0;
    height: 2px;
    background: var(--tn-scanner-reticle-color);
    box-shadow: 0 0 8px 1px var(--tn-scanner-reticle-color), 0 1px 3px rgba(0, 0, 0, 0.55);
    opacity: 0;
}

/* Hunting — neutral, scanline sweeps top to bottom on a loop. */
.tn-scanner-reticle--hunting .tn-scanner-scanline {
    opacity: 0.9;
    animation: tnScannerSweep 1.8s ease-in-out infinite;
}

@keyframes tnScannerSweep {
    0%   { top: 4%; opacity: 0; }
    10%  { opacity: 0.9; }
    90%  { opacity: 0.9; }
    100% { top: 94%; opacity: 0; }
}

/* Starting — dimmed, corners only, spinner takes the centre. */
.tn-scanner-reticle--starting {
    opacity: 0.5;
}

.tn-scanner-reticle--starting::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 32px;
    height: 32px;
    margin: -16px 0 0 -16px;
    border: 3px solid rgba(255, 255, 255, 0.25);
    border-top-color: #fff;
    border-radius: 50%;
    animation: tnScannerSpin 0.8s linear infinite;
}

@keyframes tnScannerSpin {
    to { transform: rotate(360deg); }
}

/* Hit — snaps green, one short flash. */
.tn-scanner-reticle--hit {
    --tn-scanner-reticle-color: var(--tn-success);
    animation: tnScannerFlash 0.3s ease;
}

@keyframes tnScannerFlash {
    0%   { transform: scale(1); }
    35%  { transform: scale(1.06); }
    100% { transform: scale(1); }
}

/* Rejected — snaps red, short shake. */
.tn-scanner-reticle--rejected {
    --tn-scanner-reticle-color: var(--tn-danger);
    animation: tnScannerShake 0.3s ease;
}

@keyframes tnScannerShake {
    0%, 100% { transform: translateX(0); }
    25%      { transform: translateX(-6px); }
    75%      { transform: translateX(6px); }
}

.tn-scanner-hint {
    margin: 0;
    max-width: 320px;
    text-align: center;
    color: var(--tn-text-on-dark-bright);
    font-size: var(--tn-font-size-base);
    /* Guaranteed legibility against a bright subject even where the stage
       scrim has faded — the scrim alone isn't enough at its own edges. */
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7), 0 0 8px rgba(0, 0, 0, 0.45);
}

.tn-scanner-reticle--starting ~ .tn-scanner-hint { color: var(--tn-text-on-dark); }
.tn-scanner-reticle--hit ~ .tn-scanner-hint       { color: #fff; font-weight: 600; }
.tn-scanner-reticle--rejected ~ .tn-scanner-hint  { color: #FFD9D9; font-weight: 600; }

/* Decoded value — shown briefly on a hit so the user sees *what* was
   scanned, not just that something was. Hidden unless the sheet fills it. */
.tn-scanner-value {
    margin: 0;
    padding: 6px 14px;
    border-radius: var(--tn-radius);
    background: rgba(0, 0, 0, 0.45);
    color: #fff;
    font-family: var(--tn-font-mono);
    font-size: var(--tn-font-size-sm);
    letter-spacing: 0.02em;
}

.tn-scanner-value:empty {
    display: none;
}

/* ── Failed state — permission denied / no camera. Replaces the video
   content with a full-surface message; never a dead end. ── */
.tn-scanner-fail {
    display: none;
    position: absolute;
    inset: 0;
    z-index: 2;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--tn-space-md);
    padding: var(--tn-space-xl);
    text-align: center;
    background: var(--tn-primary);
}

.tn-scanner-sheet.is-failed .tn-scanner-fail {
    display: flex;
}

.tn-scanner-sheet.is-failed .tn-scanner-video,
.tn-scanner-sheet.is-failed .tn-scanner-stage {
    display: none;
}

.tn-scanner-fail-icon {
    color: var(--tn-text-on-dark);
    font-size: 2rem;
}

.tn-scanner-fail-msg {
    margin: 0;
    max-width: 320px;
    color: var(--tn-text-on-dark-bright);
    font-size: var(--tn-font-size-lg);
    font-weight: 600;
}

/* ── Mobile refinement — slightly smaller reticle on short viewports so
   the hint + value never crowd the bottom safe area. ── */
@media (max-height: 640px) {
    .tn-scanner-reticle {
        width: min(64vw, 220px);
    }

    .tn-scanner-stage {
        gap: var(--tn-space-md);
    }
}
