/* Business Card Scanner — NexBi Solutions group.
   Palette taken from the group logo: navy #01377D, emerald #23A771, teal #1D676E. */

:root {
  color-scheme: light;
  --navy: #01377d;
  --navy-700: #052f66;
  --navy-900: #021f47;
  --emerald: #23a771;
  --emerald-600: #1b8c5e;
  --teal: #1d676e;

  --bg: #f1f4f8;
  --surface: #ffffff;
  --surface-2: #f7f9fc;
  --ink: #0f1829;
  --ink-2: #3d4a60;
  --muted: #6b7890;
  --line: #dde3ec;
  --line-strong: #c3ccdb;

  --brand: var(--navy);
  --brand-ink: #ffffff;
  --ok: var(--emerald-600);
  --warn: #9a6400;
  --bad: #c0392f;

  --radius: 16px;
  --radius-sm: 10px;
  --shadow-sm: 0 1px 2px rgba(5, 23, 54, .07);
  --shadow: 0 2px 6px rgba(5, 23, 54, .06), 0 12px 28px rgba(5, 23, 54, .07);
  --focus: 0 0 0 3px rgba(35, 167, 113, .35);
}

/* The app is a light interface by design. The dark palette below is kept
   but applies only when a page opts in with <html data-theme="dark">, so a
   phone set to dark mode no longer flips the scan pages. */
:root[data-theme="dark"] {
    --bg: #0c1220;
    --surface: #151d2e;
    --surface-2: #1b2437;
    --ink: #e9eef7;
    --ink-2: #b9c4d6;
    --muted: #8793a8;
    --line: #27324a;
    --line-strong: #35425e;

    --brand: #4d8ae0;
    --brand-ink: #06101f;
    --ok: #46c489;
    --warn: #e0ad4a;
    --bad: #f0837a;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .5);
    --shadow: 0 2px 6px rgba(0, 0, 0, .45), 0 16px 32px rgba(0, 0, 0, .35);
}

* { box-sizing: border-box; }

/* An author `display` beats the browser's [hidden] rule, so anything the page
   hides with the attribute needs this or it stays on screen. The flip cue was
   showing from the moment the camera opened for exactly that reason. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100dvh;
  font: 16px/1.55 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  background: var(--bg);
  color: var(--ink);
}

h1, h2, h3 { letter-spacing: -.012em; }

/* --- brand mark --------------------------------------------------------- */

.mark {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-weight: 700;
  letter-spacing: -.01em;
  color: inherit;
  text-decoration: none;
}

.mark svg { flex: none; }
/* The word mark is a 2.9:1 image, not a square glyph: height is what is fixed
   and the width follows, so the bar keeps its rhythm at any logo revision. */
.mark img { flex: none; display: block; height: 22px; width: auto; }
.signin-logo img { display: block; height: 65px; width: auto; }
.event-mark img { height: 18px; width: auto; }
.mark .mark-text { display: flex; flex-direction: column; line-height: 1.15; }
.mark .mark-text small { font-size: 11px; font-weight: 600; color: var(--muted); letter-spacing: .06em; text-transform: uppercase; }

/* --- app shell ---------------------------------------------------------- */

header.bar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  padding-top: max(10px, env(safe-area-inset-top));
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: saturate(1.4) blur(12px);
  border-bottom: 1px solid var(--line);
}

header.bar h1 { font-size: 16px; margin: 0; flex: 1; font-weight: 650; }

/* Admin drops the title from the bar — the tab strip underneath already says
   where you are, and the word mark plus the tabs read cleaner without it. The
   heading stays in the document for the outline and for a screen reader, and
   the push to the right is the user menu's own, further down, so that it does
   not depend on a title being there to be the flexible gap. */
header.bar h1.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  flex: none;
}


header.bar a,
header.bar button.link {
  color: var(--ink-2);
  text-decoration: none;
  font-size: 14px;
  font-weight: 600;
  padding: 7px 11px;
  border-radius: 999px;
}

header.bar a:hover,
header.bar button.link:hover { background: var(--surface-2); color: var(--ink); }

main { max-width: 760px; margin: 0 auto; padding: 18px 16px 40px; }

/* The admin tables carry six columns and need more room than the scan page,
   which is deliberately narrow for one-handed use. */
main.wide { max-width: 1140px; }

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 18px;
  margin-bottom: 14px;
}

.card > h2 {
  margin: 0 0 14px;
  font-size: 15px;
  font-weight: 650;
}

/* The camera is the primary action in its step, but Submit is the primary
   action on the page, so they are deliberately different colours. */
button.wide.alt {
  background: linear-gradient(135deg, var(--navy) 0%, var(--navy-700) 100%);
  box-shadow: 0 6px 18px rgba(1, 55, 125, .22);
}

/* A numbered step heading on the scan page. */
.step { margin-bottom: 12px; }

.step .t { font-weight: 650; font-size: 15px; }
.step .t span { display: block; font-weight: 400; font-size: 13px; color: var(--muted); }

label { display: block; font-weight: 600; font-size: 14px; margin-bottom: 7px; }
.hint { color: var(--muted); font-size: 13px; font-weight: 400; }

input[type=text], input[type=password], input[type=number], select, textarea {
  width: 100%;
  padding: 13px 14px;
  font: inherit;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  transition: border-color .15s, box-shadow .15s, background .15s;
}

input::placeholder { color: var(--muted); }

input:focus, select:focus, textarea:focus {
  outline: none;
  background: var(--surface);
  border-color: var(--emerald);
  box-shadow: var(--focus);
}

select {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%),
                    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 19px) 55%, calc(100% - 13px) 55%;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
  padding-right: 38px;
}

button {
  font: inherit;
  font-weight: 650;
  padding: 13px 18px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  background: var(--brand);
  color: var(--brand-ink);
  cursor: pointer;
  transition: filter .15s, transform .06s, background .15s;
}

button:hover:not(:disabled) { filter: brightness(1.08); }
button:active:not(:disabled) { transform: translateY(1px); }
button:focus-visible { outline: none; box-shadow: var(--focus); }
button.secondary { background: var(--surface); color: var(--ink); border-color: var(--line-strong); }
button.ghost { background: none; color: var(--brand); padding: 8px 10px; }
button:disabled { opacity: .45; cursor: not-allowed; }
button.link { background: none; border: none; cursor: pointer; padding: 0; }

