/* ══ character ═══════════════════════════════════════════════════════════
 * The one card that is always on screen, so it is the card the whole look is
 * calibrated against: a slab, a ring-lit avatar, a money strip in gold, three
 * vitals, and the small facts underneath. The bell lives here too - it is the
 * eighth tab, and it hangs off this card because the nav is full.
 */
/* Tight on purpose. This card is never scrolled to and never dismissed - it is
   the top of the rail on every screen in Orbis - so every row it grows pushes
   the nav under it further down, and the nav is what the card is sitting on top
   of. Five blocks at ten points of air each read as a page rather than as a
   heads-up display; eight is enough to keep them apart. */
.char-card { position: relative; padding: 10px 11px; display: grid; gap: 8px;
  background: var(--panel-2); border-radius: var(--r-lg); }
.char-card.dead { filter: grayscale(.85); }
.char-top { display: flex; gap: 9px; align-items: center; }
/* Cut out of the card rather than sitting on it: a ring of page color, then a
   ring of the avatar's own color. Two box-shadows do what a border cannot -
   a second outline outside the first. */
.avatar { width: 38px; height: 38px; border-radius: 50%; flex-shrink: 0;
  background: var(--accent); color: var(--on-accent);
  display: grid; place-items: center;
  font-family: var(--font-display); font-size: 17px; font-weight: 600;
  border: 2px solid var(--bg); box-shadow: 0 0 0 2px var(--accent); }
.avatar.sm { width: 34px; height: 34px; font-size: 15px; }
/* ── framing ──────────────────────────────────────────────────────────
   Part art is drawn on one big square canvas with room around the head for
   everything that will ever hang off it - a wide-brimmed hat, long hair, a
   collar. Dropped into the medallion at 1:1 that room is what you see: a small
   head adrift in a circle.

   So the stack is framed rather than fitted. These two numbers say where the
   head *is* on the canvas, and they are the only place that knowledge lives:
   the art keeps its margins, the compositor keeps knowing nothing about
   anatomy, and re-cropping every face in Orbis is editing two values here.
   `--face-y` is the head's centre measured from the canvas centre; the zoom is
   what it takes to fill the disc afterwards. */
/* Framing is not one number, because a portrait and a marker are not the same
   picture, and almost every avatar in Orbis is a marker: a 34px disc in a feed
   rail answering "who wrote this". At that size the room the art leaves around
   the head - the jaw, the shoulders, the brim of whatever is on it - is just a
   smaller head, so a marker crops in.

   *In*, not through. Cropping until the head fills the disc edge to edge cuts
   the skull at the sides and the crown at the top, and a face pressed against
   its own frame reads as a mistake rather than as a close-up. The number below
   is the one that fills the circle while leaving the head whole; the two places
   somebody is deliberately looking at a face pull back further still. The art
   never changes - only where the frame sits on it. */
/* Keyed on `.worn` and not on `.avatar`, because `.avatar` is only *one* of the
   shapes a face is drawn in: a byline draws `.byline-avatar`, the character
   picker draws `.char-mark`, and the next surface will draw its own. `worn` is
   the class `kit::icon::Avatar` puts on any face that has art, so it is the only
   name every one of them shares. Keying the framing on the medallion's class
   instead left a byline face at zoom 1 - and, far worse, left it unclipped and
   unpositioned, so its absolutely-positioned layers escaped to the nearest
   positioned ancestor and drew one citizen's head across the entire feed. */
.worn { --face-zoom: 1.55; --face-y: -13%; }
.avatar.face-big, .profile-head .avatar, .avatar.face-huge {
  --face-zoom: 1.38; --face-y: -11%; }
/* A face is layers stacked in one box: every layer fills the same square and
   the art itself decides where a thing sits, so a hat is not positioned here
   and adding a slot needs no rule here at all. The letter underneath is what a
   face looks like before any of its art is drawn.
   Order comes from the DOM, which the server already sorted by layer. */
/* Clipped to the disc, so what is left of the canvas past the jaw reads as a
   portrait cropped by its frame rather than as a drawing that ran out. */
/* `position` is as load-bearing as the clip: every layer below is `absolute`,
   and a stack with no positioned parent is a stack that anchors to whatever
   scrolling panel happens to be positioned and draws at that panel's size. */
.worn { position: relative; overflow: hidden; }
/* The layer's own green is no longer a mask at all: it is cut out of the
   image's pixels before the browser ever scales it (see `ui::kit::chroma`).
   The one mask left is the hole a clipper above punches in this layer (a hat
   cutting the hair), an ordinary keyed image applied to `.face-paint`; these
   rules keep any mask stretched to the same box the art fills. */
