Files
Behavision/web/src/styles.css
Suriyakumarvijayanayagam 51b9cb9743 The assistant is Loya, and she lives in the top-right corner
A name, a voice and a door. The prompt now asks for a colleague on the
shop floor - answer first, one to three sentences, the shop's name and
the person's name, the one thing to do next - instead of a report with
headings. Both apps put her behind the Loyaly mark in the top-right
corner of every screen, because a buddy you have to find in a sidebar
is not around.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
2026-09-19 13:42:35 +05:30

571 lines
30 KiB
CSS

/* Behavision platform — head office.
Shares the desktop app's palette on purpose: they are one product, and an
owner who sees a shop PC and then this should not wonder whether they are
looking at the same system. The layout differs because the questions do —
the desktop is an instrument panel watched all day, this is read on a laptop
and scrolls. */
:root {
--ground: #0E1317;
--surface: #161D23;
--surface-2: #1D262D;
--line: #27333B;
--line-soft: #1F2A31;
--ink: #E7EEF3;
--ink-2: #B4C2CC;
--muted: #7C8B97;
--accent: #45B0C7;
--accent-dim:#123039;
--ok: #4FB37B;
--ok-dim: #12291F;
--warn: #E0A33A;
--warn-dim: #2C2313;
--bad: #E0655A;
--bad-dim: #2B1917;
--radius: 10px;
--mono: "SFMono-Regular", ui-monospace, Menlo, Consolas, monospace;
}
* { box-sizing: border-box; margin: 0; }
html { color-scheme: dark; }
body {
background: var(--ground);
color: var(--ink);
font: 15px/1.55 system-ui, -apple-system, "Segoe UI", sans-serif;
-webkit-font-smoothing: antialiased;
}
button, input, select, textarea { font: inherit; color: inherit; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
code { font-family: var(--mono); font-size: .9em; }
h1 { font-size: 22px; font-weight: 620; letter-spacing: -.015em; }
h2 { font-size: 16px; font-weight: 600; }
h3 { font-size: 13px; font-weight: 600; text-transform: uppercase;
letter-spacing: .08em; color: var(--muted); }
.sub { color: var(--muted); font-size: 13.5px; }
.num { font-variant-numeric: tabular-nums; }
.ok { color: var(--ok); } .warn { color: var(--warn); } .bad { color: var(--bad); }
/* Those three are text utilities and they are inherited, so a card that carried
`ok` as its own state class tinted every word inside it green. Card severity
is therefore namespaced `state-*` below - a structural state and a colour
utility must not share a name. */
/* ------------------------------------------------------------- shell ---- */
.app { min-height: 100vh; display: flex; flex-direction: column; }
.topbar {
position: sticky; top: 0; z-index: 20;
display: flex; align-items: center; gap: 28px;
padding: 0 24px; height: 60px;
background: var(--surface); border-bottom: 1px solid var(--line);
}
.brand { display: flex; align-items: center; gap: 11px; }
.brand strong { display: block; font-size: 15px; font-weight: 620; letter-spacing: -.01em; }
.brand .org { display: block; font-size: 12px; color: var(--muted); }
.mark {
width: 22px; height: 22px; flex: none;
background: url(/loyaly-mark.png) center / contain no-repeat;
}
.mark.big { width: 40px; height: 40px; margin-bottom: 14px; }
.tabs { display: flex; gap: 2px; margin-right: auto; }
.tabs button {
background: none; border: 0; border-radius: 7px; padding: 7px 13px;
color: var(--ink-2); cursor: pointer; font-size: 14px;
}
.tabs button:hover { background: var(--surface-2); color: var(--ink); }
.tabs button[aria-current="page"] { background: var(--accent-dim); color: var(--accent); font-weight: 550; }
.who { display: flex; align-items: center; gap: 12px; }
.who .name { font-size: 13.5px; }
.who .role { font-size: 11px; color: var(--muted); text-transform: uppercase;
letter-spacing: .07em; }
.page { flex: 1; padding: 26px 24px 64px; max-width: 1280px; width: 100%;
margin: 0 auto; display: flex; flex-direction: column; gap: 20px; }
.head { display: flex; align-items: flex-end; gap: 16px; flex-wrap: wrap; }
.head h1 { margin-right: auto; }
.head .sub { padding-bottom: 2px; }
/* ------------------------------------------------------------ buttons --- */
button.primary {
background: var(--accent); color: #04161B; border: 0; border-radius: 7px;
padding: 9px 16px; font-weight: 600; cursor: pointer;
}
button.primary:disabled { opacity: .5; cursor: default; }
button.ghost {
background: none; border: 1px solid var(--line); border-radius: 7px;
padding: 7px 13px; color: var(--ink-2); cursor: pointer;
}
button.ghost:hover { border-color: var(--muted); color: var(--ink); }
.segmented { display: flex; border: 1px solid var(--line); border-radius: 8px;
overflow: hidden; }
.segmented button {
background: none; border: 0; padding: 7px 14px; color: var(--ink-2);
cursor: pointer; font-size: 13.5px;
}
.segmented button + button { border-left: 1px solid var(--line); }
.segmented button[aria-current] { background: var(--accent-dim); color: var(--accent); }
/* -------------------------------------------------------------- cards --- */
.card { background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius); }
.grid { display: grid; gap: 14px; }
.grid.sites { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 16px; }
.grid.stats { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
/* Shops, laid out like cameras: the shop's own camera view IS the card.
The severity stripe survives on the left edge - a card that is entirely red
reads as broken even when the shop is merely busy. */
.card.site { overflow: hidden; padding: 0; cursor: pointer;
border-left: 3px solid var(--line);
transition: border-color .15s ease, transform .15s ease; }
.card.site.state-ok { border-left-color: var(--ok); }
.card.site.state-warn { border-left-color: var(--warn); }
.card.site.state-bad { border-left-color: var(--bad); }
.card.site.state-idle { border-left-color: var(--muted); }
.card.site:hover { transform: translateY(-1px); }
.card.site:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .card.site { transition: none; } }
/* Three numbers, evenly weighted, because they are three different questions
and none of them is the headline. The headline is the verdict line below. */
.metrics { display: grid; grid-template-columns: repeat(3, 1fr);
border-top: 1px solid var(--line-soft); }
.metric { padding: 11px 6px; text-align: center; }
.metric + .metric { border-left: 1px solid var(--line-soft); }
.metric b { display: block; font-size: 15px; font-weight: 600;
font-variant-numeric: tabular-nums; letter-spacing: -.01em; }
.metric b.ok { color: var(--ink); } .metric b.warn { color: var(--warn); }
.metric b.bad { color: var(--bad); } .metric b.idle { color: var(--muted); }
.metric span { display: block; margin-top: 1px; font-size: 11px; color: var(--muted); }
/* A shopfront, drawn for the same reason the camera tile draws a lens: most
deployments store no images, so an empty tile is the ordinary state and it
should still read as a shop. */
.shopmark { width: 38px; height: 30px; fill: none; stroke: var(--line);
stroke-width: 2; stroke-linejoin: round; }
.pill {
display: inline-block; padding: 3px 9px; border-radius: 20px; flex: none;
font-size: 11.5px; font-weight: 550; letter-spacing: .01em;
background: var(--surface-2); color: var(--ink-2);
}
.pill.ok { background: var(--ok-dim); color: var(--ok); }
.pill.warn { background: var(--warn-dim); color: var(--warn); }
.pill.bad { background: var(--bad-dim); color: var(--bad); }
.pill.new { background: var(--accent-dim); color: var(--accent); }
.dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%;
background: var(--muted); margin-right: 7px; vertical-align: 1px; }
.dot.ok { background: var(--ok); } .dot.warn { background: var(--warn); }
/* ------------------------------------------------------------- stats ---- */
.card.stat { padding: 16px 18px; display: flex; flex-direction: column; gap: 3px; }
.stat .value { font-size: 30px; font-weight: 620; letter-spacing: -.02em;
font-variant-numeric: tabular-nums; line-height: 1.15; }
.stat .label { font-size: 13.5px; color: var(--ink-2); }
.stat .note { font-size: 12.5px; color: var(--muted); margin-top: 3px; }
.banner { padding: 12px 16px; border-radius: var(--radius); font-size: 14px;
display: flex; gap: 16px; align-items: flex-start; }
.banner.warn { background: var(--warn-dim); border: 1px solid #4A3A18; color: #F0D9A6; }
.banner.ok { background: var(--ok-dim); border: 1px solid #1E4534; color: #C6E9D6; }
.banner > div { flex: 1; }
.creds { display: flex; gap: 26px; margin-top: 10px; flex-wrap: wrap; }
.creds dt { font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
color: var(--muted); }
.creds dd { font-family: var(--mono); font-size: 14px; user-select: all; }
/* ----------------------------------------------------------- arrivals --- */
.arrivals { list-style: none; padding: 0; display: grid; gap: 8px; }
.card.arrival { display: flex; align-items: center; gap: 14px; padding: 11px 14px; }
.face { width: 46px; height: 46px; border-radius: 50%; flex: none;
overflow: hidden; object-fit: cover; background: var(--surface-2); }
/* .face is a <span> wrapping the picture rather than the <img> itself, because
a face served from this server's own database has to be fetched with the
session before it can be shown. The image inside still has to fill the
circle, and the wrapper clips it. */
.face > img { width: 100%; height: 100%; object-fit: cover; display: block; }
.face.initials { display: grid; place-items: center; color: var(--muted);
font-size: 15px; font-weight: 600; letter-spacing: .02em; }
.who-col { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.who-col strong { font-weight: 570; }
.attrs { font-size: 12.5px; color: var(--muted); text-transform: capitalize; }
/* ------------------------------------------------------------ tables ---- */
.toolbar { display: flex; gap: 10px; }
.search {
flex: 1; max-width: 380px; background: var(--surface);
border: 1px solid var(--line); border-radius: 8px; padding: 9px 13px;
}
.search::placeholder { color: var(--muted); }
/* Every placeholder, not just the search box. On this ground a browser's
default placeholder is nearly as bright as a typed value, so an example in a
field reads as an answer already given - an operator saw "counter PC" in the
PC-label box and reasonably believed they had labelled it. */
input::placeholder, textarea::placeholder { color: var(--muted); opacity: 1; }
.tablewrap { overflow-x: auto; background: var(--surface);
border: 1px solid var(--line); border-radius: var(--radius); }
table.rows { border-collapse: collapse; width: 100%; min-width: 620px; }
table.rows th, table.rows td { text-align: left; padding: 11px 16px;
border-bottom: 1px solid var(--line-soft); }
table.rows tr:last-child td { border-bottom: 0; }
table.rows th { font-size: 11.5px; text-transform: uppercase; letter-spacing: .08em;
color: var(--muted); font-weight: 600; background: var(--surface-2); }
table.rows tbody tr { cursor: pointer; }
table.rows tbody tr:hover { background: var(--surface-2); }
table.rows td.num { font-variant-numeric: tabular-nums; }
/* ------------------------------------------------------------- chart ---- */
.chart-card { padding: 18px; }
.chart-head { margin-bottom: 16px; }
.peak { font-size: 12.5px; color: var(--muted); margin-bottom: 8px; }
.peak b { color: var(--ink-2); font-weight: 600; font-variant-numeric: tabular-nums; }
.chart { display: flex; align-items: flex-end; gap: 3px; height: 190px;
overflow-x: auto; padding-bottom: 4px; }
.col { flex: 1; min-width: 16px; display: flex; flex-direction: column;
align-items: center; gap: 6px; height: 100%; }
.bars { flex: 1; width: 100%; display: flex; flex-direction: column;
justify-content: flex-end; }
.bar { display: block; width: 100%; border-radius: 2px 2px 0 0; }
.bar.new { background: var(--accent); }
.bar.returning { background: var(--accent-dim); border-radius: 0; }
.col:hover .bar.returning { background: #1B4552; }
.tick { font-size: 10.5px; color: var(--muted); white-space: nowrap;
font-variant-numeric: tabular-nums; }
.legend { display: flex; gap: 18px; align-items: center; margin-top: 14px;
padding-top: 12px; border-top: 1px solid var(--line-soft);
font-size: 12.5px; color: var(--ink-2); }
.legend span { display: flex; align-items: center; gap: 7px; }
.swatch { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }
.swatch.new { background: var(--accent); }
.swatch.returning { background: var(--accent-dim); }
.pad { padding: 24px 0; }
/* ------------------------------------------------------------ drawer ---- */
.overlay { position: fixed; inset: 0; background: rgba(4, 8, 10, .62);
display: flex; justify-content: flex-end; z-index: 50; }
.drawer { width: min(560px, 100%); background: var(--surface);
border-left: 1px solid var(--line); height: 100%; overflow-y: auto;
display: flex; flex-direction: column; }
.drawer.narrow { width: min(440px, 100%); }
/* Sticky, and inside the scrolling panel rather than positioned against the
fixed overlay — otherwise Close prints itself over whatever has scrolled
under it. */
.drawer-head { position: sticky; top: 0; z-index: 1; display: flex;
align-items: flex-start; gap: 16px; padding: 18px 22px;
background: var(--surface); border-bottom: 1px solid var(--line); }
.drawer-head h2 { margin-right: auto; }
.drawer-head > div { flex: 1; }
.drawer-body { padding: 20px 22px; display: flex; flex-direction: column; gap: 14px; }
.drawer-body + .drawer-body { border-top: 1px solid var(--line-soft); }
fieldset { border: 0; padding: 0; margin: 0; display: flex;
flex-direction: column; gap: 14px; }
fieldset:disabled { opacity: .6; }
label { display: flex; flex-direction: column; gap: 6px; font-size: 13px;
color: var(--ink-2); }
label input[type="text"], label input[type="email"], label input[type="password"],
label input:not([type]), .drawer input {
background: var(--ground); border: 1px solid var(--line); border-radius: 7px;
padding: 9px 12px; color: var(--ink); font-size: 14.5px;
}
label .hint { font-size: 12px; color: var(--muted); line-height: 1.45; }
label.check { flex-direction: row; align-items: center; gap: 9px; }
label.check input { width: auto; }
.timeline { list-style: none; padding: 0; display: grid; gap: 8px; }
.timeline li { display: flex; align-items: center; gap: 12px; font-size: 13.5px;
padding: 9px 12px; background: var(--ground); border-radius: 7px; }
.timeline .when { font-variant-numeric: tabular-nums; }
.timeline .where { color: var(--muted); margin-right: auto; }
/* ------------------------------------------------------------ states ---- */
.state { display: flex; flex-direction: column; align-items: center; gap: 8px;
padding: 64px 20px; text-align: center; color: var(--ink-2); }
.state .sub { max-width: 46ch; }
.boot { min-height: 100vh; display: flex; align-items: center; justify-content: center;
gap: 10px; color: var(--muted); }
.error { color: var(--bad); font-size: 13.5px; }
.spinner { width: 14px; height: 14px; border-radius: 50%;
border: 2px solid var(--line); border-top-color: var(--accent);
animation: spin .7s linear infinite; display: inline-block; }
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation: none; } }
/* ------------------------------------------------------------ sign in --- */
.signin { min-height: 100vh; display: grid; place-items: center; padding: 24px; }
.signin .card { width: min(380px, 100%); padding: 30px; display: flex;
flex-direction: column; gap: 14px; }
.signin h1 { font-size: 20px; }
.signin .sub { margin-top: -8px; margin-bottom: 6px; }
.signin .foot { font-size: 12.5px; color: var(--muted); text-align: center;
margin-top: 4px; line-height: 1.5; }
@media (max-width: 720px) {
.topbar { height: auto; flex-wrap: wrap; padding: 12px 16px; gap: 12px; }
.tabs { order: 3; width: 100%; overflow-x: auto; }
.page { padding: 18px 16px 48px; }
.who .name { display: none; }
}
.pair { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
select { background: var(--ground); border: 1px solid var(--line);
border-radius: 7px; padding: 9px 12px; color: var(--ink);
font-size: 14.5px; }
button.ghost.danger { border-color: #4A211D; color: var(--bad); }
button.ghost.danger:hover { border-color: var(--bad); }
/* ------------------------------------------------------- camera setup --- */
.drawer.wizard { width: min(560px, 100%); }
/* The step rail encodes real sequence: each step is unanswerable until the one
before it has passed, which is why they are numbered rather than tabbed. */
.steps { list-style: none; display: flex; gap: 4px; padding: 14px 22px;
margin: 0; border-bottom: 1px solid var(--line-soft);
background: var(--surface); position: sticky; top: 62px; z-index: 1; }
.steps li { flex: 1; display: flex; align-items: center; gap: 7px;
font-size: 12.5px; color: var(--muted); min-width: 0; }
.steps li .dot { width: 20px; height: 20px; border-radius: 50%; flex: none;
display: grid; place-items: center; font-size: 11px;
background: var(--surface-2); color: var(--muted); }
.steps li.now { color: var(--ink); font-weight: 550; }
.steps li.now .dot { background: var(--accent); color: #04161B; }
.steps li.done .dot { background: var(--ok-dim); color: var(--ok); }
.waiting { display: flex; gap: 12px; align-items: center; padding: 14px 16px;
background: var(--ground); border: 1px solid var(--line);
border-radius: var(--radius); }
.waiting > div { display: flex; flex-direction: column; gap: 2px; }
.outcome { border: 1px solid var(--line); border-left-width: 3px;
border-radius: var(--radius); padding: 14px 16px;
display: flex; flex-direction: column; gap: 12px; }
.outcome.ok { border-left-color: var(--ok); background: var(--ok-dim); }
.outcome.warn { border-left-color: var(--warn); background: var(--warn-dim); }
.outcome.bad { border-left-color: var(--bad); background: var(--bad-dim); }
.outcome-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.outcome-head b { font-size: 14.5px; }
.proof { width: 100%; border-radius: 6px; display: block; background: #05090B; }
.advice { margin: 0; padding-left: 18px; display: grid; gap: 6px;
font-size: 13.5px; color: var(--ink-2); line-height: 1.5; }
/* Connected and verified are different claims; the card states both. */
.verified { display: flex; align-items: center; gap: 8px; margin-top: 10px;
font-size: 13px; }
.verified .mark { width: 17px; height: 17px; border-radius: 50%; flex: none;
display: grid; place-items: center; font-size: 10.5px; }
.verified.ok { color: var(--ok); }
.verified.ok .mark { background: var(--ok-dim); }
.verified.warn { color: var(--warn); }
.verified.warn .mark { background: var(--warn-dim); }
.verified.bad { color: var(--bad); }
.verified.bad .mark { background: var(--bad-dim); }
.verified.idle { color: var(--muted); }
.verified.idle .mark { background: var(--surface-2); }
/* ------------------------------------------------------- site check ----- */
.checklist { list-style: none; padding: 0; margin: 0; display: grid; gap: 2px; }
.checklist li { display: flex; gap: 12px; padding: 13px 14px;
background: var(--ground); border-radius: 8px;
border-left: 3px solid var(--line); }
.checklist li > div { display: flex; flex-direction: column; gap: 3px; flex: 1; }
.checklist li.pass { border-left-color: var(--ok); }
.checklist li.warn { border-left-color: var(--warn); }
.checklist li.fail { border-left-color: var(--bad); }
.checklist li.unknown { border-left-color: var(--muted); }
.checklist .mark { width: 20px; height: 20px; border-radius: 50%; flex: none;
display: grid; place-items: center; font-size: 11px;
background: var(--surface-2); color: var(--muted); }
.checklist li.pass .mark { background: var(--ok-dim); color: var(--ok); }
.checklist li.warn .mark { background: var(--warn-dim); color: var(--warn); }
.checklist li.fail .mark { background: var(--bad-dim); color: var(--bad); }
.advice-line { font-size: 13px; color: var(--warn); line-height: 1.45; }
.checklist li.pass .advice-line { color: var(--muted); }
/* ------------------------------------------------- pictures as cards --- */
/* Shared by the camera and shop cards: the picture IS the card, and every
technical detail lives one click away. */
.grid.cams { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px; }
.card.cam { overflow: hidden; border-left: 0; padding: 0; cursor: pointer;
transition: border-color .15s ease, transform .15s ease; }
.card.cam:hover { border-color: var(--muted); transform: translateY(-1px); }
.card.cam:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .card.cam { transition: none; } }
.shot { position: relative; aspect-ratio: 16 / 10; background: #05090B;
display: grid; place-items: center; }
.shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.noshot { display: flex; flex-direction: column; align-items: center; gap: 10px;
color: var(--muted); font-size: 12.5px; text-align: center;
padding: 0 28px; line-height: 1.5; }
/* A lens, drawn rather than an emoji: an empty tile that still reads as a
camera, instead of a black hole with an apology in it. */
.lens { width: 34px; height: 34px; border-radius: 50%;
border: 2px solid var(--line); position: relative; }
.lens::after { content: ''; position: absolute; inset: 7px; border-radius: 50%;
border: 2px solid var(--line-soft); }
/* A gradient, so white text stays legible over a bright shop floor. */
.shot-over { position: absolute; inset: auto 0 0 0; display: flex;
align-items: flex-end; justify-content: space-between; gap: 10px;
padding: 26px 14px 12px;
background: linear-gradient(transparent, rgba(4,8,10,.85)); }
.shot-name b { display: block; font-size: 14.5px; font-weight: 600;
letter-spacing: -.01em; }
.shot-name span { display: block; font-size: 11.5px; color: #9FB0BA; margin-top: 1px; }
.status { display: flex; align-items: center; gap: 6px; flex: none;
font-size: 11px; font-weight: 550; letter-spacing: .01em;
padding: 4px 9px; border-radius: 20px; white-space: nowrap;
background: rgba(14,19,23,.72); backdrop-filter: blur(6px);
color: var(--ink-2); }
.status i { width: 6px; height: 6px; border-radius: 50%; background: var(--muted); }
.status.ok { color: #8FE0B4; } .status.ok i { background: var(--ok); }
.status.warn { color: #EFC77C; } .status.warn i { background: var(--warn); }
.status.bad { color: #F0A79E; } .status.bad i { background: var(--bad); }
.status.idle i { background: var(--muted); }
.shot-age { position: absolute; top: 10px; right: 12px; font-size: 10.5px;
color: #9FB0BA; background: rgba(4,8,10,.6); padding: 2px 7px;
border-radius: 20px; backdrop-filter: blur(6px); }
/* One line, and it is the line that matters. */
.verdict { display: flex; align-items: center; gap: 10px; padding: 12px 14px;
font-size: 13px; border-top: 1px solid var(--line-soft); }
.verdict .words { flex: 1; min-width: 0; }
.verdict .go { color: var(--muted); font-size: 14px; }
.verdict .mark { width: 18px; height: 18px; border-radius: 50%; flex: none;
display: grid; place-items: center; font-size: 10.5px; }
.verdict.ok { color: var(--ok); } .verdict.ok .mark { background: var(--ok-dim); }
.verdict.warn { color: var(--warn); } .verdict.warn .mark { background: var(--warn-dim); }
.verdict.bad { color: var(--bad); } .verdict.bad .mark { background: var(--bad-dim); }
.verdict.idle { color: var(--muted); }.verdict.idle .mark { background: var(--surface-2); }
/* Claiming a shop PC, inside the shop drawer. */
.claim { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--line-soft); }
.claim h3 { font-size: 14px; font-weight: 600; margin-bottom: 6px; }
.claim .field { display: block; margin: 12px 0; }
.claim .field span { display: block; font-size: 12.5px; color: var(--muted); margin-bottom: 5px; }
.claim .field input { width: 100%; background: var(--ground); border: 1px solid var(--line);
border-radius: 7px; padding: 9px 12px; }
.claim .row { display: flex; gap: 8px; margin-top: 12px; }
/* Big, monospaced and selectable: this gets read down a phone line and typed
into another machine, so ambiguity between O and 0 is a support call. */
.claim .code { font-family: var(--mono); font-size: 19px; letter-spacing: .08em;
background: var(--ground); border: 1px solid var(--line);
border-radius: 8px; padding: 14px 12px; text-align: center;
margin: 12px 0 10px; user-select: all; word-break: break-all; }
/* ---------------------------------------------------------- assistant --- */
.with-assistant { display: flex; flex: 1; min-height: 0; }
.with-assistant .page { flex: 1; min-width: 0; }
.ask-btn { display: inline-flex; align-items: center; gap: 7px; background: var(--accent-dim); color: var(--accent); border: 0;
border-radius: 7px; padding: 7px 13px; cursor: pointer;
font-size: 13.5px; font-weight: 550; display: flex; align-items: center; gap: 7px; }
.ask-btn:hover, .ask-btn.on { background: var(--accent); color: #04161B; }
.assistant { width: 360px; flex: none; border-left: 1px solid var(--line);
background: var(--surface); display: flex; flex-direction: column;
position: sticky; top: 60px; height: calc(100vh - 60px); }
.assistant-head { display: flex; align-items: flex-start; gap: 12px;
padding: 15px 16px; border-bottom: 1px solid var(--line-soft); }
.assistant-head > div { flex: 1; display: flex; flex-direction: column; gap: 2px; }
.assistant-body { flex: 1; overflow-y: auto; padding: 16px;
display: flex; flex-direction: column; gap: 12px; }
.assistant-ask { display: flex; gap: 8px; padding: 12px 14px;
border-top: 1px solid var(--line-soft); }
.assistant-ask input { flex: 1; min-width: 0; background: var(--ground);
border: 1px solid var(--line); border-radius: 8px;
padding: 9px 12px; }
.bubble { padding: 11px 13px; border-radius: 12px; font-size: 14px;
line-height: 1.55; white-space: pre-wrap; max-width: 100%; }
.bubble.user { background: var(--accent-dim); color: var(--ink);
align-self: flex-end; border-bottom-right-radius: 4px; }
.bubble.assistant { background: var(--ground); border: 1px solid var(--line-soft);
border-bottom-left-radius: 4px; }
.bubble.failed { border-color: #4A211D; color: var(--bad); }
.bubble.assistant-thinking { color: var(--muted); display: flex; align-items: center; gap: 9px; }
.used { display: block; margin-top: 8px; padding-top: 7px;
border-top: 1px solid var(--line-soft);
font-size: 11.5px; color: var(--muted); }
.suggest { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.chip { background: var(--ground); border: 1px solid var(--line); border-radius: 20px;
padding: 7px 13px; color: var(--ink-2); cursor: pointer; font-size: 13px;
text-align: left; }
.chip:hover { border-color: var(--accent); color: var(--accent); }
@media (max-width: 900px) {
.with-assistant { flex-direction: column; }
.assistant { width: 100%; height: 60vh; position: static; border-left: 0;
border-top: 1px solid var(--line); }
}
/* ------------------------------------------------------- live camera view */
/* Deliberately a small affordance on the picture rather than a big play
button: opening it makes a shop PC start uploading, so it is an action
somebody chooses, not one a page does on their behalf. */
.card.cam .watch {
position: absolute; left: 10px; top: 10px; z-index: 2;
display: inline-flex; align-items: center; gap: 6px;
padding: 4px 9px; border-radius: 999px; border: 0; cursor: pointer;
font: inherit; font-size: 11.5px; font-weight: 600; letter-spacing: .02em;
color: #fff; background: rgba(12, 16, 20, .72);
backdrop-filter: blur(6px);
}
.card.cam .watch:hover { background: rgba(12, 16, 20, .9); }
.card.cam .watch i {
width: 7px; height: 7px; border-radius: 50%; background: #E5484D;
box-shadow: 0 0 0 0 rgba(229, 72, 77, .7);
animation: livepulse 2s infinite;
}
@keyframes livepulse {
70% { box-shadow: 0 0 0 6px rgba(229, 72, 77, 0); }
100% { box-shadow: 0 0 0 0 rgba(229, 72, 77, 0); }
}
@media (prefers-reduced-motion: reduce) {
.card.cam .watch i { animation: none; }
}
.drawer.live { max-width: 760px; }
.liveshot {
position: relative; background: #000; border-radius: 10px; overflow: hidden;
aspect-ratio: 16 / 9;
}
.liveshot img { width: 100%; height: 100%; object-fit: contain; display: block; }
.livewait {
position: absolute; inset: 0; display: grid; place-items: center;
font-size: 13px; color: rgba(255, 255, 255, .78);
background: rgba(0, 0, 0, .35);
}
/* ---------------------------------------------------------------- team --- */
.rows tr.inactive td { opacity: .55; }
.rows .role { text-transform: capitalize; }
.rows td.right { text-align: right; }
.pill.muted { margin-left: 8px; font-size: 11px; padding: 1px 7px; border-radius: 999px;
background: var(--surface-2); color: var(--muted); vertical-align: middle; }
.pending { margin-top: 26px; }
.pending h2 { font-size: 14px; font-weight: 600; color: var(--muted); margin: 0 0 10px; }
.invites { list-style: none; padding: 0; display: grid; gap: 8px; }
.card.invite { display: flex; align-items: center; justify-content: space-between;
gap: 14px; padding: 11px 14px; }
.card.invite .sub { display: block; }
/* The code is read aloud and typed in, so it is set wide and monospaced.
Grouped in sixes by the server for the same reason. */
.creds code.big { font-size: 16px; letter-spacing: .06em; }
/* A button that reads as a link. Used where the action is a change of screen
rather than a submission, so it must not look like the primary button next
to it. */
.linkish { background: none; border: 0; padding: 0; font: inherit;
color: var(--accent); cursor: pointer; text-decoration: underline;
text-underline-offset: 2px; }
.linkish:hover { opacity: .8; }
/* The code is read off a screen or a phone call, so it is set wide. */
.codefield { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
letter-spacing: .04em; text-transform: uppercase; }
.ask-btn .mark { width: 18px; height: 18px; }