/* shared.css — the chrome that belongs to the app rather than to a screen.

   Two things live here, both of which were written inside board.css while the
   board was the only screen that used them, and both of which a second screen
   now needs: the bottom tab bar, and the live beacon. A mark that differs by
   a pixel between two screens of one app reads as two apps.

   Linked by board.html, stock.html and session.html. The tab bar is NOT on
   session.html: a session is a mode a rep is inside of, with its own composer
   on the floor of the screen, and a nav bar there would offer to walk away
   mid-sentence and fight the composer for the same 60px. You leave a session
   by its back arrow, which is a decision, not a tab. Session takes this file
   for the beacon, which it was already asking for and not getting. */

/* ── NOTIF-01 · somebody just walked in ────────────────────────────────── */
/* The app's core promise, and the reason it is on a phone at all: the moment
   a client opens their VIP showroom, the rep is told — wherever they are and
   whatever they are doing.

   It arrives OVER the screen rather than pushing it down, because that is
   what a notification is: it did not come from the page it landed on, and a
   banner that reflows the header claims to be part of it. Green and the live
   beacon, which is what this surface already means by "somebody is waiting"
   (`.cb-now`, `.cb-tab.is-live`).

   NOT RED. Red on this app is a problem — a request going cold, a promise
   about to break. A client opening their showroom is the opposite: it is a
   sale starting, and the one alert a rep should want to see. It is urgent
   because it is good, and green is how the card says which of the two it is
   before the rep has read a word of it.

   IT DOES NOT LEAVE ON ITS OWN. A client standing in a showroom with nobody
   attending is the failure this app exists to prevent (NOTIF-03), and an
   alert that times out has decided on the rep's behalf that they saw it. It
   stays until the rep answers it or puts it away, both of which are presses.

   THE TWO PRESSES ARE KEPT APART. Assist is the whole width of the card, at
   the bottom; the dismiss is a small × in the opposite corner, above the two
   lines of text. Side by side they were four pixels apart, and the one that
   loses that mis-tap is the client — a rep who meant Assist and hit × has
   dismissed the arrival and gone on reading a stock list.

   NOTIF-09 — no price on it, ever. It names the client and the material and
   nothing else, because it can be read on a locked screen in front of
   anyone. */

.cb-push {
  position: absolute;
  z-index: 11;
  top: max(10px, env(safe-area-inset-top));
  left: 10px; right: 10px;
  padding: 14px 12px 12px;
  border-radius: 7px;
  background: var(--surface);
  box-shadow: var(--shadow-lift), inset 0 0 0 1px var(--good-2);
  text-align: center;
  animation: cb-push-in 260ms cubic-bezier(.2, .8, .3, 1);
}
.cb-push[hidden] { display: none; }
.cb-push.is-out { animation: cb-push-out 200ms ease-in forwards; }

/* Padded off both edges by more than the × takes, so the sentence is centred
   in the card rather than in what the × left of it. */
.cb-push-say { padding: 0 26px; }

.cb-push-say b {
  display: block;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.35;
  letter-spacing: -.01em;
  color: var(--ink);
}
.cb-push-say b span { display: block; }

.cb-push-say em {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 5px;
  font-style: normal;
  font-size: 11px;
  line-height: 1.3;
  color: var(--mute);
}

/* NOTIF-07 — the alert opens the thing it is about, so the way in is on the
   alert itself and not a step the rep has to find afterwards. Full width,
   because it is the reason the card exists and because width is what puts it
   out of the dismiss's reach. */
.cb-push-go {
  display: grid;
  place-items: center;
  width: 100%;
  min-height: 42px;
  margin-top: 11px;
  border-radius: 5px;
  font-size: 13px;
  font-weight: 700;
  color: var(--on-good);
  background: var(--good);
}

/* Opposite corner from Assist, and small: putting a client away should be the
   deliberate press, not the easy one. Lifted out of the flow so the sentence
   can be centred against the card and not against the space the × left.

   The one round thing in this app that is not the beacon. It is the only
   control on the card that is not the point of the card, and the circle is
   what says so at a glance — 5px would have made it a second button. */
.cb-push-x {
  position: absolute;
  top: 8px; right: 8px;
  display: grid;
  place-items: center;
  width: 28px; height: 28px;
  border-radius: 50%;
  color: var(--mute);
  background: var(--surface-2);
  box-shadow: inset 0 0 0 1px var(--line);
}
.cb-push-x svg { width: 14px; height: 14px; }

