/* New Future, application layer.
   ---------------------------------------------------------------------------
   new-future.css is the designer's deliverable and ships exactly as it was
   handed over. This file sits on top of it and holds the rules that only the
   real pages need: the inner structure of a few components (the promotion
   block, the profile columns, the events ladder), table column widths, and
   the small utilities that replace the inline style="" attributes the old
   templates were full of.

   It uses the same custom properties, so there are no loose colour values in
   here. Load it AFTER new-future.css.
   --------------------------------------------------------------------------- */

/* ---------------------------------------------------------------- shell */
.nf-banner__img img { width: 100%; height: 100%; object-fit: cover; }

/* How strongly the noir street behind the game is drawn.
   ---------------------------------------------------------------------------
   This is the ALPHA OF THE BLACK WASH over the illustration, so it runs the
   opposite way to the setting a player picks: 0.95 here is a backdrop shown at
   5%. home.php does that one subtraction and prints the result as this
   property, from the account's ui_bg_opacity column (see app/ui.php).

   The value here is the fallback, and it has to exist: index.php and
   register.php load this stylesheet with nobody logged in, so there is no
   account to read a preference from. It matches the column's default. */
:root { --nf-bg-wash: 0.95; }

/* ONE noir backdrop for the whole page.
   ---------------------------------------------------------------------------
   The street used to be painted twice: once on the body (the margins) and once
   as a z-index:-1 pseudo behind the middle column. The pseudo never actually
   showed - a negative-z child is painted BEFORE its parent's own background, so
   the column's opaque #0b0b0b covered it - and even if it had, two separately
   cropped copies never lined up. So there is now a single copy, fixed to the
   viewport on the body, and everything the game draws floats ON TOP of it at
   varying transparency. Margins, menu columns and content gaps then all show
   the same continuous scene.

   The stack, top layer first:
     - two soft fog banks rising from the bottom corners (pale, cool, barely
       there) so there is some drift and depth in the picture;
     - a black wash that knocks the already-dark #242424 silhouettes back to a
       ghost;
     - the illustration itself, sat on the base black.
   `fixed` on every layer pins the whole thing to the viewport so it spans the
   margins and holds still while the shell scrolls. */
body {
  background-color: var(--nf-bg);
  background-image:
    linear-gradient(rgba(0, 0, 0, var(--nf-bg-wash)), rgba(0, 0, 0, var(--nf-bg-wash))),
    url('images/noir-street.svg');
  background-repeat: no-repeat, no-repeat;
  background-position: center, bottom center;
  background-size: cover, cover;
  background-attachment: fixed, fixed;
}

/* Let the backdrop through the shell.
   ---------------------------------------------------------------------------
   new-future.css gives each column and the middle a solid near-black fill. Made
   translucent here, the fixed backdrop above shows behind the menus you click,
   the stat column and the content gaps - which is the whole point. The tints
   are still dark enough to keep menu text and figures readable; the amber
   section bars and the panels below stay as their own anchors. Loaded after
   new-future.css, so these win without !important. */
.nf-shell__left,
.nf-shell__right { background: rgba(9, 9, 9, 0.55); }
.nf-shell__main  { background: transparent; }
.nf-ticker { background: rgba(15, 15, 15, 0.6); }

/* ---------------------------------------------------------------- online strip
   Sits at the foot of the middle column, just clear of the chat bar, and
   FOLLOWS THE SCREEN as you scroll.

   sticky and not fixed. Fixed would lift it out of the flow permanently, so on
   a short page it would hang over the last line of content with nothing to
   scroll behind it. Sticky leaves it where it belongs at the bottom of the
   column and pins it there while the page scrolls past.

   FLUSH WITH THE CHAT BAR, no gap. `bottom` is where it pins while scrolling
   and there is no margin under it, so the strip's bottom edge meets the top of
   the bar exactly, whether it is pinned or resting.

   --nf-dock-h is the BAR's measured height, published by app.js - not the whole
   dock's. The dock also carries a row of chat windows above the bar, and that
   row is transparent and right-aligned: measuring it stopped everything 30px
   short of the visible edge and left a black band with nothing in it.

   Near-opaque, and that is not decoration: at 0.55 the street image behind it
   looked fine while the strip sat still, and turned the names to mush the
   moment body text began scrolling underneath it.

   z-index 50 is under the dock (60) and over the page - where they meet, the
   dock wins, which is right: it is the thing being typed into. */
.nf-online {
  position: sticky;
  bottom: var(--nf-dock-h);
  z-index: 50;
  background: rgba(9, 9, 9, 0.97);
  margin-bottom: 0;
}

/* SAME HEIGHT AS THE NEWS STRIP.
   It was 37px against the ticker's 31, which is what made it the heavier of
   the two sitting directly under it. The padding was already identical - the
   difference was line-height 1.9 here against the 18px the ticker inherits,
   and a second border I had added underneath. Both are matched to the ticker
   now rather than nudged by eye, so the two strips stay the same height if the
   type scale ever changes. */
.nf-online__list { padding: 6px 11px; line-height: 18px; }

/* Staff, in their own bit at the front. A rule and a little air rather than a
   second heading: the names are already red and already carry the Admin badge,
   so the divider only has to say "these are separate", not say it twice. */
.nf-online__staff {
  padding-right: 10px;
  margin-right: 6px;
  border-right: 1px solid var(--nf-line-strong);
}

/* The panels that hold real content (the Home welcome box, every table and
   card) still need a solid-ish bed so text stays crisp over the backdrop.
   Same #0d0d0d as new-future.css, just 88% opaque, so the street shows through
   only faintly where a panel sits. */
.nf-panel {
  background: rgba(13, 13, 13, 0.88);
}

/* Logout as a form, not a link.
   ---------------------------------------------------------------------------
   logout.php is a POST now (so its CSRF token never touches the URL), which
   makes each logout control a one-button form. display:contents drops the
   <form> box out of the layout, so its <button> becomes a direct child of the
   nav list or the tab bar grid it sits in - landing exactly where the old <a>
   did. The rules then strip the button back to nothing and hand it the same
   look as its sibling links. */