.face-layer, .face-paint {
  -webkit-mask-size: 100% 100%; mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center; }
/* The framing, applied to every layer identically - which is the only way it
   can be applied without breaking the composite: a hat and the head under it
   must be moved by exactly the same amount or the hat comes off. */
.face-layer { position: absolute; inset: 0; pointer-events: none;
  transform: scale(var(--face-zoom, 1)) translateY(var(--face-y, 0)); }
.face-paint { position: absolute; inset: 0; }
/* Color multiplies into the drawing rather than being poured through its
   alpha. That distinction is the whole reason hair art is drawn white: white ×
   color is the color, and a grey shadow the artist drew into the same file ×
   color is that color *darkened* - still a shadow. Filling through alpha
   instead would have turned every shadow into a see-through patch of hair.
   The multiply is an SVG color matrix on the image itself, minted per color
   in `ui::kit::icon` - which is also where it is written down why it cannot be
   the `multiply` blend mode, and what that cost every soft pixel while it was. */
.face-art { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; image-rendering: auto; }
/* A cast shadow: an extra file belonging to one part, drawn just under it onto
   whatever is beneath. It darkens rather than covers, which is the only thing
   that makes it a shadow instead of a grey smear - so nothing between it and
   the layers below may isolate, which is why `.face-paint` above no longer
   does. */
.face-layer.shade { mix-blend-mode: multiply; }
/* What a layer shows while its art is still a drawing somebody owes: the
   item's own emoji, in the same box. */
.face-fallback { position: absolute; top: -7px; left: 50%; transform: translateX(-50%);
  line-height: 1; font-size: 15px; filter: drop-shadow(0 1px 1px rgba(0,0,0,.35)); }
.worn.sm .face-fallback { font-size: 13px; }
.face-hairpin .face-fallback { top: -7px; left: -5px; transform: none; }
.face-face .face-fallback { top: 9px; }
.face-head .face-fallback { top: -11px; }
/* ── the name, and why it is fenced in ────────────────────────────────
   This block gets whatever the top row has left, and the top row spends most
   of itself before reaching it: a 38px medallion, a bell and a ⋯, plus the gaps
   between them, is over half a 254px wall. What is left is about eighty pixels,
   and a name is one word.

   `min-width: 0` lets the *box* shrink to that; it does nothing about the word
   inside, which keeps its own width and paints straight over the bell, out of
   the card and into the wall - and a wall with something sticking out of it
   scrolls sideways, because `overflow-y: auto` makes the other axis `auto` too.
   So the word is told it may break, and the box is told to keep whatever
   happens inside it. A name that needs two lines gets two lines: hiding half of
   somebody's name to save a row is the wrong trade on the one card that is
   always on screen. */
.char-id { min-width: 0; flex: 1; overflow: hidden; }
.char-name { font-size: 14.5px; font-weight: 800; line-height: 1.25; color: var(--text-hi);
  overflow-wrap: anywhere; }
/* The handle and the city are not the name. They are looked at once, they are
   both available in full on the profile, and neither is worth a second line -
   so these truncate where the name wraps. */