@keyframes cb-push-in {
  from { opacity: 0; transform: translateY(-120%); }
  to   { opacity: 1; transform: none; }
}
@keyframes cb-push-out {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(-120%); }
}
@media (prefers-reduced-motion: reduce) {
  .cb-push, .cb-push.is-out { animation: none; }
}

/* ── The name of a destination ─────────────────────────────────────────── */
/* Concierge, Stock, Clients, Requests — every screen the bottom bar can
   reach wears its name in the same type and the same blue. It was only the
   board's until the bar had somewhere else to go, and one screen calling
   itself blue while its neighbours call themselves black reads as two
   different headers rather than one app in four places.

   Deliberately NOT profile.html: that header has a back arrow, sits at 16px,
   and stays ink. It is a page you went into, not a place you are. */

.cb-h1 {
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -.03em;
  color: var(--accent-deep);
}

/* ── Who is signed in ──────────────────────────────────────────────────── */
/* The top-right of every screen that has a top-right. It is the same object
   on the board, on Stock and on Clients, so it is here rather than authored
   three times — which is how one of the three ends up a pixel out. */

.cb-avatar {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  border-radius: 5px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--accent-deep);
  background: var(--accent-wash);
  box-shadow: inset 0 0 0 1px var(--line);
}

/* A saved profile photo fills the tile it replaces the initials in. */
.cb-avatar-img { width: 100%; height: 100%; border-radius: 5px; object-fit: cover; }

/* ── The live beacon ───────────────────────────────────────────────────── */
/* Somebody is standing in the showroom this second. It pulses at roughly
   0.7 Hz — well under the 3 Hz photosensitivity threshold — and its ring
   expands so it reads as live at a glance, not just as a coloured dot. */

.cb-beacon {
  position: relative;
  flex: 0 0 auto;
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--good);
}
.cb-beacon::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  border: 2px solid var(--good);
  animation: cb-ring 1.4s ease-out infinite;
}
.cb-beacon--sm { width: 7px; height: 7px; }

@keyframes cb-ring {
  0%   { transform: scale(.7); opacity: .9; }
  70%  { transform: scale(1.8); opacity: 0; }
  100% { transform: scale(1.8); opacity: 0; }
}

/* ── The conversations you are in ──────────────────────────────────────────
   A rep on a busy floor is not in one conversation, they are in three, and
   until 0029 the app could only hold one: every switch went out to the board,
   down the queue and back in, which is four taps to answer a question that
   takes six words.

   NOT ON THE REQUIREMENTS. §9 is written as though a session is a place a rep
   goes — "opening a client from the board opens their session" — and ASSIST-04
   only constrains the other direction, one rep per client. Nothing says a rep
   may hold several at once, and nothing says they may not. This is the missing
   rule, and it wants writing down: **a rep holds as many conversations as they
   have joined, and can move between them without leaving any.**

   THE FACE IS THE TARGET, THE BADGE IS THE REASON. Which chat a rep opens next
   is decided by who is waiting on them, so the unread count rides on the
   avatar rather than under it. Red, because an unanswered client is the one
   thing on this surface red is for (0028) — the ring around the face is the
   green one, and the two say different things about the same person: she is
   here, and she is waiting.

   FIVE ACROSS AT 390px WITHOUT SCROLLING. A switcher that hides the fourth
   conversation behind a swipe is a switcher that loses it. */

/* Vertical spacing only. It sits inside a header that already carries the
   screen's gutter — 16px on the board, 14px in a session — so padding of its
   own would inset it twice and by a different amount on each. The hairline
   runs the content width for the same reason: one rule that lands correctly
   in both headers beats two that each know where they are. */
.cb-chats {
  margin-top: 12px;
  padding-top: 11px;
  border-top: 1px solid var(--line-soft);
}

.cb-chats-h {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--mute);
}

.cb-chats-list {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  margin-top: 9px;
}

.cb-chat {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: 56px;
}

/* A tile, not a circle — the signed-in avatar in the corner of every screen is
   a 5px tile, and a client's face two centimetres away from it should not be a
   different kind of object. */
.cb-chat-face {
  position: relative;
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  border-radius: 5px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--accent-deep);
  background: var(--accent-wash);
  box-shadow: 0 0 0 2px var(--good);
}

/* She has left the showroom and the conversation is still open — which is
   normal, and is why the rail is not the same list as the board. The ring goes
   quiet rather than away: the chat is still yours, she is just not in the
   room, and those are two different facts. */
.cb-chat--gone .cb-chat-face,
.cb-chat-face--gone {
  color: var(--mute);
  background: var(--surface-2);
  box-shadow: 0 0 0 2px var(--line);
}

/* The one you are reading. An outer ring, clear of the inner one, because the
   inner ring already means "she is in the room" and a control that overwrites
   a fact to show a selection has destroyed the fact. */