button.wide {
  width: 100%;
  padding: 17px;
  font-size: 17px;
  background: linear-gradient(135deg, var(--emerald), var(--emerald-600));
  color: #fff;
  box-shadow: 0 6px 18px rgba(27, 140, 94, .28);
}

button.wide:disabled { background: var(--line-strong); color: var(--muted); box-shadow: none; }

/* --- sign in ------------------------------------------------------------ */

/* The page is the clip, with one card on it: a logo, two fields and a button.
   The brand panel, the pitch and its bullet list are gone — the clip carries
   the wordmark itself. */

.signin {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: 24px 16px;
  max-width: none;
  margin: 0;
}

.signin-card {
  width: 100%;
  max-width: 372px;
  padding: 30px 26px 26px;
  border-radius: var(--radius);
  /* Glass, not a panel: the clip reads straight through it. .42 is as far as
     it goes and keep dark text safe — behind the card the clip runs from luma
     74 to 191, which this and the scrim land at 169-225. */
  background: rgba(255, 255, 255, .42);
  backdrop-filter: saturate(1.3) blur(26px);
  border: 1px solid rgba(255, 255, 255, .55);
  box-shadow: 0 2px 10px rgba(10, 30, 60, .12), 0 28px 70px rgba(10, 30, 60, .22);
}

.signin-logo {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  margin-bottom: 24px;
  color: var(--navy);
  text-align: center;
}

.signin-logo strong { font-size: 15px; font-weight: 700; letter-spacing: .02em; }


.field { margin-bottom: 15px; }
.field-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }

.pw-wrap { position: relative; }
.pw-wrap input { padding-right: 46px; }

.pw-toggle {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  padding: 0;
  background: none;
  border: none;
  border-radius: 50%;
  color: var(--muted);
  cursor: pointer;
}

.pw-toggle:hover { background: var(--surface-2); color: var(--ink); }
.pw-toggle:focus-visible { outline: none; box-shadow: var(--focus); }

/* The eye: the slash draws itself across when the password is showing, and
   the pupil shrinks away behind it. Both run off aria-pressed, so the button's
   state and its picture cannot disagree. The line is 21.5 units long, which is
   what the dash array has to match for it to draw rather than fade. */
.pw-toggle .eye-slash {
  stroke-dasharray: 21.5;
  stroke-dashoffset: 21.5;
  transition: stroke-dashoffset .3s ease;
}

.pw-toggle .eye-pupil,
.pw-toggle .eye-lid {
  transform-origin: 12px 12px;
  transition: transform .3s ease, opacity .25s ease;
}

.pw-toggle[aria-pressed="true"] .eye-slash { stroke-dashoffset: 0; }
.pw-toggle[aria-pressed="true"] .eye-pupil { transform: scale(.5); opacity: .5; }
/* A squint rather than a second path: the lid keeps its shape and just closes
   a little, which is what makes the two states read as one eye. */
.pw-toggle[aria-pressed="true"] .eye-lid { transform: scaleY(.74); }

/* The whole point is the movement, so for anyone who has asked for less of it
   the two states still differ — the slash is simply there or not. */
@media (prefers-reduced-motion: reduce) {
  .pw-toggle .eye-slash,
  .pw-toggle .eye-pupil,
  .pw-toggle .eye-lid { transition: none; }
}

/* --- capture tiles ------------------------------------------------------ */

/* minmax(0,1fr) so the tiles may shrink: plain 1fr keeps their content's
   width and pushes the second tile off a narrow screen. */
.shots { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }

.shot {
  position: relative;
  border: 1.5px dashed var(--line-strong);
  border-radius: 14px;
  min-height: 150px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 12px;
  text-align: center;
  color: var(--muted);
  font-size: 14px;
  background: var(--surface-2);
  transition: border-color .15s, background .15s;
}

.shot.filled {
  border-style: solid;
  border-color: var(--emerald);
  background: color-mix(in srgb, var(--emerald) 7%, var(--surface));
  color: var(--ink);
}

.shot img { width: 100%; height: 112px; object-fit: cover; border-radius: 10px; }
.shot .actions { display: flex; gap: 6px; max-width: 100%; }
/* A half-width tile at 390px leaves about 130px for two buttons, which is
   less than their natural width — so they share the row as equal halves
   instead of wrapping and doubling the tile's height. */
.shot .actions { flex-wrap: nowrap; justify-content: center; width: 100%; }

.shot .actions button {
  flex: 1 1 0;
  min-width: 0;
  padding: 7px 4px;
  font-size: 12px;
  white-space: nowrap;
}

.shot .tag {
  position: absolute;
  top: 9px;
  left: 9px;
  font-size: 11px;
  font-weight: 650;
  letter-spacing: .04em;
  text-transform: uppercase;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 3px 9px;
  color: var(--muted);
}

.shot.filled .tag { border-color: var(--emerald); color: var(--emerald-600); }

.consent { display: flex; gap: 12px; align-items: flex-start; }
.consent input { width: 22px; height: 22px; margin-top: 2px; flex: none; accent-color: var(--emerald); }
.consent label { font-weight: 500; margin: 0; }

/* --- feedback ----------------------------------------------------------- */

.toast {
  position: fixed;
  left: 50%;
  bottom: 26px;
  transform: translateX(-50%) translateY(140%);
  background: var(--navy-900);
  color: #fff;
  padding: 13px 20px;
  border-radius: 999px;
  box-shadow: var(--shadow);
  transition: transform .26s cubic-bezier(.2, .8, .3, 1);
  z-index: 50;
  max-width: 90vw;
  font-size: 14.5px;
}

.toast.show { transform: translateX(-50%) translateY(0); }
.toast.bad { background: var(--bad); }

.rows { display: flex; flex-direction: column; gap: 10px; }

/* One card per line is right on a phone and wasteful on anything else: at 760
   a row was a name on the left and a button stranded at the far right. The
   list becomes a grid of tiles instead, as many across as the width allows,
   and each tile keeps the layout it already had on the phone.
   align-items: start so a tile carrying an extra line — a reason a card needs
   attention — does not stretch its whole row to match. */
