/* EventKH-style UI — mirrors event.gov.kh (Work Sans · navy #1C4178 · clean white gov-SaaS) */
:root{
  --navy:#1c4178; --navy-600:#173a6b; --navy-700:#122e54; --navy-50:#eef3fb; --navy-100:#dce7f5;
  --ink:#0b1220; --head:#020817; --text:#334155; --muted:#64748b; --faint:#94a3b8;
  --line:#e5eaf1; --line-2:#cfd8e3; --bg:#f4f7fb; --card:#ffffff;
  --accent:#e8552b; --accent-50:#fdece4;
  --good:#15803d; --good-bg:#dcfce7; --warn:#b45309; --warn-bg:#fef3c7;
  --danger:#b91c1c; --danger-bg:#fee2e2; --purple:#7c3aed; --purple-bg:#f0eafe;
  --shadow:0 1px 2px rgba(2,8,23,.05), 0 1px 3px rgba(2,8,23,.06);
  --shadow-md:0 4px 14px rgba(2,8,23,.07); --shadow-lg:0 18px 40px rgba(2,8,23,.10);
  --r:14px; --r-sm:9px;
}
*{box-sizing:border-box;margin:0;padding:0}
body{
  font-family:"Work Sans","Kantumruy Pro",ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  background:var(--bg);color:var(--text);line-height:1.55;-webkit-font-smoothing:antialiased;
  min-height:100vh;min-height:100dvh;display:flex;flex-direction:column;   /* sticky footer: content grows, footer sits at the bottom */
}
a{color:var(--navy);text-decoration:none}
a:hover{text-decoration:underline}

/* ---------- header ---------- */
.top{
  display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;
  padding:12px clamp(16px,4vw,44px);background:#fff;border-bottom:1px solid var(--line);
  position:sticky;top:0;z-index:20;box-shadow:var(--shadow)
}
.top::before{content:"";position:absolute;left:0;right:0;top:0;height:3px;
  background:linear-gradient(90deg,var(--navy),var(--accent))}       /* signature band */
.brand{display:flex;align-items:center;gap:12px;flex-shrink:0}
.brand img{height:30px;width:auto;display:block}
.brand span{font-size:11px;font-weight:600;color:var(--muted);letter-spacing:.04em;
  border-left:1px solid var(--line-2);padding-left:12px}

/* segmented main nav — icon + label pills with a clear active state */
.mainnav{display:flex;gap:3px;align-items:center;background:var(--navy-50);padding:4px;border-radius:12px}
.navlink{display:inline-flex;align-items:center;gap:7px;height:36px;padding:0 14px;border-radius:9px;
  font-size:14px;font-weight:600;color:var(--muted);transition:.15s;white-space:nowrap}