.cb-chat.is-on .cb-chat-face {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.cb-chat-n {
  position: absolute;
  top: -6px; right: -7px;
  display: grid;
  place-items: center;
  min-width: 17px; height: 17px;
  padding: 0 4px;
  border-radius: 9px;
  font-size: 10px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--on-urge);
  background: var(--urge);
  box-shadow: 0 0 0 2px var(--ground-top);
}

.cb-chat-name {
  max-width: 100%;
  font-size: 11px;
  font-weight: 500;
  color: var(--soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cb-chat.is-on .cb-chat-name { font-weight: 700; color: var(--accent); }

/* ── Tab bar ───────────────────────────────────────────────────────────── */

.cb-tabs {
  position: relative;
  z-index: 3;
  flex: 0 0 auto;
  display: flex;
  align-items: flex-end;
  background: var(--ground-top);
  border-top: 1px solid var(--line);
  /* The floor is the screen's rounded corner, not the safe area. On a handset
     the home-indicator inset clears it; in the desk frame that inset is 0 and
     the 46px radius crowds the outer two labels. 18px clears both. */
  padding-bottom: max(18px, env(safe-area-inset-bottom));
}

.cb-tab {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 9px 2px 10px;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .01em;
  color: var(--mute);
}

.cb-ico { width: 21px; height: 21px; }

.cb-tab.is-on { font-weight: 700; color: var(--accent); }

/* ── A client is live and the rep is not on the board (NOTIF-01, NOTIF-02) ──
   The whole promise of the app is that a rep never misses somebody standing
   in the showroom, and Stock, Share and Clients are three screens with no
   other way of saying so. The tab that leads back goes green and pulses at
   the same ~0.67 Hz as `.cb-now` — one cadence for "somebody is here", and
   well under the 3 Hz photosensitivity threshold.

   GREEN, NOT RED (0028). Urgency and bad news are two different things and
   this tab is the first one only. A red pulse says a rep has broken
   something; what has actually happened is that a client walked in and there
   is money in the room. The same rule the walk-in banner above already
   follows — red on this surface is reserved for a problem.

   Only while it is NOT the current tab: on the board itself the live clients
   are already in front of the rep, and a tab flashing at the screen they are
   already reading is noise that teaches them to ignore it. */
.cb-tab.is-live:not(.is-on) {
  font-weight: 700;
  color: var(--good);
  animation: cb-tab-live 1.5s ease-in-out infinite;
}

.cb-tab.is-live:not(.is-on) .cb-ico {
  border-radius: 50%;
  animation: cb-tab-halo 1.5s ease-in-out infinite;
}

/* Static green is the resting state, so killing the animation for
   prefers-reduced-motion still leaves the tab unmistakable. */
@keyframes cb-tab-live {
  0%, 100% { color: var(--good); }
  50%      { color: var(--good-2); }
}
@keyframes cb-tab-halo {
  0%, 100% { box-shadow: 0 0 0 0 var(--good-wash); }
  50%      { box-shadow: 0 0 0 6px var(--good-wash); }
}

/* The brand mark anchors the centre of the bar. Raised just enough to read as
   deliberate — a full floating action button would overstate a menu item. */
.cb-mark {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 46px; height: 46px;
  margin: 0 6px 7px;
  transform: translateY(-25%);   /* a quarter of the tile rides above the bar */
  border-radius: 5px;
  background: var(--mark-bg);
  box-shadow: inset 0 0 0 1px var(--line), var(--shadow);
}
.cb-mark img { width: 32px; height: 32px; }

/* The mark is a destination like the four tabs beside it — the dashboard —
   so it has to be able to say it is the current one. A ring rather than the
   accent colour the tabs use: the mark's whole job is to be the brand, and a
   brand that changes colour to mean "you are here" has stopped being one. */
.cb-mark.is-on { box-shadow: inset 0 0 0 2px var(--accent), var(--shadow); }

/* ── The avatar's menu ─────────────────────────────────────────────────────
   Anchored under the avatar rather than raised as a sheet: it belongs to the
   thing that opened it, and the board stays readable behind the scrim. One
   panel, two panes — Language swaps the pane instead of flying out sideways,
   which a 390px screen has no room for. */

/* The flag symbols are referenced, never rendered here. Not display:none —
   that has historically broken <use> references in some engines. */
.cb-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

.cb-scrim {
  position: absolute;
  inset: 0;
  z-index: 8;
  background: rgba(3, 10, 20, .44);
  opacity: 0;
  visibility: hidden;
  transition: opacity 140ms ease, visibility 0s linear 140ms;
}
.cb-scrim.is-open { opacity: 1; visibility: visible; transition-delay: 0s; }

/* top = the header's own padding-top + the 34px avatar + an 8px gap, so the
   panel stays pinned under the avatar when a notch pushes the header down. */
.cb-menu {
  position: absolute;
  z-index: 9;
  top: calc(max(14px, env(safe-area-inset-top)) + 42px);
  right: 16px;
  display: flex;
  flex-direction: column;
  width: 250px;
  max-height: calc(100% - 96px);
  border-radius: var(--r);
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-lift);
  overflow: hidden;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px) scale(.97);
  transform-origin: top right;
  transition: opacity 140ms ease, transform 140ms ease, visibility 0s linear 140ms;
}
.cb-menu.is-open {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0s;
}