@media (min-width: 720px) {
  .rows {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(336px, 1fr));
    align-items: start;
    gap: 12px;
  }

}

.row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: var(--shadow-sm);
}

/* Text, then the controls on a line of their own beneath it, pushed right by
   the first of them. One line across — the phone's old layout — left a real
   address and timestamp wrapping in a narrow column beside Open and the pill,
   at every width: a tile on a desktop is no wider than the phone's row was. */
.row { flex-wrap: wrap; }
.row .grow { flex: 1 0 100%; min-width: 0; }
.row > button { margin-left: auto; }
.row .grow b { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row .grow small { color: var(--muted); display: block; }

.pill {
  font-size: 12px;
  font-weight: 650;
  padding: 5px 11px;
  border-radius: 999px;
  white-space: nowrap;
  border: 1px solid transparent;
}

.pill.ok { background: color-mix(in srgb, var(--ok) 13%, transparent); color: var(--ok); border-color: color-mix(in srgb, var(--ok) 30%, transparent); }
.pill.warn { background: color-mix(in srgb, var(--warn) 15%, transparent); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 32%, transparent); }
.pill.bad { background: color-mix(in srgb, var(--bad) 12%, transparent); color: var(--bad); border-color: color-mix(in srgb, var(--bad) 28%, transparent); }
.pill.idle { background: color-mix(in srgb, var(--muted) 13%, transparent); color: var(--muted); border-color: color-mix(in srgb, var(--muted) 26%, transparent); }

.empty { text-align: center; color: var(--muted); padding: 46px 10px; }

/* --- admin -------------------------------------------------------------- */

.tabs {
  display: flex;
  gap: 4px;
  margin-bottom: 16px;
  padding: 4px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 999px;
  overflow-x: auto;
  scrollbar-width: none;
}

.tabs::-webkit-scrollbar { display: none; }

.tabs button {
  background: none;
  color: var(--ink-2);
  border: none;
  padding: 9px 15px;
  border-radius: 999px;
  font-size: 14px;
  white-space: nowrap;
}

.tabs button[aria-selected=true] { background: var(--brand); color: var(--brand-ink); }

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th, td { text-align: left; padding: 10px 8px; border-bottom: 1px solid var(--line); vertical-align: top; }
th { color: var(--muted); font-weight: 650; font-size: 12.5px; letter-spacing: .03em; text-transform: uppercase; }
tr:last-child td { border-bottom: none; }

dialog {
  border: none;
  border-radius: var(--radius);
  padding: 0;
  max-width: 700px;
  width: calc(100vw - 28px);
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow);
}

dialog::backdrop { background: rgba(2, 16, 40, .55); backdrop-filter: blur(2px); }
dialog .body { padding: 18px; max-height: 76vh; overflow: auto; }
dialog .foot { display: flex; gap: 10px; justify-content: flex-end; padding: 13px 18px; border-top: 1px solid var(--line); background: var(--surface-2); }

.field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 11px; }
.field-grid label { font-size: 12px; color: var(--muted); }
.lowconf input { border-color: var(--warn); background: color-mix(in srgb, var(--warn) 7%, var(--surface-2)); }
/* Company allocation: which companies a user may scan for. Wraps rather than
   columns, so a group of twenty reads as a list and not as a grid of gaps. */
.ticks { display: flex; flex-wrap: wrap; gap: 8px 16px; margin-top: 8px; }
.ticks .tick {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  color: var(--ink);
  cursor: pointer;
}
.ticks .tick input { width: auto; margin: 0; accent-color: var(--brand); }
tr.alloc td { background: var(--surface-2); }

.photos { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 16px; }
.photos img { width: 100%; border-radius: 12px; border: 1px solid var(--line); }

@media (max-width: 560px) {
  .field-grid, .photos { grid-template-columns: 1fr; }
}

/* --- live scanner ------------------------------------------------------- */

.scanner {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: #000;
  display: flex;
  align-items: center;
  justify-content: center;
  overscroll-behavior: contain;
}