.nf-logout-form { display: contents; }
.nf-logout-form button {
  font-family: inherit;
  color: var(--nf-amber);
  background: none;
  border: 0;
  cursor: pointer;
  width: 100%;
}
.nf-logout-form button:hover { color: var(--nf-amber-hi); }
/* In the side menu: match .nf-nav a (flex row, left-aligned, hover wash). */
.nf-nav button {
  display: flex;
  gap: var(--nf-2);
  padding: var(--nf-1) var(--nf-4);
  font-size: var(--nf-fs-base);
  text-align: left;
}
.nf-nav button:hover { background: #151208; color: var(--nf-amber); }
/* the "-" bullet .nf-nav a/span get from new-future.css, so Logout lines up. */
.nf-nav button::before { content: "-"; color: var(--nf-faint); }
/* In the phone tab bar (below 768px): match .nf-tabbar a. */
@media (max-width: 767px) {
  .nf-tabbar button {
    text-align: center;
    padding: 11px 2px;
    border-left: 1px solid var(--nf-line);
    font: 700 var(--nf-fs-label)/1 var(--nf-font);
    letter-spacing: .06em;
    text-transform: uppercase;
  }
  .nf-tabbar button:hover { background: #151208; color: var(--nf-amber); }
}

/* Without JavaScript the ticker prints every item, stacked, instead of
   rotating through them. app.js drops the --nojs class and takes over. */
.nf-ticker--nojs { flex-wrap: wrap; }
.nf-ticker--nojs .nf-ticker__item { flex: 1 1 100%; white-space: normal; }
.nf-ticker--nojs .nf-ticker__dots { display: none; }

/* ---------------------------------------------------------------- stat block */
.nf-statline--start { justify-content: flex-start; }
.nf-stats__group { margin-top: var(--nf-5); }
.nf-statline + .nf-bar-track { margin-top: 5px; }
.nf-good-ink { color: var(--nf-good); }
.nf-note--pad { padding: var(--nf-4); margin: 0; }

/* ---------------------------------------------------------------- start page */
.nf-welcome { padding: 44px var(--nf-8) 48px; text-align: center; }
.nf-welcome__title { font: 400 26px/1 var(--nf-display); color: var(--nf-text); text-transform: uppercase; letter-spacing: .02em; }
.nf-welcome__line { font-size: var(--nf-fs-body); color: var(--nf-muted); margin: var(--nf-5) 0 0; }
.nf-welcome__line b { color: var(--nf-amber); font-weight: 400; }
.nf-welcome .nf-hr { width: 44px; margin: var(--nf-7) auto; }
.nf-welcome__hint { font-size: var(--nf-fs-base); color: var(--nf-faint); margin: 0; }

/* ---------------------------------------------------------------- tables */
.nf-th-num { text-align: right; }
.nf-col-cash   { width: 120px; }
.nf-col-xp     { width: 60px; }
.nf-col-wait   { width: 70px; }
.nf-col-action { width: 112px; }
.nf-col-rank   { width: 130px; }
.nf-col-reward { width: 180px; }
.nf-col-status { width: 100px; }
.nf-cell-action { text-align: right; padding-top: var(--nf-2); padding-bottom: var(--nf-2); }
.nf-table .nf-num--wait { font-size: var(--nf-fs-base); color: var(--nf-faint); }
/* the crime table has its own heading in the panel above it, so on a phone,
   where the cards take over, the empty frame goes with it */
@media (max-width: 767px) { .nf-panel--tableonly { display: none; } }

/* ---------------------------------------------------------------- cards (phone) */
.nf-card__title { font-size: var(--nf-fs-body); color: var(--nf-text); }
.nf-card__figure { font: 400 var(--nf-fs-body)/1.4 var(--nf-mono); color: var(--nf-amber); white-space: nowrap; }
.nf-card--locked .nf-card__title,
.nf-card--locked .nf-card__figure { color: var(--nf-faint); }
.nf-card .nf-locked { padding: var(--nf-2) var(--nf-3); }

/* ---------------------------------------------------------------- crime page */
.nf-stat__value--rank { font: 400 15px/1.2 var(--nf-font); }
.nf-crime-number { font: 700 var(--nf-fs-small)/1 var(--nf-font); color: var(--nf-amber); margin-right: var(--nf-2); }
.nf-msg__main { flex: 1 1 260px; min-width: 0; }
.nf-msg__glyph { color: var(--nf-muted); font: 700 var(--nf-fs-body)/1.3 var(--nf-font); }
.nf-msg--reward { flex-wrap: wrap; gap: 16px; align-items: center; }
.nf-msg__list { margin: var(--nf-2) 0 0; padding-left: var(--nf-7); font-size: var(--nf-fs-base); line-height: 1.7; }
.nf-gains { display: flex; flex-wrap: wrap; gap: var(--nf-4); }
.nf-gain__label { font: 700 var(--nf-fs-micro)/1 var(--nf-font); letter-spacing: var(--nf-track-label);
                  text-transform: uppercase; color: #7fa377; }
.nf-gain__value { font: 400 20px/1.1 var(--nf-mono); color: var(--nf-good); margin-top: 3px; }

.nf-promo__body { padding: var(--nf-7) 16px; display: flex; flex-wrap: wrap; gap: var(--nf-7); align-items: center; }
.nf-promo__main { flex: 1 1 240px; min-width: 0; }
.nf-promo__line { font-size: var(--nf-fs-lead); color: #e8ddc4; margin-top: var(--nf-3); line-height: 1.5; }
.nf-promo__line b { color: var(--nf-text-strong); }
.nf-promo__unlock { flex: 0 1 240px; border: 1px solid var(--nf-amber-line); background: #0d0a03; padding: var(--nf-4) var(--nf-5); }
.nf-promo__unlock-name { font-size: var(--nf-fs-body); color: var(--nf-amber); margin: var(--nf-2) 0 var(--nf-1); }

.nf-cooldown__left { display: flex; flex-wrap: wrap; gap: 9px; align-items: center; min-width: 0; }
.nf-cooldown__label { font: 700 var(--nf-fs-label)/1 var(--nf-font); letter-spacing: .16em; text-transform: uppercase; color: var(--nf-amber); }
.nf-cooldown .nf-muted { font-size: var(--nf-fs-base); }
.nf-cooldown__track { margin-top: var(--nf-4); }
/* the line under the auto-crime countdown: keeps running when you close the
   game, and what the handicap costs */
.nf-cooldown__note { margin-top: var(--nf-3); }

/* ---------------------------------------------------------------- auto-crime */
/*
 * The whole control lives in the amber bar now.
 *
 * It used to be a panel body about 180px tall: two paragraphs of explanation, a
 * labelled dropdown, a checkbox and a Save button on their own row. All of that
 * to set one crime and one boolean. Now the three controls sit in the header
 * strip and the body is a single line of status, which is roughly 70px.
 *
 * Everything below is written against an AMBER background rather than the black
 * one the rest of the form controls assume. That is the whole difficulty of this
 * layout: --nf-input (#111) on amber is fine, but anything that signals "active"
 * by turning amber is invisible up here. Hence the switch further down uses
 * black as its active colour and amber as its ink - the palette inverted, not a
 * new palette.
 */
.nf-autocrime-form { display: flex; align-items: center; gap: var(--nf-3); flex-wrap: wrap; }

/*
 * The dropdown, finally themed.
 *
 * There was no `select` rule anywhere in either stylesheet, so the browser drew
 * its own widget: a white box with a grey chevron, in the middle of a black
 * panel. appearance:none removes it, which also removes the arrow, so the arrow
 * is redrawn here as two gradient triangles - no image file, no icon font.
 *
 * The 210px cap is what produces the ellipsis. A select only truncates if it has
 * a width to overflow; without the cap the longest crime name sets the width of
 * the whole bar. Note that text-overflow on a select is honoured by Chrome and
 * Edge but ignored by Firefox, which clips instead - no dots, no broken layout.
 */
.nf-select {
  appearance: none; -webkit-appearance: none;
  max-width: 210px;
  background-color: #1a1305;
  border: 1px solid #8a6410;
  color: #f5e4c0;
  font: 400 var(--nf-fs-base)/1.2 var(--nf-font);
  padding: 4px 22px 4px var(--nf-2);
  text-overflow: ellipsis; white-space: nowrap; overflow: hidden;
  background-image: linear-gradient(45deg, transparent 50%, var(--nf-amber) 50%),
                    linear-gradient(135deg, var(--nf-amber) 50%, transparent 50%);
  background-position: calc(100% - 12px) 52%, calc(100% - 8px) 52%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.nf-select:focus { outline: none; border-color: var(--nf-amber-ink); }
/* The open list is drawn by the operating system on the page background, not on
   the bar, so these two go back to the normal dark-panel colours. */
.nf-select option { background: #111; color: var(--nf-text); }

/*
 * The On/Off switch.
 *
 * Still a real checkbox - the form posts auto_crime_enabled exactly as it did
 * when this was a tickbox, and crime.php did not change. The input is only
 * moved out of sight; the two spans are the visible control, and because they
 * are inside the <label> the whole thing toggles on click.
 *
 * Green for on, red for off, black text on both - the two states now read
 * without being read, which matters for the one control on the page whose
 * whole meaning is which of two states it is in.
 *
 * Both are existing tokens (--nf-good, --nf-bad) and both clear WCAG AA against
 * black: the green is about 9.8:1, the red about 4.8:1. Amber was not an option
 * for the active colour here - on an amber bar it would have vanished - and
 * that is what these two replace.
 *
 * Only the ACTIVE half is coloured. Colouring both at once would give you a
 * green and a red box sitting side by side with nothing to say which one is
 * live; the dim half is the answer to that.
 */
.nf-switch { position: relative; display: inline-flex; border: 1px solid #8a6410; cursor: pointer; }
.nf-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.nf-switch span { font: 700 var(--nf-fs-label)/1 var(--nf-font); letter-spacing: var(--nf-track-label);
                  text-transform: uppercase; padding: 6px var(--nf-4); color: #6b4e05; }
.nf-switch input:checked ~ .nf-switch__on { background: var(--nf-good); color: #000; }
.nf-switch input:not(:checked) ~ .nf-switch__off { background: var(--nf-bad); color: #000; }
/* Keyboard users get the ring the hidden input would otherwise have taken with it. */
.nf-switch input:focus-visible ~ span { outline: 1px solid var(--nf-amber-ink); outline-offset: -3px; }

/* Save, sized to sit in a 5px-padded bar without stretching it. */
.nf-btn--bar { background: #1a1305; border-color: #8a6410; color: var(--nf-amber); padding: 5px var(--nf-4); }
.nf-btn--bar:hover { background: var(--nf-amber-ink); border-color: var(--nf-amber-ink); color: var(--nf-amber-hi); }

/* The one line of status left in the body. */
.nf-autocrime-note { margin: 0; text-align: center; font-size: var(--nf-fs-base); color: var(--nf-muted); }
.nf-autocrime-note b { color: var(--nf-text); font-weight: 400; }

/* On a phone the bar is not wide enough for a title and three controls, so the
   controls drop to their own full-width line underneath the title. */
@media (max-width: 560px) {
  .nf-bar--autocrime { flex-wrap: wrap; }
  .nf-autocrime-form { width: 100%; justify-content: center; padding-bottom: var(--nf-1); }
  .nf-select { flex: 1 1 auto; }
}

/* ---------------------------------------------------------------- events */
/*
 * The "(new)" marker on an unread event.
 *
 * This started out reusing .nf-nav__count and came out grey, because that class
 * is only ever styled as `.nf-nav a .nf-nav__count` - a descendant of a menu
 * link. Outside the menu the selector does not match and the span inherits the
 * colour of the line it sits in, which on this page is the same white as the
 * title next to it. Hence its own class: the marker belongs to a row in a
 * table, not to a link in a nav.
 *
 * Same red as the menu counters so the badge you clicked and the row you landed
 * on are visibly the same signal.
 */
.nf-new { font-family: var(--nf-mono); font-size: var(--nf-fs-small); color: #d32f2f; margin-left: var(--nf-2); }

/*
 * Unread rows are drawn heavier than ones already opened.
 *
 * The red (new) marker above is a small thing to spot in a long inbox, so the
 * weight of the row carries the same signal at a glance: 700 against 400 is
 * legible before you have read a word of it.
 *
 * WEIGHT IS THE SIGNAL, colour only supports it. Every part of the row lifts by
 * one step - white title, muted body, ordinary-grey date - so an unread row
 * reads as the same component turned up rather than as a different one. The
 * body rule also restores the dim grey it had when this line was .nf-faint;
 * without it the description would sit at full table colour and compete with
 * the title it belongs to.
 *
 * `read_at` is what decides this, and events.php stamps it on the way in - so a
 * row is bold on the visit that brought it and ordinary on every visit after.
 */
.nf-ev__title { font-weight: 400; color: var(--nf-text); }
.nf-ev__body  { font-size: var(--nf-fs-base); color: var(--nf-faint); margin-top: 2px; }

.nf-ev--new .nf-ev__title { font-weight: 700; color: var(--nf-text-strong); }
.nf-ev--new .nf-ev__body  { color: var(--nf-muted); }
.nf-ev--new .nf-ev__when  { color: var(--nf-text); }

/* ---------------------------------------------------------------- search the streets
   A teaser line and one small raised button, centred - the whole page is that
   single decision, so it gets the middle of the panel rather than a full-width
   bar competing with the copy above it. */
.nf-dive { text-align: center; padding: var(--nf-6) 0 var(--nf-7); }
.nf-dive__ask { margin: 0 0 var(--nf-6); font-size: var(--nf-fs-lead); color: var(--nf-text); }
.nf-dive__form { display: inline-block; }

/* The raised look is a hard bottom-and-right border rather than a blurred
   shadow, so it stays crisp on this dark background, and the button shifts down
   into its own shadow when pressed - the press is the feedback, since the page
   then reloads with the result. */
.nf-dive__btn {
  display: inline-block; padding: 9px var(--nf-8);
  background: var(--nf-amber); color: var(--nf-amber-ink);
  border: 1px solid #6b4a06;
  box-shadow: 2px 2px 0 #000;
  font: 700 var(--nf-fs-lead)/1 var(--nf-font); letter-spacing: .06em;
  cursor: pointer;
}
.nf-dive__btn:hover { background: var(--nf-amber-hi); }
.nf-dive__btn:active { transform: translate(2px, 2px); box-shadow: 0 0 0 #000; }

/* the cooldown box sits in the same centred column, so it does not stretch */
.nf-dive .nf-cooldown { text-align: left; max-width: 420px; margin: 0 auto; }

/* ---------------------------------------------------------------- chat dock
   The bar along the bottom of every page: a taskbar, and the conversation
   windows sitting on top of it.

   FIXED TO THE VIEWPORT, not the page - it holds still while the game scrolls
   underneath, which is the whole point of a dock. z-index is above everything
   the shell draws but the number is kept modest so a future modal can still sit
   over it.

   The dock itself does not take pointer events; its children do. Otherwise the
   full-width strip would swallow every click along the bottom of the page even
   where it is visually empty. */
.nf-dock { position: fixed; right: 0; bottom: 0; left: 0; z-index: 60;
           display: flex; flex-direction: column; align-items: flex-end;
           pointer-events: none; font-family: var(--nf-font); }
.nf-dock > * { pointer-events: auto; }

/*
 * KEEP THE PAGE OUT FROM UNDER THE TASKBAR.
 *
 * The dock is fixed, so it is taken out of the flow and the page does not know
 * it is there - the online list at the foot of the middle column ran 63px
 * underneath it and could not be scrolled into view, because there was nothing
 * left to scroll.
 *
 * The height is a custom property rather than a number written twice: the bar
 * sets its own padding from it and the body reserves exactly that much, so the
 * two cannot drift apart when the bar's padding changes.
 *
 * Only the BAR is reserved for, not the windows. The windows are a thing the
 * player opened and can close or fold away, and reserving 280px of dead space
 * at the bottom of every page for a chat that might not be open is a worse
 * trade than letting an open window overlap.
 */
:root { --nf-dock-bar: 34px; }

/* --nf-dock-h is the dock's REAL height, measured and published by app.js on
   every resize. --nf-dock-bar is the fallback for the moment before that runs
   and for no-JS, where the row of minimised tabs is empty and the bar is all
   there is.
   It exists because 34px stopped being the answer the moment a chat window was
   minimised: the tab row adds another 30px, the dock became 64px tall, and both
   the page padding and the online strip were reserving for half of it. The
   value is clamped in app.js so an EXPANDED window (280px) does not start
   shoving the page around - see the note there. */
:root { --nf-dock-h: var(--nf-dock-bar); }

/* EXACTLY the dock height, with no extra step on top.
   There used to be one, back when the online list sat at the foot of the page
   and needed clearing from the dock's top border. The list is up under the news
   now, so that step had become a strip of dead background between the bottom of
   the menu columns and the chat bar. */
body { padding-bottom: var(--nf-dock-h); }

/* THE COLUMNS REACH THE CHAT BAR.
   .nf-shell is as tall as its tallest column, and on a short page that left the
   menus stopping 90-odd pixels above the dock with a band of nothing under
   them. A minimum of "the screen, less the dock" makes the columns run down to
   exactly where the chat bar starts, on pages with little content, without
   changing anything on pages that are already taller than the screen.

   align-items:stretch on .nf-shell is what carries this to the columns
   themselves - they are already told to match the tallest sibling, so raising
   the floor raises all three together. */
.nf-shell { min-height: calc(100vh - var(--nf-dock-h)); }

/* dvh where it exists: on a phone, 100vh is the height with the browser's own
   toolbars HIDDEN, so the columns would run under the chat bar until the user
   scrolled. dvh is the height actually on screen right now. */
@supports (height: 100dvh) {
  .nf-shell { min-height: calc(100dvh - var(--nf-dock-h)); }
}

/* windows sit in a row, right aligned, newest nearest the edge */
.nf-dock__windows { display: flex; align-items: flex-end; gap: var(--nf-3);
                    padding: 0 var(--nf-3) 0 var(--nf-3); max-width: 100%; }

.nf-cw { width: 250px; display: flex; flex-direction: column;
         background: var(--nf-panel); border: 1px solid var(--nf-line);
         border-bottom: 0; box-shadow: 0 -2px 14px rgba(0,0,0,.6); }
.nf-cw__head { display: flex; align-items: center; gap: var(--nf-2); padding: 6px var(--nf-3);
               background: linear-gradient(#1c1a12, #141209); border-bottom: 1px solid var(--nf-amber-line);
               cursor: pointer; }
.nf-cw__dot { width: 16px; height: 16px; flex: 0 0 16px; border: 1px solid var(--nf-amber-line);
              background: #1c1505; color: var(--nf-amber); font: 700 9px/14px var(--nf-font);
              text-align: center; text-transform: uppercase; }
.nf-cw__name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
               font: 700 var(--nf-fs-base)/1.4 var(--nf-font); color: var(--nf-amber); }
.nf-cw__name.is-admin { color: var(--nf-bad); }
.nf-cw__btn { background: none; border: 0; color: var(--nf-muted); cursor: pointer;
              font: 400 13px/1 var(--nf-font); padding: 0 3px; }
.nf-cw__btn:hover { color: var(--nf-amber); }

/* the message area. Fixed height so the window does not jump about as lines
   arrive, and column-reverse is what keeps it pinned to the newest message
   without any scrollTop arithmetic. */
.nf-cw__body { height: 230px; overflow-y: auto; display: flex; flex-direction: column-reverse;
               padding: var(--nf-3); background: var(--nf-panel-alt); }
.nf-cw__lines { display: flex; flex-direction: column; gap: 7px; }
.nf-cw.is-min .nf-cw__body, .nf-cw.is-min .nf-cw__foot { display: none; }

/* a line in the global room: name then text, wrapping under itself.
   position + padding-right reserve the corner the remove control sits in, so it
   can appear and disappear on hover without the text reflowing under it. */
.nf-cw__line { position: relative; padding-right: 14px;
               font-size: var(--nf-fs-base); line-height: 1.45; color: var(--nf-text); overflow-wrap: anywhere; }
.nf-cw__who { font-weight: 700; color: var(--nf-amber); }
.nf-cw__who.is-admin { color: var(--nf-bad); }
.nf-cw__when { font: 400 var(--nf-fs-micro)/1 var(--nf-mono); color: var(--nf-faint); margin-left: 4px; }

/*
 * Remove a line. Hidden until the pointer is on the shout it belongs to, so
 * forty lines do not carry forty crosses - staff see one on every line and it
 * would be a wall of them otherwise.
 *
 * opacity and not display, and absolutely positioned: the corner is reserved by
 * the padding above either way, so nothing moves as it fades in and there is no
 * target that shifts out from under the cursor.
 *
 * :focus-visible keeps it reachable by keyboard, where there is no hover to
 * have. The coarse-pointer rule is the other half of that: a touch screen has
 * no hover either, so on a phone it simply stays visible.
 */
.nf-cw__del { position: absolute; top: 0; right: -2px; opacity: 0;
              background: none; border: 0; padding: 0 2px; cursor: pointer;
              color: var(--nf-faint); font: 400 13px/1.2 var(--nf-font);
              transition: opacity .12s, color .12s; }
.nf-cw__line:hover .nf-cw__del,
.nf-cw__del:focus-visible { opacity: 1; }
.nf-cw__del:hover { color: var(--nf-bad); }
.nf-cw__del[disabled] { opacity: .4; cursor: default; }
@media (hover: none) { .nf-cw__del { opacity: 1; } }

/* a private message: a bubble, mine on the right */
.nf-cw__msg { max-width: 85%; padding: 6px 9px; background: #17161a; border: 1px solid var(--nf-line);
              font-size: var(--nf-fs-base); line-height: 1.45; color: var(--nf-text); overflow-wrap: anywhere; }
.nf-cw__msg--mine { align-self: flex-end; background: #1a1508; border-color: var(--nf-amber-line); }

.nf-cw__foot { display: flex; border-top: 1px solid var(--nf-line); }
.nf-cw__input { flex: 1 1 auto; min-width: 0; background: var(--nf-input); border: 0; color: var(--nf-text);
                font: 400 var(--nf-fs-base) var(--nf-font); padding: 8px var(--nf-3); }
.nf-cw__input:focus { outline: none; background: #151208; }
.nf-cw__send { flex: 0 0 auto; background: var(--nf-input); border: 0; border-left: 1px solid var(--nf-line);
               color: var(--nf-amber); cursor: pointer; padding: 0 var(--nf-4); font-size: 13px; }
.nf-cw__send:hover { background: var(--nf-amber); color: var(--nf-amber-ink); }
.nf-cw__error { padding: 5px var(--nf-3); background: var(--nf-bad-bg); border-top: 1px solid var(--nf-bad-line);
                color: var(--nf-bad); font-size: var(--nf-fs-small); }

/* ---- the People panel ----
   Wider than a conversation window, because each row carries a name, a time and
   a line of the last message rather than one short sentence. */
.nf-pp { width: 320px; }
.nf-pp .nf-cw__dot { color: var(--nf-amber); }

.nf-pp__tabs { display: flex; gap: 1px; background: var(--nf-line); border-bottom: 1px solid var(--nf-line); }
.nf-pp__tabs button { flex: 1 1 auto; padding: 6px 2px; background: var(--nf-panel); border: 0; cursor: pointer;
                      font: 700 var(--nf-fs-micro)/1.6 var(--nf-font); letter-spacing: .06em;
                      text-transform: uppercase; color: var(--nf-muted); }
.nf-pp__tabs button:hover { background: #151208; color: var(--nf-amber); }
.nf-pp__tabs button.is-on { background: #17140d; color: var(--nf-amber); }
/* a tab whose feature is not built yet, greyed the way the menus grey theirs */
.nf-pp__tabs button.is-soon { color: var(--nf-disabled); }
.nf-pp__tabs button.is-soon:hover { background: var(--nf-panel); color: var(--nf-disabled); }

.nf-pp__list { height: 300px; overflow-y: auto; background: var(--nf-panel-alt); }
.nf-pp__empty { padding: var(--nf-6); color: var(--nf-faint); font-size: var(--nf-fs-base); text-align: center; }

.nf-pp__row { display: flex; align-items: center; gap: var(--nf-3); width: 100%; text-align: left;
              padding: 8px var(--nf-3); background: none; border: 0; border-bottom: 1px solid var(--nf-line-soft);
              cursor: pointer; font-family: var(--nf-font); }
.nf-pp__row:hover { background: #151208; }
.nf-pp__row[disabled] { cursor: default; opacity: .65; }
.nf-pp__row[disabled]:hover { background: none; }

/* the avatar, or the first letter when somebody has not uploaded one */
.nf-pp__av { width: 34px; height: 34px; flex: 0 0 34px; overflow: hidden; border: 1px solid var(--nf-line-strong);
             background: #1c1505; color: var(--nf-amber); font: 400 15px/32px var(--nf-display);
             text-align: center; text-transform: uppercase; }
.nf-pp__av img { width: 100%; height: 100%; object-fit: cover; display: block; filter: grayscale(1) contrast(1.1); }

.nf-pp__main { flex: 1 1 auto; min-width: 0; }
.nf-pp__top { display: flex; align-items: baseline; gap: var(--nf-3); }
.nf-pp__name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
               font: 700 var(--nf-fs-body)/1.4 var(--nf-font); color: var(--nf-amber); }
.nf-pp__name.is-admin { color: var(--nf-bad); }
.nf-pp__when { flex: 0 0 auto; font: 400 var(--nf-fs-micro)/1 var(--nf-mono); color: var(--nf-faint); }
/* the preview is clipped to one line: a long message must not make the row grow */
.nf-pp__line { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
               font-size: var(--nf-fs-base); color: var(--nf-muted); margin-top: 2px; }

.nf-pp__n { flex: 0 0 auto; min-width: 18px; padding: 0 5px; border-radius: 9px;
            background: var(--nf-good); color: #06210a;
            font: 700 var(--nf-fs-micro)/18px var(--nf-font); text-align: center; }

/* ---- the taskbar ---- */
/* min-height ties the bar to the same number the body reserves above, so the
   two cannot drift when this padding changes. */
.nf-dock__bar { display: flex; align-items: stretch; gap: 1px; width: 100%;
                min-height: var(--nf-dock-bar); box-sizing: border-box;
                background: var(--nf-line); border-top: 1px solid var(--nf-line); }
.nf-dock__tabs { flex: 1 1 auto; min-width: 0; display: flex; gap: 1px; overflow: hidden; justify-content: flex-end; }

.nf-dock__tab { display: flex; align-items: center; gap: var(--nf-2); max-width: 190px;
                padding: 7px var(--nf-4); background: #101010; border: 0; cursor: pointer;
                font: 400 var(--nf-fs-base)/1.3 var(--nf-font); color: var(--nf-text); }
.nf-dock__tab:hover { background: #171208; color: var(--nf-amber); }
.nf-dock__tab.is-open { background: #17140d; color: var(--nf-amber); }
.nf-dock__tab span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* a tab with something waiting on it */
.nf-dock__tab.is-unread { color: var(--nf-text-strong); font-weight: 700; }
.nf-dock__tab.is-unread::before { content: ""; width: 6px; height: 6px; flex: 0 0 6px; background: var(--nf-bad); }

.nf-dock__icon { position: relative; display: flex; align-items: center; justify-content: center;
                 min-width: 40px; padding: 7px var(--nf-4); background: #101010; border: 0; cursor: pointer;
                 color: var(--nf-muted); font-size: 13px; }
.nf-dock__icon:hover { background: #171208; color: var(--nf-amber); text-decoration: none; }
.nf-dock__badge { position: absolute; top: 2px; right: 3px; min-width: 15px; padding: 0 3px;
                  background: var(--nf-good); color: #06210a; border-radius: 8px;
                  font: 700 9px/15px var(--nf-font); text-align: center; }

/* On a phone the windows would cover the game, so only the taskbar stays and a
   window takes the full width when one is open. */
@media (max-width: 767px) {
  .nf-dock__windows { width: 100%; padding: 0; }
  .nf-cw { width: 100%; }
  .nf-cw + .nf-cw { display: none; }   /* one conversation at a time */
}

/* ---------------------------------------------------------------- shoutbox
   One room, everybody in it. A list of lines rather than a table: each entry is
   a time, a name and a sentence, and a table would put three borders around
   something that reads better as a run of text. */
.nf-shout__form { display: flex; gap: var(--nf-3); align-items: center; }
.nf-shout__input { flex: 1 1 auto; min-width: 0; background: var(--nf-input);
                   border: 1px solid var(--nf-line-strong); color: var(--nf-text);
                   font: 400 var(--nf-fs-body)/1 var(--nf-font); padding: 9px var(--nf-4); }
.nf-shout__input:focus { outline: none; border-color: var(--nf-amber); background: #151208; }

.nf-shouts { list-style: none; margin: 0; padding: 0; }
.nf-shout { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--nf-3);
            padding: 6px var(--nf-3); border-bottom: 1px solid var(--nf-line-soft); }
.nf-shout:last-child { border-bottom: 0; }
.nf-shout:nth-child(odd) { background: #0b0b0b; }
.nf-shout__when { font: 400 var(--nf-fs-small)/1.5 var(--nf-mono); color: var(--nf-faint); flex: 0 0 auto; }
.nf-shout__who  { font-weight: 700; flex: 0 0 auto; }
/* the line itself takes the remaining width, so a long shout wraps under the
   name instead of pushing the delete button off the end */
.nf-shout__body { flex: 1 1 200px; min-width: 0; color: var(--nf-text); overflow-wrap: anywhere; }
.nf-shout__del button { background: none; border: 0; color: var(--nf-faint); cursor: pointer;
                        font: 400 14px/1 var(--nf-font); padding: 0 var(--nf-2); }
.nf-shout__del button:hover { color: var(--nf-bad); }

/* ---------------------------------------------------------------- mail */
/*
 * Two views, one page. The inbox is the same dense table as the crime list, and
 * a row opens the whole conversation with that player.
 */
.nf-mail__col-from { width: 130px; }
.nf-mail__col-when { width: 96px; white-space: nowrap; }
/* Unread is white against the muted grey of read mail. The amber square is the
   same signal the Events badge uses, at the size of a full stop. */
.nf-mail__row--new td a { color: var(--nf-text-strong); }
.nf-mail__row--new td { background: #0b0a07 !important; }
.nf-mail__dot { display: inline-block; width: 5px; height: 5px; background: var(--nf-amber);
                margin-right: var(--nf-2); vertical-align: middle; }
.nf-mail__table td a { color: var(--nf-muted); }
.nf-mail__table td a:hover { color: var(--nf-amber); }
/* The preview is one line: the model is a subject line, and a message that
   wraps to three rows would make the table impossible to scan. */
.nf-mail__preview { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nf-mail__back { color: var(--nf-amber-ink); text-decoration: underline; font-weight: 400; }
/* Marks mail from the game rather than from a player. Muted on purpose: it is
   a category, not an alert. */
.nf-mail__tag { font: 700 var(--nf-fs-micro)/1 var(--nf-font); letter-spacing: var(--nf-track-label);
                text-transform: uppercase; color: var(--nf-faint); border: 1px solid var(--nf-line-strong);
                padding: 2px 4px; margin-left: var(--nf-2); vertical-align: middle; }

/*
 * The conversation. Mine on the right in amber, theirs on the left in grey -
 * position and colour saying the same thing twice, because on a phone the
 * bubbles are wide enough that alignment alone stops being obvious.
 *
 * max-width 74% is what keeps that alignment readable: a bubble allowed to fill
 * the row would sit against both edges and tell you nothing.
 */
.nf-mail__thread { display: flex; flex-direction: column; gap: var(--nf-4); padding: var(--nf-5);
                   max-height: 460px; overflow-y: auto; }
.nf-mail__bubble { max-width: 74%; align-self: flex-start; padding: var(--nf-3) var(--nf-4);
                   border: 1px solid var(--nf-line-soft); background: var(--nf-row); }
.nf-mail__bubble--mine { align-self: flex-end; background: var(--nf-amber-bg); border-color: var(--nf-amber-line); }
.nf-mail__who { font: 700 var(--nf-fs-micro)/1 var(--nf-font); letter-spacing: var(--nf-track-label);
                text-transform: uppercase; color: var(--nf-muted); margin-bottom: 5px; }
.nf-mail__bubble--mine .nf-mail__who { color: var(--nf-amber); }
.nf-mail__body { font-size: var(--nf-fs-body); line-height: 1.6; word-break: break-word; }
.nf-mail__time { font: 400 var(--nf-fs-micro)/1 var(--nf-mono); color: var(--nf-faint); margin-top: var(--nf-2); }

.nf-mail__composer { display: flex; align-items: flex-start; gap: var(--nf-3);
                     padding: var(--nf-4); border-top: 1px solid var(--nf-line); }
.nf-mail__reply { flex: 1 1 auto; min-width: 0; resize: vertical; min-height: 38px;
                  background: var(--nf-input); border: 1px solid var(--nf-line-strong); color: var(--nf-text);
                  font: 400 var(--nf-fs-body)/1.5 var(--nf-font); padding: var(--nf-3); }
.nf-mail__reply:focus { outline: none; border-color: var(--nf-amber); background: #151208; }
.nf-mail__reply::placeholder { color: var(--nf-faint); }
/* The compose form had no spacing of its own: .nf-field lays out ITS OWN label
   and control, but the form around them was a plain block, so the fields and
   the button sat flush against each other - the "Message" label started on the
   exact pixel the "To" input's border ended, which reads as the label sitting
   on the box above it. A flex column with the standard gap separates them, the
   same way .nf-auth__body does on the login page.

   The button gets align-self, because a stretched flex child would otherwise
   make Send span the full width of the panel. */
.nf-mail__compose { display: flex; flex-direction: column; gap: var(--nf-5); }
.nf-mail__compose .nf-btn { align-self: flex-start; }

.nf-mail__field textarea { width: 100%; resize: vertical; background: var(--nf-input);
                           border: 1px solid var(--nf-line-strong); color: var(--nf-text);
                           font: 400 var(--nf-fs-body)/1.5 var(--nf-font); padding: var(--nf-3); }
.nf-mail__field textarea:focus { outline: none; border-color: var(--nf-amber); background: #151208; }
.nf-mail__tools { display: flex; justify-content: flex-end; }

/* ---------------------------------------------------------------- bank teller */
/*
 * Deposit and withdraw as a pair, not a stack.
 *
 * flex-basis 300px with wrap, rather than a two-column grid: on the phone the
 * cards drop under each other by themselves, and there is no breakpoint to keep
 * in step with the rest of the sheet.
 *
 * Direction is carried by colour - --nf-good in, --nf-bad out, both already in
 * the palette - which is why neither card needs a sentence explaining itself.
 */
.nf-teller { display: flex; flex-wrap: wrap; gap: var(--nf-5); }
.nf-teller__card { flex: 1 1 300px; min-width: 0; border: 1px solid var(--nf-line);
                   background: var(--nf-panel); padding: var(--nf-6); }
.nf-teller__head { display: flex; align-items: center; gap: 7px;
                   font: 700 var(--nf-fs-body)/1 var(--nf-font); letter-spacing: .06em;
                   text-transform: uppercase; }
.nf-teller__arrow { font-size: 14px; line-height: 1; }
.nf-teller__card--in  .nf-teller__head { color: var(--nf-good); }
.nf-teller__card--out .nf-teller__head { color: var(--nf-bad); }

.nf-teller__form { display: flex; align-items: stretch; gap: var(--nf-3); margin-top: var(--nf-5); }

/* The field is the bordered box; the input inside it is bare. That way the
   currency mark and Max sit INSIDE the border instead of beside it. */
.nf-teller__field { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: var(--nf-2);
                    background: var(--nf-input); border: 1px solid var(--nf-line-strong); padding: 0 var(--nf-3); }
.nf-teller__field:focus-within { border-color: var(--nf-amber); background: #151208; }
.nf-teller__cur { color: var(--nf-muted); font: 400 var(--nf-fs-body)/1 var(--nf-mono); }
.nf-teller__input { flex: 1 1 auto; min-width: 0; background: transparent; border: 0; padding: 9px 0;
                    color: var(--nf-text); font: 400 var(--nf-fs-body)/1 var(--nf-mono); }
.nf-teller__input:focus { outline: none; }
/* type=number draws spinners that would fight Max for the same corner. */
.nf-teller__input::-webkit-outer-spin-button,
.nf-teller__input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.nf-teller__input { -moz-appearance: textfield; appearance: textfield; }

/* Max only works with JavaScript, so it is not shown without it - app.js puts
   nf-js on <html>. A visible button that does nothing is worse than no button. */
.nf-teller__max { display: none; background: none; border: 0; cursor: pointer; padding: var(--nf-2);
                  font: 700 var(--nf-fs-label)/1 var(--nf-font); letter-spacing: var(--nf-track-label);
                  text-transform: uppercase; color: var(--nf-muted); }
.nf-js .nf-teller__max { display: block; }
.nf-teller__max:hover { color: var(--nf-amber); }

.nf-teller__go { border: 1px solid; padding: 8px var(--nf-5); cursor: pointer; white-space: nowrap;
                 font: 700 var(--nf-fs-label)/1 var(--nf-font); letter-spacing: var(--nf-track-label);
                 text-transform: uppercase; }
.nf-teller__card--in  .nf-teller__go { background: var(--nf-good-bg); border-color: var(--nf-good-line); color: var(--nf-good); }
.nf-teller__card--in  .nf-teller__go:hover { background: var(--nf-good); border-color: var(--nf-good); color: #000; }
.nf-teller__card--out .nf-teller__go { background: var(--nf-bad-bg); border-color: var(--nf-bad-line); color: var(--nf-bad); }
.nf-teller__card--out .nf-teller__go:hover { background: var(--nf-bad); border-color: var(--nf-bad); color: #000; }

.nf-teller__note { margin: var(--nf-4) 0 0; font-size: var(--nf-fs-small); color: var(--nf-faint); }
.nf-teller__note b { color: var(--nf-text); font-weight: 400; }

/* ---------------------------------------------------------------- profile */
.nf-profile { display: flex; flex-wrap: wrap; gap: 16px; }
.nf-profile__col { flex: 1 1 340px; min-width: 0; }
.nf-profile__col--side { flex: 1 1 300px; display: flex; flex-direction: column; gap: var(--nf-5); }
.nf-profile__head { display: flex; gap: var(--nf-5); align-items: flex-start; }
.nf-profile__initial { width: 42px; height: 42px; flex: 0 0 42px; border: 1px solid var(--nf-amber); background: #1c1505;
                       color: var(--nf-amber); font: 400 22px/40px var(--nf-display); text-align: center; }
.nf-profile__ident { min-width: 0; }
.nf-profile__name { font: 400 24px/1.1 var(--nf-display); color: var(--nf-text-strong); text-transform: uppercase; word-break: break-all; }
.nf-table--stats td { border-bottom: 1px solid var(--nf-line-soft); }
.nf-th-inline { width: 110px; font: 700 var(--nf-fs-label)/1.4 var(--nf-font); letter-spacing: var(--nf-track-label);
                text-transform: uppercase; color: var(--nf-muted); }
.nf-money-cell { font: 400 14px/1.2 var(--nf-mono); color: var(--nf-amber); word-break: break-all; }
.nf-mini { border: 1px solid var(--nf-line); background: var(--nf-panel-alt); padding: var(--nf-5); }
.nf-mini__value { font: 400 15px/1.2 var(--nf-mono); margin: var(--nf-2) 0 var(--nf-3); }
.nf-mini__note { font-size: var(--nf-fs-small); color: var(--nf-muted); margin-top: 7px; }
.nf-mini__note b { color: var(--nf-text); }
.nf-mini__actions { display: flex; flex-wrap: wrap; gap: var(--nf-3); margin-top: var(--nf-3); }

/* ---------------------------------------------------------------- user information panel
   The classic BBS/mafia-game profile card: a metallic "user information" header,
   right-aligned amber labels, a grayscale portrait stacked over a crew badge, and
   a full-width stat strip along the bottom. */
/* The card does not fill the middle column. It sits centred with air either
   side, the way the original does - a 600-odd pixel card reads as a document,
   and the same content stretched the full width reads as a table. The gap is
   margin rather than padding on the column so every OTHER page still uses the
   full width it was designed for. */
.nf-uinfo { border: 1px solid var(--nf-line); background: var(--nf-panel);
            max-width: 620px; margin: 0 auto; }
.nf-uinfo__head {
  text-align: center; padding: 6px var(--nf-3);
  font: 700 var(--nf-fs-small)/1 var(--nf-font); letter-spacing: .12em; text-transform: uppercase;
  color: #dcdcdc;
  background: linear-gradient(#3a3a3a, #262626 46%, #1a1a1a 54%, #0e0e0e);
  border-bottom: 1px solid #000;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.08);
}

.nf-uinfo__grid { display: flex; align-items: stretch; }
.nf-uinfo__rows { flex: 1 1 auto; min-width: 0; padding: 4px 0; }

.nf-row { display: flex; gap: 14px; padding: 4px 14px; align-items: baseline; }
.nf-row:nth-child(odd) { background: #0b0b0b; }
.nf-row__label { flex: 0 0 96px; text-align: right; color: var(--nf-amber); font-weight: 700; }
.nf-row__value { flex: 1 1 auto; min-width: 0; color: var(--nf-text-strong); word-break: break-word; }
.nf-row__value--accent { color: var(--nf-amber); }

.nf-optlink { color: var(--nf-amber); }
.nf-optlink:hover { color: var(--nf-amber-hi); text-decoration: underline; }
.nf-optlink--danger { color: var(--nf-bad); }
.nf-optlink--danger:hover { color: #ff8a7a; text-decoration: underline; }
.nf-opt-sep { color: var(--nf-faint); margin: 0 2px; }

.nf-state-alive { color: var(--nf-good); font-weight: 700; }
.nf-state-dead { color: var(--nf-bad); font-weight: 700; }

.nf-uinfo__media { flex: 0 0 220px; display: flex; flex-direction: column; gap: 6px;
                   padding: 10px; border-left: 1px solid var(--nf-line); }

/* PORTRAIT QUALITY. This was a 176px box with a hard height of 190px, so an
   800x1000 upload was scaled down AND cropped top and bottom to fit a shape it
   never had. aspect-ratio 4/5 is the shape a portrait photo actually is, so the
   same upload now lands in the box whole.

   NO FILTER. There was a grayscale(1) contrast(1.12) brightness(.95) grade
   here, inherited from the black-and-white portrait on the card this layout
   came from. It was throwing away the player's picture: whatever they upload
   is what should be on their profile, and a page that quietly recolours it is
   showing them somebody else's idea of their portrait. The noir look on this
   site comes from the frame around the picture, not from repainting it. */
/* NO FILL AND NO BORDER, so the picture is the only thing on the card.
   Both used to be here and both were fighting the artwork: background:#000 was
   a shade off the panel (#0d0d0d), so transparent PNGs sat on a black slab
   instead of on the card, and the 1px black outline drew a hard rectangle
   around a picture whose own edges are already the interesting shape. Whatever
   is uploaded now meets the card directly. */
.nf-uinfo__portrait { border: 0; background: transparent; aspect-ratio: 4 / 5; }
.nf-uinfo__portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }
.nf-uinfo__setlink { display: block; text-align: center; font-size: var(--nf-fs-small); color: var(--nf-amber); }
.nf-uinfo__setlink:hover { color: var(--nf-amber-hi); text-decoration: underline; }

.nf-uinfo__foot { border-top: 1px solid var(--nf-line); padding: 4px 0; }
.nf-uinfo__foot .nf-row__label { flex-basis: 110px; }
.nf-uinfo__foot .nf-bar-track { margin-top: 4px; }

@media (max-width: 767px) {
  .nf-uinfo__grid { flex-direction: column; }
  .nf-uinfo__media { border-left: none; border-top: 1px solid var(--nf-line); flex-basis: auto; }
  .nf-row__label { flex-basis: 84px; }
}

/* a whole row greyed out - a field that exists in the layout but is not built
   yet (crew, crewrank, forum threads), matching the "greyed means not built"
   convention the right-hand menu already uses. */
.nf-row--soon .nf-row__label { color: var(--nf-faint); }
.nf-row--soon .nf-row__value { color: var(--nf-disabled); font-style: italic; }

/* ---------------------------------------------------------------- settings
   The portrait uploader. Two halves: the picture as the profile will actually
   show it, and the controls beside it. The preview deliberately reuses
   .nf-uinfo__portrait rather than styling its own box - if the two ever drift,
   the player is choosing a picture by looking at something that is not what
   they will get. */
.nf-set { display: flex; gap: 24px; align-items: flex-start; flex-wrap: wrap; }
.nf-set__preview { flex: 0 0 200px; }
.nf-set__caption { margin-top: var(--nf-2); font-size: var(--nf-fs-small); color: var(--nf-muted); text-align: center; }
.nf-set__form { flex: 1 1 320px; min-width: 0; }
.nf-set__title { font: 700 var(--nf-fs-lead)/1.2 var(--nf-font); color: var(--nf-text-strong); margin-bottom: var(--nf-3); }
.nf-set__note { font-size: var(--nf-fs-base); color: var(--nf-muted); line-height: 1.6; margin: 0 0 var(--nf-5); }
.nf-set__row { display: flex; gap: var(--nf-3); align-items: center; flex-wrap: wrap; margin-bottom: var(--nf-3); }
.nf-set__actions { display: flex; gap: var(--nf-3); align-items: center; flex-wrap: wrap; margin-top: var(--nf-4); }

/* the plain file input - the whole uploader when there is no JavaScript */
.nf-file { flex: 1 1 200px; min-width: 0; font: 400 var(--nf-fs-base) var(--nf-font); color: var(--nf-text);
           background: var(--nf-input); border: 1px solid var(--nf-line-strong); padding: 6px 8px; }
.nf-file::file-selector-button { font: 700 var(--nf-fs-base) var(--nf-font); color: var(--nf-amber-ink);
           background: var(--nf-amber); border: 0; padding: 6px 10px; margin-right: 10px; cursor: pointer; }
.nf-file::file-selector-button:hover { background: var(--nf-amber-hi); }

/* THE DROP ZONE.
   ONE <input type="file"> exists, always - two inputs sharing name="avatar"
   would mean the browser submits whichever it likes and the upload becomes a
   coin toss. So the label below wraps the real input, and the difference
   between no-JS and JS is only how that label is DRESSED:

     no JS  - the label is a plain wrapper, the input looks like any other
              file field, and the decorative lines are hidden. It works.
     JS     - the label becomes the dashed zone, the input is stretched over it
              invisibly (so a click is still a real file-picker click and the
              keyboard still reaches it), and the lines below appear.

   Nothing ever promises a drag-and-drop that no script is listening for. */
.nf-drop__icon, .nf-drop__main, .nf-drop__hint, .nf-drop__name { display: none; }
.nf-js .nf-drop__icon,
.nf-js .nf-drop__main,
.nf-js .nf-drop__hint,
.nf-js .nf-drop__name { display: block; }

.nf-js .nf-drop {
  /* display:block is not cosmetic. This is a <label>, so it is inline by
     default, and an inline box makes a FRAGMENTED containing block for the
     absolutely positioned input below - the input collapsed to 18px wide and
     only that sliver opened the file picker. As a block it is one box, and
     the input can stretch across all of it. */
  display: block;
  position: relative; text-align: center; cursor: pointer;
  padding: var(--nf-6) var(--nf-4);
  border: 1px dashed var(--nf-line-strong);
  background: rgba(0, 0, 0, .25);
  transition: border-color .12s, background .12s;
}
.nf-js .nf-drop:hover { border-color: var(--nf-amber); background: rgba(240, 165, 0, .05); }
/* is-over is added while a file is actually being dragged across it */
.nf-js .nf-drop.is-over { border-color: var(--nf-amber); background: rgba(240, 165, 0, .10); }
.nf-js .nf-drop:focus-within { border-color: var(--nf-amber); outline: 1px solid var(--nf-amber); }
/* Stretched by inset alone - no width/height percentages, and the .nf-file
   padding and border zeroed, so the hit area is exactly the zone and not a
   few pixels past its edge. */
.nf-js .nf-drop input[type="file"] { position: absolute; inset: 0;
                                     width: auto; height: auto;
                                     margin: 0; padding: 0; border: 0;
                                     opacity: 0; cursor: pointer; }

.nf-drop__icon { font-size: 22px; color: var(--nf-amber); line-height: 1; }
.nf-drop__main { margin-top: var(--nf-2); font-weight: 700; color: var(--nf-text-strong); }
.nf-drop__hint { margin-top: 2px; font-size: var(--nf-fs-small); color: var(--nf-muted); }
.nf-drop__name { margin-top: var(--nf-3); font-size: var(--nf-fs-small); color: var(--nf-amber);
                 word-break: break-all; }

/* a panel heading for something destructive. Same shape as every other bar so
   the panel still belongs to the page - the colour is the only difference, and
   it is the whole point. */
.nf-bar--danger { background: var(--nf-bad-bg); border-color: var(--nf-bad-line); color: var(--nf-bad); }

/* ---------------------------------------------------------------- radio group
   Used for the gender choice on the registration and settings forms.
   .nf-field input is width:100% - right for a text box, and it would stretch a
   radio button across the whole form - so the width is put back here. The
   selector is deliberately more specific than .nf-field input so it wins
   without !important. */
.nf-radios { display: flex; gap: var(--nf-5); flex-wrap: wrap; padding: 2px 0; }
.nf-radios .nf-radio { display: inline-flex; align-items: center; gap: 7px;
                       cursor: pointer; color: var(--nf-text); font-weight: 400; }
.nf-radios .nf-radio input { width: auto; margin: 0; padding: 0;
                             accent-color: var(--nf-amber); cursor: pointer; }
.nf-radios .nf-radio:hover { color: var(--nf-text-strong); }

/* ---------------------------------------------------------------- user interface page */
.nf-uiform { display: flex; flex-direction: column; gap: var(--nf-5); align-items: flex-start; }
.nf-uiform__row { display: flex; align-items: center; gap: var(--nf-5); width: 100%; max-width: 460px; }
.nf-uiform__label { flex: 0 0 74px; font: 700 var(--nf-fs-label)/1 var(--nf-font);
                    letter-spacing: var(--nf-track-label); text-transform: uppercase; color: var(--nf-muted); }
/* the readout is monospace and fixed-width so the row does not jump about by a
   pixel every time the number goes from 9% to 10% while the slider is moving */
.nf-uiform__value { flex: 0 0 46px; text-align: right; font: 400 var(--nf-fs-lead)/1 var(--nf-mono); color: var(--nf-amber); }
.nf-uiform__row--marks { font-size: var(--nf-fs-small); color: var(--nf-faint); justify-content: space-between; }
.nf-uiform__row--marks span:first-child { flex: 0 0 74px; }
.nf-uiform__row--marks span:last-child { flex: 0 0 46px; text-align: right; }

.nf-range { flex: 1 1 auto; min-width: 0; accent-color: var(--nf-amber); height: 22px; cursor: pointer; }

/* green success message, sibling of .nf-msg--warn / --bad already in new-future.css */
.nf-msg--good { border-color: var(--nf-good-line); border-left-color: var(--nf-good); background: var(--nf-good-bg); }
.nf-msg--good .nf-msg__glyph { color: var(--nf-good); }
.nf-event__title { font: 700 var(--nf-fs-body)/1.3 var(--nf-font); color: var(--nf-text); }
.nf-event__title--card { margin-top: var(--nf-4); }
.nf-event__body { font-size: var(--nf-fs-base); color: var(--nf-muted); margin-top: 3px; }
.nf-event__reward { font-size: var(--nf-fs-base); color: var(--nf-muted); }
.nf-tr--current td { padding-top: var(--nf-5); padding-bottom: var(--nf-5); }
.nf-tr--current .nf-event__title { color: var(--nf-text-strong); }
.nf-tr--current .nf-event__body { color: #c9bb98; }
.nf-tr--locked .nf-event__title { color: #6a655c; }
.nf-tr--locked .nf-event__body,
.nf-tr--locked .nf-event__reward { color: var(--nf-disabled); }
.nf-event__xp-required { font-size: var(--nf-fs-small); color: var(--nf-amber); margin-bottom: var(--nf-2); }

.nf-progress-block { border: 1px solid var(--nf-line); border-left: 4px solid var(--nf-amber); background: var(--nf-amber-bg);
                     padding: var(--nf-5) var(--nf-6); display: flex; flex-wrap: wrap; gap: var(--nf-7); }
.nf-progress-block > div { flex: 1 1 140px; min-width: 0; }
.nf-progress-block__wide { flex: 2 1 240px; }
.nf-progress-block__value { font-size: var(--nf-fs-lead); color: var(--nf-text); margin-top: 5px; }
.nf-progress-block__bar { margin-top: var(--nf-3); }

/* ---------------------------------------------------------------- phone corrections
   Two things the design asks for that the base stylesheet does not quite
   reach, both about the size of a thing you tap: the tab bar and the button
   inside a card are specified as 44px tall targets, and the padding alone
   lands them at 31px and 34px. min-height with the label centred gets there
   without changing how either looks on a desktop.

   The third rule is about the phone layout of the left column: the tab bar
   replaces the link lists there, so the section headings above those lists
   have to go with them. Bar and list are one .nf-navgroup for that reason. */
@media (max-width: 767px) {
  .nf-tabbar a { min-height: 44px; display: flex; align-items: center; justify-content: center; }
  .nf-card .nf-btn,
  .nf-card .nf-locked { min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
  .nf-shell__left .nf-navgroup { display: none; }
}
/* the stat block and the menu groups are flex items on a phone, so they need a
   place in the order: stats first, everything else after it */
@media (max-width: 767px) {
  .nf-shell__left .nf-statsblock { order: 1; }
  .nf-shell__left .nf-navgroup { order: 3; }
}

/* ---------------------------------------------------------------- side-column blocks
   Each menu section in the two side columns is drawn as a BLOCK: an outlined
   card with a tinted header strip, an accent stripe down the left edge of that
   strip, and the links sitting inside the outline underneath.

   Only inside .nf-shell__left / .nf-shell__right. Everywhere else a .nf-bar is
   still the solid amber strip the designer drew - the section heads on the
   crime table, the panels, the login card. Those are headings ON something;
   these are headings OF something, and the block is what makes that read.

   The two columns are not built the same and this has to cover both: the left
   wraps each bar and list in .nf-navgroup (the phone layout needs them to move
   together), the right leaves them as bare siblings. So the outline is drawn
   on the bar and the list rather than on a wrapper only one column has - the
   bar closes the top, the list closes the bottom, and they meet in the middle.

   Colours are the existing tokens: amber ink and an amber-tinted near-black,
   nothing new introduced. */
.nf-shell__left .nf-bar,
.nf-shell__right .nf-bar {
  background: linear-gradient(#17140d, #110f0a);
  color: var(--nf-amber);
  border: 1px solid var(--nf-line);
  border-left: 3px solid var(--nf-amber);
  border-bottom-color: var(--nf-amber-line);
  padding: 7px var(--nf-4);
  margin-top: var(--nf-4);
  align-items: center;
}
/* the list is the body of the block: same outline, closed off at the bottom */
.nf-shell__left .nf-nav,
.nf-shell__right .nf-nav {
  background: rgba(8, 8, 8, 0.55);
  border: 1px solid var(--nf-line);
  border-top: 0;
  border-left-width: 3px;
  padding: var(--nf-2) 0 var(--nf-3);
}
/* .nf-navgroup already groups the pair on the left, so the gap belongs to the
   wrapper there - otherwise the bar's own margin doubles up inside it. */
.nf-shell__left .nf-navgroup { margin-top: var(--nf-4); }
.nf-shell__left .nf-navgroup .nf-bar { margin-top: 0; }
/* the Player stat block reads as a block too, but it is not a nav list */
.nf-shell__left .nf-statsblock .nf-bar { margin-top: 0; }
.nf-shell__left .nf-stats {
  background: rgba(8, 8, 8, 0.55);
  border: 1px solid var(--nf-line);
  border-top: 0;
  border-left-width: 3px;
}
/* the greyed "not built yet" sections keep the shape, lose the amber */
.nf-shell__left .nf-bar--muted,
.nf-shell__right .nf-bar--muted {
  background: linear-gradient(#161616, #101010);
  color: var(--nf-muted);
  border-left-color: var(--nf-line-strong);
  border-bottom-color: var(--nf-line-strong);
}

/* The collapse toggle.
   app.js ADDS this button; it is not in the HTML. That is the same
   progressive-enhancement rule the rest of the file follows - with JavaScript
   off there is no minus sign promising something that cannot happen, and every
   section simply stays open. Which section is shut is remembered in
   localStorage, because every click in this game is a page load and a menu
   that springs back open on each one is worse than no toggle at all. */
.nf-bar__toggle {
  background: none;
  border: 0;
  padding: 0 0 0 var(--nf-3);
  margin: 0;
  cursor: pointer;
  color: inherit;
  font: inherit;
  line-height: 1;
  opacity: .75;
}
.nf-bar__toggle:hover { opacity: 1; }
/* The unread total, shown on the heading only while the section is shut. Same
   red and monospace as the counts in the list it is standing in for. Empty
   when there is nothing unread, and margin-left:auto pushes it over to sit
   next to the toggle rather than being spread out by the bar's
   space-between. */
.nf-bar__count {
  margin-left: auto;
  font-family: var(--nf-mono);
  color: #d32f2f;
  letter-spacing: 0;
}
.nf-bar__count:empty { display: none; }
.nf-bar__toggle::before { content: "\2212"; }              /* minus: section open */
.is-collapsed .nf-bar__toggle::before { content: "\002B"; } /* plus: section shut */
.nf-nav.is-hidden { display: none; }
/* a shut section is just the header, so it closes itself off at the bottom */
.nf-shell__left .nf-bar.is-collapsed,
.nf-shell__right .nf-bar.is-collapsed { border-bottom-color: var(--nf-line); }

/* ---------------------------------------------------------------- reorder mode
   Rearranging happens on the MENU, not on a settings page: with the mode on,
   every group in either column grows a handle you can pick it up by and drop
   anywhere in either column. With the mode off none of this markup exists, so
   the ordinary menu pays nothing for the feature.

   .nf-menucol is always present and is the drop target; it only looks like one
   while .is-reordering is on it. */
.nf-menucol.is-reordering { outline: 1px dashed var(--nf-line-strong); outline-offset: -2px; min-height: 120px; }
.nf-menucol.is-over { outline-color: var(--nf-amber); background: rgba(240, 165, 0, .04); }

.nf-drag { position: relative; }
.nf-drag.is-dragging { opacity: .4; }
.nf-js .nf-drag { cursor: grab; }
.nf-js .nf-drag:active { cursor: grabbing; }

/* the handle above each group while reordering */
.nf-drag__bar { display: flex; align-items: center; gap: var(--nf-2); padding: 3px var(--nf-3);
                margin-top: var(--nf-4); background: #17140d; border: 1px solid var(--nf-amber-line);
                border-bottom: 0; }
.nf-drag__grip { color: var(--nf-faint); letter-spacing: -3px; font-size: 12px; }
.nf-drag__name { flex: 1 1 auto; min-width: 0; font: 700 var(--nf-fs-micro)/1.6 var(--nf-font);
                 letter-spacing: .1em; text-transform: uppercase; color: var(--nf-amber); }
/* the group's own bar already has the gap; the handle supplies it now */
.nf-drag .nf-navgroup { margin-top: 0; }
.nf-drag .nf-navgroup .nf-bar { margin-top: 0; }

/* the no-JavaScript controls: real buttons that post one step, hidden once
   app.js loads and dragging takes over */
.nf-drag__btns { display: flex; gap: 2px; }
.nf-js .nf-drag__btns { display: none; }
.nf-drag__btns button { background: #151515; border: 1px solid var(--nf-line-strong); color: var(--nf-muted);
                        font: 400 9px/1 var(--nf-font); padding: 3px 5px; cursor: pointer; }
.nf-drag__btns button:hover { border-color: var(--nf-amber); color: var(--nf-amber); }

/* the on/off switch on the User Interface page */
.nf-toggle { display: flex; gap: var(--nf-6); align-items: center; flex-wrap: wrap; }
.nf-toggle__main { flex: 1 1 320px; min-width: 0; }

/* ---------------------------------------------------------------- career (the rank ladder)
   Was the events page: eighty ranks in one flat table, most of it carrying
   nothing. Now a service record - the figures first, then the ranks folded into
   tiers that open. */
.nf-career__stats { display: flex; flex-wrap: wrap; gap: var(--nf-8); }
.nf-career__figure { font: 400 22px/1.1 var(--nf-font); color: var(--nf-text-strong); margin-top: 3px; }
.nf-career__figure span { font-size: var(--nf-fs-body); color: var(--nf-faint); }
.nf-career__next { margin-top: var(--nf-7); }
.nf-career__next .nf-statline b { color: var(--nf-text-strong); font-weight: 400; }
.nf-career__next .nf-bar-track { margin-top: 5px; }

/* a tier. <details>, so it folds with scripting off */
.nf-tier { border: 1px solid var(--nf-line); background: var(--nf-panel); }
.nf-tier + .nf-tier { border-top: 0; }
.nf-tier--now { border-color: var(--nf-amber-line); }

.nf-tier__head { display: flex; align-items: center; gap: var(--nf-5); padding: 11px var(--nf-6);
                 cursor: pointer; list-style: none; }
/* the default disclosure triangle, replaced with one that matches the menus */
.nf-tier__head::-webkit-details-marker { display: none; }
.nf-tier__head::after { content: "+"; margin-left: var(--nf-2); color: var(--nf-faint); font: 400 13px/1 var(--nf-mono); }
.nf-tier[open] .nf-tier__head::after { content: "\2212"; }
.nf-tier__head:hover { background: #131108; }

.nf-tier__name { flex: 1 1 auto; min-width: 0; font: 700 var(--nf-fs-label)/1.3 var(--nf-font);
                 letter-spacing: var(--nf-track-label); text-transform: uppercase; color: var(--nf-text); }
.nf-tier--done .nf-tier__name { color: var(--nf-muted); }
.nf-tier--now  .nf-tier__name { color: var(--nf-amber); }
.nf-tier__range { color: var(--nf-faint); font-weight: 400; letter-spacing: 0; }

.nf-tier__track { flex: 0 0 130px; display: block; height: 8px; background: #171717;
                  border: 1px solid var(--nf-line-strong); overflow: hidden; }
.nf-tier__fill  { display: block; height: 100%; background: var(--nf-amber); }
.nf-tier--done .nf-tier__fill { background: var(--nf-good); }
.nf-tier__count { flex: 0 0 46px; text-align: right; font: 400 var(--nf-fs-small)/1 var(--nf-mono); color: var(--nf-muted); }

.nf-tier__body { border-top: 1px solid var(--nf-line); padding: var(--nf-5) var(--nf-6) var(--nf-6); }
.nf-tier__blurb { margin: 0 0 var(--nf-5); font-size: var(--nf-fs-base); color: var(--nf-muted); }

/* one rank */
.nf-rankrow { display: flex; gap: var(--nf-5); align-items: baseline; padding: 7px var(--nf-4);
              border-left: 2px solid transparent; }
.nf-rankrow:nth-child(odd) { background: #0b0b0b; }
.nf-rankrow--done { color: var(--nf-muted); }
.nf-rankrow--todo .nf-rankrow__name { color: var(--nf-disabled); }
.nf-rankrow--now  { border-left-color: var(--nf-amber); background: #141002; }

.nf-rankrow__nr { flex: 0 0 30px; text-align: right; font: 400 var(--nf-fs-small)/1.5 var(--nf-mono); color: var(--nf-faint); }
.nf-rankrow--now .nf-rankrow__nr { color: var(--nf-amber); }
.nf-rankrow__main { flex: 1 1 auto; min-width: 0; }
.nf-rankrow__name { font-size: var(--nf-fs-body); color: var(--nf-text); }
.nf-rankrow--now .nf-rankrow__name { color: var(--nf-text-strong); font-weight: 700; }
.nf-rankrow__blurb { display: block; font-size: var(--nf-fs-base); color: var(--nf-faint); margin-top: 2px; }
.nf-rankrow__here { font: 700 var(--nf-fs-micro)/1 var(--nf-font); letter-spacing: .12em; text-transform: uppercase;
                    color: var(--nf-amber); margin-left: var(--nf-3); }
.nf-rankrow__xp { flex: 0 0 auto; font: 400 var(--nf-fs-small)/1.5 var(--nf-mono); color: var(--nf-faint); white-space: nowrap; }

/* the reward chip, on the ten ranks that actually unlock a crime */
.nf-unlockchip { display: inline-block; margin-left: var(--nf-3); padding: 2px 6px;
                 font: 700 var(--nf-fs-micro)/1.5 var(--nf-font); letter-spacing: .08em; text-transform: uppercase;
                 border: 1px solid var(--nf-amber-line); background: #100d05; color: var(--nf-amber); }

@media (max-width: 767px) {
  .nf-career__stats { gap: var(--nf-6); }
  .nf-tier__head { flex-wrap: wrap; gap: var(--nf-3); }
  .nf-tier__track { flex: 1 1 100%; order: 3; }
  .nf-rankrow { flex-wrap: wrap; gap: var(--nf-3); }
  .nf-rankrow__xp { flex: 1 1 100%; padding-left: 44px; }
}

/* ---------------------------------------------------------------- admin panel
   Staff screens borrow the game's own components - .nf-panel, .nf-table,
   .nf-field, .nf-btn - so the panel looks like the rest of the game rather than
   a bolted-on tool. What is here is only what had no equivalent already.

   Red, and only here: amber is the game's own colour and means "a thing you can
   do". Red in this palette already means danger (see --nf-bad), which is the
   right register for screens that can edit somebody's money. */
.nf-bar--admin {
  background: linear-gradient(#2a1210, #1c0c0b);
  color: var(--nf-bad);
  border-left: 3px solid var(--nf-bad);
  border-bottom-color: var(--nf-bad-line);
}
.nf-shell__left .nf-navgroup--admin .nf-nav { border-left-color: var(--nf-bad-line); }

.nf-admin-tabs { display: flex; flex-wrap: wrap; gap: 1px; background: var(--nf-line); }
.nf-admin-tabs a { flex: 1 1 auto; text-align: center; padding: 8px var(--nf-3); background: var(--nf-panel);
                   font: 700 var(--nf-fs-label)/1 var(--nf-font); letter-spacing: .1em; text-transform: uppercase;
                   color: var(--nf-muted); }
.nf-admin-tabs a:hover { background: #151208; color: var(--nf-amber); text-decoration: none; }
.nf-admin-tabs a.is-active { background: var(--nf-bad-bg); color: var(--nf-bad); }

/* a form row that mixes inputs and a button, wrapping on a narrow screen */
.nf-admin-row { display: flex; flex-wrap: wrap; gap: var(--nf-3); align-items: center; margin-bottom: var(--nf-4); }
.nf-admin-row:last-child { margin-bottom: 0; }
.nf-admin-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--nf-5); }

/* inputs and selects, matching .nf-field input from new-future.css */
.nf-input-sm, .nf-input-lg, .nf-input-xs,
.nf-admin-row select, .nf-uiform select, .nf-field select {
  background: var(--nf-input); border: 1px solid var(--nf-line-strong); color: var(--nf-text);
  font: 400 var(--nf-fs-body)/1 var(--nf-font); padding: 8px var(--nf-3);
}
.nf-input-sm { width: 190px; max-width: 100%; }
.nf-input-xs { width: 70px; max-width: 100%; }
.nf-input-lg { flex: 1 1 320px; min-width: 0; }
.nf-input-sm:focus, .nf-input-lg:focus, .nf-input-xs:focus, select:focus { outline: none; border-color: var(--nf-amber); background: #151208; }

/* A block, not a flex row. As a flex container the bare text node became a
   shrinkable flex item and wrapped into a narrow column beside the dot; as a
   block it simply reads as a line with a control on the front of it. */
.nf-radio { display: block; font-size: var(--nf-fs-body); color: var(--nf-text);
            padding: 4px 0; cursor: pointer; }
.nf-radio input { accent-color: var(--nf-amber); margin-right: var(--nf-3); vertical-align: middle; }

.nf-hr { border: 0; border-top: 1px solid var(--nf-line); margin: var(--nf-6) 0; }

/* the staff and suspended badges, used in the player list, the online list,
   the leaderboard and on a profile */
.nf-badge-admin, .nf-badge-banned {
  display: inline-block; margin-left: var(--nf-2); padding: 1px 5px;
  font: 700 var(--nf-fs-micro)/1.6 var(--nf-font); letter-spacing: .1em; text-transform: uppercase;
}
.nf-badge-admin  { background: var(--nf-bad-bg); border: 1px solid var(--nf-bad-line); color: var(--nf-bad); }
.nf-badge-banned { background: #151515; border: 1px solid var(--nf-line-strong); color: var(--nf-faint); }

/* A staff member's NAME, in the same red as their badge, wherever it is
   printed - the online strip, the player list, a profile, the leaderboard. The
   badge says what they are; colouring the name means you spot them in a list
   without reading it. The `a.` rule is needed where the name is a link, since
   .nf-nav a and the base link colour are both amber and would otherwise win. */
.nf-name-admin,
a.nf-name-admin { color: var(--nf-bad); }
a.nf-name-admin:hover { color: #ff8a7a; }

/* ---------------------------------------------------------------- unread counter
   How many events you have not looked at, printed as (2) next to the menu item.
   It replaced a red dot: that dot was 8px square inside `.nf-nav a`, which is a
   flex row, so with no align-items it stretched to the full line height and
   rendered as a fat red ellipse.

   Amber and not red, because red in this palette means something went wrong,
   and a rank you have not read about yet is good news. Monospace so the
   brackets sit tight around the number, and the whole thing rides along with
   the link colour on hover instead of standing apart from it.

   TWO RULES IN THE MENU HAD TO BE BEATEN, both aimed at a <span> that is a
   menu item in its own right (the greyed-out "not built yet" entries in the
   right-hand column):

     .nf-nav span         { color: var(--nf-disabled); }   makes it grey
     .nf-nav span::before { content: "-"; }                puts a dash in front

   A counter is a span too, so it inherited both: grey instead of amber, and
   printed as "Events -(3)". Hence the longer selectors and the explicit
   content:none. Measured before this: rgb(79,75,69), which is --nf-disabled. */
.nf-nav a .nf-nav__count,
.nf-tabbar a .nf-nav__count { font-family: var(--nf-mono); color: #d32f2f; margin-left: -4px; margin-top: -4px; vertical-align: super; }
.nf-nav a .nf-nav__count--ready,
.nf-tabbar a .nf-nav__count--ready { color: #4caf50; }
.nf-nav a:hover .nf-nav__count--ready { color: #66bb6a; }
.nf-tabbar a.is-active .nf-nav__count--ready { color: #66bb6a; }
.nf-nav a .nf-nav__count--timer,
.nf-tabbar a .nf-nav__count--timer { color: #d32f2f; }
.nf-nav a:hover .nf-nav__count--timer { color: #ff5252; }
.nf-tabbar a.is-active .nf-nav__count--timer { color: #ff5252; }
.nf-nav a .nf-nav__count::before,
.nf-tabbar a .nf-nav__count::before { content: none; }
.nf-nav a:hover .nf-nav__count { color: #ff5252; }
.nf-tabbar a.is-active .nf-nav__count { color: #ff5252; }

/* ---------------------------------------------------------------- login, register */
.nf-auth__body { padding: var(--nf-7) 20px 20px; display: flex; flex-direction: column; gap: var(--nf-5); }
.nf-auth__body--center { text-align: center; align-items: center; }
.nf-auth__body--center p { margin: 0; font-size: var(--nf-fs-body); color: var(--nf-muted); }
.nf-auth__body--center p b { color: var(--nf-text-strong); }
.nf-auth__foot { font-size: var(--nf-fs-base); color: var(--nf-muted); text-align: center; margin: 0; }
.nf-bar--good { background: var(--nf-good); }
.nf-ok { width: 44px; height: 44px; border: 1px solid var(--nf-good-line); background: var(--nf-good-bg);
         color: var(--nf-good); font: 700 var(--nf-fs-body)/42px var(--nf-font); text-align: center; }
/* the "Go to login" button is a link, so it needs the link styling undone */
a.nf-btn, a.nf-btn:hover { color: var(--nf-amber-ink); text-decoration: none; }
.nf-btn--block { box-sizing: border-box; }