.cb-menu-pane { display: flex; flex-direction: column; min-height: 0; }
.cb-menu-pane[hidden] { display: none; }

/* ── Pane 1 · profile ──────────────────────────────────────────────────── */

.cb-mitem {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 13px 14px;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
  text-align: left;
}
.cb-mitem:hover { background: var(--surface-2); }

/* A hairline, not a gap: three items in one panel are one list. */
.cb-mitem--cut { border-top: 1px solid var(--line-soft); }

.cb-mico { width: 18px; height: 18px; flex: 0 0 auto; color: var(--mute); }

.cb-mitem-val { margin-left: auto; font-size: 12px; font-weight: 500; color: var(--mute); }

.cb-mchev { width: 15px; height: 15px; flex: 0 0 auto; color: var(--mute); }

/* Signing out is the one thing here you cannot undo by tapping again. */
.cb-mitem--out { color: var(--alarm); font-weight: 600; }
.cb-mitem--out .cb-mico { color: var(--alarm); }

/* ── Layout and theme, on the menu itself ──────────────────────────────────
   Both are binary, and a submenu built to hold two options is a submenu built
   to hold nothing — so they sit on the main pane as two-sided toggles, named
   on both sides. Not a switch: a switch has an off, and neither of these has
   an off. `Boxy` is not "Minimal, disabled".

   The two toggles share one width and one right edge (`--seg`), so the rows
   read as one control column rather than two controls that happen to be
   stacked. Sized to the widest pair. */

.cb-menu { --seg: 118px; }

.cb-mrow { cursor: default; }
.cb-mrow:hover { background: none; }

.cb-seg {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  width: var(--seg);
  margin-left: auto;
  border-radius: var(--r-sm);
  background: var(--surface-2);
  box-shadow: inset 0 0 0 1px var(--line-soft);
}

.cb-opt {
  flex: 1 1 0;
  padding: 6px 2px;
  border-radius: var(--r-sm);
  font-size: 11.5px;
  font-weight: 500;
  color: var(--mute);
  text-align: center;
  cursor: pointer;
}

/* The chosen side is the solid one. Two halves differing only in opacity is a
   control nobody reads at a glance. */
.cb-opt.is-on {
  font-weight: 700;
  color: var(--on-accent);
  background: var(--accent);
}

/* A layout nobody has drawn this screen in yet. Said, rather than hidden:
   while eight screens are still on their way, "not yet" is information. */
.cb-opt[disabled] { opacity: .4; cursor: not-allowed; }

/* ── Pane 2 · language ─────────────────────────────────────────────────── */

.cb-menu-head {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
  padding: 8px 14px 8px 6px;
  border-bottom: 1px solid var(--line-soft);
  background: var(--surface-2);
}

.cb-menu-back {
  display: grid;
  place-items: center;
  width: 32px; height: 32px;
  flex: 0 0 auto;
  color: var(--soft);
}
.cb-menu-back svg { width: 17px; height: 17px; }

.cb-menu-title {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--soft);
}

.cb-langs { overflow-y: auto; overscroll-behavior: contain; padding: 4px 0; }

.cb-lang {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 9px 14px;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
  text-align: left;
}
.cb-lang:hover { background: var(--surface-2); }

/* The ring is what makes Japan and Korea read as flags on a white panel. */
.cb-flag {
  width: 20px; height: 14px;
  flex: 0 0 auto;
  border-radius: 2px;
  box-shadow: 0 0 0 1px var(--line);
}

.cb-lang.is-on { color: var(--accent-deep); font-weight: 600; }
.cb-lang.is-on::after {
  content: "";
  margin-left: auto;
  width: 5px; height: 10px;
  border: 2px solid var(--accent);
  border-top: 0;
  border-left: 0;
  transform: translateY(-2px) rotate(42deg);
}