.scanner video,
.scanner .overlay {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.scanner .overlay { pointer-events: none; }

.scanner-top {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: max(12px, env(safe-area-inset-top)) 16px 12px;
  background: linear-gradient(rgba(0, 0, 0, .7), transparent);
  color: #fff;
}

.scanner-side { flex: 1; text-align: center; font-weight: 650; font-size: 14px; }

.scanner-top button {
  background: none;
  border: none;
  color: #fff;
  font: inherit;
  font-size: 15px;
  padding: 6px 4px;
}

.scanner-torch[aria-pressed="true"] { color: #ffd45e; }

.scanner-hint {
  position: absolute;
  bottom: 152px;
  left: 50%;
  transform: translateX(-50%);
  margin: 0;
  padding: 9px 18px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .62);
  color: #fff;
  font-size: 14px;
  white-space: nowrap;
}

.scanner-hint.good { background: var(--emerald-600); }

.scanner-bottom {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px 28px max(24px, env(safe-area-inset-bottom));
  background: linear-gradient(transparent, rgba(0, 0, 0, .72));
  color: #fff;
}

.scanner-shutter {
  width: 72px;
  height: 72px;
  border-radius: 50%;
  background: #fff;
  border: 4px solid rgba(255, 255, 255, .45);
  background-clip: padding-box;
  padding: 0;
}

.scanner-shutter:active { transform: scale(.94); }

.scanner.flash::after {
  content: "";
  position: absolute;
  inset: 0;
  background: #fff;
  animation: scanner-flash .25s ease-out forwards;
}

@keyframes scanner-flash {
  from { opacity: .85; }
  to { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* Step dots and the skip control inside the scanner. */

.scanner-steps {
  position: absolute;
  top: calc(max(12px, env(safe-area-inset-top)) + 46px);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 8px;
  z-index: 2;
}

.scanner-steps .dot {
  font-size: 11.5px;
  font-weight: 650;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .6);
  background: rgba(0, 0, 0, .45);
  border: 1px solid rgba(255, 255, 255, .22);
  border-radius: 999px;
  padding: 4px 12px;
}

.scanner-steps .dot.current { color: #fff; border-color: #fff; }

.scanner-steps .dot.done {
  color: #0b1f16;
  background: var(--emerald);
  border-color: var(--emerald);
}

.scanner-steps .dot.done::before { content: "✓ "; }

.scanner-skip {
  position: absolute;
  right: 24px;
  bottom: calc(max(24px, env(safe-area-inset-bottom)) + 22px);
  background: rgba(255, 255, 255, .16);
  border: 1px solid rgba(255, 255, 255, .34);
  color: #fff;
  padding: 10px 16px;
  font-size: 14px;
}

/* The flip cue between the two shots: the card turns over inside a scanner
   frame — a sweeping beam, corner brackets and a glow — so it reads as the
   machine working, the way a government ID scanner does, rather than a plain
   line of text. Everything animates transform and opacity only, so it stays
   cheap while the camera is still running. */

.flip-cue {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 30px;
  background:
    radial-gradient(420px 320px at 50% 44%, rgba(35, 167, 113, .18), transparent 70%),
    rgba(2, 10, 24, .76);
  color: #fff;
  pointer-events: none;
}

.flip-stage {
  position: relative;
  width: 240px;
  height: 160px;
  display: grid;
  place-items: center;
}

/* Soft pulse behind the card. */
.flip-cue .aura {
  position: absolute;
  width: 220px;
  height: 140px;
  border-radius: 24px;
  background: radial-gradient(closest-side, rgba(35, 167, 113, .45), transparent 75%);
  opacity: 0;
}

.flip-cue.playing .aura { animation: cue-aura 1.5s ease-out both; }

/* Four corner brackets, drawn as gradients so there are no extra elements. */
.flip-cue .brackets {
  position: absolute;
  width: 236px;
  height: 156px;
  background-image:
    linear-gradient(var(--emerald), var(--emerald)), linear-gradient(var(--emerald), var(--emerald)),
    linear-gradient(var(--emerald), var(--emerald)), linear-gradient(var(--emerald), var(--emerald)),
    linear-gradient(var(--emerald), var(--emerald)), linear-gradient(var(--emerald), var(--emerald)),
    linear-gradient(var(--emerald), var(--emerald)), linear-gradient(var(--emerald), var(--emerald));
  background-repeat: no-repeat;
  background-size: 26px 3px, 3px 26px, 26px 3px, 3px 26px,
                   26px 3px, 3px 26px, 26px 3px, 3px 26px;
  background-position:
    left top, left top,
    right top, right top,
    left bottom, left bottom,
    right bottom, right bottom;
  opacity: .9;
}

.flip-cue.playing .brackets { animation: cue-brackets 1.5s ease-out both; }

/* The scanning beam sweeps the frame twice while the card turns. */
.flip-cue .beam {
  position: absolute;
  left: 2px;
  right: 2px;
  height: 3px;
  border-radius: 3px;
  background: linear-gradient(90deg, transparent, var(--emerald) 18%, #eafff4 50%, var(--emerald) 82%, transparent);
  box-shadow: 0 0 18px rgba(35, 167, 113, .9);
  opacity: 0;
}

.flip-cue.playing .beam { animation: cue-beam 750ms ease-in-out 2 both; }

.flip-card {
  position: relative;
  width: 190px;
  height: 120px;
  transform-style: preserve-3d;
}

/* Ends a little before FLIP_PAUSE_MS in scanner.js, so the card is resting on
   its back face when the cue disappears rather than cut off mid-turn. */
.flip-cue.playing .flip-card { animation: card-flip 1.15s cubic-bezier(.45, .05, .55, .95) .1s both; }

.flip-card .face {
  position: absolute;
  inset: 0;
  border-radius: 12px;
  backface-visibility: hidden;
  background: #fff;
  box-shadow: 0 14px 34px rgba(0, 0, 0, .45), 0 0 0 1px rgba(35, 167, 113, .5);
}

/* A hint of printing on the front, so the two faces read as a card turning. */
.flip-card .face.front {
  background-image:
    linear-gradient(var(--emerald), var(--emerald)),
    linear-gradient(#c9d8ea, #c9d8ea),
    linear-gradient(#c9d8ea, #c9d8ea);
  background-repeat: no-repeat;
  background-size: 74px 9px, 100px 7px, 56px 7px;
  background-position: 22px 30px, 22px 54px, 22px 72px;
}

.flip-card .face.back {
  transform: rotateY(180deg);
  background-image: linear-gradient(#dde3ec, #dde3ec);
  background-repeat: no-repeat;
  background-size: 60px 7px;
  background-position: 22px 30px;
}

.flip-label {
  margin: 0;
  font-size: 17px;
  font-weight: 650;
  letter-spacing: .01em;
  background: linear-gradient(90deg, #ffffff 0%, #9ff0c8 35%, #ffffff 70%);
  background-size: 220% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.flip-cue.playing .flip-label { animation: cue-shimmer 1.5s linear both; }

/* One continuous turn. An intermediate keyframe near 90 degrees made the card
   decelerate into the edge-on angle and accelerate out of it, which read as
   the card sticking just as it went invisible. */
@keyframes card-flip {
  from { transform: perspective(900px) rotateY(0deg); }
  to   { transform: perspective(900px) rotateY(-180deg); }
}

@keyframes cue-beam {
  0%   { transform: translateY(-74px); opacity: 0; }
  15%  { opacity: 1; }
  85%  { opacity: 1; }
  100% { transform: translateY(74px); opacity: 0; }
}

@keyframes cue-aura {
  0%   { opacity: 0; transform: scale(.8); }
  40%  { opacity: 1; transform: scale(1.02); }
  100% { opacity: .25; transform: scale(1); }
}

@keyframes cue-brackets {
  0%   { opacity: 0; transform: scale(1.18); }
  35%  { opacity: 1; transform: scale(1); }
  100% { opacity: .85; transform: scale(1); }
}

@keyframes cue-shimmer {
  from { background-position: 120% 0; }
  to   { background-position: -40% 0; }
}

/* With reduced motion the card simply shows its back; the words still say it. */
@media (prefers-reduced-motion: reduce) {
  .flip-cue.playing .flip-card { animation: none; transform: perspective(900px) rotateY(-180deg); }
  .flip-cue.playing .beam,
  .flip-cue.playing .aura,
  .flip-cue.playing .brackets,
  .flip-cue.playing .flip-label { animation: none; opacity: 1; }
  .flip-label { color: #fff; background: none; -webkit-background-clip: border-box; background-clip: border-box; }
}

/* Admin tables on a narrow screen.
   Six columns of scan data cannot fit a phone, and squeezing them makes every
   row wrap into a tall mess. Below 720px each row becomes its own card with
   the column name beside each value. */

table.stack td.actions { white-space: nowrap; }
table.stack td[data-label="When"] { white-space: nowrap; }

/* Between the stacked layout and a comfortable table there is a band where the
   columns simply do not fit; let that scroll rather than squash. */
.scroll-x { overflow-x: auto; }

@media (max-width: 720px) {
  table.stack thead { display: none; }

  table.stack,
  table.stack tbody,
  table.stack tr,
  table.stack td { display: block; width: 100%; }

  table.stack tr {
    border: 1px solid var(--line);
    border-radius: 12px;
    padding: 10px 12px;
    margin: 10px 0;
    background: var(--surface-2);
  }

  table.stack td {
    display: flex;
    gap: 14px;
    align-items: baseline;
    justify-content: space-between;
    text-align: right;
    border: none;
    padding: 5px 0;
  }

  table.stack td::before {
    content: attr(data-label);
    flex: none;
    color: var(--muted);
    font-size: 12.5px;
    font-weight: 650;
    text-align: left;
  }

  /* The action cell has no label, so let the buttons use the full width. */
  table.stack td.actions {
    justify-content: flex-end;
    gap: 8px;
    padding-top: 10px;
    margin-top: 6px;
    border-top: 1px solid var(--line);
  }

  table.stack td.actions::before { display: none; }

  /* Long values wrap instead of pushing the row sideways. */
  table.stack td { overflow-wrap: anywhere; }

  /* A cell holding more than one line keeps them stacked: without this the
     name and the email below it become two flex items side by side. */
  table.stack td > .v { display: block; }
}

/* The footnote under the scan page: a line, not a banner. */
.event-mark {
  /* fit-content plus auto margins centres the pill without a text-align on
     the parent, which would have centred every card's text with it. */
  display: flex;
  width: fit-content;
  align-items: center;
  gap: 9px;
  margin: 26px auto 6px;
  padding: 7px 14px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .3);
  backdrop-filter: blur(2px);
  color: #e6f0ff;
  font-size: 11px;
  letter-spacing: .07em;
}

.event-mark img { width: 15px; height: 15px; }
.event-mark b { font-weight: 700; letter-spacing: .1em; }

/* --- the signed-in pages ------------------------------------------------ */
/* Scan, My scans and Admin share the sign-in page's look: the same
   constellation still behind them, and panels of dark glass over it. Almost
   all of it is done by moving the tokens — the components already read from
   them — with a few places that had light values baked in. */

/* The dropdown's popup list is drawn by the browser, not the page, and it
   follows the colour scheme of the root element, so it is stated here rather
   than left to the phone's own setting. */
html.app-theme { color-scheme: light; }

/* Firefox paints the list from these, so they are stated as well. */
body.app-theme option {
  background-color: #ffffff;
  color: var(--ink);
}

body.app-theme {
  --bg: #eef3f7;
  --surface: rgba(255, 255, 255, .76);
  --surface-2: rgba(10, 45, 62, .055);
  --ink: #0c2733;
  --ink-2: #2f4e5d;
  --muted: #627e8c;
  --line: rgba(12, 60, 80, .14);
  --line-strong: rgba(12, 60, 80, .26);
  --brand: var(--teal);
  --brand-ink: #ffffff;
  --ok: var(--emerald-600);
  --warn: #8a5a00;
  --bad: #c0392f;
  --shadow-sm: 0 1px 2px rgba(10, 40, 60, .10);
  --shadow: 0 2px 8px rgba(10, 40, 60, .10), 0 20px 48px rgba(10, 40, 60, .14);

  color-scheme: light;
  background: var(--bg);
}


/* The clip is a bright sky, but it still carries a grey figure and darker
   crystal edges that dark text disappears into. A white wash over it keeps
   the contrast steady wherever the frame happens to be. ::after rather than
   ::before so it lands after the <video> in paint order at the same z-index;
   header and main sit above it on z-index of their own. */
body.app-theme::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    linear-gradient(to bottom,
      rgba(255, 255, 255, .58) 0%,
      rgba(238, 245, 250, .34) 38%,
      rgba(238, 245, 250, .34) 62%,
      rgba(255, 255, 255, .60) 100%);
  transition: background .8s ease;
}

/* The bar has to out-rank main, not tie with it: both menus hang out of the
   bar and over the cards, and a card carrying backdrop-filter makes its own
   stacking context that would otherwise paint over them. A tie goes to the
   later element in the document, which is main. */
body.app-theme header.bar { position: sticky; top: 0; z-index: 40; }
body.app-theme main { position: relative; z-index: 1; }

body.app-theme header.bar {
  background: rgba(255, 255, 255, .78);
  backdrop-filter: saturate(1.2) blur(18px);
  border-bottom: 1px solid var(--line);
}

body.app-theme header.bar h1 { color: var(--ink); }
body.app-theme header.bar a:hover,
body.app-theme header.bar button.link:hover { background: rgba(10, 45, 62, .07); color: var(--ink); }

body.app-theme .card,
body.app-theme .row,
body.app-theme .tabs {
  backdrop-filter: saturate(1.15) blur(20px);
  border-color: var(--line);
}

/* Inputs: solid white so a value stays readable whatever the clip is doing
   behind it. background-color, not the shorthand — the shorthand resets the
   select arrow's size and position and the little triangle fills the whole
   control. */
body.app-theme input[type=text],
body.app-theme input[type=password],
body.app-theme input[type=number],
body.app-theme select,
body.app-theme textarea {
  background-color: rgba(255, 255, 255, .92);
  border-color: var(--line-strong);
  color: var(--ink);
}

body.app-theme input:focus,
body.app-theme select:focus,
body.app-theme textarea:focus {
  background-color: #ffffff;
  border-color: var(--emerald);
}

body.app-theme input::placeholder { color: rgba(40, 80, 100, .45); }
body.app-theme input[readonly] { color: var(--ink-2); }

/* The arrow is drawn with gradients, so it needs re-tinting and its geometry
   restating alongside. */
body.app-theme select {
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%),
                    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 19px) 55%, calc(100% - 13px) 55%;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}


body.app-theme .shot { background: rgba(10, 45, 62, .05); }
body.app-theme .shot.filled { background: rgba(35, 167, 113, .14); }
body.app-theme .shot .tag { background: rgba(255, 255, 255, .88); color: var(--ink-2); }
body.app-theme .shot .actions button.secondary { background: rgba(10, 45, 62, .08); color: var(--ink); }

body.app-theme button.secondary { background: rgba(10, 45, 62, .08); color: var(--ink); }
body.app-theme button.wide.alt {
  background: linear-gradient(135deg, #0f7f93 0%, #084a5c 100%);
  box-shadow: 0 8px 22px rgba(3, 30, 40, .5);
}
body.app-theme button.wide:disabled { background: rgba(10, 45, 62, .10); color: var(--muted); }

body.app-theme .event-mark {
  background: rgba(255, 255, 255, .72);
  color: var(--ink-2);
}
body.app-theme .event-mark span { color: var(--emerald-600); }

/* Admin tables and the stacked rows they become on a phone. */
body.app-theme table.stack tr { background: rgba(255, 255, 255, .62); }
body.app-theme th { color: var(--muted); }

/* Dialogs sit above the glass, so they are nearly solid — reading a scan's
   fields through a moving backdrop is not a kindness. */
body.app-theme dialog {
  background: #ffffff;
  color: var(--ink);
  border: 1px solid var(--line);
}

body.app-theme dialog .foot { background: rgba(10, 45, 62, .04); }
body.app-theme .photos img { border-color: var(--line); }

/* --- following the clip ------------------------------------------------- */
/* The app clip runs from a sunlit office to a near-black room, and no single
   set of text colours survives both ends: dark type disappears into the dark
   stretch, light type into the bright one. common.js measures the frame twice
   a second and puts `backdrop-dark` on <html> and <body> while it is dark;
   everything below is the light set inverted, and nothing else changes.

   Both flags are set because the colour-scheme that decides how the browser
   paints its own <select> popup is read off the root element alone. */

html.backdrop-dark.app-theme { color-scheme: dark; }
body.app-theme.backdrop-dark option { background-color: #082029; color: var(--ink); }

/* The switch is a fade, not a cut — the clip crosses over gradually and the
   interface should read as following it rather than blinking. */
body.app-theme,
body.app-theme header.bar,
body.app-theme .card,
body.app-theme .row,
body.app-theme .tabs,
body.app-theme input,
body.app-theme select,
body.app-theme textarea,
body.app-theme .event-mark,
body.app-theme .cs-toggle,
body.app-theme .um-toggle,
body.app-theme::after {
  transition: background-color .55s ease, color .55s ease,
              border-color .55s ease, background .55s ease;
}

body.app-theme.backdrop-dark {
  --bg: #04161b;
  --surface: rgba(6, 28, 36, .62);
  --surface-2: rgba(255, 255, 255, .07);
  --ink: #e8f7fa;
  --ink-2: #bcdde4;
  --muted: #9dbfc9;
  --line: rgba(130, 220, 235, .18);
  --line-strong: rgba(130, 220, 235, .32);
  --brand: #3fd0e6;
  --brand-ink: #04101f;
  --ok: #46c489;
  --warn: #e0ad4a;
  --bad: #f0837a;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .35);
  --shadow: 0 2px 8px rgba(0, 0, 0, .4), 0 20px 48px rgba(0, 0, 0, .34);

  color-scheme: dark;
}

/* The scrim turns over with the rest: a white wash on a dark frame would grey
   the clip out and lose exactly the contrast it is there to protect. */
body.app-theme.backdrop-dark::after {
  background:
    linear-gradient(to bottom,
      rgba(2, 14, 19, .76) 0%,
      rgba(3, 18, 24, .56) 38%,
      rgba(3, 18, 24, .56) 62%,
      rgba(2, 14, 19, .78) 100%);
}

body.app-theme.backdrop-dark header.bar { background: rgba(4, 22, 29, .74); }
body.app-theme.backdrop-dark header.bar a:hover,
body.app-theme.backdrop-dark header.bar button.link:hover {
  background: rgba(255, 255, 255, .09);
  color: #fff;
}

body.app-theme.backdrop-dark input[type=text],
body.app-theme.backdrop-dark input[type=password],
body.app-theme.backdrop-dark input[type=number],
body.app-theme.backdrop-dark select,
body.app-theme.backdrop-dark textarea {
  background-color: rgba(255, 255, 255, .10);
  border-color: var(--line-strong);
  color: var(--ink);
}

body.app-theme.backdrop-dark input:focus,
body.app-theme.backdrop-dark select:focus,
body.app-theme.backdrop-dark textarea:focus { background-color: rgba(255, 255, 255, .16); }
body.app-theme.backdrop-dark input::placeholder { color: rgba(190, 215, 225, .48); }

body.app-theme.backdrop-dark .shot { background: rgba(255, 255, 255, .06); }
body.app-theme.backdrop-dark .shot .tag { background: rgba(4, 22, 29, .82); }
body.app-theme.backdrop-dark .shot .actions button.secondary,
body.app-theme.backdrop-dark button.secondary { background: rgba(255, 255, 255, .12); color: var(--ink); }
body.app-theme.backdrop-dark button.wide:disabled { background: rgba(255, 255, 255, .10); }

body.app-theme.backdrop-dark .event-mark { background: rgba(4, 22, 29, .62); }
body.app-theme.backdrop-dark .event-mark span { color: var(--emerald); }
body.app-theme.backdrop-dark table.stack tr { background: rgba(255, 255, 255, .05); }

body.app-theme.backdrop-dark dialog { background: #06212a; }
body.app-theme.backdrop-dark dialog .foot { background: rgba(255, 255, 255, .05); }

/* The bar's two controls, and the two popups they open. */
body.app-theme.backdrop-dark .cs-toggle { background: rgba(255, 255, 255, .10); }
body.app-theme.backdrop-dark .cs-toggle:hover { background: rgba(255, 255, 255, .16); border-color: var(--brand); }
body.app-theme.backdrop-dark .cs-menu,
body.app-theme.backdrop-dark .um-menu { background: #07252f; }
body.app-theme.backdrop-dark .cs-menu li:hover,
body.app-theme.backdrop-dark .cs-menu li:focus-visible,
body.app-theme.backdrop-dark .um-menu a:hover,
body.app-theme.backdrop-dark .um-menu button:hover { background: rgba(255, 255, 255, .10); }
body.app-theme.backdrop-dark .cs-tick { color: var(--emerald); }
body.app-theme.backdrop-dark .um-toggle:hover { background: rgba(255, 255, 255, .10); }
body.app-theme.backdrop-dark .um-toggle[aria-expanded="true"] { background: rgba(255, 255, 255, .13); }

/* The navy mark is unreadable against the dark frame; the stylesheet turns it
   white rather than the page swapping the file out mid-clip. */
/* On the dark clips the logo used to be inverted to a white silhouette. That
   worked while it was a single-colour glyph; the real word mark has white
   letters knocked out of the purple BI block, and brightness(0) invert(1)
   fills those back in — it comes out as a solid white slab. So the artwork is
   left exactly as drawn and given a pale plate to sit on instead, which keeps
   the brand colours and still separates it from a near-black backdrop. */
body.app-theme.backdrop-dark header.bar .mark img,
body.app-theme.backdrop-dark .event-mark img {
  background: rgba(255, 255, 255, .86);
  border-radius: 5px;
  padding: 3px 6px;
  box-sizing: content-box;
}


/* --- sign-in over its clip --------------------------------------------- */
/* The page is the clip with one card of glass on it. A thin white wash steadies
   a backdrop that runs from navy in one corner to near-white in the other, so
   the card's dark text holds wherever the drift happens to be.
   Scoped to body.signin-page. */

/* A bright office interior with the NexBi mark projected in the middle of it.
   The flat colour underneath is what shows for the instant before the poster
   paints, so it is taken from the clip's own pale grey rather than left on the
   navy the previous clip needed — a navy flash under a white page reads as a
   fault. */
body.signin-page {
  background: #e8edf2 url("/static/img/login-bg.jpg?v=2026-10-26-home2") center center / cover no-repeat;
}

body.signin-page.backdrop-live { background-image: none; }

/* Above the <video>, below .signin — so it pales the clip wherever the clip
   is actually visible, and the panel's own wash still sits on top of it. */
body.signin-page::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* Barely a veil: the NexBi clip is already pale and carries its own
     lettering, so it is left almost untouched and the card's own glass does
     the rest of the work of holding the form's text. */
  background: rgba(255, 255, 255, .08);
}

/* --- night ---------------------------------------------------------------- */
/* Night is its own clip now, shot dark, so there is nothing left to turn down
   here. The transition stays: crossing from the dusk clip into the night one
   swaps the source, and easing the filter keeps that swap from snapping.
   Anything added back here has to come off the measurement in common.js too —
   a canvas draw reads the video's own pixels and never sees a CSS filter. */

body.app-theme .bg-video { transition: filter 1.2s ease; }

.bg-video {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
}

body.signin-page .signin { position: relative; z-index: 1; }

/* The fields are glass: the clip shows through them. What keeps a half-typed
   password readable is not an opaque fill but the blur behind it — it flattens
   whatever is drifting past into a soft wash, so the dark ink always has
   something even to sit on. The border carries the field's shape instead. */
body.signin-page .signin-card label { color: var(--ink-2); }

body.signin-page .signin-card input {
  background: rgba(255, 255, 255, .14);
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
  backdrop-filter: blur(14px) saturate(1.3);
  border-color: rgba(255, 255, 255, .55);
  color: var(--ink);
}

body.signin-page .signin-card input::placeholder { color: rgba(40, 70, 100, .62); }

/* Focus lifts the glass a little rather than going solid: enough to mark which
   field is live, still see-through. */
body.signin-page .signin-card input:focus {
  background: rgba(255, 255, 255, .28);
  border-color: var(--emerald);
  box-shadow: var(--focus);
}

/* Safari on an older iPhone has no backdrop-filter. There the fill is all the
   contrast there is, so it has to be heavier. */
@supports not (backdrop-filter: blur(1px)) {
  body.signin-page .signin-card input { background: rgba(255, 255, 255, .45); }
  body.signin-page .signin-card input:focus { background: rgba(255, 255, 255, .62); }
}

body.signin-page .pw-toggle { color: var(--muted); }
body.signin-page .pw-toggle:hover { background: var(--surface-2); color: var(--ink); }


/* The counter under Submit has no surface of its own, so on a wide screen it
   landed directly on the clip and disappeared. It gets the same small pill the
   footer mark uses. */
.scan-middle .hint {
  width: fit-content;
  max-width: 100%;
  margin: 10px auto 0;
  padding: 5px 13px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .72);
  backdrop-filter: saturate(1.1) blur(10px);
}

.scan-middle .hint:empty { display: none; }

body.app-theme.backdrop-dark .scan-middle .hint { background: rgba(4, 22, 29, .62); }

/* Disabled is not invisible: the flat tint vanished against a bright frame. */
body.app-theme button.wide:disabled {
  background: rgba(255, 255, 255, .74);
  border: 1px solid var(--line-strong);
  color: var(--ink-2);
  backdrop-filter: saturate(1.1) blur(10px);
}

body.app-theme.backdrop-dark button.wide:disabled {
  background: rgba(255, 255, 255, .12);
  border-color: var(--line);
}

/* The gallery fallback under the Scan button: present, not competing. */
.upload-link {
  display: block;
  width: fit-content;
  margin: 10px auto 0;
  font-size: 13.5px;
}

/* With the tiles hidden until a side is captured, the row needs the gap
   above it only when it is actually showing something. */
.shots:not([hidden]) { margin-top: 16px; }

/* The company step is a heading and a dropdown, nothing else, so it does not
   need the full card padding or a full-height control. */
.card.compact { padding: 14px 16px; }
.card.compact .step { margin-bottom: 10px; }

select.slim { padding: 9px 12px; background-position: calc(100% - 17px) 54%, calc(100% - 11px) 54%; }

/* --- scan page layout ---------------------------------------------------- */
/* The capture card is the page: it sits in the middle of whatever height the
   screen has. The company is picked once at the start of a session and then
   left alone, so it lives in the top corner rather than taking a row of its
   own above the thing people actually use. */

.scan-layout {
  min-height: calc(100dvh - 58px);
  display: flex;
  flex-direction: column;
  padding-top: 12px;
}

.company-pick {
  align-self: flex-end;
  width: min(260px, 60%);
  margin-bottom: 10px;
}

.company-pick select { font-size: 13.5px; }

.scan-middle {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Centred, but never pushed under the corner control on a short screen. */
  min-height: 0;
}

.scan-middle .card { margin-bottom: 14px; }

/* The capture card is a phone-shaped thing: given a desktop's width it became
   a 700px-wide Scan button, which reads as a banner rather than a control. It
   keeps a phone's measure and sits in the middle of the screen instead. */
@media (min-width: 600px) {
  .scan-middle { width: 100%; max-width: 440px; margin-inline: auto; }
}

@media (max-width: 420px) {
  .company-pick { width: 72%; }
}

/* The scan page's title is the group name, so it carries a wordmark's
   spacing rather than a sentence's. */
body.app-theme header.bar h1 { letter-spacing: .14em; font-weight: 700; }

/* --- company switcher ---------------------------------------------------- */
/* Shaped after Odoo's: a button carrying the current company, and a list
   where the active one is ticked. Built rather than a native select, which
   cannot hold the icon or the tick and whose popup the browser draws itself. */

.company-switcher { position: relative; }

.cs-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 8px 12px;
  background: rgba(255, 255, 255, .85);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  color: var(--ink);
  font-size: 13.5px;
  font-weight: 600;
  text-align: left;
}

.cs-toggle:hover { background: #ffffff; border-color: var(--teal); }
.cs-toggle:focus-visible { outline: none; box-shadow: var(--focus); }
.cs-current { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cs-caret { flex: none; opacity: .7; transition: transform .15s; }
.cs-toggle[aria-expanded="true"] .cs-caret { transform: rotate(180deg); }

.cs-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  z-index: 20;
  min-width: 100%;
  max-width: 300px;
  max-height: 60vh;
  overflow-y: auto;
  margin: 0;
  padding: 6px;
  list-style: none;
  background: #ffffff;
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  box-shadow: var(--shadow);
}

.cs-menu li {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 9px 10px;
  border-radius: 8px;
  font-size: 13.5px;
  color: var(--ink-2);
  cursor: pointer;
  white-space: nowrap;
}

.cs-menu li:hover,
.cs-menu li:focus-visible { background: rgba(10, 45, 62, .07); outline: none; color: var(--ink); }

/* The tick holds its space whether or not it is showing, so the names line
   up in a column instead of shifting as the selection moves. */
.cs-tick { flex: none; visibility: hidden; color: var(--emerald-600); }
.cs-menu li[aria-selected="true"] { color: var(--ink); font-weight: 650; }
.cs-menu li[aria-selected="true"] .cs-tick { visibility: visible; }

/* With an Admin link present the bar runs out of room on a phone and the
   labels wrap to two lines. */
@media (max-width: 430px) {
  header.bar { gap: 2px; }
  header.bar h1 { font-size: 15px; }
  header.bar a,
  header.bar button.link { padding: 7px 7px; font-size: 13px; }
}

/* --- the top bar's controls ---------------------------------------------- */
/* The company switcher and the user chip both live in the bar, as Odoo has
   them. The chip carries what used to be loose links in the header. */

/* Everything after the word mark is held against the right edge by the user
   menu, not by the title: the title is the flexible gap only while it is
   shown, and it is hidden on a phone and on the admin page, which left the
   chip sitting against the logo. Where the company switcher is present it
   takes the push instead and the chip tucks in beside it — two auto margins
   in one row would split the free space and strand the switcher mid-bar. */
header.bar .user-menu { margin-left: auto; }
header.bar .company-switcher { margin-left: auto; max-width: 240px; }
header.bar .company-switcher ~ .user-menu { margin-left: 0; }
header.bar .cs-toggle { padding: 6px 10px; font-size: 13px; }

.user-menu { position: relative; flex: none; }

.um-toggle {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 5px 9px 5px 5px;
  background: none;
  border: 1px solid transparent;
  border-radius: 999px;
  color: var(--ink);
}

.um-toggle:hover { background: rgba(10, 45, 62, .07); }
.um-toggle[aria-expanded="true"] { background: rgba(10, 45, 62, .09); border-color: var(--line-strong); }

.um-avatar {
  width: 30px;
  height: 30px;
  flex: none;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: linear-gradient(135deg, var(--emerald), var(--teal));
  color: #ffffff;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: .02em;
}

.um-name { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.15; }
.um-name b { font-size: 13px; font-weight: 650; }
.um-name small { font-size: 10.5px; color: var(--muted); letter-spacing: .04em; }
.um-caret { opacity: .6; flex: none; }

.um-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  z-index: 30;
  min-width: 190px;
  padding: 6px;
  background: #ffffff;
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
}

.um-menu a,
.um-menu button {
  display: block;
  width: 100%;
  padding: 9px 11px;
  border: none;
  border-radius: 8px;
  background: none;
  color: var(--ink-2);
  font: inherit;
  font-size: 13.5px;
  font-weight: 500;
  text-align: left;
  text-decoration: none;
}

.um-menu a:hover,
.um-menu button:hover { background: rgba(10, 45, 62, .07); color: var(--ink); }
.um-menu hr { border: none; border-top: 1px solid var(--line); margin: 5px 4px; }
.um-menu #umSignOut { color: var(--bad); }

/* On a phone the bar holds a lot: the chip keeps its avatar and drops its
   name, and the switcher takes whatever is left. */
@media (max-width: 560px) {
  header.bar h1 { display: none; }
  header.bar .company-switcher { max-width: none; }
  .um-name { display: none; }
  .um-toggle { padding: 4px; }
}

#counter { text-align: center; margin: 10px 0 0; }