.navlink svg{width:16px;height:16px;opacity:.9;flex-shrink:0}
.navlink:hover{background:#fff;color:var(--navy);text-decoration:none;box-shadow:var(--shadow)}
.navlink.on{background:var(--navy);color:#fff;box-shadow:var(--shadow-md)}
.navlink.on:hover{background:var(--navy-600);color:#fff}

/* right cluster — language toggle + identity chip */
.topright{display:flex;align-items:center;gap:12px;flex-shrink:0}
.userchip{display:flex;align-items:center;gap:9px}
.avatar{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:50%;
  font-size:12.5px;font-weight:700;color:#fff;background:var(--navy);letter-spacing:.02em;flex-shrink:0;
  box-shadow:0 0 0 2px #fff,0 0 0 4px var(--navy-100)}
.avatar img{width:100%;height:100%;border-radius:50%;object-fit:cover;display:block}   /* enrolled face as profile pic */
.avatar.role-admin{background:var(--purple);box-shadow:0 0 0 2px #fff,0 0 0 4px var(--purple-bg)}
.avatar.role-user{background:var(--faint);box-shadow:0 0 0 2px #fff,0 0 0 4px #e5eaf1}
.uinfo{display:flex;flex-direction:column;line-height:1.12}
.uinfo b{font-size:13px;color:var(--ink);font-weight:600}
.uinfo small{font-size:11px;color:var(--muted);text-transform:capitalize}
.logout{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:9px;
  color:var(--muted);border:1px solid var(--line);transition:.15s}
.logout svg{width:17px;height:17px}
.logout:hover{background:var(--danger-bg);color:var(--danger);border-color:transparent;text-decoration:none}
.topright a.btn{height:36px;padding:0 16px}

@media(max-width:760px){ .uinfo{display:none} .brand span{display:none} }
@media(max-width:560px){ .navlink .lbl{display:none} .navlink{padding:0 11px} }

main{max-width:1080px;margin:0 auto;padding:clamp(22px,4vw,40px);width:100%;flex:1 0 auto}   /* grows to push footer down */
main:has(.checkin-layout){max-width:1180px}   /* two-column check-in needs more room than a normal page */
footer{background:var(--navy-700);color:#c3d2e8;font-size:13px;padding:28px 20px;text-align:center;margin-top:40px;
  border-top:3px solid var(--accent);flex-shrink:0}   /* normal flow, at the very bottom — scroll to reveal, never floats */
footer img{height:28px;opacity:.9;margin-bottom:10px}
footer a{color:#dbe6f5;text-decoration:underline}

/* ---------- type ---------- */
h1{font-size:clamp(26px,4vw,34px);font-weight:800;letter-spacing:-.02em;color:var(--head);line-height:1.15;margin-bottom:6px}
h2{font-size:19px;font-weight:700;color:var(--head);letter-spacing:-.01em;margin-bottom:12px}
h3{font-weight:600;color:var(--head)}
/* role eyebrow hidden app-wide to save vertical space */
.kicker{display:none}
.muted{color:var(--muted)}
p{color:var(--text)}
.note{font-size:13px;color:var(--muted);margin-top:6px}
.err{color:var(--danger);font-size:13.5px;margin-top:10px;min-height:18px}

/* ---------- cards / layout ---------- */
.card{background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:24px;margin-bottom:18px;box-shadow:var(--shadow)}
.grid{display:grid;gap:18px}
@media(min-width:760px){.g2{grid-template-columns:1fr 1fr}}
@media(min-width:820px){.g3{grid-template-columns:repeat(3,1fr)}}

/* ---------- dashboard charts (dependency-free) ---------- */
.charts{margin-bottom:18px}
.chart-card{margin:0;display:flex;flex-direction:column}
.chart-card h3{font-size:12.5px;font-weight:700;color:var(--muted);letter-spacing:.03em;text-transform:uppercase;margin-bottom:16px}
.bars{display:flex;flex-direction:column;gap:11px}
.bar-row{display:grid;grid-template-columns:60px 1fr 26px;align-items:center;gap:10px;font-size:12.5px}
.bar-lbl{color:var(--text);text-transform:capitalize;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bar-track{height:8px;border-radius:999px;background:var(--navy-50);overflow:hidden}
.bar-track i{display:block;height:100%;border-radius:999px;min-width:2px;transition:width .5s ease}
.bar-val{font-weight:700;color:var(--ink);text-align:right;font-variant-numeric:tabular-nums}
.donut-wrap{display:flex;align-items:center;gap:16px;flex-wrap:wrap}
.donut{width:112px;height:112px;flex-shrink:0}
.donut-num{font-size:22px;font-weight:800;fill:var(--head)}
.donut-lbl{font-size:8px;fill:var(--muted);letter-spacing:.08em;text-transform:uppercase}
.legend{display:flex;flex-direction:column;gap:7px;font-size:12.5px;color:var(--text)}
.legend span{display:inline-flex;align-items:center;gap:7px}
.legend i{width:10px;height:10px;border-radius:3px;flex-shrink:0}
.legend b{color:var(--ink);margin-left:2px;font-variant-numeric:tabular-nums}
/* owner dashboard failures list */
.failrows{display:flex;flex-direction:column}
.failrow{display:flex;justify-content:space-between;align-items:center;padding:9px 0;border-top:1px solid var(--line);font-size:13.5px;color:var(--text)}
.failrow:first-child{border-top:none}
.failrow b{color:var(--ink);font-variant-numeric:tabular-nums}

/* ---------- forms ---------- */
label{display:block;font-size:13px;font-weight:600;color:var(--ink);margin:14px 0 6px}
input[type=text],input[type=email],input[type=tel],input[type=password]{
  width:100%;padding:11px 14px;border:1px solid var(--line-2);border-radius:var(--r-sm);background:#fff;
  font:inherit;font-size:15px;color:var(--ink);transition:.15s}
input:focus{outline:none;border-color:var(--navy);box-shadow:0 0 0 3px var(--navy-50)}
.check{display:flex;gap:10px;align-items:flex-start;margin-top:16px;font-size:13.5px;color:var(--text)}
.check input{margin-top:3px;accent-color:var(--navy)}

/* ---------- buttons ---------- */
.btn{display:inline-flex;align-items:center;gap:8px;justify-content:center;vertical-align:middle;
  padding:10px 18px;border-radius:var(--r-sm);border:1px solid var(--navy);background:var(--navy);color:#fff;
  font:inherit;font-weight:600;font-size:14px;line-height:1.2;cursor:pointer;transition:.15s;box-shadow:var(--shadow)}
.btn:hover{background:var(--navy-600);border-color:var(--navy-600);text-decoration:none;box-shadow:var(--shadow-md)}
.btn:disabled{opacity:.55;cursor:not-allowed;box-shadow:none}
.btn.ghost{background:#fff;color:var(--ink);border-color:var(--line-2);box-shadow:none}
.btn.ghost:hover{background:var(--navy-50);border-color:var(--navy);color:var(--navy)}
.btn.wide{width:100%}
.btn.accent{background:var(--accent);border-color:var(--accent)}
.btn.accent:hover{background:#d0491f;border-color:#d0491f}
/* one canonical compact size for inline / table-row actions (replaces ad-hoc inline paddings) */
.btn.sm{padding:7px 13px;font-size:13px;gap:6px;box-shadow:none}
.btn.sm:hover{box-shadow:var(--shadow)}
/* square icon-only button (compact table actions; use title="" for a tooltip) */
.btn.ico{padding:0;width:32px;height:32px;justify-content:center}
.btn.ico svg{width:16px;height:16px;flex-shrink:0}
/* login page demo credentials: read as a left-aligned list, not centered buttons */
.btn.demo{justify-content:flex-start;font-weight:500;font-size:13px}

/* ---------- pills / badges ---------- */
.pill{display:inline-flex;align-items:center;vertical-align:middle;font-size:11.5px;font-weight:600;letter-spacing:.02em;
  padding:4px 11px;border-radius:999px;background:#eef1f6;color:var(--muted);white-space:nowrap}
.pill.ok,.pill.published{color:var(--good);background:var(--good-bg)}
.pill.pending,.pill.qr{color:var(--warn);background:var(--warn-bg)}
.pill.rejected,.pill.live{color:var(--danger);background:var(--danger-bg)}
.pill.draft{color:var(--muted);background:#eef1f6}
.pill.face{color:var(--navy);background:var(--navy-50)}
.pill.unverified{color:var(--warn);background:var(--warn-bg)}
.pill.verified{color:var(--good);background:var(--good-bg)}
.pill.role-admin{color:var(--purple);background:var(--purple-bg)}
.pill.role-guest{color:var(--warn);background:var(--warn-bg)}
.pill.role-owner{color:var(--navy);background:var(--navy-50)}
.pill.role-user{color:var(--muted);background:#eef1f6}

/* ---------- camera / scanner ---------- */
.cam{position:relative;width:100%;max-width:560px;margin:0 auto;aspect-ratio:4/3;background:#0b1220;
  border-radius:var(--r);overflow:hidden;border:1px solid var(--line-2)}
.cam video,.cam canvas{width:100%;height:100%;object-fit:cover;display:block}
/* mirror the live selfie camera so aligning feels like a mirror. The overlay is NOT
   CSS-mirrored (that would reverse the label text) — its boxes are flipped in drawFrame. */
.cam video{transform:translateY(calc(var(--pan-y,0) * 100%)) scale(var(--cam-zoom,1)) scaleX(-1);transform-origin:center}
.cfg-val{color:var(--muted);font-weight:400;font-size:12px;float:right}
/* dim + blur everything OUTSIDE the oval so the framed face stands out (~40% darker) */
.cam.oval::before{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  -webkit-backdrop-filter:blur(var(--blur-px,2px)) brightness(var(--dim-b,.74));backdrop-filter:blur(var(--blur-px,2px)) brightness(var(--dim-b,.74));
  -webkit-mask:radial-gradient(ellipse calc(15.3% * var(--oval-scale,1)) calc(28.1% * var(--oval-scale,1)) at 50% 48.6%,transparent 97%,#000 100%);
  mask:radial-gradient(ellipse calc(15.3% * var(--oval-scale,1)) calc(28.1% * var(--oval-scale,1)) at 50% 48.6%,transparent 97%,#000 100%)}
/* enrollment framing guide (controlled capture) — align your head + ears in the outline.
   --oval-scale (overlay zoom) resizes the guide about its centre. */
.cam.oval::after{content:"";position:absolute;inset:5% 16%;pointer-events:none;z-index:1;
  transform:scale(var(--oval-scale,1));transform-origin:center;
  background:center/contain no-repeat url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 128'><g fill='none' stroke='%23ffffff' stroke-width='1.4' stroke-dasharray='5 5' stroke-linejoin='round'><path d='M21 52 q-9 3 -9 12 q0 9 9 11'/><path d='M79 52 q9 3 9 12 q0 9 -9 11'/><ellipse cx='50' cy='62' rx='29' ry='40'/></g></svg>");transition:filter .12s,transform .12s}
/* eye-aligned → solid green oval (locked) · rejected result → solid red */
.cam.oval.aligned::after{background-image:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 128'><g fill='none' stroke='%2315803d' stroke-width='1.8' stroke-linejoin='round'><path d='M21 52 q-9 3 -9 12 q0 9 9 11'/><path d='M79 52 q9 3 9 12 q0 9 -9 11'/><ellipse cx='50' cy='62' rx='29' ry='40'/></g></svg>");filter:drop-shadow(0 0 6px rgba(21,128,61,.55))}
.cam.oval.reject::after{background-image:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 128'><g fill='none' stroke='%23b91c1c' stroke-width='1.8' stroke-linejoin='round'><path d='M21 52 q-9 3 -9 12 q0 9 9 11'/><path d='M79 52 q9 3 9 12 q0 9 -9 11'/><ellipse cx='50' cy='62' rx='29' ry='40'/></g></svg>")}
/* AUTO (face+qr): on load show the plain stream — NO oval/blur until a face qualifies
   (.face-focus) or a QR is detected (.qr-focus). Plain Face mode keeps the fixed oval. */
.cam.oval.auto:not(.face-focus):not(.qr-focus)::before,
.cam.oval.auto:not(.face-focus):not(.qr-focus)::after{content:none}
/* AUTO (face+qr): the oval FOLLOWS the face — blur ellipse + outline move to the detected head
   box (vars in %), instead of the fixed centre oval. Plain Face mode keeps the fixed oval. */
/* OVAL shape: blur outside an ellipse that tracks the head box */
.cam.oval.face-focus:not(.shape-box)::before{
  -webkit-mask:radial-gradient(ellipse calc(var(--fw,30%)/2) calc(var(--fh,40%)/2) at calc(var(--fx,35%) + var(--fw,30%)/2) calc(var(--fy,20%) + var(--fh,40%)/2),transparent 96%,#000 100%);
  mask:radial-gradient(ellipse calc(var(--fw,30%)/2) calc(var(--fh,40%)/2) at calc(var(--fx,35%) + var(--fw,30%)/2) calc(var(--fy,20%) + var(--fh,40%)/2),transparent 96%,#000 100%);
}
/* CORNER BOX shape: blur outside the RECTANGLE (two-gradient add-composite hole), corners drawn on canvas */
.cam.oval.face-focus.shape-box::before{
  -webkit-mask:
    linear-gradient(to right,#000 var(--fx),transparent var(--fx) calc(var(--fx) + var(--fw)),#000 calc(var(--fx) + var(--fw))),
    linear-gradient(to bottom,#000 var(--fy),transparent var(--fy) calc(var(--fy) + var(--fh)),#000 calc(var(--fy) + var(--fh)));
  -webkit-mask-composite:source-over;
  mask:
    linear-gradient(to right,#000 var(--fx),transparent var(--fx) calc(var(--fx) + var(--fw)),#000 calc(var(--fx) + var(--fw))),
    linear-gradient(to bottom,#000 var(--fy),transparent var(--fy) calc(var(--fy) + var(--fh)),#000 calc(var(--fy) + var(--fh)));
  mask-composite:add;
}
.cam.oval.face-focus::after{opacity:0}   /* hide the fixed oval strokes while an overlay is active */
.face-oval{position:absolute;z-index:2;pointer-events:none;display:none;
  left:var(--fx);top:var(--fy);width:var(--fw);height:var(--fh);
  border:2px solid var(--navy);border-radius:50%;transition:border-color .12s,box-shadow .12s}
.cam.face-focus:not(.shape-box) .face-oval{display:block}   /* oval outline only for the oval shape */
.cam.face-focus.aligned .face-oval{border-color:var(--good);box-shadow:0 0 8px rgba(21,128,61,.5)}
.cam.face-focus.reject .face-oval{border-color:var(--danger);box-shadow:0 0 8px rgba(185,28,28,.5)}
/* AUTO (face+qr): the QR spotlight is drawn on #qroverlay (canvas) as a dim + a hole/outline
   tracing jsQR's actual 4 corners — so it follows the code's rotation/skew, not a fixed rect. */
.cam #qroverlay{position:absolute;inset:0;z-index:2;pointer-events:none}
/* QR mode: SAME stream (same mirrored transform as face), just a different overlay — a sharp
   centered frame (no blur). jsQR decodes the raw frame regardless of the CSS mirror. */
.cam.oval.mode-qr::before{display:none}
/* frame SIZE is driven by --qr-scale (background-size scales the corners about centre),
   matching the JS decode region. Thin strokes. */
.cam.oval.mode-qr::after{inset:0;background:center / calc(100% * var(--qr-scale,1)) calc(100% * var(--qr-scale,1)) no-repeat url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 75' preserveAspectRatio='none'><g fill='none' stroke='%23ffffff' stroke-width='0.9' stroke-linecap='round' opacity='.92'><path d='M38 18 H30 V26'/><path d='M62 18 H70 V26'/><path d='M38 57 H30 V49'/><path d='M62 57 H70 V49'/></g></svg>");filter:drop-shadow(0 1px 2px rgba(0,0,0,.4));transition:filter .15s,background-size .12s}
/* scan feedback: green frame on a known ticket, red on an unknown one */
.cam.oval.mode-qr.scan-ok::after{background-image:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 75' preserveAspectRatio='none'><g fill='none' stroke='%2315803d' stroke-width='1.2' stroke-linecap='round'><path d='M38 18 H30 V26'/><path d='M62 18 H70 V26'/><path d='M38 57 H30 V49'/><path d='M62 57 H70 V49'/></g></svg>");filter:drop-shadow(0 0 7px rgba(21,128,61,.6))}
.cam.oval.mode-qr.scan-bad::after{background-image:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 75' preserveAspectRatio='none'><g fill='none' stroke='%23b91c1c' stroke-width='1.2' stroke-linecap='round'><path d='M38 18 H30 V26'/><path d='M62 18 H70 V26'/><path d='M38 57 H30 V49'/><path d='M62 57 H70 V49'/></g></svg>");filter:drop-shadow(0 0 7px rgba(185,28,28,.55))}
/* QR-only stations hide the face-capture chrome — but KEEP the tab bar above the stream
   (swapped to a QR Scan tab) so the stream keeps its position instead of floating up. */
.qr-tab{display:none}
body.qr-only #face-mode [data-mode]{display:none}   /* hide live/static/demo … */
body.qr-only .qr-tab{display:inline-block}           /* … show the QR Scan tab in their place */
body.qr-only #cap-req, body.qr-only #static-controls,
body.qr-only #spoofbadge, body.qr-only #demo-controls{display:none !important}
/* streaming zoom always; QR-only drops vertical position; Face+QR drops overlay zoom (overlays auto-track) */
body.qr-only #cam-vpos-row, body.qr-only #cam-blur-row, body.qr-only #cam-dim-row{display:none}   /* QR-only has no face overlay */
body.auto-mode #cam-overlay-row{display:none}
#face-shape-row{display:none}                         /* face overlay shape: Face+QR only */
body.auto-mode #face-shape-row{display:block}
.radio-group{display:flex;gap:18px;margin:8px 0 2px}
.radio{display:inline-flex;align-items:center;gap:7px;margin:0;font-size:13.5px;font-weight:500;color:var(--ink);cursor:pointer}
.radio input{width:16px;height:16px;margin:0;accent-color:var(--navy);cursor:pointer}
.radio input:disabled{cursor:default}
.radio:has(input:disabled){opacity:.6;cursor:default}
#reader{width:100%;max-width:440px;margin:0 auto;border-radius:var(--r);overflow:hidden}

.tabs{display:inline-flex;gap:4px;background:#eef1f6;border-radius:999px;padding:4px;margin:0 auto 20px;display:flex;width:fit-content}
.tabs button{padding:8px 22px;border:none;background:transparent;border-radius:999px;font:inherit;
  font-weight:600;font-size:14px;cursor:pointer;color:var(--muted);transition:.15s}
.tabs button.on{background:var(--navy);color:#fff;box-shadow:var(--shadow)}

.result{display:flex;align-items:center;gap:16px;padding:16px;border-radius:var(--r);margin-top:18px;border:1px solid var(--line)}
.result img{width:60px;height:60px;border-radius:12px;object-fit:cover;background:#eef1f6}
.result.ok{background:var(--good-bg);border-color:transparent}
.result.dup{background:var(--warn-bg);border-color:transparent}
.result.bad{background:var(--danger-bg);border-color:transparent}
.result .big{font-size:17px;font-weight:700;color:var(--head)}
.result .sub{font-size:13px;color:var(--text)}
/* explainable check-in — confidence bar + threshold tick + runner-up gap */
.result>div{min-width:0;flex:1}
.result .xai{margin-top:11px;width:100%}
.xai-bar{position:relative;height:10px;border-radius:6px;background:rgba(20,40,30,.13);overflow:hidden}
.xai-fill{position:absolute;left:0;top:0;bottom:0;border-radius:6px;background:#15803d;transition:width .5s ease}
.result.dup .xai-fill{background:#b45309}
.xai-th{position:absolute;top:0;bottom:0;width:2px;background:#0f172a;opacity:.6}
.xai-legend{display:flex;gap:14px;flex-wrap:wrap;margin-top:8px;font-size:12.5px;color:var(--text)}
.xai-legend b{font-variant-numeric:tabular-nums;color:var(--head)}
.xai-gap{color:#15803d;font-weight:600}
.result.bad .xai-fill,.spoofx .xai-fill{background:#b91c1c}
body.kiosk .xai-legend{font-size:15px;gap:18px}
body.kiosk .xai-bar{height:13px}
/* demo strip — a scrollable list of alternate photos beside the camera */
.checkin-layout{display:flex;gap:18px;align-items:stretch;justify-content:space-between;flex-wrap:wrap;
  margin:0}   /* fill main's content box so the columns' edges line up with the page heading */
.cam-card{width:640px;max-width:100%;margin:0}
.demostrip{border-style:dashed;width:336px;max-width:100%;margin:0}
.demo-faces{display:flex;flex-direction:column;gap:5px;max-height:62vh;overflow-y:auto;margin-top:2px;
  padding-right:2px}
.demo-face{border:0;background:none;padding:6px 8px;cursor:pointer;display:flex;flex-direction:row;
  align-items:center;gap:11px;width:100%;border-radius:9px;text-align:left}
.demo-face img{width:48px;height:48px;object-fit:cover;border-radius:8px;border:2px solid var(--line);
  background:#eef1f6;flex-shrink:0;transition:border-color .15s,box-shadow .15s}
.demo-face span{font-size:14px;color:var(--text);line-height:1.2;white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis}
.demo-face:hover{background:var(--navy-50)}
.demo-face.sel{background:var(--navy-50)}
.demo-face.sel img{border-color:var(--navy);box-shadow:0 0 0 3px var(--navy-100)}
/* demo align pad — draggable/zoomable photo inside the oval (guide stays on top) */
.demopad{position:absolute;inset:0;width:100%;height:100%;z-index:1;cursor:grab;touch-action:none;
  background:#0b1220}
.demopad:active{cursor:grabbing}
.demopad.dragover{outline:3px dashed var(--gold,#c79a3a);outline-offset:-8px}
.demopad.hidden{display:none}   /* beats `.cam canvas{display:block}` so the live video shows through */
body.kiosk .demostrip{display:none}

/* ---------- operator panel: tabs (matches / camera) ---------- */
.info-panel{width:440px;max-width:100%;margin:0}
.authmethod{width:100%;margin:6px 0 0}                /* full-width segmented control in the Configure pane */
.authmethod button{flex:1;padding:8px 6px;font-size:12.5px}
.authmethod button:disabled{opacity:.55;cursor:default}
.rank-result:empty{display:none}
.info-panel .result{margin:0 0 12px}   /* result banner sits at the top of the panel */
/* the 'already checked in' banner duplicates the person card the pane renders below it —
   hide it in the operator pane (kiosk still shows it as the attendee's feedback) */
/* in the operator pane the result banner duplicates the card rendered below it, so hide the
   check-in / re-entry / already-in / invalid-QR banners (kiosk still shows them as feedback).
   The face spoof/unknown banner (.result.bad without .qr-bad) is kept — it carries extra detail. */
body:not(.kiosk) .info-panel .result.ok,
body:not(.kiosk) .info-panel .result.dup,
body:not(.kiosk) .info-panel .result.qr-bad{display:none}
.panel-tabs{margin:-4px -4px 12px}
.panel-tabs button{font-size:13px}
.rank-stats{margin:-2px 0 12px;font-size:12px}
/* extra recognition detail below the ranked list */
.rank-detail{margin-top:12px;border-top:1px solid var(--line);padding-top:10px;display:grid;
  grid-template-columns:auto 1fr;gap:4px 12px;font-size:12px}
.rank-detail b{color:var(--ink);font-weight:600;font-variant-numeric:tabular-nums;text-align:right}
.rank-detail .k{color:var(--muted)}
.rank-detail .ok{color:var(--good)} .rank-detail .no{color:var(--danger)}
/* camera configuration pane */
.cam-cfg label{margin-top:12px}
.cam-cfg select{width:100%;padding:9px 12px;border:1px solid var(--line-2);border-radius:var(--r-sm);
  background:#fff;font:inherit;font-size:14px;color:var(--ink)}
.cam-cfg .check{margin-top:14px}
.rank-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px;max-height:52vh;overflow-y:auto}
.rank-empty{padding:16px 0;text-align:center}
.rank-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--line);
  border-radius:10px;background:#fff;transition:.12s}
.rank-item.is-match{border-color:var(--good);background:var(--good-bg)}
.rank-item.is-bad{border-color:var(--danger);background:var(--danger-bg)}   /* unknown / invalid ticket */
.rank-n{width:16px;text-align:center;font-weight:700;color:var(--muted);font-size:13px;flex-shrink:0}
.rank-face{width:38px;height:38px;border-radius:8px;overflow:hidden;background:var(--navy-50);flex-shrink:0}
.rank-face-none{display:flex;align-items:center;justify-content:center;color:var(--danger);font-weight:700;font-size:17px}
.rank-tag.bad{color:var(--danger)}
.rank-face img{width:100%;height:100%;object-fit:cover;display:block}
.rank-info{flex:1;min-width:0;display:flex;flex-direction:column;line-height:1.2}
.rank-info b{font-size:13.5px;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rank-info small{font-size:11px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rank-tag{font-size:9.5px;font-weight:700;color:var(--good);letter-spacing:.04em;margin-top:2px}
.rank-score{display:flex;flex-direction:column;align-items:flex-end;gap:3px;width:70px;flex-shrink:0}
.rank-score b{font-size:12.5px;color:var(--muted);font-variant-numeric:tabular-nums}
.rank-score.over b{color:var(--good)}
.rank-bar{width:100%;height:5px;border-radius:3px;background:var(--line);overflow:hidden}
.rank-bar i{display:block;height:100%;background:var(--navy);border-radius:3px}
.rank-score.over .rank-bar i{background:var(--good)}
/* kiosk = attendee-facing: show ONLY the big result banner from the panel, hide operator detail */
body.kiosk .info-panel{display:block;width:100%;max-width:560px;margin:0 auto;padding:0;border:none;background:transparent;box-shadow:none}
body.kiosk .info-panel .panel-tabs, body.kiosk .info-panel [data-ppane="camera"]{display:none}
body.kiosk .info-panel [data-ppane="matches"] > *{display:none}   /* keep only the result banner */
body.kiosk .info-panel #rank-result{display:block}

/* anti-spoof running tally badge */
.spoofbadge{display:block;width:fit-content;margin:10px auto 0;padding:6px 14px;border-radius:999px;
  background:var(--danger-bg);color:#b91c1c;font-size:13px;font-weight:700;text-align:center}
body.kiosk .spoofbadge{font-size:16px;padding:8px 18px}

/* ---------- stats ---------- */
.stat-row{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:20px}
@media(max-width:640px){.stat-row{grid-template-columns:repeat(2,1fr)}}
.stat{background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:18px;box-shadow:var(--shadow)}
.stat .n{font-size:30px;font-weight:800;color:var(--navy);font-variant-numeric:tabular-nums;line-height:1;letter-spacing:-.02em}
.stat .l{font-size:12px;font-weight:600;letter-spacing:.02em;color:var(--muted);margin-top:8px}

/* ---------- tables ---------- */
table{width:100%;border-collapse:collapse;font-size:14px}
th,td{text-align:left;padding:12px 14px;border-bottom:1px solid var(--line);vertical-align:middle}
thead th{font-size:11.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--muted);background:#fafbfd}
tbody tr:hover{background:#fafbfd}
td b{color:var(--head);font-weight:600}
td .who{display:flex;align-items:center;gap:10px}
td .who img{width:36px;height:36px;border-radius:9px;object-fit:cover;background:#eef1f6}

/* ---------- qr / misc ---------- */
.qrbox{text-align:center;padding:6px}
.qrbox img{width:230px;height:230px;image-rendering:pixelated;border:1px solid var(--line);border-radius:12px;padding:12px;background:#fff}
.hidden{display:none}
/* client-side pagination controls */
.pager{display:flex;align-items:center;justify-content:center;gap:14px;padding:12px;flex-wrap:wrap}
.pager .note{min-width:120px;text-align:center;margin:0}

/* admin / owner sub-navigation (tab bar) */
.subnav{display:flex;gap:4px;margin-bottom:22px;border-bottom:1px solid var(--line);flex-wrap:wrap}
.subnav a{padding:10px 16px;font-size:14px;font-weight:600;color:var(--muted);border-bottom:2px solid transparent;cursor:pointer}
.subnav a:hover{color:var(--navy);text-decoration:none;background:var(--navy-50);border-radius:8px 8px 0 0}
.subnav a.on{color:var(--navy);border-bottom-color:var(--navy)}

/* ---------- admin dashboard (tabbed) ---------- */
.admin-tabs a{display:inline-flex;align-items:center;gap:7px}
.tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;
  border-radius:999px;font-size:11px;font-weight:700;background:var(--navy-100);color:var(--navy)}
.tab-badge.alert{background:var(--danger-bg);color:var(--danger)}
.stat-row{margin-bottom:18px}
.cardhead{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;padding:16px 22px;border-bottom:1px solid var(--line)}
.cardhead h2{margin:0}
.headtools{display:flex;gap:8px;align-items:center}
/* keep every control in a headtools row the same height (search box vs button vs select) */
.headtools .search,.headtools select,.headtools .btn{height:38px;box-sizing:border-box}
.headtools .btn{display:inline-flex;align-items:center}
.search{width:200px;max-width:46vw;padding:8px 12px;border:1px solid var(--line-2);border-radius:var(--r-sm);
  background:#fff;font:inherit;font-size:13.5px;color:var(--ink)}
.search:focus{outline:none;border-color:var(--navy);box-shadow:0 0 0 3px var(--navy-50)}
.rolefilter{padding:8px 30px 8px 12px;font-size:13.5px}
/* tables inside a .cardhead card: give cells breathing room, right/center helpers */
.card > table th, .card > table td{padding-left:22px;padding-right:22px}
.ta-r{text-align:right} .ta-c{text-align:center}
.rowact{display:inline-flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}
.cellrow{display:inline-flex;align-items:center;gap:9px}
.avatar.sm{width:28px;height:28px;font-size:11px;box-shadow:0 0 0 2px #fff,0 0 0 3px var(--navy-100)}
/* quick-tool cards on the dashboard */
.toolgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}
@media(max-width:720px){.toolgrid{grid-template-columns:repeat(2,1fr)}}
.tool{display:flex;flex-direction:column;gap:10px;padding:16px;border:1px solid var(--line);border-radius:var(--r-sm);
  color:var(--ink);font-weight:600;font-size:14px;transition:.15s;background:#fff}
.tool:hover{border-color:var(--navy);background:var(--navy-50);color:var(--navy);text-decoration:none;box-shadow:var(--shadow)}
.tool svg{width:22px;height:22px;color:var(--navy);opacity:.9}

/* ---------- journey / flow stepper (explains the end-to-end story to non-technical leaders) ---------- */
.flow{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin:2px 0 4px}
@media(max-width:900px){.flow{grid-template-columns:repeat(2,1fr)}}
@media(max-width:520px){.flow{grid-template-columns:1fr}}
.flow .step{background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:16px 16px 15px;box-shadow:var(--shadow)}
.flow .step .i{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:999px;
  background:var(--navy);color:#fff;font-weight:700;font-size:13px;margin-bottom:10px}
.flow .step b{display:block;color:var(--head);font-size:14.5px;margin-bottom:4px}
.flow .step p{font-size:12.5px;color:var(--muted);line-height:1.5}
.flow .step .opt{display:inline-block;font-size:9.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
  color:var(--navy);background:var(--navy-50);padding:2px 7px;border-radius:999px;margin-left:6px;vertical-align:middle}
.win{outline:2px solid var(--good);outline-offset:-2px}

/* ---------- attendee journey — step cards linked by arrows (stacks on mobile) ---------- */
.journey{display:flex;align-items:stretch;gap:6px;margin:6px 0 2px}
.jcard{flex:1 1 0;min-width:0;background:#fff;border:1px solid var(--line);border-radius:var(--r);
  padding:15px 15px 14px;box-shadow:var(--shadow);transition:.15s}
.jcard .i{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:999px;
  background:var(--navy);color:#fff;font-weight:700;font-size:13px;margin-bottom:9px}
.jcard b{display:block;color:var(--head);font-size:14px;line-height:1.3;margin-bottom:3px}
.jcard p{font-size:12px;color:var(--muted);line-height:1.5}
.jcard .opt{display:inline-block;font-size:9.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
  color:var(--navy);background:var(--navy-50);padding:2px 7px;border-radius:999px;margin-left:5px;vertical-align:middle}
.jcard.done{border-color:#bbf7d0;background:#f2fdf6}
.jcard.done .i{background:var(--good)}
.jcard.now{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-50),var(--shadow)}
.jcard.now .i{background:var(--accent)}
.jarrow{display:flex;align-items:center;flex-shrink:0;color:var(--line-2);font-size:20px;font-weight:700}
@media(max-width:720px){
  .journey{flex-direction:column;align-items:stretch}
  .jarrow{justify-content:center;transform:rotate(90deg);margin:-3px 0}
}

/* ---------- custom select (drop the native arrow for a branded chevron) ---------- */
select{
  width:100%;padding:11px 40px 11px 14px;border:1px solid var(--line-2);border-radius:var(--r-sm);
  font:inherit;font-size:15px;color:var(--ink);cursor:pointer;transition:.15s;
  -webkit-appearance:none;-moz-appearance:none;appearance:none;
  background-color:#fff;background-repeat:no-repeat;background-position:right 13px center;background-size:16px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
}
select:hover{border-color:var(--navy)}
select:focus{outline:none;border-color:var(--navy);box-shadow:0 0 0 3px var(--navy-50)}
select::-ms-expand{display:none}

/* ---------- custom file picker (native input hidden; label is a branded button) ---------- */
.filepick{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:6px}
.filepick input[type=file]{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}
.filepick .filepick-btn{margin:0}
.filepick .filepick-name{margin:0}
.filepick input[type=file]:focus-visible + .filepick-btn{border-color:var(--navy);box-shadow:0 0 0 3px var(--navy-50)}

.cam #overlay,.cam #eoverlay{position:absolute;inset:0;z-index:2;pointer-events:none}
.cam canvas.hidden{display:none}   /* grab canvases stay hidden (beats .cam canvas{display:block}) */

/* centered "verifying" overlay with a loading spinner, over the frozen capture frame */
.cam-verifying{position:absolute;inset:0;z-index:3;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:14px;background:rgba(11,18,32,.5);color:#fff;font-weight:600;font-size:15px;
  letter-spacing:.02em;backdrop-filter:blur(1px)}
.cam-verifying.hidden{display:none}
.cam-verifying .spinner{width:46px;height:46px;border:4px solid rgba(255,255,255,.28);
  border-top-color:#fff;border-radius:50%;animation:spin .8s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}

/* kiosk mode (fullscreen venue check-in) */
body.kiosk .top, body.kiosk footer{display:none}
body.kiosk main{max-width:820px;padding-top:34px}
body.kiosk .kicker, body.kiosk #kiosk{display:none}
body.kiosk main > h1, body.kiosk main > p.muted{display:none}
body.kiosk .kiosk-title{display:block !important}
body.kiosk .cam{max-width:640px}
body.kiosk .card{max-width:700px}
body.kiosk .result{padding:22px}
body.kiosk .result .big{font-size:26px}
body.kiosk .result img{width:82px;height:82px}

/* language switch — same 36px height as the other header controls */
.langsw{display:inline-flex;height:36px;margin-left:8px;border:1px solid var(--line-2);border-radius:8px;overflow:hidden}
.langsw a{display:flex;align-items:center;height:100%;padding:0 12px;font-size:12.5px;font-weight:600;color:var(--muted);border-radius:0}
.langsw a+a{border-left:1px solid var(--line-2)}
.langsw a:hover{text-decoration:none;background:var(--navy-50);color:var(--navy)}
.langsw a.on{background:var(--navy);color:#fff}
.langsw a.on:hover{background:var(--navy-600);color:#fff}

/* modal */
.modal{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:20px}
.modal.hidden{display:none}
.modal-backdrop{position:absolute;inset:0;background:rgba(11,18,32,0.5)}
.modal-content{position:relative;z-index:1;width:100%;animation:modal-in 0.15s ease-out}
@keyframes modal-in{from{opacity:0;transform:scale(0.95) translateY(-10px)}to{opacity:1;transform:scale(1) translateY(0)}}
.modalhead{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}
/* add-VIP modal photo dropzone + preview */
.vip-drop{display:block;cursor:pointer;margin:0}
.vip-preview{display:flex;align-items:center;justify-content:center;text-align:center;overflow:hidden;
  border:1.5px dashed var(--line-2);border-radius:var(--r-sm);background:var(--paper,#fafafa);
  min-height:150px;padding:12px;transition:border-color .15s}
.vip-drop:hover .vip-preview{border-color:var(--navy)}
.vip-preview img{max-height:220px;border-radius:var(--r-sm);object-fit:cover}

/* ---------- responsive polish (mobile ↔ desktop) ---------- */
img{max-width:100%}
.qrbox img{max-width:100%;height:auto}                 /* QR shrinks on narrow screens */

/* header: on small screens keep brand + identity on row 1, nav on its own scrollable row */
@media(max-width:720px){
  .top{row-gap:10px}
  .mainnav{order:2;flex-basis:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;justify-content:flex-start}
  .topright{order:1;margin-left:auto}
}
/* check-in: stack camera above the operator panel on tablets/phones (no side-by-side) */
@media(max-width:820px){
  .checkin-layout{flex-direction:column;align-items:stretch;gap:14px}
  .cam-card,.info-panel{width:100%}
  .cam{max-width:100%}
}
@media(max-width:560px){
  main{padding:18px 13px}
  .card{padding:18px}
  .top{padding:10px 13px}
  h2{font-size:17px}
  .stat{padding:14px}
  .stat .n{font-size:25px}
  .tabs{max-width:100%;overflow-x:auto}               /* pill tab bars never force page-wide scroll */
  .subnav{flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch}
  .subnav a{white-space:nowrap}
  .result{gap:12px;padding:14px}
  .checkin-layout{gap:12px}
  /* 16px inputs stop iOS from auto-zooming the page when a field is focused */
  input[type=text],input[type=email],input[type=tel],input[type=password],select{font-size:16px}
}
/* long words / emails never push a card past the viewport */
.note,p,.result .sub,.uinfo b{overflow-wrap:break-word}