.char-id .handle, .char-id .fine { overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
/* Purse, what today added to it, and what is tied up elsewhere - one strip in
   the color of money, so the number a player checks most often is never
   somewhere they have to look for. */
.orb-panel { display: flex; align-items: center; gap: 8px; padding: 6px 10px;
  border-radius: var(--r-md); background: rgba(255, 196, 66, .1);
  border: 1.5px solid rgba(255, 196, 66, .32); }
.orb-amount { font-size: 16px; font-weight: 600; color: var(--gold); }
.orb-panel .fine { margin-left: auto; font-size: 10.5px; }

/* The ⋯ and the 🔔 are the same object: a small square well sunk into the card,
   sized for the glyph and nothing else. The sketch drew the bell and left the
   ⋯ out; they share this rule so the pair can never drift apart. The popup
   behind the ⋯ is `.menu-pop`, in `overlays.css`. */
.char-menu, .bell { background: var(--panel); border: 1.5px solid var(--border);
  color: var(--muted); box-shadow: none;
  width: 29px; height: 29px; min-height: 0; padding: 0; border-radius: var(--r-md);
  flex-shrink: 0; font-size: 13px; line-height: 1; display: grid; place-items: center; }
@media (hover: hover) {
  .char-menu:hover, .bell:hover { background: var(--panel-3); border-color: var(--border-hi);
    color: var(--text); }
}

/* ── vitals ───────────────────────────────────────────────────────────
   Three bars that decide whether the character lives. Thicker and rounder than
   a progress indicator has any business being, because at a glance the *only*
   things being read are the color and the length.

   `.bar` and `.bar-fill` are the shared primitive: the phone header's mini-bars
   (`shell.css`), the attribute rows and focus pips (`work.css`) and a building's
   condition (`city.css`) all draw themselves out of it. */
.bars { display: grid; gap: 7px; }
.bar-meta { display: flex; justify-content: space-between; font-size: 12px;
  font-weight: 700; color: var(--muted); margin-bottom: 4px; }
.bar-val { color: var(--text); }
.bar { height: 9px; background: var(--bg); border-radius: var(--r-pill); overflow: hidden; }
.bar-fill { height: 100%; border-radius: var(--r-pill); transition: width .6s ease; }
.bar-fill.hunger  { background: var(--hunger); }
.bar-fill.thirst  { background: var(--thirst); }
.bar-fill.stamina { background: var(--stamina); }
/* Permadeath: a bar this low is the most important thing on the screen. */
.bar-row.danger .bar-fill { background: var(--err-solid); animation: pulse 1s infinite; }
.bar-row.danger .bar-val { color: var(--err); }
@keyframes pulse { 50% { opacity: .35; } }

/* The vitals, laid out the way the sidebar draws them: icon, bar, number. A
   compact form of `.bar-row` for the character card, where the label is an
   emoji and the meta line would be a wasted row. */
.vital-row { display: flex; align-items: center; gap: 8px; }
.vital-icon { width: 18px; flex: none; font-size: 12px; line-height: 1; text-align: center; }
img.vital-icon { width: 14px; height: 14px; object-fit: contain; }
.vital-row .bar { flex: 1; min-width: 0; }
.vital-pct { width: 34px; flex: none; text-align: right;
  font-family: var(--font-num); font-size: 11px; font-weight: 600; }
.vital-row.hunger .vital-pct { color: var(--hunger); }
.vital-row.thirst .vital-pct { color: var(--thirst); }
.vital-row.stamina .vital-pct { color: var(--ok); }
.vital-row.danger .vital-pct { color: var(--err); }
.vital-row.danger .bar-fill { background: var(--err-solid); animation: pulse 1s infinite; }

/* ── the small facts ──────────────────────────────────────────────────
   Age, weight, what you are carrying. In the sketch these were unlabelled
   pills on one line and they crowded each other at 254px - three numbers with
   three different units and nothing saying which was which. So they became a
   proper row of wells: the figure in the display face, its name under it in
   small caps. Reading them is now free, and the line below has room for the
   facts that were being squeezed out.

   The profile and the city banner reuse `.fact` at their own widths - see
   `.profile-facts` and `.city-stats`. */
.facts { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.fact { text-align: center; padding: 6px 4px; background: var(--panel);
  border: 1.5px solid var(--border); border-radius: var(--r-md); min-width: 0; }
.fact b { display: block; font-family: var(--font-display); font-size: 15px;
  font-weight: 600; color: var(--text-hi);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* One line, always. A label that wraps makes its tile taller than the two
   beside it, and a row of facts at three different heights is the exact thing
   this layout replaced. */
.fact span { display: block; font-size: 9px; font-weight: 800; text-transform: uppercase;
  letter-spacing: .06em; color: var(--muted-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The second line: what is true about this character that is not a number with
   a unit - the trade they are best at, the labor they have banked. */
.char-tags { display: flex; align-items: center; gap: 5px; flex-wrap: wrap;
  font-size: 11px; font-weight: 700; color: var(--muted); }
.char-tags .chip { margin: 0; padding: 3px 8px; font-size: 11px; }
.char-skill { padding: 0 2px; }
/* The best thing about them, colored, because it is the one fact on the card
   that is an achievement.

   It used to be pushed to the far end of its line with `margin-left: auto`,
   which is right for a line and wrong for a *wrap*: the auto margin eats the
   free space its row had left, so the two tags after it were forced onto rows
   of their own and four chips cost three lines. Packed, they cost two. */
.chip.badge-skill { background: rgba(170, 140, 255, .12);
  border-color: rgba(170, 140, 255, .35); color: var(--magic); }

/* The language picker sits at the foot of the wall: set once, then forgotten. */
.lang-picker { display: flex; align-items: center; gap: 8px; padding: 2px 11px;
  background: var(--panel-2); border: 1.5px solid var(--border); border-radius: var(--r-md); }
.lang-picker select { border: none; background: transparent; padding: 6px 24px 6px 0;
  font-size: 12.5px; font-weight: 700; box-shadow: none;
  background-position: calc(100% - 8px) 55%, calc(100% - 3px) 55%; }
.lang-picker select:focus { box-shadow: none; }

/* ── the bell ───────────────────────────────────────────────────────── */
/*
 * The nav is full, so the eighth tab lives on the character card. It has to read
 * as a *control* at a glance and its count has to be the loudest thing on a
 * quiet card - that number is the only sign a player gets that something
 * happened while they were looking elsewhere.
 *
 * Shape and color come from the rule it shares with `.char-menu` above; what
 * is here is only what makes it a *bell* rather than a menu.
 */
.bell { position: relative; align-self: center; }
.bell.active { border-color: var(--accent); color: var(--accent); }
.bell.ringing { animation: bell-swing 2.6s ease-in-out infinite; }
/* Overlapping the glyph, not pushed beside it - the button stays button-sized. */
.bell .badge.count { position: absolute; top: -5px; right: -5px; margin: 0;
  font-size: 9.5px; min-width: 15px; box-shadow: 0 0 0 2px var(--panel-2); }
@keyframes bell-swing {
  0%, 88%, 100% { transform: rotate(0); }
  91% { transform: rotate(11deg); } 94% { transform: rotate(-9deg); } 97% { transform: rotate(5deg); }
}

.notice-actions { justify-content: flex-end; }
.notice-actions .fine { margin-right: auto; }
/* A notice is a toast the server kept, and it is drawn like one: the mark in a
   coloured disc, the level as a tint behind the whole slab, the sentence at a
   size meant to be read. It used to be a flat row with a 4px stripe - the
   stripe was the only part of "this went badly" that survived being stored. */
.notices { display: grid; gap: 9px; }
.notice { display: flex; gap: 11px; align-items: flex-start; padding: 12px 14px;
  border-left: 5px solid var(--border); }
.notice.success { border-left-color: var(--ok); }
.notice.info { border-left-color: var(--accent); }
.notice.warn { border-left-color: var(--warn); }
.notice.error { border-left-color: var(--err); }
/* Read rows stay legible but stop competing: the unread ones are the screen.
   The tint rides on the unread ones only - a week of read notices all washed in
   their own colours is a screen with no signal left in it. */
.notice:not(.unread) { opacity: .72; background: var(--panel); }
.notice.unread { border-color: var(--border-hi); }
.notice.unread.success { background: var(--tint-ok); }
.notice.unread.info { background: var(--tint-cool); }
.notice.unread.warn { background: var(--tint-gold); }
.notice.unread.error { background: var(--tint-err); }
/* The disc, at the toast's own size and colouring. */
.notice-badge { flex: none; width: 32px; height: 32px; border-radius: var(--r-pill);
  display: grid; place-items: center;
  background: rgba(255, 255, 255, .06); border: 1.5px solid var(--border-hi); }
.notice.success .notice-badge { border-color: var(--ok); color: var(--ok); }
.notice.info .notice-badge { border-color: var(--accent); color: var(--accent-2); }
.notice.warn .notice-badge { border-color: var(--warn); color: var(--warn); }
.notice.error .notice-badge { border-color: var(--err); color: var(--err); }
.notice-icon { font-size: 17px; line-height: 1; }
img.notice-icon { width: 19px; height: 19px; object-fit: contain; }
.notice-body { min-width: 0; flex: 1; display: grid; gap: 2px; }
.notice-text { overflow-wrap: anywhere; font-size: 13.5px; line-height: 1.4; color: var(--text); }
/* When. The bell exists for the player who was not looking, so this is the
   field that decides whether a row is news or history - and it was the one
   thing a stored toast never carried. */
.notice-when { color: var(--muted-2); }
/* A row that leads somewhere. The chevron is the promise and the lift is the
   confirmation; a row without both is a row that does nothing, and the two
   kinds have to be told apart *before* they are pressed. */
.notice.opens { cursor: pointer; }
.notice-go { margin-left: auto; flex: none; padding-left: 8px;
  font-size: 20px; font-weight: 700; color: var(--muted-2); line-height: 1;
  transition: transform .16s cubic-bezier(.2,.9,.3,1.2), color .16s ease; }
@media (hover: hover) {
  .notice.opens:hover { border-color: var(--border-hi); background: var(--panel-3); }
  .notice.opens:hover .notice-go { transform: translateX(3px); color: var(--accent-2); }
}
.notice.opens:active { transform: translateY(1px); }

/* ── the phone ──────────────────────────────────────────────────────── */
@media (max-width: 920px) {
  /* The two square icon-buttons: sized for a cursor by default, for a fingertip
     here. */
  .topbar .bell, .char-menu { width: 38px; height: 38px; font-size: 16px; }
  .lang-picker select { min-height: 36px; }
}
/* The SVG that carries the chroma-key filter and every mask built from it.
   `<defs>` paints nothing, but an <svg> element still takes up a line box, so
   it is taken out of flow entirely rather than left to nudge a layout. */
.chroma-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
